/// ---------------------------------------------
/// 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();
}
}
}
}