using System.Collections.Concurrent; using System.Diagnostics; using System.Globalization; namespace StellaOps.ElkSharp; internal static partial class ElkEdgeRouterIterative { private static bool TryPromoteFinalBoundarySlotCandidate( CandidateSolution current, ElkRoutedEdge[] candidateEdges, ElkPositionedNode[] nodes, out CandidateSolution promoted) { promoted = current; var candidateScore = ElkEdgeRoutingScoring.ComputeScore(candidateEdges, nodes); var candidateRetryState = BuildRetryState( candidateScore, HighwayProcessingEnabled ? ElkEdgeRouterHighway.DetectRemainingBrokenHighways(candidateEdges, nodes).Count : 0); if (!IsBetterBoundarySlotRepairCandidate( candidateScore, candidateRetryState, current.Score, current.RetryState)) { return false; } promoted = current with { Score = candidateScore, RetryState = candidateRetryState, Edges = candidateEdges, }; return true; } private static bool HasBlockingBoundarySlotPromotionRegression( RoutingRetryState candidate, RoutingRetryState baseline) { return candidate.RemainingShortHighways > baseline.RemainingShortHighways || candidate.RepeatCollectorCorridorViolations > baseline.RepeatCollectorCorridorViolations || candidate.RepeatCollectorNodeClearanceViolations > baseline.RepeatCollectorNodeClearanceViolations || candidate.BelowGraphViolations > baseline.BelowGraphViolations || candidate.UnderNodeViolations > baseline.UnderNodeViolations; } private static bool HasEdgeGeometryChanged( IReadOnlyList baselineEdges, IReadOnlyList candidateEdges, string edgeId) { var baseline = baselineEdges.FirstOrDefault(edge => string.Equals(edge.Id, edgeId, StringComparison.Ordinal)); var candidate = candidateEdges.FirstOrDefault(edge => string.Equals(edge.Id, edgeId, StringComparison.Ordinal)); if (baseline is null || candidate is null) { return false; } var baselinePath = ExtractEdgePath(baseline); var candidatePath = ExtractEdgePath(candidate); if (baselinePath.Count != candidatePath.Count) { return true; } for (var i = 0; i < baselinePath.Count; i++) { if (!ElkEdgeRoutingGeometry.PointsEqual(baselinePath[i], candidatePath[i])) { return true; } } return false; } private static List ExtractEdgePath(ElkRoutedEdge edge) { var path = new List(); foreach (var section in edge.Sections) { if (path.Count == 0 || !ElkEdgeRoutingGeometry.PointsEqual(path[^1], section.StartPoint)) { path.Add(section.StartPoint); } foreach (var bendPoint in section.BendPoints) { if (path.Count == 0 || !ElkEdgeRoutingGeometry.PointsEqual(path[^1], bendPoint)) { path.Add(bendPoint); } } if (path.Count == 0 || !ElkEdgeRoutingGeometry.PointsEqual(path[^1], section.EndPoint)) { path.Add(section.EndPoint); } } return path; } private static bool TryPromoteFinalHardRuleCandidate( CandidateSolution current, ElkRoutedEdge[] candidateEdges, ElkPositionedNode[] nodes, out CandidateSolution promoted) { promoted = current; var candidateScore = ElkEdgeRoutingScoring.ComputeScore(candidateEdges, nodes); var candidateRetryState = BuildRetryState( candidateScore, HighwayProcessingEnabled ? ElkEdgeRouterHighway.DetectRemainingBrokenHighways(candidateEdges, nodes).Count : 0); if (!IsBetterBoundarySlotRepairCandidate( candidateScore, candidateRetryState, current.Score, current.RetryState)) { return false; } promoted = current with { Score = candidateScore, RetryState = candidateRetryState, Edges = candidateEdges, }; return true; } private static ElkRoutedEdge[] ApplyAggressiveSharedLaneClosure( ElkRoutedEdge[] edges, ElkPositionedNode[] nodes, ElkLayoutDirection direction, double minLineClearance, IReadOnlyCollection focusEdgeIds) { var result = edges; result = ElkEdgePostProcessor.SeparateSharedLaneConflicts(result, nodes, minLineClearance, focusEdgeIds); result = ElkEdgePostProcessor.SeparateRepeatCollectorLocalLaneConflicts(result, nodes, minLineClearance, focusEdgeIds); result = ElkRepeatCollectorCorridors.SeparateSharedLanes(result, nodes, focusEdgeIds); result = ElkEdgePostProcessor.SpreadSourceDepartureJoins(result, nodes, minLineClearance, focusEdgeIds); result = ElkEdgePostProcessor.SeparateMixedNodeFaceLaneConflicts(result, nodes, minLineClearance, focusEdgeIds); result = ElkEdgePostProcessor.SpreadTargetApproachJoins(result, nodes, minLineClearance, focusEdgeIds, forceOutwardAxisSpacing: true); result = ElkEdgePostProcessor.SpreadRectTargetApproachFeederBands(result, nodes, minLineClearance, focusEdgeIds); result = ElkEdgePostProcessor.RepairBoundaryAnglesAndTargetApproaches(result, nodes, minLineClearance, focusEdgeIds); result = ElkEdgePostProcessor.FinalizeGatewayBoundaryGeometry(result, nodes, focusEdgeIds); result = ClampBelowGraphEdges(result, nodes, focusEdgeIds); result = ElkEdgePostProcessor.AvoidNodeCrossings(result, nodes, direction, focusEdgeIds); result = ElkEdgePostProcessor.NormalizeBoundaryAngles(result, nodes); result = ElkEdgePostProcessor.NormalizeSourceExitAngles(result, nodes); result = ElkEdgePostProcessor.SeparateMixedNodeFaceLaneConflicts(result, nodes, minLineClearance, focusEdgeIds); result = ElkEdgePostProcessor.SeparateSharedLaneConflicts(result, nodes, minLineClearance, focusEdgeIds); return result; } }