166 lines
6.3 KiB
C#
166 lines
6.3 KiB
C#
using System.Collections.Concurrent;
|
|
using System.Diagnostics;
|
|
using System.Globalization;
|
|
|
|
namespace StellaOps.ElkSharp;
|
|
|
|
internal static partial class ElkEdgeRouterIterative
|
|
{
|
|
private static bool TryPromoteFinalBoundarySlotCandidate(
|
|
CandidateSolution current,
|
|
ElkRoutedEdge[] candidateEdges,
|
|
ElkPositionedNode[] nodes,
|
|
out CandidateSolution promoted)
|
|
{
|
|
promoted = current;
|
|
var candidateScore = ElkEdgeRoutingScoring.ComputeScore(candidateEdges, nodes);
|
|
var candidateRetryState = BuildRetryState(
|
|
candidateScore,
|
|
HighwayProcessingEnabled
|
|
? ElkEdgeRouterHighway.DetectRemainingBrokenHighways(candidateEdges, nodes).Count
|
|
: 0);
|
|
if (!IsBetterBoundarySlotRepairCandidate(
|
|
candidateScore,
|
|
candidateRetryState,
|
|
current.Score,
|
|
current.RetryState))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
promoted = current with
|
|
{
|
|
Score = candidateScore,
|
|
RetryState = candidateRetryState,
|
|
Edges = candidateEdges,
|
|
};
|
|
return true;
|
|
}
|
|
|
|
private static bool HasBlockingBoundarySlotPromotionRegression(
|
|
RoutingRetryState candidate,
|
|
RoutingRetryState baseline)
|
|
{
|
|
return candidate.RemainingShortHighways > baseline.RemainingShortHighways
|
|
|| candidate.RepeatCollectorCorridorViolations > baseline.RepeatCollectorCorridorViolations
|
|
|| candidate.RepeatCollectorNodeClearanceViolations > baseline.RepeatCollectorNodeClearanceViolations
|
|
|| candidate.BelowGraphViolations > baseline.BelowGraphViolations
|
|
|| candidate.UnderNodeViolations > baseline.UnderNodeViolations;
|
|
}
|
|
|
|
private static bool HasEdgeGeometryChanged(
|
|
IReadOnlyList<ElkRoutedEdge> baselineEdges,
|
|
IReadOnlyList<ElkRoutedEdge> candidateEdges,
|
|
string edgeId)
|
|
{
|
|
var baseline = baselineEdges.FirstOrDefault(edge => string.Equals(edge.Id, edgeId, StringComparison.Ordinal));
|
|
var candidate = candidateEdges.FirstOrDefault(edge => string.Equals(edge.Id, edgeId, StringComparison.Ordinal));
|
|
if (baseline is null || candidate is null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var baselinePath = ExtractEdgePath(baseline);
|
|
var candidatePath = ExtractEdgePath(candidate);
|
|
if (baselinePath.Count != candidatePath.Count)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
for (var i = 0; i < baselinePath.Count; i++)
|
|
{
|
|
if (!ElkEdgeRoutingGeometry.PointsEqual(baselinePath[i], candidatePath[i]))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private static List<ElkPoint> ExtractEdgePath(ElkRoutedEdge edge)
|
|
{
|
|
var path = new List<ElkPoint>();
|
|
foreach (var section in edge.Sections)
|
|
{
|
|
if (path.Count == 0 || !ElkEdgeRoutingGeometry.PointsEqual(path[^1], section.StartPoint))
|
|
{
|
|
path.Add(section.StartPoint);
|
|
}
|
|
|
|
foreach (var bendPoint in section.BendPoints)
|
|
{
|
|
if (path.Count == 0 || !ElkEdgeRoutingGeometry.PointsEqual(path[^1], bendPoint))
|
|
{
|
|
path.Add(bendPoint);
|
|
}
|
|
}
|
|
|
|
if (path.Count == 0 || !ElkEdgeRoutingGeometry.PointsEqual(path[^1], section.EndPoint))
|
|
{
|
|
path.Add(section.EndPoint);
|
|
}
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
private static bool TryPromoteFinalHardRuleCandidate(
|
|
CandidateSolution current,
|
|
ElkRoutedEdge[] candidateEdges,
|
|
ElkPositionedNode[] nodes,
|
|
out CandidateSolution promoted)
|
|
{
|
|
promoted = current;
|
|
var candidateScore = ElkEdgeRoutingScoring.ComputeScore(candidateEdges, nodes);
|
|
var candidateRetryState = BuildRetryState(
|
|
candidateScore,
|
|
HighwayProcessingEnabled
|
|
? ElkEdgeRouterHighway.DetectRemainingBrokenHighways(candidateEdges, nodes).Count
|
|
: 0);
|
|
if (!IsBetterBoundarySlotRepairCandidate(
|
|
candidateScore,
|
|
candidateRetryState,
|
|
current.Score,
|
|
current.RetryState))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
promoted = current with
|
|
{
|
|
Score = candidateScore,
|
|
RetryState = candidateRetryState,
|
|
Edges = candidateEdges,
|
|
};
|
|
return true;
|
|
}
|
|
|
|
private static ElkRoutedEdge[] ApplyAggressiveSharedLaneClosure(
|
|
ElkRoutedEdge[] edges,
|
|
ElkPositionedNode[] nodes,
|
|
ElkLayoutDirection direction,
|
|
double minLineClearance,
|
|
IReadOnlyCollection<string> focusEdgeIds)
|
|
{
|
|
var result = edges;
|
|
result = ElkEdgePostProcessor.SeparateSharedLaneConflicts(result, nodes, minLineClearance, focusEdgeIds);
|
|
result = ElkEdgePostProcessor.SeparateRepeatCollectorLocalLaneConflicts(result, nodes, minLineClearance, focusEdgeIds);
|
|
result = ElkRepeatCollectorCorridors.SeparateSharedLanes(result, nodes, focusEdgeIds);
|
|
result = ElkEdgePostProcessor.SpreadSourceDepartureJoins(result, nodes, minLineClearance, focusEdgeIds);
|
|
result = ElkEdgePostProcessor.SeparateMixedNodeFaceLaneConflicts(result, nodes, minLineClearance, focusEdgeIds);
|
|
result = ElkEdgePostProcessor.SpreadTargetApproachJoins(result, nodes, minLineClearance, focusEdgeIds, forceOutwardAxisSpacing: true);
|
|
result = ElkEdgePostProcessor.SpreadRectTargetApproachFeederBands(result, nodes, minLineClearance, focusEdgeIds);
|
|
result = ElkEdgePostProcessor.RepairBoundaryAnglesAndTargetApproaches(result, nodes, minLineClearance, focusEdgeIds);
|
|
result = ElkEdgePostProcessor.FinalizeGatewayBoundaryGeometry(result, nodes, focusEdgeIds);
|
|
result = ClampBelowGraphEdges(result, nodes, focusEdgeIds);
|
|
result = ElkEdgePostProcessor.AvoidNodeCrossings(result, nodes, direction, focusEdgeIds);
|
|
result = ElkEdgePostProcessor.NormalizeBoundaryAngles(result, nodes);
|
|
result = ElkEdgePostProcessor.NormalizeSourceExitAngles(result, nodes);
|
|
result = ElkEdgePostProcessor.SeparateMixedNodeFaceLaneConflicts(result, nodes, minLineClearance, focusEdgeIds);
|
|
result = ElkEdgePostProcessor.SeparateSharedLaneConflicts(result, nodes, minLineClearance, focusEdgeIds);
|
|
return result;
|
|
}
|
|
|
|
}
|