Files
git.stella-ops.org/src/__Libraries/StellaOps.ElkSharp/ElkShapeBoundaries.BoundarySlots.cs

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),
};
}
}