BBTree.cs 17 KB

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