From e0251afe79011fb7c8958961a3c30e9ad71adf61 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 31 Mar 2025 13:38:13 +0200 Subject: [PATCH] Fix last frame importing from animations to correctly loop --- .../Editor/GUI/Timeline/AnimationTimeline.cs | 2 +- .../Animations/Graph/AnimGroup.Animation.cpp | 18 ++++++++-------- Source/Engine/Core/Math/Quaternion.h | 8 +++++++ .../Tools/ModelTool/ModelTool.OpenFBX.cpp | 21 ++++++++----------- 4 files changed, 27 insertions(+), 22 deletions(-) diff --git a/Source/Editor/GUI/Timeline/AnimationTimeline.cs b/Source/Editor/GUI/Timeline/AnimationTimeline.cs index 63329bfc0..f1118f029 100644 --- a/Source/Editor/GUI/Timeline/AnimationTimeline.cs +++ b/Source/Editor/GUI/Timeline/AnimationTimeline.cs @@ -160,7 +160,7 @@ namespace FlaxEditor.GUI.Timeline { if (_preview != null) { - frame = Mathf.Clamp(frame, 0, DurationFrames - 1); + frame = Mathf.Clamp(frame, 0, DurationFrames); var time = frame / FramesPerSecond; Editor.Internal_SetAnimationTime(Object.GetUnmanagedPtr(_preview.PreviewActor), time); if (!_preview.PlayAnimation) diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index bee1f4db4..df67c7124 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -354,7 +354,7 @@ float GetAnimSamplePos(float length, Animation* anim, float pos, float speed) return animPos; } -FORCE_INLINE void GetAnimSamplePos(bool loop, float length, float startTimePos, float prevTimePos, float& newTimePos, float& pos, float& prevPos) +FORCE_INLINE void GetAnimPos(bool loop, float length, float startTimePos, float prevTimePos, float& newTimePos, float& pos, float& prevPos) { // Calculate actual time position within the animation node (defined by length and loop mode) pos = GetAnimPos(newTimePos, startTimePos, loop, length); @@ -407,7 +407,7 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* const float frameRateMatchScale = (float)(nestedAnimSpeed / anim->Data.FramesPerSecond); nestedAnimPos = nestedAnimPos * frameRateMatchScale; nestedAnimPrevPos = nestedAnimPrevPos * frameRateMatchScale; - GetAnimSamplePos(nestedAnim.Loop, nestedAnimLength, nestedAnim.StartTime, nestedAnimPrevPos, nestedAnimPos, nestedAnimPos, nestedAnimPrevPos); + GetAnimPos(nestedAnim.Loop, nestedAnimLength, nestedAnim.StartTime, nestedAnimPrevPos, nestedAnimPos, nestedAnimPos, nestedAnimPrevPos); ProcessAnimation(nodes, node, true, nestedAnimLength, nestedAnimPos, nestedAnimPrevPos, nestedAnim.Anim, 1.0f, weight, mode, usedNodes); } @@ -590,7 +590,7 @@ Variant AnimGraphExecutor::SampleAnimation(AnimGraphNode* node, bool loop, float return Value::Null; float pos, prevPos; - GetAnimSamplePos(loop, length, startTimePos, prevTimePos, newTimePos, pos, prevPos); + GetAnimPos(loop, length, startTimePos, prevTimePos, newTimePos, pos, prevPos); const auto nodes = node->GetNodes(this); InitNodes(nodes); @@ -615,7 +615,7 @@ Variant AnimGraphExecutor::SampleAnimationsWithBlend(AnimGraphNode* node, bool l return Value::Null; float pos, prevPos; - GetAnimSamplePos(loop, length, startTimePos, prevTimePos, newTimePos, pos, prevPos); + GetAnimPos(loop, length, startTimePos, prevTimePos, newTimePos, pos, prevPos); // Sample the animations with blending const auto nodes = node->GetNodes(this); @@ -638,8 +638,8 @@ Variant AnimGraphExecutor::SampleAnimationsWithBlend(AnimGraphNode* node, bool l // Get actual animation position (includes looping and start offset) float posA, prevPosA, posB, prevPosB; - GetAnimSamplePos(loop, a.Length, startTimePos, a.PrevTimePos, a.TimePos, posA, prevPosA); - GetAnimSamplePos(loop, b.Length, startTimePos, b.PrevTimePos, b.TimePos, posB, prevPosB); + GetAnimPos(loop, a.Length, startTimePos, a.PrevTimePos, a.TimePos, posA, prevPosA); + GetAnimPos(loop, b.Length, startTimePos, b.PrevTimePos, b.TimePos, posB, prevPosB); // Sample the animations with blending const auto nodes = node->GetNodes(this); @@ -663,9 +663,9 @@ Variant AnimGraphExecutor::SampleAnimationsWithBlend(AnimGraphNode* node, bool l // Get actual animation position (includes looping and start offset) float posA, prevPosA, posB, prevPosB, posC, prevPosC; - GetAnimSamplePos(loop, a.Length, startTimePos, a.PrevTimePos, a.TimePos, posA, prevPosA); - GetAnimSamplePos(loop, b.Length, startTimePos, b.PrevTimePos, b.TimePos, posB, prevPosB); - GetAnimSamplePos(loop, c.Length, startTimePos, c.PrevTimePos, c.TimePos, posC, prevPosC); + GetAnimPos(loop, a.Length, startTimePos, a.PrevTimePos, a.TimePos, posA, prevPosA); + GetAnimPos(loop, b.Length, startTimePos, b.PrevTimePos, b.TimePos, posB, prevPosB); + GetAnimPos(loop, c.Length, startTimePos, c.PrevTimePos, c.TimePos, posC, prevPosC); // Sample the animations with blending const auto nodes = node->GetNodes(this); diff --git a/Source/Engine/Core/Math/Quaternion.h b/Source/Engine/Core/Math/Quaternion.h index 0b181f862..7f457667b 100644 --- a/Source/Engine/Core/Math/Quaternion.h +++ b/Source/Engine/Core/Math/Quaternion.h @@ -565,6 +565,14 @@ public: return result; } + // Interpolates between two quaternions, using spherical linear interpolation. + static Quaternion Slerp(const Quaternion& start, const Quaternion& end, float amount) + { + Quaternion result; + Slerp(start, end, amount, result); + return result; + } + // Interpolates between two quaternions, using spherical linear interpolation. static void Slerp(const Quaternion& start, const Quaternion& end, float amount, Quaternion& result); diff --git a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp index c36e9f50a..1f91c1e41 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp @@ -14,6 +14,7 @@ #include "Engine/Platform/File.h" #define OPEN_FBX_CONVERT_SPACE 1 +#define OPEN_FBX_NAME_SIZE 256 #if BUILD_DEBUG #define OPEN_FBX_GET_CACHE_LIST(arrayName, varName, size) data.arrayName.Resize(size, false); auto& varName = data.arrayName #else @@ -204,7 +205,7 @@ struct OpenFbxImporterData ofbx::DataView aFilename = tex->getRelativeFileName(); if (aFilename == "") aFilename = tex->getFileName(); - char filenameData[256]; + char filenameData[OPEN_FBX_NAME_SIZE]; aFilename.toString(filenameData); const String filename(filenameData); String path; @@ -498,7 +499,6 @@ bool ImportBones(OpenFbxImporterData& data, String& errorMsg) // Add bone boneIndex = data.Bones.Count(); - data.Bones.EnsureCapacity(256); data.Bones.Resize(boneIndex + 1); auto& bone = data.Bones[boneIndex]; @@ -1089,7 +1089,6 @@ struct AnimInfo double TimeEnd; double Duration; int32 FramesCount; - float SamplingPeriod; }; struct Frame @@ -1122,7 +1121,7 @@ void ExtractKeyframeScale(const ofbx::Object* bone, ofbx::DVec3& trans, const Fr } template -void ImportCurve(const ofbx::AnimationCurveNode* curveNode, LinearCurve& curve, AnimInfo& info, void (*ExtractKeyframe)(const ofbx::Object*, ofbx::DVec3&, const Frame&, T&)) +void ImportCurve(const ofbx::AnimationCurveNode* curveNode, LinearCurve& curve, AnimInfo& info, void (*extractKeyframe)(const ofbx::Object*, ofbx::DVec3&, const Frame&, T&)) { if (curveNode == nullptr) return; @@ -1136,12 +1135,11 @@ void ImportCurve(const ofbx::AnimationCurveNode* curveNode, LinearCurve& curv for (int32 i = 0; i < info.FramesCount; i++) { auto& key = keyframes[i]; - const double t = info.TimeStart + ((double)i / info.FramesCount) * info.Duration; - + const double alpha = (double)i / (info.FramesCount - 1); + const double t = Math::Lerp(info.TimeStart, info.TimeEnd, alpha); key.Time = (float)i; - ofbx::DVec3 trans = curveNode->getNodeLocalTransform(t); - ExtractKeyframe(bone, trans, localFrame, key.Value); + extractKeyframe(bone, trans, localFrame, key.Value); } } @@ -1179,7 +1177,7 @@ void ImportAnimation(int32 index, ModelData& data, OpenFbxImporterData& importer auto& animation = data.Animations.AddOne(); animation.Duration = (double)(int32)(localDuration * frameRate + 0.5f); animation.FramesPerSecond = frameRate; - char nameData[256]; + char nameData[OPEN_FBX_NAME_SIZE]; takeInfo->name.toString(nameData); animation.Name = nameData; animation.Name = animation.Name.TrimTrailing(); @@ -1190,8 +1188,7 @@ void ImportAnimation(int32 index, ModelData& data, OpenFbxImporterData& importer info.TimeStart = takeInfo->local_time_from; info.TimeEnd = takeInfo->local_time_to; info.Duration = localDuration; - info.FramesCount = (int32)animation.Duration; - info.SamplingPeriod = 1.0f / frameRate; + info.FramesCount = (int32)animation.Duration + 1; // Import curves for (int32 i = 0; i < animatedNodes.Count(); i++) @@ -1319,7 +1316,7 @@ bool ModelTool::ImportDataOpenFBX(const String& path, ModelData& data, Options& { const ofbx::DataView aEmbedded = scene->getEmbeddedData(i); ofbx::DataView aFilename = scene->getEmbeddedFilename(i); - char filenameData[256]; + char filenameData[OPEN_FBX_NAME_SIZE]; aFilename.toString(filenameData); if (outputPath.IsEmpty()) {