Files
FlaxEngine/Source/Engine/Animations/Graph/AnimGraph.cpp

491 lines
16 KiB
C++

// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#include "AnimGraph.h"
#include "Engine/Animations/Animations.h"
#include "Engine/Animations/AnimEvent.h"
#include "Engine/Content/Assets/SkinnedModel.h"
#include "Engine/Graphics/Models/SkeletonData.h"
#include "Engine/Scripting/Scripting.h"
extern void RetargetSkeletonNode(const SkeletonData& sourceSkeleton, const SkeletonData& targetSkeleton, const SkinnedModel::SkeletonMapping& sourceMapping, Transform& node, int32 i);
ThreadLocal<AnimGraphContext*> AnimGraphExecutor::Context;
Transform AnimGraphImpulse::GetNodeModelTransformation(SkeletonData& skeleton, int32 nodeIndex) const
{
const int32 parentIndex = skeleton.Nodes[nodeIndex].ParentIndex;
if (parentIndex == -1)
{
return Nodes[nodeIndex];
}
const Transform parentTransform = GetNodeModelTransformation(skeleton, parentIndex);
return parentTransform.LocalToWorld(Nodes[nodeIndex]);
}
void AnimGraphImpulse::SetNodeModelTransformation(SkeletonData& skeleton, int32 nodeIndex, const Transform& value)
{
const int32 parentIndex = skeleton.Nodes[nodeIndex].ParentIndex;
if (parentIndex == -1)
{
Nodes[nodeIndex] = value;
return;
}
const Transform parentTransform = GetNodeModelTransformation(skeleton, parentIndex);
parentTransform.WorldToLocal(value, Nodes[nodeIndex]);
}
void AnimGraphInstanceData::Clear()
{
ClearState();
Slots.Clear();
Parameters.Resize(0);
}
void AnimGraphInstanceData::ClearState()
{
for (const auto& e : ActiveEvents)
OutgoingEvents.Add(e.End((AnimatedModel*)Object));
ActiveEvents.Clear();
InvokeAnimEvents();
Version = 0;
LastUpdateTime = -1;
CurrentFrame = 0;
RootTransform = Transform::Identity;
RootMotion = Transform::Identity;
State.Resize(0);
NodesPose.Resize(0);
TraceEvents.Clear();
}
void AnimGraphInstanceData::Invalidate()
{
LastUpdateTime = -1;
CurrentFrame = 0;
}
void AnimGraphInstanceData::InvokeAnimEvents()
{
const bool all = IsInMainThread();
for (int32 i = 0; i < OutgoingEvents.Count(); i++)
{
const OutgoingEvent e = OutgoingEvents[i];
if (all || e.Instance->Async)
{
OutgoingEvents.RemoveAtKeepOrder(i);
switch (e.Type)
{
case OutgoingEvent::OnEvent:
e.Instance->OnEvent(e.Actor, e.Anim, e.Time, e.DeltaTime);
break;
case OutgoingEvent::OnBegin:
((AnimContinuousEvent*)e.Instance)->OnBegin(e.Actor, e.Anim, e.Time, e.DeltaTime);
break;
case OutgoingEvent::OnEnd:
((AnimContinuousEvent*)e.Instance)->OnEnd(e.Actor, e.Anim, e.Time, e.DeltaTime);
break;
}
}
}
}
AnimGraphInstanceData::OutgoingEvent AnimGraphInstanceData::ActiveEvent::End(AnimatedModel* actor) const
{
OutgoingEvent out;
out.Instance = Instance;
out.Actor = actor;
out.Anim = Anim;
out.Time = 0.0f;
out.DeltaTime = 0.0f;
out.Type = OutgoingEvent::OnEnd;
return out;
}
AnimGraphNode::~AnimGraphNode()
{
// Free allocated memory
switch (GroupID)
{
// Animation
case 9:
switch (TypeID)
{
// Multi Blend 1D
case 12:
Allocator::Free(Data.MultiBlend1D.IndicesSorted);
break;
// Multi Blend 2D
case 13:
Allocator::Free(Data.MultiBlend2D.Triangles);
break;
// State
case 20:
// Any State
case 34:
Allocator::Free(Data.State.Transitions);
break;
}
break;
}
}
AnimGraphImpulse* AnimGraphNode::GetNodes(AnimGraphExecutor* executor)
{
auto& context = *AnimGraphExecutor::Context.Get();
const int32 count = executor->_skeletonNodesCount;
if (context.PoseCacheSize == context.PoseCache.Count())
context.PoseCache.AddOne();
auto& nodes = context.PoseCache[context.PoseCacheSize++];
nodes.Nodes.Resize(count, false);
return &nodes;
}
bool AnimGraph::Load(ReadStream* stream, bool loadMeta)
{
Version++;
_bucketsCounter = 0;
_customNodes.Clear();
// Base
if (AnimGraphBase::Load(stream, loadMeta))
return true;
if (!_isFunction)
{
// Check if has a proper parameters setup
if (Parameters.IsEmpty())
{
LOG(Warning, "Missing Animation Graph parameters.");
return true;
}
if (_rootNode == nullptr)
{
LOG(Warning, "Missing Animation Graph output node.");
return true;
}
if (BaseModel == nullptr)
{
LOG(Warning, "Missing Base Model asset for the Animation Graph. Animation won't be played.");
}
}
// Register for scripts reloading events (only if using any custom nodes)
// Handle load event always because anim graph asset may be loaded before game scripts
if (_customNodes.HasItems() && !_isRegisteredForScriptingEvents)
{
_isRegisteredForScriptingEvents = true;
#if USE_EDITOR
Scripting::ScriptsReloading.Bind<AnimGraph, &AnimGraph::OnScriptsReloading>(this);
Scripting::ScriptsReloaded.Bind<AnimGraph, &AnimGraph::OnScriptsReloaded>(this);
#endif
Scripting::ScriptsLoaded.Bind<AnimGraph, &AnimGraph::OnScriptsLoaded>(this);
}
return false;
}
AnimGraph::~AnimGraph()
{
// Unregister for scripts reloading events (only if using any custom nodes)
if (_isRegisteredForScriptingEvents)
{
#if USE_EDITOR
Scripting::ScriptsReloading.Unbind<AnimGraph, &AnimGraph::OnScriptsReloading>(this);
Scripting::ScriptsReloaded.Unbind<AnimGraph, &AnimGraph::OnScriptsReloaded>(this);
#endif
Scripting::ScriptsLoaded.Unbind<AnimGraph, &AnimGraph::OnScriptsLoaded>(this);
}
}
bool AnimGraph::onParamCreated(Parameter* p)
{
if (p->Identifier == ANIM_GRAPH_PARAM_BASE_MODEL_ID)
{
// Perform validation
if ((p->Type.Type != VariantType::Asset && p->Type.Type != VariantType::Null) || p->IsPublic)
{
LOG(Warning, "Invalid Base Model parameter from the Animation Graph.");
return true;
}
// Peek the value
BaseModel = (Guid)p->Value;
}
return Graph::onParamCreated(p);
}
AnimGraphExecutor::AnimGraphExecutor(AnimGraph& graph)
: _graph(graph)
{
_perGroupProcessCall[6] = (ProcessBoxHandler)&AnimGraphExecutor::ProcessGroupParameters;
_perGroupProcessCall[7] = (ProcessBoxHandler)&AnimGraphExecutor::ProcessGroupTools;
_perGroupProcessCall[9] = (ProcessBoxHandler)&AnimGraphExecutor::ProcessGroupAnimation;
_perGroupProcessCall[13] = (ProcessBoxHandler)&AnimGraphExecutor::ProcessGroupCustom;
_perGroupProcessCall[16] = (ProcessBoxHandler)&AnimGraphExecutor::ProcessGroupFunction;
}
void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt)
{
ASSERT(data.Parameters.Count() == _graph.Parameters.Count());
// Initialize
auto& skeleton = _graph.BaseModel->Skeleton;
auto& contextPtr = Context.Get();
if (!contextPtr)
contextPtr = New<AnimGraphContext>();
auto& context = *contextPtr;
{
ANIM_GRAPH_PROFILE_EVENT("Init");
// Init data from base model
_skeletonNodesCount = skeleton.Nodes.Count();
_rootMotionMode = (RootMotionExtraction)(int32)_graph._rootNode->Values[0];
// Prepare context data for the evaluation
context.GraphStack.Clear();
context.GraphStack.Push((Graph*)&_graph);
context.NodePath.Clear();
context.Data = &data;
context.DeltaTime = dt;
context.StackOverFlow = false;
context.CurrentFrameIndex = ++data.CurrentFrame;
context.CallStack.Clear();
context.Functions.Clear();
context.PoseCacheSize = 0;
context.ValueCache.Clear();
// Prepare instance data
if (data.Version != _graph.Version)
{
data.ClearState();
data.Version = _graph.Version;
}
if (data.State.Count() != _graph.BucketsCountTotal)
{
// Prepare memory for buckets state information
data.State.Resize(_graph.BucketsCountTotal, false);
// Initialize buckets
ResetBuckets(context, &_graph);
}
for (auto& e : data.ActiveEvents)
e.Hit = false;
data.TraceEvents.Clear();
// Init empty nodes data
context.EmptyNodes.RootMotion = Transform::Identity;
context.EmptyNodes.Position = 0.0f;
context.EmptyNodes.Length = 0.0f;
context.EmptyNodes.Nodes.Resize(_skeletonNodesCount, false);
for (int32 i = 0; i < _skeletonNodesCount; i++)
context.EmptyNodes.Nodes[i] = skeleton.Nodes[i].LocalTransform;
}
// Update the animation graph and gather skeleton nodes transformations in nodes local space
AnimGraphImpulse* animResult = nullptr;
{
ANIM_GRAPH_PROFILE_EVENT("Evaluate");
const Variant result = eatBox((Node*)_graph._rootNode, &_graph._rootNode->Boxes[0]);
switch (result.Type.Type)
{
case VariantType::Pointer:
animResult = (AnimGraphImpulse*)result.AsPointer;
break;
case ValueType::Null:
break;
default:
//LOG(Warning, "Invalid animation update result type {0}", result.Type.ToString());
break;
}
if (animResult == nullptr)
animResult = GetEmptyNodes();
}
if (data.ActiveEvents.Count() != 0)
{
ANIM_GRAPH_PROFILE_EVENT("Events");
for (int32 i = data.ActiveEvents.Count() - 1; i >= 0; i--)
{
const auto& e = data.ActiveEvents[i];
if (!e.Hit)
{
// Remove active event that was not hit during this frame (eg. animation using it was not used in blending)
data.OutgoingEvents.Add(e.End((AnimatedModel*)context.Data->Object));
data.ActiveEvents.RemoveAt(i);
}
}
}
#if !BUILD_RELEASE
{
// Perform sanity check on nodes pose to prevent crashes due to NaNs
bool anyInvalid = animResult->RootMotion.IsNanOrInfinity();
for (int32 i = 0; i < animResult->Nodes.Count(); i++)
anyInvalid |= animResult->Nodes.Get()[i].IsNanOrInfinity();
if (anyInvalid)
{
LOG(Error, "Animated Model pose contains NaNs due to animations sampling/blending bug.");
context.Data = nullptr;
return;
}
}
#endif
SkeletonData* animResultSkeleton = &skeleton;
// Retarget animation when using output pose from other skeleton
AnimGraphImpulse retargetNodes;
if (_graph.BaseModel != data.NodesSkeleton)
{
ANIM_GRAPH_PROFILE_EVENT("Retarget");
// Init nodes for the target skeleton
auto& targetSkeleton = data.NodesSkeleton->Skeleton;
retargetNodes = *animResult;
retargetNodes.Nodes.Resize(targetSkeleton.Nodes.Count());
Transform* targetNodes = retargetNodes.Nodes.Get();
for (int32 i = 0; i < retargetNodes.Nodes.Count(); i++)
targetNodes[i] = targetSkeleton.Nodes[i].LocalTransform;
// Use skeleton mapping
const SkinnedModel::SkeletonMapping mapping = data.NodesSkeleton->GetSkeletonMapping(_graph.BaseModel);
if (mapping.NodesMapping.IsValid())
{
const auto& sourceSkeleton = _graph.BaseModel->Skeleton;
Transform* sourceNodes = animResult->Nodes.Get();
for (int32 i = 0; i < retargetNodes.Nodes.Count(); i++)
{
const int32 nodeToNode = mapping.NodesMapping[i];
if (nodeToNode != -1)
{
Transform node = sourceNodes[nodeToNode];
RetargetSkeletonNode(sourceSkeleton, targetSkeleton, mapping, node, i);
targetNodes[i] = node;
}
}
}
animResult = &retargetNodes;
animResultSkeleton = &targetSkeleton;
}
// Allow for external override of the local pose (eg. by the ragdoll)
data.LocalPoseOverride(animResult);
// Calculate the global poses for the skeleton nodes
{
ANIM_GRAPH_PROFILE_EVENT("Global Pose");
ASSERT(animResultSkeleton->Nodes.Count() == animResult->Nodes.Count());
data.NodesPose.Resize(animResultSkeleton->Nodes.Count(), false);
Transform* nodesTransformations = animResult->Nodes.Get();
// Note: this assumes that nodes are sorted (parents first)
for (int32 nodeIndex = 0; nodeIndex < animResultSkeleton->Nodes.Count(); nodeIndex++)
{
const int32 parentIndex = animResultSkeleton->Nodes[nodeIndex].ParentIndex;
if (parentIndex != -1)
{
nodesTransformations[parentIndex].LocalToWorld(nodesTransformations[nodeIndex], nodesTransformations[nodeIndex]);
}
nodesTransformations[nodeIndex].GetWorld(data.NodesPose[nodeIndex]);
}
// Process the root node transformation and the motion
data.RootTransform = nodesTransformations[0];
data.RootMotion = animResult->RootMotion;
}
// Invoke any async anim events
context.Data->InvokeAnimEvents();
// Cleanup
context.Data = nullptr;
}
void AnimGraphExecutor::GetInputValue(Box* box, Value& result)
{
result = eatBox(box->GetParent<Node>(), box->FirstConnection());
}
AnimGraphImpulse* AnimGraphExecutor::GetEmptyNodes()
{
return &Context.Get()->EmptyNodes;
}
void AnimGraphExecutor::InitNodes(AnimGraphImpulse* nodes) const
{
const auto& emptyNodes = Context.Get()->EmptyNodes;
Platform::MemoryCopy(nodes->Nodes.Get(), emptyNodes.Nodes.Get(), sizeof(Transform) * _skeletonNodesCount);
nodes->RootMotion = emptyNodes.RootMotion;
nodes->Position = emptyNodes.Position;
nodes->Length = emptyNodes.Length;
}
void AnimGraphExecutor::ResetBuckets(AnimGraphContext& context, AnimGraphBase* graph)
{
if (graph == nullptr)
return;
auto& state = context.Data->State;
for (int32 i = 0; i < graph->BucketsCountTotal; i++)
{
const int32 bucketIndex = graph->BucketsStart + i;
_graph._bucketInitializerList[bucketIndex](state[bucketIndex]);
}
}
VisjectExecutor::Value AnimGraphExecutor::eatBox(Node* caller, Box* box)
{
auto& context = *Context.Get();
// Check if graph is looped or is too deep
if (context.StackOverFlow)
return Value::Zero;
if (context.CallStack.Count() >= ANIM_GRAPH_MAX_CALL_STACK)
{
OnError(caller, box, TEXT("Graph is looped or too deep!"));
context.StackOverFlow = true;
return Value::Zero;
}
#if !BUILD_RELEASE
if (box == nullptr)
{
OnError(caller, box, TEXT("Null graph box!"));
return Value::Zero;
}
#endif
// Add to the calling stack
context.CallStack.Add(caller);
#if USE_EDITOR
Animations::DebugFlowInfo flowInfo;
flowInfo.Asset = _graph._owner;
flowInfo.Instance = context.Data->Object;
flowInfo.NodeId = box->GetParent<Node>()->ID;
flowInfo.BoxId = box->ID;
const auto* nodePath = context.NodePath.Get();
for (int32 i = 0; i < context.NodePath.Count(); i++)
flowInfo.NodePath[i] = nodePath[i];
Animations::DebugFlow(flowInfo);
#endif
// Call per group custom processing event
Value value;
const auto parentNode = box->GetParent<Node>();
const ProcessBoxHandler func = _perGroupProcessCall[parentNode->GroupID];
(this->*func)(box, parentNode, value);
// Remove from the calling stack
context.CallStack.RemoveLast();
return value;
}
VisjectExecutor::Graph* AnimGraphExecutor::GetCurrentGraph() const
{
auto& context = *Context.Get();
return context.GraphStack.Peek();
}