namespace StellaOps.ElkSharp; internal static partial class ElkEdgePostProcessor { private static List FixGatewaySourcePreferredFace( IReadOnlyList sourcePath, ElkPositionedNode sourceNode) { if (!HasGatewaySourcePreferredFaceMismatch(sourcePath, sourceNode)) { return sourcePath.Select(point => new ElkPoint { X = point.X, Y = point.Y }).ToList(); } var path = sourcePath .Select(point => new ElkPoint { X = point.X, Y = point.Y }) .ToList(); var firstExteriorIndex = FindFirstGatewayExteriorPointIndex(path, sourceNode); var continuationIndex = FindGatewaySourceCurlRecoveryIndex(path, firstExteriorIndex) ?? FindPreferredGatewayExitContinuationIndex(path, sourceNode, firstExteriorIndex); var continuationPoint = path[continuationIndex]; if (!TryResolvePreferredGatewaySourceBoundary(sourceNode, continuationPoint, path[^1], out var preferredBoundary)) { return path; } return BuildGatewaySourceRepairPath( path, sourceNode, preferredBoundary, continuationPoint, continuationIndex, path[^1]); } private static bool HasGatewaySourcePreferredFaceMismatch( IReadOnlyList path, ElkPositionedNode sourceNode) { if (!ElkShapeBoundaries.IsGatewayShape(sourceNode) || path.Count < 2) { return false; } 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 boundaryDx = path[0].X - centerX; var boundaryDy = path[0].Y - centerY; if (Math.Abs(desiredDx) >= Math.Abs(desiredDy) * 1.25d) { return Math.Sign(boundaryDx) != Math.Sign(desiredDx) || Math.Abs(boundaryDy) > sourceNode.Height * 0.28d; } if (Math.Abs(desiredDy) >= Math.Abs(desiredDx) * 1.25d) { return Math.Sign(boundaryDy) != Math.Sign(desiredDy) || Math.Abs(boundaryDx) > sourceNode.Width * 0.28d; } return false; } private static List FixGatewaySourceExitCurl( IReadOnlyList sourcePath, ElkPositionedNode sourceNode) { if (!ElkShapeBoundaries.IsGatewayShape(sourceNode) || sourcePath.Count < 3) { return sourcePath.Select(point => new ElkPoint { X = point.X, Y = point.Y }).ToList(); } var sample = sourcePath .Take(Math.Min(sourcePath.Count, 6)) .ToArray(); var desiredDx = sourcePath[^1].X - sourcePath[0].X; var desiredDy = sourcePath[^1].Y - sourcePath[0].Y; if (!HasGatewaySourceExitCurl(sample)) { return sourcePath.Select(point => new ElkPoint { X = point.X, Y = point.Y }).ToList(); } var path = sourcePath .Select(point => new ElkPoint { X = point.X, Y = point.Y }) .ToList(); var firstExteriorIndex = FindFirstGatewayExteriorPointIndex(path, sourceNode); var continuationIndex = FindGatewaySourceCurlRecoveryIndex(path, firstExteriorIndex) ?? FindPreferredGatewayExitContinuationIndex(path, sourceNode, firstExteriorIndex); var continuationPoint = path[continuationIndex]; var boundary = sourceNode.Kind == "Decision" ? ResolveDecisionSourceExitBoundary(sourceNode, continuationPoint, continuationPoint) : PreferGatewaySourceExitBoundary( sourceNode, ElkShapeBoundaries.ProjectOntoShapeBoundary(sourceNode, continuationPoint), continuationPoint); var continuationAligned = BuildGatewaySourceRepairPath( path, sourceNode, boundary, continuationPoint, continuationIndex, continuationPoint); if (PathChanged(path, continuationAligned) && !HasGatewaySourceExitBacktracking(continuationAligned) && !HasGatewaySourceExitCurl(continuationAligned)) { return continuationAligned; } var collapsedCurl = TryBuildGatewaySourceDominantAxisShortcut(path, sourceNode, path[0]); if (collapsedCurl is not null && PathChanged(path, collapsedCurl) && !HasGatewaySourceExitBacktracking(collapsedCurl) && !HasGatewaySourceExitCurl(collapsedCurl)) { return collapsedCurl; } const double axisTolerance = 4d; var rebuilt = path; var dominantHorizontal = Math.Abs(desiredDx) >= Math.Abs(desiredDy) * 1.15d && Math.Sign(desiredDx) != 0; var dominantVertical = Math.Abs(desiredDy) >= Math.Abs(desiredDx) * 1.15d && Math.Sign(desiredDy) != 0; if (dominantHorizontal && Math.Abs(rebuilt[1].Y - rebuilt[0].Y) <= axisTolerance) { rebuilt[1] = new ElkPoint { X = rebuilt[1].X, Y = rebuilt[0].Y, }; } else if (dominantVertical && Math.Abs(rebuilt[1].X - rebuilt[0].X) <= axisTolerance) { rebuilt[1] = new ElkPoint { X = rebuilt[0].X, Y = rebuilt[1].Y, }; } return NormalizePathPoints(rebuilt); } private static List FixGatewaySourceDominantAxisDetour( IReadOnlyList sourcePath, ElkPositionedNode sourceNode) { var path = sourcePath .Select(point => new ElkPoint { X = point.X, Y = point.Y }) .ToList(); if (!HasGatewaySourceDominantAxisDetour(path, sourceNode)) { return path; } var boundary = path[0]; var desiredDx = path[^1].X - boundary.X; var desiredDy = path[^1].Y - boundary.Y; var dominantHorizontal = Math.Abs(desiredDx) >= Math.Abs(desiredDy) * 1.15d && Math.Sign(desiredDx) != 0; var dominantVertical = Math.Abs(desiredDy) >= Math.Abs(desiredDx) * 1.15d && Math.Sign(desiredDy) != 0; var firstExteriorIndex = FindFirstGatewayExteriorPointIndex(path, sourceNode); var boundaryReferencePoint = path[firstExteriorIndex]; if (!TryResolvePreferredGatewaySourceBoundary(sourceNode, boundaryReferencePoint, path[^1], out var preferredBoundary)) { return path; } var localContinuationPoint = path[firstExteriorIndex]; var localRepair = new List { preferredBoundary }; if (!ElkEdgeRoutingGeometry.PointsEqual(localRepair[^1], localContinuationPoint)) { AppendGatewayOrthogonalCorner( localRepair, localRepair[^1], localContinuationPoint, firstExteriorIndex + 1 < path.Count ? path[firstExteriorIndex + 1] : null, preferHorizontalFromReference: ShouldPreferHorizontalGatewayExit(localRepair[^1], localContinuationPoint)); if (!ElkEdgeRoutingGeometry.PointsEqual(localRepair[^1], localContinuationPoint)) { localRepair.Add(localContinuationPoint); } } for (var i = firstExteriorIndex + 1; i < path.Count; i++) { localRepair.Add(path[i]); } localRepair = NormalizePathPoints(localRepair); if (PathChanged(path, localRepair) && !HasGatewaySourceExitBacktracking(localRepair) && !HasGatewaySourceExitCurl(localRepair) && !HasGatewaySourceDominantAxisDetour(localRepair, sourceNode) && !HasGatewaySourcePreferredFaceMismatch(localRepair, sourceNode)) { return localRepair; } var dominantAxisShortcut = TryBuildGatewaySourceDominantAxisShortcut(path, sourceNode, preferredBoundary); if (dominantAxisShortcut is not null && PathChanged(path, dominantAxisShortcut) && !HasGatewaySourceExitBacktracking(dominantAxisShortcut) && !HasGatewaySourceExitCurl(dominantAxisShortcut) && !HasGatewaySourceDominantAxisDetour(dominantAxisShortcut, sourceNode) && !HasGatewaySourcePreferredFaceMismatch(dominantAxisShortcut, sourceNode)) { return dominantAxisShortcut; } var preferredContinuationIndex = FindPreferredGatewayExitContinuationIndex(path, sourceNode, firstExteriorIndex); var candidateContinuationIndices = new[] { firstExteriorIndex, Math.Min(path.Count - 1, firstExteriorIndex + 1), Math.Min(path.Count - 1, firstExteriorIndex + 2), preferredContinuationIndex, } .Distinct() .Where(index => index >= firstExteriorIndex && index < path.Count) .ToArray(); List? bestCandidate = null; var bestScore = double.PositiveInfinity; foreach (var continuationIndex in candidateContinuationIndices) { var continuationCandidates = new List { path[continuationIndex], }; if (dominantHorizontal) { AddUniquePoint( continuationCandidates, new ElkPoint { X = path[continuationIndex].X, Y = preferredBoundary.Y, }); } else if (dominantVertical) { AddUniquePoint( continuationCandidates, new ElkPoint { X = preferredBoundary.X, Y = path[continuationIndex].Y, }); } foreach (var continuationPoint in continuationCandidates) { var candidate = BuildGatewaySourceRepairPath( path, sourceNode, preferredBoundary, continuationPoint, continuationIndex, continuationPoint); if (!PathChanged(path, candidate)) { continue; } var score = ComputePathLength(candidate); if (!ElkEdgeRoutingGeometry.PointsEqual(continuationPoint, path[continuationIndex])) { score -= 18d; } if (HasGatewaySourceExitBacktracking(candidate) || HasGatewaySourceExitCurl(candidate)) { score += 100_000d; } if (HasGatewaySourceDominantAxisDetour(candidate, sourceNode)) { score += 50_000d; } if (HasGatewaySourcePreferredFaceMismatch(candidate, sourceNode)) { score += 25_000d; } if (score >= bestScore) { continue; } bestScore = score; bestCandidate = candidate; } } if (bestCandidate is null || HasGatewaySourceExitBacktracking(bestCandidate) || HasGatewaySourceExitCurl(bestCandidate) || HasGatewaySourceDominantAxisDetour(bestCandidate, sourceNode) || HasGatewaySourcePreferredFaceMismatch(bestCandidate, sourceNode)) { return path; } return bestCandidate; } }