Files
UnrealEngine/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_Mirror.cpp
2025-05-18 13:04:45 +08:00

356 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.UAnimGraphNode_MirrorPose
#include "AnimGraphNode_Mirror.h"
#include "Animation/AnimNode_Inertialization.h"
#include "Animation/MirrorDataTable.h"
#include "AssetRegistry/ARFilter.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "BlueprintActionFilter.h"
#include "BlueprintActionFilter.h"
#include "BlueprintActionDatabaseRegistrar.h"
#include "BlueprintNodeSpawner.h"
#include "Kismet2/CompilerResultsLog.h"
#define LOCTEXT_NAMESPACE "AnimGraphNode_MirrorPose"
FLinearColor UAnimGraphNode_Mirror::GetNodeTitleColor() const
{
return FLinearColor(0.7f, 0.7f, 0.7f);
}
FText UAnimGraphNode_Mirror::GetTooltipText() const
{
return LOCTEXT("NodeToolTip", "Mirror Pose");
}
FText UAnimGraphNode_Mirror::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
if (Node.GetMirrorDataTable())
{
FFormatNamedArguments Args;
Args.Add(TEXT("AssetName"), FText::FromName(Node.GetMirrorDataTable()->GetFName()));
return FText::Format(LOCTEXT("MirrorWithAssetNodeTitle", "Mirror with {AssetName}"), Args);
}
else
{
return LOCTEXT("MirrorNodeTitle", "Mirror");
}
}
FText UAnimGraphNode_Mirror::GetMenuCategory() const
{
return LOCTEXT("NodeCategory", "Animation|Misc.");
}
void UAnimGraphNode_Mirror::GetOutputLinkAttributes(FNodeAttributeArray& OutAttributes) const
{
if (Node.GetBlendTimeOnMirrorStateChange() > 0.0)
{
OutAttributes.Add(UE::Anim::IInertializationRequester::Attribute);
}
}
void UAnimGraphNode_Mirror::PreloadRequiredAssets()
{
if (Node.GetMirrorDataTable())
{
PreloadObject(Node.GetMirrorDataTable());
PreloadObject(Node.GetMirrorDataTable()->Skeleton);
}
Super::PreloadRequiredAssets();
}
void UAnimGraphNode_Mirror::ValidateAnimNodeDuringCompilation(class USkeleton* ForSkeleton, class FCompilerResultsLog& MessageLog)
{
Super::ValidateAnimNodeDuringCompilation(ForSkeleton, MessageLog);
if (Node.GetMirrorDataTable() == nullptr)
{
MessageLog.Error(TEXT("@@ does not have a mirror data table selected. Please select a table or delete the node."), this);
}
else if (ForSkeleton)
{
const USkeleton* MirrorTableSkeleton = Node.GetMirrorDataTable()->Skeleton;
if (MirrorTableSkeleton == nullptr)
{
MessageLog.Warning(TEXT("@@ has a mirror data table that has a missing skeleton. Please update the table or create a new table for the skeleton."), this);
}
}
BindMirrorDataTableChangedDelegate();
}
void UAnimGraphNode_Mirror::PreEditChange(FProperty* PropertyThatWillChange)
{
Super::PreEditChange(PropertyThatWillChange);
if (PropertyThatWillChange && PropertyThatWillChange->GetFName() == GET_MEMBER_NAME_STRING_CHECKED(FAnimNode_Mirror, MirrorDataTable))
{
if (Node.GetMirrorDataTable())
{
Node.GetMirrorDataTable()->OnDataTableChanged().RemoveAll(this);
}
}
}
void UAnimGraphNode_Mirror::PostPlacedNewNode()
{
Super::PostPlacedNewNode();
BindMirrorDataTableChangedDelegate();
}
void UAnimGraphNode_Mirror::PostLoad()
{
Super::PostLoad();
BindMirrorDataTableChangedDelegate();
}
void UAnimGraphNode_Mirror::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent)
{
const FName PropertyName = (PropertyChangedEvent.Property ? PropertyChangedEvent.Property->GetFName() : NAME_None);
if (PropertyName == GET_MEMBER_NAME_STRING_CHECKED(FAnimNode_Mirror, MirrorDataTable))
{
BindMirrorDataTableChangedDelegate();
}
Super::PostEditChangeProperty(PropertyChangedEvent);
}
void UAnimGraphNode_Mirror::BindMirrorDataTableChangedDelegate()
{
if (Node.GetMirrorDataTable())
{
Node.GetMirrorDataTable()->OnDataTableChanged().RemoveAll(this);
Node.GetMirrorDataTable()->OnDataTableChanged().AddUObject(this, &UAnimGraphNode_Mirror::OnMirrorDataTableChanged);
}
}
void UAnimGraphNode_Mirror::OnMirrorDataTableChanged()
{
if (HasValidBlueprint())
{
UBlueprint* BP = GetBlueprint();
BP->Status = BS_Dirty;
}
}
void UAnimGraphNode_Mirror::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const
{
auto LoadedAssetSetup = [](UEdGraphNode* NewNode, bool /*bIsTemplateNode*/, TWeakObjectPtr<UMirrorDataTable> MirrorDataTablePtr)
{
UAnimGraphNode_Mirror* MirrorNode = CastChecked<UAnimGraphNode_Mirror>(NewNode);
MirrorNode->Node.SetMirrorDataTable(MirrorDataTablePtr.Get());
};
auto UnloadedAssetSetup = [](UEdGraphNode* NewNode, bool bIsTemplateNode, const FAssetData AssetData)
{
UAnimGraphNode_Mirror* MirrorNode = CastChecked<UAnimGraphNode_Mirror>(NewNode);
if (bIsTemplateNode)
{
AssetData.GetTagValue("Skeleton", MirrorNode->UnloadedSkeletonName);
}
else
{
UMirrorDataTable* MirrorTable = Cast<UMirrorDataTable>(AssetData.GetAsset());
check(MirrorTable != nullptr);
MirrorNode->Node.SetMirrorDataTable(MirrorTable);
}
};
const UObject* QueryObject = ActionRegistrar.GetActionKeyFilter();
if (QueryObject == nullptr)
{
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
// define a filter to help in pulling UMirrrorDataTable asset data from the registry
FARFilter Filter;
Filter.ClassPaths.Add(UMirrorDataTable::StaticClass()->GetClassPathName());
Filter.bRecursiveClasses = true;
// Find matching assets and add an entry for each one
TArray<FAssetData> MirrorDataTableList;
AssetRegistryModule.Get().GetAssets(Filter, /*out*/MirrorDataTableList);
for (auto AssetIt = MirrorDataTableList.CreateConstIterator(); AssetIt; ++AssetIt)
{
const FAssetData& Asset = *AssetIt;
UBlueprintNodeSpawner* NodeSpawner = UBlueprintNodeSpawner::Create(GetClass());
if (Asset.IsAssetLoaded())
{
UMirrorDataTable* MirrorDataTable = Cast<UMirrorDataTable>(Asset.GetAsset());
if (MirrorDataTable)
{
NodeSpawner->CustomizeNodeDelegate = UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateStatic(LoadedAssetSetup, TWeakObjectPtr<UMirrorDataTable>(MirrorDataTable));
NodeSpawner->DefaultMenuSignature.MenuName = GetTitleGivenAssetInfo(FText::FromName(MirrorDataTable->GetFName()));
}
}
else
{
NodeSpawner->CustomizeNodeDelegate = UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateStatic(UnloadedAssetSetup, Asset);
NodeSpawner->DefaultMenuSignature.MenuName = GetTitleGivenAssetInfo(FText::FromName(Asset.AssetName));
}
ActionRegistrar.AddBlueprintAction(Asset, NodeSpawner);
}
}
else if (const UMirrorDataTable* MirrorDataTable = Cast<UMirrorDataTable>(QueryObject))
{
UBlueprintNodeSpawner* NodeSpawner = UBlueprintNodeSpawner::Create(GetClass());
TWeakObjectPtr<UMirrorDataTable> MirrorDataTablePtr = MakeWeakObjectPtr(const_cast<UMirrorDataTable*>(MirrorDataTable));
NodeSpawner->CustomizeNodeDelegate = UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateStatic(LoadedAssetSetup, MirrorDataTablePtr);
NodeSpawner->DefaultMenuSignature.MenuName = GetTitleGivenAssetInfo(FText::FromName(MirrorDataTablePtr->GetFName()));
NodeSpawner->DefaultMenuSignature.Tooltip = GetTitleGivenAssetInfo(FText::FromString(MirrorDataTablePtr->GetPathName()));
ActionRegistrar.AddBlueprintAction(QueryObject, NodeSpawner);
}
else if (QueryObject == GetClass())
{
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
FARFilter Filter;
Filter.ClassPaths.Add(UMirrorDataTable::StaticClass()->GetClassPathName());
Filter.bRecursiveClasses = true;
// Find matching assets and add an entry for each one
TArray<FAssetData> MirrorDataTableList;
AssetRegistryModule.Get().GetAssets(Filter, /*out*/MirrorDataTableList);
for (auto AssetIt = MirrorDataTableList.CreateConstIterator(); AssetIt; ++AssetIt)
{
const FAssetData& Asset = *AssetIt;
if (Asset.IsAssetLoaded())
{
continue;
}
UBlueprintNodeSpawner* NodeSpawner = UBlueprintNodeSpawner::Create(GetClass());
NodeSpawner->CustomizeNodeDelegate = UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateStatic(UnloadedAssetSetup, Asset);
NodeSpawner->DefaultMenuSignature.MenuName = GetTitleGivenAssetInfo(FText::FromName(Asset.AssetName));
NodeSpawner->DefaultMenuSignature.Tooltip = GetTitleGivenAssetInfo(FText::FromString(Asset.GetObjectPathString()));
ActionRegistrar.AddBlueprintAction(Asset, NodeSpawner);
}
}
UClass* ActionKey = GetClass();
if (ActionRegistrar.IsOpenForRegistration(ActionKey))
{
// Add a default create option if there are no compatible tables. This action is filtered out when
// HasMirrorDataTableForBlueprints returns true because there is a table that can be used
UBlueprintNodeSpawner* EmptyNodeSpawner = UBlueprintNodeSpawner::Create(GetClass());
EmptyNodeSpawner->DefaultMenuSignature.MenuName = FText(LOCTEXT("MirrorNodeTitle", "Mirror"));
ActionRegistrar.AddBlueprintAction(ActionKey, EmptyNodeSpawner);
}
}
bool UAnimGraphNode_Mirror::HasMirrorDataTableForBlueprints(const TArray<UBlueprint*>& Blueprints) const
{
// check to see if any mirror data table is available for the blueprint
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
FARFilter Filter;
Filter.ClassPaths.Add(UMirrorDataTable::StaticClass()->GetClassPathName());
Filter.bRecursiveClasses = true;
TArray<FAssetData> MirrorDataTableList;
AssetRegistryModule.Get().GetAssets(Filter, MirrorDataTableList);
for (UBlueprint* Blueprint : Blueprints)
{
if (UAnimBlueprint* AnimBlueprint = Cast<UAnimBlueprint>(Blueprint))
{
for (auto AssetIt = MirrorDataTableList.CreateConstIterator(); AssetIt; ++AssetIt)
{
const FAssetData& Asset = *AssetIt;
if (Asset.IsAssetLoaded())
{
UMirrorDataTable* MirrorDataTable = Cast<UMirrorDataTable>(Asset.GetAsset());
if (AnimBlueprint->TargetSkeleton && AnimBlueprint->TargetSkeleton->IsCompatibleForEditor(MirrorDataTable->Skeleton))
{
return true;
}
}
else
{
FString TableSkeletonName;
Asset.GetTagValue("Skeleton", TableSkeletonName);
if (AnimBlueprint->TargetSkeleton && AnimBlueprint->TargetSkeleton->GetName() == TableSkeletonName)
{
return true;
}
}
}
}
}
return false;
}
bool UAnimGraphNode_Mirror::IsActionFilteredOut(class FBlueprintActionFilter const& Filter)
{
bool bIsFilteredOut = false;
FBlueprintActionContext const& FilterContext = Filter.Context;
for (UBlueprint* Blueprint : FilterContext.Blueprints)
{
if (UAnimBlueprint* AnimBlueprint = Cast<UAnimBlueprint>(Blueprint))
{
UMirrorDataTable* MirrorDataTable = Node.GetMirrorDataTable();
if (MirrorDataTable)
{
if(AnimBlueprint->TargetSkeleton == nullptr || !AnimBlueprint->TargetSkeleton->IsCompatibleForEditor(MirrorDataTable->Skeleton))
{
// Mirror Data Table does not use the same skeleton as the Blueprint, cannot use
bIsFilteredOut = true;
break;
}
}
else if(!UnloadedSkeletonName.IsEmpty())
{
if(AnimBlueprint->TargetSkeleton == nullptr || !AnimBlueprint->TargetSkeleton->IsCompatibleForEditor(UnloadedSkeletonName))
{
bIsFilteredOut = true;
break;
}
}
else
{
// Only show the empty "Mirror" option if there is not a possible MirrorDataTable to select
if (HasMirrorDataTableForBlueprints(FilterContext.Blueprints))
{
bIsFilteredOut = true;
break;
}
}
}
else
{
// Not an animation Blueprint, cannot use
bIsFilteredOut = true;
break;
}
}
return bIsFilteredOut;
}
FText UAnimGraphNode_Mirror::GetTitleGivenAssetInfo(const FText& AssetName)
{
FFormatNamedArguments Args;
Args.Add(TEXT("AssetName"), AssetName);
return FText::Format(LOCTEXT("MirrorNodeTitle_WithAsset", "Mirror with {AssetName}"), Args);
}
EAnimAssetHandlerType UAnimGraphNode_Mirror::SupportsAssetClass(const UClass* AssetClass) const
{
if (AssetClass->IsChildOf(UMirrorDataTable::StaticClass()))
{
return EAnimAssetHandlerType::PrimaryHandler;
}
else
{
return EAnimAssetHandlerType::NotSupported;
}
}
#undef LOCTEXT_NAMESPACE