ABPath.cs 26 KB

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