// Copyright Epic Games, Inc. All Rights Reserved. #include "PoseSearch/AnimNode_MotionMatching.h" #include "Animation/AnimInertializationSyncScope.h" #include "Animation/AnimInstanceProxy.h" #include "Animation/AnimNode_Inertialization.h" #include "Animation/AnimRootMotionProvider.h" #include "Animation/AnimSequence.h" #include "Animation/AnimStats.h" #include "Animation/BlendSpace.h" #include "Components/SkeletalMeshComponent.h" #include "PoseSearch/AnimNode_PoseSearchHistoryCollector.h" #include "PoseSearch/PoseSearchDatabase.h" #include "PoseSearch/PoseSearchDerivedData.h" #include "PoseSearch/PoseSearchSchema.h" #include "PoseSearch/Trace/PoseSearchTraceLogger.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(AnimNode_MotionMatching) #define LOCTEXT_NAMESPACE "AnimNode_MotionMatching" #if ENABLE_ANIM_DEBUG namespace UE::Private { enum EPlayRateState : int8 { Disabled = 0, Enabled = 1, PoseSearchOnly = 2 }; } // namespace UE::Private static bool GVarAnimNodeMotionMatchingDrawQuery = false; static FAutoConsoleVariableRef CVarAnimNodeMotionMatchingDrawQuery(TEXT("a.AnimNode.MotionMatching.DebugDrawQuery"), GVarAnimNodeMotionMatchingDrawQuery, TEXT("Draw input query")); static bool GVarAnimNodeMotionMatchingDrawCurResult = false; static FAutoConsoleVariableRef CVarAnimNodeMotionMatchingDrawCurResult(TEXT("a.AnimNode.MotionMatching.DebugDrawCurResult"), GVarAnimNodeMotionMatchingDrawCurResult, TEXT("Draw current result")); static bool GVarAnimNodeMotionMatchingDrawInfo = false; static FAutoConsoleVariableRef CVarAnimNodeMotionMatchingDrawInfo(TEXT("a.AnimNode.MotionMatching.DebugDrawInfo"), GVarAnimNodeMotionMatchingDrawInfo, TEXT("Draw info like current databases and asset")); static bool GVarAnimNodeMotionMatchingDrawInfoVerbose = true; static FAutoConsoleVariableRef CVarAnimNodeMotionMatchingDrawInfoVerbose(TEXT("a.AnimNode.MotionMatching.DebugDrawInfoVerbose"), GVarAnimNodeMotionMatchingDrawInfoVerbose, TEXT("Draw additional info like blend stack")); static float GVarAnimNodeMotionMatchingDrawInfoHeight = 50.f; static FAutoConsoleVariableRef CVarAnimNodeMotionMatchingDrawInfoHeight(TEXT("a.AnimNode.MotionMatching.DebugDrawInfoHeight"), GVarAnimNodeMotionMatchingDrawInfoHeight, TEXT("Vertical offset for DebugDrawInfo")); static int32 GVarAnimNodeMotionMatchingPlayRateEnabled = UE::Private::EPlayRateState::Enabled; static FAutoConsoleVariableRef CVarAnimNodeMotionMatchingPlayRateEnabled( TEXT("a.AnimNode.MotionMatching.DebugPlayRateEnabled"), GVarAnimNodeMotionMatchingPlayRateEnabled, TEXT("Toggles if PlayRate is used in motion matching. Same as setting PlayRate to (1,1) when disabled.\n") TEXT("0: Completely disable PlayRate usage.\n") TEXT("1: Enable all usages of PlayRate.\n") TEXT("2: Enable PlayRate in PoseSeach only (Not used in actual playback).\n") ); #endif ///////////////////////////////////////////////////// // FAnimNode_MotionMatching void FAnimNode_MotionMatching::Initialize_AnyThread(const FAnimationInitializeContext& Context) { DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(Initialize_AnyThread); GetEvaluateGraphExposedInputs().Execute(Context); FAnimNode_BlendStack_Standalone::Initialize_AnyThread(Context); MotionMatchingState.Reset(); } void FAnimNode_MotionMatching::UpdateAssetPlayer(const FAnimationUpdateContext& Context) { DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(UpdateAssetPlayer); QUICK_SCOPE_CYCLE_COUNTER(STAT_PoseSearch_UpdateAssetPlayer); using namespace UE::PoseSearch; check(Context.AnimInstanceProxy); GetEvaluateGraphExposedInputs().Execute(Context); // synchronizing with GetAccumulatedTime or resetting MotionMatchingState, and conditionally resetting FAnimNode_BlendStack_Standalone if (bResetOnBecomingRelevant && UpdateCounter.HasEverBeenUpdated() && !UpdateCounter.WasSynchronizedCounter(Context.AnimInstanceProxy->GetUpdateCounter())) { // If we just became relevant and haven't been initialized yet, then reset motion matching state, otherwise update the asset time using the player node. MotionMatchingState.Reset(); FAnimNode_BlendStack_Standalone::Reset(); } else if (MotionMatchingState.SearchResult.SelectedDatabase == nullptr || MotionMatchingState.SearchResult.SelectedDatabase->Schema == nullptr) { } #if WITH_EDITOR else if (EAsyncBuildIndexResult::Success != FAsyncPoseSearchDatabasesManagement::RequestAsyncBuildIndex(MotionMatchingState.SearchResult.SelectedDatabase.Get(), ERequestAsyncBuildFlag::ContinueRequest)) { // MotionMatchingState.SearchResult.Database is indexing, and it's not safe to use its previous index properties cached in MotionMatchingState MotionMatchingState.Reset(); } #endif // WITH_EDITOR else { // We adjust the motion matching state asset time to the current player node's asset time. This is done // because the player node may have ticked more or less time than we expected due to variable dt or the // dynamic playback rate adjustment and as such the motion matching state does not update by itself MotionMatchingState.SearchResult.SelectedAnim = GetAnimAsset(); MotionMatchingState.SearchResult.SelectedTime = GetAccumulatedTime(); MotionMatchingState.SearchResult.bIsMirrored = GetMirror(); MotionMatchingState.SearchResult.BlendParameters = GetBlendParameters(); } UpdateCounter.SynchronizeWith(Context.AnimInstanceProxy->GetUpdateCounter()); // If the Database property hasn't been overridden, set it as the only database to search. if (!bOverrideDatabaseInput) { DatabasesToSearch.Reset(); if (Database != nullptr) { DatabasesToSearch.Add(Database); } } #if ENABLE_ANIM_DEBUG if (GVarAnimNodeMotionMatchingDrawInfo) { FString DebugInfo = FString::Printf(TEXT("NextUpdateInterruptMode(%s)\n"), *UEnum::GetValueAsString(NextUpdateInterruptMode)); DebugInfo += FString::Printf(TEXT("Current Database(%s)\n"), *GetNameSafe(MotionMatchingState.SearchResult.SelectedDatabase.Get())); DebugInfo += FString::Printf(TEXT("Current Asset(%s)\n"), *GetNameSafe(GetAnimAsset())); if (GVarAnimNodeMotionMatchingDrawInfoVerbose) { DebugInfo += FString::Printf(TEXT("Databases to search:\n")); for (const UPoseSearchDatabase* DatabaseToSearch : DatabasesToSearch) { DebugInfo += FString::Printf(TEXT(" %s\n"), *GetNameSafe(DatabaseToSearch)); } DebugInfo += FString::Printf(TEXT("Blend Stack:\n")); for (const FBlendStackAnimPlayer& AnimPlayer : AnimPlayers) { DebugInfo += FString::Printf(TEXT(" %s [time:%.2f|playrate:%.2f]\n"), *GetNameSafe(AnimPlayer.GetAnimationAsset()), AnimPlayer.GetAccumulatedTime(), AnimPlayer.GetPlayRate()); } } Context.AnimInstanceProxy->AnimDrawDebugInWorldMessage(DebugInfo, FVector::UpVector * GVarAnimNodeMotionMatchingDrawInfoHeight, FColor::Yellow, 1.f /*TextScale*/); } #endif // ENABLE_ANIM_DEBUG const IPoseHistory* PoseHistory = nullptr; if (const FPoseHistoryProvider* PoseHistoryProvider = Context.GetMessage()) { PoseHistory = &PoseHistoryProvider->GetPoseHistory(); } FFloatInterval PoseSearchPlayRate = PlayRate; #if ENABLE_ANIM_DEBUG bool bPoseSearchPlayRateEnabled = GVarAnimNodeMotionMatchingPlayRateEnabled == UE::Private::EPlayRateState::Enabled || GVarAnimNodeMotionMatchingPlayRateEnabled == UE::Private::EPlayRateState::PoseSearchOnly; PoseSearchPlayRate = bPoseSearchPlayRateEnabled ? PoseSearchPlayRate : FFloatInterval(1.f, 1.f); #endif // ENABLE_ANIM_DEBUG FChooserEvaluationContext EvaluationContext(Context.AnimInstanceProxy->GetAnimInstanceObject()); UPoseSearchLibrary::UpdateMotionMatchingState( &EvaluationContext, PoseHistory, DatabasesToSearch, Context.GetDeltaTime(), PoseJumpThresholdTime, PoseReselectHistory, bShouldSearch ? SearchThrottleTime : UE_BIG_NUMBER, PoseSearchPlayRate, MotionMatchingState, NextUpdateInterruptMode, bShouldUseCachedChannelData #if ENABLE_ANIM_DEBUG , GVarAnimNodeMotionMatchingDrawQuery , GVarAnimNodeMotionMatchingDrawCurResult #else // ENABLE_ANIM_DEBUG , false , false #endif // ENABLE_ANIM_DEBUG , EventToSearch ); const bool bJumpToPose = MotionMatchingState.SearchResult.SelectedAnim && !MotionMatchingState.SearchResult.bIsContinuingPoseSearch; PRAGMA_DISABLE_DEPRECATION_WARNINGS MotionMatchingState.bJumpedToPose = bJumpToPose; PRAGMA_ENABLE_DEPRECATION_WARNINGS float DesiredPlayRate = MotionMatchingState.SearchResult.WantedPlayRate * PlayRateMultiplier; #if ENABLE_ANIM_DEBUG DesiredPlayRate = GVarAnimNodeMotionMatchingPlayRateEnabled == UE::Private::EPlayRateState::Enabled ? DesiredPlayRate : PlayRateMultiplier; #endif // ENABLE_ANIM_DEBUG UE::Anim::FNodeFunctionCaller::CallFunction(GetOnUpdateMotionMatchingStateFunction(), Context, *this); // If a new pose is requested, blend into the new asset via BlendStackNode if (bJumpToPose) { const UPoseSearchDatabase* CurrentResultDatabase = MotionMatchingState.SearchResult.SelectedDatabase.Get(); if (CurrentResultDatabase && CurrentResultDatabase->Schema) { if (UAnimationAsset* AnimationAsset = MotionMatchingState.SearchResult.GetAnimationAssetForRole()) { // Clear up any sync group info before pushing new asset player (which will have sync info since its the highest weighted). for (FBlendStackAnimPlayer& AnimPlayer : AnimPlayers) { if (FAnimNode_AssetPlayerBase* AssetPlayerNode = AnimPlayer.GetAssetPlayerNode()) { AssetPlayerNode->SetGroupMethod(EAnimSyncMethod::DoNotSync); AssetPlayerNode->SetGroupRole(EAnimGroupRole::CanBeLeader); AssetPlayerNode->SetGroupName(NAME_None); } } bool bAbortBlend = false; if (!AnimPlayers.IsEmpty() && GetBlendspaceParametersDeltaThreshold() > 0.f && AnimationAsset->IsA()) { if (AnimPlayers[0].GetAnimationAsset() == AnimationAsset) { const FVector CurrentBlendParameters = AnimPlayers[0].GetBlendParameters(); const FVector DesiredBlendParameters = GetBlendspaceParameters(); const float Delta = (CurrentBlendParameters - DesiredBlendParameters).SizeSquared(); if (Delta < FMath::Square(GetBlendspaceParametersDeltaThreshold())) { // If we haven't changed assets, and our currently playing blendspace xy is within threshold of change, then keep playing it. // Time differences should be OK because of the call to AdjustAssetTime before the search. bAbortBlend = true; } } } if (!bAbortBlend) { FAnimNode_BlendStack_Standalone::BlendTo(Context, AnimationAsset, MotionMatchingState.SearchResult.SelectedTime, MotionMatchingState.SearchResult.bLoop, MotionMatchingState.SearchResult.bIsMirrored, CurrentResultDatabase->Schema->GetMirrorDataTable(MotionMatchingState.SearchResult.Role), BlendTime, BlendProfile, BlendOption, bUseInertialBlend, NAME_None, MotionMatchingState.SearchResult.BlendParameters, DesiredPlayRate, 0, GetGroupName(), GetGroupRole(), GetGroupMethod(), GetOverridePositionWhenJoiningSyncGroupAsLeader()); } } else { checkNoEntry(); } } } const bool bDidBlendToRequestAnInertialBlend = bJumpToPose && bUseInertialBlend; UE::Anim::TOptionalScopedGraphMessage InertializationSync(bDidBlendToRequestAnInertialBlend, Context); FAnimNode_BlendStack_Standalone::UpdatePlayRate(DesiredPlayRate); FAnimNode_BlendStack_Standalone::UpdateBlendspaceParameters(GetBlendspaceUpdateMode(), GetBlendspaceParameters()); FAnimNode_BlendStack_Standalone::UpdateAssetPlayer(Context); NextUpdateInterruptMode = EPoseSearchInterruptMode::DoNotInterrupt; } const FAnimNodeFunctionRef& FAnimNode_MotionMatching::GetOnUpdateMotionMatchingStateFunction() const { return GET_ANIM_NODE_DATA(FAnimNodeFunctionRef, OnMotionMatchingStateUpdated); } void FAnimNode_MotionMatching::SetDatabaseToSearch(UPoseSearchDatabase* InDatabase, EPoseSearchInterruptMode InterruptMode) { SetDatabasesToSearch(MakeArrayView(&InDatabase, 1), InterruptMode); } FVector FAnimNode_MotionMatching::GetEstimatedFutureRootMotionVelocity() const { return MotionMatchingState.GetEstimatedFutureRootMotionVelocity(); } void FAnimNode_MotionMatching::SetDatabasesToSearch(TConstArrayView InDatabases, EPoseSearchInterruptMode InterruptMode) { DatabasesToSearch.Reset(); for (UPoseSearchDatabase* InDatabase : InDatabases) { DatabasesToSearch.AddUnique(InDatabase); } NextUpdateInterruptMode = InterruptMode; bOverrideDatabaseInput = true; } void FAnimNode_MotionMatching::ResetDatabasesToSearch(EPoseSearchInterruptMode InterruptMode) { DatabasesToSearch.Reset(); bOverrideDatabaseInput = false; NextUpdateInterruptMode = InterruptMode; } void FAnimNode_MotionMatching::SetInterruptMode(EPoseSearchInterruptMode InterruptMode) { NextUpdateInterruptMode = InterruptMode; } // FAnimNode_AssetPlayerBase interface bool FAnimNode_MotionMatching::GetIgnoreForRelevancyTest() const { return GET_ANIM_NODE_DATA(bool, bIgnoreForRelevancyTest); } bool FAnimNode_MotionMatching::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; } const FVector& FAnimNode_MotionMatching::GetBlendspaceParameters() const { return GET_ANIM_NODE_DATA(FVector, BlendParameters); } float FAnimNode_MotionMatching::GetBlendspaceParametersDeltaThreshold() const { return GET_ANIM_NODE_DATA(float, BlendParametersDeltaThreshold); } EBlendStack_BlendspaceUpdateMode FAnimNode_MotionMatching::GetBlendspaceUpdateMode() const { return GET_ANIM_NODE_DATA(EBlendStack_BlendspaceUpdateMode, BlendspaceUpdateMode); } FName FAnimNode_MotionMatching::GetGroupName() const { return GET_ANIM_NODE_DATA(FName, GroupName); } EAnimGroupRole::Type FAnimNode_MotionMatching::GetGroupRole() const { return GET_ANIM_NODE_DATA(TEnumAsByte, GroupRole); } EAnimSyncMethod FAnimNode_MotionMatching::GetGroupMethod() const { return GET_ANIM_NODE_DATA(EAnimSyncMethod, Method); } bool FAnimNode_MotionMatching::GetOverridePositionWhenJoiningSyncGroupAsLeader() const { return GET_ANIM_NODE_DATA(bool, bOverridePositionWhenJoiningSyncGroupAsLeader); } bool FAnimNode_MotionMatching::IsLooping() const { if (!AnimPlayers.IsEmpty()) { return AnimPlayers[0].IsLooping(); } return false; } bool FAnimNode_MotionMatching::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_MotionMatching::SetGroupRole(EAnimGroupRole::Type InRole) { #if WITH_EDITORONLY_DATA GroupRole = InRole; #endif if(TEnumAsByte* GroupRolePtr = GET_INSTANCE_ANIM_NODE_DATA_PTR(TEnumAsByte, GroupRole)) { *GroupRolePtr = InRole; return true; } return false; } bool FAnimNode_MotionMatching::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_MotionMatching::SetOverridePositionWhenJoiningSyncGroupAsLeader(bool InOverridePositionWhenJoiningSyncGroupAsLeader) { #if WITH_EDITORONLY_DATA bOverridePositionWhenJoiningSyncGroupAsLeader = InOverridePositionWhenJoiningSyncGroupAsLeader; #endif if(bool* bOverridePositionWhenJoiningSyncGroupAsLeaderPtr = GET_INSTANCE_ANIM_NODE_DATA_PTR(bool, bOverridePositionWhenJoiningSyncGroupAsLeader)) { *bOverridePositionWhenJoiningSyncGroupAsLeaderPtr = InOverridePositionWhenJoiningSyncGroupAsLeader; return true; } return false; } #undef LOCTEXT_NAMESPACE