Add **Nested Animations** for compositing animation clips

This commit is contained in:
Wojtek Figat
2022-07-31 15:53:09 +02:00
parent 3a39dad30f
commit 3b52914416
15 changed files with 583 additions and 186 deletions

View File

@@ -872,9 +872,17 @@ private:
void ProcessGroupCustom(Box* boxBase, Node* nodeBase, Value& value);
void ProcessGroupFunction(Box* boxBase, Node* node, Value& value);
enum class ProcessAnimationMode
{
Override,
Add,
BlendAdditive,
};
int32 GetRootNodeIndex(Animation* anim);
void ExtractRootMotion(const Animation::NodeToChannel* mapping, int32 rootNodeIndex, Animation* anim, float pos, float prevPos, Transform& rootNode, RootMotionData& rootMotion);
void ProcessAnimEvents(AnimGraphNode* node, bool loop, float length, float startTimePos, float oldTimePos, float animPos, float animPrevPos, Animation* anim, float speed);
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);
Variant SampleAnimationsWithBlend(AnimGraphNode* node, bool loop, float length, float startTimePos, float prevTimePos, float& newTimePos, Animation* animA, Animation* animB, float speedA, float speedB, float alpha);
Variant SampleAnimationsWithBlend(AnimGraphNode* node, bool loop, float length, float startTimePos, float prevTimePos, float& newTimePos, Animation* animA, Animation* animB, Animation* animC, float speedA, float speedB, float speedC, float alphaA, float alphaB, float alphaC);

View File

@@ -88,7 +88,7 @@ void AnimGraphExecutor::ExtractRootMotion(const Animation::NodeToChannel* mappin
rootNode = refPose;
}
void AnimGraphExecutor::ProcessAnimEvents(AnimGraphNode* node, bool loop, float length, float startTimePos, float oldTimePos, float animPos, float animPrevPos, Animation* anim, float speed)
void AnimGraphExecutor::ProcessAnimEvents(AnimGraphNode* node, bool loop, float length, float animPos, float animPrevPos, Animation* anim, float speed)
{
if (anim->Events.Count() == 0)
return;
@@ -96,17 +96,16 @@ void AnimGraphExecutor::ProcessAnimEvents(AnimGraphNode* node, bool loop, float
auto& context = Context.Get();
float eventTimeMin = animPrevPos;
float eventTimeMax = animPos;
if (loop)
if (loop && context.DeltaTime * speed < 0)
{
// Check if animation looped
const float posNotLooped = startTimePos + oldTimePos;
if (posNotLooped < 0.0f || posNotLooped > length)
// Check if animation looped (for anim events shooting during backwards playback)
//const float posNotLooped = startTimePos + oldTimePos;
//if (posNotLooped < 0.0f || posNotLooped > length)
//const int32 animPosCycle = Math::CeilToInt(animPos / anim->GetDuration());
//const int32 animPrevPosCycle = Math::CeilToInt(animPrevPos / anim->GetDuration());
//if (animPosCycle != animPrevPosCycle)
{
if (context.DeltaTime * speed < 0)
{
// Playback backwards
Swap(eventTimeMin, eventTimeMax);
}
Swap(eventTimeMin, eventTimeMax);
}
}
const float eventTime = animPos / static_cast<float>(anim->Data.FramesPerSecond);
@@ -218,49 +217,140 @@ float GetAnimSamplePos(float length, Animation* anim, float pos, float speed)
return animPos;
}
Variant AnimGraphExecutor::SampleAnimation(AnimGraphNode* node, bool loop, float length, float startTimePos, float prevTimePos, float& newTimePos, Animation* anim, float speed)
FORCE_INLINE void GetAnimSamplePos(bool loop, float length, float startTimePos, float prevTimePos, float& newTimePos, float& pos, float& prevPos)
{
// Skip if animation is not ready to use
if (anim == nullptr || !anim->IsLoaded())
return Value::Null;
PROFILE_CPU_ASSET(anim);
const float oldTimePos = prevTimePos;
// Calculate actual time position within the animation node (defined by length and loop mode)
const float pos = GetAnimPos(newTimePos, startTimePos, loop, length);
const float prevPos = GetAnimPos(prevTimePos, startTimePos, loop, length);
pos = GetAnimPos(newTimePos, startTimePos, loop, length);
prevPos = GetAnimPos(prevTimePos, startTimePos, loop, length);
}
void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* node, bool loop, float length, float pos, float prevPos, Animation* anim, float speed, float weight, ProcessAnimationMode mode)
{
PROFILE_CPU_ASSET(anim);
// Get animation position (animation track position for channels sampling)
const float animPos = GetAnimSamplePos(length, anim, pos, speed);
const float animPrevPos = GetAnimSamplePos(length, anim, prevPos, speed);
// Sample the animation
const auto nodes = node->GetNodes(this);
nodes->RootMotion = RootMotionData::Identity;
nodes->Position = pos;
nodes->Length = length;
// Evaluate nested animations
if (anim->NestedAnims.Count() != 0)
{
for (auto& e : anim->NestedAnims)
{
const auto& nestedAnim = e.Second;
float nestedAnimPos = animPos - nestedAnim.Time;
if (nestedAnimPos >= 0.0f &&
nestedAnimPos < nestedAnim.Duration &&
nestedAnim.Enabled &&
nestedAnim.Anim &&
nestedAnim.Anim->IsLoaded())
{
// Get nested animation time position
float nestedAnimPrevPos = animPrevPos - nestedAnim.Time;
const float nestedAnimLength = nestedAnim.Anim->GetLength();
const float nestedAnimDuration = nestedAnim.Anim->GetDuration();
const float nestedAnimSpeed = nestedAnim.Speed * speed;
nestedAnimPos = nestedAnimPos / nestedAnimDuration * nestedAnimSpeed;
nestedAnimPrevPos = nestedAnimPrevPos / nestedAnimDuration * nestedAnimSpeed;
GetAnimSamplePos(nestedAnim.Loop, nestedAnimLength, nestedAnim.StartTime, nestedAnimPrevPos, nestedAnimPos, nestedAnimPos, nestedAnimPrevPos);
ProcessAnimation(nodes, node, true, nestedAnimLength, nestedAnimPos, nestedAnimPrevPos, nestedAnim.Anim, 1.0f, weight, mode);
}
}
}
// Evaluate nodes animations
const auto mapping = anim->GetMapping(_graph.BaseModel);
const bool weighted = weight < 1.0f;
const auto emptyNodes = GetEmptyNodes();
for (int32 i = 0; i < nodes->Nodes.Count(); i++)
{
const int32 nodeToChannel = mapping->At(i);
nodes->Nodes[i] = emptyNodes->Nodes[i];
Transform& dstNode = nodes->Nodes[i];
Transform srcNode = emptyNodes->Nodes[i];
if (nodeToChannel != -1)
{
// Calculate the animated node transformation
anim->Data.Channels[nodeToChannel].Evaluate(animPos, &nodes->Nodes[i], false);
anim->Data.Channels[nodeToChannel].Evaluate(animPos, &srcNode, false);
}
// Blend node
if (mode == ProcessAnimationMode::BlendAdditive)
{
dstNode.Translation += srcNode.Translation * weight;
dstNode.Scale += srcNode.Scale * weight;
BlendAdditiveWeightedRotation(dstNode.Orientation, srcNode.Orientation, weight);
}
else if (mode == ProcessAnimationMode::Add)
{
dstNode.Translation += srcNode.Translation * weight;
dstNode.Scale += srcNode.Scale * weight;
dstNode.Orientation += srcNode.Orientation * weight;
}
else if (weighted)
{
dstNode.Translation = srcNode.Translation * weight;
dstNode.Scale = srcNode.Scale * weight;
dstNode.Orientation = srcNode.Orientation * weight;
}
else
{
dstNode = srcNode;
}
}
// Handle root motion
if (_rootMotionMode != RootMotionMode::NoExtraction && anim->Data.EnableRootMotion)
{
// Calculate the root motion node transformation
const int32 rootNodeIndex = GetRootNodeIndex(anim);
ExtractRootMotion(mapping, rootNodeIndex, anim, animPos, animPrevPos, nodes->Nodes[rootNodeIndex], nodes->RootMotion);
Transform rootNode = emptyNodes->Nodes[rootNodeIndex];
RootMotionData& dstNode = nodes->RootMotion;
RootMotionData srcNode(rootNode);
ExtractRootMotion(mapping, rootNodeIndex, anim, animPos, animPrevPos, rootNode, srcNode);
// Blend root motion
if (mode == ProcessAnimationMode::BlendAdditive)
{
dstNode.Translation += srcNode.Translation * weight;
BlendAdditiveWeightedRotation(dstNode.Rotation, srcNode.Rotation, weight);
}
else if (mode == ProcessAnimationMode::Add)
{
dstNode.Translation += srcNode.Translation * weight;
dstNode.Rotation += srcNode.Rotation * weight;
}
else if (weighted)
{
dstNode.Translation = srcNode.Translation * weight;
dstNode.Rotation = srcNode.Rotation * weight;
}
else
{
dstNode = srcNode;
}
}
// Collect events
ProcessAnimEvents(node, loop, length, startTimePos, oldTimePos, animPos, animPrevPos, anim, speed);
if (weight > 0.5f)
{
ProcessAnimEvents(node, loop, length, animPos, animPrevPos, anim, speed);
}
}
Variant AnimGraphExecutor::SampleAnimation(AnimGraphNode* node, bool loop, float length, float startTimePos, float prevTimePos, float& newTimePos, Animation* anim, float speed)
{
if (anim == nullptr || !anim->IsLoaded())
return Value::Null;
float pos, prevPos;
GetAnimSamplePos(loop, length, startTimePos, prevTimePos, newTimePos, pos, prevPos);
const auto nodes = node->GetNodes(this);
InitNodes(nodes);
nodes->Position = pos;
nodes->Length = length;
ProcessAnimation(nodes, node, loop, length, pos, prevPos, anim, speed);
return nodes;
}
@@ -271,73 +361,28 @@ Variant AnimGraphExecutor::SampleAnimationsWithBlend(AnimGraphNode* node, bool l
if (animA == nullptr || !animA->IsLoaded() ||
animB == nullptr || !animB->IsLoaded())
return Value::Null;
const float oldTimePos = prevTimePos;
// Calculate actual time position within the animation node (defined by length and loop mode)
const float pos = GetAnimPos(newTimePos, startTimePos, loop, length);
const float prevPos = GetAnimPos(prevTimePos, startTimePos, loop, length);
// Get animation position (animation track position for channels sampling)
const float animPosA = GetAnimSamplePos(length, animA, pos, speedA);
const float animPrevPosA = GetAnimSamplePos(length, animA, prevPos, speedA);
const float animPosB = GetAnimSamplePos(length, animB, pos, speedB);
const float animPrevPosB = GetAnimSamplePos(length, animB, prevPos, speedB);
float pos, prevPos;
GetAnimSamplePos(loop, length, startTimePos, prevTimePos, newTimePos, pos, prevPos);
// Sample the animations with blending
const auto nodes = node->GetNodes(this);
nodes->RootMotion = RootMotionData::Identity;
InitNodes(nodes);
nodes->Position = pos;
nodes->Length = length;
const auto mappingA = animA->GetMapping(_graph.BaseModel);
const auto mappingB = animB->GetMapping(_graph.BaseModel);
const auto emptyNodes = GetEmptyNodes();
RootMotionData rootMotionA, rootMotionB;
int32 rootNodeIndexA = -1, rootNodeIndexB = -1;
if (_rootMotionMode != RootMotionMode::NoExtraction)
{
rootMotionA = rootMotionB = RootMotionData::Identity;
if (animA->Data.EnableRootMotion)
rootNodeIndexA = GetRootNodeIndex(animA);
if (animB->Data.EnableRootMotion)
rootNodeIndexB = GetRootNodeIndex(animB);
}
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++)
{
const int32 nodeToChannelA = mappingA->At(i);
const int32 nodeToChannelB = mappingB->At(i);
Transform nodeA = emptyNodes->Nodes[i];
Transform nodeB = nodeA;
// Calculate the animated node transformations
if (nodeToChannelA != -1)
{
animA->Data.Channels[nodeToChannelA].Evaluate(animPosA, &nodeA, false);
if (rootNodeIndexA == i)
ExtractRootMotion(mappingA, rootNodeIndexA, animA, animPosA, animPrevPosA, nodeA, rootMotionA);
}
if (nodeToChannelB != -1)
{
animB->Data.Channels[nodeToChannelB].Evaluate(animPosB, &nodeB, false);
if (rootNodeIndexB == i)
ExtractRootMotion(mappingB, rootNodeIndexB, animB, animPosB, animPrevPosB, nodeB, rootMotionB);
}
// Blend
Transform::Lerp(nodeA, nodeB, alpha, nodes->Nodes[i]);
nodes->Nodes[i].Orientation.Normalize();
}
// Handle root motion
if (_rootMotionMode != RootMotionMode::NoExtraction)
{
RootMotionData::Lerp(rootMotionA, rootMotionB, alpha, nodes->RootMotion);
nodes->RootMotion.Rotation.Normalize();
}
// Collect events
if (alpha > 0.5f)
ProcessAnimEvents(node, loop, length, startTimePos, oldTimePos, animPosB, animPrevPosB, animB, speedB);
else
ProcessAnimEvents(node, loop, length, startTimePos, oldTimePos, animPosA, animPrevPosA, animA, speedA);
return nodes;
}
@@ -348,114 +393,30 @@ Variant AnimGraphExecutor::SampleAnimationsWithBlend(AnimGraphNode* node, bool l
animB == nullptr || !animB->IsLoaded() ||
animC == nullptr || !animC->IsLoaded())
return Value::Null;
const float oldTimePos = prevTimePos;
// Calculate actual time position within the animation node (defined by length and loop mode)
const float pos = GetAnimPos(newTimePos, startTimePos, loop, length);
const float prevPos = GetAnimPos(prevTimePos, startTimePos, loop, length);
// Get animation position (animation track position for channels sampling)
const float animPosA = GetAnimSamplePos(length, animA, pos, speedA);
const float animPrevPosA = GetAnimSamplePos(length, animA, prevPos, speedA);
const float animPosB = GetAnimSamplePos(length, animB, pos, speedB);
const float animPrevPosB = GetAnimSamplePos(length, animB, prevPos, speedB);
const float animPosC = GetAnimSamplePos(length, animC, pos, speedC);
const float animPrevPosC = GetAnimSamplePos(length, animC, prevPos, speedC);
float pos, prevPos;
GetAnimSamplePos(loop, length, startTimePos, prevTimePos, newTimePos, pos, prevPos);
// Sample the animations with blending
const auto nodes = node->GetNodes(this);
nodes->RootMotion = RootMotionData::Identity;
InitNodes(nodes);
nodes->Position = pos;
nodes->Length = length;
const auto mappingA = animA->GetMapping(_graph.BaseModel);
const auto mappingB = animB->GetMapping(_graph.BaseModel);
const auto mappingC = animC->GetMapping(_graph.BaseModel);
Transform tmp, t;
const auto emptyNodes = GetEmptyNodes();
RootMotionData rootMotionA, rootMotionB, rootMotionC;
int32 rootNodeIndexA = -1, rootNodeIndexB = -1, rootNodeIndexC = -1;
if (_rootMotionMode != RootMotionMode::NoExtraction)
{
rootMotionA = rootMotionB = rootMotionC = RootMotionData::Identity;
if (animA->Data.EnableRootMotion)
rootNodeIndexA = GetRootNodeIndex(animA);
if (animB->Data.EnableRootMotion)
rootNodeIndexB = GetRootNodeIndex(animB);
if (animC->Data.EnableRootMotion)
rootNodeIndexC = GetRootNodeIndex(animC);
}
ASSERT(Math::Abs(alphaA + alphaB + alphaC - 1.0f) <= ANIM_GRAPH_BLEND_THRESHOLD); // Assumes weights are normalized
for (int32 i = 0; i < nodes->Nodes.Count(); i++)
{
const int32 nodeToChannelA = mappingA->At(i);
t = emptyNodes->Nodes[i];
if (nodeToChannelA != -1)
{
// Override
tmp = t;
animA->Data.Channels[nodeToChannelA].Evaluate(animPosA, &tmp, false);
if (rootNodeIndexA == i)
ExtractRootMotion(mappingA, rootNodeIndexA, animA, animPosA, animPrevPosA, tmp, rootMotionA);
t.Translation = tmp.Translation * alphaA;
t.Orientation = tmp.Orientation * alphaA;
t.Scale = tmp.Scale * alphaA;
}
nodes->Nodes[i] = t;
}
for (int32 i = 0; i < nodes->Nodes.Count(); i++)
{
const int32 nodeToChannelB = mappingB->At(i);
const int32 nodeToChannelC = mappingC->At(i);
t = nodes->Nodes[i];
if (nodeToChannelB != -1)
{
// Additive
tmp = emptyNodes->Nodes[i];
animB->Data.Channels[nodeToChannelB].Evaluate(animPosB, &tmp, false);
if (rootNodeIndexB == i)
ExtractRootMotion(mappingB, rootNodeIndexB, animB, animPosB, animPrevPosB, tmp, rootMotionB);
t.Translation += tmp.Translation * alphaB;
t.Scale += tmp.Scale * alphaB;
BlendAdditiveWeightedRotation(t.Orientation, tmp.Orientation, alphaB);
}
if (nodeToChannelC != -1)
{
// Additive
tmp = emptyNodes->Nodes[i];
animC->Data.Channels[nodeToChannelC].Evaluate(animPosC, &tmp, false);
if (rootNodeIndexC == i)
ExtractRootMotion(mappingC, rootNodeIndexC, animC, animPosC, animPrevPosC, tmp, rootMotionC);
t.Translation += tmp.Translation * alphaC;
t.Scale += tmp.Scale * alphaC;
BlendAdditiveWeightedRotation(t.Orientation, tmp.Orientation, alphaC);
}
t.Orientation.Normalize();
nodes->Nodes[i] = t;
}
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);
// Handle root motion
// Normalize rotations
for (int32 i = 0; i < nodes->Nodes.Count(); i++)
{
nodes->Nodes[i].Orientation.Normalize();
}
if (_rootMotionMode != RootMotionMode::NoExtraction)
{
nodes->RootMotion.Translation = rootMotionA.Translation * alphaA;
nodes->RootMotion.Rotation = rootMotionA.Rotation * alphaA;
nodes->RootMotion.Translation += rootMotionB.Translation * alphaB;
nodes->RootMotion.Rotation += rootMotionB.Rotation * alphaB;
BlendAdditiveWeightedRotation(nodes->RootMotion.Rotation, rootMotionB.Rotation, alphaB);
nodes->RootMotion.Translation = rootMotionC.Translation * alphaC;
nodes->RootMotion.Rotation = rootMotionC.Rotation * alphaC;
BlendAdditiveWeightedRotation(nodes->RootMotion.Rotation, rootMotionB.Rotation, alphaC);
nodes->RootMotion.Rotation.Normalize();
}
// Collect events
if (alphaC > 0.5f)
ProcessAnimEvents(node, loop, length, startTimePos, oldTimePos, animPosC, animPrevPosC, animC, speedC);
else if (alphaB > 0.5f)
ProcessAnimEvents(node, loop, length, startTimePos, oldTimePos, animPosB, animPrevPosB, animB, speedB);
else
ProcessAnimEvents(node, loop, length, startTimePos, oldTimePos, animPosA, animPrevPosA, animA, speedA);
return nodes;
}
@@ -693,6 +654,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
// Animation
case 0:
{
ANIM_GRAPH_PROFILE_EVENT("Animation");
const float length = anim ? anim->GetLength() : 0.0f;
// Calculate new time position