using System.Collections.Concurrent; using System.Diagnostics; using System.Globalization; namespace StellaOps.ElkSharp; internal static partial class ElkEdgeRouterIterative { private static ElkRoutedEdge[] ApplyPostProcessing( ElkRoutedEdge[] edges, ElkPositionedNode[] nodes, ElkLayoutOptions layoutOptions, bool preferHybridTerminalCleanup = false) { if (preferHybridTerminalCleanup) { return ApplyLeanHybridBaselinePostProcessing(edges, nodes, layoutOptions); } var result = ElkEdgePostProcessor.AvoidNodeCrossings(edges, nodes, layoutOptions.Direction); result = ElkEdgePostProcessor.EliminateDiagonalSegments(result, nodes); result = ElkEdgePostProcessorSimplify.SimplifyEdgePaths(result, nodes); result = ElkEdgePostProcessorSimplify.TightenOuterCorridors(result, nodes); if (HighwayProcessingEnabled) { result = ElkEdgeRouterHighway.BreakShortHighways(result, nodes); } result = ElkEdgePostProcessor.NormalizeBoundaryAngles(result, nodes); result = ElkEdgePostProcessor.AvoidNodeCrossings(result, nodes, layoutOptions.Direction); result = ElkEdgePostProcessorSimplify.SimplifyEdgePaths(result, nodes); result = ElkEdgePostProcessor.AvoidNodeCrossings(result, nodes, layoutOptions.Direction); result = ElkEdgePostProcessor.NormalizeBoundaryAngles(result, nodes); var serviceNodes = nodes.Where(n => n.Kind is not "Start" and not "End").ToArray(); var minLineClearance = serviceNodes.Length > 0 ? Math.Min(serviceNodes.Average(n => n.Width), serviceNodes.Average(n => n.Height)) / 2d : 50d; result = ElkEdgePostProcessor.NormalizeSourceExitAngles(result, nodes); result = ElkEdgePostProcessor.SpreadSourceDepartureJoins(result, nodes, minLineClearance); result = ElkEdgePostProcessor.RepairBoundaryAnglesAndTargetApproaches(result, nodes, minLineClearance); result = ElkEdgePostProcessor.ElevateUnderNodeViolations(result, nodes, minLineClearance); result = ElkEdgePostProcessor.AvoidNodeCrossings(result, nodes, layoutOptions.Direction); result = ElkEdgePostProcessor.NormalizeBoundaryAngles(result, nodes); result = ElkEdgePostProcessor.NormalizeSourceExitAngles(result, nodes); result = ElkEdgePostProcessor.SpreadSourceDepartureJoins(result, nodes, minLineClearance); result = ElkEdgePostProcessor.RepairBoundaryAnglesAndTargetApproaches(result, nodes, minLineClearance); result = RestoreProtectedRepeatCollectorCorridors(result, edges, nodes); result = ElkEdgePostProcessor.AvoidNodeCrossings(result, nodes, layoutOptions.Direction); result = ElkEdgePostProcessor.FinalizeGatewayBoundaryGeometry(result, nodes); result = ElkEdgePostProcessor.AvoidNodeCrossings(result, nodes, layoutOptions.Direction); result = ElkEdgePostProcessor.FinalizeGatewayBoundaryGeometry(result, nodes); result = ElkEdgePostProcessor.RepairBoundaryAnglesAndTargetApproaches(result, nodes, minLineClearance); result = ElkEdgePostProcessor.SpreadTargetApproachJoins(result, nodes, minLineClearance); result = ElkEdgePostProcessor.NormalizeBoundaryAngles(result, nodes); result = ElkEdgePostProcessor.NormalizeSourceExitAngles(result, nodes); result = ElkEdgePostProcessor.SpreadSourceDepartureJoins(result, nodes, minLineClearance); result = ElkEdgePostProcessor.AvoidNodeCrossings(result, nodes, layoutOptions.Direction); result = ElkEdgePostProcessor.FinalizeGatewayBoundaryGeometry(result, nodes); result = ElkEdgePostProcessor.AvoidNodeCrossings(result, nodes, layoutOptions.Direction); result = ElkEdgePostProcessor.RepairBoundaryAnglesAndTargetApproaches(result, nodes, minLineClearance); result = ElkEdgePostProcessor.AvoidNodeCrossings(result, nodes, layoutOptions.Direction); result = ElkEdgePostProcessor.FinalizeGatewayBoundaryGeometry(result, nodes); result = ElkEdgePostProcessor.NormalizeBoundaryAngles(result, nodes); result = ElkEdgePostProcessor.NormalizeSourceExitAngles(result, nodes); result = ElkEdgePostProcessor.SpreadSourceDepartureJoins(result, nodes, minLineClearance); result = ElkEdgePostProcessor.AvoidNodeCrossings(result, nodes, layoutOptions.Direction); result = ElkEdgePostProcessor.FinalizeGatewayBoundaryGeometry(result, nodes); result = ElkEdgePostProcessor.SpreadTargetApproachJoins(result, nodes, minLineClearance); result = ElkEdgePostProcessor.NormalizeBoundaryAngles(result, nodes); result = ElkEdgePostProcessor.NormalizeSourceExitAngles(result, nodes); result = ElkEdgePostProcessor.SpreadSourceDepartureJoins(result, nodes, minLineClearance); result = ElkEdgePostProcessor.AvoidNodeCrossings(result, nodes, layoutOptions.Direction); result = ElkEdgePostProcessor.FinalizeGatewayBoundaryGeometry(result, nodes); result = ElkEdgePostProcessor.RepairBoundaryAnglesAndTargetApproaches(result, nodes, minLineClearance); result = ElkEdgePostProcessor.NormalizeBoundaryAngles(result, nodes); result = ElkEdgePostProcessor.NormalizeSourceExitAngles(result, nodes); result = ElkEdgePostProcessor.SpreadSourceDepartureJoins(result, nodes, minLineClearance); result = ElkEdgePostProcessor.FinalizeGatewayBoundaryGeometry(result, nodes); result = ElkEdgePostProcessor.NormalizeBoundaryAngles(result, nodes); result = ElkEdgePostProcessor.FinalizeGatewayBoundaryGeometry(result, nodes); result = ClampBelowGraphEdges(result, nodes); result = ElkEdgePostProcessor.NormalizeBoundaryAngles(result, nodes); result = ElkEdgePostProcessor.NormalizeSourceExitAngles(result, nodes); result = ElkEdgePostProcessor.RepairBoundaryAnglesAndTargetApproaches(result, nodes, minLineClearance); result = ElkEdgePostProcessor.SpreadTargetApproachJoins(result, nodes, minLineClearance); result = ElkEdgePostProcessor.NormalizeBoundaryAngles(result, nodes); result = ElkEdgePostProcessor.NormalizeSourceExitAngles(result, nodes); result = ElkEdgePostProcessor.RepairBoundaryAnglesAndTargetApproaches(result, nodes, minLineClearance); result = ElkEdgePostProcessor.SpreadTargetApproachJoins(result, nodes, minLineClearance); result = ElkEdgePostProcessor.NormalizeBoundaryAngles(result, nodes); result = ElkEdgePostProcessor.NormalizeSourceExitAngles(result, nodes); result = ElkEdgePostProcessor.RepairBoundaryAnglesAndTargetApproaches(result, nodes, minLineClearance); result = ElkEdgePostProcessor.NormalizeBoundaryAngles(result, nodes); result = ElkEdgePostProcessor.FinalizeDecisionTargetEntries(result, nodes); result = ElkEdgePostProcessor.NormalizeBoundaryAngles(result, nodes); result = ElkEdgePostProcessor.SpreadTargetApproachJoins(result, nodes, minLineClearance); result = ElkEdgePostProcessor.FinalizeGatewayBoundaryGeometry(result, nodes); result = ElkEdgePostProcessor.NormalizeBoundaryAngles(result, nodes); result = ElkEdgePostProcessor.SpreadTargetApproachJoins(result, nodes, minLineClearance); result = ElkEdgePostProcessor.FinalizeGatewayBoundaryGeometry(result, nodes); result = ElkEdgePostProcessor.FinalizeDecisionTargetEntries(result, nodes); result = ElkEdgePostProcessor.SpreadRectTargetApproachFeederBands(result, nodes, minLineClearance); result = ElkEdgePostProcessor.SeparateRepeatCollectorLocalLaneConflicts(result, nodes, minLineClearance); result = ClampBelowGraphEdges(result, nodes); result = ElkEdgePostProcessor.AvoidNodeCrossings(result, nodes, layoutOptions.Direction); result = ElkEdgePostProcessor.RepairBoundaryAnglesAndTargetApproaches(result, nodes, minLineClearance); result = ElkEdgePostProcessor.SpreadTargetApproachJoins(result, nodes, minLineClearance); result = ElkEdgePostProcessor.NormalizeSourceExitAngles(result, nodes); result = ElkEdgePostProcessor.SpreadSourceDepartureJoins(result, nodes, minLineClearance); result = ElkEdgePostProcessor.SpreadRectTargetApproachFeederBands(result, nodes, minLineClearance); result = ElkEdgePostProcessor.ElevateRepeatCollectorNodeClearanceViolations(result, nodes, minLineClearance); result = ElkRepeatCollectorCorridors.SeparateSharedLanes(result, nodes); result = ElkEdgePostProcessor.SeparateRepeatCollectorLocalLaneConflicts(result, nodes, minLineClearance); result = ElkEdgePostProcessor.SeparateSharedLaneConflicts(result, nodes, minLineClearance); result = ElkEdgePostProcessor.RepairBoundaryAnglesAndTargetApproaches(result, nodes, minLineClearance); result = ElkEdgePostProcessor.SpreadTargetApproachJoins(result, nodes, minLineClearance); result = ElkEdgePostProcessor.SeparateSharedLaneConflicts(result, nodes, minLineClearance); result = ElkEdgePostProcessor.PreferShortestBoundaryShortcuts(result, nodes); result = ElkEdgePostProcessor.NormalizeBoundaryAngles(result, nodes); result = ElkEdgePostProcessor.NormalizeSourceExitAngles(result, nodes); result = ElkEdgePostProcessor.SpreadSourceDepartureJoins(result, nodes, minLineClearance); result = ElkEdgePostProcessor.SeparateMixedNodeFaceLaneConflicts(result, nodes, minLineClearance); result = ElkEdgePostProcessor.RepairBoundaryAnglesAndTargetApproaches(result, nodes, minLineClearance); result = ElkEdgePostProcessor.SpreadTargetApproachJoins(result, nodes, minLineClearance); result = ElkEdgePostProcessor.NormalizeBoundaryAngles(result, nodes); result = ElkEdgePostProcessor.NormalizeSourceExitAngles(result, nodes); // The final hard-rule closure must end on lane separation so later // boundary slot normalizers cannot collapse a repaired handoff strip // back onto the same effective rail. result = ElkEdgePostProcessor.SeparateMixedNodeFaceLaneConflicts(result, nodes, minLineClearance); result = ElkEdgePostProcessor.SeparateSharedLaneConflicts(result, nodes, minLineClearance); result = ClampBelowGraphEdges(result, nodes); result = ElkEdgePostProcessor.SnapBoundarySlotAssignments( result, nodes, minLineClearance, enforceAllNodeEndpoints: true); result = ApplyPostSlotDetourClosure(result, nodes, minLineClearance); result = ElkEdgePostProcessor.SnapBoundarySlotAssignments( result, nodes, minLineClearance, enforceAllNodeEndpoints: true); var score = ElkEdgeRoutingScoring.ComputeScore(result, nodes); var remainingBrokenHighways = HighwayProcessingEnabled ? ElkEdgeRouterHighway.DetectRemainingBrokenHighways(result, nodes).Count : 0; var retryState = BuildRetryState(score, remainingBrokenHighways); if (retryState.RequiresBlockingRetry || retryState.RequiresLengthRetry) { var stabilized = preferHybridTerminalCleanup ? ApplyHybridTerminalRuleCleanupRound( result, nodes, layoutOptions.Direction, minLineClearance) : ApplyTerminalRuleCleanupRound( result, nodes, layoutOptions.Direction, minLineClearance); var stabilizedScore = ElkEdgeRoutingScoring.ComputeScore(stabilized, nodes); var stabilizedBrokenHighways = HighwayProcessingEnabled ? ElkEdgeRouterHighway.DetectRemainingBrokenHighways(stabilized, nodes).Count : 0; var stabilizedRetryState = BuildRetryState(stabilizedScore, stabilizedBrokenHighways); if (IsBetterBoundarySlotRepairCandidate( stabilizedScore, stabilizedRetryState, score, retryState)) { result = stabilized; } } return result; } }