├── Octree ├── .gitignore ├── icon.png ├── Octree.csproj.DotSettings ├── Data │ ├── MathExtensions.cs │ ├── Ray.cs │ └── BoundingBox.cs ├── Octree.csproj ├── NuGetReadme.md ├── PointOctree.cs ├── BoundsOctree.cs ├── PointOctreeNode.cs └── BoundsOctreeNode.cs ├── Octree.Tests ├── BoundOctreeTest.cs ├── PointOctreeTest.cs ├── Octree.Tests.csproj └── DataStructureTest.cs ├── .github └── workflows │ └── ci.yml ├── Octree.sln.DotSettings ├── LICENSE ├── Octree.sln ├── .gitignore └── README.md /Octree/.gitignore: -------------------------------------------------------------------------------- 1 | Octree.xml -------------------------------------------------------------------------------- /Octree/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcserep/NetOctree/HEAD/Octree/icon.png -------------------------------------------------------------------------------- /Octree.Tests/BoundOctreeTest.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcserep/NetOctree/HEAD/Octree.Tests/BoundOctreeTest.cs -------------------------------------------------------------------------------- /Octree.Tests/PointOctreeTest.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcserep/NetOctree/HEAD/Octree.Tests/PointOctreeTest.cs -------------------------------------------------------------------------------- /Octree/Octree.csproj.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | True -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Build project 2 | on: 3 | push: 4 | branches: [ master ] 5 | pull_request: 6 | branches: [ master ] 7 | jobs: 8 | build: 9 | runs-on: windows-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Setup .NET Core 13 | uses: actions/setup-dotnet@v1 14 | with: 15 | dotnet-version: '3.1.x' 16 | - name: Install dependencies 17 | run: dotnet restore 18 | - name: Build 19 | run: dotnet build --configuration Release --no-restore 20 | - name: Run unit tests 21 | run: dotnet test Octree.sln --collect="XPlat Code Coverage" 22 | - uses: codecov/codecov-action@v2 23 | -------------------------------------------------------------------------------- /Octree.sln.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | <copyright file="$FILENAME$"> 3 | Distributed under the BSD Licence (see LICENCE file). 4 | 5 | Copyright (c) 2014, Nition, http://www.momentstudio.co.nz/ 6 | Copyright (c) 2017, Máté Cserép, http://codenet.hu 7 | All rights reserved. 8 | </copyright> -------------------------------------------------------------------------------- /Octree/Data/MathExtensions.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Distributed under the BSD Licence (see LICENCE file). 3 | // 4 | // Copyright (c) 2014, Nition, http://www.momentstudio.co.nz/ 5 | // Copyright (c) 2017, Máté Cserép, http://codenet.hu 6 | // All rights reserved. 7 | // 8 | namespace Octree 9 | { 10 | /// 11 | /// Auxiliary mathematical functions. 12 | /// 13 | public static class MathExtensions 14 | { 15 | /// 16 | /// Clamps a value between a minimum and maximum value. 17 | /// 18 | /// The value. 19 | /// The minimum. 20 | /// The maximum. 21 | /// The clamped value. 22 | public static float Clamp(float value, float min, float max) 23 | { 24 | if (value < min) 25 | return min; 26 | if (value > max) 27 | return max; 28 | return value; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Octree.Tests/Octree.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | Máté Cserép 6 | Copyright © Máté Cserép 2022 7 | 8 | false 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | runtime; build; native; contentfiles; analyzers; buildtransitive 17 | all 18 | 19 | 20 | runtime; build; native; contentfiles; analyzers; buildtransitive 21 | all 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /Octree/Octree.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.1;net462 5 | NetOctree 6 | A dynamic, loose octree implementation written in C#. 7 | Ericsson 8 | Máté Cserép 9 | Copyright © Máté Cserép 2017-2022 10 | https://github.com/mcserep/NetOctree 11 | BSD-2-Clause 12 | icon.png 13 | false 14 | NuGetReadme.md 15 | true 16 | 2.1.0 17 | 18 | 19 | 20 | Octree.xml 21 | 22 | 23 | 24 | 25 | 26 | True 27 | 28 | 29 | 30 | True 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2014, Nition, http://www.momentstudio.co.nz/ 4 | Copyright (c) 2017, Máté Cserép, http://codenet.hu 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 21 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 23 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 24 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 25 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /Octree.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.3.32811.315 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Octree", "Octree\Octree.csproj", "{B6B62BB9-05AF-40AF-81C7-B72355C09C34}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Octree.Tests", "Octree.Tests\Octree.Tests.csproj", "{41ED2C33-EB3F-4CDE-A806-99F6A6FEA283}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {B6B62BB9-05AF-40AF-81C7-B72355C09C34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {B6B62BB9-05AF-40AF-81C7-B72355C09C34}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {B6B62BB9-05AF-40AF-81C7-B72355C09C34}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {B6B62BB9-05AF-40AF-81C7-B72355C09C34}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {41ED2C33-EB3F-4CDE-A806-99F6A6FEA283}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {41ED2C33-EB3F-4CDE-A806-99F6A6FEA283}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {41ED2C33-EB3F-4CDE-A806-99F6A6FEA283}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {41ED2C33-EB3F-4CDE-A806-99F6A6FEA283}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {5BA9E959-754B-4C40-A481-BEABD361A6C2} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /Octree/Data/Ray.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Distributed under the BSD Licence (see LICENCE file). 3 | // 4 | // Copyright (c) 2014, Nition, http://www.momentstudio.co.nz/ 5 | // Copyright (c) 2017, Máté Cserép, http://codenet.hu 6 | // All rights reserved. 7 | // 8 | namespace Octree 9 | { 10 | using System; 11 | using System.Numerics; 12 | using System.Runtime.Serialization; 13 | 14 | /// 15 | /// Representation of rays. 16 | /// 17 | /// 18 | /// A ray is an infinite line starting at and going in some . 19 | /// 20 | /// This class was inspired by the Ray type of the Unity Engine and 21 | /// designed with the exact same interface to provide maximum compatibility. 22 | /// 23 | [DataContract] 24 | public struct Ray 25 | { 26 | /// 27 | /// Gets or sets the origin of the ray. 28 | /// 29 | [DataMember] 30 | public Vector3 Origin { get; set; } 31 | 32 | /// 33 | /// The direction of the ray. 34 | /// 35 | [DataMember] 36 | private Vector3 _direction; 37 | /// 38 | /// Gets or sets the direction of the ray. 39 | /// 40 | public Vector3 Direction 41 | { 42 | get { return _direction; } 43 | set { _direction = Vector3.Normalize(value); } 44 | } 45 | 46 | /// 47 | /// Creates a ray starting at origin along direction. 48 | /// 49 | /// The origin of the ray. 50 | /// The direction of the ray. 51 | public Ray(Vector3 origin, Vector3 direction) 52 | { 53 | Origin = origin; 54 | _direction = Vector3.Normalize(direction); 55 | } 56 | 57 | /// 58 | /// Returns a point at the given distance along the ray. 59 | /// 60 | /// The distance. 61 | /// The point on the ray. 62 | public Vector3 GetPoint(float distance) 63 | { 64 | return Origin + Direction * distance; 65 | } 66 | 67 | /// 68 | /// Returns a nicely formatted string for this ray. 69 | /// 70 | public override string ToString() 71 | { 72 | return String.Format("Origin: {0}, Dir: {1}", 73 | Origin, 74 | Direction 75 | ); 76 | } 77 | 78 | /// 79 | /// Returns a nicely formatted string for this ray. 80 | /// 81 | /// The format for the origin and direction. 82 | public string ToString(string format) 83 | { 84 | return String.Format("Origin: {0}, Dir: {1}", 85 | Origin.ToString(format), 86 | Direction.ToString(format) 87 | ); 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /Octree.Tests/DataStructureTest.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Distributed under the BSD Licence (see LICENCE file). 3 | // Copyright (c) 2022, Máté Cserép, https://codenet.hu 4 | // All rights reserved. 5 | // 6 | 7 | namespace Octree.Tests 8 | { 9 | using System; 10 | using System.Collections.Generic; 11 | using System.Numerics; 12 | using System.Text; 13 | using Shouldly; 14 | using Xunit; 15 | 16 | public class DataStructureTest 17 | { 18 | /// 19 | /// Tests methods class. 20 | /// 21 | [Fact] 22 | public void RayTest() 23 | { 24 | Ray ray = new Ray(Vector3.UnitX, Vector3.One); 25 | ray.Origin.ShouldBe(Vector3.UnitX); 26 | ray.Direction.ShouldBe(Vector3.Normalize(Vector3.One)); 27 | ray.GetPoint(2).ShouldBe(Vector3.UnitX + Vector3.Normalize(Vector3.One) * 2); 28 | } 29 | 30 | /// 31 | /// Tests methods class. 32 | /// 33 | [Fact] 34 | public void BoundingBoxTest() 35 | { 36 | // Test ctor + getters 37 | BoundingBox box = new BoundingBox(Vector3.One, new Vector3(1, 2, 3)); 38 | box.Center.ShouldBe(Vector3.One); 39 | box.Extents.ShouldBe(new Vector3(0.5f, 1f, 1.5f)); 40 | box.Min.ShouldBe(new Vector3(0.5f, 0f, -0.5f)); 41 | box.Max.ShouldBe(new Vector3(1.5f, 2f, 2.5f)); 42 | 43 | // Test Encapsulate() 44 | box.Encapsulate(new Vector3(5, 0, 0)); 45 | box.Center.ShouldBe(new Vector3(2.75f, 1f, 1f)); 46 | box.Extents.ShouldBe(new Vector3(2.25f, 1f, 1.5f)); 47 | box.Min.ShouldBe(new Vector3(0.5f, 0f, -0.5f)); 48 | box.Max.ShouldBe(new Vector3(5f, 2f, 2.5f)); 49 | 50 | // Test Expand() 51 | box.Expand(1); 52 | box.Center.ShouldBe(new Vector3(2.75f, 1f, 1f)); 53 | box.Extents.ShouldBe(new Vector3(2.75f, 1.5f, 2f)); 54 | box.Min.ShouldBe(new Vector3(0f, -0.5f, -1f)); 55 | box.Max.ShouldBe(new Vector3(5.5f, 2.5f, 3f)); 56 | 57 | // Test SetMinMax() 58 | box.SetMinMax(new Vector3(-1), new Vector3(3)); 59 | box.Center.ShouldBe(Vector3.One); 60 | box.Extents.ShouldBe(new Vector3(2)); 61 | box.Min.ShouldBe(new Vector3(-1)); 62 | box.Max.ShouldBe(new Vector3(3)); 63 | 64 | // Test Contains() 65 | box.Contains(Vector3.Zero).ShouldBeTrue(); 66 | box.Contains(new Vector3(3)).ShouldBeTrue(); 67 | box.Contains(new Vector3(4)).ShouldBeFalse(); 68 | box.Contains(new Vector3(3, 3, 3.1f)).ShouldBeFalse(); 69 | 70 | // Test Intersects() 71 | box.Intersects(new BoundingBox(new Vector3(4), Vector3.One)).ShouldBeFalse(); 72 | box.Intersects(new BoundingBox(new Vector3(4), new Vector3(2))).ShouldBeTrue(); // touches 73 | box.Intersects(new BoundingBox(new Vector3(4), new Vector3(3))).ShouldBeTrue(); 74 | } 75 | 76 | /// 77 | /// Tests the method. 78 | /// 79 | [Fact] 80 | public void BoundingBoxRayIntersectionTest() 81 | { 82 | Ray ray = new Ray(Vector3.UnitX, Vector3.One); 83 | new BoundingBox(new Vector3(3), new Vector3(0.5f)).IntersectRay(ray).ShouldBeFalse(); 84 | new BoundingBox(new Vector3(3), new Vector3(0.9f)).IntersectRay(ray).ShouldBeFalse(); 85 | new BoundingBox(new Vector3(3), new Vector3(1f)).IntersectRay(ray).ShouldBeTrue(); // touches 86 | new BoundingBox(new Vector3(3), new Vector3(2f)).IntersectRay(ray).ShouldBeTrue(); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Octree/NuGetReadme.md: -------------------------------------------------------------------------------- 1 | .NET Octree 2 | =========== 3 | 4 | A dynamic octree implementation written in C# as a .NET Standard 2.1 library, built on the `System.Numerics` library. 5 | 6 | Description 7 | ----------- 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, position, 1, 1.25f); 44 | // Initial size (metres), initial centre position, minimum node size (metres) 45 | PointOctree pointTree = new PointOctree(15, position, 1); 46 | ``` 47 | 48 | - 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. 49 | - The initial position should ideally be in the centre of where your objects are. 50 | - 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. 51 | - 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. 52 | 53 | **Add and Remove** 54 | 55 | ```C# 56 | boundsTree.Add(myObject, myBounds); 57 | boundsTree.Remove(myObject); 58 | 59 | pointTree.Add(myObject, myVector3); 60 | boundsTree.Remove(myObject); 61 | ``` 62 | - The object's type depends on the tree's type. 63 | - The bounds or point determine where it's inserted. 64 | 65 | **Search in the Octree** 66 | 67 | ```C# 68 | bool isColliding = boundsTree.IsColliding(bounds); 69 | ``` 70 | 71 | ```C# 72 | DataType[] collidingWith = boundsTree.GetColliding(bounds); 73 | ``` 74 | - Where `DataType` is the type of the octree 75 | 76 | ```C# 77 | DataType[] nearby = pointTree.GetNearby(myRay, 4); 78 | ``` 79 | - Where `myRay` is a `Ray` 80 | - In this case we're looking for any point within 4m of the closest point on the ray 81 | 82 | ```C# 83 | DataType[] nearby = pointTree.GetNearby(myPos, 4); 84 | ``` 85 | - Where `myPos` is a `Vector3` from `System.Numerics` 86 | 87 | **Non-Alloc query functions** 88 | 89 | A pre-initialized list can be used to store the results, which can be useful when executing a large number of queries with potentially large result sets. 90 | 91 | ```C# 92 | List collidingWith = new List(); 93 | boundsTree.GetColliding(collidingWith, bounds); 94 | ``` 95 | 96 | ```C# 97 | pointTree.GetNearby(myRay, 4, collidingWith); 98 | ``` 99 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # .NET Core 46 | project.lock.json 47 | project.fragment.lock.json 48 | artifacts/ 49 | **/Properties/launchSettings.json 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # Visual Studio code coverage results 117 | *.coverage 118 | *.coveragexml 119 | 120 | # NCrunch 121 | _NCrunch_* 122 | .*crunch*.local.xml 123 | nCrunchTemp_* 124 | 125 | # MightyMoose 126 | *.mm.* 127 | AutoTest.Net/ 128 | 129 | # Web workbench (sass) 130 | .sass-cache/ 131 | 132 | # Installshield output folder 133 | [Ee]xpress/ 134 | 135 | # DocProject is a documentation generator add-in 136 | DocProject/buildhelp/ 137 | DocProject/Help/*.HxT 138 | DocProject/Help/*.HxC 139 | DocProject/Help/*.hhc 140 | DocProject/Help/*.hhk 141 | DocProject/Help/*.hhp 142 | DocProject/Help/Html2 143 | DocProject/Help/html 144 | 145 | # Click-Once directory 146 | publish/ 147 | 148 | # Publish Web Output 149 | *.[Pp]ublish.xml 150 | *.azurePubxml 151 | # TODO: Comment the next line if you want to checkin your web deploy settings 152 | # but database connection strings (with potential passwords) will be unencrypted 153 | *.pubxml 154 | *.publishproj 155 | 156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 157 | # checkin your Azure Web App publish settings, but sensitive information contained 158 | # in these scripts will be unencrypted 159 | PublishScripts/ 160 | 161 | # NuGet Packages 162 | *.nupkg 163 | # The packages folder can be ignored because of Package Restore 164 | **/packages/* 165 | # except build/, which is used as an MSBuild target. 166 | !**/packages/build/ 167 | # Uncomment if necessary however generally it will be regenerated when needed 168 | #!**/packages/repositories.config 169 | # NuGet v3's project.json files produces more ignorable files 170 | *.nuget.props 171 | *.nuget.targets 172 | 173 | # Microsoft Azure Build Output 174 | csx/ 175 | *.build.csdef 176 | 177 | # Microsoft Azure Emulator 178 | ecf/ 179 | rcf/ 180 | 181 | # Windows Store app package directories and files 182 | AppPackages/ 183 | BundleArtifacts/ 184 | Package.StoreAssociation.xml 185 | _pkginfo.txt 186 | 187 | # Visual Studio cache files 188 | # files ending in .cache can be ignored 189 | *.[Cc]ache 190 | # but keep track of directories ending in .cache 191 | !*.[Cc]ache/ 192 | 193 | # Others 194 | ClientBin/ 195 | ~$* 196 | *~ 197 | *.dbmdl 198 | *.dbproj.schemaview 199 | *.jfm 200 | *.pfx 201 | *.publishsettings 202 | orleans.codegen.cs 203 | 204 | # Since there are multiple workflows, uncomment next line to ignore bower_components 205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 206 | #bower_components/ 207 | 208 | # RIA/Silverlight projects 209 | Generated_Code/ 210 | 211 | # Backup & report files from converting an old project file 212 | # to a newer Visual Studio version. Backup files are not needed, 213 | # because we have git ;-) 214 | _UpgradeReport_Files/ 215 | Backup*/ 216 | UpgradeLog*.XML 217 | UpgradeLog*.htm 218 | 219 | # SQL Server files 220 | *.mdf 221 | *.ldf 222 | *.ndf 223 | 224 | # Business Intelligence projects 225 | *.rdl.data 226 | *.bim.layout 227 | *.bim_*.settings 228 | 229 | # Microsoft Fakes 230 | FakesAssemblies/ 231 | 232 | # GhostDoc plugin setting file 233 | *.GhostDoc.xml 234 | 235 | # Node.js Tools for Visual Studio 236 | .ntvs_analysis.dat 237 | node_modules/ 238 | 239 | # Typescript v1 declaration files 240 | typings/ 241 | 242 | # Visual Studio 6 build log 243 | *.plg 244 | 245 | # Visual Studio 6 workspace options file 246 | *.opt 247 | 248 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 249 | *.vbw 250 | 251 | # Visual Studio LightSwitch build output 252 | **/*.HTMLClient/GeneratedArtifacts 253 | **/*.DesktopClient/GeneratedArtifacts 254 | **/*.DesktopClient/ModelManifest.xml 255 | **/*.Server/GeneratedArtifacts 256 | **/*.Server/ModelManifest.xml 257 | _Pvt_Extensions 258 | 259 | # Paket dependency manager 260 | .paket/paket.exe 261 | paket-files/ 262 | 263 | # FAKE - F# Make 264 | .fake/ 265 | 266 | # JetBrains Rider 267 | .idea/ 268 | *.sln.iml 269 | 270 | # CodeRush 271 | .cr/ 272 | 273 | # Python Tools for Visual Studio (PTVS) 274 | __pycache__/ 275 | *.pyc 276 | 277 | # Cake - Uncomment if you are using it 278 | # tools/** 279 | # !tools/packages.config 280 | 281 | # Telerik's JustMock configuration file 282 | *.jmconfig 283 | 284 | # BizTalk build output 285 | *.btp.cs 286 | *.btm.cs 287 | *.odx.cs 288 | *.xsd.cs 289 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | .NET Octree 2 | =========== 3 | 4 | A dynamic octree implementation written in C# as a .NET Standard 2.1 library, built on the `System.Numerics` library. 5 | 6 | [![Build Status](https://github.com/mcserep/NetOctree/actions/workflows/ci.yml/badge.svg)](https://github.com/mcserep/NetOctree/actions/workflows/ci.yml) 7 | [![Code Coverage report](https://codecov.io/gh/mcserep/NetOctree/branch/master/graph/badge.svg?token=F4OY27SOC3)](https://codecov.io/gh/mcserep/NetOctree) 8 | [![NuGet Version](https://img.shields.io/nuget/v/NetOctree)](https://www.nuget.org/packages/NetOctree/) 9 | [![NuGet Download](https://img.shields.io/nuget/dt/NetOctree)](https://www.nuget.org/packages/NetOctree/) 10 | 11 | How to get it? 12 | ----------- 13 | 14 | The easiest way to get **NetOctree** is to install from the [NuGet package manager](https://docs.microsoft.com/hu-hu/nuget/install-nuget-client-tools). 15 | 16 | Use the Package Manager Console: 17 | ``` 18 | PM> Install-Package NetOctree 19 | ``` 20 | 21 | Or the `dotnet` command line interface: 22 | ``` 23 | dotnet add package NetOctree 24 | ``` 25 | 26 | Description 27 | ----------- 28 | 29 | There are two octree implementations here: 30 | **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. 31 | **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. 32 | 33 | **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. 34 | 35 | **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. 36 | 37 | **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. 38 | 39 | **A few functions are implemented:** 40 | 41 | 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`). 42 | 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. 43 | 44 | 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. 45 | 46 | **Considerations:** 47 | 48 | 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. 49 | 50 | 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. 51 | 52 | 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. 53 | 54 | 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. 55 | 56 | Example Usage 57 | ----------- 58 | 59 | **Create an Octree** 60 | 61 | ```C# 62 | // Initial size (metres), initial centre position, minimum node size (metres), looseness 63 | BoundsOctree boundsTree = new BoundsOctree(15, position, 1, 1.25f); 64 | // Initial size (metres), initial centre position, minimum node size (metres) 65 | PointOctree pointTree = new PointOctree(15, position, 1); 66 | ``` 67 | 68 | - 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. 69 | - The initial position should ideally be in the centre of where your objects are. 70 | - 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. 71 | - 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. 72 | 73 | **Add and Remove** 74 | 75 | ```C# 76 | boundsTree.Add(myObject, myBounds); 77 | boundsTree.Remove(myObject); 78 | 79 | pointTree.Add(myObject, myVector3); 80 | boundsTree.Remove(myObject); 81 | ``` 82 | - The object's type depends on the tree's type. 83 | - The bounds or point determine where it's inserted. 84 | 85 | **Search in the Octree** 86 | 87 | ```C# 88 | bool isColliding = boundsTree.IsColliding(bounds); 89 | ``` 90 | 91 | ```C# 92 | DataType[] collidingWith = boundsTree.GetColliding(bounds); 93 | ``` 94 | - Where `DataType` is the type of the octree 95 | 96 | ```C# 97 | DataType[] nearby = pointTree.GetNearby(myRay, 4); 98 | ``` 99 | - Where `myRay` is a `Ray` 100 | - In this case we're looking for any point within 4m of the closest point on the ray 101 | 102 | ```C# 103 | DataType[] nearby = pointTree.GetNearby(myPos, 4); 104 | ``` 105 | - Where `myPos` is a `Vector3` from `System.Numerics` 106 | 107 | **Non-Alloc query functions** 108 | 109 | A pre-initialized list can be used to store the results, which can be useful when executing a large number of queries with potentially large result sets. 110 | 111 | ```C# 112 | List collidingWith = new List(); 113 | boundsTree.GetColliding(collidingWith, bounds); 114 | ``` 115 | 116 | ```C# 117 | pointTree.GetNearby(myRay, 4, collidingWith); 118 | ``` 119 | 120 | **Potential Improvements** 121 | 122 | 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. 123 | -------------------------------------------------------------------------------- /Octree/Data/BoundingBox.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Distributed under the BSD Licence (see LICENCE file). 3 | // 4 | // Copyright (c) 2014, Nition, http://www.momentstudio.co.nz/ 5 | // Copyright (c) 2017, Máté Cserép, http://codenet.hu 6 | // All rights reserved. 7 | // 8 | namespace Octree 9 | { 10 | using System; 11 | using System.Numerics; 12 | using System.Runtime.Serialization; 13 | 14 | /// 15 | /// Represents an axis aligned bounding box (AABB). 16 | /// 17 | /// 18 | /// This class was inspired by the Bounds type of the Unity Engine and 19 | /// designed with the exact same interface to provide maximum compatibility. 20 | /// 21 | [DataContract] 22 | public struct BoundingBox 23 | { 24 | /// 25 | /// Gets or sets the center of the bounding box. 26 | /// 27 | [DataMember] 28 | public Vector3 Center { get; set; } 29 | 30 | /// 31 | /// Gets or sets the extents of the bounding box. This is always half of the . 32 | /// 33 | [DataMember] 34 | public Vector3 Extents { get; set; } 35 | 36 | /// 37 | /// Gets or sets the size of the bounding box. This is always twice as large as the . 38 | /// 39 | public Vector3 Size 40 | { 41 | get { return Extents * 2; } 42 | set { Extents = value * 0.5f; } 43 | } 44 | 45 | /// 46 | /// Gets or sets the minimal point of the box. 47 | /// 48 | /// 49 | /// This is always equal to center-extents. 50 | /// 51 | public Vector3 Min 52 | { 53 | get { return Center - Extents; } 54 | set { SetMinMax(value, Max); } 55 | } 56 | 57 | /// 58 | /// Gets or sets the maximal point of the box. 59 | /// 60 | /// 61 | /// This is always equal to center+extents. 62 | /// 63 | public Vector3 Max 64 | { 65 | get { return Center + Extents; } 66 | set { SetMinMax(Min, value); } 67 | } 68 | 69 | /// 70 | /// Creates a new bounding box. 71 | /// 72 | /// The center of the box. 73 | /// The size of the box. 74 | public BoundingBox(Vector3 center, Vector3 size) 75 | { 76 | Center = center; 77 | Extents = size * 0.5f; 78 | } 79 | 80 | /// 81 | /// Sets the bounds to the min and max value of the box. 82 | /// 83 | /// The minimal point. 84 | /// The maximal point. 85 | public void SetMinMax(Vector3 min, Vector3 max) 86 | { 87 | Extents = (max - min) * 0.5f; 88 | Center = min + Extents; 89 | } 90 | 91 | /// 92 | /// Grows the bounding box include the point. 93 | /// 94 | /// The specified point to include. 95 | public void Encapsulate(Vector3 point) 96 | { 97 | SetMinMax(Vector3.Min(Min, point), Vector3.Max(Max, point)); 98 | } 99 | 100 | /// 101 | /// Grows the bounding box include the other box. 102 | /// 103 | /// The specified box to include. 104 | public void Encapsulate(BoundingBox box) 105 | { 106 | Encapsulate(box.Center - box.Extents); 107 | Encapsulate(box.Center + box.Extents); 108 | } 109 | 110 | /// 111 | /// Expands the bounds by increasing its by along each side. 112 | /// 113 | /// The expansions for each dimension. 114 | public void Expand(float amount) 115 | { 116 | amount *= 0.5f; 117 | Extents += new Vector3(amount, amount, amount); 118 | } 119 | 120 | /// 121 | /// Expands the bounds by increasing its by along each side. 122 | /// 123 | /// The expansions for each dimension in order. 124 | public void Expand(Vector3 amount) 125 | { 126 | Extents += amount * 0.5f; 127 | } 128 | 129 | /// 130 | /// Determines whether the box contains the point. 131 | /// 132 | /// The point to test. 133 | /// true if the box contains the point; otherwise, false. 134 | public bool Contains(Vector3 point) 135 | { 136 | return 137 | Min.X <= point.X && Max.X >= point.X && 138 | Min.Y <= point.Y && Max.Y >= point.Y && 139 | Min.Z <= point.Z && Max.Z >= point.Z; 140 | } 141 | 142 | /// 143 | /// Determines whether the bounding box intersects with another box. 144 | /// 145 | /// The box to test. 146 | /// true if the bounding box intersects with another box, false otherwise. 147 | public bool Intersects(BoundingBox box) 148 | { 149 | return 150 | Min.X <= box.Max.X && Max.X >= box.Min.X && 151 | Min.Y <= box.Max.Y && Max.Y >= box.Min.Y && 152 | Min.Z <= box.Max.Z && Max.Z >= box.Min.Z; 153 | } 154 | 155 | /// 156 | /// Determines whether the bounding box intersects with a ray. 157 | /// 158 | /// The ray to test. 159 | /// true if the box intersects with the ray, false otherwise. 160 | public bool IntersectRay(Ray ray) 161 | { 162 | float distance; 163 | return IntersectRay(ray, out distance); 164 | } 165 | 166 | /// 167 | /// Determines whether the bounding box intersects with a ray. 168 | /// 169 | /// The ray to test. 170 | /// The calculated distance from the origin of the ray to the box along the ray. 171 | /// true if the box intersects with the ray, false otherwise. 172 | public bool IntersectRay(Ray ray, out float distance) 173 | { 174 | Vector3 dirFrac = new Vector3( 175 | 1f / ray.Direction.X, 176 | 1f / ray.Direction.Y, 177 | 1f / ray.Direction.Z 178 | ); 179 | 180 | float t1 = (Min.X - ray.Origin.X) * dirFrac.X; 181 | float t2 = (Max.X - ray.Origin.X) * dirFrac.X; 182 | float t3 = (Min.Y - ray.Origin.Y) * dirFrac.Y; 183 | float t4 = (Max.Y - ray.Origin.Y) * dirFrac.Y; 184 | float t5 = (Min.Z - ray.Origin.Z) * dirFrac.Z; 185 | float t6 = (Max.Z - ray.Origin.Z) * dirFrac.Z; 186 | 187 | float tmin = Math.Max(Math.Max(Math.Min(t1, t2), Math.Min(t3, t4)), Math.Min(t5, t6)); 188 | float tmax = Math.Min(Math.Min(Math.Max(t1, t2), Math.Max(t3, t4)), Math.Max(t5, t6)); 189 | 190 | // if tmax < 0, ray (line) is intersecting AABB, but the whole AABB is behind us 191 | if (tmax < 0) 192 | { 193 | distance = tmax; 194 | return false; 195 | } 196 | 197 | // if tmin > tmax, ray doesn't intersect AABB 198 | if (tmin > tmax) 199 | { 200 | distance = tmax; 201 | return false; 202 | } 203 | 204 | distance = tmin; 205 | return true; 206 | } 207 | 208 | /// 209 | /// Returns a hash code for this instance. 210 | /// 211 | /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 212 | public override int GetHashCode() 213 | { 214 | return Center.GetHashCode() ^ Extents.GetHashCode() << 2; 215 | } 216 | 217 | /// 218 | /// Determines whether the specified object as a is equal to this instance. 219 | /// 220 | /// The object to compare with this instance. 221 | /// true if the specified box is equal to this instance; otherwise, false. 222 | public override bool Equals(object other) 223 | { 224 | bool result; 225 | if (!(other is BoundingBox)) 226 | { 227 | result = false; 228 | } 229 | else 230 | { 231 | BoundingBox box = (BoundingBox)other; 232 | result = (Center.Equals(box.Center) && Extents.Equals(box.Extents)); 233 | } 234 | return result; 235 | } 236 | 237 | /// 238 | /// Returns a nicely formatted string for this bounding box. 239 | /// 240 | public override string ToString() 241 | { 242 | return String.Format("Center: {0}, Extents: {1}", 243 | Center, 244 | Extents 245 | ); 246 | } 247 | 248 | /// 249 | /// Returns a nicely formatted string for this bounding box. 250 | /// 251 | /// The format for the center and the extent. 252 | public string ToString(string format) 253 | { 254 | return String.Format("Center: {0}, Extents: {1}", 255 | Center.ToString(format), 256 | Extents.ToString(format) 257 | ); 258 | } 259 | 260 | /// 261 | /// Determines whether two bounding boxes are equal. 262 | /// 263 | /// The first box. 264 | /// The second box. 265 | public static bool operator ==(BoundingBox lhs, BoundingBox rhs) 266 | { 267 | return lhs.Center == rhs.Center && lhs.Extents == rhs.Extents; 268 | } 269 | 270 | /// 271 | /// Determines whether two bounding boxes are different. 272 | /// 273 | /// The first box. 274 | /// The second box. 275 | public static bool operator !=(BoundingBox lhs, BoundingBox rhs) 276 | { 277 | return !(lhs == rhs); 278 | } 279 | } 280 | } 281 | 282 | -------------------------------------------------------------------------------- /Octree/PointOctree.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Distributed under the BSD Licence (see LICENCE file). 3 | // 4 | // Copyright (c) 2014, Nition, http://www.momentstudio.co.nz/ 5 | // Copyright (c) 2017, Máté Cserép, http://codenet.hu 6 | // All rights reserved. 7 | // 8 | 9 | namespace Octree 10 | { 11 | using System; 12 | using System.Collections.Generic; 13 | using System.Numerics; 14 | 15 | /// 16 | /// A Dynamic Octree for storing any objects that can be described as a single point 17 | /// 18 | /// 19 | /// 20 | /// Octree: An octree is a tree data structure which divides 3D space into smaller partitions (nodes) 21 | /// and places objects into the appropriate nodes. This allows fast access to objects 22 | /// in an area of interest without having to check every object. 23 | /// 24 | /// Dynamic: The octree grows or shrinks as required when objects as added or removed. 25 | /// It also splits and merges nodes as appropriate. There is no maximum depth. 26 | /// Nodes have a constant - - which sets the amount of items allowed in a node before it splits. 27 | /// 28 | /// See also , where objects are described by AABB bounds. 29 | /// 30 | /// The content of the octree can be anything, since the bounds data is supplied separately. 31 | public partial class PointOctree 32 | { 33 | /// 34 | /// Root node of the octree 35 | /// 36 | private Node _rootNode; 37 | 38 | /// 39 | /// Size that the octree was on creation 40 | /// 41 | private readonly float _initialSize; 42 | 43 | /// 44 | /// Minimum side length that a node can be - essentially an alternative to having a max depth 45 | /// 46 | private readonly float _minSize; 47 | 48 | /// 49 | /// The total amount of objects currently in the tree 50 | /// 51 | public int Count { get; private set; } 52 | 53 | /// 54 | /// Gets the bounding box that represents the whole octree 55 | /// 56 | /// The bounding box of the root node. 57 | public BoundingBox MaxBounds 58 | { 59 | get { return new BoundingBox(_rootNode.Center, new Vector3(_rootNode.SideLength, _rootNode.SideLength, _rootNode.SideLength)); } 60 | } 61 | 62 | /// 63 | /// Gets All the bounding box that represents the whole octree 64 | /// 65 | /// 66 | public BoundingBox[] GetChildBounds() 67 | { 68 | var bounds = new List(); 69 | _rootNode.GetChildBounds(bounds); 70 | return bounds.ToArray(); 71 | } 72 | 73 | /// 74 | /// Constructor for the point octree. 75 | /// 76 | /// Size of the sides of the initial node. The octree will never shrink smaller than this. 77 | /// Position of the centre of the initial node. 78 | /// Nodes will stop splitting if the new nodes would be smaller than this. 79 | /// Minimum node size must be at least as big as the initial world size. 80 | public PointOctree(float initialWorldSize, Vector3 initialWorldPos, float minNodeSize) 81 | { 82 | if (minNodeSize > initialWorldSize) 83 | throw new ArgumentException("Minimum node size must be at least as big as the initial world size.", 84 | nameof(minNodeSize)); 85 | 86 | Count = 0; 87 | _initialSize = initialWorldSize; 88 | _minSize = minNodeSize; 89 | _rootNode = new Node(_initialSize, _minSize, initialWorldPos); 90 | } 91 | 92 | // #### PUBLIC METHODS #### 93 | 94 | /// 95 | /// Add an object. 96 | /// 97 | /// Object to add. 98 | /// Position of the object. 99 | /// Add operation required growing the octree too much. 100 | public void Add(T obj, Vector3 objPos) 101 | { 102 | // Add object or expand the octree until it can be added 103 | int count = 0; // Safety check against infinite/excessive growth 104 | while (!_rootNode.Add(obj, objPos)) 105 | { 106 | Grow(objPos - _rootNode.Center); 107 | if (++count > 20) 108 | { 109 | throw new InvalidOperationException($"Aborted Add operation as it seemed to be going on forever " + 110 | $"({count - 1} attempts at growing the octree)."); 111 | } 112 | } 113 | Count++; 114 | } 115 | 116 | /// 117 | /// Remove an object. Makes the assumption that the object only exists once in the tree. 118 | /// 119 | /// Object to remove. 120 | /// True if the object was removed successfully. 121 | public bool Remove(T obj) 122 | { 123 | bool removed = _rootNode.Remove(obj); 124 | 125 | // See if we can shrink the octree down now that we've removed the item 126 | if (removed) 127 | { 128 | Count--; 129 | Shrink(); 130 | } 131 | 132 | return removed; 133 | } 134 | 135 | /// 136 | /// Removes the specified object at the given position. Makes the assumption that the object only exists once in the tree. 137 | /// 138 | /// Object to remove. 139 | /// Position of the object. 140 | /// True if the object was removed successfully. 141 | public bool Remove(T obj, Vector3 objPos) 142 | { 143 | bool removed = _rootNode.Remove(obj, objPos); 144 | 145 | // See if we can shrink the octree down now that we've removed the item 146 | if (removed) 147 | { 148 | Count--; 149 | Shrink(); 150 | } 151 | 152 | return removed; 153 | } 154 | 155 | /// 156 | /// Returns objects that are within of the specified ray. 157 | /// If none, returns an empty array (not null). 158 | /// 159 | /// The ray. Passing as ref to improve performance since it won't have to be copied. 160 | /// Maximum distance from the ray to consider. 161 | /// Objects within range. 162 | public T[] GetNearby(Ray ray, float maxDistance) 163 | { 164 | List collidingWith = new List(); 165 | _rootNode.GetNearby(ref ray, maxDistance, collidingWith); 166 | return collidingWith.ToArray(); 167 | } 168 | 169 | /// 170 | /// Returns objects that are within of the specified position. 171 | /// If none, returns an empty array (not null). 172 | /// 173 | /// The position. Passing as ref to improve performance since it won't have to be copied. 174 | /// Maximum distance from the position to consider. 175 | /// Objects within range. 176 | public T[] GetNearby(Vector3 position, float maxDistance) 177 | { 178 | List collidingWith = new List(); 179 | _rootNode.GetNearby(ref position, maxDistance, collidingWith); 180 | return collidingWith.ToArray(); 181 | } 182 | 183 | /// 184 | /// Returns objects that are within of the specified ray. 185 | /// If none, returns false. Uses supplied list for results. 186 | /// 187 | /// The ray. Passing as ref to improve performance since it won't have to be copied. 188 | /// Maximum distance from the ray to consider. 189 | /// Pre-initialized list to populate. 190 | /// true if items are found, false otherwise. 191 | public bool GetNearbyNonAlloc(Ray ray, float maxDistance, List nearby) 192 | { 193 | nearby.Clear(); 194 | _rootNode.GetNearby(ref ray, maxDistance, nearby); 195 | return nearby.Count > 0; 196 | } 197 | 198 | /// 199 | /// Returns objects that are within of the specified position. 200 | /// If none, returns false. Uses supplied list for results. 201 | /// 202 | /// The position. Passing as ref to improve performance since it won't have to be copied. 203 | /// Maximum distance from the position to consider. 204 | /// Pre-initialized list to populate. 205 | /// true if items are found, false otherwise. 206 | public bool GetNearbyNonAlloc(Vector3 position, float maxDistance, List nearby) 207 | { 208 | nearby.Clear(); 209 | _rootNode.GetNearby(ref position, maxDistance, nearby); 210 | return nearby.Count > 0; 211 | } 212 | 213 | /// 214 | /// Returns all objects in the tree. 215 | /// If none, returns an empty array (not null). 216 | /// 217 | /// All objects. 218 | public ICollection GetAll() 219 | { 220 | List objects = new List(Count); 221 | _rootNode.GetAll(objects); 222 | return objects; 223 | } 224 | 225 | // #### PRIVATE METHODS #### 226 | 227 | /// 228 | /// Grow the octree to fit in all objects. 229 | /// 230 | /// Direction to grow. 231 | private void Grow(Vector3 direction) 232 | { 233 | int xDirection = direction.X >= 0 ? 1 : -1; 234 | int yDirection = direction.Y >= 0 ? 1 : -1; 235 | int zDirection = direction.Z >= 0 ? 1 : -1; 236 | Node oldRoot = _rootNode; 237 | float half = _rootNode.SideLength / 2; 238 | float newLength = _rootNode.SideLength * 2; 239 | Vector3 newCenter = _rootNode.Center + new Vector3(xDirection * half, yDirection * half, zDirection * half); 240 | 241 | // Create a new, bigger octree root node 242 | _rootNode = new Node(newLength, _minSize, newCenter); 243 | 244 | if (oldRoot.HasAnyObjects()) 245 | { 246 | // Create 7 new octree children to go with the old root as children of the new root 247 | int rootPos = _rootNode.BestFitChild(oldRoot.Center); 248 | Node[] children = new Node[8]; 249 | for (int i = 0; i < 8; i++) 250 | { 251 | if (i == rootPos) 252 | { 253 | children[i] = oldRoot; 254 | } 255 | else 256 | { 257 | xDirection = i % 2 == 0 ? -1 : 1; 258 | yDirection = i > 3 ? -1 : 1; 259 | zDirection = (i < 2 || (i > 3 && i < 6)) ? -1 : 1; 260 | children[i] = new Node( 261 | oldRoot.SideLength, 262 | _minSize, 263 | newCenter + new Vector3(xDirection * half, yDirection * half, zDirection * half)); 264 | } 265 | } 266 | 267 | // Attach the new children to the new root node 268 | _rootNode.SetChildren(children); 269 | } 270 | } 271 | 272 | /// 273 | /// Shrink the octree if possible, else leave it the same. 274 | /// 275 | private void Shrink() 276 | { 277 | _rootNode = _rootNode.ShrinkIfPossible(_initialSize); 278 | } 279 | } 280 | } -------------------------------------------------------------------------------- /Octree/BoundsOctree.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Distributed under the BSD Licence (see LICENCE file). 3 | // 4 | // Copyright (c) 2014, Nition, http://www.momentstudio.co.nz/ 5 | // Copyright (c) 2017, Máté Cserép, http://codenet.hu 6 | // All rights reserved. 7 | // 8 | 9 | namespace Octree 10 | { 11 | using System; 12 | using System.Collections.Generic; 13 | using System.Numerics; 14 | 15 | /// 16 | /// A Dynamic, Loose Octree for storing any objects that can be described with AABB bounds 17 | /// 18 | /// 19 | /// 20 | /// Octree: An octree is a tree data structure which divides 3D space into smaller partitions (nodes) 21 | /// and places objects into the appropriate nodes. This allows fast access to objects 22 | /// in an area of interest without having to check every object. 23 | /// 24 | /// Dynamic: The octree grows or shrinks as required when objects as added or removed. 25 | /// It also splits and merges nodes as appropriate. There is no maximum depth. 26 | /// Nodes have a constant - - which sets the amount of items allowed in a node before it splits. 27 | /// 28 | /// Loose: The octree's nodes can be larger than 1/2 their parent's length and width, so they overlap to some extent. 29 | /// This can alleviate the problem of even tiny objects ending up in large nodes if they're near boundaries. 30 | /// A looseness value of 1.0 will make it a "normal" octree. 31 | /// 32 | /// Note: For loops are often used here since in some cases (e.g. the IsColliding method) 33 | /// they actually give much better performance than using Foreach, even in the compiled build. 34 | /// Using a LINQ expression is worse again than Foreach. 35 | /// 36 | /// See also: , where objects are stored as single points and some code can be simplified 37 | /// 38 | /// The content of the octree can be anything, since the bounds data is supplied separately. 39 | public partial class BoundsOctree 40 | { 41 | /// 42 | /// Root node of the octree 43 | /// 44 | private Node _rootNode; 45 | 46 | /// 47 | /// Should be a value between 1 and 2. A multiplier for the base size of a node. 48 | /// 49 | /// 50 | /// 1.0 is a "normal" octree, while values > 1 have overlap 51 | /// 52 | private readonly float _looseness; 53 | 54 | /// 55 | /// Size that the octree was on creation 56 | /// 57 | private readonly float _initialSize; 58 | 59 | /// 60 | /// Minimum side length that a node can be - essentially an alternative to having a max depth 61 | /// 62 | private readonly float _minSize; 63 | 64 | /// 65 | /// The total amount of objects currently in the tree 66 | /// 67 | public int Count { get; private set; } 68 | 69 | /// 70 | /// Gets the bounding box that represents the whole octree 71 | /// 72 | /// The bounding box of the root node. 73 | public BoundingBox MaxBounds 74 | { 75 | get { return _rootNode.Bounds; } 76 | } 77 | 78 | /// 79 | /// Gets All the bounding box that represents the whole octree 80 | /// 81 | /// 82 | public BoundingBox[] GetChildBounds() 83 | { 84 | var bounds = new List(); 85 | _rootNode.GetChildBounds(bounds); 86 | return bounds.ToArray(); 87 | } 88 | 89 | /// 90 | /// Constructor for the bounds octree. 91 | /// 92 | /// Size of the sides of the initial node, in metres. The octree will never shrink smaller than this. 93 | /// Position of the center of the initial node. 94 | /// Nodes will stop splitting if the new nodes would be smaller than this (metres). 95 | /// Clamped between 1 and 2. Values > 1 let nodes overlap. 96 | /// Minimum node size must be at least as big as the initial world size. 97 | public BoundsOctree(float initialWorldSize, Vector3 initialWorldPos, float minNodeSize, float loosenessVal) 98 | { 99 | if (minNodeSize > initialWorldSize) 100 | throw new ArgumentException("Minimum node size must be at least as big as the initial world size.", 101 | nameof(minNodeSize)); 102 | 103 | Count = 0; 104 | _initialSize = initialWorldSize; 105 | _minSize = minNodeSize; 106 | _looseness = MathExtensions.Clamp(loosenessVal, 1.0f, 2.0f); 107 | _rootNode = new Node(_initialSize, _minSize, _looseness, initialWorldPos); 108 | } 109 | 110 | // #### PUBLIC METHODS #### 111 | 112 | /// 113 | /// Add an object. 114 | /// 115 | /// Object to add. 116 | /// 3D bounding box around the object. 117 | /// Add operation required growing the octree too much. 118 | public void Add(T obj, BoundingBox objBounds) 119 | { 120 | // Add object or expand the octree until it can be added 121 | int count = 0; // Safety check against infinite/excessive growth 122 | while (!_rootNode.Add(obj, objBounds)) 123 | { 124 | Grow(objBounds.Center - _rootNode.Center); 125 | if (++count > 20) 126 | { 127 | throw new InvalidOperationException($"Aborted Add operation as it seemed to be going on forever " + 128 | $"({count - 1} attempts at growing the octree)."); 129 | } 130 | } 131 | Count++; 132 | } 133 | 134 | /// 135 | /// Remove an object. Makes the assumption that the object only exists once in the tree. 136 | /// 137 | /// Object to remove. 138 | /// True if the object was removed successfully. 139 | public bool Remove(T obj) 140 | { 141 | bool removed = _rootNode.Remove(obj); 142 | 143 | // See if we can shrink the octree down now that we've removed the item 144 | if (removed) 145 | { 146 | Count--; 147 | Shrink(); 148 | } 149 | 150 | return removed; 151 | } 152 | 153 | /// 154 | /// Removes the specified object at the given position. Makes the assumption that the object only exists once in the tree. 155 | /// 156 | /// Object to remove. 157 | /// 3D bounding box around the object. 158 | /// True if the object was removed successfully. 159 | public bool Remove(T obj, BoundingBox objBounds) 160 | { 161 | bool removed = _rootNode.Remove(obj, objBounds); 162 | 163 | // See if we can shrink the octree down now that we've removed the item 164 | if (removed) 165 | { 166 | Count--; 167 | Shrink(); 168 | } 169 | 170 | return removed; 171 | } 172 | 173 | /// 174 | /// Check if the specified bounds intersect with anything in the tree. See also: GetColliding. 175 | /// 176 | /// bounds to check. 177 | /// True if there was a collision. 178 | public bool IsColliding(BoundingBox checkBounds) 179 | { 180 | return _rootNode.IsColliding(ref checkBounds); 181 | } 182 | 183 | /// 184 | /// Check if the specified ray intersects with anything in the tree. See also: GetColliding. 185 | /// 186 | /// ray to check. 187 | /// distance to check. 188 | /// True if there was a collision. 189 | public bool IsColliding(Ray checkRay, float maxDistance) 190 | { 191 | return _rootNode.IsColliding(ref checkRay, maxDistance); 192 | } 193 | 194 | /// 195 | /// Returns an array of objects that intersect with the specified bounds, if any. 196 | /// Otherwise returns an empty array. 197 | /// 198 | /// 199 | /// bounds to check. 200 | /// Objects that intersect with the specified bounds. 201 | public T[] GetColliding(BoundingBox checkBounds) 202 | { 203 | List collidingWith = new List(); 204 | _rootNode.GetColliding(ref checkBounds, collidingWith); 205 | return collidingWith.ToArray(); 206 | } 207 | 208 | /// 209 | /// Returns an array of objects that intersect with the specified ray, if any. 210 | /// Otherwise returns an empty array. 211 | /// 212 | /// 213 | /// ray to check. 214 | /// distance to check. 215 | /// Objects that intersect with the specified ray. 216 | public T[] GetColliding(Ray checkRay, float maxDistance = float.PositiveInfinity) 217 | { 218 | List collidingWith = new List(); 219 | _rootNode.GetColliding(ref checkRay, collidingWith, maxDistance); 220 | return collidingWith.ToArray(); 221 | } 222 | 223 | /// 224 | /// Returns an array of objects that intersect with the specified bounds, if any. 225 | /// Otherwise returns an empty array. 226 | /// 227 | /// 228 | /// list to store intersections. 229 | /// bounds to check. 230 | /// true if items are found, false otherwise. 231 | public bool GetCollidingNonAlloc(List collidingWith, BoundingBox checkBounds) 232 | { 233 | collidingWith.Clear(); 234 | _rootNode.GetColliding(ref checkBounds, collidingWith); 235 | return collidingWith.Count > 0; 236 | } 237 | 238 | /// 239 | /// Returns an array of objects that intersect with the specified ray, if any. 240 | /// Otherwise returns an empty array. 241 | /// 242 | /// 243 | /// list to store intersections. 244 | /// ray to check. 245 | /// distance to check. 246 | /// true if items are found, false otherwise. 247 | public bool GetCollidingNonAlloc(List collidingWith, Ray checkRay, float maxDistance = float.PositiveInfinity) 248 | { 249 | collidingWith.Clear(); 250 | _rootNode.GetColliding(ref checkRay, collidingWith, maxDistance); 251 | return collidingWith.Count > 0; 252 | } 253 | 254 | // #### PRIVATE METHODS #### 255 | 256 | /// 257 | /// Grow the octree to fit in all objects. 258 | /// 259 | /// Direction to grow. 260 | private void Grow(Vector3 direction) 261 | { 262 | int xDirection = direction.X >= 0 ? 1 : -1; 263 | int yDirection = direction.Y >= 0 ? 1 : -1; 264 | int zDirection = direction.Z >= 0 ? 1 : -1; 265 | Node oldRoot = _rootNode; 266 | float half = _rootNode.BaseLength / 2; 267 | float newLength = _rootNode.BaseLength * 2; 268 | Vector3 newCenter = _rootNode.Center + new Vector3(xDirection * half, yDirection * half, zDirection * half); 269 | 270 | // Create a new, bigger octree root node 271 | _rootNode = new Node(newLength, _minSize, _looseness, newCenter); 272 | 273 | if (oldRoot.HasAnyObjects()) 274 | { 275 | // Create 7 new octree children to go with the old root as children of the new root 276 | int rootPos = _rootNode.BestFitChild(oldRoot.Center); 277 | Node[] children = new Node[8]; 278 | for (int i = 0; i < 8; i++) 279 | { 280 | if (i == rootPos) 281 | { 282 | children[i] = oldRoot; 283 | } 284 | else 285 | { 286 | xDirection = i % 2 == 0 ? -1 : 1; 287 | yDirection = i > 3 ? -1 : 1; 288 | zDirection = (i < 2 || (i > 3 && i < 6)) ? -1 : 1; 289 | children[i] = new Node( 290 | oldRoot.BaseLength, 291 | _minSize, 292 | _looseness, 293 | newCenter + new Vector3(xDirection * half, yDirection * half, zDirection * half)); 294 | } 295 | } 296 | 297 | // Attach the new children to the new root node 298 | _rootNode.SetChildren(children); 299 | } 300 | } 301 | 302 | /// 303 | /// Shrink the octree if possible, else leave it the same. 304 | /// 305 | private void Shrink() 306 | { 307 | _rootNode = _rootNode.ShrinkIfPossible(_initialSize); 308 | } 309 | } 310 | } -------------------------------------------------------------------------------- /Octree/PointOctreeNode.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Distributed under the BSD Licence (see LICENCE file). 3 | // 4 | // Copyright (c) 2014, Nition, http://www.momentstudio.co.nz/ 5 | // Copyright (c) 2017, Máté Cserép, http://codenet.hu 6 | // All rights reserved. 7 | // 8 | 9 | namespace Octree 10 | { 11 | using System; 12 | using System.Collections.Generic; 13 | using System.Linq; 14 | using System.Numerics; 15 | 16 | public partial class PointOctree 17 | { 18 | /// 19 | /// A node in a PointOctree 20 | /// 21 | private class Node 22 | { 23 | /// 24 | /// Center of this node 25 | /// 26 | public Vector3 Center { get; private set; } 27 | 28 | /// 29 | /// Length of the sides of this node 30 | /// 31 | public float SideLength { get; private set; } 32 | 33 | /// 34 | /// Minimum size for a node in this octree 35 | /// 36 | private float _minSize; 37 | 38 | /// 39 | /// Bounding box that represents this node 40 | /// 41 | private BoundingBox _bounds = default(BoundingBox); 42 | 43 | /// 44 | /// Objects in this node 45 | /// 46 | private readonly List _objects = new List(); 47 | 48 | /// 49 | /// Child nodes, if any 50 | /// 51 | private Node[] _children = null; 52 | 53 | /// 54 | /// Bounds of potential children to this node. These are actual size (with looseness taken into account), not base size 55 | /// 56 | private BoundingBox[] _childBounds; 57 | 58 | /// 59 | /// If there are already NumObjectsAllowed in a node, we split it into children 60 | /// 61 | /// 62 | /// A generally good number seems to be something around 8-15 63 | /// 64 | private const int NumObjectsAllowed = 8; 65 | 66 | /// 67 | /// For reverting the bounds size after temporary changes 68 | /// 69 | private Vector3 _actualBoundsSize; 70 | 71 | /// 72 | /// Gets a value indicating whether this node has children 73 | /// 74 | private bool HasChildren 75 | { 76 | get { return _children != null; } 77 | } 78 | 79 | /// 80 | /// An object in the octree 81 | /// 82 | private class OctreeObject 83 | { 84 | /// 85 | /// Object content 86 | /// 87 | public T Obj; 88 | 89 | /// 90 | /// Object position 91 | /// 92 | public Vector3 Pos; 93 | } 94 | 95 | /// 96 | /// Gets the bounding box that represents this node 97 | /// 98 | public BoundingBox Bounds 99 | { 100 | get { return _bounds; } 101 | } 102 | 103 | /// 104 | /// Gets All the bounding box that represents this node 105 | /// 106 | /// 107 | public void GetChildBounds(List bounds) 108 | { 109 | if (HasChildren) 110 | { 111 | foreach (var child in _children) 112 | { 113 | child.GetChildBounds(bounds); 114 | } 115 | return; 116 | } 117 | bounds.Add(Bounds); 118 | } 119 | 120 | /// 121 | /// Constructor. 122 | /// 123 | /// Length of this node, not taking looseness into account. 124 | /// Minimum size of nodes in this octree. 125 | /// Center position of this node. 126 | public Node(float baseLengthVal, float minSizeVal, Vector3 centerVal) 127 | { 128 | SetValues(baseLengthVal, minSizeVal, centerVal); 129 | } 130 | 131 | // #### PUBLIC METHODS #### 132 | 133 | /// 134 | /// Add an object. 135 | /// 136 | /// Object to add. 137 | /// Position of the object. 138 | /// 139 | public bool Add(T obj, Vector3 objPos) 140 | { 141 | if (!Encapsulates(_bounds, objPos)) 142 | { 143 | return false; 144 | } 145 | SubAdd(obj, objPos); 146 | return true; 147 | } 148 | 149 | /// 150 | /// Remove an object. Makes the assumption that the object only exists once in the tree. 151 | /// 152 | /// Object to remove. 153 | /// True if the object was removed successfully. 154 | public bool Remove(T obj) 155 | { 156 | bool removed = false; 157 | 158 | for (int i = 0; i < _objects.Count; i++) 159 | { 160 | if (_objects[i].Obj.Equals(obj)) 161 | { 162 | removed = _objects.Remove(_objects[i]); 163 | break; 164 | } 165 | } 166 | 167 | if (!removed && _children != null) 168 | { 169 | for (int i = 0; i < 8; i++) 170 | { 171 | removed = _children[i].Remove(obj); 172 | if (removed) break; 173 | } 174 | } 175 | 176 | if (removed && _children != null) 177 | { 178 | // Check if we should merge nodes now that we've removed an item 179 | if (ShouldMerge()) 180 | { 181 | Merge(); 182 | } 183 | } 184 | 185 | return removed; 186 | } 187 | 188 | /// 189 | /// Removes the specified object at the given position. Makes the assumption that the object only exists once in the tree. 190 | /// 191 | /// Object to remove. 192 | /// Position of the object. 193 | /// True if the object was removed successfully. 194 | public bool Remove(T obj, Vector3 objPos) 195 | { 196 | if (!Encapsulates(_bounds, objPos)) 197 | { 198 | return false; 199 | } 200 | return SubRemove(obj, objPos); 201 | } 202 | 203 | /// 204 | /// Return objects that are within of the specified ray. 205 | /// 206 | /// The ray. 207 | /// Maximum distance from the ray to consider. 208 | /// List result. 209 | /// Objects within range. 210 | public void GetNearby(ref Ray ray, float maxDistance, List result) 211 | { 212 | // Does the ray hit this node at all? 213 | // Note: Expanding the bounds is not exactly the same as a real distance check, but it's fast. 214 | // TODO: Does someone have a fast AND accurate formula to do this check? 215 | _bounds.Expand(new Vector3(maxDistance * 2, maxDistance * 2, maxDistance * 2)); 216 | bool intersected = _bounds.IntersectRay(ray); 217 | _bounds.Size = _actualBoundsSize; 218 | if (!intersected) 219 | { 220 | return; 221 | } 222 | 223 | // Check against any objects in this node 224 | for (int i = 0; i < _objects.Count; i++) 225 | { 226 | if (SqrDistanceToRay(ray, _objects[i].Pos) <= (maxDistance * maxDistance)) 227 | { 228 | result.Add(_objects[i].Obj); 229 | } 230 | } 231 | 232 | // Check children 233 | if (_children != null) 234 | { 235 | for (int i = 0; i < 8; i++) 236 | { 237 | _children[i].GetNearby(ref ray, maxDistance, result); 238 | } 239 | } 240 | } 241 | 242 | /// 243 | /// Return objects that are within of the specified position. 244 | /// 245 | /// The position. 246 | /// Maximum distance from the position to consider. 247 | /// List result. 248 | /// Objects within range. 249 | public void GetNearby(ref Vector3 position, float maxDistance, List result) 250 | { 251 | // Does the node contain this position at all? 252 | // Note: Expanding the bounds is not exactly the same as a real distance check, but it's fast. 253 | // TODO: Does someone have a fast AND accurate formula to do this check? 254 | _bounds.Expand(new Vector3(maxDistance * 2, maxDistance * 2, maxDistance * 2)); 255 | bool contained = _bounds.Contains(position); 256 | _bounds.Size = _actualBoundsSize; 257 | if (!contained) 258 | { 259 | return; 260 | } 261 | 262 | // Check against any objects in this node 263 | for (int i = 0; i < _objects.Count; i++) 264 | { 265 | if (Vector3.Distance(position, _objects[i].Pos) <= maxDistance) 266 | { 267 | result.Add(_objects[i].Obj); 268 | } 269 | } 270 | 271 | // Check children 272 | if (_children != null) 273 | { 274 | for (int i = 0; i < 8; i++) 275 | { 276 | _children[i].GetNearby(ref position, maxDistance, result); 277 | } 278 | } 279 | } 280 | 281 | /// 282 | /// Return all objects in the tree. 283 | /// 284 | /// All objects. 285 | public void GetAll(List result) 286 | { 287 | // add directly contained objects 288 | result.AddRange(_objects.Select(o => o.Obj)); 289 | 290 | // add children objects 291 | if (_children != null) 292 | { 293 | for (int i = 0; i < 8; i++) 294 | { 295 | _children[i].GetAll(result); 296 | } 297 | } 298 | } 299 | 300 | /// 301 | /// Set the 8 children of this octree. 302 | /// 303 | /// The 8 new child nodes. 304 | public void SetChildren(Node[] childOctrees) 305 | { 306 | if (childOctrees.Length != 8) 307 | { 308 | throw new ArgumentException("Child octree array must be length 8. Was length: " + childOctrees.Length, 309 | nameof(childOctrees)); 310 | } 311 | 312 | _children = childOctrees; 313 | } 314 | 315 | /// 316 | /// We can shrink the octree if: 317 | /// - This node is >= double minLength in length 318 | /// - All objects in the root node are within one octant 319 | /// - This node doesn't have children, or does but 7/8 children are empty 320 | /// We can also shrink it if there are no objects left at all! 321 | /// 322 | /// Minimum dimensions of a node in this octree. 323 | /// The new root, or the existing one if we didn't shrink. 324 | public Node ShrinkIfPossible(float minLength) 325 | { 326 | if (SideLength < (2 * minLength)) 327 | { 328 | return this; 329 | } 330 | if (_objects.Count == 0 && (_children == null || _children.Length == 0)) 331 | { 332 | return this; 333 | } 334 | 335 | // Check objects in root 336 | int bestFit = -1; 337 | for (int i = 0; i < _objects.Count; i++) 338 | { 339 | OctreeObject curObj = _objects[i]; 340 | int newBestFit = BestFitChild(curObj.Pos); 341 | if (i == 0 || newBestFit == bestFit) 342 | { 343 | if (bestFit < 0) 344 | { 345 | bestFit = newBestFit; 346 | } 347 | } 348 | else 349 | { 350 | return this; // Can't reduce - objects fit in different octants 351 | } 352 | } 353 | 354 | // Check objects in children if there are any 355 | if (_children != null) 356 | { 357 | bool childHadContent = false; 358 | for (int i = 0; i < _children.Length; i++) 359 | { 360 | if (_children[i].HasAnyObjects()) 361 | { 362 | if (childHadContent) 363 | { 364 | return this; // Can't shrink - another child had content already 365 | } 366 | if (bestFit >= 0 && bestFit != i) 367 | { 368 | return this; // Can't reduce - objects in root are in a different octant to objects in child 369 | } 370 | childHadContent = true; 371 | bestFit = i; 372 | } 373 | } 374 | } 375 | 376 | // Can reduce 377 | if (_children == null) 378 | { 379 | // We don't have any children, so just shrink this node to the new size 380 | // We already know that everything will still fit in it 381 | SetValues(SideLength / 2, _minSize, _childBounds[bestFit].Center); 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 position. 393 | /// One of the eight child octants. 394 | public int BestFitChild(Vector3 objPos) 395 | { 396 | return (objPos.X <= Center.X ? 0 : 1) + (objPos.Y >= Center.Y ? 0 : 4) + (objPos.Z <= Center.Z ? 0 : 2); 397 | } 398 | 399 | /// 400 | /// Checks if this node or anything below it has something in it. 401 | /// 402 | /// True if this node or any of its children, grandchildren etc have something in them 403 | public bool HasAnyObjects() 404 | { 405 | if (_objects.Count > 0) return true; 406 | 407 | if (_children != null) 408 | { 409 | for (int i = 0; i < 8; i++) 410 | { 411 | if (_children[i].HasAnyObjects()) return true; 412 | } 413 | } 414 | 415 | return false; 416 | } 417 | 418 | /// 419 | /// Returns the squared distance to the given ray from a point. 420 | /// 421 | /// The ray. 422 | /// The point to check distance from the ray. 423 | /// Squared distance from the point to the closest point of the ray. 424 | public static float SqrDistanceToRay(Ray ray, Vector3 point) 425 | { 426 | return Vector3.Cross(ray.Direction, point - ray.Origin).LengthSquared(); 427 | } 428 | 429 | // #### PRIVATE METHODS #### 430 | 431 | /// 432 | /// Set values for this node. 433 | /// 434 | /// Length of this node, not taking looseness into account. 435 | /// Minimum size of nodes in this octree. 436 | /// Centre position of this node. 437 | private void SetValues(float baseLengthVal, float minSizeVal, Vector3 centerVal) 438 | { 439 | SideLength = baseLengthVal; 440 | _minSize = minSizeVal; 441 | Center = centerVal; 442 | 443 | // Create the bounding box. 444 | _actualBoundsSize = new Vector3(SideLength, SideLength, SideLength); 445 | _bounds = new BoundingBox(Center, _actualBoundsSize); 446 | 447 | float quarter = SideLength / 4f; 448 | float childActualLength = SideLength / 2; 449 | Vector3 childActualSize = new Vector3(childActualLength, childActualLength, childActualLength); 450 | _childBounds = new BoundingBox[8]; 451 | _childBounds[0] = new BoundingBox(Center + new Vector3(-quarter, quarter, -quarter), childActualSize); 452 | _childBounds[1] = new BoundingBox(Center + new Vector3(quarter, quarter, -quarter), childActualSize); 453 | _childBounds[2] = new BoundingBox(Center + new Vector3(-quarter, quarter, quarter), childActualSize); 454 | _childBounds[3] = new BoundingBox(Center + new Vector3(quarter, quarter, quarter), childActualSize); 455 | _childBounds[4] = new BoundingBox(Center + new Vector3(-quarter, -quarter, -quarter), childActualSize); 456 | _childBounds[5] = new BoundingBox(Center + new Vector3(quarter, -quarter, -quarter), childActualSize); 457 | _childBounds[6] = new BoundingBox(Center + new Vector3(-quarter, -quarter, quarter), childActualSize); 458 | _childBounds[7] = new BoundingBox(Center + new Vector3(quarter, -quarter, quarter), childActualSize); 459 | } 460 | 461 | /// 462 | /// Private counterpart to the public Add method. 463 | /// 464 | /// Object to add. 465 | /// Position of the object. 466 | private void SubAdd(T obj, Vector3 objPos) 467 | { 468 | // We know it fits at this level if we've got this far 469 | 470 | // We always put things in the deepest possible child 471 | // So we can skip checks and simply move down if there are children aleady 472 | if (!HasChildren) 473 | { 474 | // Just add if few objects are here, or children would be below min size 475 | if (_objects.Count < NumObjectsAllowed || (SideLength / 2) < _minSize) 476 | { 477 | OctreeObject newObj = new OctreeObject { Obj = obj, Pos = objPos }; 478 | _objects.Add(newObj); 479 | return; // We're done. No children yet 480 | } 481 | 482 | // Enough objects in this node already: Create the 8 children 483 | int bestFitChild; 484 | if (_children == null) 485 | { 486 | Split(); 487 | if (_children == null) 488 | throw new InvalidOperationException("Child creation failed for an unknown reason. Early exit."); 489 | 490 | // Now that we have the new children, move this node's existing objects into them 491 | for (int i = _objects.Count - 1; i >= 0; i--) 492 | { 493 | OctreeObject existingObj = _objects[i]; 494 | // Find which child the object is closest to based on where the 495 | // object's center is located in relation to the octree's center 496 | bestFitChild = BestFitChild(existingObj.Pos); 497 | _children[bestFitChild].SubAdd(existingObj.Obj, existingObj.Pos); // Go a level deeper 498 | _objects.Remove(existingObj); // Remove from here 499 | } 500 | } 501 | } 502 | 503 | // Handle the new object we're adding now 504 | int bestFit = BestFitChild(objPos); 505 | _children[bestFit].SubAdd(obj, objPos); 506 | } 507 | 508 | /// 509 | /// Private counterpart to the public method. 510 | /// 511 | /// Object to remove. 512 | /// Position of the object. 513 | /// True if the object was removed successfully. 514 | private bool SubRemove(T obj, Vector3 objPos) 515 | { 516 | bool removed = false; 517 | 518 | for (int i = 0; i < _objects.Count; i++) 519 | { 520 | if (_objects[i].Obj.Equals(obj)) 521 | { 522 | removed = _objects.Remove(_objects[i]); 523 | break; 524 | } 525 | } 526 | 527 | if (!removed && _children != null) 528 | { 529 | int bestFitChild = BestFitChild(objPos); 530 | removed = _children[bestFitChild].SubRemove(obj, objPos); 531 | } 532 | 533 | if (removed && _children != null) 534 | { 535 | // Check if we should merge nodes now that we've removed an item 536 | if (ShouldMerge()) 537 | { 538 | Merge(); 539 | } 540 | } 541 | 542 | return removed; 543 | } 544 | 545 | /// 546 | /// Splits the octree into eight children. 547 | /// 548 | private void Split() 549 | { 550 | float quarter = SideLength / 4f; 551 | float newLength = SideLength / 2; 552 | _children = new Node[8]; 553 | _children[0] = new Node(newLength, _minSize, Center + new Vector3(-quarter, quarter, -quarter)); 554 | _children[1] = new Node(newLength, _minSize, Center + new Vector3(quarter, quarter, -quarter)); 555 | _children[2] = new Node(newLength, _minSize, Center + new Vector3(-quarter, quarter, quarter)); 556 | _children[3] = new Node(newLength, _minSize, Center + new Vector3(quarter, quarter, quarter)); 557 | _children[4] = new Node(newLength, _minSize, Center + new Vector3(-quarter, -quarter, -quarter)); 558 | _children[5] = new Node(newLength, _minSize, Center + new Vector3(quarter, -quarter, -quarter)); 559 | _children[6] = new Node(newLength, _minSize, Center + new Vector3(-quarter, -quarter, quarter)); 560 | _children[7] = new Node(newLength, _minSize, Center + new Vector3(quarter, -quarter, quarter)); 561 | } 562 | 563 | /// 564 | /// Merge all children into this node - the opposite of Split. 565 | /// Note: We only have to check one level down since a merge will never happen if the children already have children, 566 | /// since THAT won't happen unless there are already too many objects to merge. 567 | /// 568 | private void Merge() 569 | { 570 | // Note: We know children != null or we wouldn't be merging 571 | for (int i = 0; i < 8; i++) 572 | { 573 | Node curChild = _children[i]; 574 | int numObjects = curChild._objects.Count; 575 | for (int j = numObjects - 1; j >= 0; j--) 576 | { 577 | OctreeObject curObj = curChild._objects[j]; 578 | _objects.Add(curObj); 579 | } 580 | } 581 | // Remove the child nodes (and the objects in them - they've been added elsewhere now) 582 | _children = null; 583 | } 584 | 585 | /// 586 | /// Checks if outerBounds encapsulates the given point. 587 | /// 588 | /// Outer bounds. 589 | /// Point. 590 | /// True if innerBounds is fully encapsulated by outerBounds. 591 | private static bool Encapsulates(BoundingBox outerBounds, Vector3 point) 592 | { 593 | return outerBounds.Contains(point); 594 | } 595 | 596 | /// 597 | /// Checks if there are few enough objects in this node and its children that the children should all be merged into this. 598 | /// 599 | /// True there are less or the same amount of objects in this and its children than . 600 | private bool ShouldMerge() 601 | { 602 | int totalObjects = _objects.Count; 603 | if (_children != null) 604 | { 605 | foreach (Node child in _children) 606 | { 607 | if (child._children != null) 608 | { 609 | // If any of the *children* have children, there are definitely too many to merge, 610 | // or the child would have been merged already 611 | return false; 612 | } 613 | totalObjects += child._objects.Count; 614 | } 615 | } 616 | return totalObjects <= NumObjectsAllowed; 617 | } 618 | } 619 | } 620 | } -------------------------------------------------------------------------------- /Octree/BoundsOctreeNode.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Distributed under the BSD Licence (see LICENCE file). 3 | // 4 | // Copyright (c) 2014, Nition, http://www.momentstudio.co.nz/ 5 | // Copyright (c) 2017, Máté Cserép, http://codenet.hu 6 | // All rights reserved. 7 | // 8 | 9 | namespace Octree 10 | { 11 | using System; 12 | using System.Collections.Generic; 13 | using System.Numerics; 14 | 15 | public partial class BoundsOctree 16 | { 17 | /// 18 | /// A node in a BoundsOctree 19 | /// 20 | private class Node 21 | { 22 | /// 23 | /// Centre of this node 24 | /// 25 | public Vector3 Center { get; private set; } 26 | 27 | /// 28 | /// Length of this node if it has a looseness of 1.0 29 | /// 30 | public float BaseLength { get; private set; } 31 | 32 | /// 33 | /// Looseness value for this node 34 | /// 35 | private float _looseness; 36 | 37 | /// 38 | /// Minimum size for a node in this octree 39 | /// 40 | private float _minSize; 41 | 42 | /// 43 | /// Actual length of sides, taking the looseness value into account 44 | /// 45 | private float _adjLength; 46 | 47 | /// 48 | /// Bounding box that represents this node 49 | /// 50 | private BoundingBox _bounds = default(BoundingBox); 51 | 52 | /// 53 | /// Objects in this node 54 | /// 55 | private readonly List _objects = new List(); 56 | 57 | /// 58 | /// Child nodes, if any 59 | /// 60 | private Node[] _children = null; 61 | 62 | /// 63 | /// Bounds of potential children to this node. These are actual size (with looseness taken into account), not base size 64 | /// 65 | private BoundingBox[] _childBounds; 66 | 67 | /// 68 | /// If there are already NumObjectsAllowed in a node, we split it into children 69 | /// 70 | /// 71 | /// A generally good number seems to be something around 8-15 72 | /// 73 | private const int NumObjectsAllowed = 8; 74 | 75 | /// 76 | /// Gets a value indicating whether this node has children 77 | /// 78 | private bool HasChildren 79 | { 80 | get { return _children != null; } 81 | } 82 | 83 | /// 84 | /// An object in the octree 85 | /// 86 | private class OctreeObject 87 | { 88 | /// 89 | /// Object content 90 | /// 91 | public T Obj; 92 | 93 | /// 94 | /// Object bounds 95 | /// 96 | public BoundingBox Bounds; 97 | } 98 | 99 | /// 100 | /// Gets the bounding box that represents this node 101 | /// 102 | public BoundingBox Bounds 103 | { 104 | get { return _bounds; } 105 | } 106 | 107 | /// 108 | /// Gets All the bounding box that represents this node 109 | /// 110 | /// 111 | public void GetChildBounds(List bounds) 112 | { 113 | if (HasChildren) 114 | { 115 | foreach (var child in _children) 116 | { 117 | child.GetChildBounds(bounds); 118 | } 119 | return; 120 | } 121 | bounds.Add(Bounds); 122 | } 123 | 124 | /// 125 | /// Constructor. 126 | /// 127 | /// Length of this node, not taking looseness into account. 128 | /// Minimum size of nodes in this octree. 129 | /// Multiplier for baseLengthVal to get the actual size. 130 | /// Centre position of this node. 131 | public Node(float baseLengthVal, float minSizeVal, float loosenessVal, Vector3 centerVal) 132 | { 133 | SetValues(baseLengthVal, minSizeVal, loosenessVal, centerVal); 134 | } 135 | 136 | // #### PUBLIC METHODS #### 137 | 138 | /// 139 | /// Add an object. 140 | /// 141 | /// Object to add. 142 | /// 3D bounding box around the object. 143 | /// True if the object fits entirely within this node. 144 | public bool Add(T obj, BoundingBox objBounds) 145 | { 146 | if (!Encapsulates(_bounds, objBounds)) 147 | { 148 | return false; 149 | } 150 | SubAdd(obj, objBounds); 151 | return true; 152 | } 153 | 154 | /// 155 | /// Remove an object. Makes the assumption that the object only exists once in the tree. 156 | /// 157 | /// Object to remove. 158 | /// True if the object was removed successfully. 159 | public bool Remove(T obj) 160 | { 161 | bool removed = false; 162 | 163 | for (int i = 0; i < _objects.Count; i++) 164 | { 165 | if (_objects[i].Obj.Equals(obj)) 166 | { 167 | removed = _objects.Remove(_objects[i]); 168 | break; 169 | } 170 | } 171 | 172 | if (!removed && _children != null) 173 | { 174 | for (int i = 0; i < 8; i++) 175 | { 176 | removed = _children[i].Remove(obj); 177 | if (removed) break; 178 | } 179 | } 180 | 181 | if (removed && _children != null) 182 | { 183 | // Check if we should merge nodes now that we've removed an item 184 | if (ShouldMerge()) 185 | { 186 | Merge(); 187 | } 188 | } 189 | 190 | return removed; 191 | } 192 | 193 | /// 194 | /// Removes the specified object at the given position. Makes the assumption that the object only exists once in the tree. 195 | /// 196 | /// Object to remove. 197 | /// 3D bounding box around the object. 198 | /// True if the object was removed successfully. 199 | public bool Remove(T obj, BoundingBox objBounds) 200 | { 201 | if (!Encapsulates(_bounds, objBounds)) 202 | { 203 | return false; 204 | } 205 | return SubRemove(obj, objBounds); 206 | } 207 | 208 | /// 209 | /// Check if the specified bounds intersect with anything in the tree. See also: GetColliding. 210 | /// 211 | /// Bounds to check. 212 | /// True if there was a collision. 213 | public bool IsColliding(ref BoundingBox checkBounds) 214 | { 215 | // Are the input bounds at least partially in this node? 216 | if (!_bounds.Intersects(checkBounds)) 217 | { 218 | return false; 219 | } 220 | 221 | // Check against any objects in this node 222 | for (int i = 0; i < _objects.Count; i++) 223 | { 224 | if (_objects[i].Bounds.Intersects(checkBounds)) 225 | { 226 | return true; 227 | } 228 | } 229 | 230 | // Check children 231 | if (_children != null) 232 | { 233 | for (int i = 0; i < 8; i++) 234 | { 235 | if (_children[i].IsColliding(ref checkBounds)) 236 | { 237 | return true; 238 | } 239 | } 240 | } 241 | 242 | return false; 243 | } 244 | 245 | /// 246 | /// Check if the specified ray intersects with anything in the tree. See also: GetColliding. 247 | /// 248 | /// Ray to check. 249 | /// Distance to check. 250 | /// True if there was a collision. 251 | public bool IsColliding(ref Ray checkRay, float maxDistance = float.PositiveInfinity) 252 | { 253 | // Is the input ray at least partially in this node? 254 | float distance; 255 | if (!_bounds.IntersectRay(checkRay, out distance) || distance > maxDistance) 256 | { 257 | return false; 258 | } 259 | 260 | // Check against any objects in this node 261 | for (int i = 0; i < _objects.Count; i++) 262 | { 263 | if (_objects[i].Bounds.IntersectRay(checkRay, out distance) && distance <= maxDistance) 264 | { 265 | return true; 266 | } 267 | } 268 | 269 | // Check children 270 | if (_children != null) 271 | { 272 | for (int i = 0; i < 8; i++) 273 | { 274 | if (_children[i].IsColliding(ref checkRay, maxDistance)) 275 | { 276 | return true; 277 | } 278 | } 279 | } 280 | 281 | return false; 282 | } 283 | 284 | /// 285 | /// Returns an array of objects that intersect with the specified bounds, if any. Otherwise returns an empty array. See also: IsColliding. 286 | /// 287 | /// Bounds to check. Passing by ref as it improves performance with structs. 288 | /// List result. 289 | /// Objects that intersect with the specified bounds. 290 | public void GetColliding(ref BoundingBox checkBounds, List result) 291 | { 292 | // Are the input bounds at least partially in this node? 293 | if (!_bounds.Intersects(checkBounds)) 294 | { 295 | return; 296 | } 297 | 298 | // Check against any objects in this node 299 | for (int i = 0; i < _objects.Count; i++) 300 | { 301 | if (_objects[i].Bounds.Intersects(checkBounds)) 302 | { 303 | result.Add(_objects[i].Obj); 304 | } 305 | } 306 | 307 | // Check children 308 | if (_children != null) 309 | { 310 | for (int i = 0; i < 8; i++) 311 | { 312 | _children[i].GetColliding(ref checkBounds, result); 313 | } 314 | } 315 | } 316 | 317 | /// 318 | /// Returns an array of objects that intersect with the specified ray, if any. Otherwise returns an empty array. See also: IsColliding. 319 | /// 320 | /// Ray to check. Passing by ref as it improves performance with structs. 321 | /// Distance to check. 322 | /// List result. 323 | /// Objects that intersect with the specified ray. 324 | public void GetColliding(ref Ray checkRay, List result, float maxDistance = float.PositiveInfinity) 325 | { 326 | float distance; 327 | // Is the input ray at least partially in this node? 328 | if (!_bounds.IntersectRay(checkRay, out distance) || distance > maxDistance) 329 | { 330 | return; 331 | } 332 | 333 | // Check against any objects in this node 334 | for (int i = 0; i < _objects.Count; i++) 335 | { 336 | if (_objects[i].Bounds.IntersectRay(checkRay, out distance) && distance <= maxDistance) 337 | { 338 | result.Add(_objects[i].Obj); 339 | } 340 | } 341 | 342 | // Check children 343 | if (_children != null) 344 | { 345 | for (int i = 0; i < 8; i++) 346 | { 347 | _children[i].GetColliding(ref checkRay, result, maxDistance); 348 | } 349 | } 350 | } 351 | 352 | /// 353 | /// Set the 8 children of this octree. 354 | /// 355 | /// The 8 new child nodes. 356 | public void SetChildren(Node[] childOctrees) 357 | { 358 | if (childOctrees.Length != 8) 359 | throw new ArgumentException("Child octree array must be length 8. Was length: " + childOctrees.Length, 360 | nameof(childOctrees)); 361 | 362 | _children = childOctrees; 363 | } 364 | 365 | /// 366 | /// We can shrink the octree if: 367 | /// - This node is >= double minLength in length 368 | /// - All objects in the root node are within one octant 369 | /// - This node doesn't have children, or does but 7/8 children are empty 370 | /// We can also shrink it if there are no objects left at all! 371 | /// 372 | /// Minimum dimensions of a node in this octree. 373 | /// The new root, or the existing one if we didn't shrink. 374 | public Node ShrinkIfPossible(float minLength) 375 | { 376 | if (BaseLength < (2 * minLength)) 377 | { 378 | return this; 379 | } 380 | if (_objects.Count == 0 && (_children == null || _children.Length == 0)) 381 | { 382 | return this; 383 | } 384 | 385 | // Check objects in root 386 | int bestFit = -1; 387 | for (int i = 0; i < _objects.Count; i++) 388 | { 389 | OctreeObject curObj = _objects[i]; 390 | int newBestFit = BestFitChild(curObj.Bounds.Center); 391 | if (i == 0 || newBestFit == bestFit) 392 | { 393 | // In same octant as the other(s). Does it fit completely inside that octant? 394 | if (Encapsulates(_childBounds[newBestFit], curObj.Bounds)) 395 | { 396 | if (bestFit < 0) 397 | { 398 | bestFit = newBestFit; 399 | } 400 | } 401 | else 402 | { 403 | // Nope, so we can't reduce. Otherwise we continue 404 | return this; 405 | } 406 | } 407 | else 408 | { 409 | return this; // Can't reduce - objects fit in different octants 410 | } 411 | } 412 | 413 | // Check objects in children if there are any 414 | if (_children != null) 415 | { 416 | bool childHadContent = false; 417 | for (int i = 0; i < _children.Length; i++) 418 | { 419 | if (_children[i].HasAnyObjects()) 420 | { 421 | if (childHadContent) 422 | { 423 | return this; // Can't shrink - another child had content already 424 | } 425 | if (bestFit >= 0 && bestFit != i) 426 | { 427 | return this; // Can't reduce - objects in root are in a different octant to objects in child 428 | } 429 | childHadContent = true; 430 | bestFit = i; 431 | } 432 | } 433 | } 434 | 435 | // Can reduce 436 | if (_children == null) 437 | { 438 | // We don't have any children, so just shrink this node to the new size 439 | // We already know that everything will still fit in it 440 | SetValues(BaseLength / 2, _minSize, _looseness, _childBounds[bestFit].Center); 441 | return this; 442 | } 443 | 444 | // No objects in entire octree 445 | if (bestFit == -1) 446 | { 447 | return this; 448 | } 449 | 450 | // We have children. Use the appropriate child as the new root node 451 | return _children[bestFit]; 452 | } 453 | 454 | /// 455 | /// Find which child node this object would be most likely to fit in. 456 | /// 457 | /// The object's bounds center. 458 | /// One of the eight child octants. 459 | public int BestFitChild(Vector3 objBoundsCenter) 460 | { 461 | return (objBoundsCenter.X <= Center.X ? 0 : 1) 462 | + (objBoundsCenter.Y >= Center.Y ? 0 : 4) 463 | + (objBoundsCenter.Z <= Center.Z ? 0 : 2); 464 | } 465 | 466 | /// 467 | /// Checks if this node or anything below it has something in it. 468 | /// 469 | /// True if this node or any of its children, grandchildren etc have something in them 470 | public bool HasAnyObjects() 471 | { 472 | if (_objects.Count > 0) return true; 473 | 474 | if (_children != null) 475 | { 476 | for (int i = 0; i < 8; i++) 477 | { 478 | if (_children[i].HasAnyObjects()) return true; 479 | } 480 | } 481 | 482 | return false; 483 | } 484 | 485 | // #### PRIVATE METHODS #### 486 | 487 | /// 488 | /// Set values for this node. 489 | /// 490 | /// Length of this node, not taking looseness into account. 491 | /// Minimum size of nodes in this octree. 492 | /// Multiplier for baseLengthVal to get the actual size. 493 | /// Center position of this node. 494 | private void SetValues(float baseLengthVal, float minSizeVal, float loosenessVal, Vector3 centerVal) 495 | { 496 | BaseLength = baseLengthVal; 497 | _minSize = minSizeVal; 498 | _looseness = loosenessVal; 499 | Center = centerVal; 500 | _adjLength = _looseness * baseLengthVal; 501 | 502 | // Create the bounding box. 503 | Vector3 size = new Vector3(_adjLength, _adjLength, _adjLength); 504 | _bounds = new BoundingBox(Center, size); 505 | 506 | float quarter = BaseLength / 4f; 507 | float childActualLength = (BaseLength / 2) * _looseness; 508 | Vector3 childActualSize = new Vector3(childActualLength, childActualLength, childActualLength); 509 | _childBounds = new BoundingBox[8]; 510 | _childBounds[0] = new BoundingBox(Center + new Vector3(-quarter, quarter, -quarter), childActualSize); 511 | _childBounds[1] = new BoundingBox(Center + new Vector3(quarter, quarter, -quarter), childActualSize); 512 | _childBounds[2] = new BoundingBox(Center + new Vector3(-quarter, quarter, quarter), childActualSize); 513 | _childBounds[3] = new BoundingBox(Center + new Vector3(quarter, quarter, quarter), childActualSize); 514 | _childBounds[4] = new BoundingBox(Center + new Vector3(-quarter, -quarter, -quarter), childActualSize); 515 | _childBounds[5] = new BoundingBox(Center + new Vector3(quarter, -quarter, -quarter), childActualSize); 516 | _childBounds[6] = new BoundingBox(Center + new Vector3(-quarter, -quarter, quarter), childActualSize); 517 | _childBounds[7] = new BoundingBox(Center + new Vector3(quarter, -quarter, quarter), childActualSize); 518 | } 519 | 520 | /// 521 | /// Private counterpart to the public Add method. 522 | /// 523 | /// Object to add. 524 | /// 3D bounding box around the object. 525 | private void SubAdd(T obj, BoundingBox objBounds) 526 | { 527 | // We know it fits at this level if we've got this far 528 | 529 | // We always put things in the deepest possible child 530 | // So we can skip some checks if there are children already 531 | if (!HasChildren) 532 | { 533 | // Just add if few objects are here, or children would be below min size 534 | if (_objects.Count < NumObjectsAllowed || (BaseLength / 2) < _minSize) 535 | { 536 | OctreeObject newObj = new OctreeObject { Obj = obj, Bounds = objBounds }; 537 | _objects.Add(newObj); 538 | return; // We're done. No children yet 539 | } 540 | 541 | // Fits at this level, but we can go deeper. Would it fit there? 542 | // Create the 8 children 543 | if (_children == null) 544 | { 545 | Split(); 546 | if (_children == null) 547 | throw new InvalidOperationException("Child creation failed for an unknown reason. Early exit."); 548 | 549 | // Now that we have the new children, see if this node's existing objects would fit there 550 | for (int i = _objects.Count - 1; i >= 0; i--) 551 | { 552 | OctreeObject existingObj = _objects[i]; 553 | // Find which child the object is closest to based on where the 554 | // object's center is located in relation to the octree's center 555 | int bestFitChild = BestFitChild(existingObj.Bounds.Center); 556 | // Does it fit? 557 | if (Encapsulates(_children[bestFitChild]._bounds, existingObj.Bounds)) 558 | { 559 | _children[bestFitChild].SubAdd(existingObj.Obj, existingObj.Bounds); // Go a level deeper 560 | _objects.Remove(existingObj); // Remove from here 561 | } 562 | } 563 | } 564 | } 565 | 566 | // Handle the new object we're adding now 567 | int bestFit = BestFitChild(objBounds.Center); 568 | if (Encapsulates(_children[bestFit]._bounds, objBounds)) 569 | { 570 | _children[bestFit].SubAdd(obj, objBounds); 571 | } 572 | else 573 | { 574 | // Didn't fit in a child. We'll have to it to this node instead 575 | OctreeObject newObj = new OctreeObject { Obj = obj, Bounds = objBounds }; 576 | _objects.Add(newObj); 577 | } 578 | } 579 | 580 | /// 581 | /// Private counterpart to the public method. 582 | /// 583 | /// Object to remove. 584 | /// 3D bounding box around the object. 585 | /// True if the object was removed successfully. 586 | private bool SubRemove(T obj, BoundingBox objBounds) 587 | { 588 | bool removed = false; 589 | 590 | for (int i = 0; i < _objects.Count; i++) 591 | { 592 | if (_objects[i].Obj.Equals(obj)) 593 | { 594 | removed = _objects.Remove(_objects[i]); 595 | break; 596 | } 597 | } 598 | 599 | if (!removed && _children != null) 600 | { 601 | int bestFitChild = BestFitChild(objBounds.Center); 602 | removed = _children[bestFitChild].SubRemove(obj, objBounds); 603 | } 604 | 605 | if (removed && _children != null) 606 | { 607 | // Check if we should merge nodes now that we've removed an item 608 | if (ShouldMerge()) 609 | { 610 | Merge(); 611 | } 612 | } 613 | 614 | return removed; 615 | } 616 | 617 | /// 618 | /// Splits the octree into eight children. 619 | /// 620 | private void Split() 621 | { 622 | float quarter = BaseLength / 4f; 623 | float newLength = BaseLength / 2; 624 | _children = new Node[8]; 625 | _children[0] = new Node( 626 | newLength, 627 | _minSize, 628 | _looseness, 629 | Center + new Vector3(-quarter, quarter, -quarter)); 630 | _children[1] = new Node( 631 | newLength, 632 | _minSize, 633 | _looseness, 634 | Center + new Vector3(quarter, quarter, -quarter)); 635 | _children[2] = new Node( 636 | newLength, 637 | _minSize, 638 | _looseness, 639 | Center + new Vector3(-quarter, quarter, quarter)); 640 | _children[3] = new Node( 641 | newLength, 642 | _minSize, 643 | _looseness, 644 | Center + new Vector3(quarter, quarter, quarter)); 645 | _children[4] = new Node( 646 | newLength, 647 | _minSize, 648 | _looseness, 649 | Center + new Vector3(-quarter, -quarter, -quarter)); 650 | _children[5] = new Node( 651 | newLength, 652 | _minSize, 653 | _looseness, 654 | Center + new Vector3(quarter, -quarter, -quarter)); 655 | _children[6] = new Node( 656 | newLength, 657 | _minSize, 658 | _looseness, 659 | Center + new Vector3(-quarter, -quarter, quarter)); 660 | _children[7] = new Node( 661 | newLength, 662 | _minSize, 663 | _looseness, 664 | Center + new Vector3(quarter, -quarter, quarter)); 665 | } 666 | 667 | /// 668 | /// Merge all children into this node - the opposite of Split. 669 | /// Note: We only have to check one level down since a merge will never happen if the children already have children, 670 | /// since THAT won't happen unless there are already too many objects to merge. 671 | /// 672 | private void Merge() 673 | { 674 | // Note: We know children != null or we wouldn't be merging 675 | for (int i = 0; i < 8; i++) 676 | { 677 | Node curChild = _children[i]; 678 | int numObjects = curChild._objects.Count; 679 | for (int j = numObjects - 1; j >= 0; j--) 680 | { 681 | OctreeObject curObj = curChild._objects[j]; 682 | _objects.Add(curObj); 683 | } 684 | } 685 | // Remove the child nodes (and the objects in them - they've been added elsewhere now) 686 | _children = null; 687 | } 688 | 689 | /// 690 | /// Checks if outerBounds encapsulates innerBounds. 691 | /// 692 | /// Outer bounds. 693 | /// Inner bounds. 694 | /// True if innerBounds is fully encapsulated by outerBounds. 695 | private static bool Encapsulates(BoundingBox outerBounds, BoundingBox innerBounds) 696 | { 697 | return outerBounds.Contains(innerBounds.Min) && outerBounds.Contains(innerBounds.Max); 698 | } 699 | 700 | /// 701 | /// Checks if there are few enough objects in this node and its children that the children should all be merged into this. 702 | /// 703 | /// True there are less or the same amount of objects in this and its children than . 704 | private bool ShouldMerge() 705 | { 706 | int totalObjects = _objects.Count; 707 | if (_children != null) 708 | { 709 | foreach (Node child in _children) 710 | { 711 | if (child._children != null) 712 | { 713 | // If any of the *children* have children, there are definitely too many to merge, 714 | // or the child would have been merged already 715 | return false; 716 | } 717 | totalObjects += child._objects.Count; 718 | } 719 | } 720 | return totalObjects <= NumObjectsAllowed; 721 | } 722 | } 723 | } 724 | } --------------------------------------------------------------------------------