/// --------------------------------------------- /// Senses Pack for Behavior Designer Pro /// Copyright (c) Opsive. All Rights Reserved. /// https://www.opsive.com /// --------------------------------------------- namespace Opsive.BehaviorDesigner.AddOns.SensesPack.Runtime.Utility { using System; using Unity.Burst; using Unity.Collections; using UnityEngine; /// /// Data structure used to efficiently query objects within a 3D space. /// [BurstCompile] public struct OctreeNode where T : unmanaged, IPosition, IEquatable { private Bounds Bounds; private NativeList Objects; private NativeArray> Children; public Bounds NodeBounds { get => Bounds; } public NativeList NodeObjects { get => Objects; } public NativeArray> NodeChildren { get => Children; } /// /// Initializes an octree node with a given 3D region and capacity. /// /// The bounding box for the node. /// Specifies the maximum number of objects before subdivision. public OctreeNode(Bounds bounds, int capacity = 4) { Bounds = bounds; Objects = new NativeList(capacity, Allocator.Persistent); Children = default(NativeArray>); } /// /// Attempts to insert an object into the octree. /// /// The object that should be inserted. /// The new octree object. [BurstCompile] public OctreeNode Insert(T obj) { return InsertInternal(obj).Item1; } /// /// Attempts to insert an object into the octree. /// /// The object that should be inserted. /// A tuple containing the new octree object and the status if the object was inserted. [BurstCompile] private (OctreeNode, bool) InsertInternal(T obj) { if (!Bounds.Contains(obj.Position)) { return (this, false); } if (Objects.Length < Objects.Capacity) { Objects.Add(obj); return (this, true); } // Subdivide if the the capacity is exceeded. if (!Children.IsCreated) { Subdivide(); } // Attempt to insert into one of the child nodes. for (int i = 0; i < Children.Length; ++i) { var insertTuple = Children[i].InsertInternal(obj); Children[i] = insertTuple.Item1; if (insertTuple.Item2) { return (this, true); } } return (this, false); } /// /// Retrieves objects within a given search area. /// /// The search bounds. /// The found objects. /// The number of objects found. [BurstCompile] public int Query(Bounds range, NativeList found) { if (!Bounds.Intersects(range)) { return 0; } var count = 0; for (int i = 0; i < Objects.Length; ++i) { if (range.Contains(Objects[i].Position)) { found.Add(Objects[i]); count++; } } // The child nodes may also contain objects within the range. if (Children.IsCreated) { for (int i = 0; i < Children.Length; ++i) { count += Children[i].Query(range, found); } } return count; } /// /// Subdivides the into eight smaller child nodes. /// [BurstCompile] private void Subdivide() { Children = new NativeArray>(8, Allocator.Persistent); var size = Bounds.size / 2f; // Generate eight child nodes by shifting the center. for (int i = 0; i < 8; i++) { var offset = new Vector3( (i & 1) == 0 ? -size.x / 2 : size.x / 2, (i & 2) == 0 ? -size.y / 2 : size.y / 2, (i & 4) == 0 ? -size.z / 2 : size.z / 2 ); var childBounds = new Bounds(Bounds.center + offset, size); Children[i] = new OctreeNode(childBounds, Objects.Length); } } /// /// Removes an object from the octree. /// The object that should be removed. /// /// The new octree object. public OctreeNode Remove(T obj) { return RemoveInternal(obj).Item1; } /// /// Removes an object from the octree. /// The object that should be removed. /// /// A tuple containing the new octree object and the status if the object was removed. [BurstCompile] private (OctreeNode, bool) RemoveInternal(T obj) { if (!Bounds.Contains(obj.Position)) { return (this, false); } for (int i = 0; i < Objects.Length; ++i) { if (Objects[i].Equals(obj)) { Objects.RemoveAtSwapBack(i); return (this, true); } } if (Children.IsCreated) { for (int i = 0; i < Children.Length; ++i) { var removeTuple = Children[i].RemoveInternal(obj); Children[i] = removeTuple.Item1; if (removeTuple.Item2) { return (this, true); } } } return (this, false); } /// /// Cleans up any child octree nodes that are no longer used. /// [BurstCompile] private void Cleanup() { if (!Children.IsCreated) { return; } // If a child object exists then it should not be disposed. for (int i = 0; i < Children.Length; ++i) { if (Children[i].Objects.Length > 0) { return; } } // No objects exist anymore - dispose of the object. for (int i = 0; i < Children.Length; ++i) { Children[i].Dispose(); } Children.Dispose(); } /// /// Disposes the object. /// public void Dispose() { Objects.Dispose(); if (Children.IsCreated) { for (int i = 0; i < Children.Length; i++) { Children[i].Dispose(); } Children.Dispose(); } } } }