namespace StellaOps.ElkSharp; internal static partial class ElkEdgePostProcessor { private static List TrimTargetApproachBacktracking( IReadOnlyList sourcePath, ElkPositionedNode targetNode, string side, ElkPoint explicitEndpoint) { if (sourcePath.Count < 4) { return sourcePath .Select(point => new ElkPoint { X = point.X, Y = point.Y }) .ToList(); } const double tolerance = 0.5d; var startIndex = Math.Max(0, sourcePath.Count - 5); var firstOffendingIndex = -1; for (var i = startIndex; i < sourcePath.Count - 1; i++) { if (IsOnWrongSideOfTarget(sourcePath[i], targetNode, side, tolerance)) { firstOffendingIndex = i; break; } } if (firstOffendingIndex < 0) { return sourcePath .Select(point => new ElkPoint { X = point.X, Y = point.Y }) .ToList(); } var trimmed = sourcePath .Take(Math.Max(1, firstOffendingIndex)) .Select(point => new ElkPoint { X = point.X, Y = point.Y }) .ToList(); if (trimmed.Count == 0 || !ElkEdgeRoutingGeometry.PointsEqual(trimmed[^1], explicitEndpoint)) { trimmed.Add(explicitEndpoint); } return NormalizeEntryPath(trimmed, targetNode, side, explicitEndpoint); } private static bool TryNormalizeNonGatewayBacktrackingEntry( IReadOnlyList sourcePath, ElkPositionedNode targetNode, out List repairedPath) { repairedPath = sourcePath .Select(point => new ElkPoint { X = point.X, Y = point.Y }) .ToList(); if (sourcePath.Count < 2) { return false; } if (!TryResolveNonGatewayBacktrackingEndpoint(sourcePath, targetNode, out var side, out var endpoint)) { return false; } var candidate = NormalizeEntryPath(sourcePath, targetNode, side, endpoint); if (HasTargetApproachBacktracking(candidate, targetNode)) { return false; } repairedPath = candidate; return true; } private static bool TryResolveNonGatewayBacktrackingEndpoint( IReadOnlyList sourcePath, ElkPositionedNode targetNode, out string side, out ElkPoint endpoint) { side = string.Empty; endpoint = default!; if (sourcePath.Count < 2) { return false; } var anchor = sourcePath[^2]; var centerX = targetNode.X + (targetNode.Width / 2d); var centerY = targetNode.Y + (targetNode.Height / 2d); var deltaX = anchor.X - centerX; var deltaY = anchor.Y - centerY; var dominantHorizontal = Math.Abs(deltaX) >= Math.Abs(deltaY) * 1.15d; side = dominantHorizontal ? (deltaX <= 0d ? "left" : "right") : (deltaY <= 0d ? "top" : "bottom"); if (side is "left" or "right") { endpoint = new ElkPoint { X = side == "left" ? targetNode.X : targetNode.X + targetNode.Width, Y = Math.Clamp(anchor.Y, targetNode.Y + 4d, targetNode.Y + targetNode.Height - 4d), }; } else { endpoint = new ElkPoint { X = Math.Clamp(anchor.X, targetNode.X + 4d, targetNode.X + targetNode.Width - 4d), Y = side == "top" ? targetNode.Y : targetNode.Y + targetNode.Height, }; } return true; } private static bool HasTargetApproachBacktracking( IReadOnlyList path, ElkPositionedNode targetNode) { if (path.Count < 3 || ElkShapeBoundaries.IsGatewayShape(targetNode)) { return false; } var side = ElkEdgeRoutingGeometry.ResolveBoundarySide(path[^1], targetNode); if (side is not "left" and not "right" and not "top" and not "bottom") { return false; } const double tolerance = 0.5d; if (HasShortOrthogonalTargetHook(path, targetNode, side, tolerance)) { return true; } var startIndex = Math.Max(0, path.Count - (side is "left" or "right" ? 4 : 3)); var axisValues = new List(path.Count - startIndex); for (var i = startIndex; i < path.Count; i++) { var value = side is "left" or "right" ? path[i].X : path[i].Y; if (axisValues.Count == 0 || Math.Abs(axisValues[^1] - value) > tolerance) { axisValues.Add(value); } } if (axisValues.Count < 3) { return false; } var targetAxis = side switch { "left" => targetNode.X, "right" => targetNode.X + targetNode.Width, "top" => targetNode.Y, "bottom" => targetNode.Y + targetNode.Height, _ => double.NaN, }; var overshootsTargetSide = side switch { "left" or "top" => axisValues.Any(value => value > targetAxis + tolerance), "right" or "bottom" => axisValues.Any(value => value < targetAxis - tolerance), _ => false, }; if (overshootsTargetSide) { return true; } var expectsIncreasing = side is "left" or "top"; var sawProgress = false; for (var i = 1; i < axisValues.Count; i++) { var delta = axisValues[i] - axisValues[i - 1]; if (Math.Abs(delta) <= tolerance) { continue; } if (expectsIncreasing) { if (delta > tolerance) { sawProgress = true; } else if (sawProgress) { return true; } } else { if (delta < -tolerance) { sawProgress = true; } else if (sawProgress) { return true; } } } return false; } private static bool HasShortOrthogonalTargetHook( IReadOnlyList path, ElkPositionedNode targetNode, string side, double tolerance) { if (path.Count < 3) { return false; } var boundaryPoint = path[^1]; var runStartIndex = path.Count - 2; if (side is "left" or "right") { while (runStartIndex > 0 && Math.Abs(path[runStartIndex - 1].Y - boundaryPoint.Y) <= tolerance) { runStartIndex--; } } else { while (runStartIndex > 0 && Math.Abs(path[runStartIndex - 1].X - boundaryPoint.X) <= tolerance) { runStartIndex--; } } if (runStartIndex == 0) { return false; } var overallDeltaX = path[^1].X - path[0].X; var overallDeltaY = path[^1].Y - path[0].Y; var overallAbsDx = Math.Abs(overallDeltaX); var overallAbsDy = Math.Abs(overallDeltaY); var sameRowThreshold = Math.Max(24d, targetNode.Height / 3d); var sameColumnThreshold = Math.Max(24d, targetNode.Width / 3d); var looksHorizontal = overallAbsDx >= overallAbsDy * 1.15d && overallAbsDy <= sameRowThreshold && Math.Sign(overallDeltaX) != 0; var looksVertical = overallAbsDy >= overallAbsDx * 1.15d && overallAbsDx <= sameColumnThreshold && Math.Sign(overallDeltaY) != 0; var contradictsDominantApproach = side switch { "left" or "right" => looksVertical, "top" or "bottom" => looksHorizontal, _ => false, }; if (!contradictsDominantApproach) { return false; } var runStart = path[runStartIndex]; var boundaryDepth = side is "left" or "right" ? Math.Abs(boundaryPoint.X - runStart.X) : Math.Abs(boundaryPoint.Y - runStart.Y); var requiredDepth = side is "left" or "right" ? targetNode.Width : targetNode.Height; if (boundaryDepth + tolerance >= requiredDepth) { return false; } var predecessor = path[runStartIndex - 1]; var predecessorDx = Math.Abs(runStart.X - predecessor.X); var predecessorDy = Math.Abs(runStart.Y - predecessor.Y); return side switch { "left" or "right" => predecessorDy > predecessorDx * 3d, "top" or "bottom" => predecessorDx > predecessorDy * 3d, _ => false, }; } private static bool IsOnWrongSideOfTarget( ElkPoint point, ElkPositionedNode targetNode, string side, double tolerance) { return side switch { "left" => point.X > targetNode.X + tolerance, "right" => point.X < (targetNode.X + targetNode.Width) - tolerance, "top" => point.Y > targetNode.Y + tolerance, "bottom" => point.Y < (targetNode.Y + targetNode.Height) - tolerance, _ => false, }; } }