275 lines
8.8 KiB
C#
275 lines
8.8 KiB
C#
namespace StellaOps.ElkSharp;
|
|
|
|
internal static partial class ElkEdgeRouterIterative
|
|
{
|
|
private static double ScoreProtectedCollectorGatewaySourceExitCandidate(
|
|
IReadOnlyList<ElkPoint> path,
|
|
ElkPositionedNode sourceNode,
|
|
ElkPoint exitReference)
|
|
{
|
|
var score = 0d;
|
|
if (path.Count < 2)
|
|
{
|
|
return double.PositiveInfinity;
|
|
}
|
|
|
|
var boundary = path[0];
|
|
var adjacent = path[1];
|
|
var centerX = sourceNode.X + (sourceNode.Width / 2d);
|
|
var centerY = sourceNode.Y + (sourceNode.Height / 2d);
|
|
var desiredDx = exitReference.X - centerX;
|
|
var desiredDy = exitReference.Y - centerY;
|
|
var boundaryDx = boundary.X - centerX;
|
|
var boundaryDy = boundary.Y - centerY;
|
|
var firstDx = adjacent.X - boundary.X;
|
|
var firstDy = adjacent.Y - boundary.Y;
|
|
const double tolerance = 0.5d;
|
|
|
|
var dominantHorizontal = Math.Abs(desiredDx) >= Math.Abs(desiredDy) * 1.15d && Math.Sign(desiredDx) != 0;
|
|
var dominantVertical = Math.Abs(desiredDy) >= Math.Abs(desiredDx) * 1.15d && Math.Sign(desiredDy) != 0;
|
|
if (dominantHorizontal)
|
|
{
|
|
if (Math.Sign(firstDx) != Math.Sign(desiredDx) || Math.Abs(firstDx) <= tolerance)
|
|
{
|
|
score += 100_000d;
|
|
}
|
|
|
|
if (Math.Abs(boundaryDy) > sourceNode.Height * 0.28d)
|
|
{
|
|
score += 25_000d;
|
|
}
|
|
|
|
score += Math.Abs(boundaryDy) * 6d;
|
|
}
|
|
else if (dominantVertical)
|
|
{
|
|
if (Math.Sign(firstDy) != Math.Sign(desiredDy) || Math.Abs(firstDy) <= tolerance)
|
|
{
|
|
score += 100_000d;
|
|
}
|
|
|
|
if (Math.Abs(boundaryDx) > sourceNode.Width * 0.28d)
|
|
{
|
|
score += 25_000d;
|
|
}
|
|
|
|
score += Math.Abs(boundaryDx) * 6d;
|
|
}
|
|
|
|
if (ElkShapeBoundaries.IsNearGatewayVertex(sourceNode, boundary, 8d))
|
|
{
|
|
score += 4_000d;
|
|
}
|
|
|
|
var length = 0d;
|
|
for (var i = 1; i < path.Count; i++)
|
|
{
|
|
length += ElkEdgeRoutingGeometry.ComputeSegmentLength(path[i - 1], path[i]);
|
|
}
|
|
|
|
return score
|
|
+ length
|
|
+ (Math.Max(0, path.Count - 2) * 6d);
|
|
}
|
|
|
|
private static List<ElkPoint> NormalizeProtectedCollectorTail(
|
|
IReadOnlyList<ElkPoint> path,
|
|
double graphMinY,
|
|
double graphMaxY)
|
|
{
|
|
if (path.Count < 5)
|
|
{
|
|
return path.Select(point => new ElkPoint { X = point.X, Y = point.Y }).ToList();
|
|
}
|
|
|
|
const double corridorTolerance = 8d;
|
|
const double coordinateTolerance = 0.5d;
|
|
var firstCorridorIndex = -1;
|
|
var lastCorridorIndex = -1;
|
|
for (var i = 0; i < path.Count; i++)
|
|
{
|
|
if (path[i].Y < graphMinY - corridorTolerance || path[i].Y > graphMaxY + corridorTolerance)
|
|
{
|
|
if (firstCorridorIndex < 0)
|
|
{
|
|
firstCorridorIndex = i;
|
|
}
|
|
|
|
lastCorridorIndex = i;
|
|
}
|
|
}
|
|
|
|
if (firstCorridorIndex < 0
|
|
|| lastCorridorIndex <= firstCorridorIndex
|
|
|| lastCorridorIndex >= path.Count - 1
|
|
|| path[firstCorridorIndex].Y > graphMinY - corridorTolerance)
|
|
{
|
|
return path.Select(point => new ElkPoint { X = point.X, Y = point.Y }).ToList();
|
|
}
|
|
|
|
var desiredDx = path[^1].X - path[0].X;
|
|
if (Math.Abs(desiredDx) <= coordinateTolerance)
|
|
{
|
|
return path.Select(point => new ElkPoint { X = point.X, Y = point.Y }).ToList();
|
|
}
|
|
|
|
var preCorridorHorizontalIndex = -1;
|
|
for (var i = 1; i < lastCorridorIndex; i++)
|
|
{
|
|
if (Math.Abs(path[i].Y - path[i + 1].Y) > coordinateTolerance
|
|
|| Math.Abs(path[i].X - path[i + 1].X) <= coordinateTolerance)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var horizontalDelta = path[i + 1].X - path[i].X;
|
|
if (Math.Sign(horizontalDelta) == 0 || Math.Sign(horizontalDelta) == Math.Sign(desiredDx))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
preCorridorHorizontalIndex = i;
|
|
break;
|
|
}
|
|
|
|
if (preCorridorHorizontalIndex >= 0)
|
|
{
|
|
var rebuiltPrefix = path
|
|
.Take(preCorridorHorizontalIndex + 1)
|
|
.Select(point => new ElkPoint { X = point.X, Y = point.Y })
|
|
.ToList();
|
|
var rewrittenCorridorY = path[lastCorridorIndex].Y;
|
|
var rewrittenReentryPoint = path[lastCorridorIndex + 1];
|
|
|
|
if (Math.Abs(rebuiltPrefix[^1].Y - rewrittenCorridorY) > coordinateTolerance)
|
|
{
|
|
rebuiltPrefix.Add(new ElkPoint
|
|
{
|
|
X = rebuiltPrefix[^1].X,
|
|
Y = rewrittenCorridorY,
|
|
});
|
|
}
|
|
|
|
if (Math.Abs(rebuiltPrefix[^1].X - rewrittenReentryPoint.X) > coordinateTolerance)
|
|
{
|
|
rebuiltPrefix.Add(new ElkPoint
|
|
{
|
|
X = rewrittenReentryPoint.X,
|
|
Y = rewrittenCorridorY,
|
|
});
|
|
}
|
|
|
|
for (var i = lastCorridorIndex + 1; i < path.Count; i++)
|
|
{
|
|
rebuiltPrefix.Add(new ElkPoint { X = path[i].X, Y = path[i].Y });
|
|
}
|
|
|
|
return NormalizeCollectorPoints(rebuiltPrefix);
|
|
}
|
|
|
|
var firstHorizontalIndex = -1;
|
|
for (var i = firstCorridorIndex; i < lastCorridorIndex; i++)
|
|
{
|
|
if (Math.Abs(path[i].Y - path[i + 1].Y) <= coordinateTolerance
|
|
&& Math.Abs(path[i].X - path[i + 1].X) > coordinateTolerance)
|
|
{
|
|
firstHorizontalIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (firstHorizontalIndex < 0)
|
|
{
|
|
return path.Select(point => new ElkPoint { X = point.X, Y = point.Y }).ToList();
|
|
}
|
|
|
|
var firstHorizontalDelta = path[firstHorizontalIndex + 1].X - path[firstHorizontalIndex].X;
|
|
if (Math.Sign(firstHorizontalDelta) == 0 || Math.Sign(firstHorizontalDelta) == Math.Sign(desiredDx))
|
|
{
|
|
return path.Select(point => new ElkPoint { X = point.X, Y = point.Y }).ToList();
|
|
}
|
|
|
|
var rebuilt = path
|
|
.Take(firstCorridorIndex + 1)
|
|
.Select(point => new ElkPoint { X = point.X, Y = point.Y })
|
|
.ToList();
|
|
var targetCorridorY = path[lastCorridorIndex].Y;
|
|
var reentryPoint = path[lastCorridorIndex + 1];
|
|
|
|
if (Math.Abs(rebuilt[^1].Y - targetCorridorY) > coordinateTolerance)
|
|
{
|
|
rebuilt.Add(new ElkPoint
|
|
{
|
|
X = rebuilt[^1].X,
|
|
Y = targetCorridorY,
|
|
});
|
|
}
|
|
|
|
if (Math.Abs(rebuilt[^1].X - reentryPoint.X) > coordinateTolerance)
|
|
{
|
|
rebuilt.Add(new ElkPoint
|
|
{
|
|
X = reentryPoint.X,
|
|
Y = targetCorridorY,
|
|
});
|
|
}
|
|
|
|
for (var i = lastCorridorIndex + 1; i < path.Count; i++)
|
|
{
|
|
rebuilt.Add(new ElkPoint { X = path[i].X, Y = path[i].Y });
|
|
}
|
|
|
|
return NormalizeCollectorPoints(rebuilt);
|
|
}
|
|
|
|
private static ElkPoint? BuildOrthogonalCollectorCorner(ElkPoint from, ElkPoint to)
|
|
{
|
|
const double tolerance = 0.5d;
|
|
if (Math.Abs(from.X - to.X) <= tolerance || Math.Abs(from.Y - to.Y) <= tolerance)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return Math.Abs(to.Y - from.Y) >= Math.Abs(to.X - from.X)
|
|
? new ElkPoint { X = from.X, Y = to.Y }
|
|
: new ElkPoint { X = to.X, Y = from.Y };
|
|
}
|
|
|
|
private static List<ElkPoint> NormalizeCollectorPoints(IReadOnlyList<ElkPoint> points)
|
|
{
|
|
const double coordinateTolerance = 0.5d;
|
|
var deduped = new List<ElkPoint>();
|
|
foreach (var point in points)
|
|
{
|
|
if (deduped.Count == 0 || !ElkEdgeRoutingGeometry.PointsEqual(deduped[^1], point))
|
|
{
|
|
deduped.Add(point);
|
|
}
|
|
}
|
|
|
|
if (deduped.Count <= 2)
|
|
{
|
|
return deduped;
|
|
}
|
|
|
|
var simplified = new List<ElkPoint> { deduped[0] };
|
|
for (var i = 1; i < deduped.Count - 1; i++)
|
|
{
|
|
var previous = simplified[^1];
|
|
var current = deduped[i];
|
|
var next = deduped[i + 1];
|
|
var sameX = Math.Abs(previous.X - current.X) <= coordinateTolerance
|
|
&& Math.Abs(current.X - next.X) <= coordinateTolerance;
|
|
var sameY = Math.Abs(previous.Y - current.Y) <= coordinateTolerance
|
|
&& Math.Abs(current.Y - next.Y) <= coordinateTolerance;
|
|
if (!sameX && !sameY)
|
|
{
|
|
simplified.Add(current);
|
|
}
|
|
}
|
|
|
|
simplified.Add(deduped[^1]);
|
|
return simplified;
|
|
}
|
|
}
|