Files
git.stella-ops.org/src/__Libraries/StellaOps.ElkSharp/ElkEdgePostProcessor.BoundarySlots.SharedLane.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

292 lines
10 KiB
C#

namespace StellaOps.ElkSharp;
internal static partial class ElkEdgePostProcessor
{
private static bool IsValidSharedLaneRepairPath(
IReadOnlyList<ElkPoint> path,
ElkRoutedEdge edge,
double graphMinY,
double graphMaxY,
(double Left, double Top, double Right, double Bottom, string Id)[] nodeObstacles,
string? originalTargetSide,
ElkPositionedNode? targetNode)
{
return path.Count >= 2
&& (originalTargetSide is null
|| targetNode is null
|| ResolveTargetApproachSide(path, targetNode) == originalTargetSide)
&& !HasNodeObstacleCrossing(path, nodeObstacles, edge.SourceNodeId, edge.TargetNodeId)
&& !SegmentLeavesGraphBand(path, graphMinY, graphMaxY);
}
private static IEnumerable<(int SegmentIndex, double AlternateCoordinate)> EnumerateSharedLaneShiftCandidates(
IReadOnlyList<ElkPoint> path,
IReadOnlyCollection<ElkRoutedEdge> peerEdges,
double minLineClearance)
{
var yielded = new HashSet<(int SegmentIndex, double AlternateCoordinate)>();
for (var segmentIndex = 0; segmentIndex < path.Count - 1; segmentIndex++)
{
var start = path[segmentIndex];
var end = path[segmentIndex + 1];
var isHorizontal = Math.Abs(start.Y - end.Y) <= 0.5d;
var isVertical = Math.Abs(start.X - end.X) <= 0.5d;
if (!isHorizontal && !isVertical)
{
continue;
}
foreach (var peerEdge in peerEdges)
{
foreach (var otherSegment in ElkEdgeRoutingGeometry.FlattenSegments(peerEdge))
{
if (!SegmentsShareLane(
start,
end,
otherSegment.Start,
otherSegment.End,
minLineClearance))
{
continue;
}
foreach (var alternateCoordinate in ResolveLaneShiftCoordinates(
start,
end,
otherSegment.Start,
otherSegment.End,
minLineClearance))
{
if (yielded.Add((segmentIndex, alternateCoordinate)))
{
yield return (segmentIndex, alternateCoordinate);
}
}
}
}
}
}
private static bool TryBuildSharedLaneRepairEdge(
ElkRoutedEdge[] currentEdges,
int repairIndex,
ElkRoutedEdge edge,
ElkRoutedEdge otherEdge,
IReadOnlyList<ElkPoint> candidatePath,
ElkPositionedNode[] nodes,
double minLineClearance,
double graphMinY,
double graphMaxY,
(double Left, double Top, double Right, double Bottom, string Id)[] nodeObstacles,
string? originalTargetSide,
ElkPositionedNode? targetNode,
out ElkRoutedEdge repairedEdge)
{
repairedEdge = edge;
if (!IsValidSharedLaneRepairPath(
candidatePath,
edge,
graphMinY,
graphMaxY,
nodeObstacles,
originalTargetSide,
targetNode))
{
return false;
}
repairedEdge = BuildSingleSectionEdge(edge, candidatePath);
repairedEdge = RepairBoundaryAnglesAndTargetApproaches(
[repairedEdge],
nodes,
minLineClearance)[0];
repairedEdge = NormalizeSourceExitAngles([repairedEdge], nodes)[0];
var candidateEdges = currentEdges.ToArray();
candidateEdges[repairIndex] = repairedEdge;
var candidateEdgeId = repairedEdge.Id;
var repairedPath = ExtractFullPath(repairedEdge);
if (!IsValidSharedLaneRepairPath(
repairedPath,
edge,
graphMinY,
graphMaxY,
nodeObstacles,
originalTargetSide,
targetNode)
|| ElkEdgeRoutingScoring.CountLongDiagonalViolations([repairedEdge], nodes) > 0
|| ElkEdgeRoutingScoring.CountBadBoundaryAngles([repairedEdge], nodes) > 0
|| ElkEdgeRoutingScoring.CountGatewaySourceExitViolations([repairedEdge], nodes) > 0
|| ElkEdgeRoutingScoring.DetectSharedLaneConflicts(candidateEdges, nodes).Any(conflict =>
string.Equals(conflict.LeftEdgeId, candidateEdgeId, StringComparison.Ordinal)
|| string.Equals(conflict.RightEdgeId, candidateEdgeId, StringComparison.Ordinal)))
{
repairedEdge = edge;
return false;
}
return true;
}
private static bool TryPromoteSharedLaneRepairCandidate(
ElkRoutedEdge[] currentEdges,
int repairIndex,
ElkRoutedEdge edge,
ElkRoutedEdge otherEdge,
IReadOnlyList<ElkPoint> originalPath,
IReadOnlyList<ElkPoint> candidatePath,
IReadOnlyCollection<ElkRoutedEdge> peerEdges,
ElkPositionedNode[] nodes,
double minLineClearance,
double graphMinY,
double graphMaxY,
(double Left, double Top, double Right, double Bottom, string Id)[] nodeObstacles,
string? originalTargetSide,
ElkPositionedNode? targetNode,
int remainingAdditionalShifts,
HashSet<string> visitedPaths,
out ElkRoutedEdge repairedEdge)
{
repairedEdge = edge;
if (!PathChanged(originalPath, candidatePath))
{
return false;
}
if (!IsValidSharedLaneRepairPath(
candidatePath,
edge,
graphMinY,
graphMaxY,
nodeObstacles,
originalTargetSide,
targetNode))
{
return false;
}
if (!visitedPaths.Add(CreatePathSignature(candidatePath)))
{
return false;
}
if (TryBuildSharedLaneRepairEdge(
currentEdges,
repairIndex,
edge,
otherEdge,
candidatePath,
nodes,
minLineClearance,
graphMinY,
graphMaxY,
nodeObstacles,
originalTargetSide,
targetNode,
out repairedEdge))
{
return true;
}
if (remainingAdditionalShifts <= 0)
{
repairedEdge = edge;
return false;
}
foreach (var (segmentIndex, alternateCoordinate) in EnumerateSharedLaneShiftCandidates(candidatePath, peerEdges, minLineClearance))
{
var secondaryCandidate = candidatePath.Count == 2
? ShiftStraightOrthogonalPath(candidatePath, alternateCoordinate)
: ShiftSingleOrthogonalRun(candidatePath, segmentIndex, alternateCoordinate);
if (TryPromoteSharedLaneRepairCandidate(
currentEdges,
repairIndex,
edge,
otherEdge,
candidatePath,
secondaryCandidate,
peerEdges,
nodes,
minLineClearance,
graphMinY,
graphMaxY,
nodeObstacles,
originalTargetSide,
targetNode,
remainingAdditionalShifts - 1,
visitedPaths,
out repairedEdge))
{
return true;
}
}
repairedEdge = edge;
return false;
}
private static bool TrySeparateSharedLaneConflict(
ElkRoutedEdge[] currentEdges,
int repairIndex,
ElkRoutedEdge edge,
ElkRoutedEdge otherEdge,
ElkPositionedNode[] nodes,
double minLineClearance,
double graphMinY,
double graphMaxY,
(double Left, double Top, double Right, double Bottom, string Id)[] nodeObstacles,
out ElkRoutedEdge repairedEdge)
{
repairedEdge = edge;
var path = ExtractFullPath(edge);
if (path.Count < 2)
{
return false;
}
var nodesById = nodes.ToDictionary(node => node.Id, StringComparer.Ordinal);
var originalTargetSide = nodesById.TryGetValue(edge.TargetNodeId ?? string.Empty, out var targetNode)
&& !ElkShapeBoundaries.IsGatewayShape(targetNode)
? ResolveTargetApproachSide(path, targetNode)
: null;
var peerEdges = currentEdges
.Where((candidateEdge, index) => index != repairIndex)
.ToArray();
foreach (var (segmentIndex, alternateCoordinate) in EnumerateSharedLaneShiftCandidates(path, peerEdges, minLineClearance))
{
var candidate = path.Count == 2
? ShiftStraightOrthogonalPath(path, alternateCoordinate)
: ShiftSingleOrthogonalRun(path, segmentIndex, alternateCoordinate);
if (TryPromoteSharedLaneRepairCandidate(
currentEdges,
repairIndex,
edge,
otherEdge,
path,
candidate,
peerEdges,
nodes,
minLineClearance,
graphMinY,
graphMaxY,
nodeObstacles,
originalTargetSide,
targetNode,
2,
new HashSet<string>(StringComparer.Ordinal)
{
CreatePathSignature(path),
},
out repairedEdge))
{
return true;
}
}
repairedEdge = edge;
return false;
}
}