// Copyright Epic Games, Inc. All Rights Reserved. #include "RigVMModel/RigVMGraphSection.h" #include "RigVMModel/RigVMGraph.h" #include "RigVMModel/Nodes/RigVMCommentNode.h" #include "RigVMModel/Nodes/RigVMDispatchNode.h" #include "RigVMModel/Nodes/RigVMFunctionReferenceNode.h" #include "RigVMModel/Nodes/RigVMRerouteNode.h" #include "RigVMModule.h" #include "BoneControllers/BoneControllerTypes.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(RigVMGraphSection) //UE_DISABLE_OPTIMIZATION URigVMPin* FRigVMGraphSectionLink::FindSourcePinSkippingReroutes(const URigVMLink* InLink) { check(InLink); URigVMPin* SourcePin = InLink->GetSourcePin(); if (SourcePin == nullptr) { return nullptr; } // traverse the reroute and find the source node while (SourcePin->GetNode()->IsA()) { TArray SourcePins = SourcePin->GetLinkedSourcePins(); if (SourcePins.Num() == 0) { SourcePin = nullptr; break; } SourcePin = SourcePins[0]; } return SourcePin; // can be nullptr } TArray FRigVMGraphSectionLink::FindTargetPinsSkippingReroutes(const URigVMLink* InLink) { TArray AllTargetPins = {InLink->GetTargetPin()}; TArray FilteredTargetPins; for (int32 TargetPinIndex = 0; TargetPinIndex < AllTargetPins.Num(); ++TargetPinIndex) { URigVMPin* TargetPin = AllTargetPins[TargetPinIndex]; if (TargetPin == nullptr) { continue; } if (TargetPin->GetNode()->IsA()) { AllTargetPins.Append(TargetPin->GetLinkedTargetPins()); continue; } FilteredTargetPins.Add(TargetPin); } return FilteredTargetPins; } TArray FRigVMGraphSectionLink::FindLinksSkippingReroutes(const URigVMNode* InNode) { check(InNode); const TArray AllLinks = InNode->GetLinks(); TArray FilteredLinks; FilteredLinks.Reserve(AllLinks.Num()); for (const URigVMLink* Link : AllLinks) { const URigVMPin* SourcePin = FindSourcePinSkippingReroutes(Link); if (SourcePin == nullptr) { continue; } const TArray TargetPins = FindTargetPinsSkippingReroutes(Link); for (const URigVMPin* TargetPin : TargetPins) { if (TargetPin == nullptr) { continue; } FilteredLinks.Emplace(SourcePin, TargetPin); } } return FilteredLinks; } const URigVMNode* FRigVMGraphSectionLink::FindSourceNode(const URigVMNode* InTargetNode) const { check(InTargetNode); const URigVMPin* TargetPin = InTargetNode->FindPin(TargetPinPath); if (TargetPin == nullptr) { return nullptr; } const TArray& Links = TargetPin->GetLinks(); for (const URigVMLink* Link : Links) { const URigVMPin* SourcePin = FindSourcePinSkippingReroutes(Link); if (SourcePin == nullptr) { continue; } if (SourcePin->GetSegmentPath(true) != SourcePinPath) { continue; } const URigVMNode* SourceNode = SourcePin->GetNode(); if (SourceNode == nullptr) { continue; } if (SourceNodeHash != FRigVMGraphSection::GetNodeHash(SourceNode)) { continue; } return SourceNode; } return nullptr; } TArray FRigVMGraphSectionLink::FindTargetNodes(const URigVMNode* InSourceNode) const { check(InSourceNode); TArray TargetNodes; const URigVMPin* SourcePin = InSourceNode->FindPin(SourcePinPath); if (SourcePin == nullptr) { return TargetNodes; } const TArray& Links = SourcePin->GetLinks(); for (const URigVMLink* Link : Links) { const TArray TargetPins = FindTargetPinsSkippingReroutes(Link); for (const URigVMPin* TargetPin : TargetPins) { if (TargetPin->GetSegmentPath(true) != TargetPinPath) { continue; } const URigVMNode* TargetNode = TargetPin->GetNode(); if (TargetNode == nullptr) { continue; } if (TargetNodeHash != FRigVMGraphSection::GetNodeHash(TargetNode)) { continue; } TargetNodes.Add(TargetNode); } } return TargetNodes; } FRigVMGraphSection::FRigVMGraphSection() : Hash(UINT32_MAX) { } FRigVMGraphSection::FRigVMGraphSection(const TArray& InNodes) : Hash(UINT32_MAX) { if (InNodes.Contains(nullptr)) { return; } TArray SortedNodes; SortedNodes.Append(InNodes); SortedNodes.RemoveAll([](const URigVMNode* Node) -> bool { return !IsValidNode(Node); }); if(SortedNodes.IsEmpty()) { return; } const URigVMGraph* Graph = SortedNodes[0]->GetGraph(); for(const URigVMNode* Node : SortedNodes) { if(Node->GetGraph() != Graph) { UE_LOG(LogRigVM, Error, TEXT("Provided nodes are not on the same graph.")); Reset(); return; } } // find all links within the section. TArray bNodeHasOutputLink; bNodeHasOutputLink.AddZeroed(SortedNodes.Num()); TArray> PerNodeInputLinks; PerNodeInputLinks.SetNum(SortedNodes.Num()); for(int32 TargetNodeIndex = 0; TargetNodeIndex < SortedNodes.Num(); TargetNodeIndex++) { const URigVMNode* TargetNode = SortedNodes[TargetNodeIndex]; // links are sorted from first pin to last pin const TArray AllLinks = FRigVMGraphSectionLink::FindLinksSkippingReroutes(TargetNode); PerNodeInputLinks[TargetNodeIndex].Reserve(AllLinks.Num()); for (const FRigVMGraphSectionLink::FPinTuple& Link : AllLinks) { const URigVMPin* SourcePin = Link.Get<0>(); check(SourcePin); const int32 SourceNodeIndex = SortedNodes.Find(SourcePin->GetNode()); if (SourceNodeIndex == INDEX_NONE) { continue; } const URigVMPin* TargetPin = Link.Get<1>(); check(TargetPin); if (TargetPin->GetNode() == TargetNode) { PerNodeInputLinks[TargetNodeIndex].Emplace(SourcePin, TargetPin); } else { URigVMNode* OtherTargetNode = TargetPin->GetNode(); const int32 OtherTargetNodeIndex = SortedNodes.Find(OtherTargetNode); if (OtherTargetNodeIndex != INDEX_NONE) { bNodeHasOutputLink[SourceNodeIndex] = true; } } } } // determine the leaf nodes LeafNodes.Reserve(SortedNodes.Num()); for(int32 NodeIndex = 0; NodeIndex < SortedNodes.Num(); NodeIndex++) { LeafNodes.Add(NodeIndex); } LeafNodes.RemoveAll([bNodeHasOutputLink](const int32& NodeIndex) -> bool { return bNodeHasOutputLink[NodeIndex]; }); if (LeafNodes.IsEmpty()) { Reset(); return; } TMap NodeTraversalByInt32; struct Local { static void VisitNode( const TArray& InNodes, int32 NodeIndex, int32& TraversalIndex, const TArray>& InPerNodeInputLinks, TMap& NodeTraversalIndex) { if(NodeTraversalIndex.Contains(NodeIndex)) { return; } NodeTraversalIndex.Add(NodeIndex, TraversalIndex++); const TArray& LinksForNode = InPerNodeInputLinks[NodeIndex]; for(const FRigVMGraphSectionLink::FPinTuple& LinkForNode : LinksForNode) { const URigVMPin* SourcePin = LinkForNode.Get<0>(); if (SourcePin == nullptr) { continue; } URigVMNode* SourceNode = SourcePin->GetNode(); const int32 SourceNodeIndex = InNodes.Find(SourceNode); if (SourceNodeIndex != INDEX_NONE) { VisitNode(InNodes, SourceNodeIndex, TraversalIndex, InPerNodeInputLinks, NodeTraversalIndex); } } } }; // visit all leaf nodes int32 TraversalIndex = 0; TArray LeafNodeNames; for(const int32& LeafNodeIndex : LeafNodes) { LeafNodeNames.Add(SortedNodes[LeafNodeIndex]->GetFName()); Local::VisitNode(SortedNodes, LeafNodeIndex, TraversalIndex, PerNodeInputLinks, NodeTraversalByInt32); } // convert the map from int32 to object ptr TMap NodeTraversalByPtr; for(int32 NodeIndex = 0; NodeIndex < SortedNodes.Num(); NodeIndex++) { NodeTraversalByPtr.Add(SortedNodes[NodeIndex], NodeTraversalByInt32.FindChecked(NodeIndex)); } // sort the nodes Algo::Sort(SortedNodes, [NodeTraversalByPtr](const URigVMNode* InNodeA, const URigVMNode* InNodeB) -> bool { return NodeTraversalByPtr.FindChecked(InNodeA) < NodeTraversalByPtr.FindChecked(InNodeB); }); Nodes.Reserve(SortedNodes.Num()); NodeHashes.Reserve(SortedNodes.Num()); for(URigVMNode* Node : SortedNodes) { Nodes.Add(Node->GetFName()); NodeHashes.Add(GetNodeHash(Node)); } for(URigVMNode* Node : SortedNodes) { const TArray PerNodeLinks = FRigVMGraphSectionLink::FindLinksSkippingReroutes(Node); for (const FRigVMGraphSectionLink::FPinTuple& Link : PerNodeLinks) { const URigVMPin* SourcePin = Link.Get<0>(); const URigVMPin* TargetPin = Link.Get<1>(); if(SourcePin == nullptr || TargetPin == nullptr) { continue; } URigVMNode* SourceNode = SourcePin->GetNode(); URigVMNode* TargetNode = TargetPin->GetNode(); if(TargetNode == Node) { if(SortedNodes.Contains(SourceNode)) { FRigVMGraphSectionLink LinkInfo; LinkInfo.SourceNodeIndex = SortedNodes.Find(SourceNode); LinkInfo.TargetNodeIndex = SortedNodes.Find(TargetNode); LinkInfo.SourceNodeHash = NodeHashes[LinkInfo.SourceNodeIndex]; LinkInfo.TargetNodeHash = NodeHashes[LinkInfo.TargetNodeIndex]; LinkInfo.SourcePinPath = SourcePin->GetSegmentPath(true); LinkInfo.TargetPinPath = TargetPin->GetSegmentPath(true); Links.Add(LinkInfo); } } } } LeafNodes.Reset(); Algo::Transform(LeafNodeNames, LeafNodes, [this](const FName& LeafNodeName) -> int32 { return Nodes.Find(LeafNodeName); }); Hash = 0; for(uint32 NodeHash : NodeHashes) { Hash = HashCombine(Hash, NodeHash); } for (const FRigVMGraphSectionLink& Link : Links) { Hash = HashCombine(Hash, GetLinkHash(Link)); } } TArray FRigVMGraphSection::GetSectionsFromSelection(const URigVMGraph* InGraph) { TArray Sections; TArray AllSelectedNodes; Algo::Transform(InGraph->GetSelectNodes(), AllSelectedNodes, [InGraph](const FName& NodeName) -> URigVMNode* { return InGraph->FindNodeByName(NodeName); }); AllSelectedNodes.Remove(nullptr); if(AllSelectedNodes.Num() == 0) { return Sections; } const TArray> Islands = FRigVMGraphSection::GetNodeIslands(AllSelectedNodes); for (const TArray& Island : Islands) { Sections.Emplace(Island); } return Sections; } bool FRigVMGraphSection::IsValid() const { return Hash != UINT32_MAX && !Nodes.IsEmpty(); } bool FRigVMGraphSection::IsValidNode(const URigVMNode* InNode) { if (InNode) { return !InNode->IsA() && !InNode->IsA(); } return true; } void FRigVMGraphSection::Reset() { Hash = UINT32_MAX; Nodes.Reset(); NodeHashes.Reset(); LeafNodes.Reset(); Links.Reset(); } uint32 FRigVMGraphSection::GetNodeHash(const URigVMNode* InNode) { check(InNode); uint32 Hash = GetTypeHash(InNode->GetClass()); if(const URigVMFunctionReferenceNode* FunctionRefNode = Cast(InNode)) { const FRigVMGraphFunctionIdentifier Identifier = FunctionRefNode->GetFunctionIdentifier(); if(Identifier.IsValid()) { Hash = HashCombine(Hash, GetTypeHash(Identifier)); } } else if(const URigVMTemplateNode* TemplateNode = Cast(InNode)) { if(const FRigVMFunction* Function = TemplateNode->GetResolvedFunction()) { Hash = HashCombine(Hash, GetTypeHash(Function->Name)); } else if(const FRigVMTemplate* Template = TemplateNode->GetTemplate()) { Hash = HashCombine(Hash, GetTypeHash(Template->GetNotation())); const FRigVMTemplateTypeMap Types = TemplateNode->GetTemplatePinTypeMap(); for(const TPair& Pair : Types) { Hash = HashCombine(Hash, GetTypeHash(Pair.Key)); Hash = HashCombine(Hash, GetTypeHash((int32)Pair.Value)); } } } else if(const URigVMVariableNode* VariableNode = Cast(InNode)) { Hash = HashCombine(Hash, GetTypeHash(VariableNode->GetVariableName())); Hash = HashCombine(Hash, GetTypeHash(VariableNode->IsGetter())); } return Hash; } uint32 FRigVMGraphSection::GetLinkHash(const FRigVMGraphSectionLink& InLink) { uint32 Hash = HashCombine(GetTypeHash(InLink.SourceNodeIndex), GetTypeHash(InLink.TargetNodeIndex)); Hash = HashCombine(Hash, GetTypeHash(InLink.SourcePinPath)); Hash = HashCombine(Hash, GetTypeHash(InLink.TargetPinPath)); return Hash; } TArray FRigVMGraphSection::FindMatches(const URigVMGraph* InGraph) const { return FindMatches(InGraph->GetNodes()); } TArray FRigVMGraphSection::FindMatches(const TArray& InAvailableNodes) const { TArray AvailableNodes = InAvailableNodes; TArray Matches; TArray MatchedNodes; bool bFoundMatch = true; while (bFoundMatch) { bFoundMatch = false; for (const URigVMNode* AvailableNode : AvailableNodes) { MatchedNodes.Reset(); if (MatchesNode(AvailableNode, AvailableNodes, &MatchedNodes)) { FRigVMGraphSection Match(MatchedNodes); if (Match.IsValid() && Match.Hash == Hash) { Matches.Add(Match); AvailableNodes.RemoveAll([&MatchedNodes]( const URigVMNode* Node) -> bool { return MatchedNodes.Contains(Node); }); bFoundMatch = true; break; } } } } return Matches; } TArray> FRigVMGraphSection::GetNodeIslands(const TArray& InNodes) { TArray> Islands; // compute islands of connected nodes - each subset is going // to represent one island TArray IslandIndexPerNode; IslandIndexPerNode.AddZeroed(InNodes.Num()); while (IslandIndexPerNode.Contains(0)) { TArray NodeIndices = {IslandIndexPerNode.Find(0)}; Islands.Add({}); for (int32 Index = 0; Index < NodeIndices.Num(); Index++) { const int32& NodeIndex = NodeIndices[Index]; URigVMNode* Node = InNodes[NodeIndex]; Islands.Last().Add(Node); IslandIndexPerNode[NodeIndex] = Islands.Num(); TArray LinkedNodes = Node->GetLinkedSourceNodes(); LinkedNodes.Append(Node->GetLinkedTargetNodes()); for (URigVMNode* LinkedNode : LinkedNodes) { const int32 LinkedNodeIndex = InNodes.Find(LinkedNode); if (LinkedNodeIndex == INDEX_NONE) { continue; } if (IslandIndexPerNode[LinkedNodeIndex] == 0) { NodeIndices.AddUnique(LinkedNodeIndex); } } } } return Islands; } bool FRigVMGraphSection::ContainsNode(const FName& InNodeName) const { return Nodes.Contains(InNodeName); } bool FRigVMGraphSection::MatchesNode(const URigVMNode* InNode, const TArray& InAvailableNodes, TArray* OutMatchingNodesForSet) const { if (!IsValidNode(InNode)) { return false; } const uint32 NodeHash = GetNodeHash(InNode); for (int32 NodeIndex = 0; NodeIndex < Nodes.Num(); NodeIndex++) { if (NodeHash != NodeHashes[NodeIndex]) { continue; } TArray VisitedNodes; VisitedNodes.AddZeroed(Nodes.Num()); if (MatchesNode_Impl(InNode, InAvailableNodes, NodeIndex, VisitedNodes)) { if (OutMatchingNodesForSet) { OutMatchingNodesForSet->Append(VisitedNodes); } return true; } } return false; } bool FRigVMGraphSection::MatchesNode_Impl(const URigVMNode* InNode, const TArray& InAvailableNodes, int32 InNodeIndex, TArray& VisitedNodes) const { if (!InAvailableNodes.Contains(InNode)) { return false; } if (VisitedNodes[InNodeIndex]) { return true; } if (VisitedNodes.Contains(InNode)) { return false; } VisitedNodes[InNodeIndex] = const_cast(InNode); // traverse all right to left for (const FRigVMGraphSectionLink& Link : Links) { if (Link.TargetNodeIndex == InNodeIndex) { if (VisitedNodes[Link.SourceNodeIndex]) { continue; } if (const URigVMNode* LinkedNode = Link.FindSourceNode(InNode)) { if (!MatchesNode_Impl(LinkedNode, InAvailableNodes, Link.SourceNodeIndex, VisitedNodes)) { VisitedNodes[InNodeIndex] = nullptr; return false; } } else { VisitedNodes[InNodeIndex] = nullptr; return false; } } } // also traverse all links from left to right for (const FRigVMGraphSectionLink& Link : Links) { if (Link.SourceNodeIndex == InNodeIndex) { if (VisitedNodes[Link.TargetNodeIndex]) { continue; } TArray LinkedNodes = Link.FindTargetNodes(InNode); if (LinkedNodes.Num() == 0) { VisitedNodes[InNodeIndex] = nullptr; return false; } if (LinkedNodes.Num() == 1) { if (!MatchesNode_Impl(LinkedNodes[0], InAvailableNodes, Link.TargetNodeIndex, VisitedNodes)) { VisitedNodes[InNodeIndex] = nullptr; return false; } } // if we have more than one match for the linked nodes - we'll need to see which // one may be a potential match. for that we'll need to create a copy of the visited // nodes array since it may be filled up to a point - just to realize that we hit // a mismatching node later. else { const TArray VisitedNodesCopy = VisitedNodes; bool bFoundMatch = false; for (const URigVMNode* LinkedNode : LinkedNodes) { if (!MatchesNode_Impl(LinkedNode, InAvailableNodes, Link.TargetNodeIndex, VisitedNodes)) { // roll back the visited nodes array VisitedNodes = VisitedNodesCopy; } else { bFoundMatch = true; break; } } if (!bFoundMatch) { VisitedNodes[InNodeIndex] = nullptr; return false; } } } } return true; } //UE_ENABLE_OPTIMIZATION