fully fixed untracked bones parent

This commit is contained in:
4sval 2023-02-16 12:57:43 +01:00
parent 3b3fe6cb95
commit ad7dc46a0a
7 changed files with 162 additions and 126 deletions

@ -1 +1 @@
Subproject commit f8e462f7f12432626b9400409fd50f685309516a
Subproject commit c24bb1a031f8d463a8401e3952c9a11344e208a8

View File

@ -77,13 +77,10 @@ public partial class MainWindow
#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
}

View File

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

View File

@ -1,4 +1,5 @@
using System;
using System.Linq;
using System.Numerics;
using CUE4Parse_Conversion.Animations;
using CUE4Parse.UE4.Assets.Exports.Animation;
@ -35,107 +36,93 @@ public class Sequence : IDisposable
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.BoneIndex];
BonesTransform[boneIndices.BoneIndex] = new Transform[sequence.NumFrames];
var originalTransform = skeleton.BonesTransformByIndex[boneIndices.Index];
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:
{
var sourceTranslationLength = (originalTransform.Position / Constants.SCALE_DOWN_RATIO).Size();
if (sourceTranslationLength > UnrealMath.KindaSmallNumber)
{
var targetTranslationLength = sequence.RetargetBasePose?[trackIndex].Translation.Size() ?? anim.BonePositions[trackIndex].Translation.Size();
bonePosition.Scale(targetTranslationLength / sourceTranslationLength);
}
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))
{
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;
}
Relation = originalTransform.LocalMatrix * BonesTransform[boneIndices.ParentTrackIndex][frame].Matrix
};
}
// 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++)
else
{
BonesTransform[boneIndex][frame] = new Transform
var trackIndex = boneIndices.TrackIndex;
for (int frame = 0; frame < BonesTransform[boneIndices.BoneIndex].Length; frame++)
{
Relation = boneIndices.ParentIndex >= 0 ? BonesTransform[boneIndices.ParentIndex][frame].Matrix : originalTransform.Relation,
Rotation = originalTransform.Rotation,
Position = originalTransform.Position,
Scale = originalTransform.Scale
};
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])
{
case EBoneTranslationRetargetingMode.Skeleton when !rotationOnly:
{
var targetTransform = sequence.RetargetBasePose?[trackIndex] ?? anim.BonePositions[trackIndex];
bonePosition = targetTransform.Translation;
break;
}
case EBoneTranslationRetargetingMode.AnimationScaled when !rotationOnly:
{
var sourceTranslationLength = (originalTransform.Position / Constants.SCALE_DOWN_RATIO).Size();
if (sourceTranslationLength > UnrealMath.KindaSmallNumber)
{
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;
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;
}
}
// revert FixRotationKeys
if (trackIndex > 0) boneOrientation.Conjugate();
bonePosition *= Constants.SCALE_DOWN_RATIO;
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
};
}
}
}
}

View File

@ -5,6 +5,7 @@ using CUE4Parse_Conversion.Animations;
using CUE4Parse.UE4.Assets.Exports.Animation;
using FModel.Views.Snooper.Buffers;
using OpenTK.Graphics.OpenGL4;
using Serilog;
namespace FModel.Views.Snooper.Models.Animations;
@ -34,14 +35,20 @@ public class Skeleton : IDisposable
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
{
@ -51,23 +58,73 @@ public class Skeleton : IDisposable
};
}
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();

View File

@ -260,8 +260,8 @@ public class Model : IDisposable
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;

View File

@ -7,6 +7,7 @@ using CUE4Parse_Conversion.Animations;
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;
@ -284,23 +285,8 @@ public class Renderer : IDisposable
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;