using System.Collections.Concurrent; using System.Diagnostics; using System.Globalization; namespace StellaOps.ElkSharp; internal static partial class ElkEdgeRouterIterative { private static bool CanRepairEdgeLocally( ElkRoutedEdge edge, IReadOnlyCollection nodes, double graphMinY, double graphMaxY) { if (ShouldRouteEdge(edge, graphMinY, graphMaxY)) { return true; } if (!string.IsNullOrWhiteSpace(edge.SourcePortId) || !string.IsNullOrWhiteSpace(edge.TargetPortId)) { return false; } if (!string.IsNullOrWhiteSpace(edge.Kind) && edge.Kind.StartsWith("backward|", StringComparison.OrdinalIgnoreCase)) { return false; } if (ElkEdgePostProcessor.IsRepeatCollectorLabel(edge.Label)) { return false; } return ElkEdgePostProcessor.HasCorridorBendPoints(edge, graphMinY, graphMaxY) && HasClearOrthogonalShortcut(edge, nodes); } private static bool CanRouteSelectedRepairEdge( ElkRoutedEdge edge, double graphMinY, double graphMaxY, IReadOnlySet routeRepairEdgeIds) { if (ShouldRouteEdge(edge, graphMinY, graphMaxY)) { return true; } if (!routeRepairEdgeIds.Contains(edge.Id)) { return false; } if (!string.IsNullOrWhiteSpace(edge.SourcePortId) || !string.IsNullOrWhiteSpace(edge.TargetPortId)) { return false; } if (!string.IsNullOrWhiteSpace(edge.Kind) && edge.Kind.StartsWith("backward|", StringComparison.OrdinalIgnoreCase)) { return false; } return ElkEdgePostProcessor.IsRepeatCollectorLabel(edge.Label) && !ElkEdgePostProcessor.HasCorridorBendPoints(edge, graphMinY, graphMaxY); } private static bool HasClearOrthogonalShortcut( ElkRoutedEdge edge, IReadOnlyCollection nodes) { var firstSection = edge.Sections.FirstOrDefault(); var lastSection = edge.Sections.LastOrDefault(); if (firstSection is null || lastSection is null) { return false; } var start = firstSection.StartPoint; var end = lastSection.EndPoint; var obstacles = nodes.Select(node => ( Left: node.X, Top: node.Y, Right: node.X + node.Width, Bottom: node.Y + node.Height, Id: node.Id)).ToArray(); bool SegmentIsClear(ElkPoint from, ElkPoint to) => !ElkEdgePostProcessor.SegmentCrossesObstacle( from, to, obstacles, edge.SourceNodeId, edge.TargetNodeId); if (Math.Abs(start.X - end.X) < 2d || Math.Abs(start.Y - end.Y) < 2d) { return SegmentIsClear(start, end); } var horizontalThenVertical = new ElkPoint { X = end.X, Y = start.Y }; if (SegmentIsClear(start, horizontalThenVertical) && SegmentIsClear(horizontalThenVertical, end)) { return true; } var verticalThenHorizontal = new ElkPoint { X = start.X, Y = end.Y }; return SegmentIsClear(start, verticalThenHorizontal) && SegmentIsClear(verticalThenHorizontal, end); } private static int CountRepeatCollectorNodeClearanceViolations( IReadOnlyCollection edges, IReadOnlyCollection nodes, Dictionary? severityByEdgeId, int severityWeight = 1) { if (edges.Count == 0 || nodes.Count == 0) { return 0; } var serviceNodes = nodes.Where(node => node.Kind is not "Start" and not "End").ToArray(); var minClearance = serviceNodes.Length > 0 ? Math.Min(serviceNodes.Average(node => node.Width), serviceNodes.Average(node => node.Height)) / 2d : 50d; var graphMinY = nodes.Min(node => node.Y); var graphMaxY = nodes.Max(node => node.Y + node.Height); var count = 0; foreach (var edge in edges) { if (!ElkEdgePostProcessor.IsRepeatCollectorLabel(edge.Label)) { continue; } var edgeViolations = 0; foreach (var segment in ElkEdgeRoutingGeometry.FlattenSegments(edge)) { var horizontal = Math.Abs(segment.Start.Y - segment.End.Y) < 2d; var vertical = Math.Abs(segment.Start.X - segment.End.X) < 2d; if (!horizontal && !vertical) { continue; } foreach (var node in nodes) { if (node.Id == edge.SourceNodeId || node.Id == edge.TargetNodeId) { continue; } if (horizontal) { var overlapX = Math.Max(segment.Start.X, segment.End.X) > node.X && Math.Min(segment.Start.X, segment.End.X) < node.X + node.Width; if (!overlapX) { continue; } var distance = Math.Min( Math.Abs(segment.Start.Y - node.Y), Math.Abs(segment.Start.Y - (node.Y + node.Height))); if (distance > 0.5d && distance < minClearance) { edgeViolations++; } } else { var overlapY = Math.Max(segment.Start.Y, segment.End.Y) > node.Y && Math.Min(segment.Start.Y, segment.End.Y) < node.Y + node.Height; if (!overlapY) { continue; } var distance = Math.Min( Math.Abs(segment.Start.X - node.X), Math.Abs(segment.Start.X - (node.X + node.Width))); if (distance > 0.5d && distance < minClearance) { edgeViolations++; } } } } if (edgeViolations <= 0) { continue; } count += edgeViolations; if (severityByEdgeId is not null) { severityByEdgeId[edge.Id] = severityByEdgeId.GetValueOrDefault(edge.Id) + (edgeViolations * severityWeight); } } return count; } private static (ElkPoint StartPoint, ElkPoint EndPoint) ResolveRoutingEndpoints( ElkPoint startPoint, ElkPoint endPoint, string? sourceNodeId, string? targetNodeId, IReadOnlyDictionary nodesById) { var adjustedStart = startPoint; if (nodesById.TryGetValue(sourceNodeId ?? string.Empty, out var sourceNode) && ElkShapeBoundaries.IsGatewayShape(sourceNode)) { adjustedStart = ResolveGatewayRoutingDeparturePoint(sourceNode, endPoint, startPoint); } var adjustedEnd = endPoint; if (nodesById.TryGetValue(targetNodeId ?? string.Empty, out var targetNode) && ElkShapeBoundaries.IsGatewayShape(targetNode)) { adjustedEnd = ResolveGatewayRoutingApproachPoint(targetNode, adjustedStart, endPoint); } return (adjustedStart, adjustedEnd); } private static ElkPoint ResolveGatewayRoutingBoundary( ElkPositionedNode node, ElkPoint referencePoint) { var boundary = ElkShapeBoundaries.ProjectOntoShapeBoundary(node, referencePoint); return ElkShapeBoundaries.PreferGatewayEdgeInteriorBoundary(node, boundary, referencePoint); } private static ElkPoint ResolveGatewayRoutingDeparturePoint( ElkPositionedNode node, ElkPoint referencePoint, ElkPoint preferredBoundary) { var boundary = ElkShapeBoundaries.IsGatewayBoundaryPoint(node, preferredBoundary) ? ElkShapeBoundaries.PreferGatewayEdgeInteriorBoundary(node, preferredBoundary, referencePoint) : ResolveGatewayRoutingBoundary(node, referencePoint); var exteriorDeparture = ElkShapeBoundaries.BuildGatewayDirectionalExteriorPoint(node, boundary, referencePoint); return ElkShapeBoundaries.IsInsideNodeBoundingBoxInterior(node, exteriorDeparture) ? boundary : exteriorDeparture; } private static ElkPoint ResolveGatewayRoutingApproachPoint( ElkPositionedNode node, ElkPoint referencePoint, ElkPoint preferredBoundary) { var boundary = ElkShapeBoundaries.IsGatewayBoundaryPoint(node, preferredBoundary) ? ElkShapeBoundaries.PreferGatewayEdgeInteriorBoundary(node, preferredBoundary, referencePoint) : ResolveGatewayRoutingBoundary(node, referencePoint); var exteriorApproach = ElkShapeBoundaries.BuildGatewayDirectionalExteriorPoint(node, boundary, referencePoint); return ElkShapeBoundaries.IsInsideNodeBoundingBoxInterior(node, exteriorApproach) ? boundary : exteriorApproach; } }