// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. #include "AnimGraphPrivatePCH.h" #include "AnimGraphNode_Base.h" #include "AnimationGraphSchema.h" #include "BlueprintNodeSpawner.h" #include "BlueprintActionDatabaseRegistrar.h" #include "Animation/AnimInstance.h" #include "AnimBlueprintNodeOptionalPinManager.h" ///////////////////////////////////////////////////// // UAnimGraphNode_Base UAnimGraphNode_Base::UAnimGraphNode_Base(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { } void UAnimGraphNode_Base::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) { FName PropertyName = (PropertyChangedEvent.Property != NULL) ? PropertyChangedEvent.Property->GetFName() : NAME_None; if ((PropertyName == GET_MEMBER_NAME_CHECKED(FOptionalPinFromProperty, bShowPin))) { GetSchema()->ReconstructNode(*this); } Super::PostEditChangeProperty(PropertyChangedEvent); } void UAnimGraphNode_Base::CreateOutputPins() { if (!IsSinkNode()) { const UAnimationGraphSchema* Schema = GetDefault(); CreatePin(EGPD_Output, Schema->PC_Struct, TEXT(""), FPoseLink::StaticStruct(), /*bIsArray=*/ false, /*bIsReference=*/ false, TEXT("Pose")); } } void UAnimGraphNode_Base::InternalPinCreation(TArray* OldPins) { // preload required assets first before creating pins PreloadRequiredAssets(); const UAnimationGraphSchema* Schema = GetDefault(); if (const UStructProperty* NodeStruct = GetFNodeProperty()) { // Display any currently visible optional pins { FAnimBlueprintNodeOptionalPinManager OptionalPinManager(this, OldPins); OptionalPinManager.AllocateDefaultPins(NodeStruct->Struct, NodeStruct->ContainerPtrToValuePtr(this)); } // Create the output pin, if needed CreateOutputPins(); } } void UAnimGraphNode_Base::AllocateDefaultPins() { InternalPinCreation(NULL); } void UAnimGraphNode_Base::ReallocatePinsDuringReconstruction(TArray& OldPins) { InternalPinCreation(&OldPins); } FLinearColor UAnimGraphNode_Base::GetNodeTitleColor() const { return FLinearColor::Black; } UScriptStruct* UAnimGraphNode_Base::GetFNodeType() const { UScriptStruct* BaseFStruct = FAnimNode_Base::StaticStruct(); for (TFieldIterator PropIt(GetClass(), EFieldIteratorFlags::IncludeSuper); PropIt; ++PropIt) { if (UStructProperty* StructProp = Cast(*PropIt)) { if (StructProp->Struct->IsChildOf(BaseFStruct)) { return StructProp->Struct; } } } return NULL; } UStructProperty* UAnimGraphNode_Base::GetFNodeProperty() const { UScriptStruct* BaseFStruct = FAnimNode_Base::StaticStruct(); for (TFieldIterator PropIt(GetClass(), EFieldIteratorFlags::IncludeSuper); PropIt; ++PropIt) { if (UStructProperty* StructProp = Cast(*PropIt)) { if (StructProp->Struct->IsChildOf(BaseFStruct)) { return StructProp; } } } return NULL; } FString UAnimGraphNode_Base::GetNodeCategory() const { return TEXT("Misc."); } void UAnimGraphNode_Base::GetNodeAttributes( TArray>& OutNodeAttributes ) const { OutNodeAttributes.Add( TKeyValuePair( TEXT( "Type" ), TEXT( "AnimGraphNode" ) )); OutNodeAttributes.Add( TKeyValuePair( TEXT( "Class" ), GetClass()->GetName() )); OutNodeAttributes.Add( TKeyValuePair( 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, UEdGraphPin* InputPin, UProperty*& OutProperty, int32& OutIndex) { OutProperty = nullptr; OutIndex = INDEX_NONE; //@TODO: Name-based hackery, avoid the roundtrip and better indicate when it's an array pose pin int32 UnderscoreIndex = InputPin->PinName.Find(TEXT("_"), ESearchCase::CaseSensitive); if (UnderscoreIndex != INDEX_NONE) { FString ArrayName = InputPin->PinName.Left(UnderscoreIndex); int32 ArrayIndex = FCString::Atoi(*(InputPin->PinName.Mid(UnderscoreIndex + 1))); if (UArrayProperty* ArrayProperty = FindField(NodeType, *ArrayName)) { OutProperty = ArrayProperty; OutIndex = ArrayIndex; } } // If the array check failed or we have no underscores if(OutProperty == nullptr) { if (UProperty* Property = FindField(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(FBlueprintEditorUtils::FindFirstCompilerRelevantNode(SourcePin->LinkedTo[0]))) { //@TODO: Name-based hackery, avoid the roundtrip and better indicate when it's an array pose pin int32 UnderscoreIndex = SourcePin->PinName.Find(TEXT("_"), ESearchCase::CaseSensitive); if (UnderscoreIndex != INDEX_NONE) { FString ArrayName = SourcePin->PinName.Left(UnderscoreIndex); int32 ArrayIndex = FCString::Atoi(*(SourcePin->PinName.Mid(UnderscoreIndex + 1))); if (UArrayProperty* ArrayProperty = FindField(NodeType, *ArrayName)) { if (UStructProperty* Property = Cast(ArrayProperty->Inner)) { if (Property->Struct->IsChildOf(FPoseLinkBase::StaticStruct())) { return FPoseLinkMappingRecord::MakeFromArrayEntry(this, LinkedNode, ArrayProperty, ArrayIndex); } } } } else { if (UStructProperty* Property = FindField(NodeType, *(SourcePin->PinName))) { if (Property->Struct->IsChildOf(FPoseLinkBase::StaticStruct())) { return FPoseLinkMappingRecord::MakeFromMember(this, LinkedNode, Property); } } } } } return FPoseLinkMappingRecord::MakeInvalid(); } void UAnimGraphNode_Base::CreatePinsForPoseLink(UProperty* PoseProperty, int32 ArrayIndex) { const UAnimationGraphSchema* Schema = GetDefault(); UScriptStruct* A2PoseStruct = FA2Pose::StaticStruct(); // pose input const FString NewPinName = (ArrayIndex == INDEX_NONE) ? PoseProperty->GetName() : FString::Printf(TEXT("%s_%d"), *(PoseProperty->GetName()), ArrayIndex); CreatePin(EGPD_Input, Schema->PC_Struct, TEXT(""), A2PoseStruct, /*bIsArray=*/ false, /*bIsReference=*/ false, NewPinName); } void UAnimGraphNode_Base::PostProcessPinName(const UEdGraphPin* Pin, FString& DisplayName) const { if (Pin->Direction == EGPD_Output) { if (Pin->PinName == TEXT("Pose")) { DisplayName = TEXT(""); } } } 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::HandleAnimReferenceCollection(UAnimationAsset* AnimAsset, TArray& AnimationAssets) const { if(AnimAsset) { AnimAsset->HandleAnimReferenceCollection(AnimationAssets, true); } }