namespace StellaOps.ElkSharp; internal static partial class ElkEdgePostProcessor { internal static bool IsRepeatCollectorLabel(string? label) { if (string.IsNullOrWhiteSpace(label)) { return false; } var normalized = label.Trim().ToLowerInvariant(); return normalized.StartsWith("repeat ", StringComparison.Ordinal) || normalized.Equals("body", StringComparison.Ordinal); } private static bool ShouldPreserveSourceExitGeometry( ElkRoutedEdge edge, double graphMinY, double graphMaxY) { if (HasProtectedUnderNodeGeometry(edge)) { return true; } if (!HasCorridorBendPoints(edge, graphMinY, graphMaxY)) { return false; } return IsRepeatCollectorLabel(edge.Label) || (!string.IsNullOrWhiteSpace(edge.Kind) && edge.Kind.StartsWith("backward|", StringComparison.OrdinalIgnoreCase)); } internal static bool IsCorridorSegment(ElkPoint p1, ElkPoint p2, double graphMinY, double graphMaxY) { return p1.Y < graphMinY - 8d || p1.Y > graphMaxY + 8d || p2.Y < graphMinY - 8d || p2.Y > graphMaxY + 8d; } internal static bool HasCorridorBendPoints(ElkRoutedEdge edge, double graphMinY, double graphMaxY) { foreach (var section in edge.Sections) { foreach (var bp in section.BendPoints) { if (bp.Y < graphMinY - 8d || bp.Y > graphMaxY + 8d) { return true; } } } return false; } internal static bool SegmentCrossesObstacle( ElkPoint p1, ElkPoint p2, (double Left, double Top, double Right, double Bottom, string Id)[] obstacles, string sourceId, string targetId) { var isH = Math.Abs(p1.Y - p2.Y) < 2d; var isV = Math.Abs(p1.X - p2.X) < 2d; foreach (var ob in obstacles) { if (ob.Id == sourceId || ob.Id == targetId) continue; if (isH && p1.Y > ob.Top && p1.Y < ob.Bottom) { var minX = Math.Min(p1.X, p2.X); var maxX = Math.Max(p1.X, p2.X); if (maxX > ob.Left && minX < ob.Right) return true; } else if (isV && p1.X > ob.Left && p1.X < ob.Right) { var minY = Math.Min(p1.Y, p2.Y); var maxY = Math.Max(p1.Y, p2.Y); if (maxY > ob.Top && minY < ob.Bottom) return true; } else if (!isH && !isV) { // Diagonal segment: check actual intersection with obstacle rectangle if (ElkEdgeRoutingGeometry.SegmentsIntersect(p1, p2, new ElkPoint { X = ob.Left, Y = ob.Top }, new ElkPoint { X = ob.Right, Y = ob.Top }) || ElkEdgeRoutingGeometry.SegmentsIntersect(p1, p2, new ElkPoint { X = ob.Right, Y = ob.Top }, new ElkPoint { X = ob.Right, Y = ob.Bottom }) || ElkEdgeRoutingGeometry.SegmentsIntersect(p1, p2, new ElkPoint { X = ob.Right, Y = ob.Bottom }, new ElkPoint { X = ob.Left, Y = ob.Bottom }) || ElkEdgeRoutingGeometry.SegmentsIntersect(p1, p2, new ElkPoint { X = ob.Left, Y = ob.Bottom }, new ElkPoint { X = ob.Left, Y = ob.Top })) { return true; } } } return false; } private static bool HasClearSourceExitSegment( IReadOnlyList path, IReadOnlyCollection nodes, string? sourceNodeId, string? targetNodeId) { return HasClearBoundarySegments(path, nodes, sourceNodeId, targetNodeId, true, 2); } private static string ResolvePreferredRectSourceExitSide( IReadOnlyList path, ElkPositionedNode sourceNode) { var currentSide = ElkEdgeRoutingGeometry.ResolveBoundarySide(path[0], sourceNode); if (path.Count < 2) { return currentSide; } var overallDeltaX = path[^1].X - path[0].X; var overallDeltaY = path[^1].Y - path[0].Y; var overallAbsDx = Math.Abs(overallDeltaX); var overallAbsDy = Math.Abs(overallDeltaY); var sameRowThreshold = Math.Max(24d, sourceNode.Height / 3d); var sameColumnThreshold = Math.Max(24d, sourceNode.Width / 3d); if (overallAbsDx >= overallAbsDy * 1.15d && overallAbsDy <= sameRowThreshold && Math.Sign(overallDeltaX) != 0) { return overallDeltaX > 0d ? "right" : "left"; } if (overallAbsDy >= overallAbsDx * 1.15d && overallAbsDx <= sameColumnThreshold && Math.Sign(overallDeltaY) != 0) { return overallDeltaY > 0d ? "bottom" : "top"; } if (HasValidBoundaryAngle(path[0], path[1], sourceNode)) { return currentSide; } var deltaX = path[1].X - path[0].X; var deltaY = path[1].Y - path[0].Y; var absDx = Math.Abs(deltaX); var absDy = Math.Abs(deltaY); if (absDx >= absDy * 1.15d && Math.Sign(deltaX) != 0) { return deltaX > 0d ? "right" : "left"; } if (absDy >= absDx * 1.15d && Math.Sign(deltaY) != 0) { return deltaY > 0d ? "bottom" : "top"; } return currentSide; } private static string ResolvePreferredRectTargetEntrySide( IReadOnlyList path, ElkPositionedNode targetNode) { var currentSide = ElkEdgeRoutingGeometry.ResolveBoundarySide(path[^1], targetNode); if (path.Count < 2) { return currentSide; } var overallDeltaX = path[^1].X - path[0].X; var overallDeltaY = path[^1].Y - path[0].Y; var overallAbsDx = Math.Abs(overallDeltaX); var overallAbsDy = Math.Abs(overallDeltaY); var sameRowThreshold = Math.Max(24d, targetNode.Height / 3d); var sameColumnThreshold = Math.Max(24d, targetNode.Width / 3d); if (overallAbsDx >= overallAbsDy * 1.15d && overallAbsDy <= sameRowThreshold && Math.Sign(overallDeltaX) != 0) { return overallDeltaX > 0d ? "left" : "right"; } if (overallAbsDy >= overallAbsDx * 1.15d && overallAbsDx <= sameColumnThreshold && Math.Sign(overallDeltaY) != 0) { return overallDeltaY > 0d ? "top" : "bottom"; } if (HasValidBoundaryAngle(path[^1], path[^2], targetNode)) { return currentSide; } var deltaX = path[^1].X - path[^2].X; var deltaY = path[^1].Y - path[^2].Y; var absDx = Math.Abs(deltaX); var absDy = Math.Abs(deltaY); if (absDx >= absDy * 1.15d && Math.Sign(deltaX) != 0) { return deltaX > 0d ? "left" : "right"; } if (absDy >= absDx * 1.15d && Math.Sign(deltaY) != 0) { return deltaY > 0d ? "top" : "bottom"; } return currentSide; } private static ElkPoint BuildRectBoundaryPointForSide( ElkPositionedNode node, string side, ElkPoint referencePoint) { var insetX = Math.Min(24d, Math.Max(8d, node.Width / 4d)); var insetY = Math.Min(24d, Math.Max(8d, node.Height / 4d)); return side switch { "left" => new ElkPoint { X = node.X, Y = Math.Clamp(referencePoint.Y, node.Y + insetY, (node.Y + node.Height) - insetY), }, "right" => new ElkPoint { X = node.X + node.Width, Y = Math.Clamp(referencePoint.Y, node.Y + insetY, (node.Y + node.Height) - insetY), }, "top" => new ElkPoint { X = Math.Clamp(referencePoint.X, node.X + insetX, (node.X + node.Width) - insetX), Y = node.Y, }, "bottom" => new ElkPoint { X = Math.Clamp(referencePoint.X, node.X + insetX, (node.X + node.Width) - insetX), Y = node.Y + node.Height, }, _ => ElkShapeBoundaries.ProjectOntoShapeBoundary(node, referencePoint), }; } }