├── LICENCE ├── LICENCE.meta ├── README.md ├── README.md.meta ├── Scripts.meta ├── Scripts ├── BoundsOctree.cs ├── BoundsOctree.cs.meta ├── BoundsOctreeNode.cs ├── BoundsOctreeNode.cs.meta ├── PointOctree.cs ├── PointOctree.cs.meta ├── PointOctreeNode.cs └── PointOctreeNode.cs.meta ├── marker.tif ├── marker.tif.meta ├── octree-visualisation.jpg └── octree-visualisation.jpg.meta /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Nition 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /LICENCE.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: eca63f5432115bf49955c8431cc5852f 3 | timeCreated: 1481513782 4 | licenseType: Pro 5 | DefaultImporter: 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Please note: This repository is no longer maintained. 2 | 3 | UnityOctree 4 | =========== 5 | 6 | A dynamic octree implementation for Unity written in C#. 7 | Originally written for my game [Scraps](http://www.scrapsgame.com) but intended to be general-purpose. 8 | 9 | There are two octree implementations here: 10 | **BoundsOctree** stores any type of object, with the object boundaries defined as an axis-aligned bounding box. It's a dynamic octree and can also be a loose octree. 11 | **PointOctree** is the same basic implementation, but stores objects as a point in space instead of bounds. This allows some simplification of the code. It's a dynamic octree as well. 12 | 13 | **Octree:** An octree 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. 14 | 15 | **Dynamic:** The octree grows or shrinks as required when objects are 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. 16 | 17 | **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. 18 | 19 | **A few functions are implemented:** 20 | 21 | With BoundsOctree, you can pass in bounds and get a true/false answer for if it's colliding with anything (IsColliding), or get a list of everything it's collising with (GetColliding). 22 | With PointOctree, you can cast a ray and get a list of objects that are within x distance of that ray (GetNearby). You may also get a list of objects that are within x distance from a specified origin point. 23 | 24 | It shouldn't be too hard to implement additional functions if needed. For instance, PointOctree could check for points that fall inside a given bounds. 25 | 26 | **Considerations:** 27 | 28 | Tree searches are recursive, so there is technically the potential for a stack overflow on very large trees. The minNodeSize parameter limits node side and hence the depth of the tree, putting a cap on recursion. 29 | 30 | I tried switching to an iterative solution using my own stack, but creating and manipulating the stack made the results generally slower than the simple recursive solution. However, I wouldn't be surprised it someone smarter than me can come up with a faster solution. 31 | 32 | Another note: You may notice when viewing the bounds visualisation that the child nodes' outer edges are all inside the parent nodes. But loose octrees are meant to make the inner nodes bigger... aren't they? The answer is yes, but the parent nodes are *also* bigger, and e.g. ((1.2 * 10) - 10) is bigger than ((1.2 * 5) - 5), so the parent node ends up being bigger overall. 33 | 34 | This seems to be the standard way that loose octrees are done. I did an experiment: I tried making the child node dimensions looseness * the parent's actual size, instead of looseness * the parent's base size before looseness is applied. This seems more intuitively correct to me, but performance seems to be about the same. 35 | 36 | Example Usage 37 | =========== 38 | 39 | **Create An Octree** 40 | 41 | ```C# 42 | // Initial size (metres), initial centre position, minimum node size (metres), looseness 43 | BoundsOctree boundsTree = new BoundsOctree(15, MyContainer.position, 1, 1.25f); 44 | // Initial size (metres), initial centre position, minimum node size (metres) 45 | PointOctree pointTree = new PointOctree(15, MyContainer.position, 1); 46 | ``` 47 | 48 | - Here I've used GameObject, but the tree's content can be any type you like (as long as it's all the *same* type 49 | - The initial size should ideally cover an area just encompassing all your objects. If you guess too small, the octree will grow automatically, but it will be eight times the size (double dimensions), which could end up covering a large area unnecessarily. At the same time, the octree will be able to shrink down again if the outlying objects are removed. If you guess an initial size that's too big, it won't be able to shrink down, but that may be the safer option. Don't worry too much: In reality the starting value isn't hugely important for performance. 50 | - The initial position should ideally be in the centre of where your objects are. 51 | - The minimum node size is effectively a depth limit; it limits how many times the tree can divide. If all your objects are e.g. 1m+ wide, you wouldn't want to set it smaller than 1m. 52 | - The best way to choose a looseness value is to try different values (between 1 and maybe 1.5) and check the performance with your particular data. Generally around 1.2 is good. 53 | 54 | **Add And Remove** 55 | 56 | ```C# 57 | boundsTree.Add(myObject, myBounds); 58 | boundsTree.Remove(myObject); 59 | 60 | pointTree.Add(myObject, myVector3); 61 | boundsTree.Remove(myObject); 62 | ``` 63 | - The object's type depends on the tree's type. 64 | - The bounds or point determine where it's inserted. 65 | 66 | **Built-in Functions** 67 | 68 | ```C# 69 | bool isColliding = boundsTree.IsColliding(bounds); 70 | ``` 71 | 72 | ```C# 73 | List collidingWith = new List(); 74 | boundsTree.GetColliding(collidingWith, bounds); 75 | ``` 76 | - Where GameObject is the type of the octree 77 | 78 | ```C# 79 | pointTree.GetNearby(myRay, 4); 80 | ``` 81 | - Where myRay is a [Ray](http://docs.unity3d.com/Documentation/ScriptReference/Ray.html) 82 | - In this case we're looking for any point within 4m of the closest point on the ray 83 | 84 | ```C# 85 | pointTree.GetNearby(myPos, 4); 86 | ``` 87 | - Where myPos is a [Vector3](http://docs.unity3d.com/Documentation/ScriptReference/Vector3.html) 88 | 89 | **Debugging Visuals** 90 | 91 | ![Visualisation example.](https://raw.github.com/nition/UnityOctree/master/octree-visualisation.jpg) 92 | 93 | ```C# 94 | void OnDrawGizmos() { 95 | boundsTree.DrawAllBounds(); // Draw node boundaries 96 | boundsTree.DrawAllObjects(); // Draw object boundaries 97 | boundsTree.DrawCollisionChecks(); // Draw the last *numCollisionsToSave* collision check boundaries 98 | 99 | pointTree.DrawAllBounds(); // Draw node boundaries 100 | pointTree.DrawAllObjects(); // Mark object positions 101 | } 102 | ``` 103 | - Must be in Unity's OnDrawGizmos() method in a class that inherits from MonoBehaviour 104 | - Point octrees need the marker.tif file to be in your Unity /Assets/Gizmos subfolder for DrawAllObjects to work 105 | 106 | 107 | **Potential Improvements** 108 | 109 | A significant portion of the octree's time is taken just to traverse through the nodes themselves. There's potential for a performance increase there, maybe by linearising the tree - that is, representing all the nodes as a one-dimensional array lookup. 110 | 111 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4fc9af6064d57b64781077cf839a35f5 3 | timeCreated: 1481513780 4 | licenseType: Pro 5 | DefaultImporter: 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Scripts.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: eefe4120991ee9348852edf28927bd49 3 | folderAsset: yes 4 | timeCreated: 1481513782 5 | licenseType: Pro 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Scripts/BoundsOctree.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | 4 | // A Dynamic, Loose Octree for storing any objects that can be described with AABB bounds 5 | // See also: PointOctree, where objects are stored as single points and some code can be simplified 6 | // Octree: An octree is a tree data structure which divides 3D space into smaller partitions (nodes) 7 | // and places objects into the appropriate nodes. This allows fast access to objects 8 | // in an area of interest without having to check every object. 9 | // Dynamic: The octree grows or shrinks as required when objects as added or removed 10 | // It also splits and merges nodes as appropriate. There is no maximum depth. 11 | // Nodes have a constant - numObjectsAllowed - which sets the amount of items allowed in a node before it splits. 12 | // Loose: The octree's nodes can be larger than 1/2 their parent's length and width, so they overlap to some extent. 13 | // This can alleviate the problem of even tiny objects ending up in large nodes if they're near boundaries. 14 | // A looseness value of 1.0 will make it a "normal" octree. 15 | // T: The content of the octree can be anything, since the bounds data is supplied separately. 16 | 17 | // Originally written for my game Scraps (http://www.scrapsgame.com) but intended to be general-purpose. 18 | // Copyright 2014 Nition, BSD licence (see LICENCE file). www.momentstudio.co.nz 19 | // Unity-based, but could be adapted to work in pure C# 20 | 21 | // Note: For loops are often used here since in some cases (e.g. the IsColliding method) 22 | // they actually give much better performance than using Foreach, even in the compiled build. 23 | // Using a LINQ expression is worse again than Foreach. 24 | public class BoundsOctree { 25 | // The total amount of objects currently in the tree 26 | public int Count { get; private set; } 27 | 28 | // Root node of the octree 29 | BoundsOctreeNode rootNode; 30 | 31 | // Should be a value between 1 and 2. A multiplier for the base size of a node. 32 | // 1.0 is a "normal" octree, while values > 1 have overlap 33 | readonly float looseness; 34 | 35 | // Size that the octree was on creation 36 | readonly float initialSize; 37 | 38 | // Minimum side length that a node can be - essentially an alternative to having a max depth 39 | readonly float minSize; 40 | // For collision visualisation. Automatically removed in builds. 41 | #if UNITY_EDITOR 42 | const int numCollisionsToSave = 4; 43 | readonly Queue lastBoundsCollisionChecks = new Queue(); 44 | readonly Queue lastRayCollisionChecks = new Queue(); 45 | #endif 46 | 47 | /// 48 | /// Constructor for the bounds octree. 49 | /// 50 | /// Size of the sides of the initial node, in metres. The octree will never shrink smaller than this. 51 | /// Position of the centre of the initial node. 52 | /// Nodes will stop splitting if the new nodes would be smaller than this (metres). 53 | /// Clamped between 1 and 2. Values > 1 let nodes overlap. 54 | public BoundsOctree(float initialWorldSize, Vector3 initialWorldPos, float minNodeSize, float loosenessVal) { 55 | if (minNodeSize > initialWorldSize) { 56 | Debug.LogWarning("Minimum node size must be at least as big as the initial world size. Was: " + minNodeSize + " Adjusted to: " + initialWorldSize); 57 | minNodeSize = initialWorldSize; 58 | } 59 | Count = 0; 60 | initialSize = initialWorldSize; 61 | minSize = minNodeSize; 62 | looseness = Mathf.Clamp(loosenessVal, 1.0f, 2.0f); 63 | rootNode = new BoundsOctreeNode(initialSize, minSize, looseness, initialWorldPos); 64 | } 65 | 66 | // #### PUBLIC METHODS #### 67 | 68 | /// 69 | /// Add an object. 70 | /// 71 | /// Object to add. 72 | /// 3D bounding box around the object. 73 | public void Add(T obj, Bounds objBounds) { 74 | // Add object or expand the octree until it can be added 75 | int count = 0; // Safety check against infinite/excessive growth 76 | while (!rootNode.Add(obj, objBounds)) { 77 | Grow(objBounds.center - rootNode.Center); 78 | if (++count > 20) { 79 | Debug.LogError("Aborted Add operation as it seemed to be going on forever (" + (count - 1) + ") attempts at growing the octree."); 80 | return; 81 | } 82 | } 83 | Count++; 84 | } 85 | 86 | /// 87 | /// Remove an object. Makes the assumption that the object only exists once in the tree. 88 | /// 89 | /// Object to remove. 90 | /// True if the object was removed successfully. 91 | public bool Remove(T obj) { 92 | bool removed = rootNode.Remove(obj); 93 | 94 | // See if we can shrink the octree down now that we've removed the item 95 | if (removed) { 96 | Count--; 97 | Shrink(); 98 | } 99 | 100 | return removed; 101 | } 102 | 103 | /// 104 | /// Removes the specified object at the given position. Makes the assumption that the object only exists once in the tree. 105 | /// 106 | /// Object to remove. 107 | /// 3D bounding box around the object. 108 | /// True if the object was removed successfully. 109 | public bool Remove(T obj, Bounds objBounds) { 110 | bool removed = rootNode.Remove(obj, objBounds); 111 | 112 | // See if we can shrink the octree down now that we've removed the item 113 | if (removed) { 114 | Count--; 115 | Shrink(); 116 | } 117 | 118 | return removed; 119 | } 120 | 121 | /// 122 | /// Check if the specified bounds intersect with anything in the tree. See also: GetColliding. 123 | /// 124 | /// bounds to check. 125 | /// True if there was a collision. 126 | public bool IsColliding(Bounds checkBounds) { 127 | //#if UNITY_EDITOR 128 | // For debugging 129 | //AddCollisionCheck(checkBounds); 130 | //#endif 131 | return rootNode.IsColliding(ref checkBounds); 132 | } 133 | 134 | /// 135 | /// Check if the specified ray intersects with anything in the tree. See also: GetColliding. 136 | /// 137 | /// ray to check. 138 | /// distance to check. 139 | /// True if there was a collision. 140 | public bool IsColliding(Ray checkRay, float maxDistance) { 141 | //#if UNITY_EDITOR 142 | // For debugging 143 | //AddCollisionCheck(checkRay); 144 | //#endif 145 | return rootNode.IsColliding(ref checkRay, maxDistance); 146 | } 147 | 148 | /// 149 | /// Returns an array of objects that intersect with the specified bounds, if any. Otherwise returns an empty array. See also: IsColliding. 150 | /// 151 | /// list to store intersections. 152 | /// bounds to check. 153 | /// Objects that intersect with the specified bounds. 154 | public void GetColliding(List collidingWith, Bounds checkBounds) { 155 | //#if UNITY_EDITOR 156 | // For debugging 157 | //AddCollisionCheck(checkBounds); 158 | //#endif 159 | rootNode.GetColliding(ref checkBounds, collidingWith); 160 | } 161 | 162 | /// 163 | /// Returns an array of objects that intersect with the specified ray, if any. Otherwise returns an empty array. See also: IsColliding. 164 | /// 165 | /// list to store intersections. 166 | /// ray to check. 167 | /// distance to check. 168 | /// Objects that intersect with the specified ray. 169 | public void GetColliding(List collidingWith, Ray checkRay, float maxDistance = float.PositiveInfinity) { 170 | //#if UNITY_EDITOR 171 | // For debugging 172 | //AddCollisionCheck(checkRay); 173 | //#endif 174 | rootNode.GetColliding(ref checkRay, collidingWith, maxDistance); 175 | } 176 | 177 | public List GetWithinFrustum(Camera cam) { 178 | var planes = GeometryUtility.CalculateFrustumPlanes(cam); 179 | 180 | var list = new List(); 181 | rootNode.GetWithinFrustum(planes, list); 182 | return list; 183 | } 184 | 185 | public Bounds GetMaxBounds() { 186 | return rootNode.GetBounds(); 187 | } 188 | 189 | /// 190 | /// Draws node boundaries visually for debugging. 191 | /// Must be called from OnDrawGizmos externally. See also: DrawAllObjects. 192 | /// 193 | public void DrawAllBounds() { 194 | rootNode.DrawAllBounds(); 195 | } 196 | 197 | /// 198 | /// Draws the bounds of all objects in the tree visually for debugging. 199 | /// Must be called from OnDrawGizmos externally. See also: DrawAllBounds. 200 | /// 201 | public void DrawAllObjects() { 202 | rootNode.DrawAllObjects(); 203 | } 204 | 205 | // Intended for debugging. Must be called from OnDrawGizmos externally 206 | // See also DrawAllBounds and DrawAllObjects 207 | /// 208 | /// Visualises collision checks from IsColliding and GetColliding. 209 | /// Collision visualisation code is automatically removed from builds so that collision checks aren't slowed down. 210 | /// 211 | #if UNITY_EDITOR 212 | public void DrawCollisionChecks() { 213 | int count = 0; 214 | foreach (Bounds collisionCheck in lastBoundsCollisionChecks) { 215 | Gizmos.color = new Color(1.0f, 1.0f - ((float)count / numCollisionsToSave), 1.0f); 216 | Gizmos.DrawCube(collisionCheck.center, collisionCheck.size); 217 | count++; 218 | } 219 | 220 | foreach (Ray collisionCheck in lastRayCollisionChecks) { 221 | Gizmos.color = new Color(1.0f, 1.0f - ((float)count / numCollisionsToSave), 1.0f); 222 | Gizmos.DrawRay(collisionCheck.origin, collisionCheck.direction); 223 | count++; 224 | } 225 | Gizmos.color = Color.white; 226 | } 227 | #endif 228 | 229 | // #### PRIVATE METHODS #### 230 | 231 | /// 232 | /// Used for visualising collision checks with DrawCollisionChecks. 233 | /// Automatically removed from builds so that collision checks aren't slowed down. 234 | /// 235 | /// bounds that were passed in to check for collisions. 236 | #if UNITY_EDITOR 237 | void AddCollisionCheck(Bounds checkBounds) { 238 | lastBoundsCollisionChecks.Enqueue(checkBounds); 239 | if (lastBoundsCollisionChecks.Count > numCollisionsToSave) { 240 | lastBoundsCollisionChecks.Dequeue(); 241 | } 242 | } 243 | #endif 244 | 245 | /// 246 | /// Used for visualising collision checks with DrawCollisionChecks. 247 | /// Automatically removed from builds so that collision checks aren't slowed down. 248 | /// 249 | /// ray that was passed in to check for collisions. 250 | #if UNITY_EDITOR 251 | void AddCollisionCheck(Ray checkRay) { 252 | lastRayCollisionChecks.Enqueue(checkRay); 253 | if (lastRayCollisionChecks.Count > numCollisionsToSave) { 254 | lastRayCollisionChecks.Dequeue(); 255 | } 256 | } 257 | #endif 258 | 259 | /// 260 | /// Grow the octree to fit in all objects. 261 | /// 262 | /// Direction to grow. 263 | void Grow(Vector3 direction) { 264 | int xDirection = direction.x >= 0 ? 1 : -1; 265 | int yDirection = direction.y >= 0 ? 1 : -1; 266 | int zDirection = direction.z >= 0 ? 1 : -1; 267 | BoundsOctreeNode oldRoot = rootNode; 268 | float half = rootNode.BaseLength / 2; 269 | float newLength = rootNode.BaseLength * 2; 270 | Vector3 newCenter = rootNode.Center + new Vector3(xDirection * half, yDirection * half, zDirection * half); 271 | 272 | // Create a new, bigger octree root node 273 | rootNode = new BoundsOctreeNode(newLength, minSize, looseness, newCenter); 274 | 275 | if (oldRoot.HasAnyObjects()) { 276 | // Create 7 new octree children to go with the old root as children of the new root 277 | int rootPos = rootNode.BestFitChild(oldRoot.Center); 278 | BoundsOctreeNode[] children = new BoundsOctreeNode[8]; 279 | for (int i = 0; i < 8; i++) { 280 | if (i == rootPos) { 281 | children[i] = oldRoot; 282 | } 283 | else { 284 | xDirection = i % 2 == 0 ? -1 : 1; 285 | yDirection = i > 3 ? -1 : 1; 286 | zDirection = (i < 2 || (i > 3 && i < 6)) ? -1 : 1; 287 | children[i] = new BoundsOctreeNode(oldRoot.BaseLength, minSize, looseness, newCenter + new Vector3(xDirection * half, yDirection * half, zDirection * half)); 288 | } 289 | } 290 | 291 | // Attach the new children to the new root node 292 | rootNode.SetChildren(children); 293 | } 294 | } 295 | 296 | /// 297 | /// Shrink the octree if possible, else leave it the same. 298 | /// 299 | void Shrink() { 300 | rootNode = rootNode.ShrinkIfPossible(initialSize); 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /Scripts/BoundsOctree.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: acb06913ab8ee284b8b436ed59cce729 3 | timeCreated: 1481513782 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Scripts/BoundsOctreeNode.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | 4 | // A node in a BoundsOctree 5 | // Copyright 2014 Nition, BSD licence (see LICENCE file). www.momentstudio.co.nz 6 | public class BoundsOctreeNode { 7 | // Centre of this node 8 | public Vector3 Center { get; private set; } 9 | 10 | // Length of this node if it has a looseness of 1.0 11 | public float BaseLength { get; private set; } 12 | 13 | // Looseness value for this node 14 | float looseness; 15 | 16 | // Minimum size for a node in this octree 17 | float minSize; 18 | 19 | // Actual length of sides, taking the looseness value into account 20 | float adjLength; 21 | 22 | // Bounding box that represents this node 23 | Bounds bounds = default(Bounds); 24 | 25 | // Objects in this node 26 | readonly List objects = new List(); 27 | 28 | // Child nodes, if any 29 | BoundsOctreeNode[] children = null; 30 | 31 | bool HasChildren { get { return children != null; } } 32 | 33 | // Bounds of potential children to this node. These are actual size (with looseness taken into account), not base size 34 | Bounds[] childBounds; 35 | 36 | // If there are already NUM_OBJECTS_ALLOWED in a node, we split it into children 37 | // A generally good number seems to be something around 8-15 38 | const int NUM_OBJECTS_ALLOWED = 8; 39 | 40 | // An object in the octree 41 | struct OctreeObject { 42 | public T Obj; 43 | public Bounds Bounds; 44 | } 45 | 46 | /// 47 | /// Constructor. 48 | /// 49 | /// Length of this node, not taking looseness into account. 50 | /// Minimum size of nodes in this octree. 51 | /// Multiplier for baseLengthVal to get the actual size. 52 | /// Centre position of this node. 53 | public BoundsOctreeNode(float baseLengthVal, float minSizeVal, float loosenessVal, Vector3 centerVal) { 54 | SetValues(baseLengthVal, minSizeVal, loosenessVal, centerVal); 55 | } 56 | 57 | // #### PUBLIC METHODS #### 58 | 59 | /// 60 | /// Add an object. 61 | /// 62 | /// Object to add. 63 | /// 3D bounding box around the object. 64 | /// True if the object fits entirely within this node. 65 | public bool Add(T obj, Bounds objBounds) { 66 | if (!Encapsulates(bounds, objBounds)) { 67 | return false; 68 | } 69 | SubAdd(obj, objBounds); 70 | return true; 71 | } 72 | 73 | /// 74 | /// Remove an object. Makes the assumption that the object only exists once in the tree. 75 | /// 76 | /// Object to remove. 77 | /// True if the object was removed successfully. 78 | public bool Remove(T obj) { 79 | bool removed = false; 80 | 81 | for (int i = 0; i < objects.Count; i++) { 82 | if (objects[i].Obj.Equals(obj)) { 83 | removed = objects.Remove(objects[i]); 84 | break; 85 | } 86 | } 87 | 88 | if (!removed && children != null) { 89 | for (int i = 0; i < 8; i++) { 90 | removed = children[i].Remove(obj); 91 | if (removed) break; 92 | } 93 | } 94 | 95 | if (removed && children != null) { 96 | // Check if we should merge nodes now that we've removed an item 97 | if (ShouldMerge()) { 98 | Merge(); 99 | } 100 | } 101 | 102 | return removed; 103 | } 104 | 105 | /// 106 | /// Removes the specified object at the given position. Makes the assumption that the object only exists once in the tree. 107 | /// 108 | /// Object to remove. 109 | /// 3D bounding box around the object. 110 | /// True if the object was removed successfully. 111 | public bool Remove(T obj, Bounds objBounds) { 112 | if (!Encapsulates(bounds, objBounds)) { 113 | return false; 114 | } 115 | return SubRemove(obj, objBounds); 116 | } 117 | 118 | /// 119 | /// Check if the specified bounds intersect with anything in the tree. See also: GetColliding. 120 | /// 121 | /// Bounds to check. 122 | /// True if there was a collision. 123 | public bool IsColliding(ref Bounds checkBounds) { 124 | // Are the input bounds at least partially in this node? 125 | if (!bounds.Intersects(checkBounds)) { 126 | return false; 127 | } 128 | 129 | // Check against any objects in this node 130 | for (int i = 0; i < objects.Count; i++) { 131 | if (objects[i].Bounds.Intersects(checkBounds)) { 132 | return true; 133 | } 134 | } 135 | 136 | // Check children 137 | if (children != null) { 138 | for (int i = 0; i < 8; i++) { 139 | if (children[i].IsColliding(ref checkBounds)) { 140 | return true; 141 | } 142 | } 143 | } 144 | 145 | return false; 146 | } 147 | 148 | /// 149 | /// Check if the specified ray intersects with anything in the tree. See also: GetColliding. 150 | /// 151 | /// Ray to check. 152 | /// Distance to check. 153 | /// True if there was a collision. 154 | public bool IsColliding(ref Ray checkRay, float maxDistance = float.PositiveInfinity) { 155 | // Is the input ray at least partially in this node? 156 | float distance; 157 | if (!bounds.IntersectRay(checkRay, out distance) || distance > maxDistance) { 158 | return false; 159 | } 160 | 161 | // Check against any objects in this node 162 | for (int i = 0; i < objects.Count; i++) { 163 | if (objects[i].Bounds.IntersectRay(checkRay, out distance) && distance <= maxDistance) { 164 | return true; 165 | } 166 | } 167 | 168 | // Check children 169 | if (children != null) { 170 | for (int i = 0; i < 8; i++) { 171 | if (children[i].IsColliding(ref checkRay, maxDistance)) { 172 | return true; 173 | } 174 | } 175 | } 176 | 177 | return false; 178 | } 179 | 180 | /// 181 | /// Returns an array of objects that intersect with the specified bounds, if any. Otherwise returns an empty array. See also: IsColliding. 182 | /// 183 | /// Bounds to check. Passing by ref as it improves performance with structs. 184 | /// List result. 185 | /// Objects that intersect with the specified bounds. 186 | public void GetColliding(ref Bounds checkBounds, List result) { 187 | // Are the input bounds at least partially in this node? 188 | if (!bounds.Intersects(checkBounds)) { 189 | return; 190 | } 191 | 192 | // Check against any objects in this node 193 | for (int i = 0; i < objects.Count; i++) { 194 | if (objects[i].Bounds.Intersects(checkBounds)) { 195 | result.Add(objects[i].Obj); 196 | } 197 | } 198 | 199 | // Check children 200 | if (children != null) { 201 | for (int i = 0; i < 8; i++) { 202 | children[i].GetColliding(ref checkBounds, result); 203 | } 204 | } 205 | } 206 | 207 | /// 208 | /// Returns an array of objects that intersect with the specified ray, if any. Otherwise returns an empty array. See also: IsColliding. 209 | /// 210 | /// Ray to check. Passing by ref as it improves performance with structs. 211 | /// Distance to check. 212 | /// List result. 213 | /// Objects that intersect with the specified ray. 214 | public void GetColliding(ref Ray checkRay, List result, float maxDistance = float.PositiveInfinity) { 215 | float distance; 216 | // Is the input ray at least partially in this node? 217 | if (!bounds.IntersectRay(checkRay, out distance) || distance > maxDistance) { 218 | return; 219 | } 220 | 221 | // Check against any objects in this node 222 | for (int i = 0; i < objects.Count; i++) { 223 | if (objects[i].Bounds.IntersectRay(checkRay, out distance) && distance <= maxDistance) { 224 | result.Add(objects[i].Obj); 225 | } 226 | } 227 | 228 | // Check children 229 | if (children != null) { 230 | for (int i = 0; i < 8; i++) { 231 | children[i].GetColliding(ref checkRay, result, maxDistance); 232 | } 233 | } 234 | } 235 | 236 | public void GetWithinFrustum(Plane[] planes, List result) { 237 | // Is the input node inside the frustum? 238 | if (!GeometryUtility.TestPlanesAABB(planes, bounds)) { 239 | return; 240 | } 241 | 242 | // Check against any objects in this node 243 | for (int i = 0; i < objects.Count; i++) { 244 | if (GeometryUtility.TestPlanesAABB(planes, objects[i].Bounds)) { 245 | result.Add(objects[i].Obj); 246 | } 247 | } 248 | 249 | // Check children 250 | if (children != null) { 251 | for (int i = 0; i < 8; i++) { 252 | children[i].GetWithinFrustum(planes, result); 253 | } 254 | } 255 | } 256 | 257 | /// 258 | /// Set the 8 children of this octree. 259 | /// 260 | /// The 8 new child nodes. 261 | public void SetChildren(BoundsOctreeNode[] childOctrees) { 262 | if (childOctrees.Length != 8) { 263 | Debug.LogError("Child octree array must be length 8. Was length: " + childOctrees.Length); 264 | return; 265 | } 266 | 267 | children = childOctrees; 268 | } 269 | 270 | public Bounds GetBounds() { 271 | return bounds; 272 | } 273 | 274 | /// 275 | /// Draws node boundaries visually for debugging. 276 | /// Must be called from OnDrawGizmos externally. See also: DrawAllObjects. 277 | /// 278 | /// Used for recurcive calls to this method. 279 | public void DrawAllBounds(float depth = 0) { 280 | float tintVal = depth / 7; // Will eventually get values > 1. Color rounds to 1 automatically 281 | Gizmos.color = new Color(tintVal, 0, 1.0f - tintVal); 282 | 283 | Bounds thisBounds = new Bounds(Center, new Vector3(adjLength, adjLength, adjLength)); 284 | Gizmos.DrawWireCube(thisBounds.center, thisBounds.size); 285 | 286 | if (children != null) { 287 | depth++; 288 | for (int i = 0; i < 8; i++) { 289 | children[i].DrawAllBounds(depth); 290 | } 291 | } 292 | Gizmos.color = Color.white; 293 | } 294 | 295 | /// 296 | /// Draws the bounds of all objects in the tree visually for debugging. 297 | /// Must be called from OnDrawGizmos externally. See also: DrawAllBounds. 298 | /// 299 | public void DrawAllObjects() { 300 | float tintVal = BaseLength / 20; 301 | Gizmos.color = new Color(0, 1.0f - tintVal, tintVal, 0.25f); 302 | 303 | foreach (OctreeObject obj in objects) { 304 | Gizmos.DrawCube(obj.Bounds.center, obj.Bounds.size); 305 | } 306 | 307 | if (children != null) { 308 | for (int i = 0; i < 8; i++) { 309 | children[i].DrawAllObjects(); 310 | } 311 | } 312 | 313 | Gizmos.color = Color.white; 314 | } 315 | 316 | /// 317 | /// We can shrink the octree if: 318 | /// - This node is >= double minLength in length 319 | /// - All objects in the root node are within one octant 320 | /// - This node doesn't have children, or does but 7/8 children are empty 321 | /// We can also shrink it if there are no objects left at all! 322 | /// 323 | /// Minimum dimensions of a node in this octree. 324 | /// The new root, or the existing one if we didn't shrink. 325 | public BoundsOctreeNode ShrinkIfPossible(float minLength) { 326 | if (BaseLength < (2 * minLength)) { 327 | return this; 328 | } 329 | if (objects.Count == 0 && (children == null || children.Length == 0)) { 330 | return this; 331 | } 332 | 333 | // Check objects in root 334 | int bestFit = -1; 335 | for (int i = 0; i < objects.Count; i++) { 336 | OctreeObject curObj = objects[i]; 337 | int newBestFit = BestFitChild(curObj.Bounds.center); 338 | if (i == 0 || newBestFit == bestFit) { 339 | // In same octant as the other(s). Does it fit completely inside that octant? 340 | if (Encapsulates(childBounds[newBestFit], curObj.Bounds)) { 341 | if (bestFit < 0) { 342 | bestFit = newBestFit; 343 | } 344 | } 345 | else { 346 | // Nope, so we can't reduce. Otherwise we continue 347 | return this; 348 | } 349 | } 350 | else { 351 | return this; // Can't reduce - objects fit in different octants 352 | } 353 | } 354 | 355 | // Check objects in children if there are any 356 | if (children != null) { 357 | bool childHadContent = false; 358 | for (int i = 0; i < children.Length; i++) { 359 | if (children[i].HasAnyObjects()) { 360 | if (childHadContent) { 361 | return this; // Can't shrink - another child had content already 362 | } 363 | if (bestFit >= 0 && bestFit != i) { 364 | return this; // Can't reduce - objects in root are in a different octant to objects in child 365 | } 366 | childHadContent = true; 367 | bestFit = i; 368 | } 369 | } 370 | } 371 | 372 | // Can reduce 373 | if (children == null) { 374 | // We don't have any children, so just shrink this node to the new size 375 | // We already know that everything will still fit in it 376 | SetValues(BaseLength / 2, minSize, looseness, childBounds[bestFit].center); 377 | return this; 378 | } 379 | 380 | // No objects in entire octree 381 | if (bestFit == -1) { 382 | return this; 383 | } 384 | 385 | // We have children. Use the appropriate child as the new root node 386 | return children[bestFit]; 387 | } 388 | 389 | /// 390 | /// Find which child node this object would be most likely to fit in. 391 | /// 392 | /// The object's bounds. 393 | /// One of the eight child octants. 394 | public int BestFitChild(Vector3 objBoundsCenter) { 395 | return (objBoundsCenter.x <= Center.x ? 0 : 1) + (objBoundsCenter.y >= Center.y ? 0 : 4) + (objBoundsCenter.z <= Center.z ? 0 : 2); 396 | } 397 | 398 | /// 399 | /// Checks if this node or anything below it has something in it. 400 | /// 401 | /// True if this node or any of its children, grandchildren etc have something in them 402 | public bool HasAnyObjects() { 403 | if (objects.Count > 0) return true; 404 | 405 | if (children != null) { 406 | for (int i = 0; i < 8; i++) { 407 | if (children[i].HasAnyObjects()) return true; 408 | } 409 | } 410 | 411 | return false; 412 | } 413 | 414 | /* 415 | /// 416 | /// Get the total amount of objects in this node and all its children, grandchildren etc. Useful for debugging. 417 | /// 418 | /// Used by recursive calls to add to the previous total. 419 | /// Total objects in this node and its children, grandchildren etc. 420 | public int GetTotalObjects(int startingNum = 0) { 421 | int totalObjects = startingNum + objects.Count; 422 | if (children != null) { 423 | for (int i = 0; i < 8; i++) { 424 | totalObjects += children[i].GetTotalObjects(); 425 | } 426 | } 427 | return totalObjects; 428 | } 429 | */ 430 | 431 | // #### PRIVATE METHODS #### 432 | 433 | /// 434 | /// Set values for this node. 435 | /// 436 | /// Length of this node, not taking looseness into account. 437 | /// Minimum size of nodes in this octree. 438 | /// Multiplier for baseLengthVal to get the actual size. 439 | /// Centre position of this node. 440 | void SetValues(float baseLengthVal, float minSizeVal, float loosenessVal, Vector3 centerVal) { 441 | BaseLength = baseLengthVal; 442 | minSize = minSizeVal; 443 | looseness = loosenessVal; 444 | Center = centerVal; 445 | adjLength = looseness * baseLengthVal; 446 | 447 | // Create the bounding box. 448 | Vector3 size = new Vector3(adjLength, adjLength, adjLength); 449 | bounds = new Bounds(Center, size); 450 | 451 | float quarter = BaseLength / 4f; 452 | float childActualLength = (BaseLength / 2) * looseness; 453 | Vector3 childActualSize = new Vector3(childActualLength, childActualLength, childActualLength); 454 | childBounds = new Bounds[8]; 455 | childBounds[0] = new Bounds(Center + new Vector3(-quarter, quarter, -quarter), childActualSize); 456 | childBounds[1] = new Bounds(Center + new Vector3(quarter, quarter, -quarter), childActualSize); 457 | childBounds[2] = new Bounds(Center + new Vector3(-quarter, quarter, quarter), childActualSize); 458 | childBounds[3] = new Bounds(Center + new Vector3(quarter, quarter, quarter), childActualSize); 459 | childBounds[4] = new Bounds(Center + new Vector3(-quarter, -quarter, -quarter), childActualSize); 460 | childBounds[5] = new Bounds(Center + new Vector3(quarter, -quarter, -quarter), childActualSize); 461 | childBounds[6] = new Bounds(Center + new Vector3(-quarter, -quarter, quarter), childActualSize); 462 | childBounds[7] = new Bounds(Center + new Vector3(quarter, -quarter, quarter), childActualSize); 463 | } 464 | 465 | /// 466 | /// Private counterpart to the public Add method. 467 | /// 468 | /// Object to add. 469 | /// 3D bounding box around the object. 470 | void SubAdd(T obj, Bounds objBounds) { 471 | // We know it fits at this level if we've got this far 472 | 473 | // We always put things in the deepest possible child 474 | // So we can skip some checks if there are children aleady 475 | if (!HasChildren) { 476 | // Just add if few objects are here, or children would be below min size 477 | if (objects.Count < NUM_OBJECTS_ALLOWED || (BaseLength / 2) < minSize) { 478 | OctreeObject newObj = new OctreeObject { Obj = obj, Bounds = objBounds }; 479 | objects.Add(newObj); 480 | return; // We're done. No children yet 481 | } 482 | 483 | // Fits at this level, but we can go deeper. Would it fit there? 484 | // Create the 8 children 485 | int bestFitChild; 486 | if (children == null) { 487 | Split(); 488 | if (children == null) { 489 | Debug.LogError("Child creation failed for an unknown reason. Early exit."); 490 | return; 491 | } 492 | 493 | // Now that we have the new children, see if this node's existing objects would fit there 494 | for (int i = objects.Count - 1; i >= 0; i--) { 495 | OctreeObject existingObj = objects[i]; 496 | // Find which child the object is closest to based on where the 497 | // object's center is located in relation to the octree's center 498 | bestFitChild = BestFitChild(existingObj.Bounds.center); 499 | // Does it fit? 500 | if (Encapsulates(children[bestFitChild].bounds, existingObj.Bounds)) { 501 | children[bestFitChild].SubAdd(existingObj.Obj, existingObj.Bounds); // Go a level deeper 502 | objects.Remove(existingObj); // Remove from here 503 | } 504 | } 505 | } 506 | } 507 | 508 | // Handle the new object we're adding now 509 | int bestFit = BestFitChild(objBounds.center); 510 | if (Encapsulates(children[bestFit].bounds, objBounds)) { 511 | children[bestFit].SubAdd(obj, objBounds); 512 | } 513 | else { 514 | // Didn't fit in a child. We'll have to it to this node instead 515 | OctreeObject newObj = new OctreeObject { Obj = obj, Bounds = objBounds }; 516 | objects.Add(newObj); 517 | } 518 | } 519 | 520 | /// 521 | /// Private counterpart to the public method. 522 | /// 523 | /// Object to remove. 524 | /// 3D bounding box around the object. 525 | /// True if the object was removed successfully. 526 | bool SubRemove(T obj, Bounds objBounds) { 527 | bool removed = false; 528 | 529 | for (int i = 0; i < objects.Count; i++) { 530 | if (objects[i].Obj.Equals(obj)) { 531 | removed = objects.Remove(objects[i]); 532 | break; 533 | } 534 | } 535 | 536 | if (!removed && children != null) { 537 | int bestFitChild = BestFitChild(objBounds.center); 538 | removed = children[bestFitChild].SubRemove(obj, objBounds); 539 | } 540 | 541 | if (removed && children != null) { 542 | // Check if we should merge nodes now that we've removed an item 543 | if (ShouldMerge()) { 544 | Merge(); 545 | } 546 | } 547 | 548 | return removed; 549 | } 550 | 551 | /// 552 | /// Splits the octree into eight children. 553 | /// 554 | void Split() { 555 | float quarter = BaseLength / 4f; 556 | float newLength = BaseLength / 2; 557 | children = new BoundsOctreeNode[8]; 558 | children[0] = new BoundsOctreeNode(newLength, minSize, looseness, Center + new Vector3(-quarter, quarter, -quarter)); 559 | children[1] = new BoundsOctreeNode(newLength, minSize, looseness, Center + new Vector3(quarter, quarter, -quarter)); 560 | children[2] = new BoundsOctreeNode(newLength, minSize, looseness, Center + new Vector3(-quarter, quarter, quarter)); 561 | children[3] = new BoundsOctreeNode(newLength, minSize, looseness, Center + new Vector3(quarter, quarter, quarter)); 562 | children[4] = new BoundsOctreeNode(newLength, minSize, looseness, Center + new Vector3(-quarter, -quarter, -quarter)); 563 | children[5] = new BoundsOctreeNode(newLength, minSize, looseness, Center + new Vector3(quarter, -quarter, -quarter)); 564 | children[6] = new BoundsOctreeNode(newLength, minSize, looseness, Center + new Vector3(-quarter, -quarter, quarter)); 565 | children[7] = new BoundsOctreeNode(newLength, minSize, looseness, Center + new Vector3(quarter, -quarter, quarter)); 566 | } 567 | 568 | /// 569 | /// Merge all children into this node - the opposite of Split. 570 | /// Note: We only have to check one level down since a merge will never happen if the children already have children, 571 | /// since THAT won't happen unless there are already too many objects to merge. 572 | /// 573 | void Merge() { 574 | // Note: We know children != null or we wouldn't be merging 575 | for (int i = 0; i < 8; i++) { 576 | BoundsOctreeNode curChild = children[i]; 577 | int numObjects = curChild.objects.Count; 578 | for (int j = numObjects - 1; j >= 0; j--) { 579 | OctreeObject curObj = curChild.objects[j]; 580 | objects.Add(curObj); 581 | } 582 | } 583 | // Remove the child nodes (and the objects in them - they've been added elsewhere now) 584 | children = null; 585 | } 586 | 587 | /// 588 | /// Checks if outerBounds encapsulates innerBounds. 589 | /// 590 | /// Outer bounds. 591 | /// Inner bounds. 592 | /// True if innerBounds is fully encapsulated by outerBounds. 593 | static bool Encapsulates(Bounds outerBounds, Bounds innerBounds) { 594 | return outerBounds.Contains(innerBounds.min) && outerBounds.Contains(innerBounds.max); 595 | } 596 | 597 | /// 598 | /// Checks if there are few enough objects in this node and its children that the children should all be merged into this. 599 | /// 600 | /// True there are less or the same abount of objects in this and its children than numObjectsAllowed. 601 | bool ShouldMerge() { 602 | int totalObjects = objects.Count; 603 | if (children != null) { 604 | foreach (BoundsOctreeNode child in children) { 605 | if (child.children != null) { 606 | // If any of the *children* have children, there are definitely too many to merge, 607 | // or the child woudl have been merged already 608 | return false; 609 | } 610 | totalObjects += child.objects.Count; 611 | } 612 | } 613 | return totalObjects <= NUM_OBJECTS_ALLOWED; 614 | } 615 | } -------------------------------------------------------------------------------- /Scripts/BoundsOctreeNode.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 098c3a5fece947a4ba701dc1f6723c21 3 | timeCreated: 1481513782 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Scripts/PointOctree.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | 4 | // A Dynamic Octree for storing any objects that can be described as a single point 5 | // See also: BoundsOctree, where objects are described by AABB bounds 6 | // Octree: An octree is a tree data structure which divides 3D space into smaller partitions (nodes) 7 | // and places objects into the appropriate nodes. This allows fast access to objects 8 | // in an area of interest without having to check every object. 9 | // Dynamic: The octree grows or shrinks as required when objects as added or removed 10 | // It also splits and merges nodes as appropriate. There is no maximum depth. 11 | // Nodes have a constant - numObjectsAllowed - which sets the amount of items allowed in a node before it splits. 12 | // T: The content of the octree can be anything, since the bounds data is supplied separately. 13 | 14 | // Originally written for my game Scraps (http://www.scrapsgame.com) but intended to be general-purpose. 15 | // Copyright 2014 Nition, BSD licence (see LICENCE file). www.momentstudio.co.nz 16 | // Unity-based, but could be adapted to work in pure C# 17 | public class PointOctree { 18 | // The total amount of objects currently in the tree 19 | public int Count { get; private set; } 20 | 21 | // Root node of the octree 22 | PointOctreeNode rootNode; 23 | 24 | // Size that the octree was on creation 25 | readonly float initialSize; 26 | 27 | // Minimum side length that a node can be - essentially an alternative to having a max depth 28 | readonly float minSize; 29 | 30 | /// 31 | /// Constructor for the point octree. 32 | /// 33 | /// Size of the sides of the initial node. The octree will never shrink smaller than this. 34 | /// Position of the centre of the initial node. 35 | /// Nodes will stop splitting if the new nodes would be smaller than this. 36 | public PointOctree(float initialWorldSize, Vector3 initialWorldPos, float minNodeSize) { 37 | if (minNodeSize > initialWorldSize) { 38 | Debug.LogWarning("Minimum node size must be at least as big as the initial world size. Was: " + minNodeSize + " Adjusted to: " + initialWorldSize); 39 | minNodeSize = initialWorldSize; 40 | } 41 | Count = 0; 42 | initialSize = initialWorldSize; 43 | minSize = minNodeSize; 44 | rootNode = new PointOctreeNode(initialSize, minSize, initialWorldPos); 45 | } 46 | 47 | // #### PUBLIC METHODS #### 48 | 49 | /// 50 | /// Add an object. 51 | /// 52 | /// Object to add. 53 | /// Position of the object. 54 | public void Add(T obj, Vector3 objPos) { 55 | // Add object or expand the octree until it can be added 56 | int count = 0; // Safety check against infinite/excessive growth 57 | while (!rootNode.Add(obj, objPos)) { 58 | Grow(objPos - rootNode.Center); 59 | if (++count > 20) { 60 | Debug.LogError("Aborted Add operation as it seemed to be going on forever (" + (count - 1) + ") attempts at growing the octree."); 61 | return; 62 | } 63 | } 64 | Count++; 65 | } 66 | 67 | /// 68 | /// Remove an object. Makes the assumption that the object only exists once in the tree. 69 | /// 70 | /// Object to remove. 71 | /// True if the object was removed successfully. 72 | public bool Remove(T obj) { 73 | bool removed = rootNode.Remove(obj); 74 | 75 | // See if we can shrink the octree down now that we've removed the item 76 | if (removed) { 77 | Count--; 78 | Shrink(); 79 | } 80 | 81 | return removed; 82 | } 83 | 84 | /// 85 | /// Removes the specified object at the given position. Makes the assumption that the object only exists once in the tree. 86 | /// 87 | /// Object to remove. 88 | /// Position of the object. 89 | /// True if the object was removed successfully. 90 | public bool Remove(T obj, Vector3 objPos) { 91 | bool removed = rootNode.Remove(obj, objPos); 92 | 93 | // See if we can shrink the octree down now that we've removed the item 94 | if (removed) { 95 | Count--; 96 | Shrink(); 97 | } 98 | 99 | return removed; 100 | } 101 | 102 | /// 103 | /// Returns objects that are within of the specified ray. 104 | /// If none, returns false. Uses supplied list for results. 105 | /// 106 | /// The ray. Passing as ref to improve performance since it won't have to be copied. 107 | /// Maximum distance from the ray to consider 108 | /// Pre-initialized list to populate 109 | /// True if items are found, false if not 110 | public bool GetNearbyNonAlloc(Ray ray, float maxDistance, List nearBy) { 111 | nearBy.Clear(); 112 | rootNode.GetNearby(ref ray, maxDistance, nearBy); 113 | if (nearBy.Count > 0) 114 | return true; 115 | return false; 116 | } 117 | 118 | /// 119 | /// Returns objects that are within of the specified ray. 120 | /// If none, returns an empty array (not null). 121 | /// 122 | /// The ray. Passing as ref to improve performance since it won't have to be copied. 123 | /// Maximum distance from the ray to consider. 124 | /// Objects within range. 125 | public T[] GetNearby(Ray ray, float maxDistance) { 126 | List collidingWith = new List(); 127 | rootNode.GetNearby(ref ray, maxDistance, collidingWith); 128 | return collidingWith.ToArray(); 129 | } 130 | 131 | /// 132 | /// Returns objects that are within of the specified position. 133 | /// If none, returns an empty array (not null). 134 | /// 135 | /// The position. Passing as ref to improve performance since it won't have to be copied. 136 | /// Maximum distance from the position to consider. 137 | /// Objects within range. 138 | public T[] GetNearby(Vector3 position, float maxDistance) { 139 | List collidingWith = new List(); 140 | rootNode.GetNearby(ref position, maxDistance, collidingWith); 141 | return collidingWith.ToArray(); 142 | } 143 | 144 | /// 145 | /// Returns objects that are within of the specified position. 146 | /// If none, returns false. Uses supplied list for results. 147 | /// 148 | /// Maximum distance from the position to consider 149 | /// Pre-initialized list to populate 150 | /// True if items are found, false if not 151 | public bool GetNearbyNonAlloc(Vector3 position, float maxDistance, List nearBy) { 152 | nearBy.Clear(); 153 | rootNode.GetNearby(ref position, maxDistance, nearBy); 154 | if (nearBy.Count > 0) 155 | return true; 156 | return false; 157 | } 158 | 159 | /// 160 | /// Return all objects in the tree. 161 | /// If none, returns an empty array (not null). 162 | /// 163 | /// All objects. 164 | public ICollection GetAll() { 165 | List objects = new List(Count); 166 | rootNode.GetAll(objects); 167 | return objects; 168 | } 169 | 170 | /// 171 | /// Draws node boundaries visually for debugging. 172 | /// Must be called from OnDrawGizmos externally. See also: DrawAllObjects. 173 | /// 174 | public void DrawAllBounds() { 175 | rootNode.DrawAllBounds(); 176 | } 177 | 178 | /// 179 | /// Draws the bounds of all objects in the tree visually for debugging. 180 | /// Must be called from OnDrawGizmos externally. See also: DrawAllBounds. 181 | /// 182 | public void DrawAllObjects() { 183 | rootNode.DrawAllObjects(); 184 | } 185 | 186 | // #### PRIVATE METHODS #### 187 | 188 | /// 189 | /// Grow the octree to fit in all objects. 190 | /// 191 | /// Direction to grow. 192 | void Grow(Vector3 direction) { 193 | int xDirection = direction.x >= 0 ? 1 : -1; 194 | int yDirection = direction.y >= 0 ? 1 : -1; 195 | int zDirection = direction.z >= 0 ? 1 : -1; 196 | PointOctreeNode oldRoot = rootNode; 197 | float half = rootNode.SideLength / 2; 198 | float newLength = rootNode.SideLength * 2; 199 | Vector3 newCenter = rootNode.Center + new Vector3(xDirection * half, yDirection * half, zDirection * half); 200 | 201 | // Create a new, bigger octree root node 202 | rootNode = new PointOctreeNode(newLength, minSize, newCenter); 203 | 204 | if (oldRoot.HasAnyObjects()) { 205 | // Create 7 new octree children to go with the old root as children of the new root 206 | int rootPos = rootNode.BestFitChild(oldRoot.Center); 207 | PointOctreeNode[] children = new PointOctreeNode[8]; 208 | for (int i = 0; i < 8; i++) { 209 | if (i == rootPos) { 210 | children[i] = oldRoot; 211 | } 212 | else { 213 | xDirection = i % 2 == 0 ? -1 : 1; 214 | yDirection = i > 3 ? -1 : 1; 215 | zDirection = (i < 2 || (i > 3 && i < 6)) ? -1 : 1; 216 | children[i] = new PointOctreeNode(oldRoot.SideLength, minSize, newCenter + new Vector3(xDirection * half, yDirection * half, zDirection * half)); 217 | } 218 | } 219 | 220 | // Attach the new children to the new root node 221 | rootNode.SetChildren(children); 222 | } 223 | } 224 | 225 | /// 226 | /// Shrink the octree if possible, else leave it the same. 227 | /// 228 | void Shrink() { 229 | rootNode = rootNode.ShrinkIfPossible(initialSize); 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /Scripts/PointOctree.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9db0c90c9ab9af246b08fa68962659ae 3 | timeCreated: 1481513782 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Scripts/PointOctreeNode.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using UnityEngine; 4 | 5 | // A node in a PointOctree 6 | // Copyright 2014 Nition, BSD licence (see LICENCE file). www.momentstudio.co.nz 7 | public class PointOctreeNode { 8 | // Centre of this node 9 | public Vector3 Center { get; private set; } 10 | 11 | // Length of the sides of this node 12 | public float SideLength { get; private set; } 13 | 14 | // Minimum size for a node in this octree 15 | float minSize; 16 | 17 | // Bounding box that represents this node 18 | Bounds bounds = default(Bounds); 19 | 20 | // Objects in this node 21 | readonly List objects = new List(); 22 | 23 | // Child nodes, if any 24 | PointOctreeNode[] children = null; 25 | 26 | bool HasChildren { get { return children != null; } } 27 | 28 | // bounds of potential children to this node. These are actual size (with looseness taken into account), not base size 29 | Bounds[] childBounds; 30 | 31 | // If there are already NUM_OBJECTS_ALLOWED in a node, we split it into children 32 | // A generally good number seems to be something around 8-15 33 | const int NUM_OBJECTS_ALLOWED = 8; 34 | 35 | // For reverting the bounds size after temporary changes 36 | Vector3 actualBoundsSize; 37 | 38 | // An object in the octree 39 | class OctreeObject { 40 | public T Obj; 41 | public Vector3 Pos; 42 | } 43 | 44 | /// 45 | /// Constructor. 46 | /// 47 | /// Length of this node, not taking looseness into account. 48 | /// Minimum size of nodes in this octree. 49 | /// Centre position of this node. 50 | public PointOctreeNode(float baseLengthVal, float minSizeVal, Vector3 centerVal) { 51 | SetValues(baseLengthVal, minSizeVal, centerVal); 52 | } 53 | 54 | // #### PUBLIC METHODS #### 55 | 56 | /// 57 | /// Add an object. 58 | /// 59 | /// Object to add. 60 | /// Position of the object. 61 | /// 62 | public bool Add(T obj, Vector3 objPos) { 63 | if (!Encapsulates(bounds, objPos)) { 64 | return false; 65 | } 66 | SubAdd(obj, objPos); 67 | return true; 68 | } 69 | 70 | /// 71 | /// Remove an object. Makes the assumption that the object only exists once in the tree. 72 | /// 73 | /// Object to remove. 74 | /// True if the object was removed successfully. 75 | public bool Remove(T obj) { 76 | bool removed = false; 77 | 78 | for (int i = 0; i < objects.Count; i++) { 79 | if (objects[i].Obj.Equals(obj)) { 80 | removed = objects.Remove(objects[i]); 81 | break; 82 | } 83 | } 84 | 85 | if (!removed && children != null) { 86 | for (int i = 0; i < 8; i++) { 87 | removed = children[i].Remove(obj); 88 | if (removed) break; 89 | } 90 | } 91 | 92 | if (removed && children != null) { 93 | // Check if we should merge nodes now that we've removed an item 94 | if (ShouldMerge()) { 95 | Merge(); 96 | } 97 | } 98 | 99 | return removed; 100 | } 101 | 102 | /// 103 | /// Removes the specified object at the given position. Makes the assumption that the object only exists once in the tree. 104 | /// 105 | /// Object to remove. 106 | /// Position of the object. 107 | /// True if the object was removed successfully. 108 | public bool Remove(T obj, Vector3 objPos) { 109 | if (!Encapsulates(bounds, objPos)) { 110 | return false; 111 | } 112 | return SubRemove(obj, objPos); 113 | } 114 | 115 | /// 116 | /// Return objects that are within maxDistance of the specified ray. 117 | /// 118 | /// The ray. 119 | /// Maximum distance from the ray to consider. 120 | /// List result. 121 | /// Objects within range. 122 | public void GetNearby(ref Ray ray, float maxDistance, List result) { 123 | // Does the ray hit this node at all? 124 | // Note: Expanding the bounds is not exactly the same as a real distance check, but it's fast. 125 | // TODO: Does someone have a fast AND accurate formula to do this check? 126 | bounds.Expand(new Vector3(maxDistance * 2, maxDistance * 2, maxDistance * 2)); 127 | bool intersected = bounds.IntersectRay(ray); 128 | bounds.size = actualBoundsSize; 129 | if (!intersected) { 130 | return; 131 | } 132 | 133 | // Check against any objects in this node 134 | for (int i = 0; i < objects.Count; i++) { 135 | if (SqrDistanceToRay(ray, objects[i].Pos) <= (maxDistance * maxDistance)) { 136 | result.Add(objects[i].Obj); 137 | } 138 | } 139 | 140 | // Check children 141 | if (children != null) { 142 | for (int i = 0; i < 8; i++) { 143 | children[i].GetNearby(ref ray, maxDistance, result); 144 | } 145 | } 146 | } 147 | 148 | /// 149 | /// Return objects that are within of the specified position. 150 | /// 151 | /// The position. 152 | /// Maximum distance from the position to consider. 153 | /// List result. 154 | /// Objects within range. 155 | public void GetNearby(ref Vector3 position, float maxDistance, List result) { 156 | float sqrMaxDistance = maxDistance * maxDistance; 157 | 158 | #if UNITY_2017_1_OR_NEWER 159 | // Does the node intersect with the sphere of center = position and radius = maxDistance? 160 | if ((bounds.ClosestPoint(position) - position).sqrMagnitude > sqrMaxDistance) { 161 | return; 162 | } 163 | #else 164 | // Does the ray hit this node at all? 165 | // Note: Expanding the bounds is not exactly the same as a real distance check, but it's fast 166 | // TODO: Does someone have a fast AND accurate formula to do this check? 167 | bounds.Expand(new Vector3(maxDistance * 2, maxDistance * 2, maxDistance * 2)); 168 | bool contained = bounds.Contains(position); 169 | bounds.size = actualBoundsSize; 170 | if (!contained) { 171 | return; 172 | } 173 | #endif 174 | 175 | // Check against any objects in this node 176 | for (int i = 0; i < objects.Count; i++) { 177 | if ((position - objects[i].Pos).sqrMagnitude <= sqrMaxDistance) { 178 | result.Add(objects[i].Obj); 179 | } 180 | } 181 | 182 | // Check children 183 | if (children != null) { 184 | for (int i = 0; i < 8; i++) { 185 | children[i].GetNearby(ref position, maxDistance, result); 186 | } 187 | } 188 | } 189 | 190 | /// 191 | /// Return all objects in the tree. 192 | /// 193 | /// All objects. 194 | public void GetAll(List result) { 195 | // add directly contained objects 196 | result.AddRange(objects.Select(o => o.Obj)); 197 | 198 | // add children objects 199 | if (children != null) { 200 | for (int i = 0; i < 8; i++) { 201 | children[i].GetAll(result); 202 | } 203 | } 204 | } 205 | 206 | /// 207 | /// Set the 8 children of this octree. 208 | /// 209 | /// The 8 new child nodes. 210 | public void SetChildren(PointOctreeNode[] childOctrees) { 211 | if (childOctrees.Length != 8) { 212 | Debug.LogError("Child octree array must be length 8. Was length: " + childOctrees.Length); 213 | return; 214 | } 215 | 216 | children = childOctrees; 217 | } 218 | 219 | /// 220 | /// Draws node boundaries visually for debugging. 221 | /// Must be called from OnDrawGizmos externally. See also: DrawAllObjects. 222 | /// 223 | /// Used for recurcive calls to this method. 224 | public void DrawAllBounds(float depth = 0) { 225 | float tintVal = depth / 7; // Will eventually get values > 1. Color rounds to 1 automatically 226 | Gizmos.color = new Color(tintVal, 0, 1.0f - tintVal); 227 | 228 | Bounds thisBounds = new Bounds(Center, new Vector3(SideLength, SideLength, SideLength)); 229 | Gizmos.DrawWireCube(thisBounds.center, thisBounds.size); 230 | 231 | if (children != null) { 232 | depth++; 233 | for (int i = 0; i < 8; i++) { 234 | children[i].DrawAllBounds(depth); 235 | } 236 | } 237 | Gizmos.color = Color.white; 238 | } 239 | 240 | /// 241 | /// Draws the bounds of all objects in the tree visually for debugging. 242 | /// Must be called from OnDrawGizmos externally. See also: DrawAllBounds. 243 | /// NOTE: marker.tif must be placed in your Unity /Assets/Gizmos subfolder for this to work. 244 | /// 245 | public void DrawAllObjects() { 246 | float tintVal = SideLength / 20; 247 | Gizmos.color = new Color(0, 1.0f - tintVal, tintVal, 0.25f); 248 | 249 | foreach (OctreeObject obj in objects) { 250 | Gizmos.DrawIcon(obj.Pos, "marker.tif", true); 251 | } 252 | 253 | if (children != null) { 254 | for (int i = 0; i < 8; i++) { 255 | children[i].DrawAllObjects(); 256 | } 257 | } 258 | 259 | Gizmos.color = Color.white; 260 | } 261 | 262 | /// 263 | /// We can shrink the octree if: 264 | /// - This node is >= double minLength in length 265 | /// - All objects in the root node are within one octant 266 | /// - This node doesn't have children, or does but 7/8 children are empty 267 | /// We can also shrink it if there are no objects left at all! 268 | /// 269 | /// Minimum dimensions of a node in this octree. 270 | /// The new root, or the existing one if we didn't shrink. 271 | public PointOctreeNode ShrinkIfPossible(float minLength) { 272 | if (SideLength < (2 * minLength)) { 273 | return this; 274 | } 275 | if (objects.Count == 0 && (children == null || children.Length == 0)) { 276 | return this; 277 | } 278 | 279 | // Check objects in root 280 | int bestFit = -1; 281 | for (int i = 0; i < objects.Count; i++) { 282 | OctreeObject curObj = objects[i]; 283 | int newBestFit = BestFitChild(curObj.Pos); 284 | if (i == 0 || newBestFit == bestFit) { 285 | if (bestFit < 0) { 286 | bestFit = newBestFit; 287 | } 288 | } 289 | else { 290 | return this; // Can't reduce - objects fit in different octants 291 | } 292 | } 293 | 294 | // Check objects in children if there are any 295 | if (children != null) { 296 | bool childHadContent = false; 297 | for (int i = 0; i < children.Length; i++) { 298 | if (children[i].HasAnyObjects()) { 299 | if (childHadContent) { 300 | return this; // Can't shrink - another child had content already 301 | } 302 | if (bestFit >= 0 && bestFit != i) { 303 | return this; // Can't reduce - objects in root are in a different octant to objects in child 304 | } 305 | childHadContent = true; 306 | bestFit = i; 307 | } 308 | } 309 | } 310 | 311 | // Can reduce 312 | if (children == null) { 313 | // We don't have any children, so just shrink this node to the new size 314 | // We already know that everything will still fit in it 315 | SetValues(SideLength / 2, minSize, childBounds[bestFit].center); 316 | return this; 317 | } 318 | 319 | // We have children. Use the appropriate child as the new root node 320 | return children[bestFit]; 321 | } 322 | 323 | /// 324 | /// Find which child node this object would be most likely to fit in. 325 | /// 326 | /// The object's position. 327 | /// One of the eight child octants. 328 | public int BestFitChild(Vector3 objPos) { 329 | return (objPos.x <= Center.x ? 0 : 1) + (objPos.y >= Center.y ? 0 : 4) + (objPos.z <= Center.z ? 0 : 2); 330 | } 331 | 332 | /// 333 | /// Checks if this node or anything below it has something in it. 334 | /// 335 | /// True if this node or any of its children, grandchildren etc have something in them 336 | public bool HasAnyObjects() { 337 | if (objects.Count > 0) return true; 338 | 339 | if (children != null) { 340 | for (int i = 0; i < 8; i++) { 341 | if (children[i].HasAnyObjects()) return true; 342 | } 343 | } 344 | 345 | return false; 346 | } 347 | 348 | /* 349 | /// 350 | /// Get the total amount of objects in this node and all its children, grandchildren etc. Useful for debugging. 351 | /// 352 | /// Used by recursive calls to add to the previous total. 353 | /// Total objects in this node and its children, grandchildren etc. 354 | public int GetTotalObjects(int startingNum = 0) { 355 | int totalObjects = startingNum + objects.Count; 356 | if (children != null) { 357 | for (int i = 0; i < 8; i++) { 358 | totalObjects += children[i].GetTotalObjects(); 359 | } 360 | } 361 | return totalObjects; 362 | } 363 | */ 364 | 365 | // #### PRIVATE METHODS #### 366 | 367 | /// 368 | /// Set values for this node. 369 | /// 370 | /// Length of this node, not taking looseness into account. 371 | /// Minimum size of nodes in this octree. 372 | /// Centre position of this node. 373 | void SetValues(float baseLengthVal, float minSizeVal, Vector3 centerVal) { 374 | SideLength = baseLengthVal; 375 | minSize = minSizeVal; 376 | Center = centerVal; 377 | 378 | // Create the bounding box. 379 | actualBoundsSize = new Vector3(SideLength, SideLength, SideLength); 380 | bounds = new Bounds(Center, actualBoundsSize); 381 | 382 | float quarter = SideLength / 4f; 383 | float childActualLength = SideLength / 2; 384 | Vector3 childActualSize = new Vector3(childActualLength, childActualLength, childActualLength); 385 | childBounds = new Bounds[8]; 386 | childBounds[0] = new Bounds(Center + new Vector3(-quarter, quarter, -quarter), childActualSize); 387 | childBounds[1] = new Bounds(Center + new Vector3(quarter, quarter, -quarter), childActualSize); 388 | childBounds[2] = new Bounds(Center + new Vector3(-quarter, quarter, quarter), childActualSize); 389 | childBounds[3] = new Bounds(Center + new Vector3(quarter, quarter, quarter), childActualSize); 390 | childBounds[4] = new Bounds(Center + new Vector3(-quarter, -quarter, -quarter), childActualSize); 391 | childBounds[5] = new Bounds(Center + new Vector3(quarter, -quarter, -quarter), childActualSize); 392 | childBounds[6] = new Bounds(Center + new Vector3(-quarter, -quarter, quarter), childActualSize); 393 | childBounds[7] = new Bounds(Center + new Vector3(quarter, -quarter, quarter), childActualSize); 394 | } 395 | 396 | /// 397 | /// Private counterpart to the public Add method. 398 | /// 399 | /// Object to add. 400 | /// Position of the object. 401 | void SubAdd(T obj, Vector3 objPos) { 402 | // We know it fits at this level if we've got this far 403 | 404 | // We always put things in the deepest possible child 405 | // So we can skip checks and simply move down if there are children aleady 406 | if (!HasChildren) { 407 | // Just add if few objects are here, or children would be below min size 408 | if (objects.Count < NUM_OBJECTS_ALLOWED || (SideLength / 2) < minSize) { 409 | OctreeObject newObj = new OctreeObject { Obj = obj, Pos = objPos }; 410 | objects.Add(newObj); 411 | return; // We're done. No children yet 412 | } 413 | 414 | // Enough objects in this node already: Create the 8 children 415 | int bestFitChild; 416 | if (children == null) { 417 | Split(); 418 | if (children == null) { 419 | Debug.LogError("Child creation failed for an unknown reason. Early exit."); 420 | return; 421 | } 422 | 423 | // Now that we have the new children, move this node's existing objects into them 424 | for (int i = objects.Count - 1; i >= 0; i--) { 425 | OctreeObject existingObj = objects[i]; 426 | // Find which child the object is closest to based on where the 427 | // object's center is located in relation to the octree's center 428 | bestFitChild = BestFitChild(existingObj.Pos); 429 | children[bestFitChild].SubAdd(existingObj.Obj, existingObj.Pos); // Go a level deeper 430 | objects.Remove(existingObj); // Remove from here 431 | } 432 | } 433 | } 434 | 435 | // Handle the new object we're adding now 436 | int bestFit = BestFitChild(objPos); 437 | children[bestFit].SubAdd(obj, objPos); 438 | } 439 | 440 | /// 441 | /// Private counterpart to the public method. 442 | /// 443 | /// Object to remove. 444 | /// Position of the object. 445 | /// True if the object was removed successfully. 446 | bool SubRemove(T obj, Vector3 objPos) { 447 | bool removed = false; 448 | 449 | for (int i = 0; i < objects.Count; i++) { 450 | if (objects[i].Obj.Equals(obj)) { 451 | removed = objects.Remove(objects[i]); 452 | break; 453 | } 454 | } 455 | 456 | if (!removed && children != null) { 457 | int bestFitChild = BestFitChild(objPos); 458 | removed = children[bestFitChild].SubRemove(obj, objPos); 459 | } 460 | 461 | if (removed && children != null) { 462 | // Check if we should merge nodes now that we've removed an item 463 | if (ShouldMerge()) { 464 | Merge(); 465 | } 466 | } 467 | 468 | return removed; 469 | } 470 | 471 | /// 472 | /// Splits the octree into eight children. 473 | /// 474 | void Split() { 475 | float quarter = SideLength / 4f; 476 | float newLength = SideLength / 2; 477 | children = new PointOctreeNode[8]; 478 | children[0] = new PointOctreeNode(newLength, minSize, Center + new Vector3(-quarter, quarter, -quarter)); 479 | children[1] = new PointOctreeNode(newLength, minSize, Center + new Vector3(quarter, quarter, -quarter)); 480 | children[2] = new PointOctreeNode(newLength, minSize, Center + new Vector3(-quarter, quarter, quarter)); 481 | children[3] = new PointOctreeNode(newLength, minSize, Center + new Vector3(quarter, quarter, quarter)); 482 | children[4] = new PointOctreeNode(newLength, minSize, Center + new Vector3(-quarter, -quarter, -quarter)); 483 | children[5] = new PointOctreeNode(newLength, minSize, Center + new Vector3(quarter, -quarter, -quarter)); 484 | children[6] = new PointOctreeNode(newLength, minSize, Center + new Vector3(-quarter, -quarter, quarter)); 485 | children[7] = new PointOctreeNode(newLength, minSize, Center + new Vector3(quarter, -quarter, quarter)); 486 | } 487 | 488 | /// 489 | /// Merge all children into this node - the opposite of Split. 490 | /// Note: We only have to check one level down since a merge will never happen if the children already have children, 491 | /// since THAT won't happen unless there are already too many objects to merge. 492 | /// 493 | void Merge() { 494 | // Note: We know children != null or we wouldn't be merging 495 | for (int i = 0; i < 8; i++) { 496 | PointOctreeNode curChild = children[i]; 497 | int numObjects = curChild.objects.Count; 498 | for (int j = numObjects - 1; j >= 0; j--) { 499 | OctreeObject curObj = curChild.objects[j]; 500 | objects.Add(curObj); 501 | } 502 | } 503 | // Remove the child nodes (and the objects in them - they've been added elsewhere now) 504 | children = null; 505 | } 506 | 507 | /// 508 | /// Checks if outerBounds encapsulates the given point. 509 | /// 510 | /// Outer bounds. 511 | /// Point. 512 | /// True if innerBounds is fully encapsulated by outerBounds. 513 | static bool Encapsulates(Bounds outerBounds, Vector3 point) { 514 | return outerBounds.Contains(point); 515 | } 516 | 517 | /// 518 | /// Checks if there are few enough objects in this node and its children that the children should all be merged into this. 519 | /// 520 | /// True there are less or the same abount of objects in this and its children than numObjectsAllowed. 521 | bool ShouldMerge() { 522 | int totalObjects = objects.Count; 523 | if (children != null) { 524 | foreach (PointOctreeNode child in children) { 525 | if (child.children != null) { 526 | // If any of the *children* have children, there are definitely too many to merge, 527 | // or the child woudl have been merged already 528 | return false; 529 | } 530 | totalObjects += child.objects.Count; 531 | } 532 | } 533 | return totalObjects <= NUM_OBJECTS_ALLOWED; 534 | } 535 | 536 | /// 537 | /// Returns the closest distance to the given ray from a point. 538 | /// 539 | /// The ray. 540 | /// The point to check distance from the ray. 541 | /// Squared distance from the point to the closest point of the ray. 542 | public static float SqrDistanceToRay(Ray ray, Vector3 point) { 543 | return Vector3.Cross(ray.direction, point - ray.origin).sqrMagnitude; 544 | } 545 | } 546 | -------------------------------------------------------------------------------- /Scripts/PointOctreeNode.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7a3117d130f3f2b46b4761386f25c3b8 3 | timeCreated: 1481513782 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /marker.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nition/UnityOctree/bbc473571c8a0879024077a928ca658a52ecbd99/marker.tif -------------------------------------------------------------------------------- /marker.tif.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1107feee395f6bf449de6678527683c0 3 | timeCreated: 1481513783 4 | licenseType: Pro 5 | TextureImporter: 6 | fileIDToRecycleName: {} 7 | serializedVersion: 2 8 | mipmaps: 9 | mipMapMode: 0 10 | enableMipMap: 1 11 | linearTexture: 0 12 | correctGamma: 0 13 | fadeOut: 0 14 | borderMipMap: 0 15 | mipMapFadeDistanceStart: 1 16 | mipMapFadeDistanceEnd: 3 17 | bumpmap: 18 | convertToNormalMap: 0 19 | externalNormalMap: 0 20 | heightScale: 0.25 21 | normalMapFilter: 0 22 | isReadable: 0 23 | grayScaleToAlpha: 0 24 | generateCubemap: 0 25 | cubemapConvolution: 0 26 | cubemapConvolutionSteps: 7 27 | cubemapConvolutionExponent: 1.5 28 | seamlessCubemap: 0 29 | textureFormat: -1 30 | maxTextureSize: 2048 31 | textureSettings: 32 | filterMode: -1 33 | aniso: -1 34 | mipBias: -1 35 | wrapMode: -1 36 | nPOTScale: 1 37 | lightmap: 0 38 | rGBM: 0 39 | compressionQuality: 50 40 | allowsAlphaSplitting: 0 41 | spriteMode: 0 42 | spriteExtrude: 1 43 | spriteMeshType: 1 44 | alignment: 0 45 | spritePivot: {x: 0.5, y: 0.5} 46 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 47 | spritePixelsToUnits: 100 48 | alphaIsTransparency: 0 49 | spriteTessellationDetail: -1 50 | textureType: -1 51 | buildTargetSettings: [] 52 | spriteSheet: 53 | serializedVersion: 2 54 | sprites: [] 55 | outline: [] 56 | spritePackingTag: 57 | userData: 58 | assetBundleName: 59 | assetBundleVariant: 60 | -------------------------------------------------------------------------------- /octree-visualisation.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nition/UnityOctree/bbc473571c8a0879024077a928ca658a52ecbd99/octree-visualisation.jpg -------------------------------------------------------------------------------- /octree-visualisation.jpg.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 65b1ef2cd0149d54eb93c516252937b8 3 | timeCreated: 1481513784 4 | licenseType: Pro 5 | TextureImporter: 6 | fileIDToRecycleName: {} 7 | serializedVersion: 2 8 | mipmaps: 9 | mipMapMode: 0 10 | enableMipMap: 1 11 | linearTexture: 0 12 | correctGamma: 0 13 | fadeOut: 0 14 | borderMipMap: 0 15 | mipMapFadeDistanceStart: 1 16 | mipMapFadeDistanceEnd: 3 17 | bumpmap: 18 | convertToNormalMap: 0 19 | externalNormalMap: 0 20 | heightScale: 0.25 21 | normalMapFilter: 0 22 | isReadable: 0 23 | grayScaleToAlpha: 0 24 | generateCubemap: 0 25 | cubemapConvolution: 0 26 | cubemapConvolutionSteps: 7 27 | cubemapConvolutionExponent: 1.5 28 | seamlessCubemap: 0 29 | textureFormat: -1 30 | maxTextureSize: 2048 31 | textureSettings: 32 | filterMode: -1 33 | aniso: -1 34 | mipBias: -1 35 | wrapMode: -1 36 | nPOTScale: 1 37 | lightmap: 0 38 | rGBM: 0 39 | compressionQuality: 50 40 | allowsAlphaSplitting: 0 41 | spriteMode: 0 42 | spriteExtrude: 1 43 | spriteMeshType: 1 44 | alignment: 0 45 | spritePivot: {x: 0.5, y: 0.5} 46 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 47 | spritePixelsToUnits: 100 48 | alphaIsTransparency: 0 49 | spriteTessellationDetail: -1 50 | textureType: -1 51 | buildTargetSettings: [] 52 | spriteSheet: 53 | serializedVersion: 2 54 | sprites: [] 55 | outline: [] 56 | spritePackingTag: 57 | userData: 58 | assetBundleName: 59 | assetBundleVariant: 60 | --------------------------------------------------------------------------------