From ad7dc46a0abb879ec24204f6299c7bf335f3c4c3 Mon Sep 17 00:00:00 2001 From: 4sval Date: Thu, 16 Feb 2023 12:57:43 +0100 Subject: [PATCH] fully fixed untracked bones parent --- CUE4Parse | 2 +- FModel/MainWindow.xaml.cs | 7 +- .../Snooper/Models/Animations/BoneIndice.cs | 15 +- .../Snooper/Models/Animations/Sequence.cs | 171 ++++++++---------- .../Snooper/Models/Animations/Skeleton.cs | 69 ++++++- FModel/Views/Snooper/Models/Model.cs | 4 +- FModel/Views/Snooper/Renderer.cs | 20 +- 7 files changed, 162 insertions(+), 126 deletions(-) diff --git a/CUE4Parse b/CUE4Parse index f8e462f7..c24bb1a0 160000 --- a/CUE4Parse +++ b/CUE4Parse @@ -1 +1 @@ -Subproject commit f8e462f7f12432626b9400409fd50f685309516a +Subproject commit c24bb1a031f8d463a8401e3952c9a11344e208a8 diff --git a/FModel/MainWindow.xaml.cs b/FModel/MainWindow.xaml.cs index 3a731196..0d6b8be2 100644 --- a/FModel/MainWindow.xaml.cs +++ b/FModel/MainWindow.xaml.cs @@ -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 } diff --git a/FModel/Views/Snooper/Models/Animations/BoneIndice.cs b/FModel/Views/Snooper/Models/Animations/BoneIndice.cs index bf820230..f8d9fc16 100644 --- a/FModel/Views/Snooper/Models/Animations/BoneIndice.cs +++ b/FModel/Views/Snooper/Models/Animations/BoneIndice.cs @@ -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})" : "")}"; } diff --git a/FModel/Views/Snooper/Models/Animations/Sequence.cs b/FModel/Views/Snooper/Models/Animations/Sequence.cs index be76e014..0fd08fb4 100644 --- a/FModel/Views/Snooper/Models/Animations/Sequence.cs +++ b/FModel/Views/Snooper/Models/Animations/Sequence.cs @@ -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 + }; + } } } } diff --git a/FModel/Views/Snooper/Models/Animations/Skeleton.cs b/FModel/Views/Snooper/Models/Animations/Skeleton.cs index f0834ef7..73ce41f0 100644 --- a/FModel/Views/Snooper/Models/Animations/Skeleton.cs +++ b/FModel/Views/Snooper/Models/Animations/Skeleton.cs @@ -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(); diff --git a/FModel/Views/Snooper/Models/Model.cs b/FModel/Views/Snooper/Models/Model.cs index 3e8a7475..133d4127 100644 --- a/FModel/Views/Snooper/Models/Model.cs +++ b/FModel/Views/Snooper/Models/Model.cs @@ -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; diff --git a/FModel/Views/Snooper/Renderer.cs b/FModel/Views/Snooper/Renderer.cs index 65a19bda..247df8a6 100644 --- a/FModel/Views/Snooper/Renderer.cs +++ b/FModel/Views/Snooper/Renderer.cs @@ -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(); - 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;