using System.Collections.Concurrent; using System.Diagnostics; using System.Globalization; namespace StellaOps.ElkSharp; internal static partial class ElkEdgeRouterIterative { internal static ElkRoutedEdge[] BuildFinalRestabilizedCandidate( ElkRoutedEdge[] edges, ElkPositionedNode[] nodes, ElkLayoutDirection direction, double minLineClearance, IReadOnlyCollection? restrictedEdgeIds = null) { var focusEdgeIds = restrictedEdgeIds?.Count > 0 ? restrictedEdgeIds : edges .Select(edge => edge.Id) .OrderBy(edgeId => edgeId, StringComparer.Ordinal) .ToArray(); var focusEdgeSet = focusEdgeIds.ToHashSet(StringComparer.Ordinal); var candidate = ChoosePreferredHardRuleLayout( edges, CloseRemainingTerminalViolations(edges, nodes, direction, minLineClearance, restrictedEdgeIds), nodes); if (focusEdgeIds.Count > 0) { candidate = ChoosePreferredHardRuleLayout( candidate, ApplyAggressiveSharedLaneClosure( candidate, nodes, direction, minLineClearance, focusEdgeIds), nodes); } candidate = BuildFinalBoundarySlotCandidate( candidate, nodes, direction, minLineClearance, restrictedEdgeIds, allowLateRestabilizedClosure: false); if (focusEdgeIds.Count > 0) { candidate = ChoosePreferredHardRuleLayout( candidate, ApplyAggressiveSharedLaneClosure( candidate, nodes, direction, minLineClearance, focusEdgeIds), nodes); } candidate = ChoosePreferredBoundarySlotRepairLayout( candidate, ElkEdgePostProcessor.SnapBoundarySlotAssignments( candidate, nodes, minLineClearance, restrictedEdgeIds, enforceAllNodeEndpoints: true), nodes); candidate = ChoosePreferredBoundarySlotRepairLayout( candidate, CloseRemainingTerminalViolations(candidate, nodes, direction, minLineClearance, restrictedEdgeIds), nodes); if (focusEdgeIds.Count > 0) { var lateHardRuleCandidate = ApplyAggressiveSharedLaneClosure( candidate, nodes, direction, minLineClearance, focusEdgeIds); lateHardRuleCandidate = CloseRemainingTerminalViolations( lateHardRuleCandidate, nodes, direction, minLineClearance, focusEdgeIds); candidate = ChoosePreferredHardRuleLayout(candidate, lateHardRuleCandidate, nodes); var remainingBadAngles = new Dictionary(StringComparer.Ordinal); ElkEdgeRoutingScoring.CountBadBoundaryAngles(candidate, nodes, remainingBadAngles, 10); var remainingTerminalFocus = ExpandWinningSolutionFocus( candidate, remainingBadAngles.Keys.Where(focusEdgeSet.Contains)) .Where(focusEdgeSet.Contains) .OrderBy(edgeId => edgeId, StringComparer.Ordinal) .ToArray(); if (remainingTerminalFocus.Length > 0) { var terminalFocus = (IReadOnlyCollection)remainingTerminalFocus; var lateTerminalCandidate = ElkEdgePostProcessor.RepairBoundaryAnglesAndTargetApproaches( candidate, nodes, minLineClearance, terminalFocus); lateTerminalCandidate = BuildFinalBoundarySlotCandidate( lateTerminalCandidate, nodes, direction, minLineClearance, terminalFocus, allowLateRestabilizedClosure: false); lateTerminalCandidate = CloseRemainingTerminalViolations( lateTerminalCandidate, nodes, direction, minLineClearance, terminalFocus); candidate = ChoosePreferredBoundarySlotRepairLayout(candidate, lateTerminalCandidate, nodes); } } candidate = ChoosePreferredBoundarySlotRepairLayout( candidate, ElkEdgePostProcessor.SnapBoundarySlotAssignments( candidate, nodes, minLineClearance, restrictedEdgeIds, enforceAllNodeEndpoints: true), nodes); candidate = ChoosePreferredBoundarySlotRepairLayout( candidate, ApplyPostSlotDetourClosure(candidate, nodes, minLineClearance, restrictedEdgeIds), nodes); candidate = ChoosePreferredBoundarySlotRepairLayout( candidate, ElkEdgePostProcessor.SnapBoundarySlotAssignments( candidate, nodes, minLineClearance, restrictedEdgeIds, enforceAllNodeEndpoints: true), nodes); candidate = ApplyLateBoundarySlotRestabilization( candidate, nodes, minLineClearance, focusEdgeIds); candidate = ChoosePreferredBoundarySlotRepairLayout( candidate, ElkEdgePostProcessor.SnapBoundarySlotAssignments( candidate, nodes, minLineClearance, restrictedEdgeIds, enforceAllNodeEndpoints: true), nodes); if (focusEdgeIds.Count > 0) { var finalSharedLaneCandidate = ElkEdgePostProcessor.SeparateSharedLaneConflicts( candidate, nodes, minLineClearance, focusEdgeIds); candidate = ChoosePreferredSharedLanePolishLayout(candidate, finalSharedLaneCandidate, nodes); var finalSourceJoinCandidate = ElkEdgePostProcessor.SpreadSourceDepartureJoins( finalSharedLaneCandidate, nodes, minLineClearance, focusEdgeIds); candidate = ChoosePreferredSharedLanePolishLayout(candidate, finalSourceJoinCandidate, nodes); var finalTargetJoinCandidate = ElkEdgePostProcessor.SpreadTargetApproachJoins( finalSourceJoinCandidate, nodes, minLineClearance, focusEdgeIds, forceOutwardAxisSpacing: true); candidate = ChoosePreferredSharedLanePolishLayout(candidate, finalTargetJoinCandidate, nodes); var finalAggressiveCandidate = ApplyAggressiveSharedLaneClosure( candidate, nodes, direction, minLineClearance, focusEdgeIds); candidate = ChoosePreferredSharedLanePolishLayout(candidate, finalAggressiveCandidate, nodes); var forcedFinalSharedLaneCandidate = ElkEdgePostProcessor.SeparateSharedLaneConflicts( candidate, nodes, minLineClearance, focusEdgeIds); var baselineSharedLaneViolations = ElkEdgeRoutingScoring.CountSharedLaneViolations(candidate, nodes); var forcedSharedLaneViolations = ElkEdgeRoutingScoring.CountSharedLaneViolations(forcedFinalSharedLaneCandidate, nodes); if (forcedSharedLaneViolations < baselineSharedLaneViolations) { var baselineScore = ElkEdgeRoutingScoring.ComputeScore(candidate, nodes); var forcedScore = ElkEdgeRoutingScoring.ComputeScore(forcedFinalSharedLaneCandidate, nodes); if (forcedScore.NodeCrossings <= baselineScore.NodeCrossings) { candidate = forcedFinalSharedLaneCandidate; } } } return candidate; } }