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>
This commit is contained in:
@@ -0,0 +1,291 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user