123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697 |
- using UnityEngine;
- using System.Collections.Generic;
- 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>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>
- /// Current target for Sequential <see cref="heuristicMode"/>.
- /// Refers to an item in the targetPoints array
- /// </summary>
- int sequentialTarget;
- /// <summary>
- /// How to calculate the heuristic.
- /// The <see cref="<see cref="hTarget"/> heuristic target"/> can be calculated in different ways,
- /// by taking the Average position of all targets, or taking the mid point of them (i.e center of the AABB encapsulating all targets).
- ///
- /// The one which works best seems to be Sequential, it sets <see cref="hTarget"/> to the target furthest away, and when that target is found, it moves on to the next one.
- /// Some modes have the option to be 'moving' (e.g 'MovingAverage'), that means that it is updated every time a target is found.
- /// The H score is calculated according to AstarPath.heuristic
- ///
- /// Note: If pathsForAll is false then this option is ignored and it is always treated as being set to None
- /// </summary>
- public HeuristicMode heuristicMode = HeuristicMode.Sequential;
- public enum HeuristicMode {
- None,
- Average,
- MovingAverage,
- Midpoint,
- MovingMidpoint,
- Sequential
- }
- /// <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; }
- /// <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;
- startIntPoint = (Int3)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;
- sequentialTarget = 0;
- inverted = true;
- heuristicMode = HeuristicMode.Sequential;
- }
- protected override void OnEnterPool () {
- if (vectorPaths != null)
- for (int i = 0; i < vectorPaths.Length; i++)
- if (vectorPaths[i] != null) Util.ListPool<Vector3>.Release(vectorPaths[i]);
- vectorPaths = null;
- vectorPath = null;
- if (nodePaths != null)
- for (int i = 0; i < nodePaths.Length; i++)
- if (nodePaths[i] != null) Util.ListPool<GraphNode>.Release(nodePaths[i]);
- nodePaths = null;
- path = null;
- callbacks = null;
- targetNodes = null;
- targetsFound = 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 only be one non-null path
- chosenTarget = -1;
- if (nodePaths != null) {
- uint bestG = int.MaxValue;
- for (int i = 0; i < nodePaths.Length; i++) {
- var currentPath = nodePaths[i];
- if (currentPath != null) {
- // Get the G score of the first or the last node in the path
- // depending on if the paths are reversed or not
- var g = pathHandler.GetPathNode(currentPath[inverted ? 0 : currentPath.Count-1]).G;
- if (chosenTarget == -1 || g < bestG) {
- chosenTarget = i;
- bestG = g;
- }
- }
- }
- }
- }
- void SetPathParametersForReturn (int target) {
- path = nodePaths[target];
- vectorPath = vectorPaths[target];
- if (inverted) {
- startNode = targetNodes[target];
- startPoint = targetPoints[target];
- originalStartPoint = originalTargetPoints[target];
- } else {
- endNode = targetNodes[target];
- endPoint = targetPoints[target];
- originalEndPoint = originalTargetPoints[target];
- }
- }
- 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;
- endNode = startNode;
- 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 FoundTarget (PathNode nodeR, int i) {
- nodeR.flag1 = false; // Reset bit 8
- Trace(nodeR);
- vectorPaths[i] = vectorPath;
- nodePaths[i] = path;
- vectorPath = Util.ListPool<Vector3>.Claim();
- path = Util.ListPool<GraphNode>.Claim();
- targetsFound[i] = true;
- targetNodeCount--;
- // Since we have found one target
- // and the heuristic is always set to None when
- // pathsForAll is false, we will have found the shortest path
- 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(false);
- }
- protected void RebuildOpenList () {
- BinaryHeap heap = pathHandler.heap;
- for (int j = 0; j < heap.numberOfItems; j++) {
- PathNode nodeR = heap.GetNode(j);
- nodeR.H = CalculateHScore(nodeR.node);
- heap.SetF(j, nodeR.F);
- }
- pathHandler.heap.Rebuild();
- }
- protected override void Prepare () {
- nnConstraint.tags = enabledTags;
- var startNNInfo = AstarPath.active.GetNearest(startPoint, nnConstraint);
- startNode = startNNInfo.node;
- 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
- var pathNNConstraint = nnConstraint as PathNNConstraint;
- if (pathNNConstraint != null) {
- pathNNConstraint.SetStart(startNNInfo.node);
- }
- vectorPaths = new List<Vector3>[targetPoints.Length];
- nodePaths = new List<GraphNode>[targetPoints.Length];
- targetNodes = new GraphNode[targetPoints.Length];
- targetsFound = new bool[targetPoints.Length];
- targetNodeCount = targetPoints.Length;
- bool anyWalkable = false;
- bool anySameArea = false;
- bool anyNotNull = false;
- for (int i = 0; i < targetPoints.Length; i++) {
- var endNNInfo = AstarPath.active.GetNearest(targetPoints[i], nnConstraint);
- targetNodes[i] = endNNInfo.node;
- targetPoints[i] = endNNInfo.position;
- if (targetNodes[i] != null) {
- anyNotNull = true;
- endNode = targetNodes[i];
- }
- 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;
- targetNodeCount--;
- }
- }
- startPoint = startNNInfo.position;
- startIntPoint = (Int3)startPoint;
- 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 are no valid paths to the targets");
- return;
- }
- RecalculateHTarget(true);
- }
- void RecalculateHTarget (bool firstTime) {
- // When pathsForAll is false
- // then no heuristic should be used
- if (!pathsForAll) {
- heuristic = Heuristic.None;
- heuristicScale = 0.0F;
- return;
- }
- // Calculate a new hTarget and rebuild the open list if necessary
- // Rebuilding the open list is necessary when the H score for nodes changes
- switch (heuristicMode) {
- case HeuristicMode.None:
- heuristic = Heuristic.None;
- heuristicScale = 0F;
- break;
- case HeuristicMode.Average:
- if (!firstTime) return;
- // No break
- // The first time the implementation
- // for Average and MovingAverage is identical
- // so we just use fallthrough
- goto case HeuristicMode.MovingAverage;
- case HeuristicMode.MovingAverage:
- // Pick the average position of all nodes that have not been found yet
- var avg = Vector3.zero;
- int count = 0;
- for (int j = 0; j < targetPoints.Length; j++) {
- if (!targetsFound[j]) {
- avg += (Vector3)targetNodes[j].position;
- count++;
- }
- }
- // Should use asserts, but they were first added in Unity 5.1
- // so I cannot use them because I want to keep compatibility with 4.6
- // (as of 2015)
- if (count == 0) throw new System.Exception("Should not happen");
- avg /= count;
- hTarget = (Int3)avg;
- break;
- case HeuristicMode.Midpoint:
- if (!firstTime) return;
- // No break
- // The first time the implementation
- // for Midpoint and MovingMidpoint is identical
- // so we just use fallthrough
- goto case HeuristicMode.MovingMidpoint;
- case HeuristicMode.MovingMidpoint:
- Vector3 min = Vector3.zero;
- Vector3 max = Vector3.zero;
- bool set = false;
- // Pick the median of all points that have
- // not been found yet
- for (int j = 0; j < targetPoints.Length; j++) {
- if (!targetsFound[j]) {
- if (!set) {
- min = (Vector3)targetNodes[j].position;
- max = (Vector3)targetNodes[j].position;
- set = true;
- } else {
- min = Vector3.Min((Vector3)targetNodes[j].position, min);
- max = Vector3.Max((Vector3)targetNodes[j].position, max);
- }
- }
- }
- var midpoint = (Int3)((min+max)*0.5F);
- hTarget = midpoint;
- break;
- case HeuristicMode.Sequential:
- // The first time the hTarget should always be recalculated
- // But other times we can skip it if we have not yet found the current target
- // since then the hTarget would just be set to the same value again
- if (!firstTime && !targetsFound[sequentialTarget]) {
- return;
- }
- float dist = 0;
- // Pick the target which is furthest away and has not been found yet
- for (int j = 0; j < targetPoints.Length; j++) {
- if (!targetsFound[j]) {
- float d = (targetNodes[j].position-startNode.position).sqrMagnitude;
- if (d > dist) {
- dist = d;
- hTarget = (Int3)targetPoints[j];
- sequentialTarget = j;
- }
- }
- }
- break;
- }
- // Rebuild the open list since all the H scores have changed
- // However the first time we can skip this since
- // no nodes are added to the heap yet
- if (!firstTime) {
- RebuildOpenList();
- }
- }
- protected override void Initialize () {
- // Reset the start node to prevent
- // old info from previous paths to be used
- PathNode startRNode = pathHandler.GetPathNode(startNode);
- startRNode.node = startNode;
- startRNode.pathID = pathID;
- startRNode.parent = null;
- startRNode.cost = 0;
- startRNode.G = GetTraversalCost(startNode);
- startRNode.H = CalculateHScore(startNode);
- for (int j = 0; j < targetNodes.Length; j++) {
- if (startNode == targetNodes[j]) {
- // The start node is equal to the target node
- // so we can immediately mark the path as calculated
- FoundTarget(startRNode, j);
- } else if (targetNodes[j] != null) {
- // Mark the node with a flag so that we can quickly check if we have found a target node
- pathHandler.GetPathNode(targetNodes[j]).flag1 = true;
- }
- }
- // If all paths have either been invalidated or found already because they were at the same node as the start node
- if (targetNodeCount <= 0) {
- CompleteState = PathCompleteState.Complete;
- return;
- }
- //if (recalcStartEndCosts) {
- // startNode.InitialOpen (open,hTarget,startIntPoint,this,true);
- //} else {
- startNode.Open(this, startRNode, pathHandler);
- //}
- searchedNodes++;
- //any nodes left to search?
- if (pathHandler.heap.isEmpty) {
- FailWithError("No open points, the start node didn't open any nodes");
- return;
- }
- // Take the first node off the heap
- currentR = pathHandler.heap.Remove();
- }
- protected override void Cleanup () {
- // Make sure that the shortest path is set
- // after the path has been calculated
- ChooseShortestPath();
- ResetFlags();
- }
- /// <summary>Reset flag1 on all nodes after the pathfinding has completed (no matter if an error occurs or if the path is canceled)</summary>
- void ResetFlags () {
- // Reset all flags
- if (targetNodes != null) {
- for (int i = 0; i < targetNodes.Length; i++) {
- if (targetNodes[i] != null) pathHandler.GetPathNode(targetNodes[i]).flag1 = false;
- }
- }
- }
- protected override void CalculateStep (long targetTick) {
- int counter = 0;
- // Continue to search as long as we haven't encountered an error and we haven't found the target
- while (CompleteState == PathCompleteState.NotCalculated) {
- // @Performance Just for debug info
- searchedNodes++;
- // The node might be the target node for one of the paths
- if (currentR.flag1) {
- // Close the current node, if the current node is the target node then the path is finnished
- for (int i = 0; i < targetNodes.Length; i++) {
- if (!targetsFound[i] && currentR.node == targetNodes[i]) {
- FoundTarget(currentR, i);
- if (CompleteState != PathCompleteState.NotCalculated) {
- break;
- }
- }
- }
- if (targetNodeCount <= 0) {
- CompleteState = PathCompleteState.Complete;
- break;
- }
- }
- // Loop through all walkable neighbours of the node and add them to the open list.
- currentR.node.Open(this, currentR, pathHandler);
- // Any nodes left to search?
- if (pathHandler.heap.isEmpty) {
- CompleteState = PathCompleteState.Complete;
- break;
- }
- // Select the node with the lowest F score and remove it from the open list
- AstarProfiler.StartFastProfile(7);
- currentR = pathHandler.heap.Remove();
- AstarProfiler.EndFastProfile(7);
- // Check for time every 500 nodes, roughly every 0.5 ms usually
- if (counter > 500) {
- // Have we exceded the maxFrameTime, if so we should wait one frame before continuing the search since we don't want the game to lag
- if (System.DateTime.UtcNow.Ticks >= targetTick) {
- // Return instead of yield'ing, a separate function handles the yield (CalculatePaths)
- return;
- }
- counter = 0;
- }
- counter++;
- }
- }
- protected override void Trace (PathNode node) {
- base.Trace(node);
- if (inverted) {
- // Reverse the paths
- int half = path.Count/2;
- for (int i = 0; i < half; i++) {
- GraphNode tmp = path[i];
- path[i] = path[path.Count-i-1];
- path[path.Count-i-1] = tmp;
- }
- for (int i = 0; i < half; i++) {
- Vector3 tmp = vectorPath[i];
- vectorPath[i] = vectorPath[vectorPath.Count-i-1];
- vectorPath[vectorPath.Count-i-1] = tmp;
- }
- }
- }
- 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);
- GraphNode node = nodePaths[i][nodePaths[i].Count-1];
- if (node != null) {
- PathNode nodeR = pathHandler.GetPathNode(endNode);
- if (nodeR != null) {
- text.Append("\n End Node");
- text.Append("\n G: ");
- text.Append(nodeR.G);
- text.Append("\n H: ");
- text.Append(nodeR.H);
- text.Append("\n F: ");
- text.Append(nodeR.F);
- text.Append("\n Point: ");
- text.Append(((Vector3)endPoint).ToString());
- text.Append("\n Graph: ");
- text.Append(endNode.GraphIndex);
- } else {
- text.Append("\n End Node: Null");
- }
- }
- }
- }
- 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 == null ? "Null" : (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();
- }
- }
- }
|