├── 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 | [](https://github.com/mcserep/NetOctree/actions/workflows/ci.yml)
7 | [](https://codecov.io/gh/mcserep/NetOctree)
8 | [](https://www.nuget.org/packages/NetOctree/)
9 | [](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 | }
--------------------------------------------------------------------------------