183 lines
5.6 KiB
C#
183 lines
5.6 KiB
C#
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<ElkPoint>();
|
|
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<ElkPoint> candidates,
|
|
IEnumerable<ElkPoint> 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<ElkPoint> 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<ElkPoint> 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),
|
|
};
|
|
}
|
|
}
|