mirror of
https://github.com/4sval/FModel.git
synced 2026-06-21 07:20:05 -05:00
fully fixed untracked bones parent
This commit is contained in:
parent
3b3fe6cb95
commit
ad7dc46a0a
|
|
@ -1 +1 @@
|
|||
Subproject commit f8e462f7f12432626b9400409fd50f685309516a
|
||||
Subproject commit c24bb1a031f8d463a8401e3952c9a11344e208a8
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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})" : "")}";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user