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

112 lines
4.6 KiB
C#

namespace StellaOps.ElkSharp;
internal static partial class ElkEdgePostProcessorSimplify
{
internal static ElkRoutedEdge[] SimplifyEdgePaths(
ElkRoutedEdge[] edges,
ElkPositionedNode[] nodes)
{
var obstacles = nodes.Select(n => (L: n.X - 4d, T: n.Y - 4d, R: n.X + n.Width + 4d, B: n.Y + n.Height + 4d, Id: n.Id)).ToArray();
var graphMinY = nodes.Length > 0 ? nodes.Min(n => n.Y) : 0d;
var graphMaxY = nodes.Length > 0 ? nodes.Max(n => n.Y + n.Height) : 0d;
var result = new ElkRoutedEdge[edges.Length];
for (var i = 0; i < edges.Length; i++)
{
var edge = edges[i];
var excludeIds = new HashSet<string>(StringComparer.Ordinal) { edge.SourceNodeId ?? "", edge.TargetNodeId ?? "" };
var anyChanged = false;
var newSections = new List<ElkEdgeSection>(edge.Sections.Count);
var hasCorridor = ElkEdgePostProcessor.HasCorridorBendPoints(edge, graphMinY, graphMaxY);
foreach (var section in edge.Sections)
{
var pts = new List<ElkPoint> { section.StartPoint };
pts.AddRange(section.BendPoints);
pts.Add(section.EndPoint);
// Pass 1: Remove collinear points
var cleaned = new List<ElkPoint> { pts[0] };
for (var j = 1; j < pts.Count - 1; j++)
{
var prev = cleaned[^1];
var curr = pts[j];
var next = pts[j + 1];
var sameX = Math.Abs(prev.X - curr.X) < 1d && Math.Abs(curr.X - next.X) < 1d;
var sameY = Math.Abs(prev.Y - curr.Y) < 1d && Math.Abs(curr.Y - next.Y) < 1d;
if (sameX || sameY)
{
anyChanged = true;
}
else
{
cleaned.Add(curr);
}
}
cleaned.Add(pts[^1]);
// Pass 2: Try L-shape shortcuts for each triple (skip for corridor-routed edges)
var changed = !hasCorridor;
var simplifyPass = 0;
while (changed && simplifyPass++ < 20)
{
changed = false;
for (var j = 0; j + 2 < cleaned.Count; j++)
{
var a = cleaned[j];
var c = cleaned[j + 2];
var corner1 = new ElkPoint { X = a.X, Y = c.Y };
var corner2 = new ElkPoint { X = c.X, Y = a.Y };
foreach (var corner in new[] { corner1, corner2 })
{
if (SegmentClearsObstacles(a, corner, obstacles, excludeIds)
&& SegmentClearsObstacles(corner, c, obstacles, excludeIds))
{
cleaned[j + 1] = corner;
changed = true;
anyChanged = true;
break;
}
}
}
if (!changed && TryApplyOrthogonalShortcut(cleaned, obstacles, excludeIds))
{
changed = true;
anyChanged = true;
}
}
// Remove trailing duplicates (bend point == endpoint)
while (cleaned.Count > 2
&& Math.Abs(cleaned[^1].X - cleaned[^2].X) < 1d
&& Math.Abs(cleaned[^1].Y - cleaned[^2].Y) < 1d)
{
cleaned.RemoveAt(cleaned.Count - 2);
}
// Remove leading duplicates (start point == first bend)
while (cleaned.Count > 2
&& Math.Abs(cleaned[0].X - cleaned[1].X) < 1d
&& Math.Abs(cleaned[0].Y - cleaned[1].Y) < 1d)
{
cleaned.RemoveAt(1);
}
newSections.Add(new ElkEdgeSection
{
StartPoint = cleaned[0],
EndPoint = cleaned[^1],
BendPoints = cleaned.Skip(1).Take(cleaned.Count - 2).ToArray(),
});
}
result[i] = anyChanged
? new ElkRoutedEdge { Id = edge.Id, SourceNodeId = edge.SourceNodeId, TargetNodeId = edge.TargetNodeId, Label = edge.Label, Sections = newSections }
: edge;
}
return result;
}
}