PointOctree.cs 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827
  1. /*
  2. Copyright (c) 2020 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 System.Linq;
  27. using UnityEngine;
  28. namespace PluginMaster
  29. {
  30. // A node in a PointOctree
  31. public class PointOctreeNode<T>
  32. {
  33. // Centre of this node
  34. public Vector3 Center { get; private set; }
  35. // Length of the sides of this node
  36. public float SideLength { get; private set; }
  37. // Minimum size for a node in this octree
  38. float minSize;
  39. // Bounding box that represents this node
  40. Bounds bounds = default(Bounds);
  41. // Objects in this node
  42. readonly System.Collections.Generic.List<OctreeObject> objects = new System.Collections.Generic.List<OctreeObject>();
  43. // Child nodes, if any
  44. PointOctreeNode<T>[] children = null;
  45. bool HasChildren { get { return children != null; } }
  46. // bounds of potential children to this node. These are actual size (with looseness taken into account), not base size
  47. Bounds[] childBounds;
  48. // If there are already NUM_OBJECTS_ALLOWED in a node, we split it into children
  49. // A generally good number seems to be something around 8-15
  50. const int NUM_OBJECTS_ALLOWED = 8;
  51. // For reverting the bounds size after temporary changes
  52. Vector3 actualBoundsSize;
  53. // An object in the octree
  54. class OctreeObject
  55. {
  56. public T Obj;
  57. public Vector3 Pos;
  58. }
  59. /// <summary>
  60. /// Constructor.
  61. /// </summary>
  62. /// <param name="baseLengthVal">Length of this node, not taking looseness into account.</param>
  63. /// <param name="minSizeVal">Minimum size of nodes in this octree.</param>
  64. /// <param name="centerVal">Centre position of this node.</param>
  65. public PointOctreeNode(float baseLengthVal, float minSizeVal, Vector3 centerVal)
  66. {
  67. SetValues(baseLengthVal, minSizeVal, centerVal);
  68. }
  69. // #### PUBLIC METHODS ####
  70. /// <summary>
  71. /// Add an object.
  72. /// </summary>
  73. /// <param name="obj">Object to add.</param>
  74. /// <param name="objPos">Position of the object.</param>
  75. /// <returns></returns>
  76. public bool Add(T obj, Vector3 objPos)
  77. {
  78. if (!Encapsulates(bounds, objPos))
  79. {
  80. return false;
  81. }
  82. SubAdd(obj, objPos);
  83. return true;
  84. }
  85. /// <summary>
  86. /// Remove an object. Makes the assumption that the object only exists once in the tree.
  87. /// </summary>
  88. /// <param name="obj">Object to remove.</param>
  89. /// <returns>True if the object was removed successfully.</returns>
  90. public bool Remove(T obj)
  91. {
  92. bool removed = false;
  93. for (int i = 0; i < objects.Count; i++)
  94. {
  95. if (objects[i].Obj.Equals(obj))
  96. {
  97. removed = objects.Remove(objects[i]);
  98. break;
  99. }
  100. }
  101. if (!removed && children != null)
  102. {
  103. for (int i = 0; i < 8; i++)
  104. {
  105. removed = children[i].Remove(obj);
  106. if (removed) break;
  107. }
  108. }
  109. if (removed && children != null)
  110. {
  111. // Check if we should merge nodes now that we've removed an item
  112. if (ShouldMerge())
  113. {
  114. Merge();
  115. }
  116. }
  117. return removed;
  118. }
  119. /// <summary>
  120. /// Removes the specified object at the given position.
  121. /// Makes the assumption that the object only exists once in the tree.
  122. /// </summary>
  123. /// <param name="obj">Object to remove.</param>
  124. /// <param name="objPos">Position of the object.</param>
  125. /// <returns>True if the object was removed successfully.</returns>
  126. public bool Remove(T obj, Vector3 objPos)
  127. {
  128. if (!Encapsulates(bounds, objPos))
  129. {
  130. return false;
  131. }
  132. return SubRemove(obj, objPos);
  133. }
  134. /// <summary>
  135. /// Return objects that are within maxDistance of the specified ray.
  136. /// </summary>
  137. /// <param name="ray">The ray.</param>
  138. /// <param name="maxDistance">Maximum distance from the ray to consider.</param>
  139. /// <param name="result">List result.</param>
  140. /// <returns>Objects within range.</returns>
  141. public void GetNearby(ref Ray ray, float maxDistance, System.Collections.Generic.List<T> result)
  142. {
  143. // Does the ray hit this node at all?
  144. // Note: Expanding the bounds is not exactly the same as a real distance check, but it's fast.
  145. bounds.Expand(new Vector3(maxDistance * 2, maxDistance * 2, maxDistance * 2));
  146. bool intersected = bounds.IntersectRay(ray);
  147. bounds.size = actualBoundsSize;
  148. if (!intersected)
  149. {
  150. return;
  151. }
  152. // Check against any objects in this node
  153. for (int i = 0; i < objects.Count; i++)
  154. {
  155. if (objects[i].Obj != null && SqrDistanceToRay(ray, objects[i].Pos) <= (maxDistance * maxDistance))
  156. {
  157. result.Add(objects[i].Obj);
  158. }
  159. }
  160. // Check children
  161. if (children != null)
  162. {
  163. for (int i = 0; i < 8; i++)
  164. {
  165. children[i].GetNearby(ref ray, maxDistance, result);
  166. }
  167. }
  168. }
  169. /// <summary>
  170. /// Return objects that are within <paramref name="maxDistance"/> of the specified position.
  171. /// </summary>
  172. /// <param name="position">The position.</param>
  173. /// <param name="maxDistance">Maximum distance from the position to consider.</param>
  174. /// <param name="result">List result.</param>
  175. /// <returns>Objects within range.</returns>
  176. public void GetNearby(ref Vector3 position, float maxDistance, System.Collections.Generic.List<T> result)
  177. {
  178. float sqrMaxDistance = maxDistance * maxDistance;
  179. // Does the node intersect with the sphere of center = position and radius = maxDistance?
  180. if ((bounds.ClosestPoint(position) - position).sqrMagnitude > sqrMaxDistance)
  181. {
  182. return;
  183. }
  184. // Check against any objects in this node
  185. for (int i = 0; i < objects.Count; i++)
  186. {
  187. if ((position - objects[i].Pos).sqrMagnitude <= sqrMaxDistance)
  188. {
  189. result.Add(objects[i].Obj);
  190. }
  191. }
  192. // Check children
  193. if (children != null)
  194. {
  195. for (int i = 0; i < 8; i++)
  196. {
  197. children[i].GetNearby(ref position, maxDistance, result);
  198. }
  199. }
  200. }
  201. /// <summary>
  202. /// Return all objects in the tree.
  203. /// </summary>
  204. /// <returns>All objects.</returns>
  205. public void GetAll(System.Collections.Generic.List<T> result)
  206. {
  207. // add directly contained objects
  208. result.AddRange(objects.Select(o => o.Obj));
  209. // add children objects
  210. if (children != null)
  211. {
  212. for (int i = 0; i < 8; i++)
  213. {
  214. children[i].GetAll(result);
  215. }
  216. }
  217. }
  218. /// <summary>
  219. /// Set the 8 children of this octree.
  220. /// </summary>
  221. /// <param name="childOctrees">The 8 new child nodes.</param>
  222. public void SetChildren(PointOctreeNode<T>[] childOctrees)
  223. {
  224. if (childOctrees.Length != 8)
  225. {
  226. Debug.LogError("Child octree array must be length 8. Was length: " + childOctrees.Length);
  227. return;
  228. }
  229. children = childOctrees;
  230. }
  231. /// <summary>
  232. /// We can shrink the octree if:
  233. /// - This node is >= double minLength in length
  234. /// - All objects in the root node are within one octant
  235. /// - This node doesn't have children, or does but 7/8 children are empty
  236. /// We can also shrink it if there are no objects left at all!
  237. /// </summary>
  238. /// <param name="minLength">Minimum dimensions of a node in this octree.</param>
  239. /// <returns>The new root, or the existing one if we didn't shrink.</returns>
  240. public PointOctreeNode<T> ShrinkIfPossible(float minLength)
  241. {
  242. if (SideLength < (2 * minLength))
  243. {
  244. return this;
  245. }
  246. if (objects.Count == 0 && (children == null || children.Length == 0))
  247. {
  248. return this;
  249. }
  250. // Check objects in root
  251. int bestFit = -1;
  252. for (int i = 0; i < objects.Count; i++)
  253. {
  254. OctreeObject curObj = objects[i];
  255. int newBestFit = BestFitChild(curObj.Pos);
  256. if (i == 0 || newBestFit == bestFit)
  257. {
  258. if (bestFit < 0)
  259. {
  260. bestFit = newBestFit;
  261. }
  262. }
  263. else
  264. {
  265. return this; // Can't reduce - objects fit in different octants
  266. }
  267. }
  268. // Check objects in children if there are any
  269. if (children != null)
  270. {
  271. bool childHadContent = false;
  272. for (int i = 0; i < children.Length; i++)
  273. {
  274. if (children[i].HasAnyObjects())
  275. {
  276. if (childHadContent)
  277. {
  278. return this; // Can't shrink - another child had content already
  279. }
  280. if (bestFit >= 0 && bestFit != i)
  281. {
  282. return this; // Can't reduce - objects in root are in a different octant to objects in child
  283. }
  284. childHadContent = true;
  285. bestFit = i;
  286. }
  287. }
  288. }
  289. // Can reduce
  290. if (children == null)
  291. {
  292. // We don't have any children, so just shrink this node to the new size
  293. // We already know that everything will still fit in it
  294. SetValues(SideLength / 2, minSize, childBounds[bestFit].center);
  295. return this;
  296. }
  297. // We have children. Use the appropriate child as the new root node
  298. return children[bestFit];
  299. }
  300. /// <summary>
  301. /// Find which child node this object would be most likely to fit in.
  302. /// </summary>
  303. /// <param name="objPos">The object's position.</param>
  304. /// <returns>One of the eight child octants.</returns>
  305. public int BestFitChild(Vector3 objPos)
  306. {
  307. return (objPos.x <= Center.x ? 0 : 1) + (objPos.y >= Center.y ? 0 : 4) + (objPos.z <= Center.z ? 0 : 2);
  308. }
  309. /// <summary>
  310. /// Checks if this node or anything below it has something in it.
  311. /// </summary>
  312. /// <returns>True if this node or any of its children, grandchildren etc have something in them</returns>
  313. public bool HasAnyObjects()
  314. {
  315. if (objects.Count > 0) return true;
  316. if (children != null)
  317. {
  318. for (int i = 0; i < 8; i++)
  319. {
  320. if (children[i].HasAnyObjects()) return true;
  321. }
  322. }
  323. return false;
  324. }
  325. // #### PRIVATE METHODS ####
  326. /// <summary>
  327. /// Set values for this node.
  328. /// </summary>
  329. /// <param name="baseLengthVal">Length of this node, not taking looseness into account.</param>
  330. /// <param name="minSizeVal">Minimum size of nodes in this octree.</param>
  331. /// <param name="centerVal">Centre position of this node.</param>
  332. void SetValues(float baseLengthVal, float minSizeVal, Vector3 centerVal)
  333. {
  334. SideLength = baseLengthVal;
  335. minSize = minSizeVal;
  336. Center = centerVal;
  337. // Create the bounding box.
  338. actualBoundsSize = new Vector3(SideLength, SideLength, SideLength);
  339. bounds = new Bounds(Center, actualBoundsSize);
  340. float quarter = SideLength / 4f;
  341. float childActualLength = SideLength / 2;
  342. Vector3 childActualSize = new Vector3(childActualLength, childActualLength, childActualLength);
  343. childBounds = new Bounds[8];
  344. childBounds[0] = new Bounds(Center + new Vector3(-quarter, quarter, -quarter), childActualSize);
  345. childBounds[1] = new Bounds(Center + new Vector3(quarter, quarter, -quarter), childActualSize);
  346. childBounds[2] = new Bounds(Center + new Vector3(-quarter, quarter, quarter), childActualSize);
  347. childBounds[3] = new Bounds(Center + new Vector3(quarter, quarter, quarter), childActualSize);
  348. childBounds[4] = new Bounds(Center + new Vector3(-quarter, -quarter, -quarter), childActualSize);
  349. childBounds[5] = new Bounds(Center + new Vector3(quarter, -quarter, -quarter), childActualSize);
  350. childBounds[6] = new Bounds(Center + new Vector3(-quarter, -quarter, quarter), childActualSize);
  351. childBounds[7] = new Bounds(Center + new Vector3(quarter, -quarter, quarter), childActualSize);
  352. }
  353. /// <summary>
  354. /// Private counterpart to the public Add method.
  355. /// </summary>
  356. /// <param name="obj">Object to add.</param>
  357. /// <param name="objPos">Position of the object.</param>
  358. void SubAdd(T obj, Vector3 objPos)
  359. {
  360. // We know it fits at this level if we've got this far
  361. // We always put things in the deepest possible child
  362. // So we can skip checks and simply move down if there are children aleady
  363. if (!HasChildren)
  364. {
  365. // Just add if few objects are here, or children would be below min size
  366. if (objects.Count < NUM_OBJECTS_ALLOWED || (SideLength / 2) < minSize)
  367. {
  368. OctreeObject newObj = new OctreeObject { Obj = obj, Pos = objPos };
  369. objects.Add(newObj);
  370. return; // We're done. No children yet
  371. }
  372. // Enough objects in this node already: Create the 8 children
  373. int bestFitChild;
  374. if (children == null)
  375. {
  376. Split();
  377. if (children == null)
  378. {
  379. Debug.LogError("Child creation failed for an unknown reason. Early exit.");
  380. return;
  381. }
  382. // Now that we have the new children, move this node's existing objects into them
  383. for (int i = objects.Count - 1; i >= 0; i--)
  384. {
  385. OctreeObject existingObj = objects[i];
  386. // Find which child the object is closest to based on where the
  387. // object's center is located in relation to the octree's center
  388. bestFitChild = BestFitChild(existingObj.Pos);
  389. children[bestFitChild].SubAdd(existingObj.Obj, existingObj.Pos); // Go a level deeper
  390. objects.Remove(existingObj); // Remove from here
  391. }
  392. }
  393. }
  394. // Handle the new object we're adding now
  395. int bestFit = BestFitChild(objPos);
  396. children[bestFit].SubAdd(obj, objPos);
  397. }
  398. /// <summary>
  399. /// Private counterpart to the public <see cref="Remove(T, Vector3)"/> method.
  400. /// </summary>
  401. /// <param name="obj">Object to remove.</param>
  402. /// <param name="objPos">Position of the object.</param>
  403. /// <returns>True if the object was removed successfully.</returns>
  404. bool SubRemove(T obj, Vector3 objPos)
  405. {
  406. bool removed = false;
  407. for (int i = 0; i < objects.Count; i++)
  408. {
  409. if (objects[i].Obj.Equals(obj))
  410. {
  411. removed = objects.Remove(objects[i]);
  412. break;
  413. }
  414. }
  415. if (!removed && children != null)
  416. {
  417. int bestFitChild = BestFitChild(objPos);
  418. removed = children[bestFitChild].SubRemove(obj, objPos);
  419. }
  420. if (removed && children != null)
  421. {
  422. // Check if we should merge nodes now that we've removed an item
  423. if (ShouldMerge())
  424. {
  425. Merge();
  426. }
  427. }
  428. return removed;
  429. }
  430. /// <summary>
  431. /// Splits the octree into eight children.
  432. /// </summary>
  433. void Split()
  434. {
  435. float quarter = SideLength / 4f;
  436. float newLength = SideLength / 2;
  437. children = new PointOctreeNode<T>[8];
  438. children[0] = new PointOctreeNode<T>(newLength, minSize, Center + new Vector3(-quarter, quarter, -quarter));
  439. children[1] = new PointOctreeNode<T>(newLength, minSize, Center + new Vector3(quarter, quarter, -quarter));
  440. children[2] = new PointOctreeNode<T>(newLength, minSize, Center + new Vector3(-quarter, quarter, quarter));
  441. children[3] = new PointOctreeNode<T>(newLength, minSize, Center + new Vector3(quarter, quarter, quarter));
  442. children[4] = new PointOctreeNode<T>(newLength, minSize, Center + new Vector3(-quarter, -quarter, -quarter));
  443. children[5] = new PointOctreeNode<T>(newLength, minSize, Center + new Vector3(quarter, -quarter, -quarter));
  444. children[6] = new PointOctreeNode<T>(newLength, minSize, Center + new Vector3(-quarter, -quarter, quarter));
  445. children[7] = new PointOctreeNode<T>(newLength, minSize, Center + new Vector3(quarter, -quarter, quarter));
  446. }
  447. /// <summary>
  448. /// Merge all children into this node - the opposite of Split.
  449. /// Note: We only have to check one level down since a merge will never happen if the children already have children,
  450. /// since THAT won't happen unless there are already too many objects to merge.
  451. /// </summary>
  452. void Merge()
  453. {
  454. // Note: We know children != null or we wouldn't be merging
  455. for (int i = 0; i < 8; i++)
  456. {
  457. PointOctreeNode<T> curChild = children[i];
  458. int numObjects = curChild.objects.Count;
  459. for (int j = numObjects - 1; j >= 0; j--)
  460. {
  461. OctreeObject curObj = curChild.objects[j];
  462. objects.Add(curObj);
  463. }
  464. }
  465. // Remove the child nodes (and the objects in them - they've been added elsewhere now)
  466. children = null;
  467. }
  468. /// <summary>
  469. /// Checks if outerBounds encapsulates the given point.
  470. /// </summary>
  471. /// <param name="outerBounds">Outer bounds.</param>
  472. /// <param name="point">Point.</param>
  473. /// <returns>True if innerBounds is fully encapsulated by outerBounds.</returns>
  474. static bool Encapsulates(Bounds outerBounds, Vector3 point)
  475. {
  476. return outerBounds.Contains(point);
  477. }
  478. /// <summary>
  479. /// Checks if there are few enough objects in this node and its children
  480. /// that the children should all be merged into this.
  481. /// </summary>
  482. /// <returns>True there are less or the same abount of objects in this and its children than numObjectsAllowed.
  483. /// </returns>
  484. bool ShouldMerge()
  485. {
  486. int totalObjects = objects.Count;
  487. if (children != null)
  488. {
  489. foreach (PointOctreeNode<T> child in children)
  490. {
  491. if (child.children != null)
  492. {
  493. // If any of the *children* have children, there are definitely too many to merge,
  494. // or the child woudl have been merged already
  495. return false;
  496. }
  497. totalObjects += child.objects.Count;
  498. }
  499. }
  500. return totalObjects <= NUM_OBJECTS_ALLOWED;
  501. }
  502. /// <summary>
  503. /// Returns the closest distance to the given ray from a point.
  504. /// </summary>
  505. /// <param name="ray">The ray.</param>
  506. /// <param name="point">The point to check distance from the ray.</param>
  507. /// <returns>Squared distance from the point to the closest point of the ray.</returns>
  508. public static float SqrDistanceToRay(Ray ray, Vector3 point)
  509. {
  510. return Vector3.Cross(ray.direction, point - ray.origin).sqrMagnitude;
  511. }
  512. }
  513. // A Dynamic Octree for storing any objects that can be described as a single point
  514. // See also: BoundsOctree, where objects are described by AABB bounds
  515. // Octree: An octree is a tree data structure which divides 3D space into smaller partitions (nodes)
  516. // and places objects into the appropriate nodes. This allows fast access to objects
  517. // in an area of interest without having to check every object.
  518. // Dynamic: The octree grows or shrinks as required when objects as added or removed
  519. // It also splits and merges nodes as appropriate. There is no maximum depth.
  520. // Nodes have a constant - numObjectsAllowed - which sets the amount of items allowed in a node before it splits.
  521. // T: The content of the octree can be anything, since the bounds data is supplied separately.
  522. public class PointOctree<T>
  523. {
  524. // The total amount of objects currently in the tree
  525. public int Count { get; private set; }
  526. // Root node of the octree
  527. PointOctreeNode<T> rootNode;
  528. // Size that the octree was on creation
  529. readonly float initialSize;
  530. // Minimum side length that a node can be - essentially an alternative to having a max depth
  531. readonly float minSize;
  532. private System.Collections.Generic.HashSet<int> _allObjectIds = new System.Collections.Generic.HashSet<int>();
  533. public bool Contains(int objId) => _allObjectIds.Contains(objId);
  534. /// <summary>
  535. /// Constructor for the point octree.
  536. /// </summary>
  537. /// <param name="initialWorldSize">Size of the sides of the initial node.
  538. /// The octree will never shrink smaller than this.</param>
  539. /// <param name="initialWorldPos">Position of the centre of the initial node.</param>
  540. /// <param name="minNodeSize">Nodes will stop splitting if the new nodes would be smaller than this.</param>
  541. public PointOctree(float initialWorldSize, Vector3 initialWorldPos, float minNodeSize)
  542. {
  543. if (minNodeSize > initialWorldSize)
  544. {
  545. Debug.LogWarning("Minimum node size must be at least as big as the initial world size. Was: "
  546. + minNodeSize + " Adjusted to: " + initialWorldSize);
  547. minNodeSize = initialWorldSize;
  548. }
  549. Count = 0;
  550. initialSize = initialWorldSize;
  551. minSize = minNodeSize;
  552. rootNode = new PointOctreeNode<T>(initialSize, minSize, initialWorldPos);
  553. }
  554. // #### PUBLIC METHODS ####
  555. /// <summary>
  556. /// Add an object.
  557. /// </summary>
  558. /// <param name="obj">Object to add.</param>
  559. /// <param name="objPos">Position of the object.</param>
  560. public void Add(T obj, Vector3 objPos)
  561. {
  562. if (obj is GameObject)
  563. {
  564. var gameObj = obj as GameObject;
  565. var allChildren = gameObj.GetComponentsInChildren<Transform>();
  566. foreach (var child in allChildren)
  567. {
  568. if (!_allObjectIds.Contains(child.gameObject.GetInstanceID()))
  569. _allObjectIds.Add(child.gameObject.GetInstanceID());
  570. }
  571. }
  572. // Add object or expand the octree until it can be added
  573. int count = 0; // Safety check against infinite/excessive growth
  574. while (!rootNode.Add(obj, objPos))
  575. {
  576. Grow(objPos - rootNode.Center);
  577. if (++count > 20)
  578. {
  579. Debug.LogError("Aborted Add operation as it seemed to be going on forever ("
  580. + (count - 1) + ") attempts at growing the octree.");
  581. return;
  582. }
  583. }
  584. Count++;
  585. }
  586. /// <summary>
  587. /// Remove an object. Makes the assumption that the object only exists once in the tree.
  588. /// </summary>
  589. /// <param name="obj">Object to remove.</param>
  590. /// <returns>True if the object was removed successfully.</returns>
  591. public bool Remove(T obj)
  592. {
  593. bool removed = rootNode.Remove(obj);
  594. // See if we can shrink the octree down now that we've removed the item
  595. if (removed)
  596. {
  597. Count--;
  598. Shrink();
  599. }
  600. return removed;
  601. }
  602. /// <summary>
  603. /// Removes the specified object at the given position.
  604. /// Makes the assumption that the object only exists once in the tree.
  605. /// </summary>
  606. /// <param name="obj">Object to remove.</param>
  607. /// <param name="objPos">Position of the object.</param>
  608. /// <returns>True if the object was removed successfully.</returns>
  609. public bool Remove(T obj, Vector3 objPos)
  610. {
  611. bool removed = rootNode.Remove(obj, objPos);
  612. // See if we can shrink the octree down now that we've removed the item
  613. if (removed)
  614. {
  615. Count--;
  616. Shrink();
  617. }
  618. return removed;
  619. }
  620. /// <summary>
  621. /// Returns objects that are within <paramref name="maxDistance"/> of the specified ray.
  622. /// If none, returns false. Uses supplied list for results.
  623. /// </summary>
  624. /// <param name="ray">The ray. Passing as ref to improve performance since it won't have to be copied.</param>
  625. /// <param name="maxDistance">Maximum distance from the ray to consider</param>
  626. /// <param name="nearBy">Pre-initialized list to populate</param>
  627. /// <returns>True if items are found, false if not</returns>
  628. public bool GetNearbyNonAlloc(Ray ray, float maxDistance, System.Collections.Generic.List<T> nearBy)
  629. {
  630. nearBy.Clear();
  631. rootNode.GetNearby(ref ray, maxDistance, nearBy);
  632. if (nearBy.Count > 0)
  633. return true;
  634. return false;
  635. }
  636. /// <summary>
  637. /// Returns objects that are within <paramref name="maxDistance"/> of the specified ray.
  638. /// If none, returns an empty array (not null).
  639. /// </summary>
  640. /// <param name="ray">The ray. Passing as ref to improve performance since it won't have to be copied.</param>
  641. /// <param name="maxDistance">Maximum distance from the ray to consider.</param>
  642. /// <returns>Objects within range.</returns>
  643. public T[] GetNearby(Ray ray, float maxDistance)
  644. {
  645. var collidingWith = new System.Collections.Generic.List<T>();
  646. rootNode.GetNearby(ref ray, maxDistance, collidingWith);
  647. return collidingWith.Where(o => o != null).ToArray();
  648. }
  649. /// <summary>
  650. /// Returns objects that are within <paramref name="maxDistance"/> of the specified position.
  651. /// If none, returns an empty array (not null).
  652. /// </summary>
  653. /// <param name="position">The position. Passing as ref to improve performance since it won't have to be copied.
  654. /// </param>
  655. /// <param name="maxDistance">Maximum distance from the position to consider.</param>
  656. /// <returns>Objects within range.</returns>
  657. public T[] GetNearby(Vector3 position, float maxDistance)
  658. {
  659. var collidingWith = new System.Collections.Generic.List<T>();
  660. rootNode.GetNearby(ref position, maxDistance, collidingWith);
  661. return collidingWith.ToArray();
  662. }
  663. /// <summary>
  664. /// Returns objects that are within <paramref name="maxDistance"/> of the specified position.
  665. /// If none, returns false. Uses supplied list for results.
  666. /// </summary>
  667. /// <param name="maxDistance">Maximum distance from the position to consider</param>
  668. /// <param name="nearBy">Pre-initialized list to populate</param>
  669. /// <returns>True if items are found, false if not</returns>
  670. public bool GetNearbyNonAlloc(Vector3 position, float maxDistance, System.Collections.Generic.List<T> nearBy)
  671. {
  672. nearBy.Clear();
  673. rootNode.GetNearby(ref position, maxDistance, nearBy);
  674. if (nearBy.Count > 0)
  675. return true;
  676. return false;
  677. }
  678. /// <summary>
  679. /// Return all objects in the tree.
  680. /// If none, returns an empty array (not null).
  681. /// </summary>
  682. /// <returns>All objects.</returns>
  683. public System.Collections.Generic.ICollection<T> GetAll()
  684. {
  685. var objects = new System.Collections.Generic.List<T>(Count);
  686. rootNode.GetAll(objects);
  687. return objects;
  688. }
  689. // #### PRIVATE METHODS ####
  690. /// <summary>
  691. /// Grow the octree to fit in all objects.
  692. /// </summary>
  693. /// <param name="direction">Direction to grow.</param>
  694. void Grow(Vector3 direction)
  695. {
  696. int xDirection = direction.x >= 0 ? 1 : -1;
  697. int yDirection = direction.y >= 0 ? 1 : -1;
  698. int zDirection = direction.z >= 0 ? 1 : -1;
  699. PointOctreeNode<T> oldRoot = rootNode;
  700. float half = rootNode.SideLength / 2;
  701. float newLength = rootNode.SideLength * 2;
  702. Vector3 newCenter = rootNode.Center + new Vector3(xDirection * half, yDirection * half, zDirection * half);
  703. // Create a new, bigger octree root node
  704. rootNode = new PointOctreeNode<T>(newLength, minSize, newCenter);
  705. if (oldRoot.HasAnyObjects())
  706. {
  707. // Create 7 new octree children to go with the old root as children of the new root
  708. int rootPos = rootNode.BestFitChild(oldRoot.Center);
  709. PointOctreeNode<T>[] children = new PointOctreeNode<T>[8];
  710. for (int i = 0; i < 8; i++)
  711. {
  712. if (i == rootPos)
  713. {
  714. children[i] = oldRoot;
  715. }
  716. else
  717. {
  718. xDirection = i % 2 == 0 ? -1 : 1;
  719. yDirection = i > 3 ? -1 : 1;
  720. zDirection = (i < 2 || (i > 3 && i < 6)) ? -1 : 1;
  721. children[i] = new PointOctreeNode<T>(oldRoot.SideLength, minSize,
  722. newCenter + new Vector3(xDirection * half, yDirection * half, zDirection * half));
  723. }
  724. }
  725. // Attach the new children to the new root node
  726. rootNode.SetChildren(children);
  727. }
  728. }
  729. /// <summary>
  730. /// Shrink the octree if possible, else leave it the same.
  731. /// </summary>
  732. void Shrink()
  733. {
  734. rootNode = rootNode.ShrinkIfPossible(initialSize);
  735. }
  736. }
  737. }