// Copyright Epic Games, Inc. All Rights Reserved. #include "MuCOE/GraphTraversal.h" #include "AssetRegistry/AssetRegistryModule.h" #include "MuCO/LoadUtils.h" #include "MuCOE/CustomizableObjectPin.h" #include "MuCOE/EdGraphSchema_CustomizableObject.h" #include "MuCOE/Nodes/CustomizableObjectNodeEnumParameter.h" #include "MuCOE/Nodes/CustomizableObjectNodeExposePin.h" #include "MuCOE/Nodes/CustomizableObjectNodeModifierExtendMeshSection.h" #include "MuCOE/Nodes/CustomizableObjectNodeExternalPin.h" #include "MuCOE/Nodes/CustomizableObjectNodeMaterial.h" #include "MuCOE/Nodes/CustomizableObjectNodeMaterialVariation.h" #include "MuCOE/Nodes/CustomizableObjectNodeMeshMorph.h" #include "MuCOE/Nodes/CustomizableObjectNodeMeshMorphStackApplication.h" #include "MuCOE/Nodes/CustomizableObjectNodeMeshMorphStackDefinition.h" #include "MuCOE/Nodes/CustomizableObjectNodeMeshReshape.h" #include "MuCOE/Nodes/CustomizableObjectNodeMeshSwitch.h" #include "MuCOE/Nodes/CustomizableObjectNodeMeshVariation.h" #include "MuCOE/Nodes/CustomizableObjectNodeSkeletalMeshParameter.h" #include "MuCOE/Nodes/CustomizableObjectNodeObject.h" #include "MuCOE/Nodes/CustomizableObjectNodeSkeletalMesh.h" #include "MuCOE/Nodes/CustomizableObjectNodeStaticMesh.h" #include "MuCOE/Nodes/CustomizableObjectNodeStaticString.h" #include "MuCOE/Nodes/CustomizableObjectNodeTable.h" #include "MuCOE/Nodes/CustomizableObjectNodeAnimationPose.h" #include "MuCOE/Nodes/CustomizableObjectNodeObjectGroup.h" #include "MuCOE/Nodes/CustomizableObjectNodeReroute.h" #include "UObject/UObjectIterator.h" TArray FollowPinArray(const UEdGraphPin& Pin, bool bIgnoreOrphan, bool* bOutCycleDetected) { MUTABLE_CPUPROFILER_SCOPE(FollowPinArray) bool bCycleDetected = false; TArray Result; // Early out optimization if (Pin.LinkedTo.IsEmpty()) { if (bOutCycleDetected) { *bOutCycleDetected = bCycleDetected; } return Result; } TSet Visited; Visited.Reserve(32); TArray PinsToVisit; PinsToVisit.Reserve(32); PinsToVisit.Add(&Pin); while (PinsToVisit.Num()) { const UEdGraphPin& CurrentPin = *PinsToVisit.Pop(); if (!bIgnoreOrphan && IsPinOrphan(CurrentPin)) { continue; } Visited.FindOrAdd(&CurrentPin, &bCycleDetected); if (bCycleDetected) { continue; } for (UEdGraphPin* LinkedPin : CurrentPin.LinkedTo) { if (!bIgnoreOrphan && IsPinOrphan(*LinkedPin)) { continue; } if (const UCustomizableObjectNodeReroute* NodeReroute = Cast(LinkedPin->GetOwningNodeUnchecked())) { PinsToVisit.Add(Pin.Direction == EGPD_Input ? NodeReroute->GetInputPin() : NodeReroute->GetOutputPin()); } else if (const UCustomizableObjectNodeExternalPin* ExternalPinNode = Cast(LinkedPin->GetOwningNodeUnchecked())) { check(Pin.Direction == EGPD_Input); if (const UCustomizableObjectNodeExposePin* LinkedNode = ExternalPinNode->GetNodeExposePin()) { const UEdGraphPin* ExposePin = LinkedNode->InputPin(); check(ExposePin); PinsToVisit.Add(ExposePin); } } else if (const UCustomizableObjectNodeExposePin* ExposePinNode = Cast(LinkedPin->GetOwningNodeUnchecked())) { check(Pin.Direction == EGPD_Output); for (TObjectIterator It; It; ++It) { const UCustomizableObjectNodeExternalPin* LinkedNode = *It; if (LinkedNode && LinkedNode->GetNodeExposePin() == ExposePinNode) { const UEdGraphPin* ExternalPin = LinkedNode->GetExternalPin(); check(ExternalPin); PinsToVisit.Add(ExternalPin); } } } else { Result.Add(LinkedPin); } } } if (bOutCycleDetected) { *bOutCycleDetected = bCycleDetected; } return Result; } TArray FollowInputPinArray(const UEdGraphPin& Pin, bool* bOutCycleDetected) { check(Pin.Direction == EGPD_Input); return FollowPinArray(Pin, true, bOutCycleDetected); } UEdGraphPin* FollowInputPin(const UEdGraphPin& Pin, bool* CycleDetected) { TArray Result = FollowInputPinArray(Pin, CycleDetected); check(Result.Num() <= 1); // Use FollowInputPinArray if the pin can have more than one input. if (!Result.IsEmpty()) { return Result[0]; } else { return nullptr; } } TArray FollowOutputPinArray(const UEdGraphPin& Pin, bool* bOutCycleDetected) { check(Pin.Direction == EGPD_Output); return FollowPinArray(Pin, true, bOutCycleDetected); } UEdGraphPin* FollowOutputPin(const UEdGraphPin& Pin, bool* CycleDetected) { TArray Result = FollowOutputPinArray(Pin, CycleDetected); check(Result.Num() <= 1); // Use FollowInputPinArray if the pin can have more than one input. if (!Result.IsEmpty()) { return Result[0]; } else { return nullptr; } } TArray ReverseFollowPinArray(const UEdGraphPin& Pin, bool bIgnoreOrphan, bool* bOutCycleDetected) { bool bCycleDetected = false; TArray Result; TSet Visited; TArray PinsToVisit; PinsToVisit.Add(const_cast(&Pin)); while (PinsToVisit.Num()) { UEdGraphPin& CurrentPin = *PinsToVisit.Pop(); if (!bIgnoreOrphan && IsPinOrphan(CurrentPin)) { continue; } Visited.FindOrAdd(&CurrentPin, &bCycleDetected); if (bCycleDetected) { continue; } if (const UCustomizableObjectNodeExposePin* ExposePinNode = Cast(CurrentPin.GetOwningNodeUnchecked())) { check(Pin.Direction == EGPD_Input); for (TObjectIterator It; It; ++It) { const UCustomizableObjectNodeExternalPin* LinkedNode = *It; if (IsValid(LinkedNode) && !LinkedNode->IsTemplate() && LinkedNode->GetNodeExposePin() == ExposePinNode) { const UEdGraphPin* ExternalPin = LinkedNode->GetExternalPin(); check(ExternalPin); for (UEdGraphPin* LinkedPin : ExternalPin->LinkedTo) { PinsToVisit.Add(LinkedPin); } } } } else if (const UCustomizableObjectNodeExternalPin* ExternalPinNode = Cast(CurrentPin.GetOwningNodeUnchecked())) { check(Pin.Direction == EGPD_Output); if (const UCustomizableObjectNodeExposePin* LinkedNode = ExternalPinNode->GetNodeExposePin()) { const UEdGraphPin* ExposePin = LinkedNode->InputPin(); check(ExposePin); for (UEdGraphPin* LinkedPin : ExposePin->LinkedTo) { PinsToVisit.Add(LinkedPin); } } } else if (const UCustomizableObjectNodeReroute* NodeReroute = Cast(CurrentPin.GetOwningNodeUnchecked())) { UEdGraphPin* ReroutePin = Pin.Direction == EGPD_Output ? NodeReroute->GetInputPin() : NodeReroute->GetOutputPin(); for (UEdGraphPin* LinkedPin : ReroutePin->LinkedTo) { PinsToVisit.Add(LinkedPin); } } else { if (bIgnoreOrphan || !IsPinOrphan(CurrentPin)) { Result.Add(&CurrentPin); } } } if (bOutCycleDetected) { *bOutCycleDetected = bCycleDetected; } return Result; } UCustomizableObjectNodeObject* GetRootNode(const UCustomizableObject* Object) { if (!Object) { return nullptr; } for (UEdGraphNode* Node : Object->GetPrivate()->GetSource()->Nodes) { if (UCustomizableObjectNodeObject* NodeObject = Cast(Node)) { if (NodeObject->bIsBase) { return NodeObject; } } } return nullptr; } bool GetParentsUntilRoot(const UCustomizableObject* Object, TArray& ArrayNodeObject, TArray& ArrayCustomizableObject) { UCustomizableObjectNodeObject* Root = GetRootNode(Object); bool bSuccess = true; if (Root) { if (!ArrayCustomizableObject.Contains(Object)) { ArrayNodeObject.Add(Root); ArrayCustomizableObject.Add(Object); } else { // This object has already been visted which means that there is a Cycle between Customizable Objects return false; } if (Root->ParentObject != nullptr) { bSuccess = GetParentsUntilRoot(Root->ParentObject, ArrayNodeObject, ArrayCustomizableObject); } } return bSuccess; } bool HasCandidateAsParent(UCustomizableObjectNodeObject* Node, UCustomizableObject* ParentCandidate) { if (Node->ParentObject == ParentCandidate) { return true; } if (Node->ParentObject != nullptr) { UCustomizableObjectNodeObject* ParentNodeObject = GetRootNode(Node->ParentObject); if (ParentNodeObject->ParentObject) { return HasCandidateAsParent(ParentNodeObject, ParentCandidate); } else { return false; } } return false; } UCustomizableObject* GetFullGraphRootObject(const UCustomizableObjectNodeObject* Node, TArray& VisitedObjects) { if (Node->ParentObject != nullptr) { VisitedObjects.Add(Node->ParentObject); UCustomizableObjectNodeObject* Root = GetRootNode(Node->ParentObject); if (Root->ParentObject == nullptr) { return Node->ParentObject; } else { if (VisitedObjects.Contains(Root->ParentObject)) { //There is a cycle return nullptr; } else { return GetFullGraphRootObject(Root, VisitedObjects); } } } return nullptr; } UCustomizableObject* GraphTraversal::GetObject(const UCustomizableObjectNode& Node) { if (Node.IsInMacro()) { return nullptr; } return CastChecked(Node.GetGraph()->GetOuter()); } UCustomizableObject* GraphTraversal::GetRootObject(UCustomizableObject* ChildObject) { const UCustomizableObject* ConstChildObject = ChildObject; return const_cast(GetRootObject(ConstChildObject)); } const UCustomizableObject* GraphTraversal::GetRootObject(const UCustomizableObject* ChildObject) { // Grab a node to start the search -> Get the root since it should be always present UCustomizableObjectNodeObject* ObjectRootNode = GetRootNode(ChildObject); if (ObjectRootNode && ObjectRootNode->ParentObject) { TArray VisitedNodes; return GetFullGraphRootObject(ObjectRootNode, VisitedNodes); } // No parent object found, return input as the parent of the graph // This can also mean the ObjectRootNode does not exist because it has not been opened yet (so no nodes have been generated) return ChildObject; } void RecursiveVisitNodes(UCustomizableObjectNode& CurrentNode, const TFunction& VisitFunction, TArray& MacroContext, TSet& VisitedNodes, const TMultiMap& ObjectGroupMap) { // Check if we already visited this node if (VisitedNodes.Contains(&CurrentNode)) { return; } // Add it to the cache of already visited nodes VisitedNodes.Add(&CurrentNode); // Visit the current Node VisitFunction(CurrentNode); // Iterate through all nodes linked to the current node for (UEdGraphPin* Pin : CurrentNode.GetAllNonOrphanPins()) { if (Pin->Direction != EGPD_Input) { continue; } for (UEdGraphPin* ConnectedPin : FollowInputPinArray(*Pin)) { UEdGraphNode* ConnectedNode = ConnectedPin->GetOwningNode(); if (UCustomizableObjectNodeObjectGroup* ObjectGroupNode = Cast(ConnectedNode)) { // Visit the pins directly connected to the ObjectGroup node such as the child objects and projectors RecursiveVisitNodes(*ObjectGroupNode, VisitFunction, MacroContext, VisitedNodes, ObjectGroupMap); // Visit the child objects in other COs TArray ChildObjectNodes; ObjectGroupMap.MultiFind(ObjectGroupNode->NodeGuid, ChildObjectNodes); for (UCustomizableObjectNode* ChildObjectNode : ChildObjectNodes) { RecursiveVisitNodes(*ChildObjectNode, VisitFunction, MacroContext, VisitedNodes, ObjectGroupMap); } } else if (UCustomizableObjectNodeMacroInstance* MacroInstanceNode = Cast(ConnectedNode)) { // We visit this node here because we need to pass through it multiple times if (!VisitedNodes.Contains(MacroInstanceNode)) { VisitFunction(*MacroInstanceNode); VisitedNodes.Add(MacroInstanceNode); } if (const UEdGraphPin* OutputPin = MacroInstanceNode->GetMacroIOPin(ECOMacroIOType::COMVT_Output, ConnectedPin->PinName)) { if (UCustomizableObjectNode* OutputNode = Cast(OutputPin->GetOwningNode())) { // We visit this node here because we need to pass through it multiple times if (!VisitedNodes.Contains(OutputNode)) { VisitFunction(*OutputNode); VisitedNodes.Add(OutputNode); } } // Going to the node linked to the OutputPin if (const UEdGraphPin* OutputLinkedPin = FollowInputPin(*OutputPin)) { // We go directly to the node linked to the output node to ensure that is linked and we need to visit it. if (UCustomizableObjectNode* OutputLinkedNode = Cast(OutputLinkedPin->GetOwningNode())) { MacroContext.Push(MacroInstanceNode); RecursiveVisitNodes(*OutputLinkedNode, VisitFunction, MacroContext, VisitedNodes, ObjectGroupMap); MacroContext.Pop(); } } } } else if (UCustomizableObjectNodeTunnel* IOMacroNode = Cast(ConnectedNode)) { // Output Nodes are already visited if (IOMacroNode->bIsInputNode) { check(MacroContext.Num()); // We visit this node here because we need to pass through it multiple times if (!VisitedNodes.Contains(IOMacroNode)) { VisitFunction(*IOMacroNode); VisitedNodes.Add(IOMacroNode); } // Going to the parent's graph where the current macro was instantiated const UCustomizableObjectNodeMacroInstance* ParentMacroInstanceNode = MacroContext.Pop(); if (ParentMacroInstanceNode) { if (const UEdGraphPin* InputPin = ParentMacroInstanceNode->FindPin(ConnectedPin->PinName, EEdGraphPinDirection::EGPD_Input)) { // Going to the node linked to the Macro Instance if (const UEdGraphPin* InputLinkedPin = FollowInputPin(*InputPin)) { if (UCustomizableObjectNode* InputLinkedNode = Cast(InputLinkedPin->GetOwningNode())) { RecursiveVisitNodes(*InputLinkedNode, VisitFunction, MacroContext, VisitedNodes, ObjectGroupMap); } } } } MacroContext.Push(ParentMacroInstanceNode); } } else if (UCustomizableObjectNode* Node = Cast(ConnectedNode)) { RecursiveVisitNodes(*Node, VisitFunction, MacroContext, VisitedNodes, ObjectGroupMap); } } } } void GraphTraversal::VisitNodes(UCustomizableObjectNode& StartNode, const TFunction& VisitFunction, const TMultiMap* ObjectGroupMap, TArray* MacroContext) { TSet VisitedNodes; TMultiMap LocalObjectGroupMap; TArray LocalMacroContext; if (!ObjectGroupMap) { LocalObjectGroupMap = GetNodeGroupObjectNodeMapping(GetObject(StartNode)); ObjectGroupMap = &LocalObjectGroupMap; } RecursiveVisitNodes(StartNode, VisitFunction, MacroContext ? *MacroContext : LocalMacroContext, VisitedNodes, *ObjectGroupMap); check(MacroContext ? MacroContext->IsEmpty() : LocalMacroContext.IsEmpty()); } const UEdGraphPin* GraphTraversal::FindIOPinSourceThroughMacroContext(const UEdGraphPin& Pin, TArray* MacroContext) { const UEdGraphPin* ReturnPin = nullptr; const UEdGraphNode* Node = Pin.GetOwningNode(); check(Node); if (const UCustomizableObjectNodeMacroInstance* NodeMacro = Cast(Node)) { if (const UEdGraphPin* OutputPin = NodeMacro->GetMacroIOPin(Pin.Direction == EGPD_Output ? ECOMacroIOType::COMVT_Output : ECOMacroIOType::COMVT_Input, Pin.PinName)) { if (const UEdGraphPin* ConnectedPin = FollowInputPin(*OutputPin)) { TArray NewMacroContext; if (!MacroContext) { MacroContext = &NewMacroContext; } else if (MacroContext->Contains(NodeMacro)) { return nullptr; } MacroContext->Push(NodeMacro); ReturnPin = GraphTraversal::FindIOPinSourceThroughMacroContext(*ConnectedPin, MacroContext); MacroContext->Pop(); } } } else if (const UCustomizableObjectNodeTunnel* NodeTunnel = Cast(Node)) { if (MacroContext && MacroContext->Num()) { const UCustomizableObjectNodeMacroInstance* MacroInstanceNode = MacroContext->Pop(); if (MacroInstanceNode) { if (const UEdGraphPin* FollowPin = MacroInstanceNode->FindPin(Pin.PinName, NodeTunnel->bIsInputNode ? EEdGraphPinDirection::EGPD_Input : EEdGraphPinDirection::EGPD_Output)) { if (const UEdGraphPin* ConnectedPin = NodeTunnel->bIsInputNode ? FollowInputPin(*FollowPin) : FollowOutputPin(*FollowPin)) { ReturnPin = GraphTraversal::FindIOPinSourceThroughMacroContext(*ConnectedPin, MacroContext); } } } MacroContext->Push(MacroInstanceNode); } } else { ReturnPin = &Pin; } return ReturnPin; } UCustomizableObjectNodeObject* GraphTraversal::GetFullGraphRootNode(const UCustomizableObject* Object, TArray& VisitedObjects) { if (Object != nullptr) { VisitedObjects.Add(Object); UCustomizableObjectNodeObject* Root = GetRootNode(Object); if (Root->ParentObject == nullptr) { return Root; } else { if (VisitedObjects.Contains(Root->ParentObject)) { //There is a cycle return nullptr; } else { return GetFullGraphRootNode(Root->ParentObject, VisitedObjects); } } } return nullptr; } const UEdGraphPin* FindMeshBaseSource(const UEdGraphPin& Pin, const bool bOnlyLookForStaticMesh, TArray* MacroContext) { check(Pin.Direction == EGPD_Output); check(Pin.PinType.PinCategory == UEdGraphSchema_CustomizableObject::PC_Mesh || Pin.PinType.PinCategory == UEdGraphSchema_CustomizableObject::PC_PassThroughMesh || Pin.PinType.PinCategory == UEdGraphSchema_CustomizableObject::PC_Material || Pin.PinType.PinCategory == UEdGraphSchema_CustomizableObject::PC_Modifier ); const UEdGraphNode* Node = Pin.GetOwningNode(); check(Node); if (Cast(Node)) { if (!bOnlyLookForStaticMesh) { return &Pin; } } else if (Cast(Node)) { return &Pin; } else if (const UCustomizableObjectNodeMeshReshape* TypedNodeReshape = Cast(Node)) { if (const UEdGraphPin* ConnectedPin = FollowInputPin(*TypedNodeReshape->BaseMeshPin())) { return FindMeshBaseSource(*ConnectedPin, bOnlyLookForStaticMesh, MacroContext); } } else if (const UCustomizableObjectNodeMeshMorph* TypedNodeMorph = Cast(Node)) { if (const UEdGraphPin* ConnectedPin = FollowInputPin(*TypedNodeMorph->MeshPin())) { return FindMeshBaseSource(*ConnectedPin, bOnlyLookForStaticMesh, MacroContext); } } else if (const UCustomizableObjectNodeMeshSwitch* TypedNodeSwitch = Cast(Node)) { if (const UEdGraphPin* EnumParameterPin = FollowInputPin(*TypedNodeSwitch->SwitchParameter())) { if (const UCustomizableObjectNodeEnumParameter* EnumNode = Cast(EnumParameterPin->GetOwningNode())) { if (const UEdGraphPin* DefaultPin = TypedNodeSwitch->GetElementPin(EnumNode->DefaultIndex)) { if (const UEdGraphPin* ConnectedPin = FollowInputPin(*DefaultPin)) { return FindMeshBaseSource(*ConnectedPin, bOnlyLookForStaticMesh, MacroContext); } } } } } else if (const UCustomizableObjectNodeMeshVariation* TypedNodeMeshVar = Cast(Node)) { if (const UEdGraphPin* ConnectedPin = FollowInputPin(*TypedNodeMeshVar->DefaultPin())) { return FindMeshBaseSource(*ConnectedPin, bOnlyLookForStaticMesh, MacroContext); } for (int32 i = 0; i < TypedNodeMeshVar->GetNumVariations(); ++i) { if (const UEdGraphPin* ConnectedPin = FollowInputPin(*TypedNodeMeshVar->VariationPin(i))) { return FindMeshBaseSource(*ConnectedPin, bOnlyLookForStaticMesh, MacroContext); } } } else if (const UCustomizableObjectNodeMaterialBase* TypedNodeMat = Cast(Node)) { if (const UEdGraphPin* ConnectedPin = FollowInputPin(*TypedNodeMat->GetMeshPin())) { return FindMeshBaseSource(*ConnectedPin, bOnlyLookForStaticMesh, MacroContext); } } else if (const UCustomizableObjectNodeMaterialVariation* TypedNodeMatVar = Cast(Node)) { if (const UEdGraphPin* ConnectedPin = FollowInputPin(*TypedNodeMatVar->DefaultPin())) { return FindMeshBaseSource(*ConnectedPin, bOnlyLookForStaticMesh, MacroContext); } } else if (const UCustomizableObjectNodeModifierExtendMeshSection* TypedNodeExtend = Cast(Node)) { if (const UEdGraphPin* ConnectedPin = FollowInputPin(*TypedNodeExtend->AddMeshPin())) { return FindMeshBaseSource(*ConnectedPin, bOnlyLookForStaticMesh, MacroContext); } } else if (const UCustomizableObjectNodeMeshMorphStackDefinition* TypedNodeMorphStackDef = Cast(Node)) { if (const UEdGraphPin* ConnectedPin = FollowInputPin(*TypedNodeMorphStackDef->GetMeshPin())) { return FindMeshBaseSource(*ConnectedPin, bOnlyLookForStaticMesh, MacroContext); } } else if (const UCustomizableObjectNodeMeshMorphStackApplication* TypedNodeMorphStackApp = Cast(Node)) { if (const UEdGraphPin* ConnectedPin = FollowInputPin(*TypedNodeMorphStackApp->GetMeshPin())) { return FindMeshBaseSource(*ConnectedPin, bOnlyLookForStaticMesh, MacroContext); } } else if (const UCustomizableObjectNodeMeshReshape* NodeMeshReshape = Cast(Node)) { if (const UEdGraphPin* ConnectedPin = FollowInputPin(*NodeMeshReshape->BaseMeshPin())) { return FindMeshBaseSource(*ConnectedPin, bOnlyLookForStaticMesh, MacroContext); } } else if (Cast(Node)) { if (!bOnlyLookForStaticMesh) { return &Pin; } } else if (const UCustomizableObjectNodeAnimationPose* NodeMeshPose = Cast(Node)) { if (const UEdGraphPin* ConnectedPin = FollowInputPin(*NodeMeshPose->GetInputMeshPin())) { return FindMeshBaseSource(*ConnectedPin, bOnlyLookForStaticMesh, MacroContext); } } else if (Cast(Node)) { if (!bOnlyLookForStaticMesh) { return &Pin; } } else if (const UCustomizableObjectNodeMacroInstance* NodeMacro = Cast(Node)) { const UEdGraphPin* ConnectedPin = GraphTraversal::FindIOPinSourceThroughMacroContext(Pin, MacroContext); if (ConnectedPin) { return FindMeshBaseSource(*ConnectedPin, bOnlyLookForStaticMesh, MacroContext); } } else if (const UCustomizableObjectNodeTunnel* NodeTunnel = Cast(Node)) { const UEdGraphPin* ConnectedPin = GraphTraversal::FindIOPinSourceThroughMacroContext(Pin, MacroContext); if (ConnectedPin) { return FindMeshBaseSource(*ConnectedPin, bOnlyLookForStaticMesh, MacroContext); } } else { unimplemented(); // Case missing. } return nullptr; } void GetNodeGroupObjectNodeMappingImmersive(UCustomizableObject* Object, IAssetRegistry& AssetRegistry, TSet& Visited, TMultiMap& Mapping) { Visited.Add(Object); TArray ArrayReferenceNames; AssetRegistry.GetReferencers(*Object->GetOuter()->GetPathName(), ArrayReferenceNames, UE::AssetRegistry::EDependencyCategory::Package, UE::AssetRegistry::EDependencyQuery::Hard); FARFilter Filter; Filter.bIncludeOnlyOnDiskAssets = false; for (const FName& ReferenceName : ArrayReferenceNames) { if (!ReferenceName.ToString().StartsWith(TEXT("/TempAutosave"))) { Filter.PackageNames.Add(ReferenceName); } } TArray ArrayAssetData; AssetRegistry.GetAssets(Filter, ArrayAssetData); for (FAssetData& AssetData : ArrayAssetData) { UCustomizableObject* ChildObject = Cast(MutablePrivate::LoadObject(AssetData)); if (!ChildObject) { continue; } if (ChildObject != Object && !ChildObject->HasAnyFlags(RF_Transient)) { if (UCustomizableObjectNodeObject* ChildRoot = GetRootNode(ChildObject)) { if (ChildRoot->ParentObject == Object) { Mapping.Add(ChildRoot->ParentObjectGroupId, ChildRoot); } } } if (!Visited.Contains(ChildObject)) { GetNodeGroupObjectNodeMappingImmersive(ChildObject, AssetRegistry, Visited, Mapping); } } } TMultiMap GetNodeGroupObjectNodeMapping(UCustomizableObject* Object) { MUTABLE_CPUPROFILER_SCOPE(GetNodeGroupObjectNodeMapping); IAssetRegistry& AssetRegistry = FModuleManager::LoadModuleChecked("AssetRegistry").Get(); TSet Visited; TMultiMap Mapping; GetNodeGroupObjectNodeMappingImmersive(Object, AssetRegistry, Visited, Mapping); return Mapping; } void GetAllObjectsInGraph(UCustomizableObject* Object, TSet& OutObjects) { if (!Object) { return; } // Search the root of the CO's graph UCustomizableObject* RootObject = GraphTraversal::GetRootObject(Object); TMultiMap DummyMap; IAssetRegistry& AssetRegistry = FModuleManager::LoadModuleChecked("AssetRegistry").Get(); GetNodeGroupObjectNodeMappingImmersive(RootObject, AssetRegistry, OutObjects, DummyMap); } namespace GraphTraversal { bool IsRootObject(const UCustomizableObject& Object) { const TObjectPtr Source = Object.GetPrivate()->GetSource(); if (!Source || !Source->Nodes.Num()) { // Conservative approach. return true; } TArray ObjectNodes; Source->GetNodesOfClass(ObjectNodes); // Look for the base object node const UCustomizableObjectNodeObject* Root = nullptr; for (TArray::TIterator It(ObjectNodes); It; ++It) { if ((*It)->bIsBase) { Root = *It; } } return Root && !Root->ParentObject; } } void NodePinConnectionListChanged(const TArray& Pins) { TMap> SortedPins; for (UEdGraphPin* Pin : Pins) { if (UEdGraphNode* Node = Pin->GetOwningNodeUnchecked()) { SortedPins.FindOrAdd(Node).Add(Pin); } } for (TTuple> Pair : SortedPins) { for (UEdGraphPin* ConnectedPin : Pair.Value) { Pair.Key->PinConnectionListChanged(ConnectedPin); } Pair.Key->NodeConnectionListChanged(); } }