112 lines
4.6 KiB
C#
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;
|
|
}
|
|
|
|
}
|