//$ Copyright 2015-22, Code Respawn Technologies Pvt Ltd - All Rights Reserved $// using System.Collections.Generic; using UnityEngine; namespace DungeonArchitect.Graphs.Layouts.Layered { class LayoutTreeNode { public GraphLayoutNode GraphNode; public float X; public int Depth; public float Mod; public LayoutTreeNode Parent; public List> Children = new List>(); } class LayoutTree { public LayoutTreeNode root; public List> nodes = new List>(); } [System.Serializable] public class GraphLayoutLayeredConfig { [SerializeField] public Vector2 separation = new Vector2(130, 100); } public class GraphLayoutLayered : GraphLayoutBase { GraphLayoutLayeredConfig config; public GraphLayoutLayered(GraphLayoutLayeredConfig config) { this.config = config; } LayoutTreeNode BuildTreeNode(LayoutTree tree, LayoutTreeNode parent, GraphLayoutNode graphNode, HashSet> visited) { visited.Add(graphNode); var treeNode = new LayoutTreeNode(); treeNode.GraphNode = graphNode; treeNode.Parent = parent; tree.nodes.Add(treeNode); foreach (var outgoingGraphNode in graphNode.Outgoing) { if (!visited.Contains(outgoingGraphNode)) { var childTreeNode = BuildTreeNode(tree, treeNode, outgoingGraphNode, visited); treeNode.Children.Add(childTreeNode); } } return treeNode; } LayoutTree BuildTree(GraphLayoutNode[] nodes) { var tree = new LayoutTree(); // Find the root node if (nodes.Length == 0) { return tree; } var startNode = nodes[0]; { var visited = new HashSet>(); while (startNode.Incoming.Count > 0) { if (visited.Contains(startNode)) { break; } visited.Add(startNode); startNode = startNode.Incoming[0]; } } { var visited = new HashSet>(); tree.root = BuildTreeNode(tree, null, startNode, visited); } return tree; } void TagNodeLevels(LayoutTreeNode node, int depth) { node.Depth = depth; foreach (var child in node.Children) { TagNodeLevels(child, depth + 1); } } void CalculateInitialX(LayoutTreeNode Node, LayoutTreeNode LeftSibling) { LayoutTreeNode LeftChild = null; foreach (LayoutTreeNode Child in Node.Children) { CalculateInitialX(Child, LeftChild); LeftChild = Child; } bool bIsLeftMost = LeftSibling == null; bool bIsLeaf = (Node.Children.Count == 0); if (bIsLeaf) { if (bIsLeftMost) { Node.X = 0; } else { Node.X = LeftSibling.X + 1; } } else if (Node.Children.Count == 1) { if (bIsLeftMost) { Node.X = Node.Children[0].X; } else { Node.X = LeftSibling.X + 1; Node.Mod = Node.X - Node.Children[0].X; } } else { float LeftX = Node.Children[0].X; float RightX = Node.Children[Node.Children.Count - 1].X; float MidX = (LeftX + RightX) / 2.0f; if (bIsLeftMost) { Node.X = MidX; } else { Node.X = LeftSibling.X + 1; Node.Mod = Node.X - MidX; } } if (!bIsLeaf && !bIsLeftMost) { ResolveConflicts(Node); } } void ResolveConflicts(LayoutTreeNode Node) { float ShiftValue = 0.0f; float MinDistance = 1.0f; var NodeContour = new Dictionary(); GetLeftContour(Node, 0, NodeContour); var NodeLevels = new List(NodeContour.Keys); NodeLevels.Sort(); LayoutTreeNode Sibling = GetLeftMostSibling(Node); while (Sibling != null && Sibling != Node) { var SiblingContour = new Dictionary(); GetRightContour(Sibling, 0, SiblingContour); var SiblingLevels = new List(SiblingContour.Keys); SiblingLevels.Sort(); int MaxNodeLevel = NodeLevels[NodeLevels.Count - 1]; int MaxSiblingLevel = SiblingLevels[SiblingLevels.Count - 1]; int StartLevel = Node.Depth + 1; int EndLevel = Mathf.Min(MaxNodeLevel, MaxSiblingLevel); for (int Level = StartLevel; Level <= EndLevel; Level++) { float Distance = NodeContour[Level] - SiblingContour[Level]; if (Distance + ShiftValue < MinDistance) { ShiftValue = MinDistance - Distance; } } if (ShiftValue > 0) { Node.X += ShiftValue; Node.Mod += ShiftValue; ShiftValue = 0; } Sibling = GetNextSibling(Sibling); } } void GetLeftContour(LayoutTreeNode Node, float ModSum, Dictionary ContourMap) { if (!ContourMap.ContainsKey(Node.Depth)) { ContourMap.Add(Node.Depth, Node.X + ModSum); } else { ContourMap[Node.Depth] = Mathf.Min(ContourMap[Node.Depth], Node.X + ModSum); } foreach (var Child in Node.Children) { GetLeftContour(Child, ModSum + Node.Mod, ContourMap); } } void GetRightContour(LayoutTreeNode Node, float ModSum, Dictionary ContourMap) { if (!ContourMap.ContainsKey(Node.Depth)) { ContourMap.Add(Node.Depth, Node.X + ModSum); } else { ContourMap[Node.Depth] = Mathf.Max(ContourMap[Node.Depth], Node.X + ModSum); } foreach (var Child in Node.Children) { GetRightContour(Child, ModSum + Node.Mod, ContourMap); } } LayoutTreeNode GetLeftMostSibling(LayoutTreeNode Node) { if (Node == null || Node.Parent == null) { return null; } return Node.Parent.Children[0]; } LayoutTreeNode GetNextSibling(LayoutTreeNode Node) { if (Node == null || Node.Parent == null) { return null; } int NodeIdx = Node.Parent.Children.IndexOf(Node); if (NodeIdx == -1 || NodeIdx == Node.Parent.Children.Count - 1) { return null; } return Node.Parent.Children[NodeIdx + 1]; } void CalculateFinalX(LayoutTreeNode Node, float TotalMod) { Node.X += TotalMod; foreach (var Child in Node.Children) { CalculateFinalX(Child, TotalMod + Node.Mod); } } protected override void LayoutImpl(GraphLayoutNode[] nodes) { var tree = BuildTree(nodes); TagNodeLevels(tree.root, 0); CalculateInitialX(tree.root, null); CalculateFinalX(tree.root, 0); float depthDistance = config.separation.x; float siblingDistance = config.separation.y; foreach (var node in tree.nodes) { var position = new Vector2( depthDistance * node.Depth, siblingDistance * node.X); node.GraphNode.Position = position; } } } }