BoundsOctree.cs 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953
  1. /*
  2. Copyright (c) 2023 Omar Duarte
  3. Unauthorized copying of this file, via any medium is strictly prohibited.
  4. Modified by Omar Duarte, 2020.
  5. This file incorporates work covered by the following copyright and
  6. permission notice:
  7. Copyright (c) 2014, Nition, BSD licence. All rights reserved.
  8. Redistribution and use in source and binary forms, with or without
  9. modification, are permitted provided that the following conditions are met:
  10. * Redistributions of source code must retain the above copyright notice, this
  11. list of conditions and the following disclaimer.
  12. * Redistributions in binary form must reproduce the above copyright notice,
  13. this list of conditions and the following disclaimer in the documentation
  14. and/or other materials provided with the distribution.
  15. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  16. AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  17. IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  18. DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
  19. FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  20. DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  21. SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  22. CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  23. OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  24. OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  25. */
  26. using UnityEngine;
  27. namespace PluginMaster
  28. {
  29. // A node in a BoundsOctree
  30. public class BoundsOctreeNode<T>
  31. {
  32. // Centre of this node
  33. public Vector3 Center { get; private set; }
  34. // Length of this node if it has a looseness of 1.0
  35. public float BaseLength { get; private set; }
  36. // Looseness value for this node
  37. float looseness;
  38. // Minimum size for a node in this octree
  39. float minSize;
  40. // Actual length of sides, taking the looseness value into account
  41. float adjLength;
  42. // Bounding box that represents this node
  43. Bounds bounds = default(Bounds);
  44. // Objects in this node
  45. readonly System.Collections.Generic.List<OctreeObject> objects = new System.Collections.Generic.List<OctreeObject>();
  46. // Child nodes, if any
  47. BoundsOctreeNode<T>[] children = null;
  48. bool HasChildren { get { return children != null; } }
  49. // Bounds of potential children to this node. These are actual size (with looseness taken into account), not base size
  50. Bounds[] childBounds;
  51. // If there are already NUM_OBJECTS_ALLOWED in a node, we split it into children
  52. // A generally good number seems to be something around 8-15
  53. const int NUM_OBJECTS_ALLOWED = 8;
  54. // An object in the octree
  55. struct OctreeObject
  56. {
  57. public T Obj;
  58. public Bounds Bounds;
  59. }
  60. /// <summary>
  61. /// Constructor.
  62. /// </summary>
  63. /// <param name="baseLengthVal">Length of this node, not taking looseness into account.</param>
  64. /// <param name="minSizeVal">Minimum size of nodes in this octree.</param>
  65. /// <param name="loosenessVal">Multiplier for baseLengthVal to get the actual size.</param>
  66. /// <param name="centerVal">Centre position of this node.</param>
  67. public BoundsOctreeNode(float baseLengthVal, float minSizeVal, float loosenessVal, Vector3 centerVal)
  68. {
  69. SetValues(baseLengthVal, minSizeVal, loosenessVal, centerVal);
  70. }
  71. // #### PUBLIC METHODS ####
  72. /// <summary>
  73. /// Add an object.
  74. /// </summary>
  75. /// <param name="obj">Object to add.</param>
  76. /// <param name="objBounds">3D bounding box around the object.</param>
  77. /// <returns>True if the object fits entirely within this node.</returns>
  78. public bool Add(T obj, Bounds objBounds)
  79. {
  80. if (!Encapsulates(bounds, objBounds))
  81. {
  82. return false;
  83. }
  84. SubAdd(obj, objBounds);
  85. return true;
  86. }
  87. /// <summary>
  88. /// Remove an object. Makes the assumption that the object only exists once in the tree.
  89. /// </summary>
  90. /// <param name="obj">Object to remove.</param>
  91. /// <returns>True if the object was removed successfully.</returns>
  92. public bool Remove(T obj)
  93. {
  94. bool removed = false;
  95. for (int i = 0; i < objects.Count; i++)
  96. {
  97. if (objects[i].Obj.Equals(obj))
  98. {
  99. removed = objects.Remove(objects[i]);
  100. break;
  101. }
  102. }
  103. if (!removed && children != null)
  104. {
  105. for (int i = 0; i < 8; i++)
  106. {
  107. removed = children[i].Remove(obj);
  108. if (removed) break;
  109. }
  110. }
  111. if (removed && children != null)
  112. {
  113. // Check if we should merge nodes now that we've removed an item
  114. if (ShouldMerge())
  115. {
  116. Merge();
  117. }
  118. }
  119. return removed;
  120. }
  121. /// <summary>
  122. /// Removes the specified object at the given position. Makes the assumption that the object only exists once in the tree.
  123. /// </summary>
  124. /// <param name="obj">Object to remove.</param>
  125. /// <param name="objBounds">3D bounding box around the object.</param>
  126. /// <returns>True if the object was removed successfully.</returns>
  127. public bool Remove(T obj, Bounds objBounds)
  128. {
  129. if (!Encapsulates(bounds, objBounds))
  130. {
  131. return false;
  132. }
  133. return SubRemove(obj, objBounds);
  134. }
  135. /// <summary>
  136. /// Check if the specified bounds intersect with anything in the tree. See also: GetColliding.
  137. /// </summary>
  138. /// <param name="checkBounds">Bounds to check.</param>
  139. /// <returns>True if there was a collision.</returns>
  140. public bool IsColliding(ref Bounds checkBounds)
  141. {
  142. // Are the input bounds at least partially in this node?
  143. if (!bounds.Intersects(checkBounds))
  144. {
  145. return false;
  146. }
  147. // Check against any objects in this node
  148. for (int i = 0; i < objects.Count; i++)
  149. {
  150. if (objects[i].Bounds.Intersects(checkBounds))
  151. {
  152. return true;
  153. }
  154. }
  155. // Check children
  156. if (children != null)
  157. {
  158. for (int i = 0; i < 8; i++)
  159. {
  160. if (children[i].IsColliding(ref checkBounds))
  161. {
  162. return true;
  163. }
  164. }
  165. }
  166. return false;
  167. }
  168. /// <summary>
  169. /// Check if the specified ray intersects with anything in the tree. See also: GetColliding.
  170. /// </summary>
  171. /// <param name="checkRay">Ray to check.</param>
  172. /// <param name="maxDistance">Distance to check.</param>
  173. /// <returns>True if there was a collision.</returns>
  174. public bool IsColliding(ref Ray checkRay, float maxDistance = float.PositiveInfinity)
  175. {
  176. // Is the input ray at least partially in this node?
  177. float distance;
  178. if (!bounds.IntersectRay(checkRay, out distance) || distance > maxDistance)
  179. {
  180. return false;
  181. }
  182. // Check against any objects in this node
  183. for (int i = 0; i < objects.Count; i++)
  184. {
  185. if (objects[i].Bounds.IntersectRay(checkRay, out distance) && distance <= maxDistance)
  186. {
  187. return true;
  188. }
  189. }
  190. // Check children
  191. if (children != null)
  192. {
  193. for (int i = 0; i < 8; i++)
  194. {
  195. if (children[i].IsColliding(ref checkRay, maxDistance))
  196. {
  197. return true;
  198. }
  199. }
  200. }
  201. return false;
  202. }
  203. /// <summary>
  204. /// Returns an array of objects that intersect with the specified bounds, if any. Otherwise returns an empty array. See also: IsColliding.
  205. /// </summary>
  206. /// <param name="checkBounds">Bounds to check. Passing by ref as it improves performance with structs.</param>
  207. /// <param name="result">List result.</param>
  208. /// <returns>Objects that intersect with the specified bounds.</returns>
  209. public void GetColliding(ref Bounds checkBounds, System.Collections.Generic.List<T> result)
  210. {
  211. // Are the input bounds at least partially in this node?
  212. if (!bounds.Intersects(checkBounds))
  213. {
  214. return;
  215. }
  216. // Check against any objects in this node
  217. for (int i = 0; i < objects.Count; i++)
  218. {
  219. if (objects[i].Bounds.Intersects(checkBounds))
  220. {
  221. result.Add(objects[i].Obj);
  222. }
  223. }
  224. // Check children
  225. if (children != null)
  226. {
  227. for (int i = 0; i < 8; i++)
  228. {
  229. children[i].GetColliding(ref checkBounds, result);
  230. }
  231. }
  232. }
  233. public void GetColliding(Vector3 center, Vector3 localInnerRadius,
  234. Quaternion gridRotation, Quaternion objectRotation, System.Collections.Generic.List<T> result)
  235. {
  236. var checkSize = localInnerRadius * 1.9999f;
  237. var checkBounds = new Bounds(center, checkSize);
  238. if (!bounds.Intersects(checkBounds))
  239. {
  240. return;
  241. }
  242. var nullObjectsIndexes = new System.Collections.Generic.List<int>();
  243. // Check against any objects in this node
  244. for (int i = 0; i < objects.Count; i++)
  245. {
  246. var octreeObj = objects[i];
  247. if (octreeObj.Obj == null)
  248. {
  249. nullObjectsIndexes.Insert(0, i);
  250. continue;
  251. }
  252. var objCenter = octreeObj.Bounds.center;
  253. var fromTargetToObj = objCenter - center;
  254. var rotatedCellCenter = center + Quaternion.Inverse(gridRotation) * fromTargetToObj;
  255. var rotatedBounds = new Bounds(rotatedCellCenter, octreeObj.Bounds.size);
  256. if (rotatedBounds.Intersects(checkBounds))
  257. result.Add(objects[i].Obj);
  258. }
  259. foreach(var i in nullObjectsIndexes) objects.RemoveAt(i);
  260. // Check children
  261. if (children != null)
  262. {
  263. for (int i = 0; i < 8; i++)
  264. {
  265. children[i].GetColliding(center, localInnerRadius, gridRotation, objectRotation, result);
  266. }
  267. }
  268. }
  269. /// <summary>
  270. /// Returns an array of objects that intersect with the specified ray, if any. Otherwise returns an empty array. See also: IsColliding.
  271. /// </summary>
  272. /// <param name="checkRay">Ray to check. Passing by ref as it improves performance with structs.</param>
  273. /// <param name="maxDistance">Distance to check.</param>
  274. /// <param name="result">List result.</param>
  275. /// <returns>Objects that intersect with the specified ray.</returns>
  276. public void GetColliding(ref Ray checkRay, System.Collections.Generic.List<T> result, float maxDistance = float.PositiveInfinity)
  277. {
  278. float distance;
  279. // Is the input ray at least partially in this node?
  280. if (!bounds.IntersectRay(checkRay, out distance) || distance > maxDistance)
  281. {
  282. return;
  283. }
  284. // Check against any objects in this node
  285. for (int i = 0; i < objects.Count; i++)
  286. {
  287. if (objects[i].Bounds.IntersectRay(checkRay, out distance) && distance <= maxDistance)
  288. {
  289. result.Add(objects[i].Obj);
  290. }
  291. }
  292. // Check children
  293. if (children != null)
  294. {
  295. for (int i = 0; i < 8; i++)
  296. {
  297. children[i].GetColliding(ref checkRay, result, maxDistance);
  298. }
  299. }
  300. }
  301. public void GetWithinFrustum(Plane[] planes, System.Collections.Generic.List<T> result)
  302. {
  303. // Is the input node inside the frustum?
  304. if (!GeometryUtility.TestPlanesAABB(planes, bounds))
  305. {
  306. return;
  307. }
  308. // Check against any objects in this node
  309. for (int i = 0; i < objects.Count; i++)
  310. {
  311. if (GeometryUtility.TestPlanesAABB(planes, objects[i].Bounds))
  312. {
  313. result.Add(objects[i].Obj);
  314. }
  315. }
  316. // Check children
  317. if (children != null)
  318. {
  319. for (int i = 0; i < 8; i++)
  320. {
  321. children[i].GetWithinFrustum(planes, result);
  322. }
  323. }
  324. }
  325. /// <summary>
  326. /// Set the 8 children of this octree.
  327. /// </summary>
  328. /// <param name="childOctrees">The 8 new child nodes.</param>
  329. public void SetChildren(BoundsOctreeNode<T>[] childOctrees)
  330. {
  331. if (childOctrees.Length != 8)
  332. {
  333. Debug.LogError("Child octree array must be length 8. Was length: " + childOctrees.Length);
  334. return;
  335. }
  336. children = childOctrees;
  337. }
  338. public Bounds GetBounds()
  339. {
  340. return bounds;
  341. }
  342. /// <summary>
  343. /// We can shrink the octree if:
  344. /// - This node is >= double minLength in length
  345. /// - All objects in the root node are within one octant
  346. /// - This node doesn't have children, or does but 7/8 children are empty
  347. /// We can also shrink it if there are no objects left at all!
  348. /// </summary>
  349. /// <param name="minLength">Minimum dimensions of a node in this octree.</param>
  350. /// <returns>The new root, or the existing one if we didn't shrink.</returns>
  351. public BoundsOctreeNode<T> ShrinkIfPossible(float minLength)
  352. {
  353. if (BaseLength < (2 * minLength))
  354. {
  355. return this;
  356. }
  357. if (objects.Count == 0 && (children == null || children.Length == 0))
  358. {
  359. return this;
  360. }
  361. // Check objects in root
  362. int bestFit = -1;
  363. for (int i = 0; i < objects.Count; i++)
  364. {
  365. OctreeObject curObj = objects[i];
  366. int newBestFit = BestFitChild(curObj.Bounds.center);
  367. if (i == 0 || newBestFit == bestFit)
  368. {
  369. // In same octant as the other(s). Does it fit completely inside that octant?
  370. if (Encapsulates(childBounds[newBestFit], curObj.Bounds))
  371. {
  372. if (bestFit < 0)
  373. {
  374. bestFit = newBestFit;
  375. }
  376. }
  377. else
  378. {
  379. // Nope, so we can't reduce. Otherwise we continue
  380. return this;
  381. }
  382. }
  383. else
  384. {
  385. return this; // Can't reduce - objects fit in different octants
  386. }
  387. }
  388. // Check objects in children if there are any
  389. if (children != null)
  390. {
  391. bool childHadContent = false;
  392. for (int i = 0; i < children.Length; i++)
  393. {
  394. if (children[i].HasAnyObjects())
  395. {
  396. if (childHadContent)
  397. {
  398. return this; // Can't shrink - another child had content already
  399. }
  400. if (bestFit >= 0 && bestFit != i)
  401. {
  402. return this; // Can't reduce - objects in root are in a different octant to objects in child
  403. }
  404. childHadContent = true;
  405. bestFit = i;
  406. }
  407. }
  408. }
  409. // Can reduce
  410. if (children == null)
  411. {
  412. // We don't have any children, so just shrink this node to the new size
  413. // We already know that everything will still fit in it
  414. SetValues(BaseLength / 2, minSize, looseness, childBounds[bestFit].center);
  415. return this;
  416. }
  417. // No objects in entire octree
  418. if (bestFit == -1)
  419. {
  420. return this;
  421. }
  422. // We have children. Use the appropriate child as the new root node
  423. return children[bestFit];
  424. }
  425. /// <summary>
  426. /// Find which child node this object would be most likely to fit in.
  427. /// </summary>
  428. /// <param name="objBounds">The object's bounds.</param>
  429. /// <returns>One of the eight child octants.</returns>
  430. public int BestFitChild(Vector3 objBoundsCenter)
  431. {
  432. return (objBoundsCenter.x <= Center.x ? 0 : 1) + (objBoundsCenter.y >= Center.y ? 0 : 4) + (objBoundsCenter.z <= Center.z ? 0 : 2);
  433. }
  434. /// <summary>
  435. /// Checks if this node or anything below it has something in it.
  436. /// </summary>
  437. /// <returns>True if this node or any of its children, grandchildren etc have something in them</returns>
  438. public bool HasAnyObjects()
  439. {
  440. if (objects.Count > 0) return true;
  441. if (children != null)
  442. {
  443. for (int i = 0; i < 8; i++)
  444. {
  445. if (children[i].HasAnyObjects()) return true;
  446. }
  447. }
  448. return false;
  449. }
  450. // #### PRIVATE METHODS ####
  451. /// <summary>
  452. /// Set values for this node.
  453. /// </summary>
  454. /// <param name="baseLengthVal">Length of this node, not taking looseness into account.</param>
  455. /// <param name="minSizeVal">Minimum size of nodes in this octree.</param>
  456. /// <param name="loosenessVal">Multiplier for baseLengthVal to get the actual size.</param>
  457. /// <param name="centerVal">Centre position of this node.</param>
  458. void SetValues(float baseLengthVal, float minSizeVal, float loosenessVal, Vector3 centerVal)
  459. {
  460. BaseLength = baseLengthVal;
  461. minSize = minSizeVal;
  462. looseness = loosenessVal;
  463. Center = centerVal;
  464. adjLength = looseness * baseLengthVal;
  465. // Create the bounding box.
  466. Vector3 size = new Vector3(adjLength, adjLength, adjLength);
  467. bounds = new Bounds(Center, size);
  468. float quarter = BaseLength / 4f;
  469. float childActualLength = (BaseLength / 2) * looseness;
  470. Vector3 childActualSize = new Vector3(childActualLength, childActualLength, childActualLength);
  471. childBounds = new Bounds[8];
  472. childBounds[0] = new Bounds(Center + new Vector3(-quarter, quarter, -quarter), childActualSize);
  473. childBounds[1] = new Bounds(Center + new Vector3(quarter, quarter, -quarter), childActualSize);
  474. childBounds[2] = new Bounds(Center + new Vector3(-quarter, quarter, quarter), childActualSize);
  475. childBounds[3] = new Bounds(Center + new Vector3(quarter, quarter, quarter), childActualSize);
  476. childBounds[4] = new Bounds(Center + new Vector3(-quarter, -quarter, -quarter), childActualSize);
  477. childBounds[5] = new Bounds(Center + new Vector3(quarter, -quarter, -quarter), childActualSize);
  478. childBounds[6] = new Bounds(Center + new Vector3(-quarter, -quarter, quarter), childActualSize);
  479. childBounds[7] = new Bounds(Center + new Vector3(quarter, -quarter, quarter), childActualSize);
  480. }
  481. /// <summary>
  482. /// Private counterpart to the public Add method.
  483. /// </summary>
  484. /// <param name="obj">Object to add.</param>
  485. /// <param name="objBounds">3D bounding box around the object.</param>
  486. void SubAdd(T obj, Bounds objBounds)
  487. {
  488. // We know it fits at this level if we've got this far
  489. // We always put things in the deepest possible child
  490. // So we can skip some checks if there are children aleady
  491. if (!HasChildren)
  492. {
  493. // Just add if few objects are here, or children would be below min size
  494. if (objects.Count < NUM_OBJECTS_ALLOWED || (BaseLength / 2) < minSize)
  495. {
  496. OctreeObject newObj = new OctreeObject { Obj = obj, Bounds = objBounds };
  497. objects.Add(newObj);
  498. return; // We're done. No children yet
  499. }
  500. // Fits at this level, but we can go deeper. Would it fit there?
  501. // Create the 8 children
  502. int bestFitChild;
  503. if (children == null)
  504. {
  505. Split();
  506. if (children == null)
  507. {
  508. Debug.LogError("Child creation failed for an unknown reason. Early exit.");
  509. return;
  510. }
  511. // Now that we have the new children, see if this node's existing objects would fit there
  512. for (int i = objects.Count - 1; i >= 0; i--)
  513. {
  514. OctreeObject existingObj = objects[i];
  515. // Find which child the object is closest to based on where the
  516. // object's center is located in relation to the octree's center
  517. bestFitChild = BestFitChild(existingObj.Bounds.center);
  518. // Does it fit?
  519. if (Encapsulates(children[bestFitChild].bounds, existingObj.Bounds))
  520. {
  521. children[bestFitChild].SubAdd(existingObj.Obj, existingObj.Bounds); // Go a level deeper
  522. objects.Remove(existingObj); // Remove from here
  523. }
  524. }
  525. }
  526. }
  527. // Handle the new object we're adding now
  528. int bestFit = BestFitChild(objBounds.center);
  529. if (Encapsulates(children[bestFit].bounds, objBounds))
  530. {
  531. children[bestFit].SubAdd(obj, objBounds);
  532. }
  533. else
  534. {
  535. // Didn't fit in a child. We'll have to it to this node instead
  536. OctreeObject newObj = new OctreeObject { Obj = obj, Bounds = objBounds };
  537. objects.Add(newObj);
  538. }
  539. }
  540. /// <summary>
  541. /// Private counterpart to the public <see cref="Remove(T, Bounds)"/> method.
  542. /// </summary>
  543. /// <param name="obj">Object to remove.</param>
  544. /// <param name="objBounds">3D bounding box around the object.</param>
  545. /// <returns>True if the object was removed successfully.</returns>
  546. bool SubRemove(T obj, Bounds objBounds)
  547. {
  548. bool removed = false;
  549. for (int i = 0; i < objects.Count; i++)
  550. {
  551. if (objects[i].Obj.Equals(obj))
  552. {
  553. removed = objects.Remove(objects[i]);
  554. break;
  555. }
  556. }
  557. if (!removed && children != null)
  558. {
  559. int bestFitChild = BestFitChild(objBounds.center);
  560. removed = children[bestFitChild].SubRemove(obj, objBounds);
  561. }
  562. if (removed && children != null)
  563. {
  564. // Check if we should merge nodes now that we've removed an item
  565. if (ShouldMerge())
  566. {
  567. Merge();
  568. }
  569. }
  570. return removed;
  571. }
  572. /// <summary>
  573. /// Splits the octree into eight children.
  574. /// </summary>
  575. void Split()
  576. {
  577. float quarter = BaseLength / 4f;
  578. float newLength = BaseLength / 2;
  579. children = new BoundsOctreeNode<T>[8];
  580. children[0] = new BoundsOctreeNode<T>(newLength, minSize, looseness, Center + new Vector3(-quarter, quarter, -quarter));
  581. children[1] = new BoundsOctreeNode<T>(newLength, minSize, looseness, Center + new Vector3(quarter, quarter, -quarter));
  582. children[2] = new BoundsOctreeNode<T>(newLength, minSize, looseness, Center + new Vector3(-quarter, quarter, quarter));
  583. children[3] = new BoundsOctreeNode<T>(newLength, minSize, looseness, Center + new Vector3(quarter, quarter, quarter));
  584. children[4] = new BoundsOctreeNode<T>(newLength, minSize, looseness, Center + new Vector3(-quarter, -quarter, -quarter));
  585. children[5] = new BoundsOctreeNode<T>(newLength, minSize, looseness, Center + new Vector3(quarter, -quarter, -quarter));
  586. children[6] = new BoundsOctreeNode<T>(newLength, minSize, looseness, Center + new Vector3(-quarter, -quarter, quarter));
  587. children[7] = new BoundsOctreeNode<T>(newLength, minSize, looseness, Center + new Vector3(quarter, -quarter, quarter));
  588. }
  589. /// <summary>
  590. /// Merge all children into this node - the opposite of Split.
  591. /// Note: We only have to check one level down since a merge will never happen if the children already have children,
  592. /// since THAT won't happen unless there are already too many objects to merge.
  593. /// </summary>
  594. void Merge()
  595. {
  596. // Note: We know children != null or we wouldn't be merging
  597. for (int i = 0; i < 8; i++)
  598. {
  599. BoundsOctreeNode<T> curChild = children[i];
  600. int numObjects = curChild.objects.Count;
  601. for (int j = numObjects - 1; j >= 0; j--)
  602. {
  603. OctreeObject curObj = curChild.objects[j];
  604. objects.Add(curObj);
  605. }
  606. }
  607. // Remove the child nodes (and the objects in them - they've been added elsewhere now)
  608. children = null;
  609. }
  610. /// <summary>
  611. /// Checks if outerBounds encapsulates innerBounds.
  612. /// </summary>
  613. /// <param name="outerBounds">Outer bounds.</param>
  614. /// <param name="innerBounds">Inner bounds.</param>
  615. /// <returns>True if innerBounds is fully encapsulated by outerBounds.</returns>
  616. static bool Encapsulates(Bounds outerBounds, Bounds innerBounds)
  617. {
  618. return outerBounds.Contains(innerBounds.min) && outerBounds.Contains(innerBounds.max);
  619. }
  620. /// <summary>
  621. /// Checks if there are few enough objects in this node and its children that the children should all be merged into this.
  622. /// </summary>
  623. /// <returns>True there are less or the same abount of objects in this and its children than numObjectsAllowed.</returns>
  624. bool ShouldMerge()
  625. {
  626. int totalObjects = objects.Count;
  627. if (children != null)
  628. {
  629. foreach (BoundsOctreeNode<T> child in children)
  630. {
  631. if (child.children != null)
  632. {
  633. // If any of the *children* have children, there are definitely too many to merge,
  634. // or the child woudl have been merged already
  635. return false;
  636. }
  637. totalObjects += child.objects.Count;
  638. }
  639. }
  640. return totalObjects <= NUM_OBJECTS_ALLOWED;
  641. }
  642. }
  643. // A Dynamic, Loose Octree for storing any objects that can be described with AABB bounds
  644. // See also: PointOctree, where objects are stored as single points and some code can be simplified
  645. // Octree: An octree is a tree data structure which divides 3D space into smaller partitions (nodes)
  646. // and places objects into the appropriate nodes. This allows fast access to objects
  647. // in an area of interest without having to check every object.
  648. // Dynamic: The octree grows or shrinks as required when objects as added or removed
  649. // It also splits and merges nodes as appropriate. There is no maximum depth.
  650. // Nodes have a constant - numObjectsAllowed - which sets the amount of items allowed in a node before it splits.
  651. // Loose: The octree's nodes can be larger than 1/2 their parent's length and width, so they overlap to some extent.
  652. // This can alleviate the problem of even tiny objects ending up in large nodes if they're near boundaries.
  653. // A looseness value of 1.0 will make it a "normal" octree.
  654. // T: The content of the octree can be anything, since the bounds data is supplied separately.
  655. public class BoundsOctree<T>
  656. {
  657. // The total amount of objects currently in the tree
  658. public int Count { get; private set; }
  659. // Root node of the octree
  660. BoundsOctreeNode<T> rootNode;
  661. // Should be a value between 1 and 2. A multiplier for the base size of a node.
  662. // 1.0 is a "normal" octree, while values > 1 have overlap
  663. readonly float looseness;
  664. // Size that the octree was on creation
  665. readonly float initialSize;
  666. // Minimum side length that a node can be - essentially an alternative to having a max depth
  667. readonly float minSize;
  668. // For collision visualisation. Automatically removed in builds.
  669. /// <summary>
  670. /// Constructor for the bounds octree.
  671. /// </summary>
  672. /// <param name="initialWorldSize">Size of the sides of the initial node, in metres. The octree will never shrink smaller than this.</param>
  673. /// <param name="initialWorldPos">Position of the centre of the initial node.</param>
  674. /// <param name="minNodeSize">Nodes will stop splitting if the new nodes would be smaller than this (metres).</param>
  675. /// <param name="loosenessVal">Clamped between 1 and 2. Values > 1 let nodes overlap.</param>
  676. public BoundsOctree(float initialWorldSize, Vector3 initialWorldPos, float minNodeSize, float loosenessVal)
  677. {
  678. if (minNodeSize > initialWorldSize)
  679. {
  680. Debug.LogWarning("Minimum node size must be at least as big as the initial world size. Was: " + minNodeSize + " Adjusted to: " + initialWorldSize);
  681. minNodeSize = initialWorldSize;
  682. }
  683. Count = 0;
  684. initialSize = initialWorldSize;
  685. minSize = minNodeSize;
  686. looseness = Mathf.Clamp(loosenessVal, 1.0f, 2.0f);
  687. rootNode = new BoundsOctreeNode<T>(initialSize, minSize, looseness, initialWorldPos);
  688. }
  689. // #### PUBLIC METHODS ####
  690. /// <summary>
  691. /// Add an object.
  692. /// </summary>
  693. /// <param name="obj">Object to add.</param>
  694. /// <param name="objBounds">3D bounding box around the object.</param>
  695. public void Add(T obj, Bounds objBounds)
  696. {
  697. // Add object or expand the octree until it can be added
  698. int count = 0; // Safety check against infinite/excessive growth
  699. while (!rootNode.Add(obj, objBounds))
  700. {
  701. Grow(objBounds.center - rootNode.Center);
  702. if (++count > 20)
  703. {
  704. Debug.LogError("Aborted Add operation as it seemed to be going on forever (" + (count - 1) + ") attempts at growing the octree.");
  705. return;
  706. }
  707. }
  708. Count++;
  709. }
  710. /// <summary>
  711. /// Remove an object. Makes the assumption that the object only exists once in the tree.
  712. /// </summary>
  713. /// <param name="obj">Object to remove.</param>
  714. /// <returns>True if the object was removed successfully.</returns>
  715. public bool Remove(T obj)
  716. {
  717. bool removed = rootNode.Remove(obj);
  718. // See if we can shrink the octree down now that we've removed the item
  719. if (removed)
  720. {
  721. Count--;
  722. Shrink();
  723. }
  724. return removed;
  725. }
  726. /// <summary>
  727. /// Removes the specified object at the given position. Makes the assumption that the object only exists once in the tree.
  728. /// </summary>
  729. /// <param name="obj">Object to remove.</param>
  730. /// <param name="objBounds">3D bounding box around the object.</param>
  731. /// <returns>True if the object was removed successfully.</returns>
  732. public bool Remove(T obj, Bounds objBounds)
  733. {
  734. bool removed = rootNode.Remove(obj, objBounds);
  735. // See if we can shrink the octree down now that we've removed the item
  736. if (removed)
  737. {
  738. Count--;
  739. Shrink();
  740. }
  741. return removed;
  742. }
  743. /// <summary>
  744. /// Check if the specified bounds intersect with anything in the tree. See also: GetColliding.
  745. /// </summary>
  746. /// <param name="checkBounds">bounds to check.</param>
  747. /// <returns>True if there was a collision.</returns>
  748. public bool IsColliding(Bounds checkBounds)
  749. {
  750. return rootNode.IsColliding(ref checkBounds);
  751. }
  752. /// <summary>
  753. /// Check if the specified ray intersects with anything in the tree. See also: GetColliding.
  754. /// </summary>
  755. /// <param name="checkRay">ray to check.</param>
  756. /// <param name="maxDistance">distance to check.</param>
  757. /// <returns>True if there was a collision.</returns>
  758. public bool IsColliding(Ray checkRay, float maxDistance)
  759. {
  760. return rootNode.IsColliding(ref checkRay, maxDistance);
  761. }
  762. /// <summary>
  763. /// Returns an array of objects that intersect with the specified bounds, if any. Otherwise returns an empty array. See also: IsColliding.
  764. /// </summary>
  765. /// <param name="collidingWith">list to store intersections.</param>
  766. /// <param name="checkBounds">bounds to check.</param>
  767. /// <returns>Objects that intersect with the specified bounds.</returns>
  768. public void GetColliding(System.Collections.Generic.List<T> collidingWith, Bounds checkBounds)
  769. {
  770. rootNode.GetColliding(ref checkBounds, collidingWith);
  771. }
  772. public void GetColliding(Vector3 center, Vector3 localInnerRadius, Quaternion gridRotation, Quaternion objectRotation,
  773. System.Collections.Generic.List<T> result)
  774. {
  775. rootNode.GetColliding(center, localInnerRadius, gridRotation, objectRotation, result);
  776. }
  777. /// <summary>
  778. /// Returns an array of objects that intersect with the specified ray, if any. Otherwise returns an empty array. See also: IsColliding.
  779. /// </summary>
  780. /// <param name="collidingWith">list to store intersections.</param>
  781. /// <param name="checkRay">ray to check.</param>
  782. /// <param name="maxDistance">distance to check.</param>
  783. /// <returns>Objects that intersect with the specified ray.</returns>
  784. public void GetColliding(System.Collections.Generic.List<T> collidingWith, Ray checkRay, float maxDistance = float.PositiveInfinity)
  785. {
  786. rootNode.GetColliding(ref checkRay, collidingWith, maxDistance);
  787. }
  788. public System.Collections.Generic.List<T> GetWithinFrustum(Camera cam)
  789. {
  790. var planes = GeometryUtility.CalculateFrustumPlanes(cam);
  791. var list = new System.Collections.Generic.List<T>();
  792. rootNode.GetWithinFrustum(planes, list);
  793. return list;
  794. }
  795. public Bounds GetMaxBounds()
  796. {
  797. return rootNode.GetBounds();
  798. }
  799. // #### PRIVATE METHODS ####
  800. /// <summary>
  801. /// Grow the octree to fit in all objects.
  802. /// </summary>
  803. /// <param name="direction">Direction to grow.</param>
  804. void Grow(Vector3 direction)
  805. {
  806. int xDirection = direction.x >= 0 ? 1 : -1;
  807. int yDirection = direction.y >= 0 ? 1 : -1;
  808. int zDirection = direction.z >= 0 ? 1 : -1;
  809. BoundsOctreeNode<T> oldRoot = rootNode;
  810. float half = rootNode.BaseLength / 2;
  811. float newLength = rootNode.BaseLength * 2;
  812. Vector3 newCenter = rootNode.Center + new Vector3(xDirection * half, yDirection * half, zDirection * half);
  813. // Create a new, bigger octree root node
  814. rootNode = new BoundsOctreeNode<T>(newLength, minSize, looseness, newCenter);
  815. if (oldRoot.HasAnyObjects())
  816. {
  817. // Create 7 new octree children to go with the old root as children of the new root
  818. int rootPos = rootNode.BestFitChild(oldRoot.Center);
  819. BoundsOctreeNode<T>[] children = new BoundsOctreeNode<T>[8];
  820. for (int i = 0; i < 8; i++)
  821. {
  822. if (i == rootPos)
  823. {
  824. children[i] = oldRoot;
  825. }
  826. else
  827. {
  828. xDirection = i % 2 == 0 ? -1 : 1;
  829. yDirection = i > 3 ? -1 : 1;
  830. zDirection = (i < 2 || (i > 3 && i < 6)) ? -1 : 1;
  831. children[i] = new BoundsOctreeNode<T>(oldRoot.BaseLength, minSize, looseness, newCenter + new Vector3(xDirection * half, yDirection * half, zDirection * half));
  832. }
  833. }
  834. // Attach the new children to the new root node
  835. rootNode.SetChildren(children);
  836. }
  837. }
  838. /// <summary>
  839. /// Shrink the octree if possible, else leave it the same.
  840. /// </summary>
  841. void Shrink()
  842. {
  843. rootNode = rootNode.ShrinkIfPossible(initialSize);
  844. }
  845. }
  846. }