Improve Root Motion extraction and playback

This commit is contained in:
Wojtek Figat
2023-05-04 15:10:55 +02:00
parent 57052b3b14
commit c91bc0d8c6
2 changed files with 68 additions and 70 deletions

View File

@@ -831,7 +831,6 @@ private:
};
int32 GetRootNodeIndex(Animation* anim);
void ExtractRootMotion(Span<int32> mapping, int32 rootNodeIndex, Animation* anim, float pos, float prevPos, Transform& rootNode, Transform& rootMotion);
void ProcessAnimEvents(AnimGraphNode* node, bool loop, float length, float animPos, float animPrevPos, Animation* anim, float speed);
void ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* node, bool loop, float length, float pos, float prevPos, Animation* anim, float speed, float weight = 1.0f, ProcessAnimationMode mode = ProcessAnimationMode::Override);
Variant SampleAnimation(AnimGraphNode* node, bool loop, float length, float startTimePos, float prevTimePos, float& newTimePos, Animation* anim, float speed);

View File

@@ -19,6 +19,18 @@ namespace
additive *= -1;
base += additive;
}
FORCE_INLINE void NormalizeRotations(AnimGraphImpulse* nodes, RootMotionMode rootMotionMode)
{
for (int32 i = 0; i < nodes->Nodes.Count(); i++)
{
nodes->Nodes[i].Orientation.Normalize();
}
if (rootMotionMode != RootMotionMode::NoExtraction)
{
nodes->RootMotion.Orientation.Normalize();
}
}
}
void RetargetSkeletonNode(const SkeletonData& sourceSkeleton, const SkeletonData& targetSkeleton, const SkinnedModel::SkeletonMapping& mapping, Transform& node, int32 i)
@@ -58,52 +70,6 @@ int32 AnimGraphExecutor::GetRootNodeIndex(Animation* anim)
return rootNodeIndex;
}
void AnimGraphExecutor::ExtractRootMotion(const Span<int32> mapping, int32 rootNodeIndex, Animation* anim, float pos, float prevPos, Transform& rootNode, Transform& rootMotion)
{
const Transform& refPose = GetEmptyNodes()->Nodes[rootNodeIndex];
const int32 nodeToChannel = mapping[rootNodeIndex];
if (_rootMotionMode == RootMotionMode::Enable && nodeToChannel != -1)
{
// Get the root bone transformation
Transform rootBefore = refPose;
const NodeAnimationData& rootChannel = anim->Data.Channels[nodeToChannel];
rootChannel.Evaluate(prevPos, &rootBefore, false);
// Check if animation looped
if (pos < prevPos)
{
const float length = anim->GetLength();
const float endPos = length * static_cast<float>(anim->Data.FramesPerSecond);
const float timeToEnd = endPos - prevPos;
Transform rootBegin = refPose;
rootChannel.Evaluate(0, &rootBegin, false);
Transform rootEnd = refPose;
rootChannel.Evaluate(endPos, &rootEnd, false);
//rootChannel.Evaluate(pos - timeToEnd, &rootNow, true);
// Complex motion calculation to preserve the looped movement
// (end - before + now - begin)
// It sums the motion since the last update to anim end and since the start to now
rootMotion.Translation = rootEnd.Translation - rootBefore.Translation + rootNode.Translation - rootBegin.Translation;
rootMotion.Orientation = rootEnd.Orientation * rootBefore.Orientation.Conjugated() * (rootNode.Orientation * rootBegin.Orientation.Conjugated());
//rootMotion.Orientation = Quaternion::Identity;
}
else
{
// Simple motion delta
// (now - before)
rootMotion.Translation = rootNode.Translation - rootBefore.Translation;
rootMotion.Orientation = rootBefore.Orientation.Conjugated() * rootNode.Orientation;
}
}
// Remove root node motion after extraction
rootNode = refPose;
}
void AnimGraphExecutor::ProcessAnimEvents(AnimGraphNode* node, bool loop, float length, float animPos, float animPrevPos, Animation* anim, float speed)
{
if (anim->Events.Count() == 0)
@@ -334,10 +300,60 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
{
// Calculate the root motion node transformation
const int32 rootNodeIndex = GetRootNodeIndex(anim);
Transform rootNode = emptyNodes->Nodes[rootNodeIndex];
const Transform& refPose = emptyNodes->Nodes[rootNodeIndex];
Transform& rootNode = nodes->Nodes[rootNodeIndex];
Transform& dstNode = nodes->RootMotion;
Transform srcNode(rootNode);
ExtractRootMotion(mapping.NodesMapping, rootNodeIndex, anim, animPos, animPrevPos, rootNode, srcNode);
Transform srcNode = Transform::Identity;
const int32 nodeToChannel = mapping.NodesMapping[rootNodeIndex];
if (_rootMotionMode == RootMotionMode::Enable && nodeToChannel != -1)
{
// Get the root bone transformation
Transform rootBefore = refPose;
const NodeAnimationData& rootChannel = anim->Data.Channels[nodeToChannel];
rootChannel.Evaluate(animPrevPos, &rootBefore, false);
// Check if animation looped
if (animPos < animPrevPos)
{
const float endPos = anim->GetLength() * static_cast<float>(anim->Data.FramesPerSecond);
const float timeToEnd = endPos - animPrevPos;
Transform rootBegin = refPose;
rootChannel.Evaluate(0, &rootBegin, false);
Transform rootEnd = refPose;
rootChannel.Evaluate(endPos, &rootEnd, false);
//rootChannel.Evaluate(animPos - timeToEnd, &rootNow, true);
// Complex motion calculation to preserve the looped movement
// (end - before + now - begin)
// It sums the motion since the last update to anim end and since the start to now
srcNode.Translation = rootEnd.Translation - rootBefore.Translation + rootNode.Translation - rootBegin.Translation;
srcNode.Orientation = rootEnd.Orientation * rootBefore.Orientation.Conjugated() * (rootNode.Orientation * rootBegin.Orientation.Conjugated());
//srcNode.Orientation = Quaternion::Identity;
}
else
{
// Simple motion delta
// (now - before)
srcNode.Translation = rootNode.Translation - rootBefore.Translation;
srcNode.Orientation = rootBefore.Orientation.Conjugated() * rootNode.Orientation;
}
// 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);
parentIndex = skeleton.Nodes[parentIndex].ParentIndex;
}
}
// Remove root node motion after extraction
rootNode = refPose;
// Blend root motion
if (mode == ProcessAnimationMode::BlendAdditive)
@@ -381,6 +397,7 @@ Variant AnimGraphExecutor::SampleAnimation(AnimGraphNode* node, bool loop, float
nodes->Position = pos;
nodes->Length = length;
ProcessAnimation(nodes, node, loop, length, pos, prevPos, anim, speed);
NormalizeRotations(nodes, _rootMotionMode);
return nodes;
}
@@ -402,16 +419,7 @@ Variant AnimGraphExecutor::SampleAnimationsWithBlend(AnimGraphNode* node, bool l
nodes->Length = length;
ProcessAnimation(nodes, node, loop, length, pos, prevPos, animA, speedA, 1.0f - alpha, ProcessAnimationMode::Override);
ProcessAnimation(nodes, node, loop, length, pos, prevPos, animB, speedB, alpha, ProcessAnimationMode::BlendAdditive);
// Normalize rotations
for (int32 i = 0; i < nodes->Nodes.Count(); i++)
{
nodes->Nodes[i].Orientation.Normalize();
}
if (_rootMotionMode != RootMotionMode::NoExtraction)
{
nodes->RootMotion.Orientation.Normalize();
}
NormalizeRotations(nodes, _rootMotionMode);
return nodes;
}
@@ -436,16 +444,7 @@ Variant AnimGraphExecutor::SampleAnimationsWithBlend(AnimGraphNode* node, bool l
ProcessAnimation(nodes, node, loop, length, pos, prevPos, animA, speedA, alphaA, ProcessAnimationMode::Override);
ProcessAnimation(nodes, node, loop, length, pos, prevPos, animB, speedB, alphaB, ProcessAnimationMode::BlendAdditive);
ProcessAnimation(nodes, node, loop, length, pos, prevPos, animC, speedC, alphaC, ProcessAnimationMode::BlendAdditive);
// Normalize rotations
for (int32 i = 0; i < nodes->Nodes.Count(); i++)
{
nodes->Nodes[i].Orientation.Normalize();
}
if (_rootMotionMode != RootMotionMode::NoExtraction)
{
nodes->RootMotion.Orientation.Normalize();
}
NormalizeRotations(nodes, _rootMotionMode);
return nodes;
}