elksharp stabilization

This commit is contained in:
master
2026-03-24 08:38:09 +02:00
parent d788ee757e
commit 71edccd485
18 changed files with 6083 additions and 36 deletions

View File

@@ -69,6 +69,12 @@ internal static class ElkEdgePostProcessorSimplify
}
}
}
if (!changed && TryApplyOrthogonalShortcut(cleaned, obstacles, excludeIds))
{
changed = true;
anyChanged = true;
}
}
// Remove trailing duplicates (bend point == endpoint)
@@ -110,8 +116,12 @@ internal static class ElkEdgePostProcessorSimplify
var graphMinY = nodes.Min(n => n.Y);
var graphMaxY = nodes.Max(n => n.Y + n.Height);
const double minMargin = 12d;
const double laneGap = 8d;
var serviceNodes = nodes.Where(n => n.Kind is not "Start" and not "End").ToArray();
var minLineClearance = serviceNodes.Length > 0
? Math.Min(serviceNodes.Average(n => n.Width), serviceNodes.Average(n => n.Height)) / 2d
: 50d;
var minMargin = Math.Max(12d, minLineClearance + 4d);
var laneGap = Math.Max(8d, minLineClearance + 4d);
var outerEdges = new List<(int Index, double CorridorY, bool IsAbove)>();
for (var i = 0; i < edges.Length; i++)
@@ -250,6 +260,110 @@ internal static class ElkEdgePostProcessorSimplify
return true;
}
private static bool TryApplyOrthogonalShortcut(
List<ElkPoint> points,
(double L, double T, double R, double B, string Id)[] obstacles,
HashSet<string> excludeIds)
{
if (points.Count < 4)
{
return false;
}
for (var startIndex = 0; startIndex < points.Count - 2; startIndex++)
{
for (var endIndex = points.Count - 1; endIndex >= startIndex + 2; endIndex--)
{
var start = points[startIndex];
var end = points[endIndex];
var existingLength = ComputeSubpathLength(points, startIndex, endIndex);
foreach (var shortcut in BuildShortcutCandidates(start, end))
{
if (shortcut.Count < 2)
{
continue;
}
if (!ShortcutClearsObstacles(shortcut, obstacles, excludeIds))
{
continue;
}
var shortcutLength = ComputePathLength(shortcut);
if (shortcutLength >= existingLength - 8d)
{
continue;
}
points.RemoveRange(startIndex + 1, endIndex - startIndex - 1);
if (shortcut.Count > 2)
{
points.InsertRange(startIndex + 1, shortcut.Skip(1).Take(shortcut.Count - 2));
}
return true;
}
}
}
return false;
}
private static IReadOnlyList<List<ElkPoint>> BuildShortcutCandidates(ElkPoint start, ElkPoint end)
{
var candidates = new List<List<ElkPoint>>();
if (Math.Abs(start.X - end.X) < 1d || Math.Abs(start.Y - end.Y) < 1d)
{
candidates.Add([start, end]);
return candidates;
}
var corner1 = new ElkPoint { X = start.X, Y = end.Y };
var corner2 = new ElkPoint { X = end.X, Y = start.Y };
candidates.Add([start, corner1, end]);
candidates.Add([start, corner2, end]);
return candidates;
}
private static bool ShortcutClearsObstacles(
IReadOnlyList<ElkPoint> shortcut,
(double L, double T, double R, double B, string Id)[] obstacles,
HashSet<string> excludeIds)
{
for (var i = 0; i < shortcut.Count - 1; i++)
{
if (!SegmentClearsObstacles(shortcut[i], shortcut[i + 1], obstacles, excludeIds))
{
return false;
}
}
return true;
}
private static double ComputeSubpathLength(IReadOnlyList<ElkPoint> points, int startIndex, int endIndex)
{
var length = 0d;
for (var i = startIndex; i < endIndex; i++)
{
length += ElkEdgeRoutingGeometry.ComputeSegmentLength(points[i], points[i + 1]);
}
return length;
}
private static double ComputePathLength(IReadOnlyList<ElkPoint> points)
{
var length = 0d;
for (var i = 0; i < points.Count - 1; i++)
{
length += ElkEdgeRoutingGeometry.ComputeSegmentLength(points[i], points[i + 1]);
}
return length;
}
private static void NormalizeCorridorYValues(
List<(int Index, double CorridorY, bool IsAbove)> outerEdges,
ElkRoutedEdge[] edges,