namespace StellaOps.ElkSharp; internal static partial class ElkShapeBoundaries { private static double DistanceToSegment(ElkPoint point, ElkPoint start, ElkPoint end) { var deltaX = end.X - start.X; var deltaY = end.Y - start.Y; var lengthSquared = (deltaX * deltaX) + (deltaY * deltaY); if (lengthSquared <= 0.001d) { return Math.Sqrt(((point.X - start.X) * (point.X - start.X)) + ((point.Y - start.Y) * (point.Y - start.Y))); } var t = (((point.X - start.X) * deltaX) + ((point.Y - start.Y) * deltaY)) / lengthSquared; t = Math.Max(0d, Math.Min(1d, t)); var projectionX = start.X + (t * deltaX); var projectionY = start.Y + (t * deltaY); var distanceX = point.X - projectionX; var distanceY = point.Y - projectionY; return Math.Sqrt((distanceX * distanceX) + (distanceY * distanceY)); } private static bool TryGetGatewayBoundaryFace( ElkPositionedNode node, ElkPoint boundaryPoint, out ElkPoint faceStart, out ElkPoint faceEnd) { faceStart = default!; faceEnd = default!; var polygon = BuildGatewayBoundaryPoints(node); var bestDistance = double.PositiveInfinity; var bestIndex = -1; for (var index = 0; index < polygon.Count; index++) { var start = polygon[index]; var end = polygon[(index + 1) % polygon.Count]; var distance = DistanceToSegment(boundaryPoint, start, end); if (distance > 2d || distance >= bestDistance) { continue; } bestDistance = distance; bestIndex = index; } if (bestIndex < 0) { return false; } faceStart = polygon[bestIndex]; faceEnd = polygon[(bestIndex + 1) % polygon.Count]; return true; } private static bool IsDisallowedGatewayVertex( ElkPositionedNode node, ElkPoint boundaryPoint) { return IsNearGatewayVertex(node, boundaryPoint, GatewayVertexTolerance) && !IsAllowedGatewayTipVertex(node, boundaryPoint, GatewayVertexTolerance); } private static (double X, double Y) BuildGatewayFaceNormal( ElkPositionedNode node, ElkPoint faceStart, ElkPoint faceEnd, ElkPoint boundaryPoint) { var deltaX = faceEnd.X - faceStart.X; var deltaY = faceEnd.Y - faceStart.Y; var length = Math.Sqrt((deltaX * deltaX) + (deltaY * deltaY)); if (length <= 0.001d) { return (0d, -1d); } var normalAX = deltaY / length; var normalAY = -deltaX / length; var normalBX = -normalAX; var normalBY = -normalAY; var centerX = node.X + (node.Width / 2d); var centerY = node.Y + (node.Height / 2d); var centerToBoundaryX = boundaryPoint.X - centerX; var centerToBoundaryY = boundaryPoint.Y - centerY; var dotA = (normalAX * centerToBoundaryX) + (normalAY * centerToBoundaryY); var dotB = (normalBX * centerToBoundaryX) + (normalBY * centerToBoundaryY); return dotA >= dotB ? (normalAX, normalAY) : (normalBX, normalBY); } private static double ComputeRayExitDistanceFromBoundingBox( ElkPositionedNode node, ElkPoint origin, double directionX, double directionY) { const double epsilon = 0.0001d; var bestDistance = double.PositiveInfinity; if (directionX > epsilon) { bestDistance = Math.Min(bestDistance, (node.X + node.Width - origin.X) / directionX); } else if (directionX < -epsilon) { bestDistance = Math.Min(bestDistance, (node.X - origin.X) / directionX); } if (directionY > epsilon) { bestDistance = Math.Min(bestDistance, (node.Y + node.Height - origin.Y) / directionY); } else if (directionY < -epsilon) { bestDistance = Math.Min(bestDistance, (node.Y - origin.Y) / directionY); } if (double.IsInfinity(bestDistance) || bestDistance < 0d) { return 0d; } return bestDistance; } }