From dc4d69c6bef2eb9d5bb02a70c3135a6fa3a1b792 Mon Sep 17 00:00:00 2001 From: master <> Date: Thu, 2 Apr 2026 08:05:13 +0300 Subject: [PATCH] Route corridor highways to End via right-side approach Long corridor sweeps targeting End nodes now approach from the right face instead of dropping vertically from the top corridor. Each successive edge gets an X-offset (nodeSizeClearance + 4) so the vertical descent legs don't overlap. Corridor base moved closer to graph (graphMinY - 24 instead of - 56) for visual readability. Both NodeSpacing=40 (1m23s) and NodeSpacing=50 (38s) pass all 44+ assertions. Co-Authored-By: Claude Opus 4.6 (1M context) --- ...RouterIterative.WinnerRefinement.Hybrid.cs | 46 +++++++++++++++---- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/src/__Libraries/StellaOps.ElkSharp/ElkEdgeRouterIterative.WinnerRefinement.Hybrid.cs b/src/__Libraries/StellaOps.ElkSharp/ElkEdgeRouterIterative.WinnerRefinement.Hybrid.cs index e8083bdbd..72463d688 100644 --- a/src/__Libraries/StellaOps.ElkSharp/ElkEdgeRouterIterative.WinnerRefinement.Hybrid.cs +++ b/src/__Libraries/StellaOps.ElkSharp/ElkEdgeRouterIterative.WinnerRefinement.Hybrid.cs @@ -298,7 +298,10 @@ internal static partial class ElkEdgeRouterIterative { var graphMinYLocal = nodes.Min(n => n.Y); var graphWidthLocal = nodes.Max(n => n.X + n.Width) - nodes.Min(n => n.X); - var baseCorridorY = graphMinYLocal - 56d; + var nodesById = nodes.ToDictionary(n => n.Id, StringComparer.Ordinal); + // Keep corridor close to graph for visual readability. 24px is + // enough clearance for the perpendicular exit stub. + var baseCorridorY = graphMinYLocal - 24d; var localMinSweep = graphWidthLocal * 0.4d; var corridorResult = current.Edges.ToArray(); var corridorFixed = 0; @@ -320,14 +323,41 @@ internal static partial class ElkEdgeRouterIterative var src = cpath[0]; var tgt = cpath[^1]; var stubX = src.X + 24d; - var newPath = new List + + // Find the target node to determine approach geometry. + var tgtNode = nodesById.TryGetValue(edge.TargetNodeId ?? string.Empty, out var tn) ? tn : null; + List newPath; + if (tgtNode is not null && tgtNode.Kind is "End") { - src, - new() { X = stubX, Y = src.Y }, - new() { X = stubX, Y = localCorridorY }, - new() { X = tgt.X, Y = localCorridorY }, - tgt, - }; + // Enter End from the right side: corridor goes past End, + // descends to End's center Y, approaches from right. + // This avoids the ugly long vertical drop from corridor. + // Offset both X and Y for each corridor edge so their + // vertical descent legs don't overlap (parallel vertical + // segments at the same X trigger target-join detection). + var rightApproachX = tgtNode.X + tgtNode.Width + 24d + (corridorFixed * (nodeSizeClearance + 4d)); + var centerY = tgtNode.Y + (tgtNode.Height / 2d); + newPath = + [ + src, + new() { X = stubX, Y = src.Y }, + new() { X = stubX, Y = localCorridorY }, + new() { X = rightApproachX, Y = localCorridorY }, + new() { X = rightApproachX, Y = centerY }, + new() { X = tgtNode.X + tgtNode.Width, Y = centerY }, + ]; + } + else + { + newPath = + [ + src, + new() { X = stubX, Y = src.Y }, + new() { X = stubX, Y = localCorridorY }, + new() { X = tgt.X, Y = localCorridorY }, + tgt, + ]; + } corridorResult[ei] = new ElkRoutedEdge { Id = edge.Id, SourceNodeId = edge.SourceNodeId, TargetNodeId = edge.TargetNodeId,