You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
Adds 'template' anim BP concept. These anim BPs have no TargetSkeleton and as such cannot have direct references to animations placed inside of their anim graphs Adds function call support from anim nodes. All anim graph nodes can now call functions when initialized, updated, evaluated or when they first become relevant. Relevancy is established using a new FAnimSubsystemInstance_NodeRelevancy which tracks nodes in a local map, per UAnimInstance, if nodes require it. Functions are displayed on the node if bound, and in the details panel. Added a new override point to FAnimSubsystemInstance to allow for WT init. Moved FMemberReference customization into a public header so it can be used on anim node functions. Wrapped functions up into FAnimNodeFunctionRef structure so they can be re-used more effectively. Converted CallFunction node to use them. Added a couple of simple BP function libraries to demonstrate the use of the new FAnimNodeContext (this is intended to be a generic way of exposing FAnimNode_Base types to script without the node types themselves having to be known to script directly). Added the ability to set exposed properties as 'always dynamic' so they appear in mutable data rather than being merged into constants. This allows for the anim node data API to be changed to be less strict (and more script friendly). Now values can be set in mutable data (via GET_MUTABLE_ANIM_NODE_DATA_PTR) and attempted sets to constant data can be ignored and reported to client code. A few minor crash fixes with edge cases (inc when trying to open an asset editor fails because of a missing skeleton/cancellation). Also fixes an issue where literal linked anim graph/control rig/custom poroperty node inputs didnt get copied #rb Jurre.deBaare,Aaron.Cox #ROBOMERGE-SOURCE: CL 16703644 in //UE5/Main/... #ROBOMERGE-BOT: STARSHIP (Main -> Release-Engine-Test) (v835-16672529) [CL 16703653 by thomas sarkanen in ue5-release-engine-test branch]
1057 lines
36 KiB
C++
1057 lines
36 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
/*=============================================================================
|
|
AnimationGraphSchema.cpp
|
|
=============================================================================*/
|
|
|
|
#include "AnimationGraphSchema.h"
|
|
#include "Animation/AnimationAsset.h"
|
|
#include "Animation/AnimBlueprint.h"
|
|
#include "Framework/MultiBox/MultiBoxBuilder.h"
|
|
#include "ToolMenus.h"
|
|
#include "ObjectEditorUtils.h"
|
|
#include "K2Node.h"
|
|
#include "EdGraphSchema_K2_Actions.h"
|
|
#include "Kismet2/BlueprintEditorUtils.h"
|
|
#include "Animation/AnimSequence.h"
|
|
#include "AnimStateNode.h"
|
|
#include "Animation/BlendSpace.h"
|
|
#include "Animation/AimOffsetBlendSpace.h"
|
|
#include "Animation/AimOffsetBlendSpace1D.h"
|
|
#include "Animation/AnimNodeBase.h"
|
|
#include "AnimGraphNode_Base.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 "Animation/PoseAsset.h"
|
|
#include "AnimGraphNode_PoseBlendNode.h"
|
|
#include "AnimGraphNode_PoseByName.h"
|
|
#include "AnimGraphCommands.h"
|
|
#include "K2Node_Knot.h"
|
|
#include "ScopedTransaction.h"
|
|
#include "Animation/AnimMontage.h"
|
|
#include "AnimGraphNode_LinkedInputPose.h"
|
|
#include "AnimGraphNode_LinkedAnimLayer.h"
|
|
#include "AnimGraphNode_RigidBody.h"
|
|
#include "AnimationBlendSpaceSampleGraph.h"
|
|
#include "GraphEditorDragDropAction.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "AnimationGraphSchema"
|
|
|
|
/////////////////////////////////////////////////////
|
|
// FAnimationLayerDragDropAction
|
|
/** DragDropAction class for drag and dropping animation layers */
|
|
class ANIMGRAPH_API FAnimationLayerDragDropAction : public FGraphSchemaActionDragDropAction
|
|
{
|
|
public:
|
|
DRAG_DROP_OPERATOR_TYPE(FAnimationLayerDragDropAction, FGraphSchemaActionDragDropAction)
|
|
|
|
virtual FReply DroppedOnPanel(const TSharedRef< class SWidget >& Panel, FVector2D ScreenPosition, FVector2D GraphPosition, UEdGraph& Graph) override;
|
|
virtual FReply DroppedOnNode(FVector2D ScreenPosition, FVector2D GraphPosition) override;
|
|
virtual FReply DroppedOnPin(FVector2D ScreenPosition, FVector2D GraphPosition) override;
|
|
virtual FReply DroppedOnAction(TSharedRef<FEdGraphSchemaAction> Action) override;
|
|
virtual FReply DroppedOnCategory(FText Category) override;
|
|
virtual void HoverTargetChanged() override;
|
|
|
|
protected:
|
|
|
|
/** Constructor */
|
|
FAnimationLayerDragDropAction();
|
|
|
|
static TSharedRef<FAnimationLayerDragDropAction> New(TSharedPtr<FEdGraphSchemaAction> InAction, FName InFuncName, UAnimBlueprint* InRigBlueprint, UAnimationGraph* InRigGraph);
|
|
|
|
UAnimBlueprint* SourceAnimBlueprint;
|
|
UAnimationGraph* SourceAnimLayerGraph;
|
|
FName SourceFuncName;
|
|
|
|
friend class UAnimationGraphSchema;
|
|
};
|
|
|
|
|
|
FAnimationLayerDragDropAction::FAnimationLayerDragDropAction()
|
|
: FGraphSchemaActionDragDropAction()
|
|
, SourceAnimBlueprint(nullptr)
|
|
, SourceAnimLayerGraph(nullptr)
|
|
, SourceFuncName(NAME_None)
|
|
{
|
|
}
|
|
|
|
FReply FAnimationLayerDragDropAction::DroppedOnPanel(const TSharedRef< class SWidget >& Panel, FVector2D ScreenPosition, FVector2D GraphPosition, UEdGraph& Graph)
|
|
{
|
|
if (UAnimationGraph* TargetRigGraph = Cast<UAnimationGraph>(&Graph))
|
|
{
|
|
if (UAnimBlueprint* TargetAnimBlueprint = Cast<UAnimBlueprint>(FBlueprintEditorUtils::FindBlueprintForGraph(TargetRigGraph)))
|
|
{
|
|
FGraphNodeCreator<UAnimGraphNode_LinkedAnimLayer> LinkedInputLayerNodeCreator(*TargetRigGraph);
|
|
UAnimGraphNode_LinkedAnimLayer* LinkedAnimLayerNode = LinkedInputLayerNodeCreator.CreateNode();
|
|
const FName GraphName = TargetRigGraph->GetFName();
|
|
LinkedAnimLayerNode->Node.Layer = SourceFuncName;
|
|
LinkedInputLayerNodeCreator.Finalize();
|
|
LinkedAnimLayerNode->NodePosX = GraphPosition.X;
|
|
LinkedAnimLayerNode->NodePosY = GraphPosition.Y;
|
|
}
|
|
}
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
FReply FAnimationLayerDragDropAction::DroppedOnNode(FVector2D ScreenPosition, FVector2D GraphPosition)
|
|
{
|
|
if (UEdGraphNode* TargetNode = GetHoveredNode())
|
|
{
|
|
if (UAnimGraphNode_LinkedAnimLayer* LinkedAnimLayer = Cast<UAnimGraphNode_LinkedAnimLayer>(TargetNode))
|
|
{
|
|
LinkedAnimLayer->Node.Layer = SourceFuncName;
|
|
return FReply::Handled();
|
|
}
|
|
}
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
FReply FAnimationLayerDragDropAction::DroppedOnPin(FVector2D ScreenPosition, FVector2D GraphPosition)
|
|
{
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
FReply FAnimationLayerDragDropAction::DroppedOnAction(TSharedRef<FEdGraphSchemaAction> Action)
|
|
{
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
FReply FAnimationLayerDragDropAction::DroppedOnCategory(FText Category)
|
|
{
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
void FAnimationLayerDragDropAction::HoverTargetChanged()
|
|
{
|
|
FGraphSchemaActionDragDropAction::HoverTargetChanged();
|
|
bDropTargetValid = true;
|
|
}
|
|
|
|
|
|
TSharedRef<FAnimationLayerDragDropAction> FAnimationLayerDragDropAction::New(TSharedPtr<FEdGraphSchemaAction> InAction, FName InFuncName, UAnimBlueprint* InAnimBlueprint, UAnimationGraph* InAnimationLayerGraph)
|
|
{
|
|
TSharedRef<FAnimationLayerDragDropAction> Action = MakeShareable(new FAnimationLayerDragDropAction);
|
|
Action->SourceAction = InAction;
|
|
Action->SourceAnimBlueprint = InAnimBlueprint;
|
|
Action->SourceAnimLayerGraph = InAnimationLayerGraph;
|
|
Action->SourceFuncName = InFuncName;
|
|
Action->Construct();
|
|
return Action;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////
|
|
// 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<UAnimGraphNode_Root> 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<UAnimStateNodeBase*> StateNodes;
|
|
FBlueprintEditorUtils::GetAllNodesOfClassEx<UAnimStateNode>(Blueprint, StateNodes);
|
|
|
|
TSet<UAnimStateNodeBase*> 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<UAnimStateNodeBase*>::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)
|
|
{
|
|
UScriptStruct* PoseLinkStruct = FPoseLink::StaticStruct();
|
|
return (PinType.PinCategory == UAnimationGraphSchema::PC_Struct) && (PinType.PinSubCategoryObject == PoseLinkStruct);
|
|
}
|
|
|
|
bool UAnimationGraphSchema::IsComponentSpacePosePin(const FEdGraphPinType& PinType)
|
|
{
|
|
UScriptStruct* ComponentSpacePoseLinkStruct = FComponentSpacePoseLink::StaticStruct();
|
|
return (PinType.PinCategory == UAnimationGraphSchema::PC_Struct) && (PinType.PinSubCategoryObject == ComponentSpacePoseLinkStruct);
|
|
}
|
|
|
|
FEdGraphPinType UAnimationGraphSchema::MakeLocalSpacePosePin()
|
|
{
|
|
FEdGraphPinType PinType;
|
|
PinType.ResetToDefaults();
|
|
|
|
PinType.PinCategory = UAnimationGraphSchema::PC_Struct;
|
|
PinType.PinSubCategoryObject = FPoseLink::StaticStruct();
|
|
|
|
return PinType;
|
|
}
|
|
|
|
FEdGraphPinType UAnimationGraphSchema::MakeComponentSpacePosePin()
|
|
{
|
|
FEdGraphPinType PinType;
|
|
PinType.ResetToDefaults();
|
|
|
|
PinType.PinCategory = UAnimationGraphSchema::PC_Struct;
|
|
PinType.PinSubCategoryObject = FComponentSpacePoseLink::StaticStruct();
|
|
|
|
return PinType;
|
|
}
|
|
|
|
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);
|
|
|
|
UK2Node_Knot* OutputKnotNode = Cast<UK2Node_Knot>(OutputPin->GetOwningNode());
|
|
UK2Node_Knot* InputKnotNode = Cast<UK2Node_Knot>(InputPin->GetOwningNode());
|
|
bool bConnectionWithKnot = OutputKnotNode != nullptr || InputKnotNode != nullptr;
|
|
|
|
if(bConnectionWithKnot)
|
|
{
|
|
// 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();
|
|
}
|
|
}
|
|
|
|
if(Super::TryCreateConnection(A, B))
|
|
{
|
|
// Connection made - remove any bindings on the input pin
|
|
if(UAnimGraphNode_Base* AnimGraphNode = Cast<UAnimGraphNode_Base>(InputPin->GetOwningNode()))
|
|
{
|
|
// Compare FName without number to make sure we catch array properties that are split into multiple pins
|
|
FName ComparisonName = InputPin->GetFName();
|
|
ComparisonName.SetNumber(0);
|
|
AnimGraphNode->PropertyBindings.Remove(ComparisonName);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// Disallow pose pins connecting to wildcards (apart from reroute nodes)
|
|
if(IsPosePin(PinA->PinType) && PinB->PinType.PinCategory == PC_Wildcard)
|
|
{
|
|
return Cast<UK2Node_Knot>(PinB->GetOwningNode()) != nullptr;
|
|
}
|
|
else if(IsPosePin(PinB->PinType) && PinA->PinType.PinCategory == PC_Wildcard)
|
|
{
|
|
return Cast<UK2Node_Knot>(PinA->GetOwningNode()) != nullptr;
|
|
}
|
|
|
|
return Super::ArePinsCompatible(PinA, PinB, CallingContext, bIgnoreArray);
|
|
}
|
|
|
|
bool UAnimationGraphSchema::DoesSupportAnimNotifyActions() const
|
|
{
|
|
// Don't offer notify items in anim graph
|
|
return false;
|
|
}
|
|
|
|
void UAnimationGraphSchema::CreateFunctionGraphTerminators(UEdGraph& Graph, UClass* Class) const
|
|
{
|
|
if(const UAnimationGraphSchema* Schema = ExactCast<UAnimationGraphSchema>(Graph.GetSchema()))
|
|
{
|
|
const FName GraphName = Graph.GetFName();
|
|
|
|
// Get the function GUID from the most up-to-date class
|
|
FGuid GraphGuid;
|
|
FBlueprintEditorUtils::GetFunctionGuidFromClassByFieldName(FBlueprintEditorUtils::GetMostUpToDateClass(Class), GraphName, GraphGuid);
|
|
|
|
// Create a root node
|
|
FGraphNodeCreator<UAnimGraphNode_Root> RootNodeCreator(Graph);
|
|
UAnimGraphNode_Root* RootNode = RootNodeCreator.CreateNode();
|
|
RootNodeCreator.Finalize();
|
|
SetNodeMetaData(RootNode, FNodeMetadata::DefaultGraphNode);
|
|
|
|
UFunction* InterfaceToImplement = FindUField<UFunction>(Class, GraphName);
|
|
if (InterfaceToImplement)
|
|
{
|
|
// Propagate group from metadata
|
|
TArray<UAnimGraphNode_Root*> RootNodes;
|
|
Graph.GetNodesOfClass<UAnimGraphNode_Root>(RootNodes);
|
|
|
|
check(RootNodes.Num() == 1);
|
|
RootNodes[0]->Node.SetGroup(*FObjectEditorUtils::GetCategoryText(InterfaceToImplement).ToString());
|
|
|
|
int32 CurrentPoseIndex = 0;
|
|
for (TFieldIterator<FProperty> PropIt(InterfaceToImplement); PropIt && (PropIt->PropertyFlags & CPF_Parm); ++PropIt)
|
|
{
|
|
FProperty* Param = *PropIt;
|
|
|
|
const bool bIsFunctionInput = !Param->HasAnyPropertyFlags(CPF_OutParm) || Param->HasAnyPropertyFlags(CPF_ReferenceParm);
|
|
|
|
if (bIsFunctionInput)
|
|
{
|
|
FEdGraphPinType PinType;
|
|
if(Schema->ConvertPropertyToPinType(Param, PinType))
|
|
{
|
|
// Create linked input pose for each pose pin type
|
|
if(UAnimationGraphSchema::IsPosePin(PinType))
|
|
{
|
|
FGraphNodeCreator<UAnimGraphNode_LinkedInputPose> LinkedInputNodeCreator(Graph);
|
|
UAnimGraphNode_LinkedInputPose* LinkedInputNode = LinkedInputNodeCreator.CreateNode();
|
|
LinkedInputNode->FunctionReference.SetExternalMember(GraphName, Class, GraphGuid);
|
|
LinkedInputNode->Node.Name = Param->GetFName();
|
|
LinkedInputNode->InputPoseIndex = CurrentPoseIndex;
|
|
|
|
LinkedInputNode->ReconstructNode();
|
|
SetNodeMetaData(LinkedInputNode, FNodeMetadata::DefaultGraphNode);
|
|
LinkedInputNodeCreator.Finalize();
|
|
|
|
CurrentPoseIndex++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
AutoArrangeInterfaceGraph(Graph);
|
|
}
|
|
else
|
|
{
|
|
Super::CreateFunctionGraphTerminators(Graph, Class);
|
|
}
|
|
}
|
|
|
|
|
|
bool UAnimationGraphSchema::CanGraphBeDropped(TSharedPtr<FEdGraphSchemaAction> InAction) const
|
|
{
|
|
if (!InAction.IsValid())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (InAction->GetTypeId() == FEdGraphSchemaAction_K2Graph::StaticGetTypeId())
|
|
{
|
|
FEdGraphSchemaAction_K2Graph* FuncAction = (FEdGraphSchemaAction_K2Graph*)InAction.Get();
|
|
if (UAnimationGraph* AnimGraph = Cast<UAnimationGraph>((UEdGraph*)FuncAction->EdGraph))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
FReply UAnimationGraphSchema::BeginGraphDragAction(TSharedPtr<FEdGraphSchemaAction> InAction, const FPointerEvent& MouseEvent) const
|
|
{
|
|
if (!InAction.IsValid())
|
|
{
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
if (InAction->GetTypeId() == FEdGraphSchemaAction_K2Graph::StaticGetTypeId())
|
|
{
|
|
FEdGraphSchemaAction_K2Graph* FuncAction = (FEdGraphSchemaAction_K2Graph*)InAction.Get();
|
|
if (UAnimationGraph* AnimationLayerGraph = Cast<UAnimationGraph>((UEdGraph*)FuncAction->EdGraph))
|
|
{
|
|
if (UAnimBlueprint* TargetAnimBlueprint = Cast<UAnimBlueprint>(FBlueprintEditorUtils::FindBlueprintForGraph(AnimationLayerGraph)))
|
|
{
|
|
return FReply::Handled().BeginDragDrop(FAnimationLayerDragDropAction::New(InAction, FuncAction->FuncName, TargetAnimBlueprint, AnimationLayerGraph));
|
|
}
|
|
}
|
|
}
|
|
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
bool UAnimationGraphSchema::SearchForAutocastFunction(const FEdGraphPinType& OutputPinType, const FEdGraphPinType& InputPinType, FName& TargetFunction, /*out*/ UClass*& FunctionOwner) const
|
|
{
|
|
if (IsComponentSpacePosePin(OutputPinType) && IsLocalSpacePosePin(InputPinType))
|
|
{
|
|
// Insert a Component To LocalSpace conversion
|
|
return true;
|
|
}
|
|
else if (IsLocalSpacePosePin(OutputPinType) && IsComponentSpacePosePin(InputPinType))
|
|
{
|
|
// Insert a Local To ComponentSpace conversion
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return Super::SearchForAutocastFunction(OutputPinType, InputPinType, 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<UAnimGraphNode_ComponentToLocalSpace>();
|
|
}
|
|
else if (IsLocalSpacePosePin(OutputPin->PinType) && IsComponentSpacePosePin(InputPin->PinType))
|
|
{
|
|
TemplateNode = NewObject<UAnimGraphNode_LocalToComponentSpace>();
|
|
}
|
|
|
|
// 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<UK2Node>(Graph, TemplateNode, AverageLocation);
|
|
AutowireConversionNode(InputPin, OutputPin, ConversionNode);
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
// Give the regular conversions a shot
|
|
return Super::CreateAutomaticConversionNodeAndConnections(PinA, PinB);
|
|
}
|
|
}
|
|
|
|
bool IsAimOffsetBlendSpace(UBlendSpace* 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<UAnimBlueprint>(FBlueprintEditorUtils::FindBlueprintForGraph(Graph));
|
|
|
|
const bool bSkelMatch = (AnimBlueprint != NULL) && (AnimBlueprint->TargetSkeleton->IsCompatible(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<UAnimGraphNode_AssetPlayerBase>(GetTransientPackage(), NewNodeClass);
|
|
NewNode->SetAnimationAsset(Asset);
|
|
Action.NodeTemplate = NewNode;
|
|
|
|
Action.PerformAction(Graph, PinIfAvailable, GraphPosition);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UAnimationGraphSchema::SpawnRigidBodyNodeFromAsset(UPhysicsAsset* Asset, const FVector2D& GraphPosition, UEdGraph* Graph)
|
|
{
|
|
check(Graph);
|
|
check(Graph->GetSchema()->IsA(UAnimationGraphSchema::StaticClass()));
|
|
check(Asset);
|
|
|
|
FEdGraphSchemaAction_K2NewNode Action;
|
|
|
|
UAnimGraphNode_RigidBody* NewNode = NewObject<UAnimGraphNode_RigidBody>(GetTransientPackage());
|
|
NewNode->Node.OverridePhysicsAsset = Asset;
|
|
Action.NodeTemplate = NewNode;
|
|
|
|
Action.PerformAction(Graph, nullptr, GraphPosition);
|
|
}
|
|
|
|
void UAnimationGraphSchema::UpdateNodeWithAsset(UK2Node* K2Node, UAnimationAsset* Asset)
|
|
{
|
|
if (Asset != NULL)
|
|
{
|
|
if (UAnimGraphNode_AssetPlayerBase* AssetPlayerNode = Cast<UAnimGraphNode_AssetPlayerBase>(K2Node))
|
|
{
|
|
if (AssetPlayerNode->SupportsAssetClass(Asset->GetClass()) != EAnimAssetHandlerType::NotSupported)
|
|
{
|
|
const FScopedTransaction Transaction(LOCTEXT("UpdateNodeWithAsset", "Updating Node with Asset"));
|
|
AssetPlayerNode->Modify();
|
|
|
|
AssetPlayerNode->SetAnimationAsset(Asset);
|
|
|
|
K2Node->GetSchema()->ForceVisualizationCacheClear();
|
|
K2Node->ReconstructNode();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void UAnimationGraphSchema::DroppedAssetsOnGraph( const TArray<FAssetData>& Assets, const FVector2D& GraphPosition, UEdGraph* Graph ) const
|
|
{
|
|
if (Graph != NULL)
|
|
{
|
|
if (UAnimationAsset* AnimationAsset = FAssetData::GetFirstAsset<UAnimationAsset>(Assets))
|
|
{
|
|
SpawnNodeFromAsset(AnimationAsset, GraphPosition, Graph, NULL);
|
|
}
|
|
else if (UPhysicsAsset* PhysicsAsset = FAssetData::GetFirstAsset<UPhysicsAsset>(Assets))
|
|
{
|
|
SpawnRigidBodyNodeFromAsset(PhysicsAsset, GraphPosition, Graph);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UAnimationGraphSchema::DroppedAssetsOnNode(const TArray<FAssetData>& Assets, const FVector2D& GraphPosition, UEdGraphNode* Node) const
|
|
{
|
|
UAnimationAsset* Asset = FAssetData::GetFirstAsset<UAnimationAsset>(Assets);
|
|
UK2Node* K2Node = Cast<UK2Node>(Node);
|
|
if ((Asset != NULL) && (K2Node!= NULL))
|
|
{
|
|
UpdateNodeWithAsset(K2Node, Asset);
|
|
}
|
|
}
|
|
|
|
void UAnimationGraphSchema::DroppedAssetsOnPin(const TArray<FAssetData>& Assets, const FVector2D& GraphPosition, UEdGraphPin* Pin) const
|
|
{
|
|
UAnimationAsset* Asset = FAssetData::GetFirstAsset<UAnimationAsset>(Assets);
|
|
if ((Asset != NULL) && (Pin != NULL))
|
|
{
|
|
// Don't have access to bounding information for node, using fixed offset that should work for most cases.
|
|
const FVector2D FixedOffset(-250.0f, 50.0f);
|
|
SpawnNodeFromAsset(Asset, GraphPosition + FixedOffset, Pin->GetOwningNode()->GetGraph(), Pin);
|
|
}
|
|
}
|
|
|
|
void UAnimationGraphSchema::GetAssetsNodeHoverMessage(const TArray<FAssetData>& Assets, const UEdGraphNode* HoverNode, FString& OutTooltipText, bool& OutOkIcon) const
|
|
{
|
|
UAnimationAsset* Asset = FAssetData::GetFirstAsset<UAnimationAsset>(Assets);
|
|
if ((Asset == NULL) || (HoverNode == NULL) || !HoverNode->IsA(UAnimGraphNode_Base::StaticClass()))
|
|
{
|
|
OutTooltipText = TEXT("");
|
|
OutOkIcon = false;
|
|
return;
|
|
}
|
|
|
|
bool bCanPlayAsset = SupportNodeClassForAsset(Asset->GetClass(), HoverNode->GetClass());
|
|
|
|
// this one only should happen when there is an Anim Blueprint
|
|
UAnimBlueprint* AnimBlueprint = Cast<UAnimBlueprint>(FBlueprintEditorUtils::FindBlueprintForNode(HoverNode));
|
|
const bool bSkelMatch = (AnimBlueprint != NULL) && (AnimBlueprint->TargetSkeleton->IsCompatible(Asset->GetSkeleton()));
|
|
|
|
if (!bSkelMatch)
|
|
{
|
|
OutOkIcon = false;
|
|
OutTooltipText = LOCTEXT("SkeletonsNotCompatible", "Skeletons are not compatible").ToString();
|
|
}
|
|
else if (bCanPlayAsset)
|
|
{
|
|
OutOkIcon = true;
|
|
OutTooltipText = FText::Format(LOCTEXT("AssetNodeHoverMessage_Success", "Change node to play '{0}'"), FText::FromString(Asset->GetName())).ToString();
|
|
}
|
|
else
|
|
{
|
|
OutOkIcon = false;
|
|
OutTooltipText = FText::Format(LOCTEXT("AssetNodeHoverMessage_Fail", "Cannot play '{0}' on this node type"), FText::FromString(Asset->GetName())).ToString();
|
|
}
|
|
}
|
|
|
|
void UAnimationGraphSchema::GetAssetsPinHoverMessage(const TArray<FAssetData>& Assets, const UEdGraphPin* HoverPin, FString& OutTooltipText, bool& OutOkIcon) const
|
|
{
|
|
UAnimationAsset* Asset = FAssetData::GetFirstAsset<UAnimationAsset>(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<UAnimBlueprint>(FBlueprintEditorUtils::FindBlueprintForNode(HoverPin->GetOwningNode()));
|
|
|
|
const bool bSkelMatch = (AnimBlueprint != NULL) && (AnimBlueprint->TargetSkeleton->IsCompatible(Asset->GetSkeleton()));
|
|
const bool bTypeMatch = UAnimationGraphSchema::IsLocalSpacePosePin(HoverPin->PinType);
|
|
const bool bDirectionMatch = HoverPin->Direction == EGPD_Input;
|
|
|
|
if (bSkelMatch && bTypeMatch && bDirectionMatch)
|
|
{
|
|
OutOkIcon = true;
|
|
OutTooltipText = FText::Format(LOCTEXT("AssetPinHoverMessage_Success", "Play {0} and feed to {1}"), FText::FromString(Asset->GetName()), FText::FromName(HoverPin->PinName)).ToString();
|
|
}
|
|
else
|
|
{
|
|
OutOkIcon = false;
|
|
OutTooltipText = LOCTEXT("AssetPinHoverMessage_Fail", "Type or direction mismatch; must be wired to a pose input").ToString();
|
|
}
|
|
}
|
|
|
|
void UAnimationGraphSchema::GetAssetsGraphHoverMessage(const TArray<FAssetData>& Assets, const UEdGraph* HoverGraph, FString& OutTooltipText, bool& OutOkIcon) const
|
|
{
|
|
if (UAnimationAsset* AnimationAsset = FAssetData::GetFirstAsset<UAnimationAsset>(Assets))
|
|
{
|
|
UAnimBlueprint* AnimBlueprint = Cast<UAnimBlueprint>(FBlueprintEditorUtils::FindBlueprintForGraph(HoverGraph));
|
|
const bool bSkelMatch = (AnimBlueprint != NULL) && (AnimBlueprint->TargetSkeleton->IsCompatible(AnimationAsset->GetSkeleton()));
|
|
if (!bSkelMatch)
|
|
{
|
|
OutOkIcon = false;
|
|
OutTooltipText = LOCTEXT("SkeletonsNotCompatible", "Skeletons are not compatible").ToString();
|
|
}
|
|
else if(UAnimMontage* Montage = FAssetData::GetFirstAsset<UAnimMontage>(Assets))
|
|
{
|
|
OutOkIcon = false;
|
|
OutTooltipText = LOCTEXT("NoMontagesInAnimGraphs", "Montages cannot be used in animation graphs").ToString();
|
|
}
|
|
else
|
|
{
|
|
OutOkIcon = true;
|
|
OutTooltipText = TEXT("");
|
|
}
|
|
}
|
|
else if(UPhysicsAsset* PhysicsAsset = FAssetData::GetFirstAsset<UPhysicsAsset>(Assets))
|
|
{
|
|
OutOkIcon = true;
|
|
OutTooltipText = TEXT("");
|
|
}
|
|
}
|
|
|
|
void UAnimationGraphSchema::GetContextMenuActions(UToolMenu* Menu, UGraphNodeContextMenuContext* Context) const
|
|
{
|
|
Super::GetContextMenuActions(Menu, Context);
|
|
|
|
if (const UAnimGraphNode_Base* AnimGraphNode = Cast<const UAnimGraphNode_Base>(Context->Node))
|
|
{
|
|
{
|
|
// Node contextual actions
|
|
FToolMenuSection& Section = Menu->AddSection("AnimGraphSchemaNodeActions", LOCTEXT("AnimNodeActionsMenuHeader", "Anim Node Actions"));
|
|
Section.AddMenuEntry(FAnimGraphCommands::Get().TogglePoseWatch);
|
|
}
|
|
|
|
if(Context->Pin && !IsPosePin(Context->Pin->PinType) && AnimGraphNode->IsPinBindable(Context->Pin))
|
|
{
|
|
FToolMenuSection& Section = Menu->AddSection("EdGraphSchemaPinActions");
|
|
|
|
FProperty* PinProperty = AnimGraphNode->GetPinProperty(Context->Pin);
|
|
check(PinProperty);
|
|
|
|
const int32 OptionalPinIndex = AnimGraphNode->ShowPinForProperties.IndexOfByPredicate([PinProperty](const FOptionalPinFromProperty& InOptionalPin)
|
|
{
|
|
return PinProperty->GetFName() == InOptionalPin.PropertyName;
|
|
});
|
|
|
|
const UAnimGraphNode_Base::FAnimPropertyBindingWidgetArgs Args({ const_cast<UAnimGraphNode_Base*>(AnimGraphNode) }, PinProperty, Context->Pin->GetFName(), OptionalPinIndex);
|
|
Section.AddEntry(FToolMenuEntry::InitWidget("BindingWidget", UAnimGraphNode_Base::MakePropertyBindingWidget(Args), LOCTEXT("BindingWidgetLabel", "Binding"), true));
|
|
}
|
|
}
|
|
}
|
|
|
|
FText UAnimationGraphSchema::GetPinDisplayName(const UEdGraphPin* Pin) const
|
|
{
|
|
check(Pin != NULL);
|
|
|
|
FText DisplayName = Super::GetPinDisplayName(Pin);
|
|
|
|
if (UAnimGraphNode_Base* Node = Cast<UAnimGraphNode_Base>(Pin->GetOwningNode()))
|
|
{
|
|
FString ProcessedDisplayName = DisplayName.ToString();
|
|
Node->PostProcessPinName(Pin, ProcessedDisplayName);
|
|
DisplayName = FText::FromString(ProcessedDisplayName);
|
|
}
|
|
|
|
return DisplayName;
|
|
}
|
|
|
|
bool UAnimationGraphSchema::CanDuplicateGraph(UEdGraph* InSourceGraph) const
|
|
{
|
|
return InSourceGraph->GetFName() != UEdGraphSchema_K2::GN_AnimGraph && !InSourceGraph->IsA<UAnimationBlendSpaceSampleGraph>();
|
|
}
|
|
|
|
void UAnimationGraphSchema::GetGraphDisplayInformation(const UEdGraph& Graph, /*out*/ FGraphDisplayInfo& DisplayInfo) const
|
|
{
|
|
if (GetGraphType(&Graph) == GT_Animation)
|
|
{
|
|
DisplayInfo.PlainName = FText::FromString(Graph.GetName());
|
|
DisplayInfo.DisplayName = DisplayInfo.PlainName;
|
|
DisplayInfo.Tooltip = Graph.GetFName() == UEdGraphSchema_K2::GN_AnimGraph ?
|
|
LOCTEXT("GraphTooltip_AnimGraph", "Graph used to blend together different animations.") :
|
|
LOCTEXT("GraphTooltip_AnimGraphLayer", "Layer used to organize blending into sub-graphs.");
|
|
|
|
if(!Graph.bAllowDeletion)
|
|
{
|
|
// Might be from an interface, so check
|
|
if(UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(&Graph))
|
|
{
|
|
TSubclassOf<UInterface> Interface;
|
|
|
|
auto FindInterfaceForGraph = [&Blueprint, &Graph](TSubclassOf<UInterface>& OutInterface)
|
|
{
|
|
for(const FBPInterfaceDescription& InterfaceDesc : Blueprint->ImplementedInterfaces)
|
|
{
|
|
for(UEdGraph* InterfaceGraph : InterfaceDesc.Graphs)
|
|
{
|
|
if(InterfaceGraph == &Graph)
|
|
{
|
|
OutInterface = InterfaceDesc.Interface;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
if(FindInterfaceForGraph(Interface))
|
|
{
|
|
DisplayInfo.Tooltip = FText::Format(LOCTEXT("GraphTooltip_AnimGraphInterface", "Layer inherited from interface '{0}'."), FText::FromString(Interface.Get()->GetName()));
|
|
}
|
|
}
|
|
}
|
|
|
|
DisplayInfo.DocLink = TEXT("Shared/Editors/BlueprintEditor/GraphTypes");
|
|
DisplayInfo.DocExcerptName = TEXT("AnimGraph");
|
|
}
|
|
else
|
|
{
|
|
Super::GetGraphDisplayInformation(Graph, DisplayInfo);
|
|
}
|
|
}
|
|
|
|
void UAnimationGraphSchema::AutoArrangeInterfaceGraph(UEdGraph& Graph)
|
|
{
|
|
// auto-arrange all nodes now
|
|
TArray<UAnimGraphNode_Root*> RootNodes;
|
|
Graph.GetNodesOfClass<UAnimGraphNode_Root>(RootNodes);
|
|
check(RootNodes.Num() == 1);
|
|
UAnimGraphNode_Root* Root = RootNodes[0];
|
|
|
|
TArray<UAnimGraphNode_LinkedInputPose*> LinkedInputPoseNodes;
|
|
Graph.GetNodesOfClass<UAnimGraphNode_LinkedInputPose>(LinkedInputPoseNodes);
|
|
|
|
FBox2D RootBounds(FVector2D(Root->NodePosX, Root->NodePosY), FVector2D(Root->NodePosX + 130, Root->NodePosY + 200));
|
|
|
|
float TotalHeight = 0.0f;
|
|
float MaxWidth = 0.0f;
|
|
const int32 HeightPerProperty = 30;
|
|
|
|
for(UAnimGraphNode_LinkedInputPose* Node : LinkedInputPoseNodes)
|
|
{
|
|
FBox2D LinkedInputPoseBounds(
|
|
FVector2D(Node->NodePosX, Node->NodePosY),
|
|
FVector2D(Node->NodePosX + 400, Node->NodePosY + 100 + (Node->GetNumInputs() * HeightPerProperty))
|
|
);
|
|
|
|
FVector2D BoundsSize = LinkedInputPoseBounds.GetSize();
|
|
TotalHeight += BoundsSize.Y + 10.0f;
|
|
MaxWidth = FMath::Max(BoundsSize.X, MaxWidth);
|
|
}
|
|
|
|
float NodeOffset = RootBounds.GetCenter().Y - (TotalHeight * 0.5f);
|
|
float NodePosX = RootBounds.Min.X - (MaxWidth + 100.0f);
|
|
for(UAnimGraphNode_LinkedInputPose* Node : LinkedInputPoseNodes)
|
|
{
|
|
Node->NodePosX = NodePosX;
|
|
Node->NodePosY = NodeOffset;
|
|
|
|
FBox2D LinkedInputPoseBounds(
|
|
FVector2D(Node->NodePosX, Node->NodePosY),
|
|
FVector2D(Node->NodePosX + 400, Node->NodePosY + 100 + (Node->GetNumInputs() * HeightPerProperty))
|
|
);
|
|
|
|
NodeOffset += LinkedInputPoseBounds.GetSize().Y + 10.0f;
|
|
}
|
|
}
|
|
|
|
void UAnimationGraphSchema::ConformAnimGraphToInterface(UBlueprint* InBlueprint, UEdGraph& InGraph, UFunction* InFunction)
|
|
{
|
|
if(const UAnimationGraphSchema* Schema = ExactCast<UAnimationGraphSchema>(InGraph.GetSchema()))
|
|
{
|
|
// Propagate group from metadata
|
|
TArray<UAnimGraphNode_Root*> RootNodes;
|
|
InGraph.GetNodesOfClass<UAnimGraphNode_Root>(RootNodes);
|
|
|
|
check(RootNodes.Num() == 1);
|
|
RootNodes[0]->Node.SetGroup(*FObjectEditorUtils::GetCategoryText(InFunction).ToString());
|
|
|
|
TArray<UAnimGraphNode_LinkedInputPose*> LinkedInputPoseNodes;
|
|
InGraph.GetNodesOfClass<UAnimGraphNode_LinkedInputPose>(LinkedInputPoseNodes);
|
|
|
|
for(UAnimGraphNode_LinkedInputPose* LinkedInputPoseNode : LinkedInputPoseNodes)
|
|
{
|
|
// Sync pose names in case they have changed
|
|
LinkedInputPoseNode->ConformInputPoseName();
|
|
|
|
// Clean up any old linked input poses that no longer exist
|
|
if(!LinkedInputPoseNode->ValidateAgainstFunctionReference())
|
|
{
|
|
InGraph.RemoveNode(LinkedInputPoseNode);
|
|
}
|
|
}
|
|
|
|
UClass* InterfaceClass = CastChecked<UClass>(InFunction->GetOuter());
|
|
|
|
// Add any inputs that are not present in the graph (matching by pose index)
|
|
int32 CurrentPoseIndex = 0;
|
|
for (TFieldIterator<FProperty> PropIt(InFunction); PropIt && (PropIt->PropertyFlags & CPF_Parm); ++PropIt)
|
|
{
|
|
FProperty* Param = *PropIt;
|
|
|
|
const bool bIsFunctionInput = !Param->HasAnyPropertyFlags(CPF_OutParm) || Param->HasAnyPropertyFlags(CPF_ReferenceParm);
|
|
|
|
if (bIsFunctionInput)
|
|
{
|
|
FEdGraphPinType PinType;
|
|
if(Schema->ConvertPropertyToPinType(Param, PinType))
|
|
{
|
|
// Create linked input pose for each pose pin type
|
|
if(UAnimationGraphSchema::IsPosePin(PinType))
|
|
{
|
|
UAnimGraphNode_LinkedInputPose** MatchingNode = LinkedInputPoseNodes.FindByPredicate(
|
|
[CurrentPoseIndex](UAnimGraphNode_LinkedInputPose* InLinkedInputPoseNode)
|
|
{
|
|
return InLinkedInputPoseNode->InputPoseIndex == CurrentPoseIndex;
|
|
});
|
|
|
|
if(MatchingNode == nullptr)
|
|
{
|
|
const FName GraphName = InGraph.GetFName();
|
|
|
|
// Get the function GUID from the most up-to-date class
|
|
FGuid GraphGuid;
|
|
FBlueprintEditorUtils::GetFunctionGuidFromClassByFieldName(FBlueprintEditorUtils::GetMostUpToDateClass(InterfaceClass), GraphName, GraphGuid);
|
|
|
|
// not found, add this node
|
|
FGraphNodeCreator<UAnimGraphNode_LinkedInputPose> LinkedInputPoseNodeCreator(InGraph);
|
|
UAnimGraphNode_LinkedInputPose* LinkedInputPoseNode = LinkedInputPoseNodeCreator.CreateNode();
|
|
LinkedInputPoseNode->FunctionReference.SetExternalMember(GraphName, InterfaceClass, GraphGuid);
|
|
LinkedInputPoseNode->Node.Name = Param->GetFName();
|
|
LinkedInputPoseNode->InputPoseIndex = CurrentPoseIndex;
|
|
|
|
LinkedInputPoseNode->ReconstructNode();
|
|
SetNodeMetaData(LinkedInputPoseNode, FNodeMetadata::DefaultGraphNode);
|
|
LinkedInputPoseNodeCreator.Finalize();
|
|
|
|
FVector2D NewPosition = GetPositionForNewLinkedInputPoseNode(InGraph);
|
|
LinkedInputPoseNode->NodePosX = NewPosition.X;
|
|
LinkedInputPoseNode->NodePosY = NewPosition.Y;
|
|
}
|
|
|
|
CurrentPoseIndex++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void UAnimationGraphSchema::ConformAnimLayersByGuid(const UAnimBlueprint* InAnimBlueprint, const FBPInterfaceDescription& CurrentInterfaceDesc)
|
|
{
|
|
const UBlueprint* InterfaceBlueprint = CastChecked<UBlueprint>(CurrentInterfaceDesc.Interface->ClassGeneratedBy);
|
|
|
|
TArray<UEdGraph*> InterfaceGraphs;
|
|
InterfaceBlueprint->GetAllGraphs(InterfaceGraphs);
|
|
|
|
TArray<UEdGraph*> Graphs;
|
|
InAnimBlueprint->GetAllGraphs(Graphs);
|
|
|
|
for (UEdGraph* Graph : Graphs)
|
|
{
|
|
TArray<UAnimGraphNode_LinkedAnimLayer*> LayerNodes;
|
|
Graph->GetNodesOfClass<UAnimGraphNode_LinkedAnimLayer>(LayerNodes);
|
|
|
|
for (UAnimGraphNode_LinkedAnimLayer* LayerNode : LayerNodes)
|
|
{
|
|
LayerNode->UpdateGuidForLayer();
|
|
|
|
if (LayerNode->InterfaceGuid.IsValid())
|
|
{
|
|
for (UEdGraph* InterfaceGraph : InterfaceGraphs)
|
|
{
|
|
// Check to see if GUID matches but name does not and update if so
|
|
if (InterfaceGraph->GraphGuid == LayerNode->InterfaceGuid && InterfaceGraph->GetFName() != LayerNode->Node.Layer)
|
|
{
|
|
LayerNode->Node.Layer = InterfaceGraph->GetFName();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FVector2D UAnimationGraphSchema::GetPositionForNewLinkedInputPoseNode(UEdGraph& InGraph)
|
|
{
|
|
TArray<UAnimGraphNode_LinkedInputPose*> LinkedInputPoseNodes;
|
|
InGraph.GetNodesOfClass<UAnimGraphNode_LinkedInputPose>(LinkedInputPoseNodes);
|
|
|
|
if(LinkedInputPoseNodes.Num() == 0)
|
|
{
|
|
TArray<UAnimGraphNode_Base*> AllNodes;
|
|
InGraph.GetNodesOfClass<UAnimGraphNode_Base>(AllNodes);
|
|
|
|
// No nodes, so insert to the top-left of all existing nodes.
|
|
FBox2D AllNodesBounds(ForceInit);
|
|
for(UAnimGraphNode_Base* Node : AllNodes)
|
|
{
|
|
FBox2D NodeBounds(
|
|
FVector2D(Node->NodePosX, Node->NodePosY),
|
|
FVector2D(Node->NodePosX + 400, Node->NodePosY + 100)
|
|
);
|
|
|
|
AllNodesBounds += NodeBounds;
|
|
}
|
|
|
|
return FVector2D(AllNodesBounds.Min.X - 300.0f, AllNodesBounds.Min.Y);
|
|
}
|
|
else
|
|
{
|
|
const int32 HeightPerProperty = 30;
|
|
|
|
// Some existing linked input poses. Insert below the bottom-most one.
|
|
UAnimGraphNode_LinkedInputPose* Node = LinkedInputPoseNodes[0];
|
|
|
|
FBox2D BottomMostLinkedInputPoseBounds(
|
|
FVector2D(Node->NodePosX, Node->NodePosY),
|
|
FVector2D(Node->NodePosX + 400, Node->NodePosY + 100 + (Node->GetNumInputs() * HeightPerProperty))
|
|
);
|
|
|
|
for(int32 LinkedInputPoseIndex = 1; LinkedInputPoseIndex < LinkedInputPoseNodes.Num(); ++LinkedInputPoseIndex)
|
|
{
|
|
Node = LinkedInputPoseNodes[LinkedInputPoseIndex];
|
|
|
|
FBox2D LinkedInputPoseBounds(
|
|
FVector2D(Node->NodePosX, Node->NodePosY),
|
|
FVector2D(Node->NodePosX + 400, Node->NodePosY + 100 + (Node->GetNumInputs() * HeightPerProperty))
|
|
);
|
|
|
|
if(LinkedInputPoseBounds.Min.Y > BottomMostLinkedInputPoseBounds.Min.Y)
|
|
{
|
|
BottomMostLinkedInputPoseBounds = LinkedInputPoseBounds;
|
|
}
|
|
}
|
|
|
|
return FVector2D(BottomMostLinkedInputPoseBounds.Min.X, BottomMostLinkedInputPoseBounds.Max.Y + 10.0f);
|
|
}
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|