Skip to content

Commit

Permalink
fully fixed untracked bones parent
Browse files Browse the repository at this point in the history
  • Loading branch information
4sval committed Feb 16, 2023
1 parent 3b3fe6c commit ad7dc46
Show file tree
Hide file tree
Showing 7 changed files with 155 additions and 119 deletions.
7 changes: 2 additions & 5 deletions FModel/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,10 @@ private async void OnLoaded(object sender, RoutedEventArgs e)
#if DEBUG
await _threadWorkerView.Begin(cancellationToken =>
_applicationView.CUE4Parse.Extract(cancellationToken,
"fortnitegame/Content/Characters/Player/Male/Large/Bodies/M_LRG_BasilStrong/Meshes/M_LRG_BasilStrong.uasset"));
"fortnitegame/Content/Characters/Player/Female/Medium/Heads/F_MED_HIS_Ramirez_Head_01/Mesh/F_MED_HIS_Ramirez_Head_01.uasset"));
await _threadWorkerView.Begin(cancellationToken =>
_applicationView.CUE4Parse.Extract(cancellationToken,
"fortnitegame/Content/Animation/Game/MainPlayer/Emotes/Alliteration/Emote_Alliteration_CMM.uasset"));
// await _threadWorkerView.Begin(cancellationToken =>
// _applicationView.CUE4Parse.Extract(cancellationToken,
// "MoonMan/Content/DeliverUsTheMoon/Characters/Astronaut/AM_OxygenHub_Enter.uasset"));
"fortnitegame/Content/Animation/Game/MainPlayer/Emotes/Calculated/Emote_Calculated_CMF_Montage.uasset"));
#endif
}

Expand Down
15 changes: 12 additions & 3 deletions FModel/Views/Snooper/Models/Animations/BoneIndice.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
namespace FModel.Views.Snooper.Models.Animations;

public struct BoneIndice
public class BoneIndice
{
public int Index;
public int ParentIndex;
public int BoneIndex = -1;
public int ParentBoneIndex = -1;
public string LoweredParentBoneName;
public bool IsRoot => BoneIndex == 0 && ParentBoneIndex == -1 && string.IsNullOrEmpty(LoweredParentBoneName);

public int TrackIndex = -1;
public int ParentTrackIndex = -1; // bone index of the first tracked parent bone
public bool HasTrack => TrackIndex > -1;
public bool HasParentTrack => ParentTrackIndex > -1;

public override string ToString() => $"{ParentBoneIndex} -> {BoneIndex}{(HasTrack ? $" ({ParentTrackIndex} -> {TrackIndex})" : "")}";
}
157 changes: 72 additions & 85 deletions FModel/Views/Snooper/Models/Animations/Sequence.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Linq;
using System.Numerics;
using CUE4Parse_Conversion.Animations;
using CUE4Parse.UE4.Assets.Exports.Animation;
Expand Down Expand Up @@ -35,107 +36,93 @@ private Sequence(CAnimSequence sequence)
public Sequence(Skeleton skeleton, CAnimSet anim, CAnimSequence sequence, bool rotationOnly) : this(sequence)
{
BonesTransform = new Transform[skeleton.BoneCount][];

for (int trackIndex = 0; trackIndex < anim.TrackBonesInfo.Length; trackIndex++)
foreach (var boneIndices in skeleton.BonesIndicesByLoweredName.Values)
{
if (!skeleton.BonesIndicesByLoweredName.TryGetValue(anim.TrackBonesInfo[trackIndex].Name.Text.ToLower(), out var boneIndices))
continue;

var originalTransform = skeleton.BonesTransformByIndex[boneIndices.Index];
var originalTransform = skeleton.BonesTransformByIndex[boneIndices.BoneIndex];
BonesTransform[boneIndices.BoneIndex] = new Transform[sequence.NumFrames];

BonesTransform[boneIndices.Index] = new Transform[sequence.NumFrames];
for (int frame = 0; frame < BonesTransform[boneIndices.Index].Length; frame++)
if (!boneIndices.HasTrack)
{
var boneOrientation = originalTransform.Rotation;
var bonePosition = originalTransform.Position;
var boneScale = originalTransform.Scale;

sequence.Tracks[trackIndex].GetBonePosition(frame, sequence.NumFrames, false, ref bonePosition, ref boneOrientation);
if (frame < sequence.Tracks[trackIndex].KeyScale.Length)
boneScale = sequence.Tracks[trackIndex].KeyScale[frame];

switch (anim.BoneModes[trackIndex])
for (int frame = 0; frame < BonesTransform[boneIndices.BoneIndex].Length; frame++)
{
case EBoneTranslationRetargetingMode.Skeleton:
BonesTransform[boneIndices.BoneIndex][frame] = new Transform
{
var targetTransform = sequence.RetargetBasePose?[trackIndex] ?? anim.BonePositions[trackIndex];
bonePosition = targetTransform.Translation;
break;
}
case EBoneTranslationRetargetingMode.AnimationScaled:
Relation = originalTransform.LocalMatrix * BonesTransform[boneIndices.ParentTrackIndex][frame].Matrix
};
}
}
else
{
var trackIndex = boneIndices.TrackIndex;
for (int frame = 0; frame < BonesTransform[boneIndices.BoneIndex].Length; frame++)
{
var boneOrientation = originalTransform.Rotation;
var bonePosition = originalTransform.Position;
var boneScale = originalTransform.Scale;

sequence.Tracks[trackIndex].GetBonePosition(frame, sequence.NumFrames, false, ref bonePosition, ref boneOrientation);
if (frame < sequence.Tracks[trackIndex].KeyScale.Length)
boneScale = sequence.Tracks[trackIndex].KeyScale[frame];

switch (anim.BoneModes[trackIndex])
{
var sourceTranslationLength = (originalTransform.Position / Constants.SCALE_DOWN_RATIO).Size();
if (sourceTranslationLength > UnrealMath.KindaSmallNumber)
case EBoneTranslationRetargetingMode.Skeleton when !rotationOnly:
{
var targetTranslationLength = sequence.RetargetBasePose?[trackIndex].Translation.Size() ?? anim.BonePositions[trackIndex].Translation.Size();
bonePosition.Scale(targetTranslationLength / sourceTranslationLength);
var targetTransform = sequence.RetargetBasePose?[trackIndex] ?? anim.BonePositions[trackIndex];
bonePosition = targetTransform.Translation;
break;
}
break;
}
case EBoneTranslationRetargetingMode.AnimationRelative:
{
// https://github.com/EpicGames/UnrealEngine/blob/cdaec5b33ea5d332e51eee4e4866495c90442122/Engine/Source/Runtime/Engine/Private/Animation/AnimationRuntime.cpp#L2586
var refPoseTransform = sequence.RetargetBasePose?[trackIndex] ?? anim.BonePositions[trackIndex];
break;
}
case EBoneTranslationRetargetingMode.OrientAndScale:
{
var sourceSkelTrans = originalTransform.Position / Constants.SCALE_DOWN_RATIO;
var targetSkelTrans = sequence.RetargetBasePose?[trackIndex].Translation ?? anim.BonePositions[trackIndex].Translation;

if (!sourceSkelTrans.Equals(targetSkelTrans))
case EBoneTranslationRetargetingMode.AnimationScaled when !rotationOnly:
{
var sourceSkelTransLength = sourceSkelTrans.Size();
var targetSkelTransLength = targetSkelTrans.Size();
if (!UnrealMath.IsNearlyZero(sourceSkelTransLength * targetSkelTransLength))
var sourceTranslationLength = (originalTransform.Position / Constants.SCALE_DOWN_RATIO).Size();
if (sourceTranslationLength > UnrealMath.KindaSmallNumber)
{
var sourceSkelTransDir = sourceSkelTrans / sourceSkelTransLength;
var targetSkelTransDir = targetSkelTrans / targetSkelTransLength;
var targetTranslationLength = sequence.RetargetBasePose?[trackIndex].Translation.Size() ?? anim.BonePositions[trackIndex].Translation.Size();
bonePosition.Scale(targetTranslationLength / sourceTranslationLength);
}
break;
}
case EBoneTranslationRetargetingMode.AnimationRelative when !rotationOnly:
{
// https://github.com/EpicGames/UnrealEngine/blob/cdaec5b33ea5d332e51eee4e4866495c90442122/Engine/Source/Runtime/Engine/Private/Animation/AnimationRuntime.cpp#L2586
var refPoseTransform = sequence.RetargetBasePose?[trackIndex] ?? anim.BonePositions[trackIndex];
break;
}
case EBoneTranslationRetargetingMode.OrientAndScale when !rotationOnly:
{
var sourceSkelTrans = originalTransform.Position / Constants.SCALE_DOWN_RATIO;
var targetSkelTrans = sequence.RetargetBasePose?[trackIndex].Translation ?? anim.BonePositions[trackIndex].Translation;

var deltaRotation = FQuat.FindBetweenNormals(sourceSkelTransDir, targetSkelTransDir);
var scale = targetSkelTransLength / sourceSkelTransLength;
bonePosition = deltaRotation.RotateVector(bonePosition) * scale;
if (!sourceSkelTrans.Equals(targetSkelTrans))
{
var sourceSkelTransLength = sourceSkelTrans.Size();
var targetSkelTransLength = targetSkelTrans.Size();
if (!UnrealMath.IsNearlyZero(sourceSkelTransLength * targetSkelTransLength))
{
var sourceSkelTransDir = sourceSkelTrans / sourceSkelTransLength;
var targetSkelTransDir = targetSkelTrans / targetSkelTransLength;

var deltaRotation = FQuat.FindBetweenNormals(sourceSkelTransDir, targetSkelTransDir);
var scale = targetSkelTransLength / sourceSkelTransLength;
bonePosition = deltaRotation.RotateVector(bonePosition) * scale;
}
}
break;
}
break;
}
}

// revert FixRotationKeys
if (trackIndex > 0) boneOrientation.Conjugate();
bonePosition *= Constants.SCALE_DOWN_RATIO;
// revert FixRotationKeys
if (trackIndex > 0) boneOrientation.Conjugate();
bonePosition *= Constants.SCALE_DOWN_RATIO;

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

// TODO: move this out of each fucking sequence
foreach ((var boneName, var boneIndices) in skeleton.BonesIndicesByLoweredName)
{
var boneIndex = boneIndices.Index;
if (BonesTransform[boneIndex] != null) continue;
#if DEBUG
Log.Warning($"Bone Mismatch: {boneName} ({boneIndex}) was not present in {sequence.Name}'s target skeleton");
#endif

var originalTransform = skeleton.BonesTransformByIndex[boneIndex];

BonesTransform[boneIndex] = new Transform[sequence.NumFrames];
for (int frame = 0; frame < BonesTransform[boneIndex].Length; frame++)
{
BonesTransform[boneIndex][frame] = new Transform
{
Relation = boneIndices.ParentIndex >= 0 ? BonesTransform[boneIndices.ParentIndex][frame].Matrix : originalTransform.Relation,
Rotation = originalTransform.Rotation,
Position = originalTransform.Position,
Scale = originalTransform.Scale
};
BonesTransform[boneIndices.BoneIndex][frame] = new Transform
{
Relation = boneIndices.HasParentTrack ? BonesTransform[boneIndices.ParentTrackIndex][frame].Matrix : originalTransform.Relation,
Rotation = boneOrientation,
Position = rotationOnly ? originalTransform.Position : bonePosition,
Scale = boneScale
};
}
}
}
}
Expand Down
69 changes: 63 additions & 6 deletions FModel/Views/Snooper/Models/Animations/Skeleton.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using CUE4Parse.UE4.Assets.Exports.Animation;
using FModel.Views.Snooper.Buffers;
using OpenTK.Graphics.OpenGL4;
using Serilog;

namespace FModel.Views.Snooper.Models.Animations;

Expand Down Expand Up @@ -34,14 +35,20 @@ public Skeleton(FReferenceSkeleton referenceSkeleton) : this()
for (int boneIndex = 0; boneIndex < referenceSkeleton.FinalRefBoneInfo.Length; boneIndex++)
{
var info = referenceSkeleton.FinalRefBoneInfo[boneIndex];
BonesIndicesByLoweredName[info.Name.Text.ToLower()] = new BoneIndice { Index = boneIndex, ParentIndex = info.ParentIndex };

var boneIndices = new BoneIndice { BoneIndex = boneIndex, ParentBoneIndex = info.ParentIndex };
if (!boneIndices.IsRoot)
boneIndices.LoweredParentBoneName =
referenceSkeleton.FinalRefBoneInfo[boneIndices.ParentBoneIndex].Name.Text.ToLower();

BonesIndicesByLoweredName[info.Name.Text.ToLower()] = boneIndices;
}

InvertedBonesMatrix = new Matrix4x4[BonesIndicesByLoweredName.Count];
foreach (var boneIndices in BonesIndicesByLoweredName.Values)
{
var bone = referenceSkeleton.FinalRefBonePose[boneIndices.Index];
if (!BonesTransformByIndex.TryGetValue(boneIndices.Index, out var boneTransform))
var bone = referenceSkeleton.FinalRefBonePose[boneIndices.BoneIndex];
if (!BonesTransformByIndex.TryGetValue(boneIndices.BoneIndex, out var boneTransform))
{
boneTransform = new Transform
{
Expand All @@ -51,23 +58,73 @@ public Skeleton(FReferenceSkeleton referenceSkeleton) : this()
};
}

if (!BonesTransformByIndex.TryGetValue(boneIndices.ParentIndex, out var parentTransform))
if (!BonesTransformByIndex.TryGetValue(boneIndices.ParentBoneIndex, out var parentTransform))
parentTransform = new Transform { Relation = Matrix4x4.Identity };

boneTransform.Relation = parentTransform.Matrix;
Matrix4x4.Invert(boneTransform.Matrix, out var inverted);


BonesTransformByIndex[boneIndices.Index] = boneTransform;
InvertedBonesMatrix[boneIndices.Index] = inverted;
BonesTransformByIndex[boneIndices.BoneIndex] = boneTransform;
InvertedBonesMatrix[boneIndices.BoneIndex] = inverted;
}
}

public void SetAnimation(CAnimSet anim, bool rotationOnly)
{
TrackSkeleton(anim);
Anim = new Animation(this, anim, rotationOnly);
}

private void TrackSkeleton(CAnimSet anim)
{
// reset
foreach (var boneIndices in BonesIndicesByLoweredName.Values)
{
boneIndices.TrackIndex = -1;
boneIndices.ParentTrackIndex = -1;
}

// tracked bones
for (int trackIndex = 0; trackIndex < anim.TrackBonesInfo.Length; trackIndex++)
{
var info = anim.TrackBonesInfo[trackIndex];
if (!BonesIndicesByLoweredName.TryGetValue(info.Name.Text.ToLower(), out var boneIndices))
continue;

boneIndices.TrackIndex = trackIndex;
var parentTrackIndex = info.ParentIndex;
if (parentTrackIndex < 0) continue;

do
{
info = anim.TrackBonesInfo[parentTrackIndex];
if (BonesIndicesByLoweredName.TryGetValue(info.Name.Text.ToLower(), out var parentBoneIndices) && parentBoneIndices.HasTrack)
boneIndices.ParentTrackIndex = parentBoneIndices.BoneIndex;
else parentTrackIndex = info.ParentIndex;
} while (!boneIndices.HasParentTrack);
}

// fix parent of untracked bones
foreach ((var boneName, var boneIndices) in BonesIndicesByLoweredName)
{
if (boneIndices.IsRoot || boneIndices.HasTrack && boneIndices.HasParentTrack) // assuming root bone always has a track
continue;

#if DEBUG
Log.Warning($"Bone Mismatch: {boneName} ({boneIndices.BoneIndex}) was not present in the anim's target skeleton");
#endif

var loweredParentBoneName = boneIndices.LoweredParentBoneName;
do
{
var parentBoneIndices = BonesIndicesByLoweredName[loweredParentBoneName];
if (parentBoneIndices.HasTrack) boneIndices.ParentTrackIndex = parentBoneIndices.BoneIndex;
else loweredParentBoneName = parentBoneIndices.LoweredParentBoneName;
} while (!boneIndices.HasParentTrack);
}
}

public void Setup()
{
_handle = GL.CreateProgram();
Expand Down
4 changes: 2 additions & 2 deletions FModel/Views/Snooper/Models/Model.cs
Original file line number Diff line number Diff line change
Expand Up @@ -260,8 +260,8 @@ public void UpdateMatrices(Options options, float deltaSeconds = 0f, bool update
if (HasSkeleton && Skeleton.BonesIndicesByLoweredName.TryGetValue(socket.BoneName.Text.ToLower(), out var boneIndices))
{
boneMatrix = Skeleton.HasAnim
? Skeleton.Anim.InterpolateBoneTransform(boneIndices.Index)
: Skeleton.BonesTransformByIndex[boneIndices.Index].Matrix;
? Skeleton.Anim.InterpolateBoneTransform(boneIndices.BoneIndex)
: Skeleton.BonesTransformByIndex[boneIndices.BoneIndex].Matrix;
}

var socketRelation = boneMatrix * worldMatrix;
Expand Down
20 changes: 3 additions & 17 deletions FModel/Views/Snooper/Renderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using CUE4Parse_Conversion.Meshes;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Animation;
using CUE4Parse.UE4.Assets.Exports.Component.StaticMesh;
using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Exports.SkeletalMesh;
using CUE4Parse.UE4.Assets.Exports.StaticMesh;
Expand Down Expand Up @@ -284,23 +285,8 @@ private void WorldLight(UObject actor)
private void WorldMesh(UObject actor, Transform transform)
{
if (!actor.TryGetValue(out FPackageIndex staticMeshComponent, "StaticMeshComponent", "StaticMesh", "Mesh", "LightMesh") ||
staticMeshComponent.Load() is not { } staticMeshComp) return;

if (!staticMeshComp.TryGetValue(out FPackageIndex staticMesh, "StaticMesh") && actor.Class is UBlueprintGeneratedClass)
{
foreach (var actorExp in actor.Class.Owner.GetExports())
if (actorExp.TryGetValue(out staticMesh, "StaticMesh"))
break;
var super = actor.Class.SuperStruct.Load<UBlueprintGeneratedClass>();
if (staticMesh == null && super != null) { // look in parent struct if not found
foreach (var actorExp in super.Owner.GetExports()) {
if (actorExp.TryGetValue(out staticMesh, "StaticMesh"))
break;
}
}
}

if (staticMesh?.Load() is not UStaticMesh m || m.Materials.Length < 1)
!staticMeshComponent.TryLoad(out UStaticMeshComponent staticMeshComp) ||
!staticMeshComp.GetStaticMesh().TryLoad(out UStaticMesh m) || m.Materials.Length < 1)
return;

var guid = m.LightingGuid;
Expand Down

0 comments on commit ad7dc46

Please sign in to comment.