Files
UnrealEngineUWP/Engine/Source/Runtime/AnimGraphRuntime/Private/AnimNodes/AnimNode_BlendSpacePlayer.cpp
Thomas Sarkanen d9c2b172f7 Skeleton compatibility improvements
Skeleton compatibility is now bi-directional. Specifying a compatible skeleton A -> B now implies B -> A.
Skeleton compatibility is now an editor-only concern. The runtime will attempt to do the 'best it can' via name -> name mappings. Only the editor will prevent assigning incompatible skeletons in (e.g.) asset pickers etc.
Skeleton compatibility checks in editor can now be disabled in the editor preferences (and each asset picker now has a checkbox option in its view settings that allows for quick access to this).

Moves FSkeletonRemapping to its own file (which is now private).
Skeleton remappings are now generated on demand on worker threads just before animation decompression and stored in a registry, guarded by FRWScopeLock for thread-safety.

Fixed some anim BP compiler edge cases where asset references on pins were not getting preloaded correctly, causing skeletons to be erroneously reported as missing.

Exposed the current asset registry filter in SAssetView so that menu extensions can access it (and use it to provide context)

#jira UE-166054
#jira UE-167355
#rb Jurre.deBaare,John.vanderBerg
#preflight 635902602e6690262afa86f9

[CL 22878911 by Thomas Sarkanen in ue5-main branch]
2022-11-01 06:25:59 -04:00

404 lines
11 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "AnimNodes/AnimNode_BlendSpacePlayer.h"
#include "Animation/BlendSpace.h"
#include "Animation/AnimSequence.h"
#include "Animation/AnimInstanceProxy.h"
#include "AnimGraphRuntimeTrace.h"
#include "Animation/AnimSync.h"
#include "Animation/AnimSyncScope.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(AnimNode_BlendSpacePlayer)
#if WITH_EDITORONLY_DATA
#include "Animation/AnimBlueprintGeneratedClass.h"
#endif
/////////////////////////////////////////////////////
// FAnimNode_BlendSpacePlayerBase
float FAnimNode_BlendSpacePlayerBase::GetCurrentAssetTime() const
{
if(const FBlendSampleData* HighestWeightedSample = GetHighestWeightedSample())
{
return HighestWeightedSample->Time;
}
// No sample
return 0.0f;
}
float FAnimNode_BlendSpacePlayerBase::GetCurrentAssetTimePlayRateAdjusted() const
{
float Length = GetCurrentAssetLength();
return GetPlayRate() < 0.0f ? Length - InternalTimeAccumulator * Length : Length * InternalTimeAccumulator;
}
float FAnimNode_BlendSpacePlayerBase::GetCurrentAssetLength() const
{
if(const FBlendSampleData* HighestWeightedSample = GetHighestWeightedSample())
{
UBlendSpace* CurrentBlendSpace = GetBlendSpace();
if (CurrentBlendSpace != nullptr)
{
const FBlendSample& Sample = CurrentBlendSpace->GetBlendSample(HighestWeightedSample->SampleDataIndex);
return Sample.Animation->GetPlayLength();
}
}
// No sample
return 0.0f;
}
void FAnimNode_BlendSpacePlayerBase::Initialize_AnyThread(const FAnimationInitializeContext& Context)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(Initialize_AnyThread)
FAnimNode_AssetPlayerBase::Initialize_AnyThread(Context);
GetEvaluateGraphExposedInputs().Execute(Context);
Reinitialize();
PreviousBlendSpace = GetBlendSpace();
}
void FAnimNode_BlendSpacePlayerBase::CacheBones_AnyThread(const FAnimationCacheBonesContext& Context)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(CacheBones_AnyThread)
}
void FAnimNode_BlendSpacePlayerBase::UpdateAssetPlayer(const FAnimationUpdateContext& Context)
{
GetEvaluateGraphExposedInputs().Execute(Context);
UpdateInternal(Context);
}
void FAnimNode_BlendSpacePlayerBase::UpdateInternal(const FAnimationUpdateContext& Context)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(UpdateInternal)
UBlendSpace* CurrentBlendSpace = GetBlendSpace();
if (CurrentBlendSpace != nullptr && CurrentBlendSpace->GetSkeleton() != nullptr)
{
if (PreviousBlendSpace != CurrentBlendSpace)
{
Reinitialize(ShouldResetPlayTimeWhenBlendSpaceChanges());
}
const FVector Position = GetPosition();
// Create a tick record and push into the closest scope
UE::Anim::FAnimSyncGroupScope& SyncScope = Context.GetMessageChecked<UE::Anim::FAnimSyncGroupScope>();
FAnimTickRecord TickRecord(
CurrentBlendSpace, Position, BlendSampleDataCache, BlendFilter, GetLoop(), GetPlayRate(), ShouldTeleportToTime(),
IsEvaluator(), Context.GetFinalBlendWeight(), /*inout*/ InternalTimeAccumulator, MarkerTickRecord);
TickRecord.RootMotionWeightModifier = Context.GetRootMotionWeightModifier();
TickRecord.DeltaTimeRecord = &DeltaTimeRecord;
UE::Anim::FAnimSyncParams SyncParams(GetGroupName(), GetGroupRole(), GetGroupMethod());
TickRecord.GatherContextData(Context);
SyncScope.AddTickRecord(TickRecord, SyncParams, UE::Anim::FAnimSyncDebugInfo(Context));
TRACE_ANIM_TICK_RECORD(Context, TickRecord);
#if WITH_EDITORONLY_DATA
if (FAnimBlueprintDebugData* DebugData = Context.AnimInstanceProxy->GetAnimBlueprintDebugData())
{
DebugData->RecordBlendSpacePlayer(Context.GetCurrentNodeId(), CurrentBlendSpace, Position, BlendFilter.GetFilterLastOutput());
}
#endif
PreviousBlendSpace = CurrentBlendSpace;
}
TRACE_BLENDSPACE_PLAYER(Context, *this);
TRACE_ANIM_NODE_VALUE(Context, TEXT("Name"), CurrentBlendSpace ? *CurrentBlendSpace->GetName() : TEXT("None"));
TRACE_ANIM_NODE_VALUE(Context, TEXT("Blend Space"), CurrentBlendSpace);
TRACE_ANIM_NODE_VALUE(Context, TEXT("Playback Time"), InternalTimeAccumulator);
}
void FAnimNode_BlendSpacePlayerBase::Evaluate_AnyThread(FPoseContext& Output)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(Evaluate_AnyThread)
UBlendSpace* CurrentBlendSpace = GetBlendSpace();
if (CurrentBlendSpace != nullptr && CurrentBlendSpace->GetSkeleton() != nullptr)
{
FAnimationPoseData AnimationPoseData(Output);
CurrentBlendSpace->GetAnimationPose(BlendSampleDataCache, FAnimExtractContext(static_cast<double>(InternalTimeAccumulator), Output.AnimInstanceProxy->ShouldExtractRootMotion(), DeltaTimeRecord, GetLoop()), AnimationPoseData);
}
else
{
Output.ResetToRefPose();
}
}
void FAnimNode_BlendSpacePlayerBase::GatherDebugData(FNodeDebugData& DebugData)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(GatherDebugData)
FString DebugLine = DebugData.GetNodeName(this);
UBlendSpace* CurrentBlendSpace = GetBlendSpace();
if (CurrentBlendSpace)
{
DebugLine += FString::Printf(TEXT("('%s' Play Time: %.3f)"), *CurrentBlendSpace->GetName(), InternalTimeAccumulator);
DebugData.AddDebugItem(DebugLine, true);
}
}
float FAnimNode_BlendSpacePlayerBase::GetTimeFromEnd(float CurrentTime) const
{
// Blend-spaces use normalized time value
const float PlayLength = 1.0f;
return GetBlendSpace() != nullptr ? PlayLength - CurrentTime : 0.0f;
}
void FAnimNode_BlendSpacePlayerBase::SnapToPosition(const FVector& NewPosition)
{
const int32 NumAxis = FMath::Min(BlendFilter.FilterPerAxis.Num(), 3);
for (int32 Idx = 0; Idx < NumAxis; Idx++)
{
BlendFilter.FilterPerAxis[Idx].SetToValue(static_cast<float>(NewPosition[Idx]));
}
}
UAnimationAsset* FAnimNode_BlendSpacePlayerBase::GetAnimAsset() const
{
return GetBlendSpace();
}
const FBlendSampleData* FAnimNode_BlendSpacePlayerBase::GetHighestWeightedSample() const
{
if(BlendSampleDataCache.Num() == 0)
{
return nullptr;
}
const FBlendSampleData* HighestSample = &BlendSampleDataCache[0];
for(int32 Idx = 1; Idx < BlendSampleDataCache.Num(); ++Idx)
{
if(BlendSampleDataCache[Idx].TotalWeight > HighestSample->TotalWeight)
{
HighestSample = &BlendSampleDataCache[Idx];
}
}
return HighestSample;
}
void FAnimNode_BlendSpacePlayerBase::Reinitialize(bool bResetTime)
{
BlendSampleDataCache.Empty();
if(bResetTime)
{
float CurrentStartPosition = GetStartPosition();
InternalTimeAccumulator = FMath::Clamp(CurrentStartPosition, 0.f, 1.0f);
if (CurrentStartPosition == 0.f && GetPlayRate() < 0.0f)
{
// Blend spaces run between 0 and 1
InternalTimeAccumulator = 1.0f;
}
}
UBlendSpace* CurrentBlendSpace = GetBlendSpace();
if (CurrentBlendSpace != nullptr)
{
CurrentBlendSpace->InitializeFilter(&BlendFilter);
}
}
/////////////////////////////////////////////////////
// FAnimNode_BlendSpacePlayer
FName FAnimNode_BlendSpacePlayer::GetGroupName() const
{
return GET_ANIM_NODE_DATA(FName, GroupName);
}
EAnimGroupRole::Type FAnimNode_BlendSpacePlayer::GetGroupRole() const
{
return GET_ANIM_NODE_DATA(TEnumAsByte<EAnimGroupRole::Type>, GroupRole);
}
EAnimSyncMethod FAnimNode_BlendSpacePlayer::GetGroupMethod() const
{
return GET_ANIM_NODE_DATA(EAnimSyncMethod, Method);
}
bool FAnimNode_BlendSpacePlayer::GetIgnoreForRelevancyTest() const
{
return GET_ANIM_NODE_DATA(bool, bIgnoreForRelevancyTest);
}
bool FAnimNode_BlendSpacePlayer::SetGroupName(FName InGroupName)
{
#if WITH_EDITORONLY_DATA
GroupName = InGroupName;
#endif
if(FName* GroupNamePtr = GET_INSTANCE_ANIM_NODE_DATA_PTR(FName, GroupName))
{
*GroupNamePtr = InGroupName;
return true;
}
return false;
}
bool FAnimNode_BlendSpacePlayer::SetGroupRole(EAnimGroupRole::Type InRole)
{
#if WITH_EDITORONLY_DATA
GroupRole = InRole;
#endif
if(TEnumAsByte<EAnimGroupRole::Type>* GroupRolePtr = GET_INSTANCE_ANIM_NODE_DATA_PTR(TEnumAsByte<EAnimGroupRole::Type>, GroupRole))
{
*GroupRolePtr = InRole;
return true;
}
return false;
}
bool FAnimNode_BlendSpacePlayer::SetGroupMethod(EAnimSyncMethod InMethod)
{
#if WITH_EDITORONLY_DATA
Method = InMethod;
#endif
if(EAnimSyncMethod* MethodPtr = GET_INSTANCE_ANIM_NODE_DATA_PTR(EAnimSyncMethod, Method))
{
*MethodPtr = InMethod;
return true;
}
return false;
}
bool FAnimNode_BlendSpacePlayer::SetIgnoreForRelevancyTest(bool bInIgnoreForRelevancyTest)
{
#if WITH_EDITORONLY_DATA
bIgnoreForRelevancyTest = bInIgnoreForRelevancyTest;
#endif
if(bool* bIgnoreForRelevancyTestPtr = GET_INSTANCE_ANIM_NODE_DATA_PTR(bool, bIgnoreForRelevancyTest))
{
*bIgnoreForRelevancyTestPtr = bInIgnoreForRelevancyTest;
return true;
}
return false;
}
bool FAnimNode_BlendSpacePlayer::SetResetPlayTimeWhenBlendSpaceChanges(bool bInReset)
{
#if WITH_EDITORONLY_DATA
bResetPlayTimeWhenBlendSpaceChanges = bInReset;
#endif
if (bool* bResetPlayTimeWhenBlendSpaceChangesPtr = GET_INSTANCE_ANIM_NODE_DATA_PTR(bool, bResetPlayTimeWhenBlendSpaceChanges))
{
*bResetPlayTimeWhenBlendSpaceChangesPtr = bInReset;
return true;
}
return false;
}
bool FAnimNode_BlendSpacePlayer::SetBlendSpace(UBlendSpace* InBlendSpace)
{
BlendSpace = InBlendSpace;
return true;
}
FVector FAnimNode_BlendSpacePlayer::GetPosition() const
{
return FVector(GET_ANIM_NODE_DATA(float, X), GET_ANIM_NODE_DATA(float, Y), 0.0f);
}
bool FAnimNode_BlendSpacePlayer::SetPosition(FVector InPosition)
{
#if WITH_EDITORONLY_DATA
X = static_cast<float>(InPosition[0]);
Y = static_cast<float>(InPosition[1]);
GET_MUTABLE_ANIM_NODE_DATA(float, X) = static_cast<float>(InPosition[0]);
GET_MUTABLE_ANIM_NODE_DATA(float, Y) = static_cast<float>(InPosition[1]);
#endif
float* XPtr = GET_INSTANCE_ANIM_NODE_DATA_PTR(float, X);
float* YPtr = GET_INSTANCE_ANIM_NODE_DATA_PTR(float, Y);
if (XPtr && YPtr)
{
*XPtr = static_cast<float>(InPosition[0]);
*YPtr = static_cast<float>(InPosition[1]);
return true;
}
return false;
}
float FAnimNode_BlendSpacePlayer::GetPlayRate() const
{
return GET_ANIM_NODE_DATA(float, PlayRate);
}
bool FAnimNode_BlendSpacePlayer::SetPlayRate(float InPlayRate)
{
#if WITH_EDITORONLY_DATA
PlayRate = InPlayRate;
GET_MUTABLE_ANIM_NODE_DATA(float, PlayRate) = InPlayRate;
#endif
if (float* PlayRatePtr = GET_INSTANCE_ANIM_NODE_DATA_PTR(float, PlayRate))
{
*PlayRatePtr = InPlayRate;
return true;
}
return false;
}
bool FAnimNode_BlendSpacePlayer::GetLoop() const
{
return GET_ANIM_NODE_DATA(bool, bLoop);
}
bool FAnimNode_BlendSpacePlayer::SetLoop(bool bInLoop)
{
#if WITH_EDITORONLY_DATA
bLoop = bInLoop;
GET_MUTABLE_ANIM_NODE_DATA(bool, bLoop) = bInLoop;
#endif
if (bool* bLoopPtr = GET_INSTANCE_ANIM_NODE_DATA_PTR(bool, bLoop))
{
*bLoopPtr = bInLoop;
return true;
}
return false;
}
bool FAnimNode_BlendSpacePlayer::ShouldResetPlayTimeWhenBlendSpaceChanges() const
{
return GET_ANIM_NODE_DATA(bool, bResetPlayTimeWhenBlendSpaceChanges);
}
float FAnimNode_BlendSpacePlayer::GetStartPosition() const
{
return GET_ANIM_NODE_DATA(float, StartPosition);
}
UBlendSpace* FAnimNode_BlendSpacePlayer::GetBlendSpace() const
{
return BlendSpace;
}