From 1f3f1ea67eb71567154ea1ac94c9c2f5a556579d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 3 Oct 2025 10:52:01 +0200 Subject: [PATCH] Fix Blend Poses nodes to smoothly blend back when transition goes back #3595 --- .../Animations/Graph/AnimGraph.Base.cpp | 1 + Source/Engine/Animations/Graph/AnimGraph.h | 3 +- .../Animations/Graph/AnimGroup.Animation.cpp | 40 ++++++++----------- 3 files changed, 19 insertions(+), 25 deletions(-) diff --git a/Source/Engine/Animations/Graph/AnimGraph.Base.cpp b/Source/Engine/Animations/Graph/AnimGraph.Base.cpp index c49f0e26e..e4d7eda70 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.Base.cpp +++ b/Source/Engine/Animations/Graph/AnimGraph.Base.cpp @@ -93,6 +93,7 @@ void MultiBlendBucketInit(AnimGraphInstanceData::Bucket& bucket) void BlendPoseBucketInit(AnimGraphInstanceData::Bucket& bucket) { bucket.BlendPose.TransitionPosition = 0.0f; + bucket.BlendPose.BlendPoseIndex = -1; bucket.BlendPose.PreviousBlendPoseIndex = -1; } diff --git a/Source/Engine/Animations/Graph/AnimGraph.h b/Source/Engine/Animations/Graph/AnimGraph.h index e03d84fd9..6c36e37a5 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.h +++ b/Source/Engine/Animations/Graph/AnimGraph.h @@ -239,7 +239,8 @@ public: struct BlendPoseBucket { float TransitionPosition; - int32 PreviousBlendPoseIndex; + int16 BlendPoseIndex; + int16 PreviousBlendPoseIndex; }; struct StateMachineBucket diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index f75a8abd1..c76bddf3f 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -676,9 +676,12 @@ Variant AnimGraphExecutor::Blend(AnimGraphNode* node, const Value& poseA, const if (!ANIM_GRAPH_IS_VALID_PTR(poseB)) nodesB = GetEmptyNodes(); + const Transform* srcA = nodesA->Nodes.Get(); + const Transform* srcB = nodesB->Nodes.Get(); + Transform* dst = nodes->Nodes.Get(); for (int32 i = 0; i < nodes->Nodes.Count(); i++) { - Transform::Lerp(nodesA->Nodes[i], nodesB->Nodes[i], alpha, nodes->Nodes[i]); + Transform::Lerp(srcA[i], srcB[i], alpha, dst[i]); } Transform::Lerp(nodesA->RootMotion, nodesB->RootMotion, alpha, nodes->RootMotion); nodes->Position = Math::Lerp(nodesA->Position, nodesB->Position, alpha); @@ -1263,21 +1266,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu { const auto valueA = tryGetValue(node->GetBox(1), Value::Null); const auto valueB = tryGetValue(node->GetBox(2), Value::Null); - const auto nodes = node->GetNodes(this); - - auto nodesA = static_cast(valueA.AsPointer); - auto nodesB = static_cast(valueB.AsPointer); - if (!ANIM_GRAPH_IS_VALID_PTR(valueA)) - nodesA = GetEmptyNodes(); - if (!ANIM_GRAPH_IS_VALID_PTR(valueB)) - nodesB = GetEmptyNodes(); - - for (int32 i = 0; i < nodes->Nodes.Count(); i++) - { - Transform::Lerp(nodesA->Nodes[i], nodesB->Nodes[i], alpha, nodes->Nodes[i]); - } - Transform::Lerp(nodesA->RootMotion, nodesB->RootMotion, alpha, nodes->RootMotion); - value = nodes; + value = Blend(node, valueA, valueB, alpha, AlphaBlendMode::Linear); } break; @@ -1758,35 +1747,38 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu // [2]: int Pose Count // [3]: AlphaBlendMode Mode - // Prepare auto& bucket = context.Data->State[node->BucketIndex].BlendPose; - const int32 poseIndex = (int32)tryGetValue(node->GetBox(1), node->Values[0]); + const int16 poseIndex = (int32)tryGetValue(node->GetBox(1), node->Values[0]); const float blendDuration = (float)tryGetValue(node->GetBox(2), node->Values[1]); const int32 poseCount = Math::Clamp(node->Values[2].AsInt, 0, MaxBlendPoses); const AlphaBlendMode mode = (AlphaBlendMode)node->Values[3].AsInt; - - // Skip if nothing to blend if (poseCount == 0 || poseIndex < 0 || poseIndex >= poseCount) - { break; + + // Check if swap transition end points + if (bucket.PreviousBlendPoseIndex == poseIndex && bucket.BlendPoseIndex != poseIndex && bucket.TransitionPosition >= ANIM_GRAPH_BLEND_THRESHOLD) + { + bucket.TransitionPosition = blendDuration - bucket.TransitionPosition; + Swap(bucket.BlendPoseIndex, bucket.PreviousBlendPoseIndex); } // Check if transition is not active (first update, pose not changing or transition ended) bucket.TransitionPosition += context.DeltaTime; + bucket.BlendPoseIndex = poseIndex; if (bucket.PreviousBlendPoseIndex == -1 || bucket.PreviousBlendPoseIndex == poseIndex || bucket.TransitionPosition >= blendDuration || blendDuration <= ANIM_GRAPH_BLEND_THRESHOLD) { bucket.TransitionPosition = 0.0f; + bucket.BlendPoseIndex = poseIndex; bucket.PreviousBlendPoseIndex = poseIndex; - value = tryGetValue(node->GetBox(FirstBlendPoseBoxIndex + poseIndex), Value::Null); + value = tryGetValue(node->GetBox(FirstBlendPoseBoxIndex + bucket.BlendPoseIndex), Value::Null); break; } - ASSERT(bucket.PreviousBlendPoseIndex >= 0 && bucket.PreviousBlendPoseIndex < poseCount); // Blend two animations { const float alpha = bucket.TransitionPosition / blendDuration; const auto valueA = tryGetValue(node->GetBox(FirstBlendPoseBoxIndex + bucket.PreviousBlendPoseIndex), Value::Null); - const auto valueB = tryGetValue(node->GetBox(FirstBlendPoseBoxIndex + poseIndex), Value::Null); + const auto valueB = tryGetValue(node->GetBox(FirstBlendPoseBoxIndex + bucket.BlendPoseIndex), Value::Null); value = Blend(node, valueA, valueB, alpha, mode); }