Files
UnrealEngineUWP/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_Mirror.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

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