From 093a49c76ab9c82800b97a611c3d874b0d3d8659 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 1 Oct 2026 22:55:27 +0200 Subject: [PATCH] Fix root motion on skeletons with different coordinate system #3847 --- Source/Engine/Animations/AnimationData.h | 2 ++ .../Animations/Graph/AnimGroup.Animation.cpp | 28 +++++++++++++------ 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/Source/Engine/Animations/AnimationData.h b/Source/Engine/Animations/AnimationData.h index 8df86d766..096c544e3 100644 --- a/Source/Engine/Animations/AnimationData.h +++ b/Source/Engine/Animations/AnimationData.h @@ -96,6 +96,8 @@ API_ENUM(Attributes="Flags") enum class AnimationRootMotionFlags : byte RootPosition = RootPositionXZ | RootPositionY, // Root node position and rotation. RootTransform = RootPosition | RootRotation, + // Marks that the root node position is affected by the skeleton transform and should be extracted with transformed mask. Use this option for models that don't use Y-up coordinate system. + LocalPositionMask = 1 << 3, }; DECLARE_ENUM_OPERATORS(AnimationRootMotionFlags); diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index ae2b250e1..272c61b7e 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -499,9 +499,23 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* const bool motionPositionXZ = EnumHasAnyFlags(anim->Data.RootMotionFlags, AnimationRootMotionFlags::RootPositionXZ); const bool motionPositionY = EnumHasAnyFlags(anim->Data.RootMotionFlags, AnimationRootMotionFlags::RootPositionY); const bool motionRotation = EnumHasAnyFlags(anim->Data.RootMotionFlags, AnimationRootMotionFlags::RootRotation); - const Vector3 motionPositionMask(motionPositionXZ ? 1.0f : 0.0f, motionPositionY ? 1.0f : 0.0f, motionPositionXZ ? 1.0f : 0.0f); const bool motionPosition = motionPositionXZ | motionPositionY; + const Vector3 motionPositionMaskRef(motionPositionXZ ? 1.0f : 0.0f, motionPositionY ? 1.0f : 0.0f, motionPositionXZ ? 1.0f : 0.0f); + Vector3 motionPositionMask = motionPositionMaskRef; const int32 rootNodeIndex = GetRootNodeIndex(anim); + auto& skeleton = _graph.BaseModel->Skeleton; + if (motionPosition && EnumHasAnyFlags(anim->Data.RootMotionFlags, AnimationRootMotionFlags::LocalPositionMask)) + { + // Rotate position mask by the rotation of the root node to extract motion in the local space of the model (eg. when skeleton has different coordinate system) + Quaternion rootOrientation = Quaternion::Identity; + int32 parentIndex = skeleton.Nodes[rootNodeIndex].ParentIndex; + while (parentIndex != -1) + { + rootOrientation = rootOrientation * skeleton.Nodes[parentIndex].LocalTransform.Orientation; + parentIndex = skeleton.Nodes[parentIndex].ParentIndex; + } + Vector3::Transform(motionPositionMask, rootOrientation, motionPositionMask); + } const Transform& refPose = emptyNodes->Nodes[rootNodeIndex]; Transform& rootNode = nodes->Nodes[rootNodeIndex]; Transform& dstNode = nodes->RootMotion; @@ -545,12 +559,10 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* } // Convert root motion from local-space to the actor-space (eg. if root node is not actually a root and its parents have rotation/scale) - auto& skeleton = _graph.BaseModel->Skeleton; int32 parentIndex = skeleton.Nodes[rootNodeIndex].ParentIndex; while (parentIndex != -1) { - const Transform& parentNode = nodes->Nodes[parentIndex]; - srcNode.Translation = parentNode.LocalToWorld(srcNode.Translation); + srcNode.Translation = nodes->Nodes[parentIndex].LocalToWorld(srcNode.Translation); parentIndex = skeleton.Nodes[parentIndex].ParentIndex; } } @@ -565,28 +577,28 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* if (mode == ProcessAnimationMode::BlendAdditive) { if (motionPosition) - dstNode.Translation += srcNode.Translation * weight * motionPositionMask; + dstNode.Translation += srcNode.Translation * weight * motionPositionMaskRef; if (motionRotation) BlendAdditiveWeightedRotation(dstNode.Orientation, srcNode.Orientation, weight); } else if (mode == ProcessAnimationMode::Add) { if (motionPosition) - dstNode.Translation += srcNode.Translation * weight * motionPositionMask; + dstNode.Translation += srcNode.Translation * weight * motionPositionMaskRef; if (motionRotation) dstNode.Orientation += srcNode.Orientation * weight; } else if (weighted) { if (motionPosition) - dstNode.Translation = srcNode.Translation * weight * motionPositionMask; + dstNode.Translation = srcNode.Translation * weight * motionPositionMaskRef; if (motionRotation) dstNode.Orientation = srcNode.Orientation * weight; } else { if (motionPosition) - dstNode.Translation = srcNode.Translation * motionPositionMask; + dstNode.Translation = srcNode.Translation * motionPositionMaskRef; if (motionRotation) dstNode.Orientation = srcNode.Orientation; }