|
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 | +[](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