Refactor Multi Blend animations sampling length to fix blend spaces with anims of different length

This commit is contained in:
Wojtek Figat
2024-10-30 17:24:47 +01:00
parent 4de9e9d918
commit 6e598ae9f6
3 changed files with 227 additions and 98 deletions

View File

@@ -87,8 +87,7 @@ void AnimationBucketInit(AnimGraphInstanceData::Bucket& bucket)
void MultiBlendBucketInit(AnimGraphInstanceData::Bucket& bucket) void MultiBlendBucketInit(AnimGraphInstanceData::Bucket& bucket)
{ {
bucket.MultiBlend.TimePosition = 0.0f; Platform::MemoryClear(&bucket.MultiBlend, sizeof(bucket.MultiBlend));
bucket.MultiBlend.LastUpdateFrame = 0;
} }
void BlendPoseBucketInit(AnimGraphInstanceData::Bucket& bucket) void BlendPoseBucketInit(AnimGraphInstanceData::Bucket& bucket)

View File

@@ -245,7 +245,10 @@ public:
struct MultiBlendBucket struct MultiBlendBucket
{ {
float TimePosition; constexpr static int32 MaxCount = 3; // Up to 3 anims to be used at once in 2D blend space (triangle)
float TimePositions[MaxCount];
ANIM_GRAPH_MULTI_BLEND_INDEX Animations[MaxCount];
byte Count;
uint64 LastUpdateFrame; uint64 LastUpdateFrame;
}; };
@@ -431,22 +434,22 @@ typedef VisjectGraphBox AnimGraphBox;
class AnimGraphNode : public VisjectGraphNode<AnimGraphBox> class AnimGraphNode : public VisjectGraphNode<AnimGraphBox>
{ {
public: public:
struct MultiBlend1DData struct MultiBlendData
{ {
// Amount of blend points. // Amount of blend points.
ANIM_GRAPH_MULTI_BLEND_INDEX Count; ANIM_GRAPH_MULTI_BLEND_INDEX Count;
// The computed length of the mixes animations. Shared for all blend points to provide more stabilization during looped playback. // The computed length of the mixes animations. Shared for all blend points to provide more stabilization during looped playback.
float Length; float Length;
};
struct MultiBlend1DData : MultiBlendData
{
// The indices of the animations to blend. Sorted from the lowest X to the highest X. Contains only valid used animations. Unused items are using index ANIM_GRAPH_MULTI_BLEND_INVALID which is invalid. // The indices of the animations to blend. Sorted from the lowest X to the highest X. Contains only valid used animations. Unused items are using index ANIM_GRAPH_MULTI_BLEND_INVALID which is invalid.
ANIM_GRAPH_MULTI_BLEND_INDEX* IndicesSorted; ANIM_GRAPH_MULTI_BLEND_INDEX* IndicesSorted;
}; };
struct MultiBlend2DData struct MultiBlend2DData : MultiBlendData
{ {
// Amount of blend points.
ANIM_GRAPH_MULTI_BLEND_INDEX Count;
// The computed length of the mixes animations. Shared for all blend points to provide more stabilization during looped playback.
float Length;
// Amount of triangles. // Amount of triangles.
int32 TrianglesCount; int32 TrianglesCount;
// Cached triangles vertices (3 bytes per triangle). Contains list of indices for triangles to use for blending. // Cached triangles vertices (3 bytes per triangle). Contains list of indices for triangles to use for blending.
@@ -862,8 +865,10 @@ private:
void ProcessAnimEvents(AnimGraphNode* node, bool loop, float length, 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, BitArray<InlinedAllocation<8>>* usedNodes = nullptr); 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, BitArray<InlinedAllocation<8>>* usedNodes = nullptr);
Variant SampleAnimation(AnimGraphNode* node, bool loop, float length, float startTimePos, float prevTimePos, float& newTimePos, Animation* anim, float speed); Variant SampleAnimation(AnimGraphNode* node, bool loop, float length, float startTimePos, float prevTimePos, float& newTimePos, Animation* anim, float speed);
Variant SampleAnimation(AnimGraphNode* node, bool loop, float startTimePos, struct AnimSampleData& sample);
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, 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); Variant SampleAnimationsWithBlend(AnimGraphNode* node, bool loop, float startTimePos, AnimSampleData& a, AnimSampleData& b, float alpha);
Variant SampleAnimationsWithBlend(AnimGraphNode* node, bool loop, float startTimePos, AnimSampleData& a, AnimSampleData& b, AnimSampleData& c, float alphaA, float alphaB, float alphaC);
Variant Blend(AnimGraphNode* node, const Value& poseA, const Value& poseB, float alpha, AlphaBlendMode alphaMode); Variant Blend(AnimGraphNode* node, const Value& poseA, const Value& poseB, float alpha, AlphaBlendMode alphaMode);
Variant SampleState(AnimGraphContext& context, const AnimGraphNode* state); Variant SampleState(AnimGraphContext& context, const AnimGraphNode* state);
void InitStateTransition(AnimGraphContext& context, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, AnimGraphStateTransition* transition = nullptr); void InitStateTransition(AnimGraphContext& context, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, AnimGraphStateTransition* transition = nullptr);

View File

@@ -10,6 +10,83 @@
#include "Engine/Animations/InverseKinematics.h" #include "Engine/Animations/InverseKinematics.h"
#include "Engine/Level/Actors/AnimatedModel.h" #include "Engine/Level/Actors/AnimatedModel.h"
struct AnimSampleData
{
Animation* Anim;
float TimePos;
float PrevTimePos;
float Length;
float Speed;
ANIM_GRAPH_MULTI_BLEND_INDEX MultiBlendIndex; // Index of the animation in the multi-blend node data array
AnimSampleData(Animation* anim, float speed = 1.0f, ANIM_GRAPH_MULTI_BLEND_INDEX multiBlendIndex = 0)
: Anim(anim)
, TimePos(0.0f)
, PrevTimePos(0.0f)
, Length(anim->GetLength())
, Speed(speed)
, MultiBlendIndex(multiBlendIndex)
{
}
};
struct MultiBlendAnimData
{
float TimePosition;
ANIM_GRAPH_MULTI_BLEND_INDEX Animation;
typedef Array<MultiBlendAnimData, FixedAllocation<AnimGraphInstanceData::MultiBlendBucket::MaxCount>> List;
static void BeforeSample(const AnimGraphContext& context, const AnimGraphInstanceData::MultiBlendBucket& bucket, const List& prevList, AnimSampleData& sample, float speed = 1.0f)
{
// Find time position in the previous frame
sample.PrevTimePos = 0.0f;
for (const auto& e : prevList)
{
if (e.Animation == sample.MultiBlendIndex)
{
sample.PrevTimePos = e.TimePosition;
break;
}
}
if (speed < 0.0f && bucket.LastUpdateFrame < context.CurrentFrameIndex - 1)
{
// If speed is negative and it's the first node update then start playing from end
sample.PrevTimePos = sample.Length;
}
// Calculate new time position
sample.TimePos = sample.PrevTimePos + context.DeltaTime * speed;
}
static void AfterSample(List& newList, const AnimSampleData& sample)
{
CHECK(newList.Count() < AnimGraphInstanceData::MultiBlendBucket::MaxCount);
// Save animation position for the next frame
newList.Add({ sample.TimePos, sample.MultiBlendIndex });
}
static void GetList(const AnimGraphInstanceData::MultiBlendBucket& multiBlend, List& list)
{
list.Resize(multiBlend.Count);
for (int32 i = 0; i < multiBlend.Count; i++)
list[i] = { multiBlend.TimePositions[i], multiBlend.Animations[i] };
}
static void SetList(AnimGraphInstanceData::MultiBlendBucket& multiBlend, const List& list)
{
multiBlend.Count = list.Count();
for (int32 i = 0; i < multiBlend.Count; i++)
{
auto& e = list[i];
multiBlend.TimePositions[i] = e.TimePosition;
multiBlend.Animations[i] = e.Animation;
}
}
};
namespace namespace
{ {
FORCE_INLINE void BlendAdditiveWeightedRotation(Quaternion& base, Quaternion& additive, float weight) FORCE_INLINE void BlendAdditiveWeightedRotation(Quaternion& base, Quaternion& additive, float weight)
@@ -201,7 +278,7 @@ float GetAnimSamplePos(float length, Animation* anim, float pos, float speed)
// Convert into animation local time (track length may be bigger so fill the gaps with animation clip and include playback speed) // Convert into animation local time (track length may be bigger so fill the gaps with animation clip and include playback speed)
// Also, scale the animation to fit the total animation node length without cut in a middle // Also, scale the animation to fit the total animation node length without cut in a middle
const auto animLength = anim->GetLength(); const auto animLength = anim->GetLength();
const int32 cyclesCount = Math::FloorToInt(length / animLength); const int32 cyclesCount = Math::Max(Math::FloorToInt(length / animLength), 1);
const float cycleLength = animLength * (float)cyclesCount; const float cycleLength = animLength * (float)cyclesCount;
const float adjustRateScale = length / cycleLength; const float adjustRateScale = length / cycleLength;
auto animPos = pos * speed * adjustRateScale; auto animPos = pos * speed * adjustRateScale;
@@ -463,6 +540,11 @@ Variant AnimGraphExecutor::SampleAnimation(AnimGraphNode* node, bool loop, float
return nodes; return nodes;
} }
Variant AnimGraphExecutor::SampleAnimation(AnimGraphNode* node, bool loop, float startTimePos, AnimSampleData& sample)
{
return SampleAnimation(node, loop, sample.Length, startTimePos, sample.PrevTimePos, sample.TimePos, sample.Anim, sample.Speed);
}
Variant AnimGraphExecutor::SampleAnimationsWithBlend(AnimGraphNode* node, bool loop, float length, float startTimePos, float prevTimePos, float& newTimePos, Animation* animA, Animation* animB, float speedA, float speedB, float alpha) Variant AnimGraphExecutor::SampleAnimationsWithBlend(AnimGraphNode* node, bool loop, float length, float startTimePos, float prevTimePos, float& newTimePos, Animation* animA, Animation* animB, float speedA, float speedB, float alpha)
{ {
// Skip if any animation is not ready to use // Skip if any animation is not ready to use
@@ -485,26 +567,53 @@ Variant AnimGraphExecutor::SampleAnimationsWithBlend(AnimGraphNode* node, bool l
return nodes; return nodes;
} }
Variant AnimGraphExecutor::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) Variant AnimGraphExecutor::SampleAnimationsWithBlend(AnimGraphNode* node, bool loop, float startTimePos, AnimSampleData& a, AnimSampleData& b, float alpha)
{ {
// Skip if any animation is not ready to use // Skip if any animation is not ready to use
if (animA == nullptr || !animA->IsLoaded() || if (a.Anim == nullptr || !a.Anim->IsLoaded() ||
animB == nullptr || !animB->IsLoaded() || b.Anim == nullptr || !b.Anim->IsLoaded())
animC == nullptr || !animC->IsLoaded())
return Value::Null; return Value::Null;
float pos, prevPos; // Get actual animation position (includes looping and start offset)
GetAnimSamplePos(loop, length, startTimePos, prevTimePos, newTimePos, pos, prevPos); 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);
// Sample the animations with blending // Sample the animations with blending
const auto nodes = node->GetNodes(this); const auto nodes = node->GetNodes(this);
InitNodes(nodes); InitNodes(nodes);
nodes->Position = pos; nodes->Position = (a.TimePos + b.TimePos) / 2.0f;
nodes->Length = length; nodes->Length = Math::Max(a.Length, b.Length);
ProcessAnimation(nodes, node, loop, a.Length, posA, prevPosA, a.Anim, a.Speed, 1.0f - alpha, ProcessAnimationMode::Override);
ProcessAnimation(nodes, node, loop, b.Length, posB, prevPosB, b.Anim, b.Speed, alpha, ProcessAnimationMode::BlendAdditive);
NormalizeRotations(nodes, _rootMotionMode);
return nodes;
}
Variant AnimGraphExecutor::SampleAnimationsWithBlend(AnimGraphNode* node, bool loop, float startTimePos, AnimSampleData& a, AnimSampleData& b, AnimSampleData& c, float alphaA, float alphaB, float alphaC)
{
// Skip if any animation is not ready to use
if (a.Anim == nullptr || !a.Anim->IsLoaded() ||
b.Anim == nullptr || !b.Anim->IsLoaded() ||
c.Anim == nullptr || !c.Anim->IsLoaded())
return Value::Null;
// 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);
// Sample the animations with blending
const auto nodes = node->GetNodes(this);
InitNodes(nodes);
nodes->Position = (a.TimePos + b.TimePos + c.TimePos) / 3.0f;
nodes->Length = Math::Max(a.Length, b.Length, c.Length);
ASSERT(Math::Abs(alphaA + alphaB + alphaC - 1.0f) <= ANIM_GRAPH_BLEND_THRESHOLD); // Assumes weights are normalized ASSERT(Math::Abs(alphaA + alphaB + alphaC - 1.0f) <= ANIM_GRAPH_BLEND_THRESHOLD); // Assumes weights are normalized
ProcessAnimation(nodes, node, loop, length, pos, prevPos, animA, speedA, alphaA, ProcessAnimationMode::Override); ProcessAnimation(nodes, node, loop, a.Length, posA, prevPosA, a.Anim, a.Speed, alphaA, ProcessAnimationMode::Override);
ProcessAnimation(nodes, node, loop, length, pos, prevPos, animB, speedB, alphaB, ProcessAnimationMode::BlendAdditive); ProcessAnimation(nodes, node, loop, b.Length, posB, prevPosB, b.Anim, b.Speed, alphaB, ProcessAnimationMode::BlendAdditive);
ProcessAnimation(nodes, node, loop, length, pos, prevPos, animC, speedC, alphaC, ProcessAnimationMode::BlendAdditive); ProcessAnimation(nodes, node, loop, c.Length, posC, prevPosC, c.Anim, c.Speed, alphaC, ProcessAnimationMode::BlendAdditive);
NormalizeRotations(nodes, _rootMotionMode); NormalizeRotations(nodes, _rootMotionMode);
return nodes; return nodes;
@@ -666,7 +775,7 @@ void AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const
void ComputeMultiBlendLength(float& length, AnimGraphNode* node) void ComputeMultiBlendLength(float& length, AnimGraphNode* node)
{ {
ANIM_GRAPH_PROFILE_EVENT("Setup Mutli Blend Length"); ANIM_GRAPH_PROFILE_EVENT("Setup Multi Blend Length");
// TODO: lock graph or graph asset here? make it thread safe // TODO: lock graph or graph asset here? make it thread safe
@@ -1246,6 +1355,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
// Multi Blend 1D // Multi Blend 1D
case 12: case 12:
{ {
ANIM_GRAPH_PROFILE_EVENT("Multi Blend 1D");
ASSERT(box->ID == 0); ASSERT(box->ID == 0);
value = Value::Null; value = Value::Null;
@@ -1264,11 +1374,10 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
const auto speed = (float)tryGetValue(node->GetBox(1), node->Values[1]); const auto speed = (float)tryGetValue(node->GetBox(1), node->Values[1]);
const auto loop = (bool)tryGetValue(node->GetBox(2), node->Values[2]); const auto loop = (bool)tryGetValue(node->GetBox(2), node->Values[2]);
const auto startTimePos = (float)tryGetValue(node->GetBox(3), node->Values[3]); const auto startTimePos = (float)tryGetValue(node->GetBox(3), node->Values[3]);
const auto syncLength = false; // TODO: make it configurable via node settings? (change node->Values[2] to contain flags)
auto& data = node->Data.MultiBlend1D; auto& data = node->Data.MultiBlend1D;
// Check if not valid animation binded
if (data.Count == 0) if (data.Count == 0)
break; break; // Skip if no valid animations added
// Get axis X // Get axis X
float x = (float)tryGetValue(node->GetBox(4), Value::Zero); float x = (float)tryGetValue(node->GetBox(4), Value::Zero);
@@ -1287,42 +1396,36 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
if (data.Length <= ZeroTolerance) if (data.Length <= ZeroTolerance)
break; break;
// Calculate new time position MultiBlendAnimData::List prevList, newList;
if (speed < 0.0f && bucket.LastUpdateFrame < context.CurrentFrameIndex - 1) MultiBlendAnimData::GetList(bucket, prevList);
{
// If speed is negative and it's the first node update then start playing from end
bucket.TimePosition = data.Length;
}
float newTimePos = bucket.TimePosition + context.DeltaTime * speed;
ANIM_GRAPH_PROFILE_EVENT("Multi Blend 1D");
// Find 2 animations to blend (line) // Find 2 animations to blend (line)
for (int32 i = 0; i < data.Count - 1; i++) for (int32 i = 0; i < data.Count - 1; i++)
{ {
const auto a = data.IndicesSorted[i]; const auto aIndex = data.IndicesSorted[i];
const auto b = data.IndicesSorted[i + 1]; const auto bIndex = data.IndicesSorted[i + 1];
const auto aData = node->Values[4 + aIndex * 2].AsFloat4();
// Get A animation data AnimSampleData a(node->Assets[aIndex].As<Animation>(), aData.W, aIndex);
const auto aAnim = node->Assets[a].As<Animation>();
auto aData = node->Values[4 + a * 2].AsFloat4();
// Check single A case or the last valid animation // Check single A case or the last valid animation
if (x <= aData.X + ANIM_GRAPH_BLEND_THRESHOLD || b == ANIM_GRAPH_MULTI_BLEND_INVALID) if (x <= aData.X + ANIM_GRAPH_BLEND_THRESHOLD || bIndex == ANIM_GRAPH_MULTI_BLEND_INVALID)
{ {
value = SampleAnimation(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, aAnim, aData.W); MultiBlendAnimData::BeforeSample(context, bucket, prevList, a, speed);
value = SampleAnimation(node, loop, startTimePos, a);
MultiBlendAnimData::AfterSample(newList, a);
break; break;
} }
// Get B animation data // Get B animation data
ASSERT(b != ANIM_GRAPH_MULTI_BLEND_INVALID); auto bData = node->Values[4 + bIndex * 2].AsFloat4();
const auto bAnim = node->Assets[b].As<Animation>(); AnimSampleData b(node->Assets[bIndex].As<Animation>(), bData.W, bIndex);
auto bData = node->Values[4 + b * 2].AsFloat4();
// Check single B edge case // Check single B edge case
if (Math::NearEqual(bData.X, x, ANIM_GRAPH_BLEND_THRESHOLD)) if (Math::NearEqual(bData.X, x, ANIM_GRAPH_BLEND_THRESHOLD))
{ {
value = SampleAnimation(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, bAnim, bData.W); MultiBlendAnimData::BeforeSample(context, bucket, prevList, b, speed);
value = SampleAnimation(node, loop, startTimePos, b);
MultiBlendAnimData::AfterSample(newList, b);
break; break;
} }
@@ -1330,11 +1433,15 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
const float alpha = (x - aData.X) / (bData.X - aData.X); const float alpha = (x - aData.X) / (bData.X - aData.X);
if (alpha > 1.0f) if (alpha > 1.0f)
continue; continue;
value = SampleAnimationsWithBlend(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, aAnim, bAnim, aData.W, bData.W, alpha); MultiBlendAnimData::BeforeSample(context, bucket, prevList, a, speed);
MultiBlendAnimData::BeforeSample(context, bucket, prevList, b, speed);
value = SampleAnimationsWithBlend(node, loop, startTimePos, a, b, alpha);
MultiBlendAnimData::AfterSample(newList, a);
MultiBlendAnimData::AfterSample(newList, b);
break; break;
} }
bucket.TimePosition = newTimePos; MultiBlendAnimData::SetList(bucket, newList);
bucket.LastUpdateFrame = context.CurrentFrameIndex; bucket.LastUpdateFrame = context.CurrentFrameIndex;
break; break;
@@ -1342,6 +1449,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
// Multi Blend 2D // Multi Blend 2D
case 13: case 13:
{ {
ANIM_GRAPH_PROFILE_EVENT("Multi Blend 2D");
ASSERT(box->ID == 0); ASSERT(box->ID == 0);
value = Value::Null; value = Value::Null;
@@ -1360,11 +1468,10 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
const auto speed = (float)tryGetValue(node->GetBox(1), node->Values[1]); const auto speed = (float)tryGetValue(node->GetBox(1), node->Values[1]);
const auto loop = (bool)tryGetValue(node->GetBox(2), node->Values[2]); const auto loop = (bool)tryGetValue(node->GetBox(2), node->Values[2]);
const auto startTimePos = (float)tryGetValue(node->GetBox(3), node->Values[3]); const auto startTimePos = (float)tryGetValue(node->GetBox(3), node->Values[3]);
const auto syncLength = false; // TODO: make it configurable via node settings? (change node->Values[2] to contain flags)
auto& data = node->Data.MultiBlend2D; auto& data = node->Data.MultiBlend2D;
// Check if not valid animation binded
if (data.TrianglesCount == 0) if (data.TrianglesCount == 0)
break; break; // Skip if no valid animations added
// Get axis X // Get axis X
float x = (float)tryGetValue(node->GetBox(4), Value::Zero); float x = (float)tryGetValue(node->GetBox(4), Value::Zero);
@@ -1388,15 +1495,8 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
if (data.Length <= ZeroTolerance) if (data.Length <= ZeroTolerance)
break; break;
// Calculate new time position MultiBlendAnimData::List prevList, newList;
if (speed < 0.0f && bucket.LastUpdateFrame < context.CurrentFrameIndex - 1) MultiBlendAnimData::GetList(bucket, prevList);
{
// If speed is negative and it's the first node update then start playing from end
bucket.TimePosition = data.Length;
}
float newTimePos = bucket.TimePosition + context.DeltaTime * speed;
ANIM_GRAPH_PROFILE_EVENT("Multi Blend 2D");
// Find 3 animations to blend (triangle) // Find 3 animations to blend (triangle)
Float2 p(x, y); Float2 p(x, y);
@@ -1406,23 +1506,21 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
byte bestAnims[2]; byte bestAnims[2];
for (int32 i = 0, t = 0; i < data.TrianglesCount; i++) for (int32 i = 0, t = 0; i < data.TrianglesCount; i++)
{ {
// Get A animation data // Get animations data at vertices
const auto a = data.Triangles[t++]; const auto aIndex = data.Triangles[t++];
const auto aAnim = node->Assets[a].As<Animation>(); const auto bIndex = data.Triangles[t++];
const auto aData = node->Values[4 + a * 2].AsFloat4(); const auto cIndex = data.Triangles[t++];
const auto aData = node->Values[4 + aIndex * 2].AsFloat4();
// Get B animation data const auto bData = node->Values[4 + bIndex * 2].AsFloat4();
const auto b = data.Triangles[t++]; const auto cData = node->Values[4 + cIndex * 2].AsFloat4();
const auto bAnim = node->Assets[b].As<Animation>(); AnimSampleData a(node->Assets[aIndex].As<Animation>(), aData.W, aIndex);
const auto bData = node->Values[4 + b * 2].AsFloat4(); AnimSampleData b(node->Assets[bIndex].As<Animation>(), bData.W, bIndex);
AnimSampleData c(node->Assets[cIndex].As<Animation>(), cData.W, cIndex);
// Get C animation data if (syncLength)
const auto c = data.Triangles[t++]; a.Length = b.Length = c.Length = data.Length;
const auto cAnim = node->Assets[c].As<Animation>();
const auto cData = node->Values[4 + c * 2].AsFloat4();
// Get triangle coords // Get triangle coords
byte anims[3] = { a, b, c }; byte anims[3] = { aIndex, bIndex, cIndex };
Float2 points[3] = { Float2 points[3] = {
Float2(aData.X, aData.Y), Float2(aData.X, aData.Y),
Float2(bData.X, bData.Y), Float2(bData.X, bData.Y),
@@ -1435,19 +1533,25 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
if (Float2::DistanceSquared(p, points[0]) < ANIM_GRAPH_BLEND_THRESHOLD2) if (Float2::DistanceSquared(p, points[0]) < ANIM_GRAPH_BLEND_THRESHOLD2)
{ {
// Use only vertex A // Use only vertex A
value = SampleAnimation(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, aAnim, aData.W); MultiBlendAnimData::BeforeSample(context, bucket, prevList, a, speed);
value = SampleAnimation(node, loop, startTimePos, a);
MultiBlendAnimData::AfterSample(newList, a);
break; break;
} }
if (Float2::DistanceSquared(p, points[1]) < ANIM_GRAPH_BLEND_THRESHOLD2) if (Float2::DistanceSquared(p, points[1]) < ANIM_GRAPH_BLEND_THRESHOLD2)
{ {
// Use only vertex B // Use only vertex B
value = SampleAnimation(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, bAnim, bData.W); MultiBlendAnimData::BeforeSample(context, bucket, prevList, b, speed);
value = SampleAnimation(node, loop, startTimePos, b);
MultiBlendAnimData::AfterSample(newList, b);
break; break;
} }
if (Float2::DistanceSquared(p, points[2]) < ANIM_GRAPH_BLEND_THRESHOLD2) if (Float2::DistanceSquared(p, points[2]) < ANIM_GRAPH_BLEND_THRESHOLD2)
{ {
// Use only vertex C // Use only vertex C
value = SampleAnimation(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, cAnim, cData.W); MultiBlendAnimData::BeforeSample(context, bucket, prevList, c, speed);
value = SampleAnimation(node, loop, startTimePos, c);
MultiBlendAnimData::AfterSample(newList, c);
break; break;
} }
@@ -1468,7 +1572,9 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
if (xAxis && yAxis) if (xAxis && yAxis)
{ {
// Single animation // Single animation
value = SampleAnimation(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, aAnim, aData.W); MultiBlendAnimData::BeforeSample(context, bucket, prevList, a, speed);
value = SampleAnimation(node, loop, startTimePos, a);
MultiBlendAnimData::AfterSample(newList, a);
} }
else if (xAxis || yAxis) else if (xAxis || yAxis)
{ {
@@ -1485,31 +1591,36 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
struct BlendData struct BlendData
{ {
float AlphaX, AlphaY; float AlphaX, AlphaY;
Animation* AnimA, *AnimB; AnimSampleData *SampleA, *SampleB;
const Float4* AnimAd, *AnimBd;
}; };
BlendData blendData; BlendData blendData;
if (v1.Y >= v0.Y) if (v1.Y >= v0.Y)
{ {
if (p.Y < v0.Y && v1.Y >= v0.Y) if (p.Y < v0.Y && v1.Y >= v0.Y)
blendData = { p.Y, v0.Y, aAnim, bAnim, &aData, &bData }; blendData = { p.Y, v0.Y, &a, &b };
else else
blendData = { p.Y - v0.Y, v1.Y - v0.Y, bAnim, cAnim, &bData, &cData }; blendData = { p.Y - v0.Y, v1.Y - v0.Y, &b, &c };
} }
else else
{ {
if (p.Y < v1.Y) if (p.Y < v1.Y)
blendData = { p.Y, v1.Y, aAnim, cAnim, &aData, &cData }; blendData = { p.Y, v1.Y, &a, &c };
else else
blendData = { p.Y - v1.Y, v0.Y - v1.Y, cAnim, bAnim, &cData, &bData }; blendData = { p.Y - v1.Y, v0.Y - v1.Y, &c, &b };
} }
const float alpha = Math::IsZero(blendData.AlphaY) ? 0.0f : blendData.AlphaX / blendData.AlphaY; const float alpha = Math::IsZero(blendData.AlphaY) ? 0.0f : blendData.AlphaX / blendData.AlphaY;
value = SampleAnimationsWithBlend(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, blendData.AnimA, blendData.AnimB, blendData.AnimAd->W, blendData.AnimBd->W, alpha); MultiBlendAnimData::BeforeSample(context, bucket, prevList, *blendData.SampleA, speed);
MultiBlendAnimData::BeforeSample(context, bucket, prevList, *blendData.SampleB, speed);
value = SampleAnimationsWithBlend(node, loop, startTimePos, *blendData.SampleA, *blendData.SampleB, alpha);
MultiBlendAnimData::AfterSample(newList, *blendData.SampleA);
MultiBlendAnimData::AfterSample(newList, *blendData.SampleB);
} }
else else
{ {
// Use only vertex A for invalid triangle // Use only vertex A for invalid triangle
value = SampleAnimation(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, aAnim, aData.W); MultiBlendAnimData::BeforeSample(context, bucket, prevList, a, speed);
value = SampleAnimation(node, loop, startTimePos, a);
MultiBlendAnimData::AfterSample(newList, a);
} }
break; break;
} }
@@ -1518,7 +1629,13 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
const float u = 1.0f - v - w; const float u = 1.0f - v - w;
// Blend A and B and C // Blend A and B and C
value = SampleAnimationsWithBlend(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, aAnim, bAnim, cAnim, aData.W, bData.W, cData.W, u, v, w); MultiBlendAnimData::BeforeSample(context, bucket, prevList, a, speed);
MultiBlendAnimData::BeforeSample(context, bucket, prevList, b, speed);
MultiBlendAnimData::BeforeSample(context, bucket, prevList, c, speed);
value = SampleAnimationsWithBlend(node, loop, startTimePos, a, b, c, u, v, w);
MultiBlendAnimData::AfterSample(newList, a);
MultiBlendAnimData::AfterSample(newList, b);
MultiBlendAnimData::AfterSample(newList, c);
break; break;
} }
@@ -1538,7 +1655,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
const float d = Float2::Distance(s[0], s[1]); const float d = Float2::Distance(s[0], s[1]);
bestWeight = d < ANIM_GRAPH_BLEND_THRESHOLD ? 0 : Float2::Distance(s[0], closest) / d; bestWeight = d < ANIM_GRAPH_BLEND_THRESHOLD ? 0 : Float2::Distance(s[0], closest) / d;
bestAnims[0] = anims[j]; bestAnims[0] = anims[j];
bestAnims[1] = anims[(j + 1) % 3]; bestAnims[1] = anims[(j + 1) % 3];
} }
@@ -1548,23 +1665,31 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
// Check if use the closest sample // Check if use the closest sample
if ((void*)value == nullptr && hasBest) if ((void*)value == nullptr && hasBest)
{ {
const auto aAnim = node->Assets[bestAnims[0]].As<Animation>(); const auto best0Index = bestAnims[0];
const auto aData = node->Values[4 + bestAnims[0] * 2].AsFloat4(); const auto best1Index = bestAnims[1];
const auto best0Data = node->Values[4 + best0Index * 2].AsFloat4();
const auto best1Data = node->Values[4 + best1Index * 2].AsFloat4();
AnimSampleData best0(node->Assets[best0Index].As<Animation>(), best0Data.W, best0Index);
AnimSampleData best1(node->Assets[best1Index].As<Animation>(), best1Data.W, best1Index);
if (syncLength)
best0.Length = best1.Length = data.Length;
// Check if use only one sample // Check if use only one sample
MultiBlendAnimData::BeforeSample(context, bucket, prevList, best0, speed);
if (bestWeight < ANIM_GRAPH_BLEND_THRESHOLD) if (bestWeight < ANIM_GRAPH_BLEND_THRESHOLD)
{ {
value = SampleAnimation(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, aAnim, aData.W); value = SampleAnimation(node, loop, startTimePos, best0);
} }
else else
{ {
const auto bAnim = node->Assets[bestAnims[1]].As<Animation>(); MultiBlendAnimData::BeforeSample(context, bucket, prevList, best1, speed);
const auto bData = node->Values[4 + bestAnims[1] * 2].AsFloat4(); value = SampleAnimationsWithBlend(node, loop, startTimePos, best0, best1, bestWeight);
value = SampleAnimationsWithBlend(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, aAnim, bAnim, aData.W, bData.W, bestWeight); MultiBlendAnimData::AfterSample(newList, best1);
} }
MultiBlendAnimData::AfterSample(newList, best0);
} }
bucket.TimePosition = newTimePos; MultiBlendAnimData::SetList(bucket, newList);
bucket.LastUpdateFrame = context.CurrentFrameIndex; bucket.LastUpdateFrame = context.CurrentFrameIndex;
break; break;
@@ -2331,7 +2456,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
} }
break; break;
} }
if (bucket.LoopsLeft == 0 && slot.BlendOutTime > 0.0f && length - slot.BlendOutTime < bucket.TimePosition) if (bucket.LoopsLeft == 0 && slot.BlendOutTime > 0.0f && length - slot.BlendOutTime < bucket.TimePosition)
{ {
// Blend out // Blend out