namespace StellaOps.ElkSharp; internal static partial class ElkShapeBoundaries { internal static bool TryProjectGatewayBoundarySlot( ElkPositionedNode node, string side, double slotCoordinate, out ElkPoint boundaryPoint) { boundaryPoint = default!; if (!IsGatewayShape(node)) { return false; } var candidates = new List(); var polygon = BuildGatewayBoundaryPoints(node); switch (side) { case "left": case "right": { var y = Math.Max(node.Y + 4d, Math.Min(node.Y + node.Height - 4d, slotCoordinate)); for (var index = 0; index < polygon.Count; index++) { var start = polygon[index]; var end = polygon[(index + 1) % polygon.Count]; AddGatewaySlotIntersections(candidates, TryIntersectHorizontalSlot(start, end, y)); } if (candidates.Count == 0) { return false; } boundaryPoint = side == "left" ? candidates.OrderBy(point => point.X).ThenBy(point => point.Y).First() : candidates.OrderByDescending(point => point.X).ThenBy(point => point.Y).First(); boundaryPoint = PreferGatewayEdgeInteriorBoundary( node, boundaryPoint, new ElkPoint { X = side == "left" ? node.X - 32d : node.X + node.Width + 32d, Y = y, }); return true; } case "top": case "bottom": { var x = Math.Max(node.X + 4d, Math.Min(node.X + node.Width - 4d, slotCoordinate)); for (var index = 0; index < polygon.Count; index++) { var start = polygon[index]; var end = polygon[(index + 1) % polygon.Count]; AddGatewaySlotIntersections(candidates, TryIntersectVerticalSlot(start, end, x)); } if (candidates.Count == 0) { return false; } boundaryPoint = side == "top" ? candidates.OrderBy(point => point.Y).ThenBy(point => point.X).First() : candidates.OrderByDescending(point => point.Y).ThenBy(point => point.X).First(); boundaryPoint = PreferGatewayEdgeInteriorBoundary( node, boundaryPoint, new ElkPoint { X = x, Y = side == "top" ? node.Y - 32d : node.Y + node.Height + 32d, }); return true; } default: return false; } } private static void AddGatewaySlotIntersections( ICollection candidates, IEnumerable intersections) { foreach (var candidate in intersections) { if (candidates.Any(existing => Math.Abs(existing.X - candidate.X) <= CoordinateTolerance && Math.Abs(existing.Y - candidate.Y) <= CoordinateTolerance)) { continue; } candidates.Add(candidate); } } private static IEnumerable TryIntersectHorizontalSlot( ElkPoint start, ElkPoint end, double y) { if (Math.Abs(start.Y - end.Y) <= CoordinateTolerance) { if (Math.Abs(y - start.Y) > CoordinateTolerance) { yield break; } yield return new ElkPoint { X = start.X, Y = y }; if (Math.Abs(end.X - start.X) > CoordinateTolerance) { yield return new ElkPoint { X = end.X, Y = y }; } yield break; } var minY = Math.Min(start.Y, end.Y) - CoordinateTolerance; var maxY = Math.Max(start.Y, end.Y) + CoordinateTolerance; if (y < minY || y > maxY) { yield break; } var t = (y - start.Y) / (end.Y - start.Y); if (t < -CoordinateTolerance || t > 1d + CoordinateTolerance) { yield break; } yield return new ElkPoint { X = start.X + ((end.X - start.X) * t), Y = y, }; } private static IEnumerable TryIntersectVerticalSlot( ElkPoint start, ElkPoint end, double x) { if (Math.Abs(start.X - end.X) <= CoordinateTolerance) { if (Math.Abs(x - start.X) > CoordinateTolerance) { yield break; } yield return new ElkPoint { X = x, Y = start.Y }; if (Math.Abs(end.Y - start.Y) > CoordinateTolerance) { yield return new ElkPoint { X = x, Y = end.Y }; } yield break; } var minX = Math.Min(start.X, end.X) - CoordinateTolerance; var maxX = Math.Max(start.X, end.X) + CoordinateTolerance; if (x < minX || x > maxX) { yield break; } var t = (x - start.X) / (end.X - start.X); if (t < -CoordinateTolerance || t > 1d + CoordinateTolerance) { yield break; } yield return new ElkPoint { X = x, Y = start.Y + ((end.Y - start.Y) * t), }; } }