Skip to content

Commit 281c696

Browse files
committed
readme draft
1 parent 214b89f commit 281c696

File tree

7 files changed

+217
-22
lines changed

7 files changed

+217
-22
lines changed

.idea/.idea.NativeTrees/.idea/.gitignore

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/.idea.NativeTrees/.idea/encodings.xml

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Assets/Example/NativeOctreeExample.cs

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ private void Start()
5151
var vertices = mesh.vertices;
5252

5353
var sw = Stopwatch.StartNew();
54-
var actualTriangles = new NativeArray<Triangle>(triangles.Length / 3, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
55-
var actualBounds = new NativeArray<AABB>(actualTriangles.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
54+
var tris = new NativeArray<Triangle>(triangles.Length / 3, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
55+
var actualBounds = new NativeArray<AABB>(tris.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
5656

5757
int n = 0;
5858
for (int i = 0; i < triangles.Length; i += 3)
@@ -63,18 +63,27 @@ private void Start()
6363
vertices[triangles[i + 2]], n);
6464

6565
actualBounds[n] = tri.GetAABB();
66-
actualTriangles[n++] = tri;
66+
tris[n++] = tri;
6767
}
6868
Debug.Log($"Triangles converted in {sw.Elapsed.TotalMilliseconds}ms");
6969
sw.Restart();
7070

7171

72-
for (int i = 0; i < actualTriangles.Length; i++)
72+
// Insert a bunch of triangles
73+
for (int i = 0; i < tris.Length; i++)
7374
{
74-
octree.Insert(actualTriangles[i], actualBounds[i]);
75-
//var tri = actualTriangles[i];
76-
//octree.InsertPoint(tri, tri.a);
75+
var triangle = tris[i];
76+
octree.Insert(triangle, triangle.GetAABB());
7777
}
78+
79+
// Insert entities that are 'points'
80+
for (int i = 0; i < entities.Length; i++)
81+
{
82+
var entity = entities[i];
83+
octree.InsertPoint(entities[i], positions[i]);
84+
}
85+
86+
//
7887

7988
/*
8089
var job = new PopulateJob()
@@ -88,7 +97,7 @@ private void Start()
8897

8998
Debug.Log($"Octree constructed in {sw.Elapsed.TotalMilliseconds}ms");
9099

91-
actualTriangles.Dispose();
100+
tris.Dispose();
92101
actualBounds.Dispose();
93102
}
94103

@@ -106,6 +115,7 @@ private void Update()
106115
var sw = Stopwatch.StartNew();
107116
bool didHit = octree.Raycast<TriangleRayIntersecter>(ray, out var hit);
108117

118+
109119
if (didHit)
110120
{
111121
Debug.Log($"(Non burst) Raycast performed in {sw.Elapsed.TotalMilliseconds}ms");

Assets/Octree/IOctreeRayIntersecter.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,8 @@ public interface IOctreeRayIntersecter<T>
1414
/// intersection test is fairly expensive, it may be a good idea to first test against the object's bounds in this method.</remarks>
1515
bool IntersectRay(in PrecomputedRay ray, T obj, AABB objBounds, out float distance);
1616
}
17+
18+
19+
20+
1721
}

Assets/Octree/NativeOctreeExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public static bool RaycastAABB<T>(this NativeOctree<T> octree, Ray ray, out Octr
2323
return octree.Raycast<RayAABBIntersecter<T>>(ray, out hit);
2424
}
2525

26-
struct RayAABBIntersecter<T> : IOctreeRayIntersecter<T> where T : unmanaged
26+
struct RayAABBIntersecter<T> : IOctreeRayIntersecter<T>
2727
{
2828
public bool IntersectRay(in PrecomputedRay ray, T obj, AABB objBounds, out float distance)
2929
{

Assets/QuadTree/NativeQuadtree.cs

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ namespace NativeTrees
1111
{
1212
/// <summary>
1313
/// Generic Burst/ECS compatible sparse quadtree that stores objects together with their axis aligned bounding boxes (AABB2D's)
14+
/// Ported from <see cref="NativeOctree{T}"/>
1415
/// <para>
1516
/// Supported queries:
1617
/// - Raycast
@@ -20,7 +21,7 @@ namespace NativeTrees
2021
/// <para>
2122
/// Other features:
2223
/// - Implemented as a sparse quadtree, so only stores nodes that are occupied,
23-
/// allowing it to go to a max depth of 10 (this could be more if the nodeId's are stored as long values
24+
/// allowing it to go to a max depth of 15 (this could be more if the nodeId's are stored as long values
2425
/// - Supports insertion of AABB2D's
2526
/// - Fast path insertino for points
2627
/// - Employs an extremely fast technique of checking for AABB2D / quad overlaps, see comment near the end
@@ -248,20 +249,18 @@ private static int PointToQuadIndex(float2 point, float2 nodeCenter) =>
248249
math.bitmask((point >= nodeCenter).xyxy) >> 2;
249250

250251
/// <summary>
251-
/// Max depth of the quadtree, we need 2 bits for each level we go down
252-
/// leave one bit for root node
252+
/// Max depth of the quadtree
253253
/// </summary>
254-
public const int MaxDepth = 8 * sizeof(int) / 2 - 1;
254+
public const int MaxDepth = 8 * sizeof(int) / 2 - 1; // we need 2 bits for each level we go down
255+
/// leave one bit for root node Id
255256

256257
/// <summary>
257258
/// Gets a unique identifier for a node.
258-
/// The octants are identified using the following pattern, where a zero stands for the negative side:
259-
///
260-
/// 110 111
261-
/// 010 011
262-
///
263-
/// 100 101
264-
/// 000 001
259+
/// The quads are identified using the following pattern, where a zero stands for the negative side:
260+
///
261+
/// 10 11
262+
///
263+
/// 00 01
265264
///
266265
/// For each level we go down, we shift these bits 2 spaces to the left
267266
/// </summary>
@@ -316,7 +315,6 @@ private static int GetBoundsMask(float2 nodeCenter, AABB2D AABB2D)
316315
new float2(1, 1),
317316
};
318317

319-
320318
/// <summary>
321319
/// Stores an object together with it's bounds
322320
/// </summary>

README.md

Lines changed: 167 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,167 @@
1-
# NativeOctree
1+
# NativeTrees
2+
3+
Generic Burst/ECS compatible sparse octree and quadtree
4+
that store objects together with their axis aligned bounding boxes (AABB's)
5+
6+
### Supported queries:
7+
- Raycast
8+
- Range (AABB overlap)
9+
- N-nearest neighbour
10+
11+
12+
### Other features:
13+
- Implemented as a sparse tree, so only stores nodes that are occupied. Memory usage is therefore relatively low.
14+
The octree has a max depth of 10 and the quad tree a max depth of 15.
15+
- Supports insertion of AABB's
16+
- Fast insertion path for points
17+
- Employs an extremely fast technique of checking for AABB / octant/quad overlaps, for an explanation, see the source code
18+
- Optimized with SIMD instructions so greatly benefits from burst compilation
19+
20+
### Limitations:
21+
- No remove or update. Tried several approaches but they either left an unbalanced tree or doing a
22+
full clear and re-insert was faster.
23+
24+
- No foreach support, we leverage the stack and "struct" delegates, which suits the recursive
25+
nature of the tree better.
26+
27+
### Future todo's:
28+
- Frustrum query
29+
- 'Fat' raycast (virtually expand AABB's of nodes and objects when testing for ray intersections)
30+
31+
## Usage
32+
33+
There are two samples included that show how to use the octree and quadtree.
34+
35+
36+
### Insertion
37+
The objects can be of any unmanaged type, when inserting, an AABB must be provided:
38+
39+
// Insert a bunch of triangles
40+
for (int i = 0; i < tris.Length; i++)
41+
{
42+
var triangle = tris[i];
43+
octree.Insert(triangle, triangle.GetAABB());
44+
}
45+
46+
Often times however, it's more efficient to insert Id's that map to something outside of
47+
the tree (like DOTS entities).
48+
49+
If you know your objects are points, you can insert them faster by using:
50+
51+
// Insert entities that are 'points'
52+
for (int i = 0; i < entities.Length; i++)
53+
{
54+
var entity = entities[i];
55+
octree.InsertPoint(entities[i], positions[i]);
56+
}
57+
58+
### Queries
59+
60+
All of the supported queries use the same pattern which is
61+
to (ab)use structs as a sort of delegate. This separates collision/intersection
62+
code from the type of objects, allowing you to insert even primitive types or types from another assembly.
63+
This turned out to be the most efficent and easiest to implement while keeping things fully compatibly with Unity's Burst compiler.
64+
65+
### Raycast
66+
67+
A raycast query for example, requires you to implement IOctreeRayIntersecter which
68+
acts as a delegate to determine if a ray intersects with an object that's in the tree.
69+
70+
public static bool RaycastAABB<T>(this NativeOctree<T> octree, Ray ray, out OctreeRaycastHit<T> hit) where T : unmanaged
71+
{
72+
return octree.Raycast<RayAABBIntersecter<T>>(ray, out hit);
73+
}
74+
75+
struct RayAABBIntersecter<T> : IOctreeRayIntersecter<T>
76+
{
77+
public bool IntersectRay(in PrecomputedRay ray, T obj, AABB objBounds, out float distance)
78+
{
79+
return objBounds.IntersectsRay(ray, out distance);
80+
}
81+
}
82+
83+
The example above just tests the ray against the object's bounds. (See NativeOctreeExtensions) But you could go a step further
84+
and test it against a triangle, a collider and so forth. Note that the tree itself
85+
does not automatically test for Ray-AABB intersections on the objects, so it's usually a good decision to early exit
86+
if the ray doesn't exit with the object's bounds since those checks are cheap.
87+
88+
### Nearest Neighbour
89+
NativeTrees support N-nearest neighbour queries. You should implement IOctreeeNearestVisitor and IOctreeDistanceProvider.
90+
91+
struct AABBDistanceSquaredProvider<T> : IOctreeDistanceProvider<T>
92+
{
93+
// Just return the distance squared to our bounds
94+
public float DistanceSquared(float3 point, T obj, AABB bounds) => bounds.DistanceSquared(point);
95+
}
96+
97+
struct OctreeNearestAABBVisitor<T> : IOctreeNearestVisitor<T>
98+
{
99+
public T nearest;
100+
public bool found;
101+
102+
public bool OnVist(T obj)
103+
{
104+
this.found = true;
105+
this.nearest = obj;
106+
107+
return false; // immediately stop iterating at first hit
108+
// if we want the 2nd or 3rd neighbour, we could iterate on and keep track of the count!
109+
}
110+
}
111+
112+
The extensions classes show an example of these implementation. But only for AABB's.
113+
If you need more detail on your distance, you can implement your type specific behaviour using these interfaces.
114+
115+
To get the nearest neighbour:
116+
117+
var visitor = new OctreeNearestAABBVisitor<Entity>();
118+
octree.Nearest(point, maxDistance, ref visitor, default(AABBDistanceSquaredProvider<Entity>));
119+
Entity nearestEntity = visitor.nearest;
120+
121+
### Range
122+
123+
Here's an example that adds unique objects that overlap with a range to a hashset:
124+
125+
public static void RangeAABBUnique<T>(this NativeOctree<T> octree, AABB range, NativeParallelHashSet<T> results) where T : unmanaged, IEquatable<T>
126+
{
127+
var vistor = new RangeAABBUniqueVisitor<T>()
128+
{
129+
results = results
130+
};
131+
132+
octree.Range(range, ref vistor);
133+
}
134+
135+
struct RangeAABBUniqueVisitor<T> : IOctreeRangeVisitor<T> where T : unmanaged, IEquatable<T>
136+
{
137+
public NativeParallelHashSet<T> results;
138+
139+
public bool OnVisit(T obj, AABB objBounds, AABB queryRange)
140+
{
141+
if (objBounds.Overlaps(queryRange))
142+
results.Add(obj);
143+
144+
return true; // always keep iterating, we want to catch all objects
145+
}
146+
}
147+
148+
Note that OnVisit is called for all objects for which their octant/quad overlaps with the input AABB.
149+
So that's why the example code checks if the object's bounds overlap with the input range.
150+
151+
## Donate
152+
If this project saved you some time, consider saving me some spare bucks ;)
153+
154+
[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](YOUR_EMAIL_CODE)
155+
156+
157+
### Sources
158+
159+
Fast raycast traversal algorithm:
160+
https://daeken.svbtle.com/a-stupidly-simple-fast-quadtree-traversal-for-ray-intersection
161+
162+
Ray box 'slab' intersection method:
163+
https://tavianator.com/2011/ray_box.html
164+
165+
Nearest neighbour search:
166+
https://stackoverflow.com/a/41306992
167+

0 commit comments

Comments
 (0)