only parse bone tracks + anim montage test

This commit is contained in:
4sval
2023-02-08 01:58:04 +01:00
parent af2fceb9e5
commit 70d4791b5b
9 changed files with 166 additions and 117 deletions

View File

@@ -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
}

View File

@@ -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;
}

View File

@@ -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<int, int> 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<int, int>();
Sequences = Array.Empty<Sequence>();
}
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();
}
}

View File

@@ -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();
}
}

View File

@@ -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<string, int> BonesIndexByLoweredName;
public readonly bool IsLoaded;
public readonly Dictionary<string, BoneIndice> BonesIndicesByLoweredName;
public readonly Dictionary<int, Transform> BonesTransformByIndex;
public readonly Dictionary<int, Matrix4x4> InvertedBonesMatrixByIndex;
public readonly bool IsLoaded;
public Animation Anim;
public bool HasAnim => Anim != null;
public Skeleton()
{
BonesIndexByLoweredName = new Dictionary<string, int>();
BonesIndicesByLoweredName = new Dictionary<string, BoneIndice>();
BonesTransformByIndex = new Dictionary<int, Transform>();
InvertedBonesMatrixByIndex = new Dictionary<int, Matrix4x4>();
}
@@ -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();
}
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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
{