diff --git a/CUE4Parse b/CUE4Parse index b18da130..d0c44876 160000 --- a/CUE4Parse +++ b/CUE4Parse @@ -1 +1 @@ -Subproject commit b18da13023101f3d050420cf4878f4a1175dc344 +Subproject commit d0c44876fc718e0893ed699551bc354b8d2a5422 diff --git a/FModel/MainWindow.xaml.cs b/FModel/MainWindow.xaml.cs index 71508d8e..449f04a0 100644 --- a/FModel/MainWindow.xaml.cs +++ b/FModel/MainWindow.xaml.cs @@ -85,10 +85,10 @@ public partial class MainWindow #if DEBUG await _threadWorkerView.Begin(cancellationToken => _applicationView.CUE4Parse.Extract(cancellationToken, - "ShooterGame/Content/Characters/Guide/S0/3P/Models/TP_Guide_S0_Skelmesh.uasset")); + "fortnitegame/Content/Characters/Player/Female/Medium/Bodies/F_Med_Soldier_01/Meshes/F_Med_Soldier_01.uasset")); await _threadWorkerView.Begin(cancellationToken => _applicationView.CUE4Parse.Extract(cancellationToken, - "ShooterGame/Content/Characters/Guide/S0/Ability_Q/3P/Anims/TP_Guide_S0_Q_WolfTotem_Cast_Outro_Montage.uasset")); + "fortnitegame/Content/Animation/Game/MainPlayer/Emotes/Cowbell/Cowbell_CMM_Loop_M.uasset")); #endif } diff --git a/FModel/Resources/default.vert b/FModel/Resources/default.vert index 1cae7fda..3d530a5b 100644 --- a/FModel/Resources/default.vert +++ b/FModel/Resources/default.vert @@ -16,10 +16,15 @@ layout(std430, binding = 1) buffer BoneMatrices { mat4 uFinalBonesMatrix[]; }; +layout(std430, binding = 2) buffer RestBoneMatrices +{ + mat4 uRestBonesMatrix[]; +}; uniform mat4 uView; uniform mat4 uProjection; uniform float uMorphTime; +uniform bool uIsAnimated; out vec3 fPos; out vec3 fNormal; @@ -37,14 +42,14 @@ void main() vec4 finalPos = vec4(0.0); vec4 finalNormal = vec4(0.0); vec4 finalTangent = vec4(0.0); - if (vBoneIds != vBoneWeights) + if (uIsAnimated) { for(int i = 0 ; i < 4; i++) { int boneIndex = int(vBoneIds[i]); if(boneIndex < 0) break; - mat4 boneMatrix = uFinalBonesMatrix[boneIndex]; + mat4 boneMatrix = uFinalBonesMatrix[boneIndex] * inverse(uRestBonesMatrix[boneIndex]); mat4 inverseBoneMatrix = transpose(inverse(boneMatrix)); float weight = vBoneWeights[i]; diff --git a/FModel/Resources/outline.vert b/FModel/Resources/outline.vert index 56de336d..ffb85c6c 100644 --- a/FModel/Resources/outline.vert +++ b/FModel/Resources/outline.vert @@ -11,11 +11,16 @@ layout(std430, binding = 1) buffer BoneMatrices { mat4 uFinalBonesMatrix[]; }; +layout(std430, binding = 2) buffer RestBoneMatrices +{ + mat4 uRestBonesMatrix[]; +}; uniform mat4 uView; uniform vec3 uViewPos; uniform mat4 uProjection; uniform float uMorphTime; +uniform bool uIsAnimated; void main() { @@ -24,14 +29,14 @@ void main() vec4 finalPos = vec4(0.0); vec4 finalNormal = vec4(0.0); - if (vBoneIds != vBoneWeights) + if (uIsAnimated) { for(int i = 0 ; i < 4; i++) { int boneIndex = int(vBoneIds[i]); if(boneIndex < 0) break; - mat4 boneMatrix = uFinalBonesMatrix[boneIndex]; + mat4 boneMatrix = uFinalBonesMatrix[boneIndex] * inverse(uRestBonesMatrix[boneIndex]); float weight = vBoneWeights[i]; finalPos += boneMatrix * bindPos * weight; diff --git a/FModel/Resources/picking.vert b/FModel/Resources/picking.vert index 9f354e9f..7d267c0c 100644 --- a/FModel/Resources/picking.vert +++ b/FModel/Resources/picking.vert @@ -10,24 +10,29 @@ layout(std430, binding = 1) buffer BoneMatrices { mat4 uFinalBonesMatrix[]; }; +layout(std430, binding = 2) buffer RestBoneMatrices +{ + mat4 uRestBonesMatrix[]; +}; uniform mat4 uView; uniform mat4 uProjection; uniform float uMorphTime; +uniform bool uIsAnimated; void main() { vec4 bindPos = vec4(mix(vPos, vMorphTargetPos, uMorphTime), 1.0); vec4 finalPos = vec4(0.0); - if (vBoneIds != vBoneWeights) + if (uIsAnimated) { for(int i = 0 ; i < 4; i++) { int boneIndex = int(vBoneIds[i]); if(boneIndex < 0) break; - finalPos += uFinalBonesMatrix[boneIndex] * bindPos * vBoneWeights[i]; + finalPos += uFinalBonesMatrix[boneIndex] * inverse(uRestBonesMatrix[boneIndex]) * bindPos * vBoneWeights[i]; } } else finalPos = bindPos; diff --git a/FModel/Views/Resources/Resources.xaml b/FModel/Views/Resources/Resources.xaml index 162ffa1f..61f071b9 100644 --- a/FModel/Views/Resources/Resources.xaml +++ b/FModel/Views/Resources/Resources.xaml @@ -655,47 +655,50 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/FModel/Views/Snooper/Animations/Animation.cs b/FModel/Views/Snooper/Animations/Animation.cs index 028676c5..14b51c71 100644 --- a/FModel/Views/Snooper/Animations/Animation.cs +++ b/FModel/Views/Snooper/Animations/Animation.cs @@ -11,8 +11,8 @@ namespace FModel.Views.Snooper.Animations; public class Animation : IDisposable { private readonly UObject _export; - private readonly CAnimSet _animSet; + public readonly CAnimSet UnrealAnim; public readonly string Path; public readonly string Name; public readonly Sequence[] Sequences; @@ -38,12 +38,12 @@ public class Animation : IDisposable public Animation(UObject export, CAnimSet animSet) : this(export) { - _animSet = animSet; + UnrealAnim = animSet; - Sequences = new Sequence[_animSet.Sequences.Count]; + Sequences = new Sequence[UnrealAnim.Sequences.Count]; for (int i = 0; i < Sequences.Length; i++) { - Sequences[i] = new Sequence(_animSet.Sequences[i]); + Sequences[i] = new Sequence(UnrealAnim.Sequences[i]); EndTime = Sequences[i].EndTime; } @@ -62,7 +62,7 @@ public class Animation : IDisposable for (int i = 0; i < Sequences.Length; i++) { var sequence = Sequences[i]; - if (elapsedTime < sequence.EndTime && elapsedTime >= sequence.StartTime) + if (elapsedTime <= sequence.EndTime && elapsedTime >= sequence.StartTime) { Framing[i] = (elapsedTime - sequence.StartTime) / sequence.TimePerFrame; } @@ -122,7 +122,7 @@ public class Animation : IDisposable { if (selected) AttachedModels.Remove(guid); else AttachedModels.Add(guid); model.Skeleton.ResetAnimatedData(true); - if (!selected) model.Skeleton.Animate(_animSet, s.Renderer.AnimateWithRotationOnly); + if (!selected) model.Skeleton.Animate(UnrealAnim); } } ImGui.EndMenu(); diff --git a/FModel/Views/Snooper/Animations/Skeleton.cs b/FModel/Views/Snooper/Animations/Skeleton.cs index 94f33938..a2cf922f 100644 --- a/FModel/Views/Snooper/Animations/Skeleton.cs +++ b/FModel/Views/Snooper/Animations/Skeleton.cs @@ -1,11 +1,11 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Numerics; using CUE4Parse_Conversion.Animations.PSA; using CUE4Parse.UE4.Assets.Exports.Animation; -using CUE4Parse.UE4.Objects.Core.Math; -using CUE4Parse.Utils; using FModel.Views.Snooper.Buffers; +using FModel.Views.Snooper.Shading; using OpenTK.Graphics.OpenGL4; using Serilog; @@ -14,27 +14,24 @@ namespace FModel.Views.Snooper.Animations; public class Skeleton : IDisposable { private int _handle; + private BufferObject _rest; private BufferObject _ssbo; public string Name; - public readonly Dictionary BonesByLoweredName; + public bool IsAnimated { get; private set; } - private Transform[][][] _animatedBonesTransform; // [sequence][bone][frame] - private readonly Matrix4x4[] _invertedBonesMatrix; - public int BoneCount => _invertedBonesMatrix.Length; - public bool IsAnimated => _animatedBonesTransform.Length > 0; + public readonly int BoneCount; + public readonly Dictionary BonesByLoweredName; public Skeleton() { BonesByLoweredName = new Dictionary(); - _animatedBonesTransform = Array.Empty(); - _invertedBonesMatrix = Array.Empty(); } public Skeleton(FReferenceSkeleton referenceSkeleton) : this() { - _invertedBonesMatrix = new Matrix4x4[referenceSkeleton.FinalRefBoneInfo.Length]; - for (int boneIndex = 0; boneIndex < _invertedBonesMatrix.Length; boneIndex++) + BoneCount = referenceSkeleton.FinalRefBoneInfo.Length; + for (int boneIndex = 0; boneIndex < BoneCount; boneIndex++) { var info = referenceSkeleton.FinalRefBoneInfo[boneIndex]; var boneTransform = new Transform @@ -53,128 +50,35 @@ public class Skeleton : IDisposable } BonesByLoweredName[info.Name.Text.ToLower()] = bone; - - Matrix4x4.Invert(boneTransform.Matrix, out var inverted); - _invertedBonesMatrix[bone.Index] = inverted; } } - public void Animate(CAnimSet anim, bool rotationOnly) - { - MapSkeleton(anim); - - _animatedBonesTransform = new Transform[anim.Sequences.Count][][]; - for (int s = 0; s < _animatedBonesTransform.Length; s++) - { - var sequence = anim.Sequences[s]; - _animatedBonesTransform[s] = new Transform[BoneCount][]; - foreach (var bone in BonesByLoweredName.Values) - { - _animatedBonesTransform[s][bone.Index] = new Transform[sequence.NumFrames]; - - var skeletonBoneIndex = bone.SkeletonIndex; - if (sequence.OriginalSequence.FindTrackForBoneIndex(skeletonBoneIndex) < 0) - { - for (int frame = 0; frame < _animatedBonesTransform[s][bone.Index].Length; frame++) - { - _animatedBonesTransform[s][bone.Index][frame] = new Transform - { - Relation = bone.IsRoot ? bone.Rest.Relation : - bone.Rest.LocalMatrix * _animatedBonesTransform[s][bone.ParentIndex][frame].Matrix - }; - } - } - else - { - bone.AnimatedBySequences.Add(s); - for (int frame = 0; frame < _animatedBonesTransform[s][bone.Index].Length; frame++) - { - var boneOrientation = bone.Rest.Rotation; - var bonePosition = bone.Rest.Position; - var boneScale = bone.Rest.Scale; - - sequence.Tracks[skeletonBoneIndex].GetBoneTransform(frame, sequence.NumFrames, ref boneOrientation, ref bonePosition, ref boneScale); - - switch (anim.Skeleton.BoneTree[skeletonBoneIndex]) - { - case EBoneTranslationRetargetingMode.Skeleton when !rotationOnly: - { - var targetTransform = sequence.RetargetBasePose?[skeletonBoneIndex] ?? anim.Skeleton.ReferenceSkeleton.FinalRefBonePose[skeletonBoneIndex]; - bonePosition = targetTransform.Translation; - break; - } - case EBoneTranslationRetargetingMode.AnimationScaled when !rotationOnly: - { - var sourceTranslationLength = (bone.Rest.Position / Constants.SCALE_DOWN_RATIO).Size(); - if (sourceTranslationLength > UnrealMath.KindaSmallNumber) - { - var targetTranslationLength = sequence.RetargetBasePose?[skeletonBoneIndex].Translation.Size() ?? anim.Skeleton.ReferenceSkeleton.FinalRefBonePose[skeletonBoneIndex].Translation.Size(); - bonePosition.Scale(targetTranslationLength / sourceTranslationLength); - } - break; - } - case EBoneTranslationRetargetingMode.AnimationRelative when !rotationOnly: - { - // can't tell if it's working or not - var sourceSkelTrans = bone.Rest.Position / Constants.SCALE_DOWN_RATIO; - var refPoseTransform = sequence.RetargetBasePose?[skeletonBoneIndex] ?? anim.Skeleton.ReferenceSkeleton.FinalRefBonePose[skeletonBoneIndex]; - - boneOrientation = boneOrientation * FQuat.Conjugate(bone.Rest.Rotation) * refPoseTransform.Rotation; - bonePosition += refPoseTransform.Translation - sourceSkelTrans; - boneScale *= refPoseTransform.Scale3D * bone.Rest.Scale; - boneOrientation.Normalize(); - break; - } - case EBoneTranslationRetargetingMode.OrientAndScale when !rotationOnly: - { - var sourceSkelTrans = bone.Rest.Position / Constants.SCALE_DOWN_RATIO; - var targetSkelTrans = sequence.RetargetBasePose?[skeletonBoneIndex].Translation ?? anim.Skeleton.ReferenceSkeleton.FinalRefBonePose[skeletonBoneIndex].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; - } - } - - _animatedBonesTransform[s][bone.Index][frame] = new Transform - { - Relation = bone.IsRoot ? bone.Rest.Relation : _animatedBonesTransform[s][bone.ParentIndex][frame].Matrix, - Rotation = boneOrientation, - Position = rotationOnly ? bone.Rest.Position : bonePosition * Constants.SCALE_DOWN_RATIO, - Scale = boneScale - }; - } - } - } - } - } - - private void MapSkeleton(CAnimSet anim) + public void Animate(CAnimSet animation) { + IsAnimated = true; ResetAnimatedData(); // map bones - for (int boneIndex = 0; boneIndex < anim.Skeleton.BoneCount; boneIndex++) + for (int boneIndex = 0; boneIndex < animation.Skeleton.BoneCount; boneIndex++) { - var info = anim.Skeleton.ReferenceSkeleton.FinalRefBoneInfo[boneIndex]; + var info = animation.Skeleton.ReferenceSkeleton.FinalRefBoneInfo[boneIndex]; if (!BonesByLoweredName.TryGetValue(info.Name.Text.ToLower(), out var bone)) continue; bone.SkeletonIndex = boneIndex; } + // find playable sequences + for (int s = 0; s < animation.Sequences.Count; s++) + { + var sequence = animation.Sequences[s]; + foreach (var bone in BonesByLoweredName.Values.Where(bone => sequence.OriginalSequence.FindTrackForBoneIndex(bone.SkeletonIndex) >= 0)) + { + bone.AnimatedBySequences.Add(s); + } + sequence.Retarget(animation.Skeleton); + } + #if DEBUG foreach ((var boneName, var bone) in BonesByLoweredName) { @@ -195,7 +99,7 @@ public class Skeleton : IDisposable } if (!full) return; - _animatedBonesTransform = Array.Empty(); + IsAnimated = false; _ssbo.UpdateRange(BoneCount, Matrix4x4.Identity); } @@ -203,11 +107,18 @@ public class Skeleton : IDisposable { _handle = GL.CreateProgram(); + _rest = new BufferObject(BoneCount, BufferTarget.ShaderStorageBuffer); + foreach (var bone in BonesByLoweredName.Values) + { + _rest.Update(bone.Index, bone.Rest.Matrix); + } + _rest.Unbind(); + _ssbo = new BufferObject(BoneCount, BufferTarget.ShaderStorageBuffer); _ssbo.UpdateRange(BoneCount, Matrix4x4.Identity); } - public void UpdateAnimationMatrices(Animation animation) + public void UpdateAnimationMatrices(Animation animation, bool rotationOnly) { if (!IsAnimated) return; @@ -215,17 +126,28 @@ public class Skeleton : IDisposable foreach (var bone in BonesByLoweredName.Values) { - var (s, f) = GetBoneFrameData(bone, animation); - var frameInSequence = Math.Min(f.FloorToInt(), animation.Sequences[s].EndFrame); - var nextFrameInSequence = Math.Min(frameInSequence + 1, animation.Sequences[s].EndFrame); - var lerpAmount = Math.Clamp(f - frameInSequence, 0, 1); + var boneRelation = bone.IsRoot ? bone.Rest.Relation : bone.Rest.LocalMatrix * _ssbo.Get(bone.ParentIndex); + if (bone.IsAnimated) + { + var (s, f) = GetBoneFrameData(bone, animation); + var sequence = animation.UnrealAnim.Sequences[s]; + var boneOrientation = bone.Rest.Rotation; + var bonePosition = bone.Rest.Position; + var boneScale = bone.Rest.Scale; - var boneIndex = bone.Index; - var matrix = Matrix4x4.Lerp( - _animatedBonesTransform[s][boneIndex][frameInSequence].Matrix, - _animatedBonesTransform[s][boneIndex][nextFrameInSequence].Matrix, - lerpAmount); - _ssbo.Update(boneIndex, _invertedBonesMatrix[boneIndex] * matrix); + sequence.Tracks[bone.SkeletonIndex].GetBoneTransform(f, sequence.NumFrames, ref boneOrientation, ref bonePosition, ref boneScale); + if (!bone.IsRoot) boneRelation = _ssbo.Get(bone.ParentIndex); + bonePosition = rotationOnly ? bone.Rest.Position : bonePosition * Constants.SCALE_DOWN_RATIO; + + _ssbo.Update(bone.Index, new Transform + { + Relation = boneRelation, + Rotation = boneOrientation, + Position = bonePosition, + Scale = boneScale + }.Matrix); + } + else _ssbo.Update(bone.Index, boneRelation); } _ssbo.Unbind(); @@ -260,17 +182,28 @@ public class Skeleton : IDisposable return (s, f); } - public Matrix4x4 GetBoneMatrix(Bone bone) => IsAnimated ? bone.Rest.Matrix * _ssbo.Get(bone.Index) : bone.Rest.Matrix; - - public void Render() + public Matrix4x4 GetBoneMatrix(Bone bone) { + _ssbo.Bind(); + var anim = _ssbo.Get(bone.Index); + _ssbo.Unbind(); + + return IsAnimated ? anim : bone.Rest.Matrix; + } + + public void Render(Shader shader) + { + shader.SetUniform("uIsAnimated", IsAnimated); + _ssbo.BindBufferBase(1); + _rest.BindBufferBase(2); } public void Dispose() { BonesByLoweredName.Clear(); + _rest?.Dispose(); _ssbo?.Dispose(); GL.DeleteProgram(_handle); } diff --git a/FModel/Views/Snooper/Buffers/BufferObject.cs b/FModel/Views/Snooper/Buffers/BufferObject.cs index db8810b5..16ad16dc 100644 --- a/FModel/Views/Snooper/Buffers/BufferObject.cs +++ b/FModel/Views/Snooper/Buffers/BufferObject.cs @@ -40,17 +40,15 @@ public class BufferObject : IDisposable where TDataType : unmanaged public unsafe void Update(TDataType[] data) { + Bind(); GL.BufferSubData(_bufferTarget, IntPtr.Zero, data.Length * sizeof(TDataType), data); + Unbind(); } public unsafe TDataType Get(int offset) { TDataType data = default; - - Bind(); GL.GetBufferSubData(_bufferTarget, (IntPtr) (offset * sizeof(TDataType)), sizeof(TDataType), ref data); - Unbind(); - return data; } diff --git a/FModel/Views/Snooper/Models/Model.cs b/FModel/Views/Snooper/Models/Model.cs index a931ccfc..05739dd0 100644 --- a/FModel/Views/Snooper/Models/Model.cs +++ b/FModel/Views/Snooper/Models/Model.cs @@ -285,9 +285,7 @@ public class Model : IDisposable public void UpdateMorph(int index) { - _morphVbo.Bind(); _morphVbo.Update(Morphs[index].Vertices); - _morphVbo.Unbind(); } public void AttachModel(Model attachedTo, Socket socket, SocketAttachementInfo info) @@ -398,7 +396,7 @@ public class Model : IDisposable _vao.Bind(); shader.SetUniform("uMorphTime", MorphTime); - if (HasSkeleton) Skeleton.Render(); + if (HasSkeleton) Skeleton.Render(shader); if (!outline) { shader.SetUniform("uUvCount", UvCount); @@ -434,7 +432,7 @@ public class Model : IDisposable _vao.Bind(); shader.SetUniform("uMorphTime", MorphTime); - if (HasSkeleton) Skeleton.Render(); + if (HasSkeleton) Skeleton.Render(shader); foreach (var section in Sections) { diff --git a/FModel/Views/Snooper/Renderer.cs b/FModel/Views/Snooper/Renderer.cs index b25391f2..0b2ee808 100644 --- a/FModel/Views/Snooper/Renderer.cs +++ b/FModel/Views/Snooper/Renderer.cs @@ -115,7 +115,7 @@ public class Renderer : IDisposable var animSet = skeleton.ConvertAnims(animSequence); var animation = new Animation(animSequence, animSet, guid); maxElapsedTime = animation.TotalElapsedTime; - model.Skeleton.Animate(animSet, AnimateWithRotationOnly); + model.Skeleton.Animate(animSet); Options.AddAnimation(animation); break; } @@ -124,7 +124,7 @@ public class Renderer : IDisposable var animSet = skeleton.ConvertAnims(animMontage); var animation = new Animation(animMontage, animSet, guid); maxElapsedTime = animation.TotalElapsedTime; - model.Skeleton.Animate(animSet, AnimateWithRotationOnly); + model.Skeleton.Animate(animSet); Options.AddAnimation(animation); foreach (var notifyEvent in animMontage.Notifies) @@ -191,7 +191,7 @@ public class Renderer : IDisposable var animSet = skeleton.ConvertAnims(animComposite); var animation = new Animation(animComposite, animSet, guid); maxElapsedTime = animation.TotalElapsedTime; - model.Skeleton.Animate(animSet, AnimateWithRotationOnly); + model.Skeleton.Animate(animSet); Options.AddAnimation(animation); break; } @@ -267,7 +267,7 @@ public class Renderer : IDisposable animation.TimeCalculation(Options.Tracker.ElapsedTime); foreach (var guid in animation.AttachedModels.Where(guid => Options.Models[guid].HasSkeleton)) { - Options.Models[guid].Skeleton.UpdateAnimationMatrices(animation); + Options.Models[guid].Skeleton.UpdateAnimationMatrices(animation, AnimateWithRotationOnly); } }