221 lines
7.9 KiB
C#
221 lines
7.9 KiB
C#
using System.Collections.Concurrent;
|
|
using System.Diagnostics;
|
|
using System.Globalization;
|
|
|
|
namespace StellaOps.ElkSharp;
|
|
|
|
internal static partial class ElkEdgeRouterIterative
|
|
{
|
|
private static List<ElkPoint>? TryRouteShortestRepair(
|
|
ElkPoint start,
|
|
ElkPoint end,
|
|
IReadOnlyCollection<ElkPositionedNode> nodes,
|
|
(double Left, double Top, double Right, double Bottom, string Id)[] obstacles,
|
|
string sourceId,
|
|
string targetId,
|
|
ElkPositionedNode? sourceNode,
|
|
ElkPositionedNode? targetNode,
|
|
AStarRoutingParams routingParams,
|
|
IReadOnlyList<OrthogonalSoftObstacle> softObstacles,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
if (sourceNode is not null && targetNode is not null)
|
|
{
|
|
if (ElkEdgePostProcessor.TryBuildPreferredBoundaryShortcutPath(
|
|
sourceNode,
|
|
targetNode,
|
|
nodes,
|
|
sourceId,
|
|
targetId,
|
|
out var preferredShortcut)
|
|
&& (targetNode is null || !HasTargetApproachBacktracking(preferredShortcut, targetNode)))
|
|
{
|
|
return preferredShortcut;
|
|
}
|
|
}
|
|
|
|
var candidateEndpoints = EnumerateShortestRepairEndpoints(start, end, targetNode).ToArray();
|
|
var minLineClearance = ResolveMinLineClearance(nodes);
|
|
var shortcutObstaclePadding = Math.Max(12d, Math.Min(routingParams.Margin, Math.Max(18d, minLineClearance - 4d)));
|
|
List<ElkPoint>? bestPath = null;
|
|
var bestLength = double.MaxValue;
|
|
|
|
foreach (var candidateEnd in candidateEndpoints)
|
|
{
|
|
var orthogonalShortcut = TryBuildShortestOrthogonalPath(
|
|
start,
|
|
candidateEnd,
|
|
nodes,
|
|
sourceId,
|
|
targetId,
|
|
targetNode,
|
|
shortcutObstaclePadding);
|
|
if (orthogonalShortcut is null
|
|
|| (targetNode is not null && HasTargetApproachBacktracking(orthogonalShortcut, targetNode)))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var shortcutLength = ComputePolylineLength(orthogonalShortcut);
|
|
if (shortcutLength < bestLength - 0.5d)
|
|
{
|
|
bestPath = orthogonalShortcut;
|
|
bestLength = shortcutLength;
|
|
}
|
|
}
|
|
|
|
foreach (var candidateEnd in candidateEndpoints)
|
|
{
|
|
var localSkirtShortcut = TryBuildLocalObstacleSkirtPath(
|
|
start,
|
|
candidateEnd,
|
|
nodes,
|
|
sourceId,
|
|
targetId,
|
|
targetNode,
|
|
shortcutObstaclePadding);
|
|
if (localSkirtShortcut is null
|
|
|| (targetNode is not null && HasTargetApproachBacktracking(localSkirtShortcut, targetNode)))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var shortcutLength = ComputePolylineLength(localSkirtShortcut);
|
|
if (shortcutLength < bestLength - 0.5d)
|
|
{
|
|
bestPath = localSkirtShortcut;
|
|
bestLength = shortcutLength;
|
|
}
|
|
}
|
|
|
|
if (bestPath is not null)
|
|
{
|
|
return bestPath;
|
|
}
|
|
|
|
var shortestParams = routingParams with
|
|
{
|
|
Margin = Math.Max(shortcutObstaclePadding, Math.Min(routingParams.Margin, minLineClearance)),
|
|
BendPenalty = Math.Min(routingParams.BendPenalty, 80d),
|
|
DiagonalPenalty = Math.Min(routingParams.DiagonalPenalty, 40d),
|
|
SoftObstacleWeight = Math.Max(0.25d, routingParams.SoftObstacleWeight * 0.35d),
|
|
SoftObstacleClearance = Math.Max(Math.Max(18d, minLineClearance * 0.6d), routingParams.SoftObstacleClearance * 0.5d),
|
|
IntermediateGridSpacing = Math.Max(12d, routingParams.IntermediateGridSpacing - 8d),
|
|
};
|
|
var shortestObstacles = nodes
|
|
.Select(node => (
|
|
Left: node.X - shortestParams.Margin,
|
|
Top: node.Y - shortestParams.Margin,
|
|
Right: node.X + node.Width + shortestParams.Margin,
|
|
Bottom: node.Y + node.Height + shortestParams.Margin,
|
|
Id: node.Id))
|
|
.ToArray();
|
|
|
|
foreach (var candidateEnd in candidateEndpoints)
|
|
{
|
|
var diagonalPath = ElkEdgeRouterAStar8Dir.Route(
|
|
start,
|
|
candidateEnd,
|
|
shortestObstacles,
|
|
sourceId,
|
|
targetId,
|
|
shortestParams,
|
|
[],
|
|
cancellationToken);
|
|
if (diagonalPath is null
|
|
|| (targetNode is not null && HasTargetApproachBacktracking(diagonalPath, targetNode)))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var pathLength = ComputePolylineLength(diagonalPath);
|
|
if (pathLength < bestLength - 0.5d)
|
|
{
|
|
bestPath = diagonalPath;
|
|
bestLength = pathLength;
|
|
}
|
|
}
|
|
|
|
return bestPath;
|
|
}
|
|
|
|
private static List<ElkPoint>? TryRouteAggressiveRepair(
|
|
ElkPoint start,
|
|
ElkPoint end,
|
|
IReadOnlyCollection<ElkPositionedNode> nodes,
|
|
(double Left, double Top, double Right, double Bottom, string Id)[] obstacles,
|
|
string sourceId,
|
|
string targetId,
|
|
ElkPositionedNode? sourceNode,
|
|
ElkPositionedNode? targetNode,
|
|
AStarRoutingParams routingParams,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var candidateEndpoints = EnumerateShortestRepairEndpoints(start, end, targetNode).ToArray();
|
|
if (candidateEndpoints.Length == 0)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var minLineClearance = ResolveMinLineClearance(nodes);
|
|
var aggressiveParams = routingParams with
|
|
{
|
|
Margin = Math.Max(10d, Math.Min(routingParams.Margin, Math.Max(14d, minLineClearance * 0.45d))),
|
|
BendPenalty = Math.Min(routingParams.BendPenalty, 60d),
|
|
DiagonalPenalty = Math.Min(routingParams.DiagonalPenalty, 35d),
|
|
SoftObstacleWeight = 0d,
|
|
SoftObstacleClearance = 0d,
|
|
IntermediateGridSpacing = Math.Max(12d, Math.Min(routingParams.IntermediateGridSpacing, Math.Max(12d, minLineClearance * 0.45d))),
|
|
};
|
|
|
|
var aggressiveObstacles = obstacles
|
|
.Select(obstacle => (
|
|
Left: obstacle.Left + Math.Min(0d, aggressiveParams.Margin - routingParams.Margin),
|
|
Top: obstacle.Top + Math.Min(0d, aggressiveParams.Margin - routingParams.Margin),
|
|
Right: obstacle.Right - Math.Min(0d, aggressiveParams.Margin - routingParams.Margin),
|
|
Bottom: obstacle.Bottom - Math.Min(0d, aggressiveParams.Margin - routingParams.Margin),
|
|
obstacle.Id))
|
|
.ToArray();
|
|
|
|
List<ElkPoint>? bestPath = null;
|
|
ElkRoutedEdge? bestEdge = null;
|
|
|
|
foreach (var candidateEnd in candidateEndpoints)
|
|
{
|
|
var candidate = ElkEdgeRouterAStar8Dir.Route(
|
|
start,
|
|
candidateEnd,
|
|
aggressiveObstacles,
|
|
sourceId,
|
|
targetId,
|
|
aggressiveParams,
|
|
[],
|
|
cancellationToken);
|
|
if (candidate is null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (targetNode is not null && HasTargetApproachBacktracking(candidate, targetNode))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var candidateEdge = BuildCandidateRepairEdge(
|
|
sourceId,
|
|
targetId,
|
|
sourceNode,
|
|
targetNode,
|
|
candidate);
|
|
if (bestPath is null || CompareSingleEdgeRepairQuality(candidateEdge, bestEdge!, nodes) < 0)
|
|
{
|
|
bestPath = candidate;
|
|
bestEdge = candidateEdge;
|
|
}
|
|
}
|
|
|
|
return bestPath;
|
|
}
|
|
|
|
}
|