// Copyright Epic Games, Inc. All Rights Reserved. #include "AnimBlueprintExtension_Attributes.h" #include "IAnimBlueprintCompilerCreationContext.h" #include "IAnimBlueprintCompilationContext.h" #include "AnimationGraphSchema.h" #include "AnimGraphNode_Base.h" #include "AnimGraphNode_SaveCachedPose.h" #include "AnimGraphNode_UseCachedPose.h" #include "AnimGraphNode_StateResult.h" #include "AnimationStateGraph.h" #include "AnimationStateMachineGraph.h" #include "AnimGraphNode_StateMachineBase.h" #include "AnimStateNode.h" #include "AnimGraphNode_Root.h" #include "AnimGraphAttributes.h" #include "AnimGraphNode_TransitionResult.h" #include "AnimGraphNode_CustomTransitionResult.h" #include "AnimationCustomTransitionGraph.h" #include "AnimStateTransitionNode.h" #include "AnimGraphNode_BlendSpaceSampleResult.h" #include "BlendSpaceGraph.h" #include "AnimGraphNode_BlendSpaceGraphBase.h" #include "AnimationBlendSpaceSampleGraph.h" void UAnimBlueprintExtension_Attributes::HandlePostProcessAnimationNodes(TArrayView InAnimNodes, IAnimBlueprintCompilationContext& InCompilationContext, IAnimBlueprintGeneratedClassCompiledData& OutCompiledData) { using FNodeAttributeMap = TMap; using FCachedPoseMap = TMultiMap; FNodeAttributeMap AttributeInputNodes; FNodeAttributeMap AttributeOutputNodes; FCachedPoseMap SaveCachedPoseMap; UAnimGraphNode_Base::FNodeAttributeArray Attributes; for(UAnimGraphNode_Base* Node : InAnimNodes) { // Establish links between save/used cached pose nodes if(UAnimGraphNode_UseCachedPose* UseCachedPoseNode = Cast(Node)) { if(UseCachedPoseNode->SaveCachedPoseNode.IsValid()) { SaveCachedPoseMap.Add(UseCachedPoseNode->SaveCachedPoseNode.Get(), UseCachedPoseNode); } } // Get I/O attributes from all nodes Node->GetInputLinkAttributes(Attributes); if(Attributes.Num() > 0) { AttributeInputNodes.Add(Node, Attributes); } Attributes.Reset(); Node->GetOutputLinkAttributes(Attributes); if(Attributes.Num() > 0) { AttributeOutputNodes.Add(Node, Attributes); } Attributes.Reset(); } // Utility struct to check whether nodes with outputs reach nodes with inputs further towards the root struct FCheckOutputNodes { private: IAnimBlueprintCompilationContext& CompilationContext; const UAnimGraphNode_Base::FNodeAttributeArray& BaseAttributes; const FNodeAttributeMap& AttributeInputNodes; const FCachedPoseMap& SaveCachedPoseMap; UAnimGraphNode_Base* OutputNode; TSet VisitedNodes; public: FCheckOutputNodes(IAnimBlueprintCompilationContext& InCompilationContext, const UAnimGraphNode_Base::FNodeAttributeArray& InAttributes, const FNodeAttributeMap& InAttributeInputNodes, const FCachedPoseMap& InSaveCachedPoseMap, UAnimGraphNode_Base* InNode) : CompilationContext(InCompilationContext) , BaseAttributes(InAttributes) , AttributeInputNodes(InAttributeInputNodes) , SaveCachedPoseMap(InSaveCachedPoseMap) , OutputNode(InNode) { UAnimGraphNode_Base::FNodeAttributeArray AbsorbedAttributes = TraverseNodes_Recursive(OutputNode, BaseAttributes); if(AbsorbedAttributes.Num() > 0) { // Push only absorbed attributes to the source node's internal set CompilationContext.AddAttributesToNode(OutputNode, AbsorbedAttributes); } } private: UAnimGraphNode_Base::FNodeAttributeArray TraverseNodes_Recursive_PerNode(UAnimGraphNode_Base* InLinkedNode, const UAnimGraphNode_Base::FNodeAttributeArray& InAttributes) { UAnimGraphNode_Base::FNodeAttributeArray AbsorbedAttributes; if (InLinkedNode) { if(!VisitedNodes.Contains(InLinkedNode)) { // See if this node absorbs this attribute const UAnimGraphNode_Base::FNodeAttributeArray* AbsorbedAttributesPtr = AttributeInputNodes.Find(InLinkedNode); auto HasAttribute = [AbsorbedAttributesPtr, InLinkedNode](FName InAttribute) { return (AbsorbedAttributesPtr && AbsorbedAttributesPtr->Contains(InAttribute)); }; if(InLinkedNode->IsA() || InLinkedNode->IsA() || InAttributes.ContainsByPredicate(HasAttribute)) { UAnimGraphNode_Base::FNodeAttributeArray ReducedAttributes; if(InLinkedNode->IsA() || InLinkedNode->IsA()) { const UAnimGraphAttributes* AnimGraphAttributes = GetDefault(); auto IsAttributeBlendable = [AnimGraphAttributes](const FName& InAttribute) { const FAnimGraphAttributeDesc* Desc = AnimGraphAttributes->FindAttributeDesc(InAttribute); return Desc && Desc->Blend == EAnimGraphAttributeBlend::Blendable; }; // Absorb all blendables at the root for(const FName& Attribute : InAttributes) { if(IsAttributeBlendable(Attribute)) { AbsorbedAttributes.Add(Attribute); } else { ReducedAttributes.Add(Attribute); } } } else { // Reduce the set of attributes that we are using in this traversal for(const FName& Attribute : InAttributes) { if(HasAttribute(Attribute)) { AbsorbedAttributes.Add(Attribute); } else { ReducedAttributes.Add(Attribute); } } } if(ReducedAttributes.Num() > 0) { UAnimGraphNode_Base::FNodeAttributeArray AbsorbedAttributesBelow = TraverseNodes_Recursive(InLinkedNode, ReducedAttributes); AbsorbedAttributes.Append(AbsorbedAttributesBelow); } } else { UAnimGraphNode_Base::FNodeAttributeArray AbsorbedAttributesBelow = TraverseNodes_Recursive(InLinkedNode, InAttributes); AbsorbedAttributes.Append(AbsorbedAttributesBelow); } } else { // already visited by another branch of a cached pose, see if any attributes got absorbed the last time we took this branch TArrayView PreviouslyAbsorbedAttributes = CompilationContext.GetAttributesFromNode(InLinkedNode); AbsorbedAttributes.Append(PreviouslyAbsorbedAttributes.GetData(), PreviouslyAbsorbedAttributes.Num()); } } // Post-recursion, we add any pass through attributes that got absorbed CompilationContext.AddAttributesToNode(InLinkedNode, AbsorbedAttributes); return AbsorbedAttributes; } UAnimGraphNode_Base::FNodeAttributeArray TraverseNodes_Recursive(UEdGraphNode* InNode, const UAnimGraphNode_Base::FNodeAttributeArray& InAttributes) { VisitedNodes.Add(InNode); for (UEdGraphPin* Pin : InNode->Pins) { if (UAnimationGraphSchema::IsPosePin(Pin->PinType) && Pin->Direction == EGPD_Output) { // Traverse pins for (UEdGraphPin* LinkedPin : Pin->LinkedTo) { UEdGraphNode* OwningNode = LinkedPin->GetOwningNode(); if(UAnimGraphNode_Base* LinkedNode = Cast(OwningNode)) { return TraverseNodes_Recursive_PerNode(LinkedNode, InAttributes); } else if(OwningNode != nullptr) { // Its a pose link, but not on an anim node, likely a knot return TraverseNodes_Recursive(OwningNode, InAttributes); } } } } // Traverse saved cached pose->all use cached pose nodes if(UAnimGraphNode_SaveCachedPose* SaveCachedPoseNode = Cast(InNode)) { TArray UseCachedPoseNodes; SaveCachedPoseMap.MultiFind(SaveCachedPoseNode, UseCachedPoseNodes); for(int32 UseCachedPoseIndex = 0; UseCachedPoseIndex < UseCachedPoseNodes.Num(); ++UseCachedPoseIndex) { UAnimGraphNode_UseCachedPose* UsedCachedPoseNode = UseCachedPoseNodes[UseCachedPoseIndex]; UAnimGraphNode_Base::FNodeAttributeArray AbsorbedAttributesBelow = TraverseNodes_Recursive_PerNode(UsedCachedPoseNode, InAttributes); if(UseCachedPoseIndex == UseCachedPoseNodes.Num() - 1) { return AbsorbedAttributesBelow; } } } // Traverse out of custom transitions else if(UAnimGraphNode_CustomTransitionResult* TransitionResultNode = Cast(InNode)) { UAnimationCustomTransitionGraph* TransitionGraph = CastChecked(TransitionResultNode->GetGraph()); UAnimStateTransitionNode* TransitionNode = CastChecked(TransitionGraph->GetOuter()); UAnimationStateMachineGraph* StateMachineGraph = CastChecked(TransitionNode->GetOuter()); UAnimGraphNode_StateMachineBase* StateMachineNode = CastChecked(StateMachineGraph->GetOuter()); return TraverseNodes_Recursive_PerNode(StateMachineNode, InAttributes); } // Traverse out of state machines else if(UAnimGraphNode_StateResult* StateResultNode = Cast(InNode)) { UAnimationStateGraph* StateGraph = CastChecked(StateResultNode->GetGraph()); UAnimStateNode* StateNode = CastChecked(StateGraph->GetOuter()); UAnimationStateMachineGraph* StateMachineGraph = CastChecked(StateNode->GetOuter()); UAnimGraphNode_StateMachineBase* StateMachineNode = CastChecked(StateMachineGraph->GetOuter()); return TraverseNodes_Recursive_PerNode(StateMachineNode, InAttributes); } else if(UAnimGraphNode_BlendSpaceSampleResult* SampleResultNode = Cast(InNode)) { UAnimationBlendSpaceSampleGraph* SampleGraph = CastChecked(SampleResultNode->GetGraph()); UBlendSpaceGraph* BlendSpaceGraph = CastChecked(SampleGraph->GetOuter()); UAnimGraphNode_BlendSpaceGraphBase* BlendSpaceGraphNode = CastChecked(BlendSpaceGraph->GetOuter()); return TraverseNodes_Recursive_PerNode(BlendSpaceGraphNode, InAttributes); } return UAnimGraphNode_Base::FNodeAttributeArray(); } }; // Color connected nodes root-wise from output nodes for(auto& NodeAttributesPair : AttributeOutputNodes) { FCheckOutputNodes Checker(InCompilationContext, NodeAttributesPair.Value, AttributeInputNodes, SaveCachedPoseMap, NodeAttributesPair.Key); } }