using System.Collections.Concurrent; using System.Diagnostics; using System.Globalization; namespace StellaOps.ElkSharp; internal static partial class ElkEdgeRouterIterative { private static RouteAllEdgesResult? RouteAllEdges( ElkRoutedEdge[] existingEdges, ElkPositionedNode[] nodes, double baseObstacleMargin, RoutingStrategy strategy, CancellationToken cancellationToken) { var routedEdges = new ElkRoutedEdge[existingEdges.Length]; Array.Copy(existingEdges, routedEdges, existingEdges.Length); var obstacleMargin = Math.Max( baseObstacleMargin, Math.Max(strategy.MinLineClearance + 4d, strategy.RoutingParams.Margin)); var obstacles = BuildObstacles(nodes, obstacleMargin); var originalNodeBounds = BuildOriginalNodeBounds(nodes); var graphMinY = nodes.Length > 0 ? nodes.Min(n => n.Y) : 0d; var graphMaxY = nodes.Length > 0 ? nodes.Max(n => n.Y + n.Height) : 0d; var nodesById = nodes.ToDictionary(n => n.Id, StringComparer.Ordinal); var routedEdgeCount = 0; var skippedEdgeCount = 0; var routedSectionCount = 0; var fallbackSectionCount = 0; // Spread endpoints: distribute edges arriving at the same target side var spreadEndpoints = SpreadTargetEndpoints(existingEdges, nodesById, graphMinY, graphMaxY, strategy.MinLineClearance); var softObstacles = new List(); foreach (var edgeIndex in strategy.EdgeOrder) { if (edgeIndex < 0 || edgeIndex >= existingEdges.Length) { continue; } cancellationToken.ThrowIfCancellationRequested(); var edge = existingEdges[edgeIndex]; // Skip edges that need special routing (backward, ports, corridors, collectors) if (!CanRepairEdgeLocally(edge, nodes, graphMinY, graphMaxY)) { skippedEdgeCount++; foreach (var segment in ElkEdgeRoutingGeometry.FlattenSegments(edge)) { softObstacles.Add(new OrthogonalSoftObstacle(segment.Start, segment.End)); } continue; } var newSections = new List(edge.Sections.Count); foreach (var section in edge.Sections) { var endPoint = spreadEndpoints.TryGetValue(edge.Id, out var spread) ? spread : section.EndPoint; var (startPoint, adjustedEndPoint) = ResolveRoutingEndpoints( section.StartPoint, endPoint, edge.SourceNodeId, edge.TargetNodeId, nodesById); var rerouted = ElkEdgeRouterAStar8Dir.Route( startPoint, adjustedEndPoint, obstacles, originalNodeBounds, edge.SourceNodeId ?? "", edge.TargetNodeId ?? "", strategy.RoutingParams, softObstacles, cancellationToken); if (rerouted is not null && rerouted.Count >= 2) { routedSectionCount++; newSections.Add(new ElkEdgeSection { StartPoint = rerouted[0], EndPoint = rerouted[^1], BendPoints = rerouted.Skip(1).Take(rerouted.Count - 2).ToArray(), }); } else { fallbackSectionCount++; newSections.Add(section); } } routedEdgeCount++; routedEdges[edgeIndex] = new ElkRoutedEdge { Id = edge.Id, SourceNodeId = edge.SourceNodeId, TargetNodeId = edge.TargetNodeId, SourcePortId = edge.SourcePortId, TargetPortId = edge.TargetPortId, Kind = edge.Kind, Label = edge.Label, Sections = newSections, }; foreach (var segment in ElkEdgeRoutingGeometry.FlattenSegments(routedEdges[edgeIndex])) { softObstacles.Add(new OrthogonalSoftObstacle(segment.Start, segment.End)); } } return new RouteAllEdgesResult( routedEdges, new ElkIterativeRouteDiagnostics { Mode = "full-strategy", TotalEdges = existingEdges.Length, RoutedEdges = routedEdgeCount, SkippedEdges = skippedEdgeCount, RoutedSections = routedSectionCount, FallbackSections = fallbackSectionCount, SoftObstacleSegments = softObstacles.Count, }); } }