BBTree.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  1. //#define ASTARDEBUG //"BBTree Debug" If enables, some queries to the tree will show debug lines. Turn off multithreading when using this since DrawLine calls cannot be called from a different thread
  2. using System;
  3. using UnityEngine;
  4. namespace PF {
  5. /** Axis Aligned Bounding Box Tree.
  6. * Holds a bounding box tree of triangles.
  7. *
  8. * \astarpro
  9. */
  10. public class BBTree : IAstarPooledObject {
  11. /** Holds all tree nodes */
  12. BBTreeBox[] tree = null;
  13. TriangleMeshNode[] nodeLookup = null;
  14. int count;
  15. int leafNodes;
  16. const int MaximumLeafSize = 4;
  17. /** Clear the tree.
  18. * Note that references to old nodes will still be intact so the GC cannot immediately collect them.
  19. */
  20. public void Clear () {
  21. count = 0;
  22. leafNodes = 0;
  23. if (tree != null) ArrayPool<BBTreeBox>.Release(ref tree);
  24. if (nodeLookup != null) {
  25. // Prevent memory leaks as the pool does not clear the array
  26. for (int i = 0; i < nodeLookup.Length; i++) nodeLookup[i] = null;
  27. ArrayPool<TriangleMeshNode>.Release(ref nodeLookup);
  28. }
  29. tree = ArrayPool<BBTreeBox>.Claim(0);
  30. nodeLookup = ArrayPool<TriangleMeshNode>.Claim(0);
  31. }
  32. void IAstarPooledObject.OnEnterPool () {
  33. Clear();
  34. }
  35. void EnsureCapacity (int c) {
  36. if (c > tree.Length) {
  37. var newArr = ArrayPool<BBTreeBox>.Claim(c);
  38. tree.CopyTo(newArr, 0);
  39. ArrayPool<BBTreeBox>.Release(ref tree);
  40. tree = newArr;
  41. }
  42. }
  43. void EnsureNodeCapacity (int c) {
  44. if (c > nodeLookup.Length) {
  45. var newArr = ArrayPool<TriangleMeshNode>.Claim(c);
  46. nodeLookup.CopyTo(newArr, 0);
  47. ArrayPool<TriangleMeshNode>.Release(ref nodeLookup);
  48. nodeLookup = newArr;
  49. }
  50. }
  51. int GetBox (IntRect rect) {
  52. if (count >= tree.Length) EnsureCapacity(count+1);
  53. tree[count] = new BBTreeBox(rect);
  54. count++;
  55. return count-1;
  56. }
  57. /** Rebuilds the tree using the specified nodes */
  58. public void RebuildFrom (TriangleMeshNode[] nodes) {
  59. Clear();
  60. if (nodes.Length == 0) return;
  61. // We will use approximately 2N tree nodes
  62. EnsureCapacity(Mathf.CeilToInt(nodes.Length * 2.1f));
  63. // We will use approximately N node references
  64. EnsureNodeCapacity(Mathf.CeilToInt(nodes.Length * 1.1f));
  65. // This will store the order of the nodes while the tree is being built
  66. // It turns out that it is a lot faster to do this than to actually modify
  67. // the nodes and nodeBounds arrays (presumably since that involves shuffling
  68. // around 20 bytes of memory (sizeof(pointer) + sizeof(IntRect)) per node
  69. // instead of 4 bytes (sizeof(int)).
  70. // It also means we don't have to make a copy of the nodes array since
  71. // we do not modify it
  72. var permutation = ArrayPool<int>.Claim(nodes.Length);
  73. for (int i = 0; i < nodes.Length; i++) {
  74. permutation[i] = i;
  75. }
  76. // Precalculate the bounds of the nodes in XZ space.
  77. // It turns out that calculating the bounds is a bottleneck and precalculating
  78. // the bounds makes it around 3 times faster to build a tree
  79. var nodeBounds = ArrayPool<IntRect>.Claim(nodes.Length);
  80. for (int i = 0; i < nodes.Length; i++) {
  81. Int3 v0, v1, v2;
  82. nodes[i].GetVertices(out v0, out v1, out v2);
  83. var rect = new IntRect(v0.x, v0.z, v0.x, v0.z);
  84. rect = rect.ExpandToContain(v1.x, v1.z);
  85. rect = rect.ExpandToContain(v2.x, v2.z);
  86. nodeBounds[i] = rect;
  87. }
  88. RebuildFromInternal(nodes, permutation, nodeBounds, 0, nodes.Length, false);
  89. ArrayPool<int>.Release(ref permutation);
  90. ArrayPool<IntRect>.Release(ref nodeBounds);
  91. }
  92. static int SplitByX (TriangleMeshNode[] nodes, int[] permutation, int from, int to, int divider) {
  93. int mx = to;
  94. for (int i = from; i < mx; i++) {
  95. if (nodes[permutation[i]].position.x > divider) {
  96. mx--;
  97. // Swap items i and mx
  98. var tmp = permutation[mx];
  99. permutation[mx] = permutation[i];
  100. permutation[i] = tmp;
  101. i--;
  102. }
  103. }
  104. return mx;
  105. }
  106. static int SplitByZ (TriangleMeshNode[] nodes, int[] permutation, int from, int to, int divider) {
  107. int mx = to;
  108. for (int i = from; i < mx; i++) {
  109. if (nodes[permutation[i]].position.z > divider) {
  110. mx--;
  111. // Swap items i and mx
  112. var tmp = permutation[mx];
  113. permutation[mx] = permutation[i];
  114. permutation[i] = tmp;
  115. i--;
  116. }
  117. }
  118. return mx;
  119. }
  120. int RebuildFromInternal (TriangleMeshNode[] nodes, int[] permutation, IntRect[] nodeBounds, int from, int to, bool odd) {
  121. var rect = NodeBounds(permutation, nodeBounds, from, to);
  122. int box = GetBox(rect);
  123. if (to - from <= MaximumLeafSize) {
  124. var nodeOffset = tree[box].nodeOffset = leafNodes*MaximumLeafSize;
  125. EnsureNodeCapacity(nodeOffset + MaximumLeafSize);
  126. leafNodes++;
  127. // Assign all nodes to the array. Note that we also need clear unused slots as the array from the pool may contain any information
  128. for (int i = 0; i < MaximumLeafSize; i++) {
  129. nodeLookup[nodeOffset + i] = i < to - from ? nodes[permutation[from + i]] : null;
  130. }
  131. return box;
  132. }
  133. int splitIndex;
  134. if (odd) {
  135. // X
  136. int divider = (rect.xmin + rect.xmax)/2;
  137. splitIndex = SplitByX(nodes, permutation, from, to, divider);
  138. } else {
  139. // Y/Z
  140. int divider = (rect.ymin + rect.ymax)/2;
  141. splitIndex = SplitByZ(nodes, permutation, from, to, divider);
  142. }
  143. if (splitIndex == from || splitIndex == to) {
  144. // All nodes were on one side of the divider
  145. // Try to split along the other axis
  146. if (!odd) {
  147. // X
  148. int divider = (rect.xmin + rect.xmax)/2;
  149. splitIndex = SplitByX(nodes, permutation, from, to, divider);
  150. } else {
  151. // Y/Z
  152. int divider = (rect.ymin + rect.ymax)/2;
  153. splitIndex = SplitByZ(nodes, permutation, from, to, divider);
  154. }
  155. if (splitIndex == from || splitIndex == to) {
  156. // All nodes were on one side of the divider
  157. // Just pick one half
  158. splitIndex = (from+to)/2;
  159. }
  160. }
  161. tree[box].left = RebuildFromInternal(nodes, permutation, nodeBounds, from, splitIndex, !odd);
  162. tree[box].right = RebuildFromInternal(nodes, permutation, nodeBounds, splitIndex, to, !odd);
  163. return box;
  164. }
  165. /** Calculates the bounding box in XZ space of all nodes between \a from (inclusive) and \a to (exclusive) */
  166. static IntRect NodeBounds (int[] permutation, IntRect[] nodeBounds, int from, int to) {
  167. var rect = nodeBounds[permutation[from]];
  168. for (int j = from + 1; j < to; j++) {
  169. var otherRect = nodeBounds[permutation[j]];
  170. // Equivalent to rect = IntRect.Union(rect, otherRect)
  171. // but manually inlining is approximately
  172. // 25% faster when building an entire tree.
  173. // This code is hot when using navmesh cutting.
  174. rect.xmin = Math.Min(rect.xmin, otherRect.xmin);
  175. rect.ymin = Math.Min(rect.ymin, otherRect.ymin);
  176. rect.xmax = Math.Max(rect.xmax, otherRect.xmax);
  177. rect.ymax = Math.Max(rect.ymax, otherRect.ymax);
  178. }
  179. return rect;
  180. }
  181. #if !SERVER
  182. [System.Diagnostics.Conditional("ASTARDEBUG")]
  183. static void DrawDebugRect (IntRect rect) {
  184. UnityEngine.Debug.DrawLine(new Vector3(rect.xmin, 0, rect.ymin), new Vector3(rect.xmax, 0, rect.ymin), UnityEngine.Color.white);
  185. UnityEngine.Debug.DrawLine(new Vector3(rect.xmin, 0, rect.ymax), new Vector3(rect.xmax, 0, rect.ymax), UnityEngine.Color.white);
  186. UnityEngine.Debug.DrawLine(new Vector3(rect.xmin, 0, rect.ymin), new Vector3(rect.xmin, 0, rect.ymax), UnityEngine.Color.white);
  187. UnityEngine.Debug.DrawLine(new Vector3(rect.xmax, 0, rect.ymin), new Vector3(rect.xmax, 0, rect.ymax), UnityEngine.Color.white);
  188. }
  189. [System.Diagnostics.Conditional("ASTARDEBUG")]
  190. static void DrawDebugNode (TriangleMeshNode node, float yoffset, UnityEngine.Color color) {
  191. UnityEngine.Debug.DrawLine(((Vector3)node.GetVertex(1) + Vector3.up*yoffset), ((Vector3)node.GetVertex(2) + Vector3.up*yoffset), color);
  192. UnityEngine.Debug.DrawLine(((Vector3)node.GetVertex(0) + Vector3.up*yoffset), ((Vector3)node.GetVertex(1) + Vector3.up*yoffset), color);
  193. UnityEngine.Debug.DrawLine(((Vector3)node.GetVertex(2) + Vector3.up*yoffset), ((Vector3)node.GetVertex(0) + Vector3.up*yoffset), color);
  194. }
  195. #endif
  196. /** Queries the tree for the closest node to \a p constrained by the NNConstraint.
  197. * Note that this function will only fill in the constrained node.
  198. * If you want a node not constrained by any NNConstraint, do an additional search with constraint = NNConstraint.None
  199. */
  200. public NNInfoInternal QueryClosest (Vector3 p, NNConstraint constraint, out float distance) {
  201. distance = float.PositiveInfinity;
  202. return QueryClosest(p, constraint, ref distance, new NNInfoInternal(null));
  203. }
  204. /** Queries the tree for the closest node to \a p constrained by the NNConstraint trying to improve an existing solution.
  205. * Note that this function will only fill in the constrained node.
  206. * If you want a node not constrained by any NNConstraint, do an additional search with constraint = NNConstraint.None
  207. *
  208. * This method will completely ignore any Y-axis differences in positions.
  209. *
  210. * \param p Point to search around
  211. * \param constraint Optionally set to constrain which nodes to return
  212. * \param distance The best distance for the \a previous solution. Will be updated with the best distance
  213. * after this search. Will be positive infinity if no node could be found.
  214. * Set to positive infinity if there was no previous solution.
  215. * \param previous This search will start from the \a previous NNInfo and improve it if possible.
  216. * Even if the search fails on this call, the solution will never be worse than \a previous.
  217. * Note that the \a distance parameter need to be configured with the distance for the previous result
  218. * otherwise it may get overwritten even though it was actually closer.
  219. */
  220. public NNInfoInternal QueryClosestXZ (Vector3 p, NNConstraint constraint, ref float distance, NNInfoInternal previous) {
  221. var sqrDistance = distance*distance;
  222. var origSqrDistance = sqrDistance;
  223. if (count > 0 && SquaredRectPointDistance(tree[0].rect, p) < sqrDistance) {
  224. SearchBoxClosestXZ(0, p, ref sqrDistance, constraint, ref previous);
  225. // Only update the distance if the squared distance changed as otherwise #distance
  226. // might change due to rounding errors even if no better solution was found
  227. if (sqrDistance < origSqrDistance) distance = Mathf.Sqrt(sqrDistance);
  228. }
  229. return previous;
  230. }
  231. void SearchBoxClosestXZ (int boxi, Vector3 p, ref float closestSqrDist, NNConstraint constraint, ref NNInfoInternal nnInfo) {
  232. BBTreeBox box = tree[boxi];
  233. if (box.IsLeaf) {
  234. var nodes = nodeLookup;
  235. for (int i = 0; i < MaximumLeafSize && nodes[box.nodeOffset+i] != null; i++) {
  236. var node = nodes[box.nodeOffset+i];
  237. // Update the NNInfo
  238. #if !SERVER
  239. DrawDebugNode(node, 0.2f, UnityEngine.Color.red);
  240. #endif
  241. if (constraint == null || constraint.Suitable(node)) {
  242. Vector3 closest = node.ClosestPointOnNodeXZ(p);
  243. // XZ squared distance
  244. float dist = (closest.x-p.x)*(closest.x-p.x)+(closest.z-p.z)*(closest.z-p.z);
  245. // There's a theoretical case when the closest point is on the edge of a node which may cause the
  246. // closest point's xz coordinates to not line up perfectly with p's xz coordinates even though they should
  247. // (because floating point errors are annoying). So use a tiny margin to cover most of those cases.
  248. const float fuzziness = 0.000001f;
  249. if (nnInfo.constrainedNode == null || dist < closestSqrDist - fuzziness || (dist <= closestSqrDist + fuzziness && Mathf.Abs(closest.y - p.y) < Mathf.Abs(nnInfo.constClampedPosition.y - p.y))) {
  250. nnInfo.constrainedNode = node;
  251. nnInfo.constClampedPosition = closest;
  252. closestSqrDist = dist;
  253. }
  254. }
  255. }
  256. } else {
  257. #if !SERVER
  258. DrawDebugRect(box.rect);
  259. #endif
  260. int first = box.left, second = box.right;
  261. float firstDist, secondDist;
  262. GetOrderedChildren(ref first, ref second, out firstDist, out secondDist, p);
  263. // Search children (closest box first to improve performance)
  264. if (firstDist <= closestSqrDist) {
  265. SearchBoxClosestXZ(first, p, ref closestSqrDist, constraint, ref nnInfo);
  266. }
  267. if (secondDist <= closestSqrDist) {
  268. SearchBoxClosestXZ(second, p, ref closestSqrDist, constraint, ref nnInfo);
  269. }
  270. }
  271. }
  272. /** Queries the tree for the closest node to \a p constrained by the NNConstraint trying to improve an existing solution.
  273. * Note that this function will only fill in the constrained node.
  274. * If you want a node not constrained by any NNConstraint, do an additional search with constraint = NNConstraint.None
  275. *
  276. * \param p Point to search around
  277. * \param constraint Optionally set to constrain which nodes to return
  278. * \param distance The best distance for the \a previous solution. Will be updated with the best distance
  279. * after this search. Will be positive infinity if no node could be found.
  280. * Set to positive infinity if there was no previous solution.
  281. * \param previous This search will start from the \a previous NNInfo and improve it if possible.
  282. * Even if the search fails on this call, the solution will never be worse than \a previous.
  283. */
  284. public NNInfoInternal QueryClosest (Vector3 p, NNConstraint constraint, ref float distance, NNInfoInternal previous) {
  285. var sqrDistance = distance*distance;
  286. var origSqrDistance = sqrDistance;
  287. if (count > 0 && SquaredRectPointDistance(tree[0].rect, p) < sqrDistance) {
  288. SearchBoxClosest(0, p, ref sqrDistance, constraint, ref previous);
  289. // Only update the distance if the squared distance changed as otherwise #distance
  290. // might change due to rounding errors even if no better solution was found
  291. if (sqrDistance < origSqrDistance) distance = Mathf.Sqrt(sqrDistance);
  292. }
  293. return previous;
  294. }
  295. void SearchBoxClosest (int boxi, Vector3 p, ref float closestSqrDist, NNConstraint constraint, ref NNInfoInternal nnInfo) {
  296. BBTreeBox box = tree[boxi];
  297. if (box.IsLeaf) {
  298. var nodes = nodeLookup;
  299. for (int i = 0; i < MaximumLeafSize && nodes[box.nodeOffset+i] != null; i++) {
  300. var node = nodes[box.nodeOffset+i];
  301. Vector3 closest = node.ClosestPointOnNode(p);
  302. float dist = (closest-p).sqrMagnitude;
  303. if (dist < closestSqrDist) {
  304. #if !SERVER
  305. DrawDebugNode(node, 0.2f, UnityEngine.Color.red);
  306. #endif
  307. if (constraint == null || constraint.Suitable(node)) {
  308. // Update the NNInfo
  309. nnInfo.constrainedNode = node;
  310. nnInfo.constClampedPosition = closest;
  311. closestSqrDist = dist;
  312. }
  313. } else {
  314. #if !SERVER
  315. DrawDebugNode(node, 0.0f, UnityEngine.Color.blue);
  316. #endif
  317. }
  318. }
  319. } else {
  320. #if !SERVER
  321. DrawDebugRect(box.rect);
  322. #endif
  323. int first = box.left, second = box.right;
  324. float firstDist, secondDist;
  325. GetOrderedChildren(ref first, ref second, out firstDist, out secondDist, p);
  326. // Search children (closest box first to improve performance)
  327. if (firstDist < closestSqrDist) {
  328. SearchBoxClosest(first, p, ref closestSqrDist, constraint, ref nnInfo);
  329. }
  330. if (secondDist < closestSqrDist) {
  331. SearchBoxClosest(second, p, ref closestSqrDist, constraint, ref nnInfo);
  332. }
  333. }
  334. }
  335. /** Orders the box indices first and second by the approximate distance to the point p */
  336. void GetOrderedChildren (ref int first, ref int second, out float firstDist, out float secondDist, Vector3 p) {
  337. firstDist = SquaredRectPointDistance(tree[first].rect, p);
  338. secondDist = SquaredRectPointDistance(tree[second].rect, p);
  339. if (secondDist < firstDist) {
  340. // Swap
  341. var tmp = first;
  342. first = second;
  343. second = tmp;
  344. var tmp2 = firstDist;
  345. firstDist = secondDist;
  346. secondDist = tmp2;
  347. }
  348. }
  349. /** Searches for a node which contains the specified point.
  350. * If there are multiple nodes that contain the point any one of them
  351. * may be returned.
  352. *
  353. * \see TriangleMeshNode.ContainsPoint
  354. */
  355. public TriangleMeshNode QueryInside (Vector3 p, NNConstraint constraint) {
  356. return count != 0 && tree[0].Contains(p) ? SearchBoxInside(0, p, constraint) : null;
  357. }
  358. TriangleMeshNode SearchBoxInside (int boxi, Vector3 p, NNConstraint constraint) {
  359. BBTreeBox box = tree[boxi];
  360. if (box.IsLeaf) {
  361. var nodes = nodeLookup;
  362. for (int i = 0; i < MaximumLeafSize && nodes[box.nodeOffset+i] != null; i++) {
  363. var node = nodes[box.nodeOffset+i];
  364. if (node.ContainsPoint((Int3)p)) {
  365. #if !SERVER
  366. DrawDebugNode(node, 0.2f, UnityEngine.Color.red);
  367. #endif
  368. if (constraint == null || constraint.Suitable(node)) {
  369. return node;
  370. }
  371. } else {
  372. #if !SERVER
  373. DrawDebugNode(node, 0.0f, UnityEngine.Color.blue);
  374. #endif
  375. }
  376. }
  377. } else {
  378. #if !SERVER
  379. DrawDebugRect(box.rect);
  380. #endif
  381. //Search children
  382. if (tree[box.left].Contains(p)) {
  383. var result = SearchBoxInside(box.left, p, constraint);
  384. if (result != null) return result;
  385. }
  386. if (tree[box.right].Contains(p)) {
  387. var result = SearchBoxInside(box.right, p, constraint);
  388. if (result != null) return result;
  389. }
  390. }
  391. return null;
  392. }
  393. struct BBTreeBox {
  394. public IntRect rect;
  395. public int nodeOffset;
  396. public int left, right;
  397. public bool IsLeaf {
  398. get {
  399. return nodeOffset >= 0;
  400. }
  401. }
  402. public BBTreeBox (IntRect rect) {
  403. nodeOffset = -1;
  404. this.rect = rect;
  405. left = right = -1;
  406. }
  407. public bool Contains (Vector3 point) {
  408. var pi = (Int3)point;
  409. return rect.Contains(pi.x, pi.z);
  410. }
  411. }
  412. /** Returns distance from \a p to the rectangle \a r */
  413. static float SquaredRectPointDistance (IntRect r, Vector3 p) {
  414. Vector3 po = p;
  415. p.x = Math.Max(p.x, r.xmin*Int3.PrecisionFactor);
  416. p.x = Math.Min(p.x, r.xmax*Int3.PrecisionFactor);
  417. p.z = Math.Max(p.z, r.ymin*Int3.PrecisionFactor);
  418. p.z = Math.Min(p.z, r.ymax*Int3.PrecisionFactor);
  419. // XZ squared magnitude comparison
  420. return (p.x-po.x)*(p.x-po.x) + (p.z-po.z)*(p.z-po.z);
  421. }
  422. }
  423. }