using System.Collections.Concurrent; using System.Diagnostics; using System.Globalization; namespace StellaOps.ElkSharp; internal static partial class ElkEdgeRouterIterative { private static List? TryRouteShortestRepair( ElkPoint start, ElkPoint end, IReadOnlyCollection nodes, (double Left, double Top, double Right, double Bottom, string Id)[] obstacles, string sourceId, string targetId, ElkPositionedNode? sourceNode, ElkPositionedNode? targetNode, AStarRoutingParams routingParams, IReadOnlyList softObstacles, CancellationToken cancellationToken) { if (sourceNode is not null && targetNode is not null) { if (ElkEdgePostProcessor.TryBuildPreferredBoundaryShortcutPath( sourceNode, targetNode, nodes, sourceId, targetId, out var preferredShortcut) && (targetNode is null || !HasTargetApproachBacktracking(preferredShortcut, targetNode))) { return preferredShortcut; } } var candidateEndpoints = EnumerateShortestRepairEndpoints(start, end, targetNode).ToArray(); var minLineClearance = ResolveMinLineClearance(nodes); var shortcutObstaclePadding = Math.Max(12d, Math.Min(routingParams.Margin, Math.Max(18d, minLineClearance - 4d))); List? bestPath = null; var bestLength = double.MaxValue; foreach (var candidateEnd in candidateEndpoints) { var orthogonalShortcut = TryBuildShortestOrthogonalPath( start, candidateEnd, nodes, sourceId, targetId, targetNode, shortcutObstaclePadding); if (orthogonalShortcut is null || (targetNode is not null && HasTargetApproachBacktracking(orthogonalShortcut, targetNode))) { continue; } var shortcutLength = ComputePolylineLength(orthogonalShortcut); if (shortcutLength < bestLength - 0.5d) { bestPath = orthogonalShortcut; bestLength = shortcutLength; } } foreach (var candidateEnd in candidateEndpoints) { var localSkirtShortcut = TryBuildLocalObstacleSkirtPath( start, candidateEnd, nodes, sourceId, targetId, targetNode, shortcutObstaclePadding); if (localSkirtShortcut is null || (targetNode is not null && HasTargetApproachBacktracking(localSkirtShortcut, targetNode))) { continue; } var shortcutLength = ComputePolylineLength(localSkirtShortcut); if (shortcutLength < bestLength - 0.5d) { bestPath = localSkirtShortcut; bestLength = shortcutLength; } } if (bestPath is not null) { return bestPath; } var shortestParams = routingParams with { Margin = Math.Max(shortcutObstaclePadding, Math.Min(routingParams.Margin, minLineClearance)), BendPenalty = Math.Min(routingParams.BendPenalty, 80d), DiagonalPenalty = Math.Min(routingParams.DiagonalPenalty, 40d), SoftObstacleWeight = Math.Max(0.25d, routingParams.SoftObstacleWeight * 0.35d), SoftObstacleClearance = Math.Max(Math.Max(18d, minLineClearance * 0.6d), routingParams.SoftObstacleClearance * 0.5d), IntermediateGridSpacing = Math.Max(12d, routingParams.IntermediateGridSpacing - 8d), }; var shortestObstacles = nodes .Select(node => ( Left: node.X - shortestParams.Margin, Top: node.Y - shortestParams.Margin, Right: node.X + node.Width + shortestParams.Margin, Bottom: node.Y + node.Height + shortestParams.Margin, Id: node.Id)) .ToArray(); foreach (var candidateEnd in candidateEndpoints) { var diagonalPath = ElkEdgeRouterAStar8Dir.Route( start, candidateEnd, shortestObstacles, sourceId, targetId, shortestParams, [], cancellationToken); if (diagonalPath is null || (targetNode is not null && HasTargetApproachBacktracking(diagonalPath, targetNode))) { continue; } var pathLength = ComputePolylineLength(diagonalPath); if (pathLength < bestLength - 0.5d) { bestPath = diagonalPath; bestLength = pathLength; } } return bestPath; } private static List? TryRouteAggressiveRepair( ElkPoint start, ElkPoint end, IReadOnlyCollection nodes, (double Left, double Top, double Right, double Bottom, string Id)[] obstacles, string sourceId, string targetId, ElkPositionedNode? sourceNode, ElkPositionedNode? targetNode, AStarRoutingParams routingParams, CancellationToken cancellationToken) { var candidateEndpoints = EnumerateShortestRepairEndpoints(start, end, targetNode).ToArray(); if (candidateEndpoints.Length == 0) { return null; } var minLineClearance = ResolveMinLineClearance(nodes); var aggressiveParams = routingParams with { Margin = Math.Max(10d, Math.Min(routingParams.Margin, Math.Max(14d, minLineClearance * 0.45d))), BendPenalty = Math.Min(routingParams.BendPenalty, 60d), DiagonalPenalty = Math.Min(routingParams.DiagonalPenalty, 35d), SoftObstacleWeight = 0d, SoftObstacleClearance = 0d, IntermediateGridSpacing = Math.Max(12d, Math.Min(routingParams.IntermediateGridSpacing, Math.Max(12d, minLineClearance * 0.45d))), }; var aggressiveObstacles = obstacles .Select(obstacle => ( Left: obstacle.Left + Math.Min(0d, aggressiveParams.Margin - routingParams.Margin), Top: obstacle.Top + Math.Min(0d, aggressiveParams.Margin - routingParams.Margin), Right: obstacle.Right - Math.Min(0d, aggressiveParams.Margin - routingParams.Margin), Bottom: obstacle.Bottom - Math.Min(0d, aggressiveParams.Margin - routingParams.Margin), obstacle.Id)) .ToArray(); List? bestPath = null; ElkRoutedEdge? bestEdge = null; foreach (var candidateEnd in candidateEndpoints) { var candidate = ElkEdgeRouterAStar8Dir.Route( start, candidateEnd, aggressiveObstacles, sourceId, targetId, aggressiveParams, [], cancellationToken); if (candidate is null) { continue; } if (targetNode is not null && HasTargetApproachBacktracking(candidate, targetNode)) { continue; } var candidateEdge = BuildCandidateRepairEdge( sourceId, targetId, sourceNode, targetNode, candidate); if (bestPath is null || CompareSingleEdgeRepairQuality(candidateEdge, bestEdge!, nodes) < 0) { bestPath = candidate; bestEdge = candidateEdge; } } return bestPath; } }