123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953 |
- /*
- Copyright (c) 2023 Omar Duarte
- Unauthorized copying of this file, via any medium is strictly prohibited.
- Modified by Omar Duarte, 2020.
- This file incorporates work covered by the following copyright and
- permission notice:
- Copyright (c) 2014, Nition, BSD licence. All rights reserved.
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions are met:
- * Redistributions of source code must retain the above copyright notice, this
- list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above copyright notice,
- this list of conditions and the following disclaimer in the documentation
- and/or other materials provided with the distribution.
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
- using UnityEngine;
- namespace PluginMaster
- {
- // A node in a BoundsOctree
- public class BoundsOctreeNode<T>
- {
- // Centre of this node
- public Vector3 Center { get; private set; }
- // Length of this node if it has a looseness of 1.0
- public float BaseLength { get; private set; }
- // Looseness value for this node
- float looseness;
- // Minimum size for a node in this octree
- float minSize;
- // Actual length of sides, taking the looseness value into account
- float adjLength;
- // Bounding box that represents this node
- Bounds bounds = default(Bounds);
- // Objects in this node
- readonly System.Collections.Generic.List<OctreeObject> objects = new System.Collections.Generic.List<OctreeObject>();
- // Child nodes, if any
- BoundsOctreeNode<T>[] children = null;
- bool HasChildren { get { return children != null; } }
- // Bounds of potential children to this node. These are actual size (with looseness taken into account), not base size
- Bounds[] childBounds;
- // If there are already NUM_OBJECTS_ALLOWED in a node, we split it into children
- // A generally good number seems to be something around 8-15
- const int NUM_OBJECTS_ALLOWED = 8;
- // An object in the octree
- struct OctreeObject
- {
- public T Obj;
- public Bounds Bounds;
- }
- /// <summary>
- /// Constructor.
- /// </summary>
- /// <param name="baseLengthVal">Length of this node, not taking looseness into account.</param>
- /// <param name="minSizeVal">Minimum size of nodes in this octree.</param>
- /// <param name="loosenessVal">Multiplier for baseLengthVal to get the actual size.</param>
- /// <param name="centerVal">Centre position of this node.</param>
- public BoundsOctreeNode(float baseLengthVal, float minSizeVal, float loosenessVal, Vector3 centerVal)
- {
- SetValues(baseLengthVal, minSizeVal, loosenessVal, centerVal);
- }
- // #### PUBLIC METHODS ####
- /// <summary>
- /// Add an object.
- /// </summary>
- /// <param name="obj">Object to add.</param>
- /// <param name="objBounds">3D bounding box around the object.</param>
- /// <returns>True if the object fits entirely within this node.</returns>
- public bool Add(T obj, Bounds objBounds)
- {
- if (!Encapsulates(bounds, objBounds))
- {
- return false;
- }
- SubAdd(obj, objBounds);
- return true;
- }
- /// <summary>
- /// Remove an object. Makes the assumption that the object only exists once in the tree.
- /// </summary>
- /// <param name="obj">Object to remove.</param>
- /// <returns>True if the object was removed successfully.</returns>
- public bool Remove(T obj)
- {
- bool removed = false;
- for (int i = 0; i < objects.Count; i++)
- {
- if (objects[i].Obj.Equals(obj))
- {
- removed = objects.Remove(objects[i]);
- break;
- }
- }
- if (!removed && children != null)
- {
- for (int i = 0; i < 8; i++)
- {
- removed = children[i].Remove(obj);
- if (removed) break;
- }
- }
- if (removed && children != null)
- {
- // Check if we should merge nodes now that we've removed an item
- if (ShouldMerge())
- {
- Merge();
- }
- }
- return removed;
- }
- /// <summary>
- /// Removes the specified object at the given position. Makes the assumption that the object only exists once in the tree.
- /// </summary>
- /// <param name="obj">Object to remove.</param>
- /// <param name="objBounds">3D bounding box around the object.</param>
- /// <returns>True if the object was removed successfully.</returns>
- public bool Remove(T obj, Bounds objBounds)
- {
- if (!Encapsulates(bounds, objBounds))
- {
- return false;
- }
- return SubRemove(obj, objBounds);
- }
- /// <summary>
- /// Check if the specified bounds intersect with anything in the tree. See also: GetColliding.
- /// </summary>
- /// <param name="checkBounds">Bounds to check.</param>
- /// <returns>True if there was a collision.</returns>
- public bool IsColliding(ref Bounds checkBounds)
- {
- // Are the input bounds at least partially in this node?
- if (!bounds.Intersects(checkBounds))
- {
- return false;
- }
- // Check against any objects in this node
- for (int i = 0; i < objects.Count; i++)
- {
- if (objects[i].Bounds.Intersects(checkBounds))
- {
- return true;
- }
- }
- // Check children
- if (children != null)
- {
- for (int i = 0; i < 8; i++)
- {
- if (children[i].IsColliding(ref checkBounds))
- {
- return true;
- }
- }
- }
- return false;
- }
- /// <summary>
- /// Check if the specified ray intersects with anything in the tree. See also: GetColliding.
- /// </summary>
- /// <param name="checkRay">Ray to check.</param>
- /// <param name="maxDistance">Distance to check.</param>
- /// <returns>True if there was a collision.</returns>
- public bool IsColliding(ref Ray checkRay, float maxDistance = float.PositiveInfinity)
- {
- // Is the input ray at least partially in this node?
- float distance;
- if (!bounds.IntersectRay(checkRay, out distance) || distance > maxDistance)
- {
- return false;
- }
- // Check against any objects in this node
- for (int i = 0; i < objects.Count; i++)
- {
- if (objects[i].Bounds.IntersectRay(checkRay, out distance) && distance <= maxDistance)
- {
- return true;
- }
- }
- // Check children
- if (children != null)
- {
- for (int i = 0; i < 8; i++)
- {
- if (children[i].IsColliding(ref checkRay, maxDistance))
- {
- return true;
- }
- }
- }
- return false;
- }
- /// <summary>
- /// Returns an array of objects that intersect with the specified bounds, if any. Otherwise returns an empty array. See also: IsColliding.
- /// </summary>
- /// <param name="checkBounds">Bounds to check. Passing by ref as it improves performance with structs.</param>
- /// <param name="result">List result.</param>
- /// <returns>Objects that intersect with the specified bounds.</returns>
- public void GetColliding(ref Bounds checkBounds, System.Collections.Generic.List<T> result)
- {
- // Are the input bounds at least partially in this node?
- if (!bounds.Intersects(checkBounds))
- {
- return;
- }
- // Check against any objects in this node
- for (int i = 0; i < objects.Count; i++)
- {
- if (objects[i].Bounds.Intersects(checkBounds))
- {
- result.Add(objects[i].Obj);
- }
- }
- // Check children
- if (children != null)
- {
- for (int i = 0; i < 8; i++)
- {
- children[i].GetColliding(ref checkBounds, result);
- }
- }
- }
- public void GetColliding(Vector3 center, Vector3 localInnerRadius,
- Quaternion gridRotation, Quaternion objectRotation, System.Collections.Generic.List<T> result)
- {
- var checkSize = localInnerRadius * 1.9999f;
- var checkBounds = new Bounds(center, checkSize);
- if (!bounds.Intersects(checkBounds))
- {
- return;
- }
- var nullObjectsIndexes = new System.Collections.Generic.List<int>();
- // Check against any objects in this node
- for (int i = 0; i < objects.Count; i++)
- {
- var octreeObj = objects[i];
- if (octreeObj.Obj == null)
- {
- nullObjectsIndexes.Insert(0, i);
- continue;
- }
- var objCenter = octreeObj.Bounds.center;
- var fromTargetToObj = objCenter - center;
- var rotatedCellCenter = center + Quaternion.Inverse(gridRotation) * fromTargetToObj;
- var rotatedBounds = new Bounds(rotatedCellCenter, octreeObj.Bounds.size);
- if (rotatedBounds.Intersects(checkBounds))
- result.Add(objects[i].Obj);
- }
- foreach(var i in nullObjectsIndexes) objects.RemoveAt(i);
- // Check children
- if (children != null)
- {
- for (int i = 0; i < 8; i++)
- {
- children[i].GetColliding(center, localInnerRadius, gridRotation, objectRotation, result);
- }
- }
- }
- /// <summary>
- /// Returns an array of objects that intersect with the specified ray, if any. Otherwise returns an empty array. See also: IsColliding.
- /// </summary>
- /// <param name="checkRay">Ray to check. Passing by ref as it improves performance with structs.</param>
- /// <param name="maxDistance">Distance to check.</param>
- /// <param name="result">List result.</param>
- /// <returns>Objects that intersect with the specified ray.</returns>
- public void GetColliding(ref Ray checkRay, System.Collections.Generic.List<T> result, float maxDistance = float.PositiveInfinity)
- {
- float distance;
- // Is the input ray at least partially in this node?
- if (!bounds.IntersectRay(checkRay, out distance) || distance > maxDistance)
- {
- return;
- }
- // Check against any objects in this node
- for (int i = 0; i < objects.Count; i++)
- {
- if (objects[i].Bounds.IntersectRay(checkRay, out distance) && distance <= maxDistance)
- {
- result.Add(objects[i].Obj);
- }
- }
- // Check children
- if (children != null)
- {
- for (int i = 0; i < 8; i++)
- {
- children[i].GetColliding(ref checkRay, result, maxDistance);
- }
- }
- }
- public void GetWithinFrustum(Plane[] planes, System.Collections.Generic.List<T> result)
- {
- // Is the input node inside the frustum?
- if (!GeometryUtility.TestPlanesAABB(planes, bounds))
- {
- return;
- }
- // Check against any objects in this node
- for (int i = 0; i < objects.Count; i++)
- {
- if (GeometryUtility.TestPlanesAABB(planes, objects[i].Bounds))
- {
- result.Add(objects[i].Obj);
- }
- }
- // Check children
- if (children != null)
- {
- for (int i = 0; i < 8; i++)
- {
- children[i].GetWithinFrustum(planes, result);
- }
- }
- }
- /// <summary>
- /// Set the 8 children of this octree.
- /// </summary>
- /// <param name="childOctrees">The 8 new child nodes.</param>
- public void SetChildren(BoundsOctreeNode<T>[] childOctrees)
- {
- if (childOctrees.Length != 8)
- {
- Debug.LogError("Child octree array must be length 8. Was length: " + childOctrees.Length);
- return;
- }
- children = childOctrees;
- }
- public Bounds GetBounds()
- {
- return bounds;
- }
- /// <summary>
- /// We can shrink the octree if:
- /// - This node is >= double minLength in length
- /// - All objects in the root node are within one octant
- /// - This node doesn't have children, or does but 7/8 children are empty
- /// We can also shrink it if there are no objects left at all!
- /// </summary>
- /// <param name="minLength">Minimum dimensions of a node in this octree.</param>
- /// <returns>The new root, or the existing one if we didn't shrink.</returns>
- public BoundsOctreeNode<T> ShrinkIfPossible(float minLength)
- {
- if (BaseLength < (2 * minLength))
- {
- return this;
- }
- if (objects.Count == 0 && (children == null || children.Length == 0))
- {
- return this;
- }
- // Check objects in root
- int bestFit = -1;
- for (int i = 0; i < objects.Count; i++)
- {
- OctreeObject curObj = objects[i];
- int newBestFit = BestFitChild(curObj.Bounds.center);
- if (i == 0 || newBestFit == bestFit)
- {
- // In same octant as the other(s). Does it fit completely inside that octant?
- if (Encapsulates(childBounds[newBestFit], curObj.Bounds))
- {
- if (bestFit < 0)
- {
- bestFit = newBestFit;
- }
- }
- else
- {
- // Nope, so we can't reduce. Otherwise we continue
- return this;
- }
- }
- else
- {
- return this; // Can't reduce - objects fit in different octants
- }
- }
- // Check objects in children if there are any
- if (children != null)
- {
- bool childHadContent = false;
- for (int i = 0; i < children.Length; i++)
- {
- if (children[i].HasAnyObjects())
- {
- if (childHadContent)
- {
- return this; // Can't shrink - another child had content already
- }
- if (bestFit >= 0 && bestFit != i)
- {
- return this; // Can't reduce - objects in root are in a different octant to objects in child
- }
- childHadContent = true;
- bestFit = i;
- }
- }
- }
- // Can reduce
- if (children == null)
- {
- // We don't have any children, so just shrink this node to the new size
- // We already know that everything will still fit in it
- SetValues(BaseLength / 2, minSize, looseness, childBounds[bestFit].center);
- return this;
- }
- // No objects in entire octree
- if (bestFit == -1)
- {
- return this;
- }
- // We have children. Use the appropriate child as the new root node
- return children[bestFit];
- }
- /// <summary>
- /// Find which child node this object would be most likely to fit in.
- /// </summary>
- /// <param name="objBounds">The object's bounds.</param>
- /// <returns>One of the eight child octants.</returns>
- public int BestFitChild(Vector3 objBoundsCenter)
- {
- return (objBoundsCenter.x <= Center.x ? 0 : 1) + (objBoundsCenter.y >= Center.y ? 0 : 4) + (objBoundsCenter.z <= Center.z ? 0 : 2);
- }
- /// <summary>
- /// Checks if this node or anything below it has something in it.
- /// </summary>
- /// <returns>True if this node or any of its children, grandchildren etc have something in them</returns>
- public bool HasAnyObjects()
- {
- if (objects.Count > 0) return true;
- if (children != null)
- {
- for (int i = 0; i < 8; i++)
- {
- if (children[i].HasAnyObjects()) return true;
- }
- }
- return false;
- }
- // #### PRIVATE METHODS ####
- /// <summary>
- /// Set values for this node.
- /// </summary>
- /// <param name="baseLengthVal">Length of this node, not taking looseness into account.</param>
- /// <param name="minSizeVal">Minimum size of nodes in this octree.</param>
- /// <param name="loosenessVal">Multiplier for baseLengthVal to get the actual size.</param>
- /// <param name="centerVal">Centre position of this node.</param>
- void SetValues(float baseLengthVal, float minSizeVal, float loosenessVal, Vector3 centerVal)
- {
- BaseLength = baseLengthVal;
- minSize = minSizeVal;
- looseness = loosenessVal;
- Center = centerVal;
- adjLength = looseness * baseLengthVal;
- // Create the bounding box.
- Vector3 size = new Vector3(adjLength, adjLength, adjLength);
- bounds = new Bounds(Center, size);
- float quarter = BaseLength / 4f;
- float childActualLength = (BaseLength / 2) * looseness;
- Vector3 childActualSize = new Vector3(childActualLength, childActualLength, childActualLength);
- childBounds = new Bounds[8];
- childBounds[0] = new Bounds(Center + new Vector3(-quarter, quarter, -quarter), childActualSize);
- childBounds[1] = new Bounds(Center + new Vector3(quarter, quarter, -quarter), childActualSize);
- childBounds[2] = new Bounds(Center + new Vector3(-quarter, quarter, quarter), childActualSize);
- childBounds[3] = new Bounds(Center + new Vector3(quarter, quarter, quarter), childActualSize);
- childBounds[4] = new Bounds(Center + new Vector3(-quarter, -quarter, -quarter), childActualSize);
- childBounds[5] = new Bounds(Center + new Vector3(quarter, -quarter, -quarter), childActualSize);
- childBounds[6] = new Bounds(Center + new Vector3(-quarter, -quarter, quarter), childActualSize);
- childBounds[7] = new Bounds(Center + new Vector3(quarter, -quarter, quarter), childActualSize);
- }
- /// <summary>
- /// Private counterpart to the public Add method.
- /// </summary>
- /// <param name="obj">Object to add.</param>
- /// <param name="objBounds">3D bounding box around the object.</param>
- void SubAdd(T obj, Bounds objBounds)
- {
- // We know it fits at this level if we've got this far
- // We always put things in the deepest possible child
- // So we can skip some checks if there are children aleady
- if (!HasChildren)
- {
- // Just add if few objects are here, or children would be below min size
- if (objects.Count < NUM_OBJECTS_ALLOWED || (BaseLength / 2) < minSize)
- {
- OctreeObject newObj = new OctreeObject { Obj = obj, Bounds = objBounds };
- objects.Add(newObj);
- return; // We're done. No children yet
- }
- // Fits at this level, but we can go deeper. Would it fit there?
- // Create the 8 children
- int bestFitChild;
- if (children == null)
- {
- Split();
- if (children == null)
- {
- Debug.LogError("Child creation failed for an unknown reason. Early exit.");
- return;
- }
- // Now that we have the new children, see if this node's existing objects would fit there
- for (int i = objects.Count - 1; i >= 0; i--)
- {
- OctreeObject existingObj = objects[i];
- // Find which child the object is closest to based on where the
- // object's center is located in relation to the octree's center
- bestFitChild = BestFitChild(existingObj.Bounds.center);
- // Does it fit?
- if (Encapsulates(children[bestFitChild].bounds, existingObj.Bounds))
- {
- children[bestFitChild].SubAdd(existingObj.Obj, existingObj.Bounds); // Go a level deeper
- objects.Remove(existingObj); // Remove from here
- }
- }
- }
- }
- // Handle the new object we're adding now
- int bestFit = BestFitChild(objBounds.center);
- if (Encapsulates(children[bestFit].bounds, objBounds))
- {
- children[bestFit].SubAdd(obj, objBounds);
- }
- else
- {
- // Didn't fit in a child. We'll have to it to this node instead
- OctreeObject newObj = new OctreeObject { Obj = obj, Bounds = objBounds };
- objects.Add(newObj);
- }
- }
- /// <summary>
- /// Private counterpart to the public <see cref="Remove(T, Bounds)"/> method.
- /// </summary>
- /// <param name="obj">Object to remove.</param>
- /// <param name="objBounds">3D bounding box around the object.</param>
- /// <returns>True if the object was removed successfully.</returns>
- bool SubRemove(T obj, Bounds objBounds)
- {
- bool removed = false;
- for (int i = 0; i < objects.Count; i++)
- {
- if (objects[i].Obj.Equals(obj))
- {
- removed = objects.Remove(objects[i]);
- break;
- }
- }
- if (!removed && children != null)
- {
- int bestFitChild = BestFitChild(objBounds.center);
- removed = children[bestFitChild].SubRemove(obj, objBounds);
- }
- if (removed && children != null)
- {
- // Check if we should merge nodes now that we've removed an item
- if (ShouldMerge())
- {
- Merge();
- }
- }
- return removed;
- }
- /// <summary>
- /// Splits the octree into eight children.
- /// </summary>
- void Split()
- {
- float quarter = BaseLength / 4f;
- float newLength = BaseLength / 2;
- children = new BoundsOctreeNode<T>[8];
- children[0] = new BoundsOctreeNode<T>(newLength, minSize, looseness, Center + new Vector3(-quarter, quarter, -quarter));
- children[1] = new BoundsOctreeNode<T>(newLength, minSize, looseness, Center + new Vector3(quarter, quarter, -quarter));
- children[2] = new BoundsOctreeNode<T>(newLength, minSize, looseness, Center + new Vector3(-quarter, quarter, quarter));
- children[3] = new BoundsOctreeNode<T>(newLength, minSize, looseness, Center + new Vector3(quarter, quarter, quarter));
- children[4] = new BoundsOctreeNode<T>(newLength, minSize, looseness, Center + new Vector3(-quarter, -quarter, -quarter));
- children[5] = new BoundsOctreeNode<T>(newLength, minSize, looseness, Center + new Vector3(quarter, -quarter, -quarter));
- children[6] = new BoundsOctreeNode<T>(newLength, minSize, looseness, Center + new Vector3(-quarter, -quarter, quarter));
- children[7] = new BoundsOctreeNode<T>(newLength, minSize, looseness, Center + new Vector3(quarter, -quarter, quarter));
- }
- /// <summary>
- /// Merge all children into this node - the opposite of Split.
- /// Note: We only have to check one level down since a merge will never happen if the children already have children,
- /// since THAT won't happen unless there are already too many objects to merge.
- /// </summary>
- void Merge()
- {
- // Note: We know children != null or we wouldn't be merging
- for (int i = 0; i < 8; i++)
- {
- BoundsOctreeNode<T> curChild = children[i];
- int numObjects = curChild.objects.Count;
- for (int j = numObjects - 1; j >= 0; j--)
- {
- OctreeObject curObj = curChild.objects[j];
- objects.Add(curObj);
- }
- }
- // Remove the child nodes (and the objects in them - they've been added elsewhere now)
- children = null;
- }
- /// <summary>
- /// Checks if outerBounds encapsulates innerBounds.
- /// </summary>
- /// <param name="outerBounds">Outer bounds.</param>
- /// <param name="innerBounds">Inner bounds.</param>
- /// <returns>True if innerBounds is fully encapsulated by outerBounds.</returns>
- static bool Encapsulates(Bounds outerBounds, Bounds innerBounds)
- {
- return outerBounds.Contains(innerBounds.min) && outerBounds.Contains(innerBounds.max);
- }
- /// <summary>
- /// Checks if there are few enough objects in this node and its children that the children should all be merged into this.
- /// </summary>
- /// <returns>True there are less or the same abount of objects in this and its children than numObjectsAllowed.</returns>
- bool ShouldMerge()
- {
- int totalObjects = objects.Count;
- if (children != null)
- {
- foreach (BoundsOctreeNode<T> child in children)
- {
- if (child.children != null)
- {
- // If any of the *children* have children, there are definitely too many to merge,
- // or the child woudl have been merged already
- return false;
- }
- totalObjects += child.objects.Count;
- }
- }
- return totalObjects <= NUM_OBJECTS_ALLOWED;
- }
- }
- // A Dynamic, Loose Octree for storing any objects that can be described with AABB bounds
- // See also: PointOctree, where objects are stored as single points and some code can be simplified
- // Octree: An octree is a tree data structure which divides 3D space into smaller partitions (nodes)
- // and places objects into the appropriate nodes. This allows fast access to objects
- // in an area of interest without having to check every object.
- // Dynamic: The octree grows or shrinks as required when objects as added or removed
- // It also splits and merges nodes as appropriate. There is no maximum depth.
- // Nodes have a constant - numObjectsAllowed - which sets the amount of items allowed in a node before it splits.
- // Loose: The octree's nodes can be larger than 1/2 their parent's length and width, so they overlap to some extent.
- // This can alleviate the problem of even tiny objects ending up in large nodes if they're near boundaries.
- // A looseness value of 1.0 will make it a "normal" octree.
- // T: The content of the octree can be anything, since the bounds data is supplied separately.
- public class BoundsOctree<T>
- {
- // The total amount of objects currently in the tree
- public int Count { get; private set; }
- // Root node of the octree
- BoundsOctreeNode<T> rootNode;
- // Should be a value between 1 and 2. A multiplier for the base size of a node.
- // 1.0 is a "normal" octree, while values > 1 have overlap
- readonly float looseness;
- // Size that the octree was on creation
- readonly float initialSize;
- // Minimum side length that a node can be - essentially an alternative to having a max depth
- readonly float minSize;
- // For collision visualisation. Automatically removed in builds.
- /// <summary>
- /// Constructor for the bounds octree.
- /// </summary>
- /// <param name="initialWorldSize">Size of the sides of the initial node, in metres. The octree will never shrink smaller than this.</param>
- /// <param name="initialWorldPos">Position of the centre of the initial node.</param>
- /// <param name="minNodeSize">Nodes will stop splitting if the new nodes would be smaller than this (metres).</param>
- /// <param name="loosenessVal">Clamped between 1 and 2. Values > 1 let nodes overlap.</param>
- public BoundsOctree(float initialWorldSize, Vector3 initialWorldPos, float minNodeSize, float loosenessVal)
- {
- if (minNodeSize > initialWorldSize)
- {
- Debug.LogWarning("Minimum node size must be at least as big as the initial world size. Was: " + minNodeSize + " Adjusted to: " + initialWorldSize);
- minNodeSize = initialWorldSize;
- }
- Count = 0;
- initialSize = initialWorldSize;
- minSize = minNodeSize;
- looseness = Mathf.Clamp(loosenessVal, 1.0f, 2.0f);
- rootNode = new BoundsOctreeNode<T>(initialSize, minSize, looseness, initialWorldPos);
- }
- // #### PUBLIC METHODS ####
- /// <summary>
- /// Add an object.
- /// </summary>
- /// <param name="obj">Object to add.</param>
- /// <param name="objBounds">3D bounding box around the object.</param>
- public void Add(T obj, Bounds objBounds)
- {
- // Add object or expand the octree until it can be added
- int count = 0; // Safety check against infinite/excessive growth
- while (!rootNode.Add(obj, objBounds))
- {
- Grow(objBounds.center - rootNode.Center);
- if (++count > 20)
- {
- Debug.LogError("Aborted Add operation as it seemed to be going on forever (" + (count - 1) + ") attempts at growing the octree.");
- return;
- }
- }
- Count++;
- }
- /// <summary>
- /// Remove an object. Makes the assumption that the object only exists once in the tree.
- /// </summary>
- /// <param name="obj">Object to remove.</param>
- /// <returns>True if the object was removed successfully.</returns>
- public bool Remove(T obj)
- {
- bool removed = rootNode.Remove(obj);
- // See if we can shrink the octree down now that we've removed the item
- if (removed)
- {
- Count--;
- Shrink();
- }
- return removed;
- }
- /// <summary>
- /// Removes the specified object at the given position. Makes the assumption that the object only exists once in the tree.
- /// </summary>
- /// <param name="obj">Object to remove.</param>
- /// <param name="objBounds">3D bounding box around the object.</param>
- /// <returns>True if the object was removed successfully.</returns>
- public bool Remove(T obj, Bounds objBounds)
- {
- bool removed = rootNode.Remove(obj, objBounds);
- // See if we can shrink the octree down now that we've removed the item
- if (removed)
- {
- Count--;
- Shrink();
- }
- return removed;
- }
- /// <summary>
- /// Check if the specified bounds intersect with anything in the tree. See also: GetColliding.
- /// </summary>
- /// <param name="checkBounds">bounds to check.</param>
- /// <returns>True if there was a collision.</returns>
- public bool IsColliding(Bounds checkBounds)
- {
- return rootNode.IsColliding(ref checkBounds);
- }
- /// <summary>
- /// Check if the specified ray intersects with anything in the tree. See also: GetColliding.
- /// </summary>
- /// <param name="checkRay">ray to check.</param>
- /// <param name="maxDistance">distance to check.</param>
- /// <returns>True if there was a collision.</returns>
- public bool IsColliding(Ray checkRay, float maxDistance)
- {
- return rootNode.IsColliding(ref checkRay, maxDistance);
- }
- /// <summary>
- /// Returns an array of objects that intersect with the specified bounds, if any. Otherwise returns an empty array. See also: IsColliding.
- /// </summary>
- /// <param name="collidingWith">list to store intersections.</param>
- /// <param name="checkBounds">bounds to check.</param>
- /// <returns>Objects that intersect with the specified bounds.</returns>
- public void GetColliding(System.Collections.Generic.List<T> collidingWith, Bounds checkBounds)
- {
- rootNode.GetColliding(ref checkBounds, collidingWith);
- }
- public void GetColliding(Vector3 center, Vector3 localInnerRadius, Quaternion gridRotation, Quaternion objectRotation,
- System.Collections.Generic.List<T> result)
- {
- rootNode.GetColliding(center, localInnerRadius, gridRotation, objectRotation, result);
- }
- /// <summary>
- /// Returns an array of objects that intersect with the specified ray, if any. Otherwise returns an empty array. See also: IsColliding.
- /// </summary>
- /// <param name="collidingWith">list to store intersections.</param>
- /// <param name="checkRay">ray to check.</param>
- /// <param name="maxDistance">distance to check.</param>
- /// <returns>Objects that intersect with the specified ray.</returns>
- public void GetColliding(System.Collections.Generic.List<T> collidingWith, Ray checkRay, float maxDistance = float.PositiveInfinity)
- {
- rootNode.GetColliding(ref checkRay, collidingWith, maxDistance);
- }
- public System.Collections.Generic.List<T> GetWithinFrustum(Camera cam)
- {
- var planes = GeometryUtility.CalculateFrustumPlanes(cam);
- var list = new System.Collections.Generic.List<T>();
- rootNode.GetWithinFrustum(planes, list);
- return list;
- }
- public Bounds GetMaxBounds()
- {
- return rootNode.GetBounds();
- }
- // #### PRIVATE METHODS ####
- /// <summary>
- /// Grow the octree to fit in all objects.
- /// </summary>
- /// <param name="direction">Direction to grow.</param>
- void Grow(Vector3 direction)
- {
- int xDirection = direction.x >= 0 ? 1 : -1;
- int yDirection = direction.y >= 0 ? 1 : -1;
- int zDirection = direction.z >= 0 ? 1 : -1;
- BoundsOctreeNode<T> oldRoot = rootNode;
- float half = rootNode.BaseLength / 2;
- float newLength = rootNode.BaseLength * 2;
- Vector3 newCenter = rootNode.Center + new Vector3(xDirection * half, yDirection * half, zDirection * half);
- // Create a new, bigger octree root node
- rootNode = new BoundsOctreeNode<T>(newLength, minSize, looseness, newCenter);
- if (oldRoot.HasAnyObjects())
- {
- // Create 7 new octree children to go with the old root as children of the new root
- int rootPos = rootNode.BestFitChild(oldRoot.Center);
- BoundsOctreeNode<T>[] children = new BoundsOctreeNode<T>[8];
- for (int i = 0; i < 8; i++)
- {
- if (i == rootPos)
- {
- children[i] = oldRoot;
- }
- else
- {
- xDirection = i % 2 == 0 ? -1 : 1;
- yDirection = i > 3 ? -1 : 1;
- zDirection = (i < 2 || (i > 3 && i < 6)) ? -1 : 1;
- children[i] = new BoundsOctreeNode<T>(oldRoot.BaseLength, minSize, looseness, newCenter + new Vector3(xDirection * half, yDirection * half, zDirection * half));
- }
- }
- // Attach the new children to the new root node
- rootNode.SetChildren(children);
- }
- }
- /// <summary>
- /// Shrink the octree if possible, else leave it the same.
- /// </summary>
- void Shrink()
- {
- rootNode = rootNode.ShrinkIfPossible(initialSize);
- }
- }
- }
|