Files
UnrealEngine/Engine/Plugins/Runtime/RigVM/Source/RigVMDeveloper/Private/RigVMModel/RigVMGraphSection.cpp
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

711 lines
17 KiB
C++

// 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<URigVMRerouteNode>())
{
TArray<URigVMPin*> SourcePins = SourcePin->GetLinkedSourcePins();
if (SourcePins.Num() == 0)
{
SourcePin = nullptr;
break;
}
SourcePin = SourcePins[0];
}
return SourcePin; // can be nullptr
}
TArray<URigVMPin*> FRigVMGraphSectionLink::FindTargetPinsSkippingReroutes(const URigVMLink* InLink)
{
TArray<URigVMPin*> AllTargetPins = {InLink->GetTargetPin()};
TArray<URigVMPin*> FilteredTargetPins;
for (int32 TargetPinIndex = 0; TargetPinIndex < AllTargetPins.Num(); ++TargetPinIndex)
{
URigVMPin* TargetPin = AllTargetPins[TargetPinIndex];
if (TargetPin == nullptr)
{
continue;
}
if (TargetPin->GetNode()->IsA<URigVMRerouteNode>())
{
AllTargetPins.Append(TargetPin->GetLinkedTargetPins());
continue;
}
FilteredTargetPins.Add(TargetPin);
}
return FilteredTargetPins;
}
TArray<FRigVMGraphSectionLink::FPinTuple> FRigVMGraphSectionLink::FindLinksSkippingReroutes(const URigVMNode* InNode)
{
check(InNode);
const TArray<URigVMLink*> AllLinks = InNode->GetLinks();
TArray<FPinTuple> FilteredLinks;
FilteredLinks.Reserve(AllLinks.Num());
for (const URigVMLink* Link : AllLinks)
{
const URigVMPin* SourcePin = FindSourcePinSkippingReroutes(Link);
if (SourcePin == nullptr)
{
continue;
}
const TArray<URigVMPin*> 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<URigVMLink*>& 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<const URigVMNode*> FRigVMGraphSectionLink::FindTargetNodes(const URigVMNode* InSourceNode) const
{
check(InSourceNode);
TArray<const URigVMNode*> TargetNodes;
const URigVMPin* SourcePin = InSourceNode->FindPin(SourcePinPath);
if (SourcePin == nullptr)
{
return TargetNodes;
}
const TArray<URigVMLink*>& Links = SourcePin->GetLinks();
for (const URigVMLink* Link : Links)
{
const TArray<URigVMPin*> 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<URigVMNode*>& InNodes)
: Hash(UINT32_MAX)
{
if (InNodes.Contains(nullptr))
{
return;
}
TArray<URigVMNode*> 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<bool> bNodeHasOutputLink;
bNodeHasOutputLink.AddZeroed(SortedNodes.Num());
TArray<TArray<FRigVMGraphSectionLink::FPinTuple>> 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<FRigVMGraphSectionLink::FPinTuple> 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<int32, int32> NodeTraversalByInt32;
struct Local
{
static void VisitNode(
const TArray<URigVMNode*>& InNodes,
int32 NodeIndex,
int32& TraversalIndex,
const TArray<TArray<FRigVMGraphSectionLink::FPinTuple>>& InPerNodeInputLinks,
TMap<int32, int32>& NodeTraversalIndex)
{
if(NodeTraversalIndex.Contains(NodeIndex))
{
return;
}
NodeTraversalIndex.Add(NodeIndex, TraversalIndex++);
const TArray<FRigVMGraphSectionLink::FPinTuple>& 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<FName> 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<const URigVMNode*, int32> 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<FRigVMGraphSectionLink::FPinTuple> 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> FRigVMGraphSection::GetSectionsFromSelection(const URigVMGraph* InGraph)
{
TArray<FRigVMGraphSection> Sections;
TArray<URigVMNode*> 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<TArray<URigVMNode*>> Islands = FRigVMGraphSection::GetNodeIslands(AllSelectedNodes);
for (const TArray<URigVMNode*>& 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<URigVMCommentNode>() &&
!InNode->IsA<URigVMRerouteNode>();
}
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<URigVMFunctionReferenceNode>(InNode))
{
const FRigVMGraphFunctionIdentifier Identifier = FunctionRefNode->GetFunctionIdentifier();
if(Identifier.IsValid())
{
Hash = HashCombine(Hash, GetTypeHash(Identifier));
}
}
else if(const URigVMTemplateNode* TemplateNode = Cast<URigVMTemplateNode>(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<FName, TRigVMTypeIndex>& Pair : Types)
{
Hash = HashCombine(Hash, GetTypeHash(Pair.Key));
Hash = HashCombine(Hash, GetTypeHash((int32)Pair.Value));
}
}
}
else if(const URigVMVariableNode* VariableNode = Cast<URigVMVariableNode>(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> FRigVMGraphSection::FindMatches(const URigVMGraph* InGraph) const
{
return FindMatches(InGraph->GetNodes());
}
TArray<FRigVMGraphSection> FRigVMGraphSection::FindMatches(const TArray<URigVMNode*>& InAvailableNodes) const
{
TArray<URigVMNode*> AvailableNodes = InAvailableNodes;
TArray<FRigVMGraphSection> Matches;
TArray<URigVMNode*> 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<TArray<URigVMNode*>> FRigVMGraphSection::GetNodeIslands(const TArray<URigVMNode*>& InNodes)
{
TArray<TArray<URigVMNode*>> Islands;
// compute islands of connected nodes - each subset is going
// to represent one island
TArray<int32> IslandIndexPerNode;
IslandIndexPerNode.AddZeroed(InNodes.Num());
while (IslandIndexPerNode.Contains(0))
{
TArray<int32> 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<URigVMNode*> 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<URigVMNode*>& InAvailableNodes, TArray<URigVMNode*>* 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<URigVMNode*> 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<URigVMNode*>& InAvailableNodes, int32 InNodeIndex, TArray<URigVMNode*>& VisitedNodes) const
{
if (!InAvailableNodes.Contains(InNode))
{
return false;
}
if (VisitedNodes[InNodeIndex])
{
return true;
}
if (VisitedNodes.Contains(InNode))
{
return false;
}
VisitedNodes[InNodeIndex] = const_cast<URigVMNode*>(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<const URigVMNode*> 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<URigVMNode*> 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