Files
UnrealEngineUWP/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_Base.cpp
Thomas Sarkanen 0ddbfb9894 Anim node data/compiler refactor
Per-node constant data is now held on a generated struct as part of sparse class data.
Per-node mutable data (i.e. pin links/property access mappings) is now held on a generated 'mutable data' struct that is compiled as part of the generated class.

The anim BP compiler is now extended more conventionally using UAnimBlueprintExtension, derived from UBlueprintExtension. This directly replaces the older 'compiler handler' pattern that was added in an emergency fashion for 4.26. Anim graph nodes now request their required extensions and these are held on the UAnimBlueprint in the UBlueprint::Extensions array. The Extensions array is potentially refreshed with any node addition or removal. The Extensions array is force-refreshed each time an anim BP is compiled for the first time to deal with newly added or removed requirements.

Const-corrected a bunch of UAnimInstance/FAnimInstanceProxy APIs that rely on (now truly) const data.
Added a split state/constant version of FInputScaleBiasClamp to allow some of its data to be split into constants.
Tweaked alignment/ordering of FPoseLinkBase to save a few bytes per pose link.
Deprecated FAnimNode_Base::OverrideAsset in favor of a more UAnimGraphNode_Base-based approach. Individual nodes can still have runtime overrides via specific accessors. The new approach will also give us the oppurtunity to override multiple assets per node if required in the future.

Moved property access into Engine module & removed event support from it - this was never used.
Includes a thread-safety fix for 4.26 that hasnt made it over to 5.0 yet.
Reworked property access compilation API a little - construction/lifetime was a bit confusing previously.

Optimized path used to create UK2Node_StructMemberSet nodes in per-node custom events. When using mutable data, the structure used is large and very sparsely connected (i.e. only a few properties are written) so we only create pins that are actually going to be used, rather than creating all of them and conly connecting a few.

Patched the following nodes to use the new data approach:

- Asset players (sequences, blendspaces, aim offsets)
- Blend lists
- Ref poses
- Roots

#rb Jurre.deBaare, Martin.Wilson, Keith.Yerex

[CL 16071104 by Thomas Sarkanen in ue5-main branch]
2021-04-21 07:09:28 -04:00

605 lines
20 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "AnimGraphNode_Base.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Animation/AnimInstance.h"
#include "AnimationGraphSchema.h"
#include "BlueprintNodeSpawner.h"
#include "BlueprintActionDatabaseRegistrar.h"
#include "AnimBlueprintNodeOptionalPinManager.h"
#include "IAnimNodeEditMode.h"
#include "AnimNodeEditModes.h"
#include "AnimationGraph.h"
#include "EditorModeManager.h"
#include "AnimationEditorUtils.h"
#include "UObject/UnrealType.h"
#include "Kismet2/CompilerResultsLog.h"
#include "Subsystems/AssetEditorSubsystem.h"
#include "AnimBlueprintCompiler.h"
#include "AnimBlueprintExtension_Base.h"
#include "AnimBlueprintExtension_Attributes.h"
#include "AnimBlueprintExtension_PropertyAccess.h"
#include "IAnimBlueprintCompilationContext.h"
#include "AnimBlueprintCompilationContext.h"
#include "AnimBlueprintExtension.h"
#include "FindInBlueprintManager.h"
#include "UObject/ReleaseObjectVersion.h"
#define LOCTEXT_NAMESPACE "UAnimGraphNode_Base"
/////////////////////////////////////////////////////
// UAnimGraphNode_Base
UAnimGraphNode_Base::UAnimGraphNode_Base(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
void UAnimGraphNode_Base::ExpandNode(FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph)
{
TUniquePtr<IAnimBlueprintCompilationContext> CompilationContext = IAnimBlueprintCompilationContext::Get(CompilerContext);
UAnimBlueprintExtension_Base* Extension = UAnimBlueprintExtension_Base::GetExtension<UAnimBlueprintExtension_Base>(GetAnimBlueprint());
Extension->CreateEvaluationHandlerForNode(*CompilationContext.Get(), this);
}
void UAnimGraphNode_Base::PreEditChange(FProperty* PropertyThatWillChange)
{
Super::PreEditChange(PropertyThatWillChange);
if (PropertyThatWillChange && PropertyThatWillChange->GetFName() == GET_MEMBER_NAME_CHECKED(FOptionalPinFromProperty, bShowPin))
{
FOptionalPinManager::CacheShownPins(ShowPinForProperties, OldShownPins);
}
}
void UAnimGraphNode_Base::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
FName PropertyName = (PropertyChangedEvent.Property != NULL) ? PropertyChangedEvent.Property->GetFName() : NAME_None;
if ((PropertyName == GET_MEMBER_NAME_CHECKED(FOptionalPinFromProperty, bShowPin)))
{
FOptionalPinManager::EvaluateOldShownPins(ShowPinForProperties, OldShownPins, this);
GetSchema()->ReconstructNode(*this);
}
Super::PostEditChangeProperty(PropertyChangedEvent);
PropertyChangeEvent.Broadcast(PropertyChangedEvent);
}
void UAnimGraphNode_Base::Serialize(FArchive& Ar)
{
Super::Serialize(Ar);
Ar.UsingCustomVersion(FReleaseObjectVersion::GUID);
if (Ar.IsLoading())
{
if (Ar.CustomVer(FReleaseObjectVersion::GUID) < FReleaseObjectVersion::AnimationGraphNodeBindingsDisplayedAsPins)
{
// Push any bindings to optional pins
bool bPushedBinding = false;
for(const TPair<FName, FAnimGraphNodePropertyBinding>& BindingPair : PropertyBindings)
{
for(FOptionalPinFromProperty& OptionalPin : ShowPinForProperties)
{
if(OptionalPin.bCanToggleVisibility && !OptionalPin.bShowPin && OptionalPin.PropertyName == BindingPair.Key)
{
OptionalPin.bShowPin = true;
bPushedBinding = true;
}
}
}
if(bPushedBinding)
{
FOptionalPinManager::EvaluateOldShownPins(ShowPinForProperties, OldShownPins, this);
}
}
}
}
void UAnimGraphNode_Base::PostPlacedNewNode()
{
Super::PostPlacedNewNode();
// This makes sure that all anim BP extensions are registered that this node needs
UAnimBlueprintExtension::RequestExtensionsForNode(this);
}
void UAnimGraphNode_Base::DestroyNode()
{
// This node may have been the last using its extension, so refresh
GetAnimBlueprint()->RequestRefreshExtensions();
Super::DestroyNode();
}
void UAnimGraphNode_Base::CreateOutputPins()
{
if (!IsSinkNode())
{
CreatePin(EGPD_Output, UAnimationGraphSchema::PC_Struct, FPoseLink::StaticStruct(), TEXT("Pose"));
}
}
void UAnimGraphNode_Base::ValidateAnimNodeDuringCompilation(USkeleton* ForSkeleton, FCompilerResultsLog& MessageLog)
{
// Validate any bone references we have
for(const TPair<FStructProperty*, const void*>& PropertyValuePair : TPropertyValueRange<FStructProperty>(GetClass(), this))
{
if(PropertyValuePair.Key->Struct == FBoneReference::StaticStruct())
{
const FBoneReference& BoneReference = *(const FBoneReference*)PropertyValuePair.Value;
// Temporary fix where skeleton is not fully loaded during AnimBP compilation and thus virtual bone name check is invalid UE-39499 (NEED FIX)
if (ForSkeleton && !ForSkeleton->HasAnyFlags(RF_NeedPostLoad))
{
if (BoneReference.BoneName != NAME_None)
{
if (ForSkeleton->GetReferenceSkeleton().FindBoneIndex(BoneReference.BoneName) == INDEX_NONE)
{
FFormatNamedArguments Args;
Args.Add(TEXT("BoneName"), FText::FromName(BoneReference.BoneName));
MessageLog.Warning(*FText::Format(LOCTEXT("NoBoneFoundToModify", "@@ - Bone {BoneName} not found in Skeleton"), Args).ToString(), this);
}
}
}
}
}
}
void UAnimGraphNode_Base::CopyTermDefaultsToDefaultObject(IAnimBlueprintCopyTermDefaultsContext& InCompilationContext, IAnimBlueprintNodeCopyTermDefaultsContext& InPerNodeContext, IAnimBlueprintGeneratedClassCompiledData& OutCompiledData)
{
InPerNodeContext.GetTargetProperty()->CopyCompleteValue(InPerNodeContext.GetDestinationPtr(), InPerNodeContext.GetSourcePtr());
OnCopyTermDefaultsToDefaultObject(InCompilationContext, InPerNodeContext, OutCompiledData);
}
void UAnimGraphNode_Base::OverrideAssets(IAnimBlueprintNodeOverrideAssetsContext& InContext) const
{
if(InContext.GetAssets().Num() > 0)
{
if(UAnimationAsset* AnimationAsset = Cast<UAnimationAsset>(InContext.GetAssets()[0]))
{
// Call the legacy implementation
PRAGMA_DISABLE_DEPRECATION_WARNINGS
InContext.GetAnimNode<FAnimNode_Base>().OverrideAsset(AnimationAsset);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
}
}
OnOverrideAssets(InContext);
}
void UAnimGraphNode_Base::InternalPinCreation(TArray<UEdGraphPin*>* OldPins)
{
// preload required assets first before creating pins
PreloadRequiredAssets();
const UAnimationGraphSchema* Schema = GetDefault<UAnimationGraphSchema>();
if (const FStructProperty* NodeStruct = GetFNodeProperty())
{
// Display any currently visible optional pins
{
UObject* NodeDefaults = GetArchetype();
FAnimBlueprintNodeOptionalPinManager OptionalPinManager(this, OldPins);
OptionalPinManager.AllocateDefaultPins(NodeStruct->Struct, NodeStruct->ContainerPtrToValuePtr<uint8>(this), NodeDefaults ? NodeStruct->ContainerPtrToValuePtr<uint8>(NodeDefaults) : nullptr);
}
// Create the output pin, if needed
CreateOutputPins();
}
}
void UAnimGraphNode_Base::AllocateDefaultPins()
{
InternalPinCreation(NULL);
}
void UAnimGraphNode_Base::ReallocatePinsDuringReconstruction(TArray<UEdGraphPin*>& OldPins)
{
InternalPinCreation(&OldPins);
RestoreSplitPins(OldPins);
}
bool UAnimGraphNode_Base::CanJumpToDefinition() const
{
return GetJumpTargetForDoubleClick() != nullptr;
}
void UAnimGraphNode_Base::JumpToDefinition() const
{
if (UObject* HyperlinkTarget = GetJumpTargetForDoubleClick())
{
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAsset(HyperlinkTarget);
}
}
FLinearColor UAnimGraphNode_Base::GetNodeTitleColor() const
{
return FLinearColor::Black;
}
UScriptStruct* UAnimGraphNode_Base::GetFNodeType() const
{
UScriptStruct* BaseFStruct = FAnimNode_Base::StaticStruct();
for (TFieldIterator<FProperty> PropIt(GetClass(), EFieldIteratorFlags::IncludeSuper); PropIt; ++PropIt)
{
if (FStructProperty* StructProp = CastField<FStructProperty>(*PropIt))
{
if (StructProp->Struct->IsChildOf(BaseFStruct))
{
return StructProp->Struct;
}
}
}
return NULL;
}
FStructProperty* UAnimGraphNode_Base::GetFNodeProperty() const
{
UScriptStruct* BaseFStruct = FAnimNode_Base::StaticStruct();
for (TFieldIterator<FProperty> PropIt(GetClass(), EFieldIteratorFlags::IncludeSuper); PropIt; ++PropIt)
{
if (FStructProperty* StructProp = CastField<FStructProperty>(*PropIt))
{
if (StructProp->Struct->IsChildOf(BaseFStruct))
{
return StructProp;
}
}
}
return NULL;
}
FString UAnimGraphNode_Base::GetNodeCategory() const
{
return TEXT("Misc.");
}
void UAnimGraphNode_Base::GetNodeAttributes( TArray<TKeyValuePair<FString, FString>>& OutNodeAttributes ) const
{
OutNodeAttributes.Add( TKeyValuePair<FString, FString>( TEXT( "Type" ), TEXT( "AnimGraphNode" ) ));
OutNodeAttributes.Add( TKeyValuePair<FString, FString>( TEXT( "Class" ), GetClass()->GetName() ));
OutNodeAttributes.Add( TKeyValuePair<FString, FString>( TEXT( "Name" ), GetName() ));
}
void UAnimGraphNode_Base::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const
{
// actions get registered under specific object-keys; the idea is that
// actions might have to be updated (or deleted) if their object-key is
// mutated (or removed)... here we use the node's class (so if the node
// type disappears, then the action should go with it)
UClass* ActionKey = GetClass();
// to keep from needlessly instantiating a UBlueprintNodeSpawner, first
// check to make sure that the registrar is looking for actions of this type
// (could be regenerating actions for a specific asset, and therefore the
// registrar would only accept actions corresponding to that asset)
if (ActionRegistrar.IsOpenForRegistration(ActionKey))
{
UBlueprintNodeSpawner* NodeSpawner = UBlueprintNodeSpawner::Create(GetClass());
check(NodeSpawner != nullptr);
ActionRegistrar.AddBlueprintAction(ActionKey, NodeSpawner);
}
}
FText UAnimGraphNode_Base::GetMenuCategory() const
{
return FText::FromString(GetNodeCategory());
}
void UAnimGraphNode_Base::GetPinAssociatedProperty(const UScriptStruct* NodeType, const UEdGraphPin* InputPin, FProperty*& OutProperty, int32& OutIndex) const
{
OutProperty = nullptr;
OutIndex = INDEX_NONE;
//@TODO: Name-based hackery, avoid the roundtrip and better indicate when it's an array pose pin
const FString PinNameStr = InputPin->PinName.ToString();
const int32 UnderscoreIndex = PinNameStr.Find(TEXT("_"), ESearchCase::CaseSensitive);
if (UnderscoreIndex != INDEX_NONE)
{
const FString ArrayName = PinNameStr.Left(UnderscoreIndex);
if (FArrayProperty* ArrayProperty = FindFProperty<FArrayProperty>(NodeType, *ArrayName))
{
const int32 ArrayIndex = FCString::Atoi(*(PinNameStr.Mid(UnderscoreIndex + 1)));
OutProperty = ArrayProperty;
OutIndex = ArrayIndex;
}
}
// If the array check failed or we have no underscores
if(OutProperty == nullptr)
{
if (FProperty* Property = FindFProperty<FProperty>(NodeType, InputPin->PinName))
{
OutProperty = Property;
OutIndex = INDEX_NONE;
}
}
}
FPoseLinkMappingRecord UAnimGraphNode_Base::GetLinkIDLocation(const UScriptStruct* NodeType, UEdGraphPin* SourcePin)
{
if (SourcePin->LinkedTo.Num() > 0)
{
if (UAnimGraphNode_Base* LinkedNode = Cast<UAnimGraphNode_Base>(FBlueprintEditorUtils::FindFirstCompilerRelevantNode(SourcePin->LinkedTo[0])))
{
//@TODO: Name-based hackery, avoid the roundtrip and better indicate when it's an array pose pin
const FString SourcePinName = SourcePin->PinName.ToString();
const int32 UnderscoreIndex = SourcePinName.Find(TEXT("_"), ESearchCase::CaseSensitive);
if (UnderscoreIndex != INDEX_NONE)
{
const FString ArrayName = SourcePinName.Left(UnderscoreIndex);
if (FArrayProperty* ArrayProperty = FindFProperty<FArrayProperty>(NodeType, *ArrayName))
{
if (FStructProperty* Property = CastField<FStructProperty>(ArrayProperty->Inner))
{
if (Property->Struct->IsChildOf(FPoseLinkBase::StaticStruct()))
{
const int32 ArrayIndex = FCString::Atoi(*(SourcePinName.Mid(UnderscoreIndex + 1)));
return FPoseLinkMappingRecord::MakeFromArrayEntry(this, LinkedNode, ArrayProperty, ArrayIndex);
}
}
}
}
else
{
if (FStructProperty* Property = FindFProperty<FStructProperty>(NodeType, SourcePin->PinName))
{
if (Property->Struct->IsChildOf(FPoseLinkBase::StaticStruct()))
{
return FPoseLinkMappingRecord::MakeFromMember(this, LinkedNode, Property);
}
}
}
}
}
return FPoseLinkMappingRecord::MakeInvalid();
}
void UAnimGraphNode_Base::CreatePinsForPoseLink(FProperty* PoseProperty, int32 ArrayIndex)
{
UScriptStruct* A2PoseStruct = FA2Pose::StaticStruct();
// pose input
const FName NewPinName = (ArrayIndex == INDEX_NONE) ? PoseProperty->GetFName() : *FString::Printf(TEXT("%s_%d"), *(PoseProperty->GetName()), ArrayIndex);
CreatePin(EGPD_Input, UAnimationGraphSchema::PC_Struct, A2PoseStruct, NewPinName);
}
void UAnimGraphNode_Base::PostProcessPinName(const UEdGraphPin* Pin, FString& DisplayName) const
{
if (Pin->Direction == EGPD_Output)
{
if (Pin->PinName == TEXT("Pose"))
{
DisplayName.Reset();
}
}
}
bool UAnimGraphNode_Base::CanCreateUnderSpecifiedSchema(const UEdGraphSchema* DesiredSchema) const
{
return DesiredSchema->GetClass()->IsChildOf(UAnimationGraphSchema::StaticClass());
}
FString UAnimGraphNode_Base::GetDocumentationLink() const
{
return TEXT("Shared/GraphNodes/Animation");
}
void UAnimGraphNode_Base::GetPinHoverText(const UEdGraphPin& Pin, FString& HoverTextOut) const
{
if (UAnimationGraphSchema::IsLocalSpacePosePin(Pin.PinType))
{
HoverTextOut = TEXT("Animation Pose");
}
else if (UAnimationGraphSchema::IsComponentSpacePosePin(Pin.PinType))
{
HoverTextOut = TEXT("Animation Pose (Component Space)");
}
else
{
Super::GetPinHoverText(Pin, HoverTextOut);
}
}
void UAnimGraphNode_Base::ProcessDuringCompilation(IAnimBlueprintCompilationContext& InCompilationContext, IAnimBlueprintGeneratedClassCompiledData& OutCompiledData)
{
UAnimBlueprintExtension_Base* Extension = UAnimBlueprintExtension::GetExtension<UAnimBlueprintExtension_Base>(GetAnimBlueprint());
// Record pose pins for later patchup and gather pins that have an associated evaluation handler
Extension->AddStructEvalHandlers(this, InCompilationContext, OutCompiledData);
// Call the override point
OnProcessDuringCompilation(InCompilationContext, OutCompiledData);
}
void UAnimGraphNode_Base::HandleAnimReferenceCollection(UAnimationAsset* AnimAsset, TArray<UAnimationAsset*>& AnimationAssets) const
{
if(AnimAsset)
{
AnimAsset->HandleAnimReferenceCollection(AnimationAssets, true);
}
}
void UAnimGraphNode_Base::OnNodeSelected(bool bInIsSelected, FEditorModeTools& InModeTools, FAnimNode_Base* InRuntimeNode)
{
const FEditorModeID ModeID = GetEditorMode();
if (ModeID != NAME_None)
{
if (bInIsSelected)
{
InModeTools.ActivateMode(ModeID);
if (FEdMode* EdMode = InModeTools.GetActiveMode(ModeID))
{
static_cast<IAnimNodeEditMode*>(EdMode)->EnterMode(this, InRuntimeNode);
}
}
else
{
if (FEdMode* EdMode = InModeTools.GetActiveMode(ModeID))
{
static_cast<IAnimNodeEditMode*>(EdMode)->ExitMode();
}
InModeTools.DeactivateMode(ModeID);
}
}
}
FEditorModeID UAnimGraphNode_Base::GetEditorMode() const
{
return AnimNodeEditModes::AnimNode;
}
FAnimNode_Base* UAnimGraphNode_Base::FindDebugAnimNode(USkeletalMeshComponent * PreviewSkelMeshComp) const
{
FAnimNode_Base* DebugNode = nullptr;
if (PreviewSkelMeshComp != nullptr && PreviewSkelMeshComp->GetAnimInstance() != nullptr)
{
// find an anim node index from debug data
UAnimBlueprintGeneratedClass* AnimBlueprintClass = Cast<UAnimBlueprintGeneratedClass>(PreviewSkelMeshComp->GetAnimInstance()->GetClass());
if (AnimBlueprintClass)
{
FAnimBlueprintDebugData& DebugData = AnimBlueprintClass->GetAnimBlueprintDebugData();
int32* IndexPtr = DebugData.NodePropertyToIndexMap.Find(this);
if (IndexPtr)
{
int32 AnimNodeIndex = *IndexPtr;
// reverse node index temporarily because of a bug in NodeGuidToIndexMap
AnimNodeIndex = AnimBlueprintClass->GetAnimNodeProperties().Num() - AnimNodeIndex - 1;
DebugNode = AnimBlueprintClass->GetAnimNodeProperties()[AnimNodeIndex]->ContainerPtrToValuePtr<FAnimNode_Base>(PreviewSkelMeshComp->GetAnimInstance());
}
}
}
return DebugNode;
}
EAnimAssetHandlerType UAnimGraphNode_Base::SupportsAssetClass(const UClass* AssetClass) const
{
return EAnimAssetHandlerType::NotSupported;
}
void UAnimGraphNode_Base::PinDefaultValueChanged(UEdGraphPin* Pin)
{
Super::PinDefaultValueChanged(Pin);
CopyPinDefaultsToNodeData(Pin);
if(UAnimationGraph* AnimationGraph = Cast<UAnimationGraph>(GetGraph()))
{
AnimationGraph->OnPinDefaultValueChanged.Broadcast(Pin);
}
}
FString UAnimGraphNode_Base::GetPinMetaData(FName InPinName, FName InKey)
{
FString MetaData = Super::GetPinMetaData(InPinName, InKey);
if(MetaData.IsEmpty())
{
// Check properties of our anim node
if(FStructProperty* NodeStructProperty = GetFNodeProperty())
{
for (TFieldIterator<FProperty> It(NodeStructProperty->Struct); It; ++It)
{
const FProperty* Property = *It;
if (Property && Property->GetFName() == InPinName)
{
return Property->GetMetaData(InKey);
}
}
}
}
return MetaData;
}
void UAnimGraphNode_Base::AddSearchMetaDataInfo(TArray<struct FSearchTagDataPair>& OutTaggedMetaData) const
{
Super::AddSearchMetaDataInfo(OutTaggedMetaData);
for(const TPair<FName, FAnimGraphNodePropertyBinding>& BindingPair : PropertyBindings)
{
OutTaggedMetaData.Add(FSearchTagDataPair(FFindInBlueprintSearchTags::FiB_Name, FText::FromName(BindingPair.Key)));
OutTaggedMetaData.Add(FSearchTagDataPair(LOCTEXT("Binding", "Binding"), BindingPair.Value.PathAsText));
}
}
bool UAnimGraphNode_Base::IsPinExposedAndLinked(const FString& InPinName, const EEdGraphPinDirection InDirection) const
{
UEdGraphPin* Pin = FindPin(InPinName, InDirection);
return Pin != nullptr && Pin->LinkedTo.Num() > 0 && Pin->LinkedTo[0] != nullptr;
}
bool UAnimGraphNode_Base::IsPinExposedAndBound(const FString& InPinName, const EEdGraphPinDirection InDirection) const
{
UEdGraphPin* Pin = FindPin(InPinName, InDirection);
return Pin != nullptr && Pin->LinkedTo.Num() == 0 && PropertyBindings.Find(Pin->GetFName()) != nullptr;
}
void UAnimGraphNode_Base::PinConnectionListChanged(UEdGraphPin* Pin)
{
if(Pin->LinkedTo.Num() > 0)
{
// If we have links, clear any bindings
// Compare FName without number to make sure we catch array properties that are split into multiple pins
FName ComparisonName = Pin->GetFName();
ComparisonName.SetNumber(0);
PropertyBindings.Remove(ComparisonName);
}
}
void UAnimGraphNode_Base::AutowireNewNode(UEdGraphPin* FromPin)
{
// Ensure the pin is valid, a pose pin, and has a single link
if (FromPin && UAnimationGraphSchema::IsPosePin(FromPin->PinType))
{
auto FindFirstPosePinInDirection = [this](EEdGraphPinDirection Direction) -> UEdGraphPin*
{
UEdGraphPin** PinToConnectTo = Pins.FindByPredicate([Direction](UEdGraphPin* Pin) -> bool
{
return Pin && Pin->Direction == Direction && UAnimationGraphSchema::IsPosePin(Pin->PinType);
});
return PinToConnectTo ? *PinToConnectTo : nullptr;
};
// Get the linked pin, if valid, and ensure it iss also a pose pin
UEdGraphPin* LinkedPin = FromPin->LinkedTo.Num() == 1 ? FromPin->LinkedTo[0] : nullptr;
if (LinkedPin && UAnimationGraphSchema::IsPosePin(LinkedPin->PinType))
{
// Retrieve the first pin, of similar direction, from this node
UEdGraphPin* PinToConnectTo = FindFirstPosePinInDirection(FromPin->Direction);
if (PinToConnectTo)
{
ensure(GetSchema()->TryCreateConnection(LinkedPin, PinToConnectTo));
}
}
// Link this node to the FromPin, so find the first pose pin of opposite direction on this node
UEdGraphPin* PinToConnectTo = FindFirstPosePinInDirection(FromPin->Direction == EEdGraphPinDirection::EGPD_Input ? EEdGraphPinDirection::EGPD_Output : EEdGraphPinDirection::EGPD_Input);
if (PinToConnectTo)
{
ensure(GetSchema()->TryCreateConnection(FromPin, PinToConnectTo));
}
}
}
#undef LOCTEXT_NAMESPACE