namespace StellaOps.ElkSharp; internal static partial class ElkEdgePostProcessor { private static List TryBuildGatewaySourceDominantBlockerEscapePath( IReadOnlyList sourcePath, ElkPositionedNode sourceNode, IReadOnlyCollection nodes, string? sourceNodeId, string? targetNodeId) { var path = sourcePath .Select(point => new ElkPoint { X = point.X, Y = point.Y }) .ToList(); if (sourceNode.Kind != "Decision" || path.Count < 3 || !HasGatewaySourceDominantAxisDetour(path, sourceNode)) { return path; } var centerX = sourceNode.X + (sourceNode.Width / 2d); var centerY = sourceNode.Y + (sourceNode.Height / 2d); var desiredDx = path[^1].X - centerX; var desiredDy = path[^1].Y - centerY; var dominantVertical = Math.Abs(desiredDy) >= Math.Abs(desiredDx) * 1.25d && Math.Sign(desiredDy) != 0; if (!dominantVertical) { return path; } var clearance = 24d; var direction = Math.Sign(desiredDy); var targetNode = string.IsNullOrWhiteSpace(targetNodeId) ? null : nodes.FirstOrDefault(node => string.Equals(node.Id, targetNodeId, StringComparison.Ordinal)); var firstExteriorIndex = FindFirstGatewayExteriorPointIndex(path, sourceNode); List? bestCandidate = null; var bestScore = double.PositiveInfinity; foreach (var continuationIndex in EnumerateGatewayDirectRepairContinuationIndices(path, sourceNode, firstExteriorIndex)) { var anchorX = path[continuationIndex].X; if (Math.Abs(anchorX - path[0].X) <= 3d) { continue; } var dominantReference = new ElkPoint { X = anchorX, Y = path[^1].Y }; if (!TryResolvePreferredGatewaySourceBoundary(sourceNode, dominantReference, path[^1], out var provisionalBoundary)) { continue; } var stemBlockers = nodes .Where(node => !string.Equals(node.Id, sourceNodeId, StringComparison.Ordinal) && !string.Equals(node.Id, targetNodeId, StringComparison.Ordinal) && provisionalBoundary.X > node.X + 0.5d && provisionalBoundary.X < node.X + node.Width - 0.5d && (direction > 0d ? node.Y > provisionalBoundary.Y + 0.5d && node.Y < path[^1].Y - 0.5d : node.Y + node.Height < provisionalBoundary.Y - 0.5d && node.Y + node.Height > path[^1].Y + 0.5d)) .OrderBy(node => direction > 0d ? node.Y : -(node.Y + node.Height)) .ToArray(); if (stemBlockers.Length == 0) { continue; } foreach (var blocker in stemBlockers) { var escapeY = direction > 0d ? blocker.Y - clearance : blocker.Y + blocker.Height + clearance; if (direction > 0d) { if (escapeY <= provisionalBoundary.Y + 8d || escapeY >= blocker.Y - 0.5d) { continue; } } else if (escapeY >= provisionalBoundary.Y - 8d || escapeY <= blocker.Y + blocker.Height + 0.5d) { continue; } var continuationPoint = new ElkPoint { X = anchorX, Y = escapeY }; var boundary = provisionalBoundary; var candidate = BuildGatewaySourceRepairPath( path, sourceNode, boundary, continuationPoint, continuationIndex, continuationPoint); if (!PathChanged(path, candidate) || !HasAcceptableGatewayBoundaryPath(candidate, nodes, sourceNodeId, targetNodeId, sourceNode, fromStart: true) || !HasClearBoundarySegments(candidate, nodes, sourceNodeId, targetNodeId, true, Math.Min(4, candidate.Count - 1)) || (targetNode is not null && (ElkShapeBoundaries.IsGatewayShape(targetNode) ? !HasAcceptableGatewayBoundaryPath(candidate, nodes, sourceNodeId, targetNodeId, targetNode, fromStart: false) : candidate.Count < 2 || !HasValidBoundaryAngle(candidate[^1], candidate[^2], targetNode))) || HasGatewaySourceExitBacktracking(candidate) || HasGatewaySourceExitCurl(candidate) || HasGatewaySourceDominantAxisDetour(candidate, sourceNode) || HasGatewaySourcePreferredFaceMismatch(candidate, sourceNode)) { continue; } var score = ScoreGatewayDirectRepairCandidate(path, candidate, sourceNode, continuationIndex); if (score >= bestScore) { continue; } bestScore = score; bestCandidate = candidate; } } if (bestCandidate is null) { return path; } return IsMaterialGatewaySourceRepairImprovement(path, bestCandidate) || IsGatewaySourceGeometryRepairImprovement(path, bestCandidate, sourceNode) ? bestCandidate : path; } private static List? TryBuildDominantAxisGatewaySourceBypassPath( ElkPositionedNode sourceNode, ElkPositionedNode targetNode, ElkPoint boundary, ElkPoint targetEndpoint, (double Left, double Top, double Right, double Bottom, string Id)[] obstacles, string? sourceNodeId, string? targetNodeId, bool dominantHorizontal, double desiredDx, double desiredDy) { const double padding = 8d; const double coordinateTolerance = 0.5d; var sourceId = sourceNodeId ?? string.Empty; var targetId = targetNodeId ?? string.Empty; List? bestCandidate = null; var bestScore = double.PositiveInfinity; void ConsiderCandidate(List rawCandidate) { var candidate = NormalizePathPoints(rawCandidate); if (candidate.Count < 2 || !IsPathClearOfObstacles(candidate, obstacles, sourceId, targetId) || !HasValidBoundaryAngle(candidate[^1], candidate[^2], targetNode) || HasGatewaySourceExitBacktracking(candidate) || HasGatewaySourceExitCurl(candidate) || HasGatewaySourceDominantAxisDetour(candidate, sourceNode) || HasGatewaySourcePreferredFaceMismatch(candidate, sourceNode)) { return; } var score = ComputePathLength(candidate) + (Math.Max(0, candidate.Count - 2) * 6d); if (score >= bestScore) { return; } bestScore = score; bestCandidate = candidate; } if (dominantHorizontal) { var movingRight = desiredDx >= 0d; var firstBlocker = obstacles .Where(ob => !string.Equals(ob.Id, sourceId, StringComparison.Ordinal) && !string.Equals(ob.Id, targetId, StringComparison.Ordinal) && boundary.Y > ob.Top + coordinateTolerance && boundary.Y < ob.Bottom - coordinateTolerance && (movingRight ? ob.Left > boundary.X + coordinateTolerance && ob.Left < targetEndpoint.X - coordinateTolerance : ob.Right < boundary.X - coordinateTolerance && ob.Right > targetEndpoint.X + coordinateTolerance)) .OrderBy(ob => movingRight ? ob.Left : -ob.Right) .FirstOrDefault(); if (string.IsNullOrWhiteSpace(firstBlocker.Id)) { return null; } var axisX = movingRight ? firstBlocker.Left - padding : firstBlocker.Right + padding; if (movingRight ? axisX <= boundary.X + 2d : axisX >= boundary.X - 2d) { return null; } var bypassYCandidates = new List(); AddUniqueCoordinate(bypassYCandidates, targetEndpoint.Y); AddUniqueCoordinate(bypassYCandidates, firstBlocker.Top - padding); AddUniqueCoordinate(bypassYCandidates, firstBlocker.Bottom + padding); foreach (var bypassY in bypassYCandidates) { var diagonalLead = new List { boundary }; var diagonalLeadPoint = new ElkPoint { X = axisX, Y = bypassY }; if (!ElkEdgeRoutingGeometry.PointsEqual(diagonalLead[^1], diagonalLeadPoint)) { diagonalLead.Add(diagonalLeadPoint); } AppendNonGatewayTargetBoundaryApproach(diagonalLead, targetNode, targetEndpoint); ConsiderCandidate(diagonalLead); var rebuilt = new List { boundary, new() { X = axisX, Y = boundary.Y }, }; if (Math.Abs(rebuilt[^1].Y - bypassY) > coordinateTolerance) { rebuilt.Add(new ElkPoint { X = axisX, Y = bypassY }); } AppendNonGatewayTargetBoundaryApproach(rebuilt, targetNode, targetEndpoint); ConsiderCandidate(rebuilt); } return bestCandidate; } var movingDown = desiredDy >= 0d; var verticalBlocker = obstacles .Where(ob => !string.Equals(ob.Id, sourceId, StringComparison.Ordinal) && !string.Equals(ob.Id, targetId, StringComparison.Ordinal) && boundary.X > ob.Left + coordinateTolerance && boundary.X < ob.Right - coordinateTolerance && (movingDown ? ob.Top > boundary.Y + coordinateTolerance && ob.Top < targetEndpoint.Y - coordinateTolerance : ob.Bottom < boundary.Y - coordinateTolerance && ob.Bottom > targetEndpoint.Y + coordinateTolerance)) .OrderBy(ob => movingDown ? ob.Top : -ob.Bottom) .FirstOrDefault(); if (string.IsNullOrWhiteSpace(verticalBlocker.Id)) { return null; } var axisY = movingDown ? verticalBlocker.Top - padding : verticalBlocker.Bottom + padding; if (movingDown ? axisY <= boundary.Y + 2d : axisY >= boundary.Y - 2d) { return null; } var bypassXCandidates = new List(); AddUniqueCoordinate(bypassXCandidates, targetEndpoint.X); AddUniqueCoordinate(bypassXCandidates, verticalBlocker.Left - padding); AddUniqueCoordinate(bypassXCandidates, verticalBlocker.Right + padding); foreach (var bypassX in bypassXCandidates) { var diagonalLead = new List { boundary }; var diagonalLeadPoint = new ElkPoint { X = bypassX, Y = axisY }; if (!ElkEdgeRoutingGeometry.PointsEqual(diagonalLead[^1], diagonalLeadPoint)) { diagonalLead.Add(diagonalLeadPoint); } AppendNonGatewayTargetBoundaryApproach(diagonalLead, targetNode, targetEndpoint); ConsiderCandidate(diagonalLead); var rebuilt = new List { boundary, new() { X = boundary.X, Y = axisY }, }; if (Math.Abs(rebuilt[^1].X - bypassX) > coordinateTolerance) { rebuilt.Add(new ElkPoint { X = bypassX, Y = axisY }); } AppendNonGatewayTargetBoundaryApproach(rebuilt, targetNode, targetEndpoint); ConsiderCandidate(rebuilt); } return bestCandidate; } }