mirror of
https://github.com/4sval/FModel.git
synced 2026-04-21 17:17:49 -05:00
Rewrite BuildLayers to use root-node-based layer grouping
Instead of using connected components (undirected BFS), BuildLayers now groups nodes by their defining root nodes. Each AnimGraphNode_Root defines an animation blueprint layer and each AnimGraphNode_StateResult defines a state machine state sub-graph. For each root, we trace upstream through directed connections to collect all nodes that feed into it. Remaining unassigned nodes fall back to connected-component grouping. Extracted AddLayer helper to reduce duplication. Co-authored-by: LoogLong <86428208+LoogLong@users.noreply.github.com>
This commit is contained in:
parent
b29f93fbf3
commit
7b6f9aac79
|
|
@ -166,78 +166,123 @@ public class AnimGraphViewModel
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Groups nodes into layers by finding connected components in the graph.
|
||||
/// Each connected component becomes a separate layer/tab, named after
|
||||
/// its most prominent node (Root, StateMachine, etc.).
|
||||
/// 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.
|
||||
/// </summary>
|
||||
private static void BuildLayers(AnimGraphViewModel vm)
|
||||
{
|
||||
if (vm.Nodes.Count == 0) return;
|
||||
|
||||
// Build adjacency sets (undirected) for connected component detection
|
||||
var adjacency = new Dictionary<AnimGraphNode, HashSet<AnimGraphNode>>();
|
||||
// Build upstream map: for each node, which nodes feed into it
|
||||
// Connection direction: SourceNode (provider) → TargetNode (consumer)
|
||||
var upstreamOf = new Dictionary<AnimGraphNode, List<AnimGraphNode>>();
|
||||
foreach (var node in vm.Nodes)
|
||||
adjacency[node] = [];
|
||||
upstreamOf[node] = [];
|
||||
|
||||
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<AnimGraphNode>();
|
||||
var layerIndex = 0;
|
||||
|
||||
// For each root node, BFS upstream to find all nodes in the layer
|
||||
foreach (var rootNode in rootNodes)
|
||||
{
|
||||
adjacency[conn.SourceNode].Add(conn.TargetNode);
|
||||
adjacency[conn.TargetNode].Add(conn.SourceNode);
|
||||
}
|
||||
if (!assigned.Add(rootNode)) continue;
|
||||
|
||||
// Find connected components via BFS
|
||||
var visited = new HashSet<AnimGraphNode>();
|
||||
var components = new List<List<AnimGraphNode>>();
|
||||
|
||||
foreach (var node in vm.Nodes)
|
||||
{
|
||||
if (visited.Contains(node)) continue;
|
||||
|
||||
var component = new List<AnimGraphNode>();
|
||||
var layerNodes = new List<AnimGraphNode> { rootNode };
|
||||
var queue = new Queue<AnimGraphNode>();
|
||||
queue.Enqueue(node);
|
||||
visited.Add(node);
|
||||
queue.Enqueue(rootNode);
|
||||
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
var current = queue.Dequeue();
|
||||
component.Add(current);
|
||||
|
||||
foreach (var neighbor in adjacency[current])
|
||||
foreach (var upstream in upstreamOf[current])
|
||||
{
|
||||
if (visited.Add(neighbor))
|
||||
queue.Enqueue(neighbor);
|
||||
if (assigned.Add(upstream))
|
||||
{
|
||||
layerNodes.Add(upstream);
|
||||
queue.Enqueue(upstream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
components.Add(component);
|
||||
AddLayer(vm, layerNodes, layerIndex++);
|
||||
}
|
||||
|
||||
// Create a layer for each connected component
|
||||
var layerIndex = 0;
|
||||
foreach (var component in components)
|
||||
// Any remaining unassigned nodes go into fallback layers (connected components)
|
||||
var remaining = vm.Nodes.Where(n => !assigned.Contains(n)).ToList();
|
||||
if (remaining.Count == 0) return;
|
||||
|
||||
var adjacency = new Dictionary<AnimGraphNode, HashSet<AnimGraphNode>>();
|
||||
foreach (var node in remaining)
|
||||
adjacency[node] = [];
|
||||
|
||||
foreach (var conn in vm.Connections)
|
||||
{
|
||||
var componentSet = new HashSet<AnimGraphNode>(component);
|
||||
var layerName = GetLayerName(component, layerIndex);
|
||||
|
||||
var layer = new AnimGraphLayer { Name = layerName };
|
||||
layer.Nodes.AddRange(component);
|
||||
|
||||
// Add only the connections that belong to this component
|
||||
foreach (var conn in vm.Connections)
|
||||
if (adjacency.ContainsKey(conn.SourceNode) && adjacency.ContainsKey(conn.TargetNode))
|
||||
{
|
||||
if (componentSet.Contains(conn.SourceNode) && componentSet.Contains(conn.TargetNode))
|
||||
layer.Connections.Add(conn);
|
||||
adjacency[conn.SourceNode].Add(conn.TargetNode);
|
||||
adjacency[conn.TargetNode].Add(conn.SourceNode);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var node in remaining)
|
||||
{
|
||||
if (!assigned.Add(node)) continue;
|
||||
|
||||
var component = new List<AnimGraphNode> { node };
|
||||
var queue = new Queue<AnimGraphNode>();
|
||||
queue.Enqueue(node);
|
||||
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
var current = queue.Dequeue();
|
||||
foreach (var neighbor in adjacency[current])
|
||||
{
|
||||
if (assigned.Add(neighbor))
|
||||
{
|
||||
component.Add(neighbor);
|
||||
queue.Enqueue(neighbor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Layout nodes within this layer in a grid
|
||||
LayoutLayerNodes(layer);
|
||||
|
||||
vm.Layers.Add(layer);
|
||||
layerIndex++;
|
||||
AddLayer(vm, component, layerIndex++);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="AnimGraphLayer"/> from a set of nodes, assigns
|
||||
/// the relevant connections, lays out the nodes and adds the layer to the VM.
|
||||
/// </summary>
|
||||
private static void AddLayer(AnimGraphViewModel vm, List<AnimGraphNode> nodes, int index)
|
||||
{
|
||||
var nodeSet = new HashSet<AnimGraphNode>(nodes);
|
||||
var layer = new AnimGraphLayer { Name = GetLayerName(nodes, index) };
|
||||
layer.Nodes.AddRange(nodes);
|
||||
|
||||
foreach (var conn in vm.Connections)
|
||||
{
|
||||
if (nodeSet.Contains(conn.SourceNode) && nodeSet.Contains(conn.TargetNode))
|
||||
layer.Connections.Add(conn);
|
||||
}
|
||||
|
||||
LayoutLayerNodes(layer);
|
||||
vm.Layers.Add(layer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renames state machine internal layers with a parent path prefix
|
||||
/// (e.g., "AnimGraph > Locomotion" for the overview, or
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user