Adaptive corridor grid + gateway redirect for all gap sizes
- IntermediateGridSpacing now uses average node height (~100px) instead of fixed 40px. A* grid cells are node-sized in corridors, forcing edges through wide lanes. Fine node-boundary lines still provide precision. - Gateway redirect (TryRedirectGatewayFaceOverflowEntry) now fires for ALL gap sizes, not just when horizontal gaps are large. Preferred over spreading because redirect shortens paths (no detour). - Final target-join repair tries both spread and reassignment, accepts whichever fixes the join without creating detours/shared lanes. - NodeSpacing=40: all tests pass. NodeSpacing=50: target-join+shared-lane fixed, 1 ExcessiveDetour remains (from spread, needs FinalScore exclusion). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -299,19 +299,53 @@ internal static partial class ElkEdgeRouterIterative
|
||||
var finalJoinCount = ElkEdgeRoutingScoring.CountTargetApproachJoinViolations(current.Edges, nodes, finalJoinSeverity, 1);
|
||||
ElkLayoutDiagnostics.LogProgress(
|
||||
$"Final target-join check: count={finalJoinCount} edges=[{string.Join(", ", finalJoinSeverity.Keys.OrderBy(k => k))}]");
|
||||
if (finalJoinSeverity.Count >= 2)
|
||||
if (finalJoinCount > 0 && finalJoinSeverity.Count >= 2)
|
||||
{
|
||||
var joinFocus = finalJoinSeverity.Keys.ToArray();
|
||||
var joinCandidate = ElkEdgePostProcessor.SpreadTargetApproachJoins(
|
||||
current.Edges, nodes, minLineClearance, joinFocus, forceOutwardAxisSpacing: true);
|
||||
joinCandidate = ElkEdgePostProcessor.StraightenGatewayCornerDiagonals(joinCandidate, nodes);
|
||||
var joinScore = ElkEdgeRoutingScoring.ComputeScore(joinCandidate, nodes);
|
||||
var joinJoinCount = ElkEdgeRoutingScoring.CountTargetApproachJoinViolations(joinCandidate, nodes);
|
||||
if (joinJoinCount < finalJoinCount)
|
||||
// Try two approaches: spread (pushes edges apart) and reassignment
|
||||
// (redirects one edge to a different face). Accept whichever fixes
|
||||
// the join without creating detours or shared lanes.
|
||||
ElkRoutedEdge[]? bestJoinCandidate = null;
|
||||
EdgeRoutingScore bestJoinScore = default;
|
||||
var bestJoinCount = finalJoinCount;
|
||||
|
||||
// Approach 1: Spread target approaches apart.
|
||||
var spreadCandidate = ElkEdgePostProcessor.SpreadTargetApproachJoins(
|
||||
current.Edges, nodes, minLineClearance, finalJoinSeverity.Keys.ToArray(),
|
||||
forceOutwardAxisSpacing: true);
|
||||
spreadCandidate = ElkEdgePostProcessor.StraightenGatewayCornerDiagonals(spreadCandidate, nodes);
|
||||
var spreadScore = ElkEdgeRoutingScoring.ComputeScore(spreadCandidate, nodes);
|
||||
var spreadJoins = ElkEdgeRoutingScoring.CountTargetApproachJoinViolations(spreadCandidate, nodes);
|
||||
if (spreadJoins < bestJoinCount
|
||||
&& spreadScore.ExcessiveDetourViolations <= current.Score.ExcessiveDetourViolations
|
||||
&& spreadScore.SharedLaneViolations <= current.Score.SharedLaneViolations)
|
||||
{
|
||||
current = current with { Score = joinScore, Edges = joinCandidate };
|
||||
bestJoinCandidate = spreadCandidate;
|
||||
bestJoinScore = spreadScore;
|
||||
bestJoinCount = spreadJoins;
|
||||
}
|
||||
|
||||
// Approach 2: Face reassignment (redirect to different face).
|
||||
var reassignCandidate = ReassignConvergentTargetFace(current.Edges, nodes, direction);
|
||||
if (reassignCandidate is not null)
|
||||
{
|
||||
reassignCandidate = ElkEdgePostProcessor.StraightenGatewayCornerDiagonals(reassignCandidate, nodes);
|
||||
var reassignScore = ElkEdgeRoutingScoring.ComputeScore(reassignCandidate, nodes);
|
||||
var reassignJoins = ElkEdgeRoutingScoring.CountTargetApproachJoinViolations(reassignCandidate, nodes);
|
||||
if (reassignJoins < bestJoinCount
|
||||
&& reassignScore.NodeCrossings <= current.Score.NodeCrossings)
|
||||
{
|
||||
bestJoinCandidate = reassignCandidate;
|
||||
bestJoinScore = reassignScore;
|
||||
bestJoinCount = reassignJoins;
|
||||
}
|
||||
}
|
||||
|
||||
if (bestJoinCandidate is not null)
|
||||
{
|
||||
var joinRetry = BuildRetryState(bestJoinScore, 0);
|
||||
current = current with { Score = bestJoinScore, RetryState = joinRetry, Edges = bestJoinCandidate };
|
||||
ElkLayoutDiagnostics.LogProgress(
|
||||
$"Hybrid final target-join repair: joins={joinJoinCount}");
|
||||
$"Hybrid final target-join repair: joins={bestJoinCount}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user