// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. /*============================================================================= AnimationGraphSchema.cpp =============================================================================*/ #include "AnimGraphPrivatePCH.h" #include "BlueprintUtilities.h" #include "GraphEditorActions.h" #include "ScopedTransaction.h" #include "AssetData.h" #include "AnimationGraphSchema.h" #include "K2Node_TransitionRuleGetter.h" #include "AnimStateNode.h" #include "Animation/AimOffsetBlendSpace.h" #include "Animation/AimOffsetBlendSpace1D.h" #include "AnimGraphNode_AssetPlayerBase.h" #include "AnimGraphNode_BlendSpacePlayer.h" #include "AnimGraphNode_ComponentToLocalSpace.h" #include "AnimGraphNode_LocalToComponentSpace.h" #include "AnimGraphNode_Root.h" #include "AnimGraphNode_RotationOffsetBlendSpace.h" #include "AnimGraphNode_SequencePlayer.h" #include "AnimGraphNode_PoseBlendNode.h" #include "AnimGraphNode_PoseByName.h" #include "AnimGraphCommands.h" #define LOCTEXT_NAMESPACE "AnimationGraphSchema" ///////////////////////////////////////////////////// // UAnimationGraphSchema UAnimationGraphSchema::UAnimationGraphSchema(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { PN_SequenceName = TEXT("Sequence"); NAME_NeverAsPin = TEXT("NeverAsPin"); NAME_PinHiddenByDefault = TEXT("PinHiddenByDefault"); NAME_PinShownByDefault = TEXT("PinShownByDefault"); NAME_AlwaysAsPin = TEXT("AlwaysAsPin"); NAME_OnEvaluate = TEXT("OnEvaluate"); NAME_CustomizeProperty = TEXT("CustomizeProperty"); DefaultEvaluationHandlerName = TEXT("EvaluateGraphExposedInputs"); } FLinearColor UAnimationGraphSchema::GetPinTypeColor(const FEdGraphPinType& PinType) const { const bool bAdditive = PinType.PinSubCategory == TEXT("Additive"); if (UAnimationGraphSchema::IsLocalSpacePosePin(PinType)) { if (bAdditive) { return FLinearColor(0.12, 0.60, 0.10); } else { return FLinearColor::White; } } else if (UAnimationGraphSchema::IsComponentSpacePosePin(PinType)) { //@TODO: Pick better colors if (bAdditive) { return FLinearColor(0.12, 0.60, 0.60); } else { return FLinearColor(0.20f, 0.50f, 1.00f); } } return Super::GetPinTypeColor(PinType); } EGraphType UAnimationGraphSchema::GetGraphType(const UEdGraph* TestEdGraph) const { return GT_Animation; } void UAnimationGraphSchema::CreateDefaultNodesForGraph(UEdGraph& Graph) const { // Create the result node FGraphNodeCreator NodeCreator(Graph); UAnimGraphNode_Root* ResultSinkNode = NodeCreator.CreateNode(); NodeCreator.Finalize(); SetNodeMetaData(ResultSinkNode, FNodeMetadata::DefaultGraphNode); } void UAnimationGraphSchema::HandleGraphBeingDeleted(UEdGraph& GraphBeingRemoved) const { if (UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(&GraphBeingRemoved)) { // Look for state nodes that reference this graph TArray StateNodes; FBlueprintEditorUtils::GetAllNodesOfClassEx(Blueprint, StateNodes); TSet NodesToDelete; for (int32 i = 0; i < StateNodes.Num(); ++i) { UAnimStateNodeBase* StateNode = StateNodes[i]; if (StateNode->GetBoundGraph() == &GraphBeingRemoved) { NodesToDelete.Add(StateNode); } } // Delete the node that owns us ensure(NodesToDelete.Num() <= 1); for (TSet::TIterator It(NodesToDelete); It; ++It) { UAnimStateNodeBase* NodeToDelete = *It; FBlueprintEditorUtils::RemoveNode(Blueprint, NodeToDelete, true); // Prevent re-entrancy here NodeToDelete->ClearBoundGraph(); } } } bool UAnimationGraphSchema::IsPosePin(const FEdGraphPinType& PinType) { return IsLocalSpacePosePin(PinType) || IsComponentSpacePosePin(PinType); } bool UAnimationGraphSchema::IsLocalSpacePosePin(const FEdGraphPinType& PinType) { const UAnimationGraphSchema* Schema = GetDefault(); UScriptStruct* PoseLinkStruct = FPoseLink::StaticStruct(); return (PinType.PinCategory == Schema->PC_Struct) && (PinType.PinSubCategoryObject == PoseLinkStruct); } bool UAnimationGraphSchema::IsComponentSpacePosePin(const FEdGraphPinType& PinType) { const UAnimationGraphSchema* Schema = GetDefault(); UScriptStruct* ComponentSpacePoseLinkStruct = FComponentSpacePoseLink::StaticStruct(); return (PinType.PinCategory == Schema->PC_Struct) && (PinType.PinSubCategoryObject == ComponentSpacePoseLinkStruct); } bool UAnimationGraphSchema::TryCreateConnection(UEdGraphPin* A, UEdGraphPin* B) const { UEdGraphPin* OutputPin = nullptr; UEdGraphPin* InputPin = nullptr; if(A->Direction == EEdGraphPinDirection::EGPD_Output) { OutputPin = A; InputPin = B; } else { OutputPin = B; InputPin = A; } check(OutputPin && InputPin); UEdGraphNode* OutputNode = OutputPin->GetOwningNode(); if(UK2Node_Knot* RerouteNode = Cast(OutputNode)) { // Double check this is our "exec"-like line bool bOutputIsPose = IsPosePin(OutputPin->PinType); bool bInputIsPose = IsPosePin(InputPin->PinType); bool bHavePosePin = bOutputIsPose || bInputIsPose; bool bHaveWildPin = InputPin->PinType.PinCategory == PC_Wildcard || OutputPin->PinType.PinCategory == PC_Wildcard; if(bOutputIsPose && bInputIsPose || (bHavePosePin && bHaveWildPin)) { // Ok this is a valid exec-like line, we need to kill any connections already on the output pin OutputPin->BreakAllPinLinks(); } } return Super::TryCreateConnection(A, B); } const FPinConnectionResponse UAnimationGraphSchema::DetermineConnectionResponseOfCompatibleTypedPins(const UEdGraphPin* PinA, const UEdGraphPin* PinB, const UEdGraphPin* InputPin, const UEdGraphPin* OutputPin) const { // Enforce a tree hierarchy; where poses can only have one output (parent) connection if (IsPosePin(OutputPin->PinType) && IsPosePin(InputPin->PinType)) { if ((OutputPin->LinkedTo.Num() > 0) || (InputPin->LinkedTo.Num() > 0)) { const ECanCreateConnectionResponse ReplyBreakOutputs = CONNECT_RESPONSE_BREAK_OTHERS_AB; return FPinConnectionResponse(ReplyBreakOutputs, TEXT("Replace existing connections")); } } // Fall back to standard K2 rules return Super::DetermineConnectionResponseOfCompatibleTypedPins(PinA, PinB, InputPin, OutputPin); } bool UAnimationGraphSchema::ArePinsCompatible(const UEdGraphPin* PinA, const UEdGraphPin* PinB, const UClass* CallingContext, bool bIgnoreArray) const { // both are pose pin, but doesn't match type, then return false; if (IsPosePin(PinA->PinType) && IsPosePin(PinB->PinType) && IsLocalSpacePosePin(PinA->PinType) != IsLocalSpacePosePin(PinB->PinType)) { return false; } return Super::ArePinsCompatible(PinA, PinB, CallingContext, bIgnoreArray); } bool UAnimationGraphSchema::DoesSupportAnimNotifyActions() const { // Don't offer notify items in anim graph return false; } bool UAnimationGraphSchema::SearchForAutocastFunction(const UEdGraphPin* OutputPin, const UEdGraphPin* InputPin, FName& TargetFunction, /*out*/ UClass*& FunctionOwner) const { if (IsComponentSpacePosePin(OutputPin->PinType) && IsLocalSpacePosePin(InputPin->PinType)) { // Insert a Component To LocalSpace conversion return true; } else if (IsLocalSpacePosePin(OutputPin->PinType) && IsComponentSpacePosePin(InputPin->PinType)) { // Insert a Local To ComponentSpace conversion return true; } else { return Super::SearchForAutocastFunction(OutputPin, InputPin, TargetFunction, FunctionOwner); } } bool UAnimationGraphSchema::CreateAutomaticConversionNodeAndConnections(UEdGraphPin* PinA, UEdGraphPin* PinB) const { // Determine which pin is an input and which pin is an output UEdGraphPin* InputPin = NULL; UEdGraphPin* OutputPin = NULL; if (!CategorizePinsByDirection(PinA, PinB, /*out*/ InputPin, /*out*/ OutputPin)) { return false; } // Look for animation specific conversion operations UK2Node* TemplateNode = NULL; if (IsComponentSpacePosePin(OutputPin->PinType) && IsLocalSpacePosePin(InputPin->PinType)) { TemplateNode = NewObject(); } else if (IsLocalSpacePosePin(OutputPin->PinType) && IsComponentSpacePosePin(InputPin->PinType)) { TemplateNode = NewObject(); } // Spawn the conversion node if it's specific to animation if (TemplateNode != NULL) { UEdGraph* Graph = InputPin->GetOwningNode()->GetGraph(); FVector2D AverageLocation = CalculateAveragePositionBetweenNodes(InputPin, OutputPin); UK2Node* ConversionNode = FEdGraphSchemaAction_K2NewNode::SpawnNodeFromTemplate(Graph, TemplateNode, AverageLocation); AutowireConversionNode(InputPin, OutputPin, ConversionNode); return true; } else { // Give the regular conversions a shot return Super::CreateAutomaticConversionNodeAndConnections(PinA, PinB); } } bool IsAimOffsetBlendSpace(UBlendSpaceBase* BlendSpace) { return BlendSpace->IsA(UAimOffsetBlendSpace::StaticClass()) || BlendSpace->IsA(UAimOffsetBlendSpace1D::StaticClass()); } void UAnimationGraphSchema::SpawnNodeFromAsset(UAnimationAsset* Asset, const FVector2D& GraphPosition, UEdGraph* Graph, UEdGraphPin* PinIfAvailable) { check(Graph); check(Graph->GetSchema()->IsA(UAnimationGraphSchema::StaticClass())); check(Asset); UAnimBlueprint* AnimBlueprint = Cast(FBlueprintEditorUtils::FindBlueprintForGraph(Graph)); const bool bSkelMatch = (AnimBlueprint != NULL) && (AnimBlueprint->TargetSkeleton == Asset->GetSkeleton()); const bool bTypeMatch = (PinIfAvailable == NULL) || UAnimationGraphSchema::IsLocalSpacePosePin(PinIfAvailable->PinType); const bool bDirectionMatch = (PinIfAvailable == NULL) || (PinIfAvailable->Direction == EGPD_Input); if (bSkelMatch && bTypeMatch && bDirectionMatch) { FEdGraphSchemaAction_K2NewNode Action; UClass* NewNodeClass = GetNodeClassForAsset(Asset->GetClass()); if (NewNodeClass) { check(NewNodeClass->IsChildOf(UAnimGraphNode_AssetPlayerBase::StaticClass())); UAnimGraphNode_AssetPlayerBase* NewNode = NewObject(GetTransientPackage(), NewNodeClass); NewNode->SetAnimationAsset(Asset); Action.NodeTemplate = NewNode; Action.PerformAction(Graph, PinIfAvailable, GraphPosition); } } } void UAnimationGraphSchema::UpdateNodeWithAsset(UK2Node* K2Node, UAnimationAsset* Asset) { if (Asset != NULL) { if (UAnimGraphNode_SequencePlayer* SequencePlayerNode = Cast(K2Node)) { if (UAnimSequence* Sequence = Cast(Asset)) { // Skeleton matches, and it's a sequence player; replace the existing sequence with the dragged one SequencePlayerNode->Node.Sequence = Sequence; } } else if (UBlendSpace* BlendSpace = Cast(Asset)) { if (IsAimOffsetBlendSpace(BlendSpace)) { if (UAnimGraphNode_RotationOffsetBlendSpace* RotationOffsetNode = Cast(K2Node)) { // Skeleton matches, and it's a blendspace player; replace the existing blendspace with the dragged one RotationOffsetNode->Node.BlendSpace = BlendSpace; } } else { if (UAnimGraphNode_BlendSpacePlayer* BlendSpacePlayerNode = Cast(K2Node)) { // Skeleton matches, and it's a blendspace player; replace the existing blendspace with the dragged one BlendSpacePlayerNode->Node.BlendSpace = BlendSpace; } } } else if (UPoseAsset* PoseAsset = Cast(Asset)) { if (UAnimGraphNode_PoseBlendNode* PoseBlendNode = Cast(K2Node)) { // Skeleton matches, and it's a blendspace player; replace the existing blendspace with the dragged one PoseBlendNode->Node.PoseAsset = PoseAsset; } else { if (UAnimGraphNode_PoseByName* PoseByNameNode = Cast(K2Node)) { // Skeleton matches, and it's a blendspace player; replace the existing blendspace with the dragged one PoseByNameNode->Node.PoseAsset = PoseAsset; } } } } } void UAnimationGraphSchema::DroppedAssetsOnGraph( const TArray& Assets, const FVector2D& GraphPosition, UEdGraph* Graph ) const { UAnimationAsset* Asset = FAssetData::GetFirstAsset(Assets); if ((Asset != NULL) && (Graph != NULL)) { SpawnNodeFromAsset(Asset, GraphPosition, Graph, NULL); } } void UAnimationGraphSchema::DroppedAssetsOnNode(const TArray& Assets, const FVector2D& GraphPosition, UEdGraphNode* Node) const { UAnimationAsset* Asset = FAssetData::GetFirstAsset(Assets); UK2Node* K2Node = Cast(Node); if ((Asset != NULL) && (K2Node!= NULL)) { UpdateNodeWithAsset(K2Node, Asset); } } void UAnimationGraphSchema::DroppedAssetsOnPin(const TArray& Assets, const FVector2D& GraphPosition, UEdGraphPin* Pin) const { UAnimationAsset* Asset = FAssetData::GetFirstAsset(Assets); if ((Asset != NULL) && (Pin != NULL)) { SpawnNodeFromAsset(Asset, GraphPosition, Pin->GetOwningNode()->GetGraph(), Pin); } } void UAnimationGraphSchema::GetAssetsNodeHoverMessage(const TArray& Assets, const UEdGraphNode* HoverNode, FString& OutTooltipText, bool& OutOkIcon) const { UAnimationAsset* Asset = FAssetData::GetFirstAsset(Assets); if ((Asset == NULL) || (HoverNode == NULL)) { OutTooltipText = TEXT(""); OutOkIcon = false; return; } const UK2Node* PlayerNodeUnderCursor = NULL; if (Asset->IsA(UAnimSequence::StaticClass())) { PlayerNodeUnderCursor = Cast(HoverNode); } else if (Asset->IsA(UBlendSpace::StaticClass())) { UBlendSpace* BlendSpace = CastChecked(Asset); if (IsAimOffsetBlendSpace(BlendSpace)) { PlayerNodeUnderCursor = Cast(HoverNode); } else { PlayerNodeUnderCursor = Cast(HoverNode); } } else if (Asset->IsA(UPoseAsset::StaticClass())) { UPoseAsset* PoseAsset = CastChecked(Asset); PlayerNodeUnderCursor = Cast(HoverNode); } // this one only should happen when there is an Anim Blueprint UAnimBlueprint* AnimBlueprint = Cast(FBlueprintEditorUtils::FindBlueprintForNode(HoverNode)); const bool bSkelMatch = (AnimBlueprint != NULL) && (AnimBlueprint->TargetSkeleton == Asset->GetSkeleton()); if (!bSkelMatch) { OutOkIcon = false; OutTooltipText = FString::Printf(TEXT("Skeletons are not compatible")); } else if (PlayerNodeUnderCursor != NULL) { OutOkIcon = true; OutTooltipText = FString::Printf(TEXT("Change node to play %s"), *(Asset->GetName())); } else { OutOkIcon = false; OutTooltipText = FString::Printf(TEXT("Cannot replace '%s' with a sequence player"), *(HoverNode->GetName())); } } void UAnimationGraphSchema::GetAssetsPinHoverMessage(const TArray& Assets, const UEdGraphPin* HoverPin, FString& OutTooltipText, bool& OutOkIcon) const { UAnimationAsset* Asset = FAssetData::GetFirstAsset(Assets); if ((Asset == NULL) || (HoverPin == NULL)) { OutTooltipText = TEXT(""); OutOkIcon = false; return; } // this one only should happen when there is an Anim Blueprint UAnimBlueprint* AnimBlueprint = Cast(FBlueprintEditorUtils::FindBlueprintForNode(HoverPin->GetOwningNode())); const bool bSkelMatch = (AnimBlueprint != NULL) && (AnimBlueprint->TargetSkeleton == Asset->GetSkeleton()); const bool bTypeMatch = UAnimationGraphSchema::IsLocalSpacePosePin(HoverPin->PinType); const bool bDirectionMatch = HoverPin->Direction == EGPD_Input; if (bSkelMatch && bTypeMatch && bDirectionMatch) { OutOkIcon = true; OutTooltipText = FString::Printf(TEXT("Play %s and feed to %s"), *(Asset->GetName()), *HoverPin->PinName); } else { OutOkIcon = false; OutTooltipText = FString::Printf(TEXT("Type or direction mismatch; must be wired to a pose input")); } } void UAnimationGraphSchema::GetAssetsGraphHoverMessage(const TArray& Assets, const UEdGraph* HoverGraph, FString& OutTooltipText, bool& OutOkIcon) const { UAnimationAsset* Asset = FAssetData::GetFirstAsset(Assets); if (Asset) { UAnimBlueprint* AnimBlueprint = Cast(FBlueprintEditorUtils::FindBlueprintForGraph(HoverGraph)); const bool bSkelMatch = (AnimBlueprint != NULL) && (AnimBlueprint->TargetSkeleton == Asset->GetSkeleton()); if (!bSkelMatch) { OutOkIcon = false; OutTooltipText = FString::Printf(TEXT("Skeletons are not compatible")); } } } void UAnimationGraphSchema::GetContextMenuActions(const UEdGraph* CurrentGraph, const UEdGraphNode* InGraphNode, const UEdGraphPin* InGraphPin, FMenuBuilder* MenuBuilder, bool bIsDebugging) const { Super::GetContextMenuActions(CurrentGraph, InGraphNode, InGraphPin, MenuBuilder, bIsDebugging); if (const UAnimGraphNode_Base* AnimGraphNode = Cast(InGraphNode)) { MenuBuilder->BeginSection("AnimGraphSchemaNodeActions", LOCTEXT("AnimNodeActionsMenuHeader", "Anim Node Actions")); { // Node contextual actions MenuBuilder->AddMenuEntry(FAnimGraphCommands::Get().TogglePoseWatch); } MenuBuilder->EndSection(); } } FText UAnimationGraphSchema::GetPinDisplayName(const UEdGraphPin* Pin) const { check(Pin != NULL); FText DisplayName = Super::GetPinDisplayName(Pin); if (UAnimGraphNode_Base* Node = Cast(Pin->GetOwningNode())) { FString ProcessedDisplayName = DisplayName.ToString(); Node->PostProcessPinName(Pin, ProcessedDisplayName); DisplayName = FText::FromString(ProcessedDisplayName); } return DisplayName; } #undef LOCTEXT_NAMESPACE