Aetheria/Assets/Plugins/MSAGL/Routing/Rectilinear/SsstRectilinearPath.cs
Reinier Vens dbc522a224 Weapos Rejiggered
Modified weapon scripts to eliminate specialized projectile behavior - allows charged weapons to have projectiles and modify weapon stats when charged
Added machine gun weapon & VFX
Moved third party code to Plugins folder to improve compile times
2021-01-31 03:52:32 +00:00

743 lines
39 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.Msagl.Core.DataStructures;
using Microsoft.Msagl.Core.Geometry;
using Microsoft.Msagl.Core.Geometry.Curves;
using Microsoft.Msagl.Core.Layout;
using Microsoft.Msagl.DebugHelpers;
using Microsoft.Msagl.Routing.Visibility;
//following "Orthogonal Connector Routing" of Michael Wybrow and others
namespace Microsoft.Msagl.Routing.Rectilinear {
/// <summary>
/// single source single target rectilinear path
/// </summary>
internal class SsstRectilinearPath {
internal double LengthImportance { get; set; }
internal double BendsImportance { get; set; }
// Only bends importance needs to be public.
internal const double DefaultBendPenaltyAsAPercentageOfDistance = 4.0;
private VisibilityVertexRectilinear Target { get; set; }
private VisibilityVertexRectilinear Source { get; set; }
private Directions EntryDirectionsToTarget { get; set; }
private double upperBoundOnCost;
private double sourceCostAdjustment;
private double targetCostAdjustment;
/// <summary>
/// The cost of the path calculation
/// </summary>
private double CombinedCost(double length, double numberOfBends) {
return LengthImportance * length + BendsImportance * numberOfBends;
}
private double TotalCostFromSourceToVertex(double length, double numberOfBends) {
return this.CombinedCost(length, numberOfBends) + this.sourceCostAdjustment;
}
/// <summary>
/// The priority queue for path extensions.
/// </summary>
private GenericBinaryHeapPriorityQueueWithTimestamp<VertexEntry> queue;
/// <summary>
/// The list of vertices we've visited for all paths.
/// </summary>
private List<VisibilityVertexRectilinear> visitedVertices;
// For consistency and speed, path extensions impose an ordering as in the paper: straight, right, left. We
// enqueue entries in the reverse order of preference so the latest timestamp will be the preferred direction.
// Thus straight-ahead neighbors are in slot 2, right in slot 1, left in slot 0. (If the target happens
// to be to the Left, then the heuristic lookahead score will override the Right preference).
private class NextNeighbor {
internal VisibilityVertexRectilinear Vertex;
internal double Weight;
internal NextNeighbor() {
Clear();
}
internal void Set(VisibilityVertexRectilinear v, double w) {
this.Vertex = v;
this.Weight = w;
}
internal void Clear() {
this.Vertex = null;
this.Weight = double.NaN;
}
}
/// <summary>
/// The next neighbors to extend the path to from the current vertex.
/// </summary>
private readonly NextNeighbor[] nextNeighbors = new[] {new NextNeighbor(), new NextNeighbor(), new NextNeighbor() };
public SsstRectilinearPath() {
LengthImportance = 1.0;
BendsImportance = 1.0;
}
private bool InitPath(VertexEntry[] sourceVertexEntries, VisibilityVertexRectilinear source, VisibilityVertexRectilinear target) {
if ((source == target) || !InitEntryDirectionsAtTarget(target)) {
return false;
}
this.Target = target;
this.Source = source;
double cost = this.TotalCostFromSourceToVertex(0, 0) + HeuristicDistanceFromVertexToTarget(source.Point, Directions. None);
if (cost >= this.upperBoundOnCost) {
return false;
}
// This path starts lower than upperBoundOnCost, so create our structures and process it.
this.queue = new GenericBinaryHeapPriorityQueueWithTimestamp<VertexEntry>();
this.visitedVertices = new List<VisibilityVertexRectilinear> { source };
if (sourceVertexEntries == null) {
EnqueueInitialVerticesFromSource(cost);
} else {
EnqueueInitialVerticesFromSourceEntries(sourceVertexEntries);
}
return this.queue.Count > 0;
}
private bool InitEntryDirectionsAtTarget(VisibilityVertex vert) {
EntryDirectionsToTarget = Directions. None;
// This routine is only called once so don't worry about optimizing foreach.
foreach (var edge in vert.OutEdges) {
#if SHARPKIT //http://code.google.com/p/sharpkit/issues/detail?id=368 property assignment not working with |= operator
EntryDirectionsToTarget = EntryDirectionsToTarget | CompassVector.DirectionsFromPointToPoint(edge.TargetPoint, vert.Point);
#else
EntryDirectionsToTarget |= CompassVector.DirectionsFromPointToPoint(edge.TargetPoint, vert.Point);
#endif
}
foreach (var edge in vert.InEdges) {
#if SHARPKIT //http://code.google.com/p/sharpkit/issues/detail?id=368 property assignment not working with |= operator
EntryDirectionsToTarget = EntryDirectionsToTarget | CompassVector.DirectionsFromPointToPoint(edge.SourcePoint, vert.Point);
#else
EntryDirectionsToTarget |= CompassVector.DirectionsFromPointToPoint(edge.SourcePoint, vert.Point);
#endif
}
// If this returns false then the target is isolated.
return EntryDirectionsToTarget != Directions. None;
}
private static bool IsInDirs(Directions direction, Directions dirs) {
return direction == (direction & dirs);
}
internal double MultistageAdjustedCostBound(double bestCost) {
// Allow an additional bend's cost for intermediate stages so we don't jump out early.
return !double.IsPositiveInfinity(bestCost) ? bestCost + this.BendsImportance : bestCost;
}
/// <summary>
/// estimation from below for the distance
/// </summary>
/// <param name="point"></param>
/// <param name="entryDirToVertex"></param>
/// <returns></returns>
private double HeuristicDistanceFromVertexToTarget(Point point, Directions entryDirToVertex) {
Point vectorToTarget = Target.Point - point;
if (ApproximateComparer.Close(vectorToTarget.X, 0) && ApproximateComparer.Close(vectorToTarget.Y, 0)) {
// We are at the target.
return this.targetCostAdjustment;
}
Directions dirToTarget = CompassVector.VectorDirection(vectorToTarget);
int numberOfBends;
if (entryDirToVertex == Directions. None) {
entryDirToVertex = Directions.East | Directions.North | Directions.West | Directions.South;
numberOfBends = GetNumberOfBends(entryDirToVertex, dirToTarget);
} else {
numberOfBends = GetNumberOfBends(entryDirToVertex, dirToTarget);
}
return CombinedCost(ManhattanDistance(point, Target.Point), numberOfBends) + this.targetCostAdjustment;
}
private int GetNumberOfBends(Directions entryDirToVertex, Directions dirToTarget) {
return CompassVector.IsPureDirection(dirToTarget)
? GetNumberOfBendsForPureDirection(entryDirToVertex, dirToTarget)
: GetBendsForNotPureDirection(dirToTarget, entryDirToVertex, EntryDirectionsToTarget);
}
private int GetNumberOfBendsForPureDirection(Directions entryDirToVertex, Directions dirToTarget) {
if ( (dirToTarget & entryDirToVertex) == dirToTarget) {
if (IsInDirs(dirToTarget, EntryDirectionsToTarget)) {
return 0;
}
if (IsInDirs(Left(dirToTarget), EntryDirectionsToTarget) || IsInDirs(Right(dirToTarget), EntryDirectionsToTarget)) {
return 2;
}
return 4;
}
return GetNumberOfBendsForPureDirection(AddOneTurn[(int)entryDirToVertex], dirToTarget) + 1;
}
private static int GetBendsForNotPureDirection(Directions dirToTarget, Directions entryDirToVertex, Directions entryDirectionsToTarget) {
Directions a = dirToTarget & entryDirToVertex;
if (a == Directions. None) {
return GetBendsForNotPureDirection(dirToTarget, AddOneTurn[(int)entryDirToVertex], entryDirectionsToTarget) + 1;
}
Directions b = dirToTarget & entryDirectionsToTarget;
if (b == Directions. None) {
return GetBendsForNotPureDirection(dirToTarget, entryDirToVertex, AddOneTurn[(int)entryDirectionsToTarget]) + 1;
}
return (a | b) == dirToTarget ? 1 : 2;
}
private static readonly Directions[] AddOneTurn=new[] {
Directions.None, //Directions. None-> None
Directions.North|Directions.East|Directions.West, // 1=N -> N,E,W
Directions.North|Directions.East|Directions.South, // 2 =E->E|N|S
(Directions)15, // 3 =E|N->E|N|S
Directions.East|Directions.South|Directions.West, // 4 =S->E|S|W
(Directions)15, // 5 =E|N->E|N|S|W
(Directions)15, //6 - S|E
(Directions)15, //7
(Directions)13, //8=W
(Directions)15, //9
(Directions)15, //10
(Directions)15, //11
(Directions)15, //12
(Directions)15, //13
(Directions)15, //14
(Directions)15, //15
};
private static Directions Left(Directions direction) {
switch (direction) {
case Directions. None:
return Directions. None;
case Directions.North:
return Directions.West;
case Directions.East:
return Directions.North;
case Directions.South:
return Directions.East;
case Directions.West:
return Directions.South;
default:
throw new ArgumentOutOfRangeException("direction");
}
}
private static Directions Right(Directions direction) {
switch (direction) {
case Directions. None:
return Directions. None;
case Directions.North:
return Directions.East;
case Directions.East:
return Directions.South;
case Directions.South:
return Directions.West;
case Directions.West:
return Directions.North;
default:
throw new ArgumentOutOfRangeException("direction");
}
}
internal static IEnumerable<Point> RestorePath(VertexEntry entry) {
return RestorePath(ref entry, null);
}
internal static IEnumerable<Point> RestorePath(ref VertexEntry entry, VisibilityVertex firstVertexInStage) {
if (entry == null) {
return null;
}
var list = new List<Point>();
bool skippedCollinearEntry = false;
Directions lastEntryDir = Directions. None;
while (true) {
// Reduce unnecessary AxisEdge creations in Nudger by including only bend points, not points in the middle of a segment.
if (lastEntryDir == entry.Direction) {
skippedCollinearEntry = true;
} else {
skippedCollinearEntry = false;
list.Add(entry.Vertex.Point);
lastEntryDir = entry.Direction;
}
var previousEntry = entry.PreviousEntry;
if ((previousEntry == null) || (entry.Vertex == firstVertexInStage)) {
break;
}
entry = previousEntry;
}
if (skippedCollinearEntry) {
list.Add(entry.Vertex.Point);
}
list.Reverse();
return list;
}
#region TEST_MSAGL
// #if TEST_MSAGL
// // ReSharper disable UnusedMember.Local
// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "len"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "newEntry"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "nbend"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "iters"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "hcost"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "ccost"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "so"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "q"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "edges"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Console.WriteLine(System.String,System.Object[])"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
// private void TestShowAllPaths(VisibilityVertex source, VertexEntry mostRecentlyExtendedPath) {
// // ReSharper restore UnusedMember.Local
// var edges = GetAllEdgesTest(source).Select(e => (ICurve) (new LineSegment(e.SourcePoint, e.TargetPoint))).
// Select(c => new DebugCurve(c));
// var q = queue.Select(ent => CurveFactory.CreateDiamond(2, 2, ent.Vertex.Point)).Select(c => new DebugCurve(c));
// var so = new[] {
// new DebugCurve(1, "brown", new Ellipse(3, 3, source.Point)),
// new DebugCurve(1, "purple", CurveFactory.CreateDiamond(4, 4, Target.Point)),
// new DebugCurve(1, "red", CurveFactory.CreateDiamond(6, 6, mostRecentlyExtendedPath.Vertex.Point))
// };
//
// var pathEdges = new List<DebugCurve>();
// var newEntries = new List<VertexEntry>();
// var count = this.visitedVertices.Count;
// for (int ii = 0; ii < count; ++ii) {
// var vertex = this.visitedVertices[ii];
// if (vertex.VertexEntries == null) {
// continue; // this is the source vertex
// }
// foreach (var entry in vertex.VertexEntries) {
// if (entry == null) {
// continue;
// }
// var color = "green";
// if (entry.PreviousEntry == mostRecentlyExtendedPath) {
// newEntries.Add(entry);
// color = "red";
// } else if (!entry.IsClosed) {
// color = "yellow";
// }
// pathEdges.Add(new DebugCurve(2, color, new LineSegment(entry.PreviousVertex.Point, vertex.Point)));
// }
// }
// Console.WriteLine("entry {0} seq = {1} len = {2} nbend = {3} ccost = {4} hcost = {5} iters = {6}/{7}", mostRecentlyExtendedPath,
// this.lastDequeueTimestamp,
// mostRecentlyExtendedPath.Length, mostRecentlyExtendedPath.NumberOfBends,
// this.CombinedCost(mostRecentlyExtendedPath.Length, mostRecentlyExtendedPath.NumberOfBends),
// this.HeuristicDistanceFromVertexToTarget(mostRecentlyExtendedPath.Vertex.Point, mostRecentlyExtendedPath.Direction),
// this.currentIterations, totalIterations);
// foreach (var newEntry in newEntries) {
// Console.WriteLine(" newEntry {0} len = {1} nbend = {2} ccost = {3} hcost = {4}", newEntry,
// newEntry.Length, newEntry.NumberOfBends,
// this.CombinedCost(newEntry.Length, newEntry.NumberOfBends),
// this.HeuristicDistanceFromVertexToTarget(newEntry.Vertex.Point, newEntry.Direction));
// }
// DevTraceDisplay(edges.Concat(q).Concat(so).Concat(pathEdges));
// }
//
// // ReSharper disable UnusedMember.Local
// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "len"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "nbend"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "iters"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "so"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "pathEdges"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "edges"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Console.WriteLine(System.String,System.Object[])"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
// private void TestShowPath(VisibilityVertex source, IEnumerable<Point> pathPoints, double cost, double length, int numberOfBends) {
// // ReSharper restore UnusedMember.Local
// var edges = GetAllEdgesTest(source).Select(e => (ICurve) (new LineSegment(e.SourcePoint, e.TargetPoint))).
// Select(c => new DebugCurve(c));
// var so = new[] {
// new DebugCurve(1, "brown", new Ellipse(3, 3, source.Point)),
// new DebugCurve(1, "purple", CurveFactory.CreateDiamond(4, 4, Target.Point)),
// };
//
// List<DebugCurve> pathEdges = GetPathEdgeDebugCurves(pathPoints, "green");
// Console.WriteLine("path {0} -> {1} cost = {2} len = {3} nbend = {4} iters = {5}/{6}",
// source.Point, this.Target.Point, cost, length, numberOfBends, this.currentIterations, totalIterations);
// DevTraceDisplay(edges.Concat(so).Concat(pathEdges));
// }
//
// internal static List<DebugCurve> GetPathEdgeDebugCurves(IEnumerable<Point> pathPoints, string color) {
// var pathEdges = new List<DebugCurve>();
// if (pathPoints != null) {
// bool first = true;
// var prevPoint = new Point();
// foreach (var point in pathPoints) {
// if (first) {
// prevPoint = point;
// first = false;
// continue;
// }
// pathEdges.Add(new DebugCurve(2, color, new LineSegment(prevPoint, point)));
// prevPoint = point;
// }
// }
// return pathEdges;
// }
//
// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
// private IEnumerable<VisibilityEdge> GetAllEdgesTest(VisibilityVertex vertex) {
// var processedVertices = new Set<VisibilityVertex>();
// var q = new Queue<VisibilityVertex>();
// q.Enqueue(vertex);
//
// // Target may not be connected to source.
// bool targetFound = (vertex == this.Target);
// while (!targetFound) {
// while (q.Count > 0) {
// VisibilityVertex v = q.Dequeue();
// if (processedVertices.Contains(v)) {
// continue;
// }
// targetFound |= (v == this.Target);
// processedVertices.Insert(v);
// foreach (var u in VertsToGo(v).Where(u => !processedVertices.Contains(u))) {
// q.Enqueue(u);
// }
// foreach (VisibilityEdge edge in v.OutEdges) {
// yield return edge;
// }
// }
// q.Enqueue(this.Target);
// targetFound = true;
// }
// }
//
// private static IEnumerable<VisibilityVertex> VertsToGo(VisibilityVertex visibilityVertex) {
// foreach (var edge in visibilityVertex.OutEdges) {
// yield return edge.Target;
// }
// foreach (var edge in visibilityVertex.InEdges) {
// yield return edge.Source;}
// }
//
// #endif // TEST_MSAGL
#endregion // TEST_MSAGL
private void QueueReversedEntryToNeighborVertexIfNeeded(VertexEntry bestEntry, VertexEntry entryFromNeighbor, double weight) {
// If we have a lower-cost path from bestEntry to entryFromNeighbor.PreviousVertex than the cost of entryFromNeighbor,
// or bestEntry has degree 1 (it is a dead-end), enqueue a path in the opposite direction (entryFromNeighbor will probably
// never be extended from this point).
int numberOfBends;
double length;
var neigVer = entryFromNeighbor.PreviousVertex;
var dirToNeighbor = GetLengthAndNumberOfBendsToNeighborVertex(bestEntry, neigVer, weight, out numberOfBends, out length);
if ((CombinedCost(length, numberOfBends) < CombinedCost(entryFromNeighbor.Length, entryFromNeighbor.NumberOfBends))
|| (bestEntry.Vertex.Degree == 1)) {
var cost = this.TotalCostFromSourceToVertex(length, numberOfBends) + HeuristicDistanceFromVertexToTarget(neigVer.Point, dirToNeighbor);
EnqueueEntry(bestEntry, neigVer, length, numberOfBends, cost);
}
}
private void UpdateEntryToNeighborVertexIfNeeded(VertexEntry bestEntry, VertexEntry neigEntry, double weight) {
int numberOfBends;
double length;
var dirToNeighbor = GetLengthAndNumberOfBendsToNeighborVertex(bestEntry, neigEntry.Vertex, weight, out numberOfBends, out length);
if (CombinedCost(length, numberOfBends) < CombinedCost(neigEntry.Length, neigEntry.NumberOfBends)) {
var newCost = this.TotalCostFromSourceToVertex(length, numberOfBends) + HeuristicDistanceFromVertexToTarget(neigEntry.Vertex.Point, dirToNeighbor);
neigEntry.ResetEntry(bestEntry, length, numberOfBends, newCost);
queue.DecreasePriority(neigEntry, newCost);
}
}
private void CreateAndEnqueueEntryToNeighborVertex(VertexEntry bestEntry, VisibilityVertexRectilinear neigVer, double weight) {
int numberOfBends;
double length;
var dirToNeighbor = GetLengthAndNumberOfBendsToNeighborVertex(bestEntry, neigVer, weight, out numberOfBends, out length);
var cost = this.TotalCostFromSourceToVertex(length, numberOfBends) + HeuristicDistanceFromVertexToTarget(neigVer.Point, dirToNeighbor);
if (cost < this.upperBoundOnCost) {
if (neigVer.VertexEntries == null) {
this.visitedVertices.Add(neigVer);
}
EnqueueEntry(bestEntry, neigVer, length, numberOfBends, cost);
}
}
private void EnqueueEntry(VertexEntry bestEntry, VisibilityVertexRectilinear neigVer, double length, int numberOfBends, double cost) {
var entry = new VertexEntry(neigVer, bestEntry, length, numberOfBends, cost);
neigVer.SetVertexEntry(entry);
this.queue.Enqueue(entry, entry.Cost);
}
private static Directions GetLengthAndNumberOfBendsToNeighborVertex(VertexEntry prevEntry,
VisibilityVertex vertex, double weight, out int numberOfBends, out double length) {
length = prevEntry.Length + ManhattanDistance(prevEntry.Vertex.Point, vertex.Point)*weight;
Directions directionToVertex = CompassVector.PureDirectionFromPointToPoint(prevEntry.Vertex.Point, vertex.Point);
numberOfBends = prevEntry.NumberOfBends;
if (prevEntry.Direction != Directions. None && directionToVertex != prevEntry.Direction) {
numberOfBends++;
}
return directionToVertex;
}
internal static double ManhattanDistance(Point a, Point b) {
return Math.Abs(b.X - a.X) + Math.Abs(b.Y - a.Y);
}
internal VertexEntry GetPathWithCost(VertexEntry[] sourceVertexEntries, VisibilityVertexRectilinear source, double adjustmentToSourceCost,
VertexEntry[] targetVertexEntries, VisibilityVertexRectilinear target, double adjustmentToTargetCost,
double priorBestCost) {
this.upperBoundOnCost = priorBestCost;
this.sourceCostAdjustment = adjustmentToSourceCost;
this.targetCostAdjustment = adjustmentToTargetCost;
DevTracePrintSourceAndTarget(source, target);
if (!InitPath(sourceVertexEntries, source, target)) {
this.DevTraceShowPath(source, null);
return null;
}
#if TEST_MSAGL
this.DevTraceShowAllPartialPaths(source, queue.Peek());
#endif // TEST_MSAGL
while (queue.Count > 0) {
this.TestPreDequeue();
var bestEntry = queue.Dequeue();
var bestVertex = bestEntry.Vertex;
if (bestVertex == Target) {
this.DevTraceShowPath(source, bestEntry);
if (targetVertexEntries == null) {
Cleanup();
return bestEntry;
}
// We'll never get a duplicate entry direction here; we either relaxed the cost via UpdateEntryToNeighborIfNeeded
// before we dequeued it, or it was closed. So, we simply remove the direction from the valid target entry directions
// and if we get to none, we're done. We return a null path until the final stage.
#if SHARPKIT //http://code.google.com/p/sharpkit/issues/detail?id=368 property assignment not working with &= operator
this.EntryDirectionsToTarget = this.EntryDirectionsToTarget & ~bestEntry.Direction;
#else
this.EntryDirectionsToTarget &= ~bestEntry.Direction;
#endif
if (this.EntryDirectionsToTarget == Directions. None) {
this.Target.VertexEntries.CopyTo(targetVertexEntries, 0);
Cleanup();
return null;
}
this.upperBoundOnCost = Math.Min(this.MultistageAdjustedCostBound(bestEntry.Cost), this.upperBoundOnCost);
continue;
}
// It's safe to close this after removing it from the queue. Any updateEntryIfNeeded that changes it must come
// while it is still on the queue; it is removed from the queue only if it has the lowest cost path, and we have
// no negative path weights, so any other path that might try to extend to it after this cannot have a lower cost.
bestEntry.IsClosed = true;
// PerfNote: Array.ForEach is optimized, but don't use .Where.
foreach (var bendNeighbor in this.nextNeighbors) {
bendNeighbor.Clear();
}
var preferredBendDir = Right(bestEntry.Direction);
this.ExtendPathAlongInEdges(bestEntry, bestVertex.InEdges, preferredBendDir);
this.ExtendPathAlongOutEdges(bestEntry, bestVertex.OutEdges, preferredBendDir);
foreach (var bendNeighbor in this.nextNeighbors) {
if (bendNeighbor.Vertex != null) {
this.ExtendPathToNeighborVertex(bestEntry, bendNeighbor.Vertex, bendNeighbor.Weight);
}
}
this.DevTraceShowAllPartialPaths(source, bestEntry);
}
// Either there is no path to the target, or we have abandoned the path due to exceeding priorBestCost.
if ((targetVertexEntries != null) && (this.Target.VertexEntries != null)) {
this.Target.VertexEntries.CopyTo(targetVertexEntries, 0);
}
this.DevTraceShowPath(source, null);
Cleanup();
return null;
}
private void ExtendPathAlongInEdges(VertexEntry bestEntry, List<VisibilityEdge> edges, Directions preferredBendDir) {
// Iteration is faster than foreach and much faster than .Where.
int count = edges.Count;
for (int ii = 0; ii < count; ++ii) {
var edge = edges[ii];
ExtendPathAlongEdge(bestEntry, edge, true, preferredBendDir);
}
}
private void ExtendPathAlongOutEdges(VertexEntry bestEntry, RbTree<VisibilityEdge> edges, Directions preferredBendDir) {
// Avoid GetEnumerator overhead.
var outEdgeNode = edges.IsEmpty() ? null : edges.TreeMinimum();
for (; outEdgeNode != null; outEdgeNode = edges.Next(outEdgeNode)) {
ExtendPathAlongEdge(bestEntry, outEdgeNode.Item, false, preferredBendDir);
}
}
private void ExtendPathAlongEdge(VertexEntry bestEntry, VisibilityEdge edge, bool isInEdges, Directions preferredBendDir) {
if (!IsPassable(edge)) {
return;
}
// This is after the initial source vertex so PreviousEntry won't be null.
var neigVer = (VisibilityVertexRectilinear)(isInEdges ? edge.Source : edge.Target);
if (neigVer == bestEntry.PreviousVertex) {
// For multistage paths, the source may be a waypoint outside the graph boundaries that is collinear
// with both the previous and next points in the path; in that case it may have only one degree.
// For other cases, we just ignore it and the path will be abandoned.
if ((bestEntry.Vertex.Degree > 1) || (bestEntry.Vertex != this.Source)) {
return;
}
this.ExtendPathToNeighborVertex(bestEntry, neigVer, edge.Weight);
return;
}
// Enqueue in reverse order of preference per comments on NextNeighbor class.
var neigDir = CompassVector.PureDirectionFromPointToPoint(bestEntry.Vertex.Point, neigVer.Point);
var nextNeighbor = this.nextNeighbors[2];
if (neigDir != bestEntry.Direction) {
nextNeighbor = this.nextNeighbors[(neigDir == preferredBendDir) ? 1 : 0];
}
Debug.Assert(nextNeighbor.Vertex == null, "bend neighbor already exists");
nextNeighbor.Set(neigVer, edge.Weight);
}
private void EnqueueInitialVerticesFromSource(double cost) {
var bestEntry = new VertexEntry(this.Source, null, 0, 0, cost) {
IsClosed = true
};
// This routine is only called once so don't worry about optimizing foreach.where
foreach (var edge in this.Source.OutEdges.Where(IsPassable)) {
this.ExtendPathToNeighborVertex(bestEntry, (VisibilityVertexRectilinear)edge.Target, edge.Weight);
}
foreach (var edge in this.Source.InEdges.Where(IsPassable)) {
this.ExtendPathToNeighborVertex(bestEntry, (VisibilityVertexRectilinear)edge.Source, edge.Weight);
}
}
private void EnqueueInitialVerticesFromSourceEntries(VertexEntry[] sourceEntries) {
foreach (var entry in sourceEntries) {
if (entry != null) {
this.queue.Enqueue(entry, entry.Cost);
}
}
}
private void ExtendPathToNeighborVertex(VertexEntry bestEntry, VisibilityVertexRectilinear neigVer, double weight) {
var dirToNeighbor = CompassVector.PureDirectionFromPointToPoint(bestEntry.Vertex.Point, neigVer.Point);
var neigEntry = (neigVer.VertexEntries != null) ? neigVer.VertexEntries[CompassVector.ToIndex(dirToNeighbor)] : null;
if (neigEntry == null) {
if (!this.CreateAndEnqueueReversedEntryToNeighborVertex(bestEntry, neigVer, weight)) {
this.CreateAndEnqueueEntryToNeighborVertex(bestEntry, neigVer, weight);
}
} else if (!neigEntry.IsClosed) {
this.UpdateEntryToNeighborVertexIfNeeded(bestEntry, neigEntry, weight);
}
}
private bool CreateAndEnqueueReversedEntryToNeighborVertex(VertexEntry bestEntry, VisibilityVertexRectilinear neigVer, double weight) {
// VertexEntries is null for the initial source. Otherwise, if there is already a path into bestEntry's vertex
// from neigVer, we're turning back on the path; therefore we have already enqueued the neighbors of neigVer.
// However, the path cost includes both path length to the current point and the lookahead; this means that we
// may now be coming into the neigVer from the opposite side with an equal score to the previous entry, but
// the new path may be going toward the target while the old one (from neigVer to bestEntry) went away from
// the target. So, if we score better going in the opposite direction, enqueue bestEntry->neigVer; ignore
// neigVer->bestEntry as it probably won't be extended again.
if (bestEntry.Vertex.VertexEntries != null) {
var dirFromNeighbor = CompassVector.PureDirectionFromPointToPoint(neigVer.Point, bestEntry.Vertex.Point);
var entryFromNeighbor = bestEntry.Vertex.VertexEntries[CompassVector.ToIndex(dirFromNeighbor)];
if (entryFromNeighbor != null) {
Debug.Assert(entryFromNeighbor.PreviousVertex == neigVer, "mismatch in turnback PreviousEntry");
Debug.Assert(entryFromNeighbor.PreviousEntry.IsClosed, "turnback PreviousEntry should be closed");
this.QueueReversedEntryToNeighborVertexIfNeeded(bestEntry, entryFromNeighbor, weight);
return true;
}
}
return false;
}
private static bool IsPassable(VisibilityEdge edge) {
return edge.IsPassable == null || edge.IsPassable();
}
private void Cleanup()
{
foreach (var v in this.visitedVertices) {
v.RemoveVertexEntries();
}
this.visitedVertices.Clear();
this.queue = null;
this.TestClearIterations();
}
#region DevTrace
#if DEVTRACE
private static readonly DevTrace ssstTrace = new DevTrace("Rectilinear_SsstTrace", "Ssst");
private static readonly DevTrace ssstDisplay = new DevTrace("Rectilinear_SsstDisplay");
#endif // DEVTRACE
[Conditional("DEVTRACE")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
void DevTraceShowAllPartialPaths(VisibilityVertex source, VertexEntry mostRecentlyExtendedPath) {
#if DEVTRACE
if (ssstTrace.IsLevel(3))
{
this.TestShowAllPaths(source, mostRecentlyExtendedPath);
}
#endif // DEVTRACE
}
[Conditional("DEVTRACE")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
private void DevTraceShowPath(VisibilityVertex source, VertexEntry lastEntry) {
#if DEVTRACE
if (ssstTrace.IsLevel(1)) {
var pathPoints = RestorePath(lastEntry);
if (pathPoints != null) {
this.TestShowPath(source, pathPoints, lastEntry.Cost, lastEntry.Length, lastEntry.NumberOfBends);
return;
}
Console.WriteLine("path abandoned; iters = {0}/{1}", this.currentIterations, totalIterations);
}
#endif // DEVTRACE
}
[Conditional("DEVTRACE")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
private void DevTracePrintSourceAndTarget(VisibilityVertex source, VisibilityVertex target) {
#if DEVTRACE
if (ssstTrace.IsLevel(2))
{
// Don't use this.Target as it hasn't been set yet.
Console.WriteLine("find path: {0} -> {1} distance {2} sourceAdjust {3} targetAdjust {4} bendPenalty {5}",
source.Point, target.Point, ManhattanDistance(source.Point, target.Point),
this.sourceCostAdjustment, this.targetCostAdjustment, this.BendsImportance);
}
#endif // DEVTRACE
}
// #if TEST_MSAGL
// [Conditional("DEVTRACE")]
// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
// private static void DevTraceDisplay(IEnumerable<DebugCurve> debugCurves)
// {
// #if DEVTRACE
// if (!ssstDisplay.IsLevel(1))
// {
// return;
// }
// #endif // DEVTRACE
// LayoutAlgorithmSettings.ShowDebugCurvesEnumeration(debugCurves);
// }
// #endif // TEST_MSAGL
// #if TEST_MSAGL
// private int currentIterations;
// private static int totalIterations;
// private UInt64 lastDequeueTimestamp;
// #endif // TEST_MSAGL
[Conditional("TEST_MSAGL")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
private void TestPreDequeue() {
// #if TEST_MSAGL
// ++currentIterations;
// ++totalIterations;
// this.lastDequeueTimestamp = this.queue.PeekTimestamp();
// #endif // TEST_MSAGL
}
[Conditional("TEST_MSAGL")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
private void TestClearIterations() {
// #if TEST_MSAGL
// currentIterations = 0;
// #endif // TEST_MSAGL
}
#endregion // DevTrace
}
}