Fix under-node violations with corridor routing and push-down
Two under-node fix strategies in the winner refinement: 1. Long sweeps (> 40% graph width): route through top corridor at graphMinY - 56, with perpendicular exit stub. Fixes edge/20. 2. Medium sweeps near graph bottom: route through bottom corridor at graphMaxY + 32 when the safe push-down Y would exceed graph bounds. Fixes edge/25 (was 29px gap, now routes below blocking nodes). Both under-node geometry violations eliminated. Edge/25 gains a below-graph flag (Y=803 vs graphMaxY=771) which the FinalScore adjustment handles as a corridor routing pattern. Also adds target-join face reassignment infrastructure (redirects outer edge to target's right face) — evaluates but not yet promoted for the current fixture. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -89,18 +89,17 @@ internal static partial class ElkEdgeRouterIterative
|
||||
}
|
||||
|
||||
// Reroute long horizontal sweeps through the top corridor.
|
||||
// Edges spanning > half the graph width with under-node violations
|
||||
// Edges spanning > 40% graph width with under-node violations
|
||||
// should route above the graph (like backward edges) instead of
|
||||
// cutting straight through the node field.
|
||||
// Also pushes medium-length sweeps below blocking nodes.
|
||||
// Each fix type is evaluated INDEPENDENTLY to prevent one fix's
|
||||
// detour from blocking another fix's under-node improvement.
|
||||
if (current.RetryState.UnderNodeViolations > 0)
|
||||
{
|
||||
var corridorCandidate = RerouteLongSweepsThroughCorridor(current.Edges, nodes, direction, minLineClearance);
|
||||
if (corridorCandidate is not null)
|
||||
{
|
||||
// Skip NormalizeBoundaryAngles for corridor-rerouted edges —
|
||||
// the normalization's NormalizeExitPath collapses corridor
|
||||
// vertical segments. The corridor path already has a correct
|
||||
// perpendicular exit stub.
|
||||
var corridorScore = ElkEdgeRoutingScoring.ComputeScore(corridorCandidate, nodes);
|
||||
if (corridorScore.Value > current.Score.Value
|
||||
&& corridorScore.NodeCrossings <= current.Score.NodeCrossings)
|
||||
@@ -144,6 +143,29 @@ internal static partial class ElkEdgeRouterIterative
|
||||
}
|
||||
}
|
||||
|
||||
// Target-join face reassignment: when two edges converge on the
|
||||
// same target face with inadequate separation, redirect the outer
|
||||
// edge to the target's adjacent face (right side for LTR layout).
|
||||
if (current.RetryState.TargetApproachJoinViolations > 0)
|
||||
{
|
||||
var joinCandidate = ReassignConvergentTargetFace(current.Edges, nodes, direction);
|
||||
if (joinCandidate is not null)
|
||||
{
|
||||
var joinScore = ElkEdgeRoutingScoring.ComputeScore(joinCandidate, nodes);
|
||||
if (joinScore.Value > current.Score.Value
|
||||
&& joinScore.NodeCrossings <= current.Score.NodeCrossings)
|
||||
{
|
||||
var joinRetry = BuildRetryState(
|
||||
joinScore,
|
||||
HighwayProcessingEnabled
|
||||
? ElkEdgeRouterHighway.DetectRemainingBrokenHighways(joinCandidate, nodes).Count
|
||||
: 0);
|
||||
current = current with { Score = joinScore, RetryState = joinRetry, Edges = joinCandidate };
|
||||
ElkLayoutDiagnostics.LogProgress($"Hybrid winner refinement after target-join reassignment: {DescribeSolution(current)}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user