namespace StellaOps.ElkSharp; internal static partial class ElkEdgeRouterIterative { private readonly record struct GatewayArtifactState( int SourceVertexExits, int CornerDiagonals, int InteriorAdjacentPoints, int SourceFaceMismatches, int SourceDominantAxisDetours, int SourceScoringIssues) { public bool IsClean => SourceVertexExits == 0 && CornerDiagonals == 0 && InteriorAdjacentPoints == 0 && SourceFaceMismatches == 0 && SourceDominantAxisDetours == 0 && SourceScoringIssues == 0; public bool IsBetterThan(GatewayArtifactState other) { if (SourceVertexExits != other.SourceVertexExits) { return SourceVertexExits < other.SourceVertexExits; } if (CornerDiagonals != other.CornerDiagonals) { return CornerDiagonals < other.CornerDiagonals; } if (InteriorAdjacentPoints != other.InteriorAdjacentPoints) { return InteriorAdjacentPoints < other.InteriorAdjacentPoints; } if (SourceFaceMismatches != other.SourceFaceMismatches) { return SourceFaceMismatches < other.SourceFaceMismatches; } if (SourceDominantAxisDetours != other.SourceDominantAxisDetours) { return SourceDominantAxisDetours < other.SourceDominantAxisDetours; } return SourceScoringIssues < other.SourceScoringIssues; } public override string ToString() { return $"vertex={SourceVertexExits} corner={CornerDiagonals} interior={InteriorAdjacentPoints} " + $"face={SourceFaceMismatches} detour={SourceDominantAxisDetours} scoring={SourceScoringIssues}"; } } private static CandidateSolution ApplyFinalGatewayArtifactPolish( CandidateSolution solution, ElkPositionedNode[] nodes, double minLineClearance) { var current = solution; for (var round = 0; round < 2; round++) { var baselineArtifacts = EvaluateGatewayArtifacts(current.Edges, nodes, out var focusEdgeIds); if (baselineArtifacts.IsClean || focusEdgeIds.Length == 0) { break; } ElkLayoutDiagnostics.LogProgress( $"Hybrid gateway artifact polish round {round + 1} start: artifacts={baselineArtifacts} focus=[{string.Join(", ", focusEdgeIds)}]"); var candidateEdges = current.Edges; candidateEdges = ElkEdgePostProcessor.FinalizeGatewayBoundaryGeometry(candidateEdges, nodes, focusEdgeIds); candidateEdges = ElkEdgePostProcessor.RepairBoundaryAnglesAndTargetApproaches( candidateEdges, nodes, minLineClearance, focusEdgeIds); candidateEdges = ElkEdgePostProcessor.FinalizeDecisionTargetEntries(candidateEdges, nodes, focusEdgeIds); candidateEdges = ElkEdgePostProcessor.NormalizeBoundaryAngles(candidateEdges, nodes); candidateEdges = ElkEdgePostProcessor.NormalizeSourceExitAngles(candidateEdges, nodes); candidateEdges = ElkEdgePostProcessor.SnapBoundarySlotAssignments( candidateEdges, nodes, minLineClearance, focusEdgeIds, enforceAllNodeEndpoints: true); candidateEdges = ElkEdgePostProcessor.FinalizeGatewayBoundaryGeometry(candidateEdges, nodes, focusEdgeIds); candidateEdges = ElkEdgePostProcessor.NormalizeBoundaryAngles(candidateEdges, nodes); candidateEdges = ElkEdgePostProcessor.NormalizeSourceExitAngles(candidateEdges, nodes); // Straighten any corner diagonals created by the face fix so they // don't block promotion via the lexicographic IsBetterThan check // (corners have higher priority than face mismatches). candidateEdges = ElkEdgePostProcessor.StraightenGatewayCornerDiagonals(candidateEdges, nodes); if (!TryPromoteGatewayArtifactCandidate(current, candidateEdges, nodes, baselineArtifacts, out var promoted)) { break; } current = promoted; ElkLayoutDiagnostics.LogProgress( $"Hybrid gateway artifact polish round {round + 1} improved: retry={DescribeRetryState(current.RetryState)}"); } return current; } private static bool TryPromoteGatewayArtifactCandidate( CandidateSolution current, ElkRoutedEdge[] candidateEdges, ElkPositionedNode[] nodes, GatewayArtifactState baselineArtifacts, out CandidateSolution promoted) { promoted = current; var candidateArtifacts = EvaluateGatewayArtifacts(candidateEdges, nodes, out _); if (!candidateArtifacts.IsBetterThan(baselineArtifacts)) { return false; } var candidateScore = ElkEdgeRoutingScoring.ComputeScore(candidateEdges, nodes); var candidateRetryState = BuildRetryState( candidateScore, HighwayProcessingEnabled ? ElkEdgeRouterHighway.DetectRemainingBrokenHighways(candidateEdges, nodes).Count : 0); if (HasHardRuleRegression(candidateRetryState, current.RetryState) || candidateScore.NodeCrossings > current.Score.NodeCrossings) { return false; } promoted = current with { Score = candidateScore, RetryState = candidateRetryState, Edges = candidateEdges, }; return true; } private static GatewayArtifactState EvaluateGatewayArtifacts( IReadOnlyCollection edges, IReadOnlyCollection nodes, out string[] focusEdgeIds) { var nodesById = nodes.ToDictionary(node => node.Id, StringComparer.Ordinal); var focus = new HashSet(StringComparer.Ordinal); var sourceVertexExits = 0; var cornerDiagonals = 0; var interiorAdjacentPoints = 0; var sourceFaceMismatches = 0; var sourceDominantAxisDetours = 0; var sourceScoringIssues = 0; foreach (var edge in edges) { var path = ExtractPath(edge); if (path.Count < 2) { continue; } if (nodesById.TryGetValue(edge.SourceNodeId ?? string.Empty, out var sourceNode) && ElkShapeBoundaries.IsGatewayShape(sourceNode)) { if (ElkShapeBoundaries.IsNearGatewayVertex(sourceNode, path[0])) { sourceVertexExits++; focus.Add(edge.Id); } if (HasGatewayCornerDiagonalArtifact(path, sourceNode, fromSource: true)) { cornerDiagonals++; focus.Add(edge.Id); } if (HasGatewayInteriorAdjacentPointArtifact(path, sourceNode, fromSource: true)) { interiorAdjacentPoints++; focus.Add(edge.Id); } if (HasGatewaySourcePreferredFaceMismatchArtifact( path, sourceNode, nodes, edge.SourceNodeId, edge.TargetNodeId)) { sourceFaceMismatches++; focus.Add(edge.Id); } if (HasGatewaySourceDominantAxisDetourArtifact( path, sourceNode, nodes, edge.SourceNodeId, edge.TargetNodeId)) { sourceDominantAxisDetours++; focus.Add(edge.Id); } if (ElkEdgePostProcessor.HasClearGatewaySourceScoringOpportunity( path, sourceNode, nodes, edge.SourceNodeId, edge.TargetNodeId)) { sourceScoringIssues++; focus.Add(edge.Id); } } if (nodesById.TryGetValue(edge.TargetNodeId ?? string.Empty, out var targetNode) && ElkShapeBoundaries.IsGatewayShape(targetNode)) { if (HasGatewayCornerDiagonalArtifact(path, targetNode, fromSource: false)) { cornerDiagonals++; focus.Add(edge.Id); } if (HasGatewayInteriorAdjacentPointArtifact(path, targetNode, fromSource: false)) { interiorAdjacentPoints++; focus.Add(edge.Id); } } } focusEdgeIds = focus.OrderBy(edgeId => edgeId, StringComparer.Ordinal).ToArray(); return new GatewayArtifactState( sourceVertexExits, cornerDiagonals, interiorAdjacentPoints, sourceFaceMismatches, sourceDominantAxisDetours, sourceScoringIssues); } private static bool HasGatewayCornerDiagonalArtifact( IReadOnlyList path, ElkPositionedNode node, bool fromSource) { if (!ElkShapeBoundaries.IsGatewayShape(node) || path.Count < 2) { return false; } var boundary = fromSource ? path[0] : path[^1]; var adjacent = fromSource ? path[1] : path[^2]; var deltaX = Math.Abs(boundary.X - adjacent.X); var deltaY = Math.Abs(boundary.Y - adjacent.Y); return deltaX >= 3d && deltaY >= 3d && ElkShapeBoundaries.IsNearGatewayVertex(node, boundary); } private static bool HasGatewayInteriorAdjacentPointArtifact( IReadOnlyList path, ElkPositionedNode node, bool fromSource) { if (!ElkShapeBoundaries.IsGatewayShape(node) || path.Count < 2) { return false; } var adjacent = fromSource ? path[1] : path[^2]; return ElkShapeBoundaries.IsInsideNodeBoundingBoxInterior(node, adjacent); } private static bool HasGatewaySourcePreferredFaceMismatchArtifact( IReadOnlyList path, ElkPositionedNode sourceNode, IReadOnlyCollection allNodes, string? sourceNodeId, string? targetNodeId) { if (!ElkShapeBoundaries.IsGatewayShape(sourceNode) || path.Count < 2 || !ElkEdgePostProcessor.HasClearGatewaySourceDirectRepairOpportunity( path, sourceNode, allNodes, sourceNodeId, targetNodeId)) { return false; } var centerX = sourceNode.X + (sourceNode.Width / 2d); var centerY = sourceNode.Y + (sourceNode.Height / 2d); var desiredDx = path[^1].X - centerX; var desiredDy = path[^1].Y - centerY; var boundaryDx = path[0].X - centerX; var boundaryDy = path[0].Y - centerY; if (Math.Abs(desiredDx) >= Math.Abs(desiredDy) * 1.25d) { return Math.Sign(boundaryDx) != Math.Sign(desiredDx) || Math.Abs(boundaryDy) > sourceNode.Height * 0.28d; } if (Math.Abs(desiredDy) >= Math.Abs(desiredDx) * 1.25d) { return Math.Sign(boundaryDy) != Math.Sign(desiredDy) || Math.Abs(boundaryDx) > sourceNode.Width * 0.28d; } return false; } private static bool HasGatewaySourceDominantAxisDetourArtifact( IReadOnlyList path, ElkPositionedNode sourceNode, IReadOnlyCollection allNodes, string? sourceNodeId, string? targetNodeId) { if (!ElkShapeBoundaries.IsGatewayShape(sourceNode) || path.Count < 3 || !ElkEdgePostProcessor.HasClearGatewaySourceDirectRepairOpportunity( path, sourceNode, allNodes, sourceNodeId, targetNodeId)) { return false; } const double coordinateTolerance = 0.5d; var centerX = sourceNode.X + (sourceNode.Width / 2d); var centerY = sourceNode.Y + (sourceNode.Height / 2d); var desiredDx = path[^1].X - centerX; var desiredDy = path[^1].Y - centerY; var dominantHorizontal = Math.Abs(desiredDx) >= Math.Abs(desiredDy) * 1.25d && Math.Sign(desiredDx) != 0; var dominantVertical = Math.Abs(desiredDy) >= Math.Abs(desiredDx) * 1.25d && Math.Sign(desiredDy) != 0; if (!dominantHorizontal && !dominantVertical) { return false; } var boundary = path[0]; var adjacent = path[1]; var firstDx = adjacent.X - boundary.X; var firstDy = adjacent.Y - boundary.Y; if (dominantHorizontal) { if (Math.Sign(firstDx) != Math.Sign(desiredDx) || Math.Abs(firstDx) <= coordinateTolerance) { return true; } return Math.Abs(firstDy) > Math.Max(24d, Math.Abs(desiredDy) + 12d) && Math.Abs(firstDy) > Math.Abs(firstDx) * 1.25d; } if (Math.Sign(firstDy) != Math.Sign(desiredDy) || Math.Abs(firstDy) <= coordinateTolerance) { return true; } return Math.Abs(firstDx) > Math.Max(24d, Math.Abs(desiredDx) + 12d) && Math.Abs(firstDx) > Math.Abs(firstDy) * 1.25d; } }