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,322 @@
|
||||
namespace StellaOps.ElkSharp;
|
||||
|
||||
internal static partial class ElkEdgePostProcessor
|
||||
{
|
||||
private static bool TryResolveSharedLaneByPairedNodeHandoffSlotRepair(
|
||||
ElkRoutedEdge[] currentEdges,
|
||||
int leftIndex,
|
||||
ElkRoutedEdge leftEdge,
|
||||
int rightIndex,
|
||||
ElkRoutedEdge rightEdge,
|
||||
ElkPositionedNode[] nodes,
|
||||
double minLineClearance,
|
||||
double graphMinY,
|
||||
double graphMaxY,
|
||||
out ElkRoutedEdge repairedLeftEdge,
|
||||
out ElkRoutedEdge repairedRightEdge)
|
||||
{
|
||||
repairedLeftEdge = leftEdge;
|
||||
repairedRightEdge = rightEdge;
|
||||
var nodesById = nodes.ToDictionary(node => node.Id, StringComparer.Ordinal);
|
||||
if (!TryResolveSharedLaneNodeHandoffContext(leftEdge, rightEdge, nodesById, graphMinY, graphMaxY, out var leftContext)
|
||||
|| !TryResolveSharedLaneNodeHandoffContext(rightEdge, leftEdge, nodesById, graphMinY, graphMaxY, out var rightContext)
|
||||
|| !string.Equals(leftContext.SharedNode.Id, rightContext.SharedNode.Id, StringComparison.Ordinal)
|
||||
|| !string.Equals(leftContext.Side, rightContext.Side, StringComparison.Ordinal)
|
||||
|| leftContext.IsOutgoing == rightContext.IsOutgoing)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var baselineConflicts = ElkEdgeRoutingScoring.DetectSharedLaneConflicts(currentEdges, nodes);
|
||||
var baselineConflictCount = baselineConflicts.Count;
|
||||
var baselineLeftConflictCount = baselineConflicts.Count(conflict =>
|
||||
string.Equals(conflict.LeftEdgeId, leftEdge.Id, StringComparison.Ordinal)
|
||||
|| string.Equals(conflict.RightEdgeId, leftEdge.Id, StringComparison.Ordinal));
|
||||
var baselineRightConflictCount = baselineConflicts.Count(conflict =>
|
||||
string.Equals(conflict.LeftEdgeId, rightEdge.Id, StringComparison.Ordinal)
|
||||
|| string.Equals(conflict.RightEdgeId, rightEdge.Id, StringComparison.Ordinal));
|
||||
var baselineCombinedPathLength = ComputePathLength(leftContext.Path) + ComputePathLength(rightContext.Path);
|
||||
|
||||
var peerCoordinates = CollectSharedLaneNodeFaceBoundaryCoordinates(
|
||||
currentEdges,
|
||||
leftContext.SharedNode,
|
||||
leftContext.Side,
|
||||
graphMinY,
|
||||
graphMaxY,
|
||||
leftEdge.Id);
|
||||
var leftRepairCoordinates = EnumerateSharedLaneBoundaryRepairCoordinates(
|
||||
leftContext.SharedNode,
|
||||
leftContext.Side,
|
||||
leftContext.CurrentBoundaryCoordinate,
|
||||
peerCoordinates)
|
||||
.ToArray();
|
||||
var rightRepairCoordinates = EnumerateSharedLaneBoundaryRepairCoordinates(
|
||||
rightContext.SharedNode,
|
||||
rightContext.Side,
|
||||
rightContext.CurrentBoundaryCoordinate,
|
||||
peerCoordinates)
|
||||
.ToArray();
|
||||
|
||||
ElkRoutedEdge? bestLeft = null;
|
||||
ElkRoutedEdge? bestRight = null;
|
||||
var bestConflictCount = baselineConflictCount;
|
||||
var bestLeftConflictCount = baselineLeftConflictCount;
|
||||
var bestRightConflictCount = baselineRightConflictCount;
|
||||
var bestCombinedPathLength = baselineCombinedPathLength;
|
||||
|
||||
foreach (var leftCoordinate in leftRepairCoordinates)
|
||||
{
|
||||
var leftCandidatePath = leftContext.IsOutgoing
|
||||
? BuildMixedSourceFaceCandidate(leftContext.Path, leftContext.SharedNode, leftContext.Side, leftCoordinate, leftContext.AxisValue)
|
||||
: BuildMixedTargetFaceCandidate(leftContext.Path, leftContext.SharedNode, leftContext.Side, leftCoordinate, leftContext.AxisValue);
|
||||
if (!IsValidSharedLaneBoundaryRepairCandidate(
|
||||
leftEdge,
|
||||
leftContext.Path,
|
||||
leftCandidatePath,
|
||||
leftContext.SharedNode,
|
||||
leftContext.IsOutgoing,
|
||||
nodes,
|
||||
graphMinY,
|
||||
graphMaxY))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var rightCoordinate in rightRepairCoordinates)
|
||||
{
|
||||
var rightCandidatePath = rightContext.IsOutgoing
|
||||
? BuildMixedSourceFaceCandidate(rightContext.Path, rightContext.SharedNode, rightContext.Side, rightCoordinate, rightContext.AxisValue)
|
||||
: BuildMixedTargetFaceCandidate(rightContext.Path, rightContext.SharedNode, rightContext.Side, rightCoordinate, rightContext.AxisValue);
|
||||
if (!IsValidSharedLaneBoundaryRepairCandidate(
|
||||
rightEdge,
|
||||
rightContext.Path,
|
||||
rightCandidatePath,
|
||||
rightContext.SharedNode,
|
||||
rightContext.IsOutgoing,
|
||||
nodes,
|
||||
graphMinY,
|
||||
graphMaxY))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var candidateLeft = BuildSingleSectionEdge(leftEdge, leftCandidatePath);
|
||||
var candidateRight = BuildSingleSectionEdge(rightEdge, rightCandidatePath);
|
||||
if (ElkEdgeRoutingScoring.DetectSharedLaneConflicts([candidateLeft, candidateRight], nodes).Count > 0
|
||||
|| ComputeUnderNodeRepairLocalHardPressure(candidateLeft, nodes) > ComputeUnderNodeRepairLocalHardPressure(leftEdge, nodes)
|
||||
|| ComputeUnderNodeRepairLocalHardPressure(candidateRight, nodes) > ComputeUnderNodeRepairLocalHardPressure(rightEdge, nodes))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var candidateEdges = currentEdges.ToArray();
|
||||
candidateEdges[leftIndex] = candidateLeft;
|
||||
candidateEdges[rightIndex] = candidateRight;
|
||||
var candidateConflicts = ElkEdgeRoutingScoring.DetectSharedLaneConflicts(candidateEdges, nodes);
|
||||
var candidateConflictCount = candidateConflicts.Count;
|
||||
var candidateLeftConflictCount = candidateConflicts.Count(conflict =>
|
||||
string.Equals(conflict.LeftEdgeId, leftEdge.Id, StringComparison.Ordinal)
|
||||
|| string.Equals(conflict.RightEdgeId, leftEdge.Id, StringComparison.Ordinal));
|
||||
var candidateRightConflictCount = candidateConflicts.Count(conflict =>
|
||||
string.Equals(conflict.LeftEdgeId, rightEdge.Id, StringComparison.Ordinal)
|
||||
|| string.Equals(conflict.RightEdgeId, rightEdge.Id, StringComparison.Ordinal));
|
||||
if (candidateConflictCount > bestConflictCount
|
||||
|| candidateLeftConflictCount > bestLeftConflictCount
|
||||
|| candidateRightConflictCount > bestRightConflictCount)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var candidateCombinedPathLength = ComputePathLength(leftCandidatePath) + ComputePathLength(rightCandidatePath);
|
||||
var isBetter =
|
||||
candidateConflictCount < bestConflictCount
|
||||
|| candidateLeftConflictCount < bestLeftConflictCount
|
||||
|| candidateRightConflictCount < bestRightConflictCount
|
||||
|| candidateCombinedPathLength + 0.5d < bestCombinedPathLength;
|
||||
if (!isBetter)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
bestLeft = candidateLeft;
|
||||
bestRight = candidateRight;
|
||||
bestConflictCount = candidateConflictCount;
|
||||
bestLeftConflictCount = candidateLeftConflictCount;
|
||||
bestRightConflictCount = candidateRightConflictCount;
|
||||
bestCombinedPathLength = candidateCombinedPathLength;
|
||||
}
|
||||
}
|
||||
|
||||
if (bestLeft is null || bestRight is null || bestConflictCount >= baselineConflictCount)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
repairedLeftEdge = bestLeft;
|
||||
repairedRightEdge = bestRight;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryResolveSharedLaneByDirectSourceSlotRepair(
|
||||
ElkRoutedEdge[] currentEdges,
|
||||
int repairIndex,
|
||||
ElkRoutedEdge edge,
|
||||
ElkRoutedEdge otherEdge,
|
||||
ElkPositionedNode[] nodes,
|
||||
double minLineClearance,
|
||||
double graphMinY,
|
||||
double graphMaxY,
|
||||
out ElkRoutedEdge repairedEdge)
|
||||
{
|
||||
repairedEdge = edge;
|
||||
if (string.IsNullOrWhiteSpace(edge.SourceNodeId)
|
||||
|| !string.Equals(edge.SourceNodeId, otherEdge.SourceNodeId, StringComparison.Ordinal))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var nodesById = nodes.ToDictionary(node => node.Id, StringComparer.Ordinal);
|
||||
if (!nodesById.TryGetValue(edge.SourceNodeId ?? string.Empty, out var sourceNode))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var path = ExtractFullPath(edge);
|
||||
var otherPath = ExtractFullPath(otherEdge);
|
||||
if (path.Count < 2
|
||||
|| otherPath.Count < 2
|
||||
|| !ShouldSpreadSourceDeparture(edge, graphMinY, graphMaxY)
|
||||
|| !ShouldSpreadSourceDeparture(otherEdge, graphMinY, graphMaxY))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var side = ResolveSourceDepartureSide(path, sourceNode);
|
||||
var otherSide = ResolveSourceDepartureSide(otherPath, sourceNode);
|
||||
if (!string.Equals(side, otherSide, StringComparison.Ordinal))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var axisValue = TryExtractSourceDepartureRun(path, side, out _, out var runEndIndex)
|
||||
? side is "left" or "right"
|
||||
? path[runEndIndex].X
|
||||
: path[runEndIndex].Y
|
||||
: ResolveDefaultSourceDepartureAxis(sourceNode, side);
|
||||
var currentBoundaryCoordinate = side is "left" or "right" ? path[0].Y : path[0].X;
|
||||
var peerCoordinates = CollectSharedLaneSourceBoundaryCoordinates(
|
||||
currentEdges,
|
||||
sourceNode,
|
||||
side,
|
||||
graphMinY,
|
||||
graphMaxY,
|
||||
edge.Id);
|
||||
|
||||
foreach (var desiredCoordinate in EnumerateSharedLaneBoundaryRepairCoordinates(
|
||||
sourceNode,
|
||||
side,
|
||||
currentBoundaryCoordinate,
|
||||
peerCoordinates))
|
||||
{
|
||||
var candidatePath = BuildMixedSourceFaceCandidate(path, sourceNode, side, desiredCoordinate, axisValue);
|
||||
if (!IsValidSharedLaneBoundaryRepairCandidate(
|
||||
edge,
|
||||
path,
|
||||
candidatePath,
|
||||
sourceNode,
|
||||
isOutgoing: true,
|
||||
nodes,
|
||||
graphMinY,
|
||||
graphMaxY))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var candidateEdges = currentEdges.ToArray();
|
||||
candidateEdges[repairIndex] = BuildSingleSectionEdge(edge, candidatePath);
|
||||
if (TryAcceptFocusedSharedLanePairRepair(
|
||||
currentEdges,
|
||||
candidateEdges,
|
||||
repairIndex,
|
||||
edge,
|
||||
otherEdge,
|
||||
nodes,
|
||||
graphMinY,
|
||||
graphMaxY,
|
||||
out repairedEdge))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryResolveSharedLaneByDirectNodeHandoffSlotRepair(
|
||||
ElkRoutedEdge[] currentEdges,
|
||||
int repairIndex,
|
||||
ElkRoutedEdge edge,
|
||||
ElkRoutedEdge otherEdge,
|
||||
ElkPositionedNode[] nodes,
|
||||
double minLineClearance,
|
||||
double graphMinY,
|
||||
double graphMaxY,
|
||||
out ElkRoutedEdge repairedEdge)
|
||||
{
|
||||
repairedEdge = edge;
|
||||
var nodesById = nodes.ToDictionary(node => node.Id, StringComparer.Ordinal);
|
||||
if (!TryResolveSharedLaneNodeHandoffContext(edge, otherEdge, nodesById, graphMinY, graphMaxY, out var context))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var peerCoordinates = CollectSharedLaneNodeFaceBoundaryCoordinates(
|
||||
currentEdges,
|
||||
context.SharedNode,
|
||||
context.Side,
|
||||
graphMinY,
|
||||
graphMaxY,
|
||||
edge.Id);
|
||||
|
||||
foreach (var desiredCoordinate in EnumerateSharedLaneBoundaryRepairCoordinates(
|
||||
context.SharedNode,
|
||||
context.Side,
|
||||
context.CurrentBoundaryCoordinate,
|
||||
peerCoordinates))
|
||||
{
|
||||
var candidatePath = context.IsOutgoing
|
||||
? BuildMixedSourceFaceCandidate(context.Path, context.SharedNode, context.Side, desiredCoordinate, context.AxisValue)
|
||||
: BuildMixedTargetFaceCandidate(context.Path, context.SharedNode, context.Side, desiredCoordinate, context.AxisValue);
|
||||
if (!IsValidSharedLaneBoundaryRepairCandidate(
|
||||
edge,
|
||||
context.Path,
|
||||
candidatePath,
|
||||
context.SharedNode,
|
||||
context.IsOutgoing,
|
||||
nodes,
|
||||
graphMinY,
|
||||
graphMaxY))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var candidateEdges = currentEdges.ToArray();
|
||||
candidateEdges[repairIndex] = BuildSingleSectionEdge(edge, candidatePath);
|
||||
if (TryAcceptFocusedSharedLanePairRepair(
|
||||
currentEdges,
|
||||
candidateEdges,
|
||||
repairIndex,
|
||||
edge,
|
||||
otherEdge,
|
||||
nodes,
|
||||
graphMinY,
|
||||
graphMaxY,
|
||||
out repairedEdge))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user