132 lines
4.2 KiB
C#
132 lines
4.2 KiB
C#
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;
|
|
}
|
|
}
|