update
This commit is contained in:
482
Packages/com.arongranberg.astar/Pathfinders/MultiTargetPath.cs
Normal file
482
Packages/com.arongranberg.astar/Pathfinders/MultiTargetPath.cs
Normal file
@@ -0,0 +1,482 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using Pathfinding.Pooling;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace Pathfinding {
|
||||
/// <summary>
|
||||
/// A path which searches from one point to a number of different targets in one search or from a number of different start points to a single target.
|
||||
///
|
||||
/// This is faster than searching with an ABPath for each target if pathsForAll is true.
|
||||
/// This path type can be used for example when you want an agent to find the closest target of a few different options.
|
||||
///
|
||||
/// When pathsForAll is true, it will calculate a path to each target point, but it can share a lot of calculations for the different paths so
|
||||
/// it is faster than requesting them separately.
|
||||
///
|
||||
/// When pathsForAll is false, it will perform a search using the heuristic set to None and stop as soon as it finds the first target.
|
||||
/// This may be faster or slower than requesting each path separately.
|
||||
/// It will run a Dijkstra search where it searches all nodes around the start point until the closest target is found.
|
||||
/// Note that this is usually faster if some target points are very close to the start point and some are very far away, but
|
||||
/// it can be slower if all target points are relatively far away because then it will have to search a much larger
|
||||
/// region since it will not use any heuristics.
|
||||
///
|
||||
/// See: Seeker.StartMultiTargetPath
|
||||
/// See: MultiTargetPathExample.cs (view in online documentation for working links) "Example of how to use multi-target-paths"
|
||||
///
|
||||
/// Version: Since 3.7.1 the vectorPath and path fields are always set to the shortest path even when pathsForAll is true.
|
||||
/// </summary>
|
||||
public class MultiTargetPath : ABPath {
|
||||
/// <summary>Callbacks to call for each individual path</summary>
|
||||
public OnPathDelegate[] callbacks;
|
||||
|
||||
/// <summary>Nearest nodes to the <see cref="targetPoints"/></summary>
|
||||
public GraphNode[] targetNodes;
|
||||
|
||||
/// <summary>Number of target nodes left to find</summary>
|
||||
protected int targetNodeCount;
|
||||
|
||||
/// <summary>Indicates if the target has been found. Also true if the target cannot be reached (is in another area)</summary>
|
||||
public bool[] targetsFound;
|
||||
|
||||
/// <summary>The cost of the calculated path for each target. Will be 0 if a path was not found.</summary>
|
||||
public uint[] targetPathCosts;
|
||||
|
||||
/// <summary>Target points specified when creating the path. These are snapped to the nearest nodes</summary>
|
||||
public Vector3[] targetPoints;
|
||||
|
||||
/// <summary>Target points specified when creating the path. These are not snapped to the nearest nodes</summary>
|
||||
public Vector3[] originalTargetPoints;
|
||||
|
||||
/// <summary>Stores all vector paths to the targets. Elements are null if no path was found</summary>
|
||||
public List<Vector3>[] vectorPaths;
|
||||
|
||||
/// <summary>Stores all paths to the targets. Elements are null if no path was found</summary>
|
||||
public List<GraphNode>[] nodePaths;
|
||||
|
||||
/// <summary>If true, a path to all targets will be returned, otherwise just the one to the closest one.</summary>
|
||||
public bool pathsForAll = true;
|
||||
|
||||
/// <summary>The closest target index (if any target was found)</summary>
|
||||
public int chosenTarget = -1;
|
||||
|
||||
/// <summary>False if the path goes from one point to multiple targets. True if it goes from multiple start points to one target point</summary>
|
||||
public bool inverted { get; protected set; }
|
||||
|
||||
public override bool endPointKnownBeforeCalculation => false;
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor.
|
||||
/// Do not use this. Instead use the static Construct method which can handle path pooling.
|
||||
/// </summary>
|
||||
public MultiTargetPath () {}
|
||||
|
||||
public static MultiTargetPath Construct (Vector3[] startPoints, Vector3 target, OnPathDelegate[] callbackDelegates, OnPathDelegate callback = null) {
|
||||
MultiTargetPath p = Construct(target, startPoints, callbackDelegates, callback);
|
||||
|
||||
p.inverted = true;
|
||||
return p;
|
||||
}
|
||||
|
||||
public static MultiTargetPath Construct (Vector3 start, Vector3[] targets, OnPathDelegate[] callbackDelegates, OnPathDelegate callback = null) {
|
||||
var p = PathPool.GetPath<MultiTargetPath>();
|
||||
|
||||
p.Setup(start, targets, callbackDelegates, callback);
|
||||
return p;
|
||||
}
|
||||
|
||||
protected void Setup (Vector3 start, Vector3[] targets, OnPathDelegate[] callbackDelegates, OnPathDelegate callback) {
|
||||
inverted = false;
|
||||
this.callback = callback;
|
||||
callbacks = callbackDelegates;
|
||||
if (callbacks != null && callbacks.Length != targets.Length) throw new System.ArgumentException("The targets array must have the same length as the callbackDelegates array");
|
||||
targetPoints = targets;
|
||||
|
||||
originalStartPoint = start;
|
||||
|
||||
startPoint = start;
|
||||
|
||||
if (targets.Length == 0) {
|
||||
FailWithError("No targets were assigned to the MultiTargetPath");
|
||||
return;
|
||||
}
|
||||
|
||||
endPoint = targets[0];
|
||||
|
||||
originalTargetPoints = new Vector3[targetPoints.Length];
|
||||
for (int i = 0; i < targetPoints.Length; i++) {
|
||||
originalTargetPoints[i] = targetPoints[i];
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Reset () {
|
||||
base.Reset();
|
||||
pathsForAll = true;
|
||||
chosenTarget = -1;
|
||||
inverted = true;
|
||||
}
|
||||
|
||||
protected override void OnEnterPool () {
|
||||
if (vectorPaths != null)
|
||||
for (int i = 0; i < vectorPaths.Length; i++)
|
||||
if (vectorPaths[i] != null) ListPool<Vector3>.Release(vectorPaths[i]);
|
||||
|
||||
vectorPaths = null;
|
||||
vectorPath = null;
|
||||
|
||||
if (nodePaths != null)
|
||||
for (int i = 0; i < nodePaths.Length; i++)
|
||||
if (nodePaths[i] != null) ListPool<GraphNode>.Release(nodePaths[i]);
|
||||
|
||||
nodePaths = null;
|
||||
path = null;
|
||||
callbacks = null;
|
||||
targetNodes = null;
|
||||
targetsFound = null;
|
||||
targetPathCosts = null;
|
||||
targetPoints = null;
|
||||
originalTargetPoints = null;
|
||||
|
||||
base.OnEnterPool();
|
||||
}
|
||||
|
||||
/// <summary>Set chosenTarget to the index of the shortest path</summary>
|
||||
void ChooseShortestPath () {
|
||||
// When pathsForAll is false there will be at most one non-null path
|
||||
chosenTarget = -1;
|
||||
if (nodePaths != null) {
|
||||
uint bestG = uint.MaxValue;
|
||||
for (int i = 0; i < nodePaths.Length; i++) {
|
||||
if (nodePaths[i] != null) {
|
||||
var g = targetPathCosts[i];
|
||||
if (g < bestG) {
|
||||
chosenTarget = i;
|
||||
bestG = g;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SetPathParametersForReturn (int target) {
|
||||
path = nodePaths[target];
|
||||
vectorPath = vectorPaths[target];
|
||||
|
||||
if (inverted) {
|
||||
startPoint = targetPoints[target];
|
||||
originalStartPoint = originalTargetPoints[target];
|
||||
} else {
|
||||
endPoint = targetPoints[target];
|
||||
originalEndPoint = originalTargetPoints[target];
|
||||
}
|
||||
cost = path != null ? targetPathCosts[target] : 0;
|
||||
}
|
||||
|
||||
protected override void ReturnPath () {
|
||||
if (error) {
|
||||
// Call all callbacks
|
||||
if (callbacks != null) {
|
||||
for (int i = 0; i < callbacks.Length; i++)
|
||||
if (callbacks[i] != null) callbacks[i] (this);
|
||||
}
|
||||
|
||||
if (callback != null) callback(this);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
bool anySucceded = false;
|
||||
|
||||
// Set the end point to the start point
|
||||
// since the path is reversed
|
||||
// (the start point will be set individually for each path)
|
||||
if (inverted) {
|
||||
endPoint = startPoint;
|
||||
originalEndPoint = originalStartPoint;
|
||||
}
|
||||
|
||||
for (int i = 0; i < nodePaths.Length; i++) {
|
||||
if (nodePaths[i] != null) {
|
||||
// Note that we use the lowercase 'completeState' here.
|
||||
// The property (CompleteState) will ensure that the complete state is never
|
||||
// changed away from the error state but in this case we don't want that behaviour.
|
||||
completeState = PathCompleteState.Complete;
|
||||
anySucceded = true;
|
||||
} else {
|
||||
completeState = PathCompleteState.Error;
|
||||
}
|
||||
|
||||
if (callbacks != null && callbacks[i] != null) {
|
||||
SetPathParametersForReturn(i);
|
||||
callbacks[i] (this);
|
||||
|
||||
// In case a modifier changed the vectorPath, update the array of all vectorPaths
|
||||
vectorPaths[i] = vectorPath;
|
||||
}
|
||||
}
|
||||
|
||||
if (anySucceded) {
|
||||
completeState = PathCompleteState.Complete;
|
||||
SetPathParametersForReturn(chosenTarget);
|
||||
} else {
|
||||
completeState = PathCompleteState.Error;
|
||||
}
|
||||
|
||||
if (callback != null) {
|
||||
callback(this);
|
||||
}
|
||||
}
|
||||
|
||||
protected void RebuildOpenList () {
|
||||
BinaryHeap heap = pathHandler.heap;
|
||||
|
||||
for (int i = 0; i < heap.numberOfItems; i++) {
|
||||
var pathNodeIndex = heap.GetPathNodeIndex(i);
|
||||
Int3 pos;
|
||||
if (pathHandler.IsTemporaryNode(pathNodeIndex)) {
|
||||
pos = pathHandler.GetTemporaryNode(pathNodeIndex).position;
|
||||
} else {
|
||||
pos = pathHandler.GetNode(pathNodeIndex).DecodeVariantPosition(pathNodeIndex, pathHandler.pathNodes[pathNodeIndex].fractionAlongEdge);
|
||||
}
|
||||
// Note: node index can be 0 here because the multi target path never uses the euclidean embedding
|
||||
var hScore = (uint)heuristicObjective.Calculate((int3)pos, 0);
|
||||
heap.SetH(i, hScore);
|
||||
}
|
||||
|
||||
pathHandler.heap.Rebuild(pathHandler.pathNodes);
|
||||
}
|
||||
|
||||
protected override void Prepare () {
|
||||
var startNNInfo = GetNearest(startPoint);
|
||||
var startNode = startNNInfo.node;
|
||||
|
||||
if (endingCondition != null) {
|
||||
FailWithError("Multi target paths cannot use custom ending conditions");
|
||||
return;
|
||||
}
|
||||
|
||||
if (startNode == null) {
|
||||
FailWithError("Could not find start node for multi target path");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CanTraverse(startNode)) {
|
||||
FailWithError("The node closest to the start point could not be traversed");
|
||||
return;
|
||||
}
|
||||
|
||||
// Tell the NNConstraint which node was found as the start node if it is a PathNNConstraint and not a normal NNConstraint
|
||||
if (nnConstraint is PathNNConstraint pathNNConstraint) {
|
||||
pathNNConstraint.SetStart(startNNInfo.node);
|
||||
}
|
||||
|
||||
pathHandler.AddTemporaryNode(new TemporaryNode {
|
||||
associatedNode = startNNInfo.node.NodeIndex,
|
||||
position = (Int3)startNNInfo.position,
|
||||
type = TemporaryNodeType.Start,
|
||||
});
|
||||
|
||||
vectorPaths = new List<Vector3>[targetPoints.Length];
|
||||
nodePaths = new List<GraphNode>[targetPoints.Length];
|
||||
targetNodes = new GraphNode[targetPoints.Length];
|
||||
targetsFound = new bool[targetPoints.Length];
|
||||
targetPathCosts = new uint[targetPoints.Length];
|
||||
targetNodeCount = 0;
|
||||
|
||||
bool anyWalkable = false;
|
||||
bool anySameArea = false;
|
||||
bool anyNotNull = false;
|
||||
|
||||
for (int i = 0; i < targetPoints.Length; i++) {
|
||||
var originalTarget = targetPoints[i];
|
||||
var endNNInfo = GetNearest(originalTarget);
|
||||
|
||||
targetNodes[i] = endNNInfo.node;
|
||||
|
||||
targetPoints[i] = endNNInfo.position;
|
||||
if (targetNodes[i] != null) {
|
||||
anyNotNull = true;
|
||||
}
|
||||
|
||||
bool notReachable = false;
|
||||
|
||||
if (endNNInfo.node != null && CanTraverse(endNNInfo.node)) {
|
||||
anyWalkable = true;
|
||||
} else {
|
||||
notReachable = true;
|
||||
}
|
||||
|
||||
if (endNNInfo.node != null && endNNInfo.node.Area == startNode.Area) {
|
||||
anySameArea = true;
|
||||
} else {
|
||||
notReachable = true;
|
||||
}
|
||||
|
||||
if (notReachable) {
|
||||
// Signal that the pathfinder should not look for this node because we have already found it
|
||||
targetsFound[i] = true;
|
||||
} else {
|
||||
targetNodeCount++;
|
||||
#if !ASTAR_NO_GRID_GRAPH
|
||||
// Potentially we want to special case grid graphs a bit
|
||||
// to better support some kinds of games
|
||||
if (!EndPointGridGraphSpecialCase(endNNInfo.node, originalTarget, i))
|
||||
#endif
|
||||
{
|
||||
pathHandler.AddTemporaryNode(new TemporaryNode {
|
||||
associatedNode = endNNInfo.node.NodeIndex,
|
||||
position = (Int3)endNNInfo.position,
|
||||
targetIndex = i,
|
||||
type = TemporaryNodeType.End,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
startPoint = startNNInfo.position;
|
||||
|
||||
if (!anyNotNull) {
|
||||
FailWithError("Couldn't find a valid node close to the any of the end points");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!anyWalkable) {
|
||||
FailWithError("No target nodes could be traversed");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!anySameArea) {
|
||||
FailWithError("There's no valid path to any of the given targets");
|
||||
return;
|
||||
}
|
||||
|
||||
MarkNodesAdjacentToTemporaryEndNodes();
|
||||
AddStartNodesToHeap();
|
||||
RecalculateHTarget();
|
||||
}
|
||||
|
||||
void RecalculateHTarget () {
|
||||
if (pathsForAll) {
|
||||
// Sequentially go through all targets.
|
||||
// First we will find the path to the first target (or at least aim for it, we might find another one along the way),
|
||||
// then we will change the heuristic objective to the second target and so on.
|
||||
// This does not guarantee that we find the targets in order of closest to furthest,
|
||||
// but that is not required since we want to find all paths anyway.
|
||||
var target = FirstTemporaryEndNode();
|
||||
heuristicObjective = new HeuristicObjective(target, target, heuristic, heuristicScale, 0, null);
|
||||
} else {
|
||||
// Create a bounding box that contains all the end points,
|
||||
// and use that to calculate the heuristic.
|
||||
// This will ensure we find the closest target first.
|
||||
TemporaryEndNodesBoundingBox(out var mnTarget, out var mxTarget);
|
||||
heuristicObjective = new HeuristicObjective(mnTarget, mxTarget, heuristic, heuristicScale, 0, null);
|
||||
}
|
||||
|
||||
// Rebuild the open list since all the H scores have changed
|
||||
RebuildOpenList();
|
||||
}
|
||||
|
||||
protected override void Cleanup () {
|
||||
// Make sure that the shortest path is set
|
||||
// after the path has been calculated
|
||||
ChooseShortestPath();
|
||||
base.Cleanup();
|
||||
}
|
||||
|
||||
protected override void OnHeapExhausted () {
|
||||
CompleteState = PathCompleteState.Complete;
|
||||
}
|
||||
|
||||
protected override void OnFoundEndNode (uint pathNode, uint hScore, uint gScore) {
|
||||
if (!pathHandler.IsTemporaryNode(pathNode)) {
|
||||
FailWithError("Expected the end node to be a temporary node. Cannot determine which path it belongs to. This could happen if you are using a custom ending condition for the path.");
|
||||
return;
|
||||
}
|
||||
|
||||
var targetIndex = pathHandler.GetTemporaryNode(pathNode).targetIndex;
|
||||
if (targetsFound[targetIndex]) throw new System.ArgumentException("This target has already been found");
|
||||
|
||||
Trace(pathNode);
|
||||
vectorPaths[targetIndex] = vectorPath;
|
||||
nodePaths[targetIndex] = path;
|
||||
vectorPath = ListPool<Vector3>.Claim();
|
||||
path = ListPool<GraphNode>.Claim();
|
||||
|
||||
targetsFound[targetIndex] = true;
|
||||
targetPathCosts[targetIndex] = gScore;
|
||||
|
||||
targetNodeCount--;
|
||||
|
||||
// Mark all end nodes for this target as ignored to avoid including them
|
||||
// in the H-score calculation and to avoid calling OnFoundEndNode for this
|
||||
// target index again.
|
||||
for (uint i = 0; i < pathHandler.numTemporaryNodes; i++) {
|
||||
var nodeIndex = pathHandler.temporaryNodeStartIndex + i;
|
||||
ref var node = ref pathHandler.GetTemporaryNode(nodeIndex);
|
||||
if (node.type == TemporaryNodeType.End && node.targetIndex == targetIndex) {
|
||||
node.type = TemporaryNodeType.Ignore;
|
||||
}
|
||||
}
|
||||
|
||||
// When we find the first target, we can stop because it will be the closest one.
|
||||
if (!pathsForAll) {
|
||||
CompleteState = PathCompleteState.Complete;
|
||||
targetNodeCount = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// If there are no more targets to find, return here and avoid calculating a new hTarget
|
||||
if (targetNodeCount <= 0) {
|
||||
CompleteState = PathCompleteState.Complete;
|
||||
return;
|
||||
}
|
||||
|
||||
RecalculateHTarget();
|
||||
}
|
||||
|
||||
protected override void Trace (uint pathNodeIndex) {
|
||||
base.Trace(pathNodeIndex, !inverted);
|
||||
}
|
||||
|
||||
protected override string DebugString (PathLog logMode) {
|
||||
if (logMode == PathLog.None || (!error && logMode == PathLog.OnlyErrors)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
System.Text.StringBuilder text = pathHandler.DebugStringBuilder;
|
||||
text.Length = 0;
|
||||
|
||||
DebugStringPrefix(logMode, text);
|
||||
|
||||
if (!error) {
|
||||
text.Append("\nShortest path was ");
|
||||
text.Append(chosenTarget == -1 ? "undefined" : nodePaths[chosenTarget].Count.ToString());
|
||||
text.Append(" nodes long");
|
||||
|
||||
if (logMode == PathLog.Heavy) {
|
||||
text.Append("\nPaths (").Append(targetsFound.Length).Append("):");
|
||||
for (int i = 0; i < targetsFound.Length; i++) {
|
||||
text.Append("\n\n Path ").Append(i).Append(" Found: ").Append(targetsFound[i]);
|
||||
|
||||
if (nodePaths[i] != null) {
|
||||
text.Append("\n Length: ");
|
||||
text.Append(nodePaths[i].Count);
|
||||
}
|
||||
}
|
||||
|
||||
text.Append("\nStart Node");
|
||||
text.Append("\n Point: ");
|
||||
text.Append(((Vector3)endPoint).ToString());
|
||||
text.Append("\n Graph: ");
|
||||
text.Append(startNode.GraphIndex);
|
||||
text.Append("\nBinary Heap size at completion: ");
|
||||
text.AppendLine((pathHandler.heap.numberOfItems-2).ToString()); // -2 because numberOfItems includes the next item to be added and item zero is not used
|
||||
}
|
||||
}
|
||||
|
||||
DebugStringSuffix(logMode, text);
|
||||
|
||||
return text.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user