diff --git a/CUE4Parse b/CUE4Parse index 9b7bb065..e69c4c17 160000 --- a/CUE4Parse +++ b/CUE4Parse @@ -1 +1 @@ -Subproject commit 9b7bb06564cff6137dc17b6635d86b220f8a0a3b +Subproject commit e69c4c17cac920a0be25e531562c5cda9e482628 diff --git a/FModel/MainWindow.xaml.cs b/FModel/MainWindow.xaml.cs index 191079d2..8bd01cea 100644 --- a/FModel/MainWindow.xaml.cs +++ b/FModel/MainWindow.xaml.cs @@ -75,15 +75,15 @@ public partial class MainWindow _discordHandler.Initialize(_applicationView.CUE4Parse.Game); #if DEBUG + await _threadWorkerView.Begin(cancellationToken => + _applicationView.CUE4Parse.Extract(cancellationToken, + "MoonMan/Content/DeliverUsTheMoon/Characters/Astronaut/SK_Astronaut.uasset")); + await _threadWorkerView.Begin(cancellationToken => + _applicationView.CUE4Parse.Extract(cancellationToken, + "MoonMan/Content/DeliverUsTheMoon/Characters/Astronaut/AM_ControllingASE.uasset")); // await _threadWorkerView.Begin(cancellationToken => // _applicationView.CUE4Parse.Extract(cancellationToken, - // "FortniteGame/Content/Characters/Player/Female/Medium/Bodies/F_MED_RoseDust/Meshes/F_MED_RoseDust.uasset")); - // await _threadWorkerView.Begin(cancellationToken => - // _applicationView.CUE4Parse.Extract(cancellationToken, - // "fortnitegame/Content/Accessories/FORT_Backpacks/Backpack_M_MED_Despair/Meshes/M_MED_Despair_Pack.uasset")); - // await _threadWorkerView.Begin(cancellationToken => - // _applicationView.CUE4Parse.Extract(cancellationToken, - // "FortniteGame/Content/Animation/Game/MainPlayer/Emotes/Acrobatic_Superhero/Emote_AcrobaticSuperhero_CMM.uasset")); + // "Hk_project/Content/Animation/CAT/Jump/CAT_JUMP_L050_H-050.uasset")); #endif } diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index 0192dd02..dd44ba00 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -816,9 +816,10 @@ public class CUE4ParseViewModel : ViewModel SnooperViewer.Run(); return true; } - case UAnimSequence a when isNone && ModelIsWaitingAnimation: + case UAnimSequence when isNone && ModelIsWaitingAnimation: + case UAnimMontage when isNone && ModelIsWaitingAnimation: { - SnooperViewer.Renderer.Animate(a); + SnooperViewer.Renderer.Animate(export); SnooperViewer.Run(); return true; } diff --git a/FModel/Views/Snooper/Models/Animations/Animation.cs b/FModel/Views/Snooper/Models/Animations/Animation.cs index f0da035f..08db9876 100644 --- a/FModel/Views/Snooper/Models/Animations/Animation.cs +++ b/FModel/Views/Snooper/Models/Animations/Animation.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Numerics; using CUE4Parse_Conversion.Animations; using CUE4Parse.Utils; @@ -9,96 +8,50 @@ namespace FModel.Views.Snooper.Models.Animations; public class Animation : IDisposable { - public int Frame; public float ElapsedTime; - public readonly int MaxFrame; - public readonly float FramesPerSecond; - public readonly Dictionary TrackIndexByBoneIndex; - public readonly Transform[][] BoneTransforms; - private float TimePerFrame => 1.0f / FramesPerSecond; + public int CurrentSequence; + public readonly Sequence[] Sequences; + public int SequencesCount => Sequences.Length; - public Animation(Skeleton skeleton, CAnimSet anim, bool rotationOnly) + public Animation() { - Frame = 0; ElapsedTime = 0; - TrackIndexByBoneIndex = new Dictionary(); + Sequences = Array.Empty(); + } - var sequence = anim.Sequences[0]; - MaxFrame = sequence.NumFrames; - FramesPerSecond = sequence.Rate; - - BoneTransforms = new Transform[skeleton.UnrealSkeleton.ReferenceSkeleton.FinalRefBoneInfo.Length][]; - for (var trackIndex = 0; trackIndex < BoneTransforms.Length; trackIndex++) + public Animation(Skeleton skeleton, CAnimSet anim, bool rotationOnly) : this() + { + Sequences = new Sequence[anim.Sequences.Count]; + for (int i = 0; i < Sequences.Length; i++) { - BoneTransforms[trackIndex] = new Transform[MaxFrame]; - - var bone = skeleton.UnrealSkeleton.ReferenceSkeleton.FinalRefBoneInfo[trackIndex]; - if (!skeleton.BonesIndexByLoweredName.TryGetValue(bone.Name.Text.ToLower(), out var boneIndex)) - continue; - if (!skeleton.BonesTransformByIndex.TryGetValue(boneIndex, out var originalTransform)) - throw new ArgumentNullException($"no transform for bone '{boneIndex}'"); - - TrackIndexByBoneIndex[boneIndex] = trackIndex; - - for (var frame = 0; frame < BoneTransforms[trackIndex].Length; frame++) - { - var boneOrientation = originalTransform.Rotation; - var bonePosition = originalTransform.Position; - var boneScale = originalTransform.Scale; - - sequence.Tracks[trackIndex].GetBonePosition(frame, MaxFrame, false, ref bonePosition, ref boneOrientation); - if (frame < sequence.Tracks[trackIndex].KeyScale.Length) - boneScale = sequence.Tracks[trackIndex].KeyScale[frame]; - - // revert FixRotationKeys - if (trackIndex > 0) boneOrientation.Conjugate(); - - bonePosition *= Constants.SCALE_DOWN_RATIO; - - switch (anim.BoneModes[trackIndex]) - { - case EBoneRetargetingMode.Animation: - case EBoneRetargetingMode.Mesh: - case EBoneRetargetingMode.AnimationScaled: - case EBoneRetargetingMode.AnimationRelative: - case EBoneRetargetingMode.OrientAndScale: - case EBoneRetargetingMode.Count: - default: - break; - } - - BoneTransforms[trackIndex][frame] = new Transform - { - Relation = bone.ParentIndex >= 0 ? BoneTransforms[bone.ParentIndex][frame].Matrix : originalTransform.Relation, - Rotation = boneOrientation, - Position = rotationOnly ? originalTransform.Position : bonePosition, - Scale = boneScale - }; - } + Sequences[i] = new Sequence(anim.Sequences[i], skeleton, rotationOnly); } } public void Update(float deltaSeconds) { - ElapsedTime += deltaSeconds / TimePerFrame; - Frame = ElapsedTime.FloorToInt() % MaxFrame; + ElapsedTime += deltaSeconds / Sequences[CurrentSequence].TimePerFrame; + Sequences[CurrentSequence].Frame = ElapsedTime.FloorToInt() % Sequences[CurrentSequence].MaxFrame; } public Matrix4x4 InterpolateBoneTransform(int trackIndex) { // interpolate here - return BoneTransforms[trackIndex][Frame].Matrix; + return Sequences[CurrentSequence].BonesTransform[trackIndex][Sequences[CurrentSequence].Frame].Matrix; } public void ImGuiTimeline() { - ImGui.Text($"Frame: {Frame}/{MaxFrame}"); - ImGui.Text($"FPS: {FramesPerSecond}"); + ImGui.BeginDisabled(SequencesCount < 2); + ImGui.DragInt("Sequence", ref CurrentSequence, 1, 0, Sequences.Length - 1, Sequences[CurrentSequence].Name); + ImGui.EndDisabled(); + ImGui.Text($"Frame: {Sequences[CurrentSequence].Frame}/{Sequences[CurrentSequence].MaxFrame}"); + ImGui.Text($"FPS: {Sequences[CurrentSequence].FramesPerSecond}"); } public void Dispose() { - TrackIndexByBoneIndex.Clear(); + } } diff --git a/FModel/Views/Snooper/Models/Animations/Sequence.cs b/FModel/Views/Snooper/Models/Animations/Sequence.cs new file mode 100644 index 00000000..a4ecf7d6 --- /dev/null +++ b/FModel/Views/Snooper/Models/Animations/Sequence.cs @@ -0,0 +1,76 @@ +using System; +using CUE4Parse_Conversion.Animations; + +namespace FModel.Views.Snooper.Models.Animations; + +public class Sequence : IDisposable +{ + public int Frame; + public readonly string Name; + public readonly int MaxFrame; + public readonly float FramesPerSecond; + + public float TimePerFrame => 1.0f / FramesPerSecond; + + public readonly Transform[][] BonesTransform; + + public Sequence(CAnimSequence sequence, Skeleton skeleton, bool rotationOnly) + { + Frame = 0; + Name = sequence.Name; + MaxFrame = sequence.NumFrames; + FramesPerSecond = sequence.Rate; + + BonesTransform = new Transform[skeleton.BonesTransformByIndex.Count][]; + for (int trackIndex = 0; trackIndex < skeleton.UnrealSkeleton.ReferenceSkeleton.FinalRefBoneInfo.Length; trackIndex++) + { + var bone = skeleton.UnrealSkeleton.ReferenceSkeleton.FinalRefBoneInfo[trackIndex]; + if (!skeleton.BonesIndicesByLoweredName.TryGetValue(bone.Name.Text.ToLower(), out var boneIndices)) + continue; + + var originalTransform = skeleton.BonesTransformByIndex[boneIndices.Index]; + + BonesTransform[boneIndices.Index] = new Transform[MaxFrame]; + for (int frame = 0; frame < BonesTransform[boneIndices.Index].Length; frame++) + { + var boneOrientation = originalTransform.Rotation; + var bonePosition = originalTransform.Position; + var boneScale = originalTransform.Scale; + + sequence.Tracks[trackIndex].GetBonePosition(frame, MaxFrame, false, ref bonePosition, ref boneOrientation); + if (frame < sequence.Tracks[trackIndex].KeyScale.Length) + boneScale = sequence.Tracks[trackIndex].KeyScale[frame]; + + // revert FixRotationKeys + if (trackIndex > 0) boneOrientation.Conjugate(); + + bonePosition *= Constants.SCALE_DOWN_RATIO; + + // switch (boneModes[trackIndex]) + // { + // case EBoneRetargetingMode.Animation: + // case EBoneRetargetingMode.Mesh: + // case EBoneRetargetingMode.AnimationScaled: + // case EBoneRetargetingMode.AnimationRelative: + // case EBoneRetargetingMode.OrientAndScale: + // case EBoneRetargetingMode.Count: + // default: + // break; + // } + + BonesTransform[boneIndices.Index][frame] = new Transform + { + Relation = boneIndices.ParentIndex >= 0 ? BonesTransform[boneIndices.ParentIndex][frame].Matrix : originalTransform.Relation, + Rotation = boneOrientation, + Position = rotationOnly ? originalTransform.Position : bonePosition, + Scale = boneScale + }; + } + } + } + + public void Dispose() + { + throw new NotImplementedException(); + } +} diff --git a/FModel/Views/Snooper/Models/Animations/Skeleton.cs b/FModel/Views/Snooper/Models/Animations/Skeleton.cs index 92f23acf..6ec5a77b 100644 --- a/FModel/Views/Snooper/Models/Animations/Skeleton.cs +++ b/FModel/Views/Snooper/Models/Animations/Skeleton.cs @@ -5,24 +5,31 @@ using CUE4Parse_Conversion.Animations; using CUE4Parse.UE4.Assets.Exports.Animation; using CUE4Parse.UE4.Objects.UObject; using FModel.Views.Snooper.Shading; +using Serilog; namespace FModel.Views.Snooper.Models.Animations; +public struct BoneIndice +{ + public int Index; + public int ParentIndex; +} + public class Skeleton : IDisposable { public readonly USkeleton UnrealSkeleton; - public readonly FReferenceSkeleton ReferenceSkeleton; - public readonly Dictionary BonesIndexByLoweredName; + public readonly bool IsLoaded; + + public readonly Dictionary BonesIndicesByLoweredName; public readonly Dictionary BonesTransformByIndex; public readonly Dictionary InvertedBonesMatrixByIndex; - public readonly bool IsLoaded; public Animation Anim; public bool HasAnim => Anim != null; public Skeleton() { - BonesIndexByLoweredName = new Dictionary(); + BonesIndicesByLoweredName = new Dictionary(); BonesTransformByIndex = new Dictionary(); InvertedBonesMatrixByIndex = new Dictionary(); } @@ -33,16 +40,25 @@ public class Skeleton : IDisposable IsLoaded = UnrealSkeleton != null; if (!IsLoaded) return; - ReferenceSkeleton = referenceSkeleton; - foreach ((var name, var boneIndex) in referenceSkeleton.FinalNameToIndexMap) - BonesIndexByLoweredName[name.ToLower()] = boneIndex; - - foreach (var boneIndex in BonesIndexByLoweredName.Values) + for (int boneIndex = 0; boneIndex < referenceSkeleton.FinalRefBoneInfo.Length; boneIndex++) { - var bone = ReferenceSkeleton.FinalRefBonePose[boneIndex]; - var parentIndex = ReferenceSkeleton.FinalRefBoneInfo[boneIndex].ParentIndex; + var info = referenceSkeleton.FinalRefBoneInfo[boneIndex]; + BonesIndicesByLoweredName[info.Name.Text.ToLower()] = new BoneIndice { Index = boneIndex, ParentIndex = info.ParentIndex }; + } - if (!BonesTransformByIndex.TryGetValue(boneIndex, out var boneTransform)) +#if DEBUG + for (int trackIndex = 0; trackIndex < UnrealSkeleton.ReferenceSkeleton.FinalRefBoneInfo.Length; trackIndex++) + { + var bone = UnrealSkeleton.ReferenceSkeleton.FinalRefBoneInfo[trackIndex]; + if (!BonesIndicesByLoweredName.TryGetValue(bone.Name.Text.ToLower(), out _)) + Log.Warning($"Bone Mismatch: {bone.Name.Text} ({trackIndex}) is not present in the mesh's skeleton"); + } +#endif + + foreach (var boneIndices in BonesIndicesByLoweredName.Values) + { + var bone = referenceSkeleton.FinalRefBonePose[boneIndices.Index]; + if (!BonesTransformByIndex.TryGetValue(boneIndices.Index, out var boneTransform)) { boneTransform = new Transform { @@ -52,14 +68,14 @@ public class Skeleton : IDisposable }; } - if (!BonesTransformByIndex.TryGetValue(parentIndex, out var parentTransform)) + if (!BonesTransformByIndex.TryGetValue(boneIndices.ParentIndex, out var parentTransform)) parentTransform = new Transform { Relation = Matrix4x4.Identity }; boneTransform.Relation = parentTransform.Matrix; Matrix4x4.Invert(boneTransform.Matrix, out var inverted); - BonesTransformByIndex[boneIndex] = boneTransform; - InvertedBonesMatrixByIndex[boneIndex] = inverted; + BonesTransformByIndex[boneIndices.Index] = boneTransform; + InvertedBonesMatrixByIndex[boneIndices.Index] = inverted; } } @@ -68,21 +84,18 @@ public class Skeleton : IDisposable Anim = new Animation(this, anim, rotationOnly); } - public void SetPoseUniform(Shader shader) - { - if (!IsLoaded) return; - foreach (var boneIndex in BonesTransformByIndex.Keys) - { - if (boneIndex >= Constants.MAX_BONE_UNIFORM) - break; - shader.SetUniform($"uFinalBonesMatrix[{boneIndex}]", Matrix4x4.Identity); - } - } - public void SetUniform(Shader shader, float deltaSeconds = 0f, bool update = false) { if (!IsLoaded) return; - if (!HasAnim) SetPoseUniform(shader); + if (!HasAnim) + { + foreach (var boneIndex in BonesTransformByIndex.Keys) + { + if (boneIndex >= Constants.MAX_BONE_UNIFORM) + break; + shader.SetUniform($"uFinalBonesMatrix[{boneIndex}]", Matrix4x4.Identity); + } + } else { if (update) Anim.Update(deltaSeconds); @@ -92,18 +105,17 @@ public class Skeleton : IDisposable break; if (!InvertedBonesMatrixByIndex.TryGetValue(boneIndex, out var invertMatrix)) throw new ArgumentNullException($"no inverse matrix for bone '{boneIndex}'"); - if (!Anim.TrackIndexByBoneIndex.TryGetValue(boneIndex, out var trackIndex)) - throw new ArgumentNullException($"no track index for bone '{boneIndex}'"); - shader.SetUniform($"uFinalBonesMatrix[{boneIndex}]", invertMatrix * Anim.InterpolateBoneTransform(trackIndex)); + shader.SetUniform($"uFinalBonesMatrix[{boneIndex}]", invertMatrix * Anim.InterpolateBoneTransform(boneIndex)); } } } public void Dispose() { - BonesIndexByLoweredName.Clear(); + BonesIndicesByLoweredName.Clear(); BonesTransformByIndex.Clear(); + InvertedBonesMatrixByIndex.Clear(); Anim?.Dispose(); } } diff --git a/FModel/Views/Snooper/Models/Model.cs b/FModel/Views/Snooper/Models/Model.cs index a9a54c7f..3ee5caa6 100644 --- a/FModel/Views/Snooper/Models/Model.cs +++ b/FModel/Views/Snooper/Models/Model.cs @@ -252,11 +252,11 @@ public class Model : IDisposable foreach (var socket in Sockets) { var boneMatrix = Matrix4x4.Identity; - if (HasSkeleton && Skeleton.BonesIndexByLoweredName.TryGetValue(socket.BoneName.Text.ToLower(), out var boneIndex)) + if (HasSkeleton && Skeleton.BonesIndicesByLoweredName.TryGetValue(socket.BoneName.Text.ToLower(), out var boneIndices)) { - if (Skeleton.Anim?.TrackIndexByBoneIndex.TryGetValue(boneIndex, out var trackIndex) ?? false) - boneMatrix = Skeleton.Anim.InterpolateBoneTransform(trackIndex); - else if (Skeleton.BonesTransformByIndex.TryGetValue(boneIndex, out var boneTransform)) + if (Skeleton.HasAnim) + boneMatrix = Skeleton.Anim.InterpolateBoneTransform(boneIndices.Index); + else if (Skeleton.BonesTransformByIndex.TryGetValue(boneIndices.Index, out var boneTransform)) boneMatrix = boneTransform.Matrix; } diff --git a/FModel/Views/Snooper/Renderer.cs b/FModel/Views/Snooper/Renderer.cs index 18348d0c..a89897bc 100644 --- a/FModel/Views/Snooper/Renderer.cs +++ b/FModel/Views/Snooper/Renderer.cs @@ -90,13 +90,20 @@ public class Renderer : IDisposable Options.SwapMaterial(false); } - public void Animate(UAnimSequence animSequence) + public void Animate(UObject anim) { - if (!Options.TryGetModel(out var model) || !model.Skeleton.IsLoaded || - model.Skeleton?.UnrealSkeleton.ConvertAnims(animSequence) is not { } anim || anim.Sequences.Count == 0) + if (!Options.TryGetModel(out var model) || !model.Skeleton.IsLoaded) return; - model.Skeleton.SetAnimation(anim, AnimateWithRotationOnly); + switch (anim) + { + case UAnimSequence animSequence: + model.Skeleton.SetAnimation(model.Skeleton.UnrealSkeleton.ConvertAnims(animSequence), AnimateWithRotationOnly); + break; + case UAnimMontage animMontage: + model.Skeleton.SetAnimation(model.Skeleton.UnrealSkeleton.ConvertAnims(animMontage), AnimateWithRotationOnly); + break; + } Options.AnimateMesh(false); } diff --git a/FModel/Views/Snooper/SnimGui.cs b/FModel/Views/Snooper/SnimGui.cs index 7de4dbef..6b471a46 100644 --- a/FModel/Views/Snooper/SnimGui.cs +++ b/FModel/Views/Snooper/SnimGui.cs @@ -452,7 +452,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio if (model.HasSkeleton) { Layout("Skeleton");ImGui.Text($" : {model.Skeleton.UnrealSkeleton.Name}"); - Layout("Bones");ImGui.Text($" : x{model.Skeleton.UnrealSkeleton.BoneTree.Length}"); + Layout("Bones");ImGui.Text($" : x{model.Skeleton.InvertedBonesMatrixByIndex.Count}"); } else {