Aetheria/Assets/Plugins/MSAGL/Routing/Rectilinear/MsmtRectilinearPath.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

221 lines
No EOL
13 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Msagl.Core.Geometry;
using Microsoft.Msagl.Routing.Visibility;
namespace Microsoft.Msagl.Routing.Rectilinear {
internal class MsmtRectilinearPath {
private readonly double bendPenaltyAsAPercentageOfDistance = SsstRectilinearPath.DefaultBendPenaltyAsAPercentageOfDistance;
// Waypoints define path stages: source -> wp1, wp1 -> wp2 ... wpn -> target.
class WaypointEntry {
internal readonly VisibilityVertex[] waypointVector = new VisibilityVertex[1];
internal readonly VertexEntry[] entryVector = new VertexEntry[4];
internal WaypointEntry(VisibilityVertex waypoint) {
this.waypointVector[0] = waypoint;
}
}
// Temporary for accumulating target entries.
private readonly VertexEntry[] currentPassTargetEntries = new VertexEntry[4];
public MsmtRectilinearPath(double bendPenalty) {
this.bendPenaltyAsAPercentageOfDistance = bendPenalty;
}
/// <summary>
/// Get the lowest-cost path from one of one or more sources to one of one or more targets, without waypoints.
/// </summary>
/// <param name="sources">One or more source vertices</param>
/// <param name="targets">One or more target vertices</param>
/// <returns>A single enumeration of path points.</returns>
internal IEnumerable<Point> GetPath(IEnumerable<VisibilityVertex> sources, IEnumerable<VisibilityVertex> targets) {
return SsstRectilinearPath.RestorePath(GetPathStage(null, sources, null, targets));
}
/// <summary>
/// Get the lowest-cost path from one of one or more sources to one of one or more targets, with waypoints.
/// </summary>
/// <param name="sources">One or more source vertices</param>
/// <param name="waypoints">One or more waypoint vertices</param>
/// <param name="targets">One or more target vertices</param>
/// <returns>An enumeration of each stage's enumeration of path points</returns>
internal IEnumerable<IEnumerable<Point>> GetPath(IEnumerable<VisibilityVertex> sources, IEnumerable<VisibilityVertex> waypoints,
IEnumerable<VisibilityVertex> targets) {
var waypointEntries = new List<WaypointEntry>();
var waypointEnum = waypoints.GetEnumerator();
waypointEnum.MoveNext();
var sourceWaypointEntry = new WaypointEntry(waypointEnum.Current);
waypointEntries.Add(sourceWaypointEntry);
VertexEntry lastEntry;
if (!GetFirstPathStage(sourceWaypointEntry, sources)
|| !GetWaypointPathStages(waypointEnum, waypointEntries, ref sourceWaypointEntry)
|| !GetLastPathStage(sourceWaypointEntry, targets, out lastEntry)) {
return null;
}
return RestorePathStages(waypointEntries, lastEntry);
}
private bool GetFirstPathStage(WaypointEntry sourceWaypointEntry, IEnumerable<VisibilityVertex> sources) {
// Get the first stage: no prior entries to the sources; this target waypoint becomes the first waypoint stage's source.
return null != this.GetPathStage(null, sources, sourceWaypointEntry.entryVector, sourceWaypointEntry.waypointVector);
}
private bool GetWaypointPathStages(IEnumerator<VisibilityVertex> waypointEnum, List<WaypointEntry> waypointEntries, ref WaypointEntry sourceWaypointEntry) {
// Get the path stages between each waypoint: send the prior entries to source and get the entries to target.
while (waypointEnum.MoveNext()) {
var targetWaypointEntry = new WaypointEntry(waypointEnum.Current);
waypointEntries.Add(targetWaypointEntry);
if (null == this.GetPathStage(sourceWaypointEntry.entryVector, sourceWaypointEntry.waypointVector,
targetWaypointEntry.entryVector, targetWaypointEntry.waypointVector)) {
return false;
}
sourceWaypointEntry = targetWaypointEntry;
}
return true;
}
private bool GetLastPathStage(WaypointEntry sourceWaypointEntry, IEnumerable<VisibilityVertex> targets, out VertexEntry lastEntry) {
// Final stage: no need to get separate entries to the targets. And because we send null for the
// target entry vector, this will return the final vertexEntry of the path.
lastEntry = this.GetPathStage(sourceWaypointEntry.entryVector, sourceWaypointEntry.waypointVector, null, targets);
return lastEntry != null;
}
private static IEnumerable<IEnumerable<Point>> RestorePathStages(List<WaypointEntry> waypointEntries, VertexEntry lastEntry) {
// Back up to restore the path stages back to each waypoint.
var paths = new List<IEnumerable<Point>>();
waypointEntries.Reverse();
foreach (var waypointEntry in waypointEntries) {
paths.Add(SsstRectilinearPath.RestorePath(ref lastEntry, waypointEntry.waypointVector[0]));
}
// Add the first stage.
paths.Add(SsstRectilinearPath.RestorePath(lastEntry));
paths.Reverse();
return paths;
}
/// <summary>
/// Route a single stage of a possibly multi-stage (due to waypoints) path.
/// </summary>
/// <param name="sourceVertexEntries">The VertexEntry array that was in the source vertex if it was the target of a prior stage.</param>
/// <param name="sources">The enumeration of source vertices; must be only one if sourceVertexEntries is non-null.</param>
/// <param name="targets">The enumeration of target vertex entries; must be only one if targetVertexEntries is non-null.</param>
/// <param name="targetVertexEntries">The VertexEntry array that is in the target at the end of the stage.</param>
private VertexEntry GetPathStage(VertexEntry[] sourceVertexEntries, IEnumerable<VisibilityVertex> sources,
VertexEntry[] targetVertexEntries, IEnumerable<VisibilityVertex> targets) {
var ssstCalculator = new SsstRectilinearPath();
VertexEntry bestEntry = null;
// This contains the best (lowest) path cost after normalizing origins to the center of the sources
// and targets. This is used to avoid selecting a vertex pair whose path has more bends than another pair of
// vertices, but the bend penalty didn't total enough to offset the additional length between the "better" pair.
// This also plays the role of an upper bound on the path length; if a path cost is greater than adjustedMinCost
// then we stop exploring it, which saves considerable time after low-cost paths have been found.
double bestCost = double.MaxValue / ScanSegment.OverlappedWeight;
double bestPathCostRatio = double.PositiveInfinity;
// Calculate the bend penalty multiplier. This is a percentage of the distance between the source and target,
// so that we have the same relative importance if we have objects of about size 20 that are about 100 apart
// as for objects of about size 200 that are about 1000 apart.
Point sourceCenter = GetBarycenterOfUniquePortLocations(sources);
Point targetCenter = GetBarycenterOfUniquePortLocations(targets);
var distance = SsstRectilinearPath.ManhattanDistance(sourceCenter, targetCenter);
ssstCalculator.BendsImportance = Math.Max(0.001, distance * (this.bendPenaltyAsAPercentageOfDistance * 0.01));
// We'll normalize by adding (a proportion of) the distance (only; not bends) from the current endpoints to
// their centers. This is similar to routeToCenter, but routing multiple paths like this means we'll always
// get at least a tie for the best vertex pair, whereas routeToCenter can introduce extraneous bends
// if the sources/targets are not collinear with the center (such as an E-R diagram).
// interiorLengthAdjustment is a way to decrease the cost adjustment slightly to allow a bend if it saves moving
// a certain proportion of the distance parallel to the object before turning to it.
var interiorLengthAdjustment = ssstCalculator.LengthImportance;
// VertexEntries for the current pass of the current stage, if multistage.
var tempTargetEntries = (targetVertexEntries != null) ? this.currentPassTargetEntries : null;
// Process closest pairs first, so we can skip longer ones (jump out of SsstRectilinear sooner, often immediately).
// This means that we'll be consistent on tiebreaking for equal scores with differing bend counts (the shorter
// path will win). In overlapped graphs the shortest path may have more higher-weight edges.
foreach (var pair in
from VisibilityVertexRectilinear source in sources
from VisibilityVertexRectilinear target in targets
orderby SsstRectilinearPath.ManhattanDistance(source.Point, target.Point)
select new { sourceV = source, targetV = target }) {
var source = pair.sourceV;
var target = pair.targetV;
if (PointComparer.Equal(source.Point, target.Point)) {
continue;
}
var sourceCostAdjustment = SsstRectilinearPath.ManhattanDistance(source.Point, sourceCenter) * interiorLengthAdjustment;
var targetCostAdjustment = SsstRectilinearPath.ManhattanDistance(target.Point, targetCenter) * interiorLengthAdjustment;
var adjustedBestCost = bestCost;
if (targetVertexEntries != null) {
Array.Clear(tempTargetEntries, 0, tempTargetEntries.Length);
adjustedBestCost = ssstCalculator.MultistageAdjustedCostBound(bestCost);
}
VertexEntry lastEntry = ssstCalculator.GetPathWithCost(sourceVertexEntries, source, sourceCostAdjustment,
tempTargetEntries, target, targetCostAdjustment,
adjustedBestCost);
if (tempTargetEntries != null) {
UpdateTargetEntriesForEachDirection(targetVertexEntries, tempTargetEntries, ref bestCost, ref bestEntry);
continue;
}
// This is the final (or only) stage. Break ties by picking the lowest ratio of cost to ManhattanDistance between the endpoints.
if (lastEntry == null) {
continue;
}
var costRatio = lastEntry.Cost / SsstRectilinearPath.ManhattanDistance(source.Point, target.Point);
if ((lastEntry.Cost < bestCost) || ApproximateComparer.Close(lastEntry.Cost, bestCost) && (costRatio < bestPathCostRatio)) {
bestCost = lastEntry.Cost;
bestEntry = lastEntry;
bestPathCostRatio = lastEntry.Cost / SsstRectilinearPath.ManhattanDistance(source.Point, target.Point);
}
}
return bestEntry;
}
private static void UpdateTargetEntriesForEachDirection(VertexEntry[] targetVertexEntries, VertexEntry[] tempTargetEntries,
ref double bestCost, ref VertexEntry bestEntry) {
for (int ii = 0; ii < tempTargetEntries.Length; ++ii) {
var tempEntry = tempTargetEntries[ii];
if (tempEntry == null) {
continue;
}
if ((targetVertexEntries[ii] == null) || (tempEntry.Cost < targetVertexEntries[ii].Cost)) {
targetVertexEntries[ii] = tempEntry;
if (tempEntry.Cost < bestCost) {
// This does not have the ratio tiebreaker because the individual stage path is only used as a success indicator.
bestCost = tempEntry.Cost;
bestEntry = tempEntry;
}
}
}
return;
}
private static Point GetBarycenterOfUniquePortLocations(IEnumerable<VisibilityVertex> vertices) {
var center = new Point();
VisibilityVertex prevVertex = null;
int count = 0;
foreach (var vertex in vertices.OrderBy(s => s.Point)) {
if ((prevVertex != null) && ApproximateComparer.CloseIntersections(vertex.Point, prevVertex.Point)) {
continue;
}
prevVertex = vertex;
++count;
center += vertex.Point;
}
return center / count;
}
}
}