diff --git a/FModel/ViewModels/AnimGraphViewModel.cs b/FModel/ViewModels/AnimGraphViewModel.cs index c0a1d50b..e4e8cda1 100644 --- a/FModel/ViewModels/AnimGraphViewModel.cs +++ b/FModel/ViewModels/AnimGraphViewModel.cs @@ -166,11 +166,11 @@ public class AnimGraphViewModel } /// - /// Groups nodes into layers based on root nodes. In Unreal Engine, each - /// animation blueprint layer has a unique AnimGraphNode_Root, and each - /// state machine state sub-graph has a unique AnimGraphNode_StateResult. - /// Starting from each root node, we trace upstream through directed - /// connections to collect all nodes that feed into it. + /// Groups nodes into layers based on root nodes. In Unreal Engine: + /// - Each animation blueprint layer has a unique AnimGraphNode_Root + /// - Each state machine state sub-graph has a unique AnimGraphNode_StateResult + /// These two types are processed in separate passes to keep graph layers + /// and state machine state sub-graphs independent. /// private static void BuildLayers(AnimGraphViewModel vm) { @@ -185,43 +185,36 @@ public class AnimGraphViewModel foreach (var conn in vm.Connections) upstreamOf[conn.TargetNode].Add(conn.SourceNode); - // Find all root nodes that define layers: - // _Root nodes define animation blueprint layers - // _StateResult nodes define state machine state sub-graphs - var rootNodes = vm.Nodes - .Where(n => n.ExportType.EndsWith("_Root", StringComparison.OrdinalIgnoreCase) || - n.ExportType.EndsWith("_StateResult", StringComparison.OrdinalIgnoreCase)) - .ToList(); - var assigned = new HashSet(); var layerIndex = 0; - // For each root node, BFS upstream to find all nodes in the layer - foreach (var rootNode in rootNodes) + // Pass 1: Build graph layers from AnimGraphNode_Root nodes. + // Each _Root node defines an animation blueprint layer (e.g. "AnimGraph"). + var graphRoots = vm.Nodes + .Where(n => n.ExportType.EndsWith("_Root", StringComparison.OrdinalIgnoreCase)) + .ToList(); + + foreach (var rootNode in graphRoots) { if (!assigned.Add(rootNode)) continue; - - var layerNodes = new List { rootNode }; - var queue = new Queue(); - queue.Enqueue(rootNode); - - while (queue.Count > 0) - { - var current = queue.Dequeue(); - foreach (var upstream in upstreamOf[current]) - { - if (assigned.Add(upstream)) - { - layerNodes.Add(upstream); - queue.Enqueue(upstream); - } - } - } - + var layerNodes = CollectUpstream(rootNode, upstreamOf, assigned); AddLayer(vm, layerNodes, layerIndex++); } - // Any remaining unassigned nodes go into fallback layers (connected components) + // Pass 2: Build state machine state sub-graphs from AnimGraphNode_StateResult nodes. + // Each _StateResult node defines a state's sub-graph within a state machine. + var stateResultRoots = vm.Nodes + .Where(n => n.ExportType.EndsWith("_StateResult", StringComparison.OrdinalIgnoreCase)) + .ToList(); + + foreach (var stateResultNode in stateResultRoots) + { + if (!assigned.Add(stateResultNode)) continue; + var layerNodes = CollectUpstream(stateResultNode, upstreamOf, assigned); + AddLayer(vm, layerNodes, layerIndex++); + } + + // Fallback: any remaining unassigned nodes go into connected-component layers var remaining = vm.Nodes.Where(n => !assigned.Contains(n)).ToList(); if (remaining.Count == 0) return; @@ -263,6 +256,34 @@ public class AnimGraphViewModel } } + /// + /// Collects a root node and all its upstream providers via BFS. + /// + private static List CollectUpstream( + AnimGraphNode root, + Dictionary> upstreamOf, + HashSet assigned) + { + var nodes = new List { root }; + var queue = new Queue(); + queue.Enqueue(root); + + while (queue.Count > 0) + { + var current = queue.Dequeue(); + foreach (var upstream in upstreamOf[current]) + { + if (assigned.Add(upstream)) + { + nodes.Add(upstream); + queue.Enqueue(upstream); + } + } + } + + return nodes; + } + /// /// Creates an from a set of nodes, assigns /// the relevant connections, lays out the nodes and adds the layer to the VM.