RichPath.cs 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896
  1. using UnityEngine;
  2. using System.Collections.Generic;
  3. using Pathfinding.Util;
  4. namespace Pathfinding {
  5. public class RichPath {
  6. int currentPart;
  7. readonly List<RichPathPart> parts = new List<RichPathPart>();
  8. public Seeker seeker;
  9. /// <summary>
  10. /// Transforms points from path space to world space.
  11. /// If null the identity transform will be used.
  12. ///
  13. /// This is used when the world position of the agent does not match the
  14. /// corresponding position on the graph. This is the case in the example
  15. /// scene called 'Moving'.
  16. ///
  17. /// See: <see cref="Pathfinding.Examples.LocalSpaceRichAI"/>
  18. /// </summary>
  19. public ITransform transform;
  20. public RichPath () {
  21. Clear();
  22. }
  23. public void Clear () {
  24. parts.Clear();
  25. currentPart = 0;
  26. Endpoint = new Vector3(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity);
  27. }
  28. /// <summary>Use this for initialization.</summary>
  29. /// <param name="seeker">Optionally provide in order to take tag penalties into account. May be null if you do not use a Seeker\</param>
  30. /// <param name="path">Path to follow</param>
  31. /// <param name="mergePartEndpoints">If true, then adjacent parts that the path is split up in will
  32. /// try to use the same start/end points. For example when using a link on a navmesh graph
  33. /// Instead of first following the path to the center of the node where the link is and then
  34. /// follow the link, the path will be adjusted to go to the exact point where the link starts
  35. /// which usually makes more sense.</param>
  36. /// <param name="simplificationMode">The path can optionally be simplified. This can be a bit expensive for long paths.</param>
  37. public void Initialize (Seeker seeker, Path path, bool mergePartEndpoints, bool simplificationMode) {
  38. if (path.error) throw new System.ArgumentException("Path has an error");
  39. List<GraphNode> nodes = path.path;
  40. if (nodes.Count == 0) throw new System.ArgumentException("Path traverses no nodes");
  41. this.seeker = seeker;
  42. // Release objects back to object pool
  43. // Yeah, I know, it's casting... but this won't be called much
  44. for (int i = 0; i < parts.Count; i++) {
  45. var funnelPart = parts[i] as RichFunnel;
  46. var specialPart = parts[i] as RichSpecial;
  47. if (funnelPart != null) ObjectPool<RichFunnel>.Release(ref funnelPart);
  48. else if (specialPart != null) ObjectPool<RichSpecial>.Release(ref specialPart);
  49. }
  50. Clear();
  51. // Initialize new
  52. Endpoint = path.vectorPath[path.vectorPath.Count-1];
  53. //Break path into parts
  54. for (int i = 0; i < nodes.Count; i++) {
  55. if (nodes[i] is TriangleMeshNode) {
  56. var graph = AstarData.GetGraph(nodes[i]) as NavmeshBase;
  57. if (graph == null) throw new System.Exception("Found a TriangleMeshNode that was not in a NavmeshBase graph");
  58. RichFunnel f = ObjectPool<RichFunnel>.Claim().Initialize(this, graph);
  59. f.funnelSimplification = simplificationMode;
  60. int sIndex = i;
  61. uint currentGraphIndex = nodes[sIndex].GraphIndex;
  62. for (; i < nodes.Count; i++) {
  63. if (nodes[i].GraphIndex != currentGraphIndex && !(nodes[i] is NodeLink3Node)) {
  64. break;
  65. }
  66. }
  67. i--;
  68. if (sIndex == 0) {
  69. f.exactStart = path.vectorPath[0];
  70. } else {
  71. f.exactStart = (Vector3)nodes[mergePartEndpoints ? sIndex-1 : sIndex].position;
  72. }
  73. if (i == nodes.Count-1) {
  74. f.exactEnd = path.vectorPath[path.vectorPath.Count-1];
  75. } else {
  76. f.exactEnd = (Vector3)nodes[mergePartEndpoints ? i+1 : i].position;
  77. }
  78. f.BuildFunnelCorridor(nodes, sIndex, i);
  79. parts.Add(f);
  80. } else if (NodeLink2.GetNodeLink(nodes[i]) != null) {
  81. NodeLink2 nl = NodeLink2.GetNodeLink(nodes[i]);
  82. int sIndex = i;
  83. uint currentGraphIndex = nodes[sIndex].GraphIndex;
  84. for (i++; i < nodes.Count; i++) {
  85. if (nodes[i].GraphIndex != currentGraphIndex) {
  86. break;
  87. }
  88. }
  89. i--;
  90. if (i - sIndex > 1) {
  91. throw new System.Exception("NodeLink2 path length greater than two (2) nodes. " + (i - sIndex));
  92. } else if (i - sIndex == 0) {
  93. //Just continue, it might be the case that a NodeLink was the closest node
  94. continue;
  95. }
  96. RichSpecial rps = ObjectPool<RichSpecial>.Claim().Initialize(nl, nodes[sIndex]);
  97. parts.Add(rps);
  98. } else if (!(nodes[i] is PointNode)) {
  99. // Some other graph type which we do not have support for
  100. throw new System.InvalidOperationException("The RichAI movment script can only be used on recast/navmesh graphs. A node of type " + nodes[i].GetType().Name + " was in the path.");
  101. }
  102. }
  103. }
  104. public Vector3 Endpoint { get; private set; }
  105. /// <summary>True if we have completed (called NextPart for) the last part in the path</summary>
  106. public bool CompletedAllParts {
  107. get {
  108. return currentPart >= parts.Count;
  109. }
  110. }
  111. /// <summary>True if we are traversing the last part of the path</summary>
  112. public bool IsLastPart {
  113. get {
  114. return currentPart >= parts.Count - 1;
  115. }
  116. }
  117. public void NextPart () {
  118. currentPart = Mathf.Min(currentPart + 1, parts.Count);
  119. }
  120. public RichPathPart GetCurrentPart () {
  121. if (parts.Count == 0) return null;
  122. return currentPart < parts.Count ? parts[currentPart] : parts[parts.Count - 1];
  123. }
  124. /// <summary>
  125. /// Replaces the buffer with the remaining path.
  126. /// See: <see cref="Pathfinding.IAstarAI.GetRemainingPath"/>
  127. /// </summary>
  128. public void GetRemainingPath (List<Vector3> buffer, Vector3 currentPosition, out bool requiresRepath) {
  129. buffer.Clear();
  130. buffer.Add(currentPosition);
  131. requiresRepath = false;
  132. for (int i = currentPart; i < parts.Count; i++) {
  133. var part = parts[i];
  134. if (part is RichFunnel funnel) {
  135. bool lastCorner;
  136. if (i != 0) buffer.Add(funnel.exactStart);
  137. funnel.Update(i == 0 ? currentPosition : funnel.exactStart, buffer, int.MaxValue, out lastCorner, out requiresRepath);
  138. if (requiresRepath) {
  139. return;
  140. }
  141. } else if (part is RichSpecial link) {
  142. // By adding all points above the link will look like just a stright line, which is reasonable
  143. }
  144. }
  145. }
  146. }
  147. public abstract class RichPathPart : Pathfinding.Util.IAstarPooledObject {
  148. public abstract void OnEnterPool();
  149. }
  150. public class RichFunnel : RichPathPart {
  151. readonly List<Vector3> left;
  152. readonly List<Vector3> right;
  153. List<TriangleMeshNode> nodes;
  154. public Vector3 exactStart;
  155. public Vector3 exactEnd;
  156. NavmeshBase graph;
  157. int currentNode;
  158. Vector3 currentPosition;
  159. int checkForDestroyedNodesCounter;
  160. RichPath path;
  161. int[] triBuffer = new int[3];
  162. /// <summary>Post process the funnel corridor or not</summary>
  163. public bool funnelSimplification = true;
  164. public RichFunnel () {
  165. left = Pathfinding.Util.ListPool<Vector3>.Claim();
  166. right = Pathfinding.Util.ListPool<Vector3>.Claim();
  167. nodes = new List<TriangleMeshNode>();
  168. this.graph = null;
  169. }
  170. /// <summary>Works like a constructor, but can be used even for pooled objects. Returns this for easy chaining</summary>
  171. public RichFunnel Initialize (RichPath path, NavmeshBase graph) {
  172. if (graph == null) throw new System.ArgumentNullException("graph");
  173. if (this.graph != null) throw new System.InvalidOperationException("Trying to initialize an already initialized object. " + graph);
  174. this.graph = graph;
  175. this.path = path;
  176. return this;
  177. }
  178. public override void OnEnterPool () {
  179. left.Clear();
  180. right.Clear();
  181. nodes.Clear();
  182. graph = null;
  183. currentNode = 0;
  184. checkForDestroyedNodesCounter = 0;
  185. }
  186. public TriangleMeshNode CurrentNode {
  187. get {
  188. var node = nodes[currentNode];
  189. if (!node.Destroyed) {
  190. return node;
  191. }
  192. return null;
  193. }
  194. }
  195. /// <summary>
  196. /// Build a funnel corridor from a node list slice.
  197. /// The nodes are assumed to be of type TriangleMeshNode.
  198. /// </summary>
  199. /// <param name="nodes">Nodes to build the funnel corridor from</param>
  200. /// <param name="start">Start index in the nodes list</param>
  201. /// <param name="end">End index in the nodes list, this index is inclusive</param>
  202. public void BuildFunnelCorridor (List<GraphNode> nodes, int start, int end) {
  203. //Make sure start and end points are on the correct nodes
  204. exactStart = (nodes[start] as MeshNode).ClosestPointOnNode(exactStart);
  205. exactEnd = (nodes[end] as MeshNode).ClosestPointOnNode(exactEnd);
  206. left.Clear();
  207. right.Clear();
  208. left.Add(exactStart);
  209. right.Add(exactStart);
  210. this.nodes.Clear();
  211. if (funnelSimplification) {
  212. List<GraphNode> tmp = Pathfinding.Util.ListPool<GraphNode>.Claim(end-start);
  213. SimplifyPath(graph, nodes, start, end, tmp, exactStart, exactEnd);
  214. if (this.nodes.Capacity < tmp.Count) this.nodes.Capacity = tmp.Count;
  215. for (int i = 0; i < tmp.Count; i++) {
  216. //Guaranteed to be TriangleMeshNodes since they are all in the same graph
  217. var node = tmp[i] as TriangleMeshNode;
  218. if (node != null) this.nodes.Add(node);
  219. }
  220. Pathfinding.Util.ListPool<GraphNode>.Release(ref tmp);
  221. } else {
  222. if (this.nodes.Capacity < end-start) this.nodes.Capacity = (end-start);
  223. for (int i = start; i <= end; i++) {
  224. //Guaranteed to be TriangleMeshNodes since they are all in the same graph
  225. var node = nodes[i] as TriangleMeshNode;
  226. if (node != null) this.nodes.Add(node);
  227. }
  228. }
  229. for (int i = 0; i < this.nodes.Count-1; i++) {
  230. /// <summary>TODO: should use return value in future versions</summary>
  231. this.nodes[i].GetPortal(this.nodes[i+1], left, right, false);
  232. }
  233. left.Add(exactEnd);
  234. right.Add(exactEnd);
  235. }
  236. /// <summary>
  237. /// Simplifies a funnel path using linecasting.
  238. /// Running time is roughly O(n^2 log n) in the worst case (where n = end-start)
  239. /// Actually it depends on how the graph looks, so in theory the actual upper limit on the worst case running time is O(n*m log n) (where n = end-start and m = nodes in the graph)
  240. /// but O(n^2 log n) is a much more realistic worst case limit.
  241. ///
  242. /// Requires <see cref="graph"/> to implement IRaycastableGraph
  243. /// </summary>
  244. void SimplifyPath (IRaycastableGraph graph, List<GraphNode> nodes, int start, int end, List<GraphNode> result, Vector3 startPoint, Vector3 endPoint) {
  245. if (graph == null) throw new System.ArgumentNullException("graph");
  246. if (start > end) {
  247. throw new System.ArgumentException("start >= end");
  248. }
  249. // Do a straight line of sight check to see if the path can be simplified to a single line
  250. {
  251. GraphHitInfo hit;
  252. if (!graph.Linecast(startPoint, endPoint, out hit) && hit.node == nodes[end]) {
  253. graph.Linecast(startPoint, endPoint, out hit, result);
  254. long penaltySum = 0;
  255. long penaltySum2 = 0;
  256. for (int i = start; i <= end; i++) {
  257. penaltySum += nodes[i].Penalty + (path.seeker != null ? path.seeker.tagPenalties[nodes[i].Tag] : 0);
  258. }
  259. for (int i = 0; i < result.Count; i++) {
  260. penaltySum2 += result[i].Penalty + (path.seeker != null ? path.seeker.tagPenalties[result[i].Tag] : 0);
  261. }
  262. // Allow 40% more penalty on average per node
  263. if ((penaltySum*1.4*result.Count) < (penaltySum2*(end-start+1))) {
  264. // The straight line penalties are much higher than the original path.
  265. // Revert the simplification
  266. result.Clear();
  267. } else {
  268. // The straight line simplification looks good.
  269. // We are done here.
  270. return;
  271. }
  272. }
  273. }
  274. int ostart = start;
  275. int count = 0;
  276. while (true) {
  277. if (count++ > 1000) {
  278. Debug.LogError("Was the path really long or have we got cought in an infinite loop?");
  279. break;
  280. }
  281. if (start == end) {
  282. result.Add(nodes[end]);
  283. return;
  284. }
  285. int resCount = result.Count;
  286. // Run a binary search to find the furthest node that we have a clear line of sight to
  287. int mx = end+1;
  288. int mn = start+1;
  289. bool anySucceded = false;
  290. while (mx > mn+1) {
  291. int mid = (mx+mn)/2;
  292. GraphHitInfo hit;
  293. Vector3 sp = start == ostart ? startPoint : (Vector3)nodes[start].position;
  294. Vector3 ep = mid == end ? endPoint : (Vector3)nodes[mid].position;
  295. // Check if there is an obstacle between these points, or if there is no obstacle, but we didn't end up at the right node.
  296. // The second case can happen for example in buildings with multiple floors.
  297. if (graph.Linecast(sp, ep, out hit) || hit.node != nodes[mid]) {
  298. mx = mid;
  299. } else {
  300. anySucceded = true;
  301. mn = mid;
  302. }
  303. }
  304. if (!anySucceded) {
  305. result.Add(nodes[start]);
  306. // It is guaranteed that mn = start+1
  307. start = mn;
  308. } else {
  309. // Replace a part of the path with the straight path to the furthest node we had line of sight to.
  310. // Need to redo the linecast to get the trace (i.e. list of nodes along the line of sight).
  311. GraphHitInfo hit;
  312. Vector3 sp = start == ostart ? startPoint : (Vector3)nodes[start].position;
  313. Vector3 ep = mn == end ? endPoint : (Vector3)nodes[mn].position;
  314. graph.Linecast(sp, ep, out hit, result);
  315. long penaltySum = 0;
  316. long penaltySum2 = 0;
  317. for (int i = start; i <= mn; i++) {
  318. penaltySum += nodes[i].Penalty + (path.seeker != null ? path.seeker.tagPenalties[nodes[i].Tag] : 0);
  319. }
  320. for (int i = resCount; i < result.Count; i++) {
  321. penaltySum2 += result[i].Penalty + (path.seeker != null ? path.seeker.tagPenalties[result[i].Tag] : 0);
  322. }
  323. // Allow 40% more penalty on average per node
  324. if ((penaltySum*1.4*(result.Count-resCount)) < (penaltySum2*(mn-start+1)) || result[result.Count-1] != nodes[mn]) {
  325. //Debug.DrawLine ((Vector3)nodes[start].Position, (Vector3)nodes[mn].Position, Color.red);
  326. // Linecast hit the wrong node
  327. result.RemoveRange(resCount, result.Count-resCount);
  328. result.Add(nodes[start]);
  329. //Debug.Break();
  330. start = start+1;
  331. } else {
  332. //Debug.DrawLine ((Vector3)nodes[start].Position, (Vector3)nodes[mn].Position, Color.green);
  333. //Remove nodes[end]
  334. result.RemoveAt(result.Count-1);
  335. start = mn;
  336. }
  337. }
  338. }
  339. }
  340. /// <summary>
  341. /// Split funnel at node index splitIndex and throw the nodes up to that point away and replace with prefix.
  342. /// Used when the AI has happened to get sidetracked and entered a node outside the funnel.
  343. /// </summary>
  344. void UpdateFunnelCorridor (int splitIndex, List<TriangleMeshNode> prefix) {
  345. nodes.RemoveRange(0, splitIndex);
  346. nodes.InsertRange(0, prefix);
  347. left.Clear();
  348. right.Clear();
  349. left.Add(exactStart);
  350. right.Add(exactStart);
  351. for (int i = 0; i < nodes.Count-1; i++) {
  352. //NOTE should use return value in future versions
  353. nodes[i].GetPortal(nodes[i+1], left, right, false);
  354. }
  355. left.Add(exactEnd);
  356. right.Add(exactEnd);
  357. }
  358. /// <summary>True if any node in the path is destroyed</summary>
  359. bool CheckForDestroyedNodes () {
  360. // Loop through all nodes and check if they are destroyed
  361. // If so, we really need a recalculation of our path quickly
  362. // since there might be an obstacle blocking our path after
  363. // a graph update or something similar
  364. for (int i = 0, t = nodes.Count; i < t; i++) {
  365. if (nodes[i].Destroyed) {
  366. return true;
  367. }
  368. }
  369. return false;
  370. }
  371. /// <summary>
  372. /// Approximate distance (as the crow flies) to the endpoint of this path part.
  373. /// See: <see cref="exactEnd"/>
  374. /// </summary>
  375. public float DistanceToEndOfPath {
  376. get {
  377. var currentNode = CurrentNode;
  378. Vector3 closestOnNode = currentNode != null? currentNode.ClosestPointOnNode(currentPosition) : currentPosition;
  379. return (exactEnd - closestOnNode).magnitude;
  380. }
  381. }
  382. /// <summary>
  383. /// Clamps the position to the navmesh and repairs the path if the agent has moved slightly outside it.
  384. /// You should not call this method with anything other than the agent's position.
  385. /// </summary>
  386. public Vector3 ClampToNavmesh (Vector3 position) {
  387. if (path.transform != null) position = path.transform.InverseTransform(position);
  388. ClampToNavmeshInternal(ref position);
  389. if (path.transform != null) position = path.transform.Transform(position);
  390. return position;
  391. }
  392. /// <summary>
  393. /// Find the next points to move towards and clamp the position to the navmesh.
  394. ///
  395. /// Returns: The position of the agent clamped to make sure it is inside the navmesh.
  396. /// </summary>
  397. /// <param name="position">The position of the agent.</param>
  398. /// <param name="buffer">Will be filled with up to numCorners points which are the next points in the path towards the target.</param>
  399. /// <param name="numCorners">See buffer.</param>
  400. /// <param name="lastCorner">True if the buffer contains the end point of the path.</param>
  401. /// <param name="requiresRepath">True if nodes along the path have been destroyed and a path recalculation is necessary.</param>
  402. public Vector3 Update (Vector3 position, List<Vector3> buffer, int numCorners, out bool lastCorner, out bool requiresRepath) {
  403. if (path.transform != null) position = path.transform.InverseTransform(position);
  404. lastCorner = false;
  405. requiresRepath = false;
  406. // Only check for destroyed nodes every 10 frames
  407. if (checkForDestroyedNodesCounter >= 10) {
  408. checkForDestroyedNodesCounter = 0;
  409. requiresRepath |= CheckForDestroyedNodes();
  410. } else {
  411. checkForDestroyedNodesCounter++;
  412. }
  413. bool nodesDestroyed = ClampToNavmeshInternal(ref position);
  414. currentPosition = position;
  415. if (nodesDestroyed) {
  416. // Some nodes on the path have been destroyed
  417. // we need to recalculate the path immediately
  418. requiresRepath = true;
  419. lastCorner = false;
  420. buffer.Add(position);
  421. } else if (!FindNextCorners(position, currentNode, buffer, numCorners, out lastCorner)) {
  422. Debug.LogError("Failed to find next corners in the path");
  423. buffer.Add(position);
  424. }
  425. if (path.transform != null) {
  426. for (int i = 0; i < buffer.Count; i++) {
  427. buffer[i] = path.transform.Transform(buffer[i]);
  428. }
  429. position = path.transform.Transform(position);
  430. }
  431. return position;
  432. }
  433. /// <summary>Cached object to avoid unnecessary allocations</summary>
  434. static Queue<TriangleMeshNode> navmeshClampQueue = new Queue<TriangleMeshNode>();
  435. /// <summary>Cached object to avoid unnecessary allocations</summary>
  436. static List<TriangleMeshNode> navmeshClampList = new List<TriangleMeshNode>();
  437. /// <summary>Cached object to avoid unnecessary allocations</summary>
  438. static Dictionary<TriangleMeshNode, TriangleMeshNode> navmeshClampDict = new Dictionary<TriangleMeshNode, TriangleMeshNode>();
  439. /// <summary>
  440. /// Searches for the node the agent is inside.
  441. /// This will also clamp the position to the navmesh
  442. /// and repair the funnel cooridor if the agent moves slightly outside it.
  443. ///
  444. /// Returns: True if nodes along the path have been destroyed so that a path recalculation is required
  445. /// </summary>
  446. bool ClampToNavmeshInternal (ref Vector3 position) {
  447. var previousNode = nodes[currentNode];
  448. if (previousNode.Destroyed) {
  449. return true;
  450. }
  451. // Check if we are in the same node as we were in during the last frame and otherwise do a more extensive search
  452. if (previousNode.ContainsPoint(position)) {
  453. return false;
  454. }
  455. // This part of the code is relatively seldom called
  456. // Most of the time we are still on the same node as during the previous frame
  457. var que = navmeshClampQueue;
  458. var allVisited = navmeshClampList;
  459. var parent = navmeshClampDict;
  460. previousNode.TemporaryFlag1 = true;
  461. parent[previousNode] = null;
  462. que.Enqueue(previousNode);
  463. allVisited.Add(previousNode);
  464. float bestDistance = float.PositiveInfinity;
  465. Vector3 bestPoint = position;
  466. TriangleMeshNode bestNode = null;
  467. while (que.Count > 0) {
  468. var node = que.Dequeue();
  469. // Snap to the closest point in XZ space (keep the Y coordinate)
  470. // If we would have snapped to the closest point in 3D space, the agent
  471. // might slow down when traversing slopes
  472. var closest = node.ClosestPointOnNodeXZ(position);
  473. var dist = VectorMath.MagnitudeXZ(closest - position);
  474. // Check if this node is any closer than the previous best node.
  475. // Allow for a small margin to both avoid floating point errors and to allow
  476. // moving past very small local minima.
  477. if (dist <= bestDistance * 1.05f + 0.001f) {
  478. if (dist < bestDistance) {
  479. bestDistance = dist;
  480. bestPoint = closest;
  481. bestNode = node;
  482. }
  483. for (int i = 0; i < node.connections.Length; i++) {
  484. var neighbour = node.connections[i].node as TriangleMeshNode;
  485. if (neighbour != null && !neighbour.TemporaryFlag1) {
  486. neighbour.TemporaryFlag1 = true;
  487. parent[neighbour] = node;
  488. que.Enqueue(neighbour);
  489. allVisited.Add(neighbour);
  490. }
  491. }
  492. }
  493. }
  494. for (int i = 0; i < allVisited.Count; i++) allVisited[i].TemporaryFlag1 = false;
  495. allVisited.ClearFast();
  496. var closestNodeInPath = nodes.IndexOf(bestNode);
  497. // Move the x and z coordinates of the chararacter but not the y coordinate
  498. // because the navmesh surface may not line up with the ground
  499. position.x = bestPoint.x;
  500. position.z = bestPoint.z;
  501. // Check if the closest node
  502. // was on the path already or if we need to adjust it
  503. if (closestNodeInPath == -1) {
  504. // Reuse this list, because why not.
  505. var prefix = navmeshClampList;
  506. while (closestNodeInPath == -1) {
  507. prefix.Add(bestNode);
  508. bestNode = parent[bestNode];
  509. closestNodeInPath = nodes.IndexOf(bestNode);
  510. }
  511. // We have found a node containing the position, but it is outside the funnel
  512. // Recalculate the funnel to include this node
  513. exactStart = position;
  514. UpdateFunnelCorridor(closestNodeInPath, prefix);
  515. prefix.ClearFast();
  516. // Restart from the first node in the updated path
  517. currentNode = 0;
  518. } else {
  519. currentNode = closestNodeInPath;
  520. }
  521. parent.Clear();
  522. // Do a quick check to see if the next node in the path has been destroyed
  523. // If that is the case then we should plan a new path immediately
  524. return currentNode + 1 < nodes.Count && nodes[currentNode+1].Destroyed;
  525. }
  526. /// <summary>
  527. /// Fill wallBuffer with all navmesh wall segments close to the current position.
  528. /// A wall segment is a node edge which is not shared by any other neighbour node, i.e an outer edge on the navmesh.
  529. /// </summary>
  530. public void FindWalls (List<Vector3> wallBuffer, float range) {
  531. FindWalls(currentNode, wallBuffer, currentPosition, range);
  532. }
  533. void FindWalls (int nodeIndex, List<Vector3> wallBuffer, Vector3 position, float range) {
  534. if (range <= 0) return;
  535. bool negAbort = false;
  536. bool posAbort = false;
  537. range *= range;
  538. position.y = 0;
  539. //Looping as 0,-1,1,-2,2,-3,3,-4,4 etc. Avoids code duplication by keeping it to one loop instead of two
  540. for (int i = 0; !negAbort || !posAbort; i = i < 0 ? -i : -i-1) {
  541. if (i < 0 && negAbort) continue;
  542. if (i > 0 && posAbort) continue;
  543. if (i < 0 && nodeIndex+i < 0) {
  544. negAbort = true;
  545. continue;
  546. }
  547. if (i > 0 && nodeIndex+i >= nodes.Count) {
  548. posAbort = true;
  549. continue;
  550. }
  551. TriangleMeshNode prev = nodeIndex+i-1 < 0 ? null : nodes[nodeIndex+i-1];
  552. TriangleMeshNode node = nodes[nodeIndex+i];
  553. TriangleMeshNode next = nodeIndex+i+1 >= nodes.Count ? null : nodes[nodeIndex+i+1];
  554. if (node.Destroyed) {
  555. break;
  556. }
  557. if ((node.ClosestPointOnNodeXZ(position)-position).sqrMagnitude > range) {
  558. if (i < 0) negAbort = true;
  559. else posAbort = true;
  560. continue;
  561. }
  562. for (int j = 0; j < 3; j++) triBuffer[j] = 0;
  563. for (int j = 0; j < node.connections.Length; j++) {
  564. var other = node.connections[j].node as TriangleMeshNode;
  565. if (other == null) continue;
  566. int va = -1;
  567. for (int a = 0; a < 3; a++) {
  568. for (int b = 0; b < 3; b++) {
  569. if (node.GetVertex(a) == other.GetVertex((b+1) % 3) && node.GetVertex((a+1) % 3) == other.GetVertex(b)) {
  570. va = a;
  571. a = 3;
  572. break;
  573. }
  574. }
  575. }
  576. if (va == -1) {
  577. //No direct connection
  578. } else {
  579. triBuffer[va] = other == prev || other == next ? 2 : 1;
  580. }
  581. }
  582. for (int j = 0; j < 3; j++) {
  583. //Tribuffer values
  584. // 0 : Navmesh border, outer edge
  585. // 1 : Inner edge, to node inside funnel
  586. // 2 : Inner edge, to node outside funnel
  587. if (triBuffer[j] == 0) {
  588. //Add edge to list of walls
  589. wallBuffer.Add((Vector3)node.GetVertex(j));
  590. wallBuffer.Add((Vector3)node.GetVertex((j+1) % 3));
  591. }
  592. }
  593. }
  594. if (path.transform != null) {
  595. for (int i = 0; i < wallBuffer.Count; i++) {
  596. wallBuffer[i] = path.transform.Transform(wallBuffer[i]);
  597. }
  598. }
  599. }
  600. bool FindNextCorners (Vector3 origin, int startIndex, List<Vector3> funnelPath, int numCorners, out bool lastCorner) {
  601. lastCorner = false;
  602. if (left == null) throw new System.Exception("left list is null");
  603. if (right == null) throw new System.Exception("right list is null");
  604. if (funnelPath == null) throw new System.ArgumentNullException("funnelPath");
  605. if (left.Count != right.Count) throw new System.ArgumentException("left and right lists must have equal length");
  606. int diagonalCount = left.Count;
  607. if (diagonalCount == 0) throw new System.ArgumentException("no diagonals");
  608. if (diagonalCount-startIndex < 3) {
  609. //Direct path
  610. funnelPath.Add(left[diagonalCount-1]);
  611. lastCorner = true;
  612. return true;
  613. }
  614. #if ASTARDEBUG
  615. for (int i = startIndex; i < left.Count-1; i++) {
  616. Debug.DrawLine(left[i], left[i+1], Color.red);
  617. Debug.DrawLine(right[i], right[i+1], Color.magenta);
  618. Debug.DrawRay(right[i], Vector3.up, Color.magenta);
  619. }
  620. for (int i = 0; i < left.Count; i++) {
  621. Debug.DrawLine(right[i], left[i], Color.cyan);
  622. }
  623. #endif
  624. //Remove identical vertices
  625. while (left[startIndex+1] == left[startIndex+2] && right[startIndex+1] == right[startIndex+2]) {
  626. //System.Console.WriteLine ("Removing identical left and right");
  627. //left.RemoveAt (1);
  628. //right.RemoveAt (1);
  629. startIndex++;
  630. if (diagonalCount-startIndex <= 3) {
  631. return false;
  632. }
  633. }
  634. Vector3 swPoint = left[startIndex+2];
  635. if (swPoint == left[startIndex+1]) {
  636. swPoint = right[startIndex+2];
  637. }
  638. //Test
  639. while (VectorMath.IsColinearXZ(origin, left[startIndex+1], right[startIndex+1]) || VectorMath.RightOrColinearXZ(left[startIndex+1], right[startIndex+1], swPoint) == VectorMath.RightOrColinearXZ(left[startIndex+1], right[startIndex+1], origin)) {
  640. #if ASTARDEBUG
  641. Debug.DrawLine(left[startIndex+1], right[startIndex+1], new Color(0, 0, 0, 0.5F));
  642. Debug.DrawLine(origin, swPoint, new Color(0, 0, 0, 0.5F));
  643. #endif
  644. //left.RemoveAt (1);
  645. //right.RemoveAt (1);
  646. startIndex++;
  647. if (diagonalCount-startIndex < 3) {
  648. //Debug.Log ("#2 " + left.Count + " - " + startIndex + " = " + (left.Count-startIndex));
  649. //Direct path
  650. funnelPath.Add(left[diagonalCount-1]);
  651. lastCorner = true;
  652. return true;
  653. }
  654. swPoint = left[startIndex+2];
  655. if (swPoint == left[startIndex+1]) {
  656. swPoint = right[startIndex+2];
  657. }
  658. }
  659. //funnelPath.Add (origin);
  660. Vector3 portalApex = origin;
  661. Vector3 portalLeft = left[startIndex+1];
  662. Vector3 portalRight = right[startIndex+1];
  663. int apexIndex = startIndex+0;
  664. int rightIndex = startIndex+1;
  665. int leftIndex = startIndex+1;
  666. for (int i = startIndex+2; i < diagonalCount; i++) {
  667. if (funnelPath.Count >= numCorners) {
  668. return true;
  669. }
  670. if (funnelPath.Count > 2000) {
  671. Debug.LogWarning("Avoiding infinite loop. Remove this check if you have this long paths.");
  672. break;
  673. }
  674. Vector3 pLeft = left[i];
  675. Vector3 pRight = right[i];
  676. /*Debug.DrawLine (portalApex,portalLeft,Color.red);
  677. * Debug.DrawLine (portalApex,portalRight,Color.yellow);
  678. * Debug.DrawLine (portalApex,left,Color.cyan);
  679. * Debug.DrawLine (portalApex,right,Color.cyan);*/
  680. if (VectorMath.SignedTriangleAreaTimes2XZ(portalApex, portalRight, pRight) >= 0) {
  681. if (portalApex == portalRight || VectorMath.SignedTriangleAreaTimes2XZ(portalApex, portalLeft, pRight) <= 0) {
  682. portalRight = pRight;
  683. rightIndex = i;
  684. } else {
  685. funnelPath.Add(portalLeft);
  686. portalApex = portalLeft;
  687. apexIndex = leftIndex;
  688. portalLeft = portalApex;
  689. portalRight = portalApex;
  690. leftIndex = apexIndex;
  691. rightIndex = apexIndex;
  692. i = apexIndex;
  693. continue;
  694. }
  695. }
  696. if (VectorMath.SignedTriangleAreaTimes2XZ(portalApex, portalLeft, pLeft) <= 0) {
  697. if (portalApex == portalLeft || VectorMath.SignedTriangleAreaTimes2XZ(portalApex, portalRight, pLeft) >= 0) {
  698. portalLeft = pLeft;
  699. leftIndex = i;
  700. } else {
  701. funnelPath.Add(portalRight);
  702. portalApex = portalRight;
  703. apexIndex = rightIndex;
  704. portalLeft = portalApex;
  705. portalRight = portalApex;
  706. leftIndex = apexIndex;
  707. rightIndex = apexIndex;
  708. i = apexIndex;
  709. continue;
  710. }
  711. }
  712. }
  713. lastCorner = true;
  714. funnelPath.Add(left[diagonalCount-1]);
  715. return true;
  716. }
  717. }
  718. public class RichSpecial : RichPathPart {
  719. public NodeLink2 nodeLink;
  720. public Transform first;
  721. public Transform second;
  722. public bool reverse;
  723. public override void OnEnterPool () {
  724. nodeLink = null;
  725. }
  726. /// <summary>Works like a constructor, but can be used even for pooled objects. Returns this for easy chaining</summary>
  727. public RichSpecial Initialize (NodeLink2 nodeLink, GraphNode first) {
  728. this.nodeLink = nodeLink;
  729. if (first == nodeLink.startNode) {
  730. this.first = nodeLink.StartTransform;
  731. this.second = nodeLink.EndTransform;
  732. reverse = false;
  733. } else {
  734. this.first = nodeLink.EndTransform;
  735. this.second = nodeLink.StartTransform;
  736. reverse = true;
  737. }
  738. return this;
  739. }
  740. }
  741. }