namespace StellaOps.ElkSharp; internal static partial class ElkEdgePostProcessor { private static bool IsValidSharedLaneRepairPath( IReadOnlyList path, ElkRoutedEdge edge, double graphMinY, double graphMaxY, (double Left, double Top, double Right, double Bottom, string Id)[] nodeObstacles, string? originalTargetSide, ElkPositionedNode? targetNode) { return path.Count >= 2 && (originalTargetSide is null || targetNode is null || ResolveTargetApproachSide(path, targetNode) == originalTargetSide) && !HasNodeObstacleCrossing(path, nodeObstacles, edge.SourceNodeId, edge.TargetNodeId) && !SegmentLeavesGraphBand(path, graphMinY, graphMaxY); } private static IEnumerable<(int SegmentIndex, double AlternateCoordinate)> EnumerateSharedLaneShiftCandidates( IReadOnlyList path, IReadOnlyCollection peerEdges, double minLineClearance) { var yielded = new HashSet<(int SegmentIndex, double AlternateCoordinate)>(); for (var segmentIndex = 0; segmentIndex < path.Count - 1; segmentIndex++) { var start = path[segmentIndex]; var end = path[segmentIndex + 1]; var isHorizontal = Math.Abs(start.Y - end.Y) <= 0.5d; var isVertical = Math.Abs(start.X - end.X) <= 0.5d; if (!isHorizontal && !isVertical) { continue; } foreach (var peerEdge in peerEdges) { foreach (var otherSegment in ElkEdgeRoutingGeometry.FlattenSegments(peerEdge)) { if (!SegmentsShareLane( start, end, otherSegment.Start, otherSegment.End, minLineClearance)) { continue; } foreach (var alternateCoordinate in ResolveLaneShiftCoordinates( start, end, otherSegment.Start, otherSegment.End, minLineClearance)) { if (yielded.Add((segmentIndex, alternateCoordinate))) { yield return (segmentIndex, alternateCoordinate); } } } } } } private static bool TryBuildSharedLaneRepairEdge( ElkRoutedEdge[] currentEdges, int repairIndex, ElkRoutedEdge edge, ElkRoutedEdge otherEdge, IReadOnlyList candidatePath, ElkPositionedNode[] nodes, double minLineClearance, double graphMinY, double graphMaxY, (double Left, double Top, double Right, double Bottom, string Id)[] nodeObstacles, string? originalTargetSide, ElkPositionedNode? targetNode, out ElkRoutedEdge repairedEdge) { repairedEdge = edge; if (!IsValidSharedLaneRepairPath( candidatePath, edge, graphMinY, graphMaxY, nodeObstacles, originalTargetSide, targetNode)) { return false; } repairedEdge = BuildSingleSectionEdge(edge, candidatePath); repairedEdge = RepairBoundaryAnglesAndTargetApproaches( [repairedEdge], nodes, minLineClearance)[0]; repairedEdge = NormalizeSourceExitAngles([repairedEdge], nodes)[0]; var candidateEdges = currentEdges.ToArray(); candidateEdges[repairIndex] = repairedEdge; var candidateEdgeId = repairedEdge.Id; var repairedPath = ExtractFullPath(repairedEdge); if (!IsValidSharedLaneRepairPath( repairedPath, edge, graphMinY, graphMaxY, nodeObstacles, originalTargetSide, targetNode) || ElkEdgeRoutingScoring.CountLongDiagonalViolations([repairedEdge], nodes) > 0 || ElkEdgeRoutingScoring.CountBadBoundaryAngles([repairedEdge], nodes) > 0 || ElkEdgeRoutingScoring.CountGatewaySourceExitViolations([repairedEdge], nodes) > 0 || ElkEdgeRoutingScoring.DetectSharedLaneConflicts(candidateEdges, nodes).Any(conflict => string.Equals(conflict.LeftEdgeId, candidateEdgeId, StringComparison.Ordinal) || string.Equals(conflict.RightEdgeId, candidateEdgeId, StringComparison.Ordinal))) { repairedEdge = edge; return false; } return true; } private static bool TryPromoteSharedLaneRepairCandidate( ElkRoutedEdge[] currentEdges, int repairIndex, ElkRoutedEdge edge, ElkRoutedEdge otherEdge, IReadOnlyList originalPath, IReadOnlyList candidatePath, IReadOnlyCollection peerEdges, ElkPositionedNode[] nodes, double minLineClearance, double graphMinY, double graphMaxY, (double Left, double Top, double Right, double Bottom, string Id)[] nodeObstacles, string? originalTargetSide, ElkPositionedNode? targetNode, int remainingAdditionalShifts, HashSet visitedPaths, out ElkRoutedEdge repairedEdge) { repairedEdge = edge; if (!PathChanged(originalPath, candidatePath)) { return false; } if (!IsValidSharedLaneRepairPath( candidatePath, edge, graphMinY, graphMaxY, nodeObstacles, originalTargetSide, targetNode)) { return false; } if (!visitedPaths.Add(CreatePathSignature(candidatePath))) { return false; } if (TryBuildSharedLaneRepairEdge( currentEdges, repairIndex, edge, otherEdge, candidatePath, nodes, minLineClearance, graphMinY, graphMaxY, nodeObstacles, originalTargetSide, targetNode, out repairedEdge)) { return true; } if (remainingAdditionalShifts <= 0) { repairedEdge = edge; return false; } foreach (var (segmentIndex, alternateCoordinate) in EnumerateSharedLaneShiftCandidates(candidatePath, peerEdges, minLineClearance)) { var secondaryCandidate = candidatePath.Count == 2 ? ShiftStraightOrthogonalPath(candidatePath, alternateCoordinate) : ShiftSingleOrthogonalRun(candidatePath, segmentIndex, alternateCoordinate); if (TryPromoteSharedLaneRepairCandidate( currentEdges, repairIndex, edge, otherEdge, candidatePath, secondaryCandidate, peerEdges, nodes, minLineClearance, graphMinY, graphMaxY, nodeObstacles, originalTargetSide, targetNode, remainingAdditionalShifts - 1, visitedPaths, out repairedEdge)) { return true; } } repairedEdge = edge; return false; } private static bool TrySeparateSharedLaneConflict( ElkRoutedEdge[] currentEdges, int repairIndex, ElkRoutedEdge edge, ElkRoutedEdge otherEdge, ElkPositionedNode[] nodes, double minLineClearance, double graphMinY, double graphMaxY, (double Left, double Top, double Right, double Bottom, string Id)[] nodeObstacles, out ElkRoutedEdge repairedEdge) { repairedEdge = edge; var path = ExtractFullPath(edge); if (path.Count < 2) { return false; } var nodesById = nodes.ToDictionary(node => node.Id, StringComparer.Ordinal); var originalTargetSide = nodesById.TryGetValue(edge.TargetNodeId ?? string.Empty, out var targetNode) && !ElkShapeBoundaries.IsGatewayShape(targetNode) ? ResolveTargetApproachSide(path, targetNode) : null; var peerEdges = currentEdges .Where((candidateEdge, index) => index != repairIndex) .ToArray(); foreach (var (segmentIndex, alternateCoordinate) in EnumerateSharedLaneShiftCandidates(path, peerEdges, minLineClearance)) { var candidate = path.Count == 2 ? ShiftStraightOrthogonalPath(path, alternateCoordinate) : ShiftSingleOrthogonalRun(path, segmentIndex, alternateCoordinate); if (TryPromoteSharedLaneRepairCandidate( currentEdges, repairIndex, edge, otherEdge, path, candidate, peerEdges, nodes, minLineClearance, graphMinY, graphMaxY, nodeObstacles, originalTargetSide, targetNode, 2, new HashSet(StringComparer.Ordinal) { CreatePathSignature(path), }, out repairedEdge)) { return true; } } repairedEdge = edge; return false; } }