Files
git.stella-ops.org/src/__Libraries/StellaOps.ElkSharp/ElkEdgeRouterIterative.LocalRepair.ShortestRepair.cs

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;
}
}