Files
UnrealEngineUWP/Engine/Source/Runtime/AnimGraphRuntime/Private/BoneControllers/AnimNode_SkeletalControlBase.cpp
Thomas Sarkanen 129df2a031 Anim node communication/messaging system
This takes the idea behind Braeden's node tracker/ancestor system and makes it a bit more generic, removing the bits of the API that were bound to FAnimNode_Base and allowing easier cross-plugin message dependencies.
The idea is that at any scope in the graph update you can add a TScopedGraphMessage with a specified payload (which can just be a node, but can also have state of its own), then further towards the leaves, that message can be detected and acted upon.
This changelist includes a re-working of the inter-node communication around inertial blending and cached poses as examples.

#rb Braeden.Shosa,Jurre.deBaare
#jira none

[CL 14640646 by Thomas Sarkanen in ue5-main branch]
2020-11-03 06:29:42 -04:00

197 lines
6.7 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "BoneControllers/AnimNode_SkeletalControlBase.h"
#include "Animation/AnimInstanceProxy.h"
#include "Engine/SkeletalMeshSocket.h"
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Socket Reference
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void FSocketReference::InitializeSocketInfo(const FAnimInstanceProxy* InAnimInstanceProxy)
{
CachedSocketMeshBoneIndex = INDEX_NONE;
CachedSocketCompactBoneIndex = FCompactPoseBoneIndex(INDEX_NONE);
if (SocketName != NAME_None)
{
const USkeletalMeshComponent* OwnerMeshComponent = InAnimInstanceProxy->GetSkelMeshComponent();
if (OwnerMeshComponent && OwnerMeshComponent->DoesSocketExist(SocketName))
{
USkeletalMeshSocket const* const Socket = OwnerMeshComponent->GetSocketByName(SocketName);
if (Socket)
{
CachedSocketLocalTransform = Socket->GetSocketLocalTransform();
// cache mesh bone index, so that we know this is valid information to follow
CachedSocketMeshBoneIndex = OwnerMeshComponent->GetBoneIndex(Socket->BoneName);
ensureMsgf(CachedSocketMeshBoneIndex != INDEX_NONE, TEXT("%s : socket has invalid bone."), *SocketName.ToString());
}
}
else
{
// @todo : move to graph node warning
UE_LOG(LogAnimation, Warning, TEXT("%s: socket doesn't exist"), *SocketName.ToString());
}
}
}
void FSocketReference::InitialzeCompactBoneIndex(const FBoneContainer& RequiredBones)
{
if (CachedSocketMeshBoneIndex != INDEX_NONE)
{
const int32 SocketBoneSkeletonIndex = RequiredBones.GetPoseToSkeletonBoneIndexArray()[CachedSocketMeshBoneIndex];
CachedSocketCompactBoneIndex = RequiredBones.GetCompactPoseIndexFromSkeletonIndex(SocketBoneSkeletonIndex);
}
}
/////////////////////////////////////////////////////
// FAnimNode_SkeletalControlBase
void FAnimNode_SkeletalControlBase::Initialize_AnyThread(const FAnimationInitializeContext& Context)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(Initialize_AnyThread)
FAnimNode_Base::Initialize_AnyThread(Context);
ComponentPose.Initialize(Context);
AlphaBoolBlend.Reinitialize();
AlphaScaleBiasClamp.Reinitialize();
}
void FAnimNode_SkeletalControlBase::CacheBones_AnyThread(const FAnimationCacheBonesContext& Context)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(CacheBones_AnyThread)
FAnimNode_Base::CacheBones_AnyThread(Context);
InitializeBoneReferences(Context.AnimInstanceProxy->GetRequiredBones());
ComponentPose.CacheBones(Context);
}
void FAnimNode_SkeletalControlBase::UpdateInternal(const FAnimationUpdateContext& Context)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(UpdateInternal)
}
void FAnimNode_SkeletalControlBase::UpdateComponentPose_AnyThread(const FAnimationUpdateContext& Context)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(UpdateComponentPose_AnyThread)
ComponentPose.Update(Context);
}
void FAnimNode_SkeletalControlBase::Update_AnyThread(const FAnimationUpdateContext& Context)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(Update_AnyThread)
UpdateComponentPose_AnyThread(Context);
ActualAlpha = 0.f;
if (IsLODEnabled(Context.AnimInstanceProxy))
{
GetEvaluateGraphExposedInputs().Execute(Context);
// Apply the skeletal control if it's valid
switch (AlphaInputType)
{
case EAnimAlphaInputType::Float :
ActualAlpha = AlphaScaleBias.ApplyTo(AlphaScaleBiasClamp.ApplyTo(Alpha, Context.GetDeltaTime()));
break;
case EAnimAlphaInputType::Bool :
ActualAlpha = AlphaBoolBlend.ApplyTo(bAlphaBoolEnabled, Context.GetDeltaTime());
break;
case EAnimAlphaInputType::Curve :
if (UAnimInstance* AnimInstance = Cast<UAnimInstance>(Context.AnimInstanceProxy->GetAnimInstanceObject()))
{
ActualAlpha = AlphaScaleBiasClamp.ApplyTo(AnimInstance->GetCurveValue(AlphaCurveName), Context.GetDeltaTime());
}
break;
};
// Make sure Alpha is clamped between 0 and 1.
ActualAlpha = FMath::Clamp<float>(ActualAlpha, 0.f, 1.f);
if (FAnimWeight::IsRelevant(ActualAlpha) && IsValidToEvaluate(Context.AnimInstanceProxy->GetSkeleton(), Context.AnimInstanceProxy->GetRequiredBones()))
{
UpdateInternal(Context);
}
}
TRACE_ANIM_NODE_VALUE(Context, TEXT("Alpha"), ActualAlpha);
}
bool ContainsNaN(const TArray<FBoneTransform> & BoneTransforms)
{
for (int32 i = 0; i < BoneTransforms.Num(); ++i)
{
if (BoneTransforms[i].Transform.ContainsNaN())
{
return true;
}
}
return false;
}
void FAnimNode_SkeletalControlBase::EvaluateComponentPose_AnyThread(FComponentSpacePoseContext& Output)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(EvaluateComponentPose_AnyThread)
// Evaluate the input
ComponentPose.EvaluateComponentSpace(Output);
}
void FAnimNode_SkeletalControlBase::EvaluateComponentSpaceInternal(FComponentSpacePoseContext& Context)
{
}
void FAnimNode_SkeletalControlBase::EvaluateComponentSpace_AnyThread(FComponentSpacePoseContext& Output)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(EvaluateComponentSpace_AnyThread)
// Cache the incoming node IDs in a base context
FAnimationBaseContext CachedContext(Output);
EvaluateComponentPose_AnyThread(Output);
#if WITH_EDITORONLY_DATA
// save current pose before applying skeletal control to compute the exact gizmo location in AnimGraphNode
ForwardedPose.CopyPose(Output.Pose);
#endif // #if WITH_EDITORONLY_DATA
#if DO_CHECK
// this is to ensure Source data does not contain NaN
ensure(Output.ContainsNaN() == false);
#endif
// Apply the skeletal control if it's valid
if (FAnimWeight::IsRelevant(ActualAlpha) && IsValidToEvaluate(Output.AnimInstanceProxy->GetSkeleton(), Output.AnimInstanceProxy->GetRequiredBones()))
{
Output.SetNodeIds(CachedContext);
EvaluateComponentSpaceInternal(Output);
BoneTransforms.Reset(BoneTransforms.Num());
EvaluateSkeletalControl_AnyThread(Output, BoneTransforms);
if (BoneTransforms.Num() > 0)
{
const float BlendWeight = FMath::Clamp<float>(ActualAlpha, 0.f, 1.f);
Output.Pose.LocalBlendCSBoneTransforms(BoneTransforms, BlendWeight);
}
// we check NaN when you get out of this function in void FComponentSpacePoseLink::EvaluateComponentSpace(FComponentSpacePoseContext& Output)
}
}
void FAnimNode_SkeletalControlBase::AddDebugNodeData(FString& OutDebugData)
{
OutDebugData += FString::Printf(TEXT("Alpha: %.1f%%"), ActualAlpha*100.f);
}
void FAnimNode_SkeletalControlBase::EvaluateSkeletalControl_AnyThread(FComponentSpacePoseContext& Output, TArray<FBoneTransform>& OutBoneTransforms)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(EvaluateSkeletalControl_AnyThread)
PRAGMA_DISABLE_DEPRECATION_WARNINGS
// Call legacy implementation for backwards compatibility
EvaluateBoneTransforms(Output.AnimInstanceProxy->GetSkelMeshComponent(), Output.Pose, OutBoneTransforms);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
}