Files
git.stella-ops.org/src/__Libraries/StellaOps.ElkSharp/ElkEdgePostProcessor.BoundarySlots.LaneShift.cs
master d04483560b Complete ElkSharp document rendering cleanup and source decomposition
- Fix target-join (edge/4+edge/17): gateway face overflow redirect to left tip
- Fix under-node (edge/14,15,20): push-first corridor reroute instead of top corridor
- Fix boundary-slots (4->0): snap after gateway polish reordering
- Fix gateway corner diagonals (2->0): post-pipeline straightening pass
- Fix gateway interior adjacent: polygon-aware IsInsideNodeShapeInterior
- Fix gateway source face mismatch (2->0): per-edge redirect with lenient validation
- Fix gateway source scoring (5->0): per-edge scoring candidate application
- Fix edge-node crossing (1->0): push horizontal segment above blocking node
- Decompose 7 oversized files (~20K lines) into 55+ partials under 400 lines each
- Archive sprints 004 (document cleanup), 005 (decomposition), 007 (render speed)

All 44+ document-processing artifact assertions pass. Hybrid deterministic mode
documented as recommended path for LeftToRight layouts.

Tests verified: StraightExit 2/2, BoundarySlotOffenders 2/2, HybridDeterministicMode 3/3,
DocumentProcessingWorkflow artifact 1/1.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 14:16:10 +03:00

156 lines
5.1 KiB
C#

namespace StellaOps.ElkSharp;
internal static partial class ElkEdgePostProcessor
{
private static List<ElkPoint> ShiftSingleOrthogonalRun(
IReadOnlyList<ElkPoint> path,
int segmentIndex,
double desiredCoordinate)
{
var candidate = path
.Select(point => new ElkPoint { X = point.X, Y = point.Y })
.ToList();
if (segmentIndex < 0 || segmentIndex >= candidate.Count - 1)
{
return candidate;
}
var start = candidate[segmentIndex];
var end = candidate[segmentIndex + 1];
if (Math.Abs(start.Y - end.Y) <= 0.5d)
{
var original = start.Y;
for (var i = 0; i < candidate.Count; i++)
{
if (Math.Abs(candidate[i].Y - original) <= 0.5d)
{
candidate[i] = new ElkPoint { X = candidate[i].X, Y = desiredCoordinate };
}
}
}
else if (Math.Abs(start.X - end.X) <= 0.5d)
{
var original = start.X;
for (var i = 0; i < candidate.Count; i++)
{
if (Math.Abs(candidate[i].X - original) <= 0.5d)
{
candidate[i] = new ElkPoint { X = desiredCoordinate, Y = candidate[i].Y };
}
}
}
return NormalizePathPoints(candidate);
}
private static List<ElkPoint> ShiftStraightOrthogonalPath(
IReadOnlyList<ElkPoint> path,
double desiredCoordinate)
{
var candidate = path
.Select(point => new ElkPoint { X = point.X, Y = point.Y })
.ToList();
if (candidate.Count != 2)
{
return candidate;
}
var start = candidate[0];
var end = candidate[1];
if (Math.Abs(start.Y - end.Y) <= 0.5d)
{
return NormalizePathPoints(
[
new ElkPoint { X = start.X, Y = start.Y },
new ElkPoint { X = start.X, Y = desiredCoordinate },
new ElkPoint { X = end.X, Y = desiredCoordinate },
new ElkPoint { X = end.X, Y = end.Y },
]);
}
if (Math.Abs(start.X - end.X) <= 0.5d)
{
return NormalizePathPoints(
[
new ElkPoint { X = start.X, Y = start.Y },
new ElkPoint { X = desiredCoordinate, Y = start.Y },
new ElkPoint { X = desiredCoordinate, Y = end.Y },
new ElkPoint { X = end.X, Y = end.Y },
]);
}
return candidate;
}
private static double[] ResolveLaneShiftCoordinates(
ElkPoint start,
ElkPoint end,
ElkPoint otherStart,
ElkPoint otherEnd,
double minLineClearance)
{
var offset = minLineClearance + 4d;
if (Math.Abs(start.Y - end.Y) <= 0.5d && Math.Abs(otherStart.Y - otherEnd.Y) <= 0.5d)
{
var lower = otherStart.Y - offset;
var upper = otherStart.Y + offset;
return start.Y <= otherStart.Y
? [lower, upper]
: [upper, lower];
}
if (Math.Abs(start.X - end.X) <= 0.5d && Math.Abs(otherStart.X - otherEnd.X) <= 0.5d)
{
var lower = otherStart.X - offset;
var upper = otherStart.X + offset;
return start.X <= otherStart.X
? [lower, upper]
: [upper, lower];
}
return [];
}
private static bool SegmentLeavesGraphBand(
IReadOnlyList<ElkPoint> path,
double graphMinY,
double graphMaxY)
{
return path.Any(point => point.Y < graphMinY - 96d || point.Y > graphMaxY + 96d);
}
private static bool SegmentsShareLane(
ElkPoint leftStart,
ElkPoint leftEnd,
ElkPoint rightStart,
ElkPoint rightEnd,
double minLineClearance)
{
var laneTolerance = Math.Max(4d, Math.Min(12d, minLineClearance * 0.2d));
var minSharedLength = Math.Max(24d, minLineClearance * 0.4d);
if (Math.Abs(leftStart.Y - leftEnd.Y) <= 0.5d
&& Math.Abs(rightStart.Y - rightEnd.Y) <= 0.5d
&& Math.Abs(leftStart.Y - rightStart.Y) <= laneTolerance)
{
var leftMinX = Math.Min(leftStart.X, leftEnd.X);
var leftMaxX = Math.Max(leftStart.X, leftEnd.X);
var rightMinX = Math.Min(rightStart.X, rightEnd.X);
var rightMaxX = Math.Max(rightStart.X, rightEnd.X);
return Math.Min(leftMaxX, rightMaxX) - Math.Max(leftMinX, rightMinX) >= minSharedLength;
}
if (Math.Abs(leftStart.X - leftEnd.X) <= 0.5d
&& Math.Abs(rightStart.X - rightEnd.X) <= 0.5d
&& Math.Abs(leftStart.X - rightStart.X) <= laneTolerance)
{
var leftMinY = Math.Min(leftStart.Y, leftEnd.Y);
var leftMaxY = Math.Max(leftStart.Y, leftEnd.Y);
var rightMinY = Math.Min(rightStart.Y, rightEnd.Y);
var rightMaxY = Math.Max(rightStart.Y, rightEnd.Y);
return Math.Min(leftMaxY, rightMaxY) - Math.Max(leftMinY, rightMinY) >= minSharedLength;
}
return false;
}
}