ABPath.cs 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760
  1. using UnityEngine;
  2. using System.Collections.Generic;
  3. namespace Pathfinding {
  4. /// <summary>
  5. /// Basic path, finds the shortest path from A to B.
  6. ///
  7. /// This is the most basic path object it will try to find the shortest path between two points.
  8. /// Many other path types inherit from this type.
  9. /// See: Seeker.StartPath
  10. /// See: calling-pathfinding (view in online documentation for working links)
  11. /// See: getstarted (view in online documentation for working links)
  12. /// </summary>
  13. public class ABPath : Path {
  14. /// <summary>Start node of the path</summary>
  15. public GraphNode startNode;
  16. /// <summary>End node of the path</summary>
  17. public GraphNode endNode;
  18. /// <summary>Start Point exactly as in the path request</summary>
  19. public Vector3 originalStartPoint;
  20. /// <summary>End Point exactly as in the path request</summary>
  21. public Vector3 originalEndPoint;
  22. /// <summary>
  23. /// Start point of the path.
  24. /// This is the closest point on the <see cref="startNode"/> to <see cref="originalStartPoint"/>
  25. /// </summary>
  26. public Vector3 startPoint;
  27. /// <summary>
  28. /// End point of the path.
  29. /// This is the closest point on the <see cref="endNode"/> to <see cref="originalEndPoint"/>
  30. /// </summary>
  31. public Vector3 endPoint;
  32. /// <summary>
  33. /// Determines if a search for an end node should be done.
  34. /// Set by different path types.
  35. /// Since: Added in 3.0.8.3
  36. /// </summary>
  37. protected virtual bool hasEndPoint {
  38. get {
  39. return true;
  40. }
  41. }
  42. public Int3 startIntPoint; /// <summary>< Start point in integer coordinates</summary>
  43. /// <summary>
  44. /// Calculate partial path if the target node cannot be reached.
  45. /// If the target node cannot be reached, the node which was closest (given by heuristic) will be chosen as target node
  46. /// and a partial path will be returned.
  47. /// This only works if a heuristic is used (which is the default).
  48. /// If a partial path is found, CompleteState is set to Partial.
  49. /// Note: It is not required by other path types to respect this setting
  50. ///
  51. /// The <see cref="endNode"/> and <see cref="endPoint"/> will be modified and be set to the node which ends up being closest to the target.
  52. ///
  53. /// Warning: Using this may make path calculations significantly slower if you have a big graph. The reason is that
  54. /// when the target node cannot be reached, the path must search through every single other node that it can reach in order
  55. /// to determine which one is closest. This may be expensive, and is why this option is disabled by default.
  56. /// </summary>
  57. public bool calculatePartial;
  58. /// <summary>
  59. /// Current best target for the partial path.
  60. /// This is the node with the lowest H score.
  61. /// </summary>
  62. protected PathNode partialBestTarget;
  63. /// <summary>Saved original costs for the end node. See: ResetCosts</summary>
  64. protected int[] endNodeCosts;
  65. #if !ASTAR_NO_GRID_GRAPH
  66. /// <summary>Used in EndPointGridGraphSpecialCase</summary>
  67. GridNode gridSpecialCaseNode;
  68. #endif
  69. /// <summary>@{ @name Constructors</summary>
  70. /// <summary>
  71. /// Default constructor.
  72. /// Do not use this. Instead use the static Construct method which can handle path pooling.
  73. /// </summary>
  74. public ABPath () {}
  75. /// <summary>
  76. /// Construct a path with a start and end point.
  77. /// The delegate will be called when the path has been calculated.
  78. /// Do not confuse it with the Seeker callback as they are sent at different times.
  79. /// If you are using a Seeker to start the path you can set callback to null.
  80. ///
  81. /// Returns: The constructed path object
  82. /// </summary>
  83. public static ABPath Construct (Vector3 start, Vector3 end, OnPathDelegate callback = null) {
  84. var p = PathPool.GetPath<ABPath>();
  85. p.Setup(start, end, callback);
  86. return p;
  87. }
  88. protected void Setup (Vector3 start, Vector3 end, OnPathDelegate callbackDelegate) {
  89. callback = callbackDelegate;
  90. UpdateStartEnd(start, end);
  91. }
  92. /// <summary>
  93. /// Creates a fake path.
  94. /// Creates a path that looks almost exactly like it would if the pathfinding system had calculated it.
  95. ///
  96. /// This is useful if you want your agents to follow some known path that cannot be calculated using the pathfinding system for some reason.
  97. ///
  98. /// <code>
  99. /// var path = ABPath.FakePath(new List<Vector3> { new Vector3(1, 2, 3), new Vector3(4, 5, 6) });
  100. ///
  101. /// ai.SetPath(path);
  102. /// </code>
  103. ///
  104. /// You can use it to combine existing paths like this:
  105. ///
  106. /// <code>
  107. /// var a = Vector3.zero;
  108. /// var b = new Vector3(1, 2, 3);
  109. /// var c = new Vector3(2, 3, 4);
  110. /// var path1 = ABPath.Construct(a, b);
  111. /// var path2 = ABPath.Construct(b, c);
  112. ///
  113. /// AstarPath.StartPath(path1);
  114. /// AstarPath.StartPath(path2);
  115. /// path1.BlockUntilCalculated();
  116. /// path2.BlockUntilCalculated();
  117. ///
  118. /// // Combine the paths
  119. /// // Note: Skip the first element in the second path as that will likely be the last element in the first path
  120. /// var newVectorPath = path1.vectorPath.Concat(path2.vectorPath.Skip(1)).ToList();
  121. /// var newNodePath = path1.path.Concat(path2.path.Skip(1)).ToList();
  122. /// var combinedPath = ABPath.FakePath(newVectorPath, newNodePath);
  123. /// </code>
  124. /// </summary>
  125. public static ABPath FakePath (List<Vector3> vectorPath, List<GraphNode> nodePath = null) {
  126. var path = PathPool.GetPath<ABPath>();
  127. for (int i = 0; i < vectorPath.Count; i++) path.vectorPath.Add(vectorPath[i]);
  128. path.completeState = PathCompleteState.Complete;
  129. ((IPathInternals)path).AdvanceState(PathState.Returned);
  130. if (vectorPath.Count > 0) {
  131. path.UpdateStartEnd(vectorPath[0], vectorPath[vectorPath.Count - 1]);
  132. }
  133. if (nodePath != null) {
  134. for (int i = 0; i < nodePath.Count; i++) path.path.Add(nodePath[i]);
  135. if (nodePath.Count > 0) {
  136. path.startNode = nodePath[0];
  137. path.endNode = nodePath[nodePath.Count - 1];
  138. }
  139. }
  140. return path;
  141. }
  142. /// <summary>@}</summary>
  143. /// <summary>
  144. /// Sets the start and end points.
  145. /// Sets <see cref="originalStartPoint"/>, <see cref="originalEndPoint"/>, <see cref="startPoint"/>, <see cref="endPoint"/>, <see cref="startIntPoint"/> and <see cref="hTarget"/> (to end )
  146. /// </summary>
  147. protected void UpdateStartEnd (Vector3 start, Vector3 end) {
  148. originalStartPoint = start;
  149. originalEndPoint = end;
  150. startPoint = start;
  151. endPoint = end;
  152. startIntPoint = (Int3)start;
  153. hTarget = (Int3)end;
  154. }
  155. public override uint GetConnectionSpecialCost (GraphNode a, GraphNode b, uint currentCost) {
  156. if (startNode != null && endNode != null) {
  157. if (a == startNode) {
  158. return (uint)((startIntPoint - (b == endNode ? hTarget : b.position)).costMagnitude * (currentCost*1.0/(a.position-b.position).costMagnitude));
  159. }
  160. if (b == startNode) {
  161. return (uint)((startIntPoint - (a == endNode ? hTarget : a.position)).costMagnitude * (currentCost*1.0/(a.position-b.position).costMagnitude));
  162. }
  163. if (a == endNode) {
  164. return (uint)((hTarget - b.position).costMagnitude * (currentCost*1.0/(a.position-b.position).costMagnitude));
  165. }
  166. if (b == endNode) {
  167. return (uint)((hTarget - a.position).costMagnitude * (currentCost*1.0/(a.position-b.position).costMagnitude));
  168. }
  169. } else {
  170. // endNode is null, startNode should never be null for an ABPath
  171. if (a == startNode) {
  172. return (uint)((startIntPoint - b.position).costMagnitude * (currentCost*1.0/(a.position-b.position).costMagnitude));
  173. }
  174. if (b == startNode) {
  175. return (uint)((startIntPoint - a.position).costMagnitude * (currentCost*1.0/(a.position-b.position).costMagnitude));
  176. }
  177. }
  178. return currentCost;
  179. }
  180. /// <summary>
  181. /// Reset all values to their default values.
  182. /// All inheriting path types must implement this function, resetting ALL their variables to enable recycling of paths.
  183. /// Call this base function in inheriting types with base.Reset ();
  184. /// </summary>
  185. protected override void Reset () {
  186. base.Reset();
  187. startNode = null;
  188. endNode = null;
  189. originalStartPoint = Vector3.zero;
  190. originalEndPoint = Vector3.zero;
  191. startPoint = Vector3.zero;
  192. endPoint = Vector3.zero;
  193. calculatePartial = false;
  194. partialBestTarget = null;
  195. startIntPoint = new Int3();
  196. hTarget = new Int3();
  197. endNodeCosts = null;
  198. #if !ASTAR_NO_GRID_GRAPH
  199. gridSpecialCaseNode = null;
  200. #endif
  201. }
  202. #if !ASTAR_NO_GRID_GRAPH
  203. /// <summary>Cached <see cref="Pathfinding.NNConstraint.None"/> to reduce allocations</summary>
  204. static readonly NNConstraint NNConstraintNone = NNConstraint.None;
  205. /// <summary>
  206. /// Applies a special case for grid nodes.
  207. ///
  208. /// Assume the closest walkable node is a grid node.
  209. /// We will now apply a special case only for grid graphs.
  210. /// In tile based games, an obstacle often occupies a whole
  211. /// node. When a path is requested to the position of an obstacle
  212. /// (single unwalkable node) the closest walkable node will be
  213. /// one of the 8 nodes surrounding that unwalkable node
  214. /// but that node is not neccessarily the one that is most
  215. /// optimal to walk to so in this special case
  216. /// we mark all nodes around the unwalkable node as targets
  217. /// and when we search and find any one of them we simply exit
  218. /// and set that first node we found to be the 'real' end node
  219. /// because that will be the optimal node (this does not apply
  220. /// in general unless the heuristic is set to None, but
  221. /// for a single unwalkable node it does).
  222. /// This also applies if the nearest node cannot be traversed for
  223. /// some other reason like restricted tags.
  224. ///
  225. /// Returns: True if the workaround was applied. If this happens the
  226. /// endPoint, endNode, hTarget and hTargetNode fields will be modified.
  227. ///
  228. /// Image below shows paths when this special case is applied. The path goes from the white sphere to the orange box.
  229. /// [Open online documentation to see images]
  230. ///
  231. /// Image below shows paths when this special case has been disabled
  232. /// [Open online documentation to see images]
  233. /// </summary>
  234. protected virtual bool EndPointGridGraphSpecialCase (GraphNode closestWalkableEndNode) {
  235. var gridNode = closestWalkableEndNode as GridNode;
  236. if (gridNode != null) {
  237. var gridGraph = GridNode.GetGridGraph(gridNode.GraphIndex);
  238. // Find the closest node, not neccessarily walkable
  239. var endNNInfo2 = AstarPath.active.GetNearest(originalEndPoint, NNConstraintNone);
  240. var gridNode2 = endNNInfo2.node as GridNode;
  241. if (gridNode != gridNode2 && gridNode2 != null && gridNode.GraphIndex == gridNode2.GraphIndex) {
  242. // Calculate the coordinates of the nodes
  243. var x1 = gridNode.NodeInGridIndex % gridGraph.width;
  244. var z1 = gridNode.NodeInGridIndex / gridGraph.width;
  245. var x2 = gridNode2.NodeInGridIndex % gridGraph.width;
  246. var z2 = gridNode2.NodeInGridIndex / gridGraph.width;
  247. bool wasClose = false;
  248. switch (gridGraph.neighbours) {
  249. case NumNeighbours.Four:
  250. if ((x1 == x2 && System.Math.Abs(z1-z2) == 1) || (z1 == z2 && System.Math.Abs(x1-x2) == 1)) {
  251. // If 'O' is gridNode2, then gridNode is one of the nodes marked with an 'x'
  252. // x
  253. // x O x
  254. // x
  255. wasClose = true;
  256. }
  257. break;
  258. case NumNeighbours.Eight:
  259. if (System.Math.Abs(x1-x2) <= 1 && System.Math.Abs(z1-z2) <= 1) {
  260. // If 'O' is gridNode2, then gridNode is one of the nodes marked with an 'x'
  261. // x x x
  262. // x O x
  263. // x x x
  264. wasClose = true;
  265. }
  266. break;
  267. case NumNeighbours.Six:
  268. // Hexagon graph
  269. for (int i = 0; i < 6; i++) {
  270. var nx = x2 + gridGraph.neighbourXOffsets[GridGraph.hexagonNeighbourIndices[i]];
  271. var nz = z2 + gridGraph.neighbourZOffsets[GridGraph.hexagonNeighbourIndices[i]];
  272. if (x1 == nx && z1 == nz) {
  273. // If 'O' is gridNode2, then gridNode is one of the nodes marked with an 'x'
  274. // x x
  275. // x O x
  276. // x x
  277. wasClose = true;
  278. break;
  279. }
  280. }
  281. break;
  282. default:
  283. // Should not happen unless NumNeighbours is modified in the future
  284. throw new System.Exception("Unhandled NumNeighbours");
  285. }
  286. if (wasClose) {
  287. // We now need to find all nodes marked with an x to be able to mark them as targets
  288. SetFlagOnSurroundingGridNodes(gridNode2, 1, true);
  289. // Note, other methods assume hTarget is (Int3)endPoint
  290. endPoint = (Vector3)gridNode2.position;
  291. hTarget = gridNode2.position;
  292. endNode = gridNode2;
  293. // hTargetNode is used for heuristic optimizations
  294. // (also known as euclidean embedding).
  295. // Even though the endNode is not walkable
  296. // we can use it for better heuristics since
  297. // there is a workaround added (EuclideanEmbedding.ApplyGridGraphEndpointSpecialCase)
  298. // which is there to support this case.
  299. hTargetNode = endNode;
  300. // We need to save this node
  301. // so that we can reset flag1 on all nodes later
  302. gridSpecialCaseNode = gridNode2;
  303. return true;
  304. }
  305. }
  306. }
  307. return false;
  308. }
  309. /// <summary>Helper method to set PathNode.flag1 to a specific value for all nodes adjacent to a grid node</summary>
  310. void SetFlagOnSurroundingGridNodes (GridNode gridNode, int flag, bool flagState) {
  311. // Loop through all adjacent grid nodes
  312. var gridGraph = GridNode.GetGridGraph(gridNode.GraphIndex);
  313. // Number of neighbours as an int
  314. int mxnum = gridGraph.neighbours == NumNeighbours.Four ? 4 : (gridGraph.neighbours == NumNeighbours.Eight ? 8 : 6);
  315. // Calculate the coordinates of the node
  316. var x = gridNode.NodeInGridIndex % gridGraph.width;
  317. var z = gridNode.NodeInGridIndex / gridGraph.width;
  318. if (flag != 1 && flag != 2)
  319. throw new System.ArgumentOutOfRangeException("flag");
  320. for (int i = 0; i < mxnum; i++) {
  321. int nx, nz;
  322. if (gridGraph.neighbours == NumNeighbours.Six) {
  323. // Hexagon graph
  324. nx = x + gridGraph.neighbourXOffsets[GridGraph.hexagonNeighbourIndices[i]];
  325. nz = z + gridGraph.neighbourZOffsets[GridGraph.hexagonNeighbourIndices[i]];
  326. } else {
  327. nx = x + gridGraph.neighbourXOffsets[i];
  328. nz = z + gridGraph.neighbourZOffsets[i];
  329. }
  330. // Check if the position is still inside the grid
  331. if (nx >= 0 && nz >= 0 && nx < gridGraph.width && nz < gridGraph.depth) {
  332. var adjacentNode = gridGraph.nodes[nz*gridGraph.width + nx];
  333. var pathNode = pathHandler.GetPathNode(adjacentNode);
  334. if (flag == 1) pathNode.flag1 = flagState;
  335. else pathNode.flag2 = flagState;
  336. }
  337. }
  338. }
  339. #endif
  340. /// <summary>Prepares the path. Searches for start and end nodes and does some simple checking if a path is at all possible</summary>
  341. protected override void Prepare () {
  342. AstarProfiler.StartProfile("Get Nearest");
  343. //Initialize the NNConstraint
  344. nnConstraint.tags = enabledTags;
  345. var startNNInfo = AstarPath.active.GetNearest(startPoint, nnConstraint);
  346. //Tell the NNConstraint which node was found as the start node if it is a PathNNConstraint and not a normal NNConstraint
  347. var pathNNConstraint = nnConstraint as PathNNConstraint;
  348. if (pathNNConstraint != null) {
  349. pathNNConstraint.SetStart(startNNInfo.node);
  350. }
  351. startPoint = startNNInfo.position;
  352. startIntPoint = (Int3)startPoint;
  353. startNode = startNNInfo.node;
  354. if (startNode == null) {
  355. FailWithError("Couldn't find a node close to the start point");
  356. return;
  357. }
  358. if (!CanTraverse(startNode)) {
  359. FailWithError("The node closest to the start point could not be traversed");
  360. return;
  361. }
  362. // If it is declared that this path type has an end point
  363. // Some path types might want to use most of the ABPath code, but will not have an explicit end point at this stage
  364. if (hasEndPoint) {
  365. var endNNInfo = AstarPath.active.GetNearest(endPoint, nnConstraint);
  366. endPoint = endNNInfo.position;
  367. endNode = endNNInfo.node;
  368. if (endNode == null) {
  369. FailWithError("Couldn't find a node close to the end point");
  370. return;
  371. }
  372. // This should not trigger unless the user has modified the NNConstraint
  373. if (!CanTraverse(endNode)) {
  374. FailWithError("The node closest to the end point could not be traversed");
  375. return;
  376. }
  377. // This should not trigger unless the user has modified the NNConstraint
  378. if (startNode.Area != endNode.Area) {
  379. FailWithError("There is no valid path to the target");
  380. return;
  381. }
  382. #if !ASTAR_NO_GRID_GRAPH
  383. // Potentially we want to special case grid graphs a bit
  384. // to better support some kinds of games
  385. // If this returns true it will overwrite the
  386. // endNode, endPoint, hTarget and hTargetNode fields
  387. if (!EndPointGridGraphSpecialCase(endNNInfo.node))
  388. #endif
  389. {
  390. // Note, other methods assume hTarget is (Int3)endPoint
  391. hTarget = (Int3)endPoint;
  392. hTargetNode = endNode;
  393. // Mark end node with flag1 to mark it as a target point
  394. pathHandler.GetPathNode(endNode).flag1 = true;
  395. }
  396. }
  397. AstarProfiler.EndProfile();
  398. }
  399. /// <summary>
  400. /// Checks if the start node is the target and complete the path if that is the case.
  401. /// This is necessary so that subclasses (e.g XPath) can override this behaviour.
  402. ///
  403. /// If the start node is a valid target point, this method should set CompleteState to Complete
  404. /// and trace the path.
  405. /// </summary>
  406. protected virtual void CompletePathIfStartIsValidTarget () {
  407. // flag1 specifies if a node is a target node for the path
  408. if (hasEndPoint && pathHandler.GetPathNode(startNode).flag1) {
  409. CompleteWith(startNode);
  410. Trace(pathHandler.GetPathNode(startNode));
  411. }
  412. }
  413. protected override void Initialize () {
  414. // Mark nodes to enable special connection costs for start and end nodes
  415. // See GetConnectionSpecialCost
  416. if (startNode != null) pathHandler.GetPathNode(startNode).flag2 = true;
  417. if (endNode != null) pathHandler.GetPathNode(endNode).flag2 = true;
  418. // Zero out the properties on the start node
  419. PathNode startRNode = pathHandler.GetPathNode(startNode);
  420. startRNode.node = startNode;
  421. startRNode.pathID = pathHandler.PathID;
  422. startRNode.parent = null;
  423. startRNode.cost = 0;
  424. startRNode.G = GetTraversalCost(startNode);
  425. startRNode.H = CalculateHScore(startNode);
  426. // Check if the start node is the target and complete the path if that is the case
  427. CompletePathIfStartIsValidTarget();
  428. if (CompleteState == PathCompleteState.Complete) return;
  429. // Open the start node and puts its neighbours in the open list
  430. startNode.Open(this, startRNode, pathHandler);
  431. searchedNodes++;
  432. partialBestTarget = startRNode;
  433. // Any nodes left to search?
  434. if (pathHandler.heap.isEmpty) {
  435. if (calculatePartial) {
  436. CompletePartial(partialBestTarget);
  437. } else {
  438. FailWithError("The start node either had no neighbours, or no neighbours that the path could traverse");
  439. }
  440. return;
  441. }
  442. // Pop the first node off the open list
  443. currentR = pathHandler.heap.Remove();
  444. }
  445. protected override void Cleanup () {
  446. // TODO: Set flag1 = false as well?
  447. if (startNode != null) {
  448. var pathStartNode = pathHandler.GetPathNode(startNode);
  449. pathStartNode.flag1 = false;
  450. pathStartNode.flag2 = false;
  451. }
  452. if (endNode != null) {
  453. var pathEndNode = pathHandler.GetPathNode(endNode);
  454. pathEndNode.flag1 = false;
  455. pathEndNode.flag2 = false;
  456. }
  457. #if !ASTAR_NO_GRID_GRAPH
  458. // Set flag1 and flag2 to false on all nodes we set it to true on
  459. // at the start of the path call. Otherwise this state
  460. // will leak to other path calculations and cause all
  461. // kinds of havoc.
  462. // flag2 is also set because the end node could have changed
  463. // and thus the flag2 which is set to false above might not set
  464. // it on the correct node
  465. if (gridSpecialCaseNode != null) {
  466. var pathNode = pathHandler.GetPathNode(gridSpecialCaseNode);
  467. pathNode.flag1 = false;
  468. pathNode.flag2 = false;
  469. SetFlagOnSurroundingGridNodes(gridSpecialCaseNode, 1, false);
  470. SetFlagOnSurroundingGridNodes(gridSpecialCaseNode, 2, false);
  471. }
  472. #endif
  473. }
  474. void CompletePartial (PathNode node) {
  475. CompleteState = PathCompleteState.Partial;
  476. endNode = node.node;
  477. endPoint = endNode.ClosestPointOnNode(originalEndPoint);
  478. Trace(node);
  479. }
  480. /// <summary>
  481. /// Completes the path using the specified target node.
  482. /// This method assumes that the node is a target node of the path
  483. /// not just any random node.
  484. /// </summary>
  485. void CompleteWith (GraphNode node) {
  486. #if !ASTAR_NO_GRID_GRAPH
  487. if (endNode == node) {
  488. // Common case, no grid graph special case has been applied
  489. // Nothing to do
  490. } else {
  491. // See EndPointGridGraphSpecialCase()
  492. var gridNode = node as GridNode;
  493. if (gridNode == null) {
  494. throw new System.Exception("Some path is not cleaning up the flag1 field. This is a bug.");
  495. }
  496. // The grid graph special case has been applied
  497. // The closest point on the node is not yet known
  498. // so we need to calculate it
  499. endPoint = gridNode.ClosestPointOnNode(originalEndPoint);
  500. // This is now our end node
  501. // We didn't know it before, but apparently it was optimal
  502. // to move to this node
  503. endNode = node;
  504. }
  505. #else
  506. // This should always be true unless
  507. // the grid graph special case has been applied
  508. // which can only happen if grid graphs have not
  509. // been stripped out with ASTAR_NO_GRID_GRAPH
  510. node.MustBeEqual(endNode);
  511. #endif
  512. // Mark the path as completed
  513. CompleteState = PathCompleteState.Complete;
  514. }
  515. /// <summary>
  516. /// Calculates the path until completed or until the time has passed targetTick.
  517. /// Usually a check is only done every 500 nodes if the time has passed targetTick.
  518. /// Time/Ticks are got from System.DateTime.UtcNow.Ticks.
  519. ///
  520. /// Basic outline of what the function does for the standard path (Pathfinding.ABPath).
  521. /// <code>
  522. /// while the end has not been found and no error has occurred
  523. /// check if we have reached the end
  524. /// if so, exit and return the path
  525. ///
  526. /// open the current node, i.e loop through its neighbours, mark them as visited and put them on a heap
  527. ///
  528. /// check if there are still nodes left to process (or have we searched the whole graph)
  529. /// if there are none, flag error and exit
  530. ///
  531. /// pop the next node of the heap and set it as current
  532. ///
  533. /// check if the function has exceeded the time limit
  534. /// if so, return and wait for the function to get called again
  535. /// </code>
  536. /// </summary>
  537. protected override void CalculateStep (long targetTick) {
  538. int counter = 0;
  539. // Continue to search as long as we haven't encountered an error and we haven't found the target
  540. while (CompleteState == PathCompleteState.NotCalculated) {
  541. searchedNodes++;
  542. // Close the current node, if the current node is the target node then the path is finished
  543. if (currentR.flag1) {
  544. // We found a target point
  545. // Mark that node as the end point
  546. CompleteWith(currentR.node);
  547. break;
  548. }
  549. if (currentR.H < partialBestTarget.H) {
  550. partialBestTarget = currentR;
  551. }
  552. AstarProfiler.StartFastProfile(4);
  553. // Loop through all walkable neighbours of the node and add them to the open list.
  554. currentR.node.Open(this, currentR, pathHandler);
  555. AstarProfiler.EndFastProfile(4);
  556. // Any nodes left to search?
  557. if (pathHandler.heap.isEmpty) {
  558. if (calculatePartial && partialBestTarget != null) {
  559. CompletePartial(partialBestTarget);
  560. } else {
  561. FailWithError("Searched all reachable nodes, but could not find target. This can happen if you have nodes with a different tag blocking the way to the goal. You can enable path.calculatePartial to handle that case workaround (though this comes with a performance cost).");
  562. }
  563. return;
  564. }
  565. // Select the node with the lowest F score and remove it from the open list
  566. AstarProfiler.StartFastProfile(7);
  567. currentR = pathHandler.heap.Remove();
  568. AstarProfiler.EndFastProfile(7);
  569. // Check for time every 500 nodes, roughly every 0.5 ms usually
  570. if (counter > 500) {
  571. // 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
  572. if (System.DateTime.UtcNow.Ticks >= targetTick) {
  573. // Return instead of yield'ing, a separate function handles the yield (CalculatePaths)
  574. return;
  575. }
  576. counter = 0;
  577. // Mostly for development
  578. if (searchedNodes > 1000000) {
  579. throw new System.Exception("Probable infinite loop. Over 1,000,000 nodes searched");
  580. }
  581. }
  582. counter++;
  583. }
  584. AstarProfiler.StartProfile("Trace");
  585. if (CompleteState == PathCompleteState.Complete) {
  586. Trace(currentR);
  587. } else if (calculatePartial && partialBestTarget != null) {
  588. CompletePartial(partialBestTarget);
  589. }
  590. AstarProfiler.EndProfile();
  591. }
  592. /// <summary>Returns a debug string for this path.</summary>
  593. protected override string DebugString (PathLog logMode) {
  594. if (logMode == PathLog.None || (!error && logMode == PathLog.OnlyErrors)) {
  595. return "";
  596. }
  597. var text = new System.Text.StringBuilder();
  598. DebugStringPrefix(logMode, text);
  599. if (!error && logMode == PathLog.Heavy) {
  600. if (hasEndPoint && endNode != null) {
  601. PathNode nodeR = pathHandler.GetPathNode(endNode);
  602. text.Append("\nEnd Node\n G: ");
  603. text.Append(nodeR.G);
  604. text.Append("\n H: ");
  605. text.Append(nodeR.H);
  606. text.Append("\n F: ");
  607. text.Append(nodeR.F);
  608. text.Append("\n Point: ");
  609. text.Append(((Vector3)endPoint).ToString());
  610. text.Append("\n Graph: ");
  611. text.Append(endNode.GraphIndex);
  612. }
  613. text.Append("\nStart Node");
  614. text.Append("\n Point: ");
  615. text.Append(((Vector3)startPoint).ToString());
  616. text.Append("\n Graph: ");
  617. if (startNode != null) text.Append(startNode.GraphIndex);
  618. else text.Append("< null startNode >");
  619. }
  620. DebugStringSuffix(logMode, text);
  621. return text.ToString();
  622. }
  623. /// <summary>\cond INTERNAL</summary>
  624. /// <summary>
  625. /// Returns in which direction to move from a point on the path.
  626. /// A simple and quite slow (well, compared to more optimized algorithms) algorithm first finds the closest path segment (from <see cref="vectorPath)"/> and then returns
  627. /// the direction to the next point from there. The direction is not normalized.
  628. /// Returns: Direction to move from a point, returns Vector3.zero if <see cref="vectorPath"/> is null or has a length of 0
  629. /// Deprecated:
  630. /// </summary>
  631. [System.Obsolete()]
  632. public Vector3 GetMovementVector (Vector3 point) {
  633. if (vectorPath == null || vectorPath.Count == 0) {
  634. return Vector3.zero;
  635. }
  636. if (vectorPath.Count == 1) {
  637. return vectorPath[0]-point;
  638. }
  639. float minDist = float.PositiveInfinity;//Mathf.Infinity;
  640. int minSegment = 0;
  641. for (int i = 0; i < vectorPath.Count-1; i++) {
  642. Vector3 closest = VectorMath.ClosestPointOnSegment(vectorPath[i], vectorPath[i+1], point);
  643. float dist = (closest-point).sqrMagnitude;
  644. if (dist < minDist) {
  645. minDist = dist;
  646. minSegment = i;
  647. }
  648. }
  649. return vectorPath[minSegment+1]-point;
  650. }
  651. /// <summary>\endcond</summary>
  652. }
  653. }