959e80cf72
assets upload description.
353 lines
14 KiB
C#
353 lines
14 KiB
C#
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
|
|
// A Dynamic, Loose Octree for storing any objects that can be described with AABB bounds
|
|
// See also: PointOctree, where objects are stored as single points and some code can be simplified
|
|
// Octree: An octree is 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.
|
|
// Dynamic: The octree grows or shrinks as required when objects as 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.
|
|
// 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.
|
|
// T: The content of the octree can be anything, since the bounds data is supplied separately.
|
|
|
|
// Originally written for my game Scraps (http://www.scrapsgame.com) but intended to be general-purpose.
|
|
// Copyright 2014 Nition, BSD licence (see LICENCE file). http://nition.co
|
|
// Unity-based, but could be adapted to work in pure C#
|
|
|
|
// Note: For loops are often used here since in some cases (e.g. the IsColliding method)
|
|
// they actually give much better performance than using Foreach, even in the compiled build.
|
|
// Using a LINQ expression is worse again than Foreach.
|
|
|
|
namespace AwesomeTechnologies.External.Octree
|
|
{
|
|
|
|
|
|
public class BoundsOctree<T>
|
|
{
|
|
// The total amount of objects currently in the tree
|
|
public int Count { get; private set; }
|
|
|
|
// Root node of the octree
|
|
BoundsOctreeNode<T> rootNode;
|
|
|
|
// Should be a value between 1 and 2. A multiplier for the base size of a node.
|
|
// 1.0 is a "normal" octree, while values > 1 have overlap
|
|
readonly float looseness;
|
|
|
|
// Size that the octree was on creation
|
|
readonly float initialSize;
|
|
|
|
// Minimum side length that a node can be - essentially an alternative to having a max depth
|
|
readonly float minSize;
|
|
|
|
// For collision visualisation. Automatically removed in builds.
|
|
#if UNITY_EDITOR
|
|
const int numCollisionsToSave = 4;
|
|
readonly Queue<Bounds> lastBoundsCollisionChecks = new Queue<Bounds>();
|
|
readonly Queue<Ray> lastRayCollisionChecks = new Queue<Ray>();
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Constructor for the bounds octree.
|
|
/// </summary>
|
|
/// <param name="initialWorldSize">Size of the sides of the initial node, in metres. The octree will never shrink smaller than this.</param>
|
|
/// <param name="initialWorldPos">Position of the centre of the initial node.</param>
|
|
/// <param name="minNodeSize">Nodes will stop splitting if the new nodes would be smaller than this (metres).</param>
|
|
/// <param name="loosenessVal">Clamped between 1 and 2. Values > 1 let nodes overlap.</param>
|
|
public BoundsOctree(float initialWorldSize, Vector3 initialWorldPos, float minNodeSize, float loosenessVal)
|
|
{
|
|
if (minNodeSize > initialWorldSize)
|
|
{
|
|
Debug.LogWarning("Minimum node size must be at least as big as the initial world size. Was: " +
|
|
minNodeSize + " Adjusted to: " + initialWorldSize);
|
|
minNodeSize = initialWorldSize;
|
|
}
|
|
Count = 0;
|
|
initialSize = initialWorldSize;
|
|
minSize = minNodeSize;
|
|
looseness = Mathf.Clamp(loosenessVal, 1.0f, 2.0f);
|
|
rootNode = new BoundsOctreeNode<T>(initialSize, minSize, loosenessVal, initialWorldPos);
|
|
}
|
|
|
|
// #### PUBLIC METHODS ####
|
|
|
|
/// <summary>
|
|
/// Add an object.
|
|
/// </summary>
|
|
/// <param name="obj">Object to add.</param>
|
|
/// <param name="objBounds">3D bounding box around the object.</param>
|
|
public void Add(T obj, Bounds objBounds)
|
|
{
|
|
// Add object or expand the octree until it can be added
|
|
int count = 0; // Safety check against infinite/excessive growth
|
|
while (!rootNode.Add(obj, objBounds))
|
|
{
|
|
Grow(objBounds.center - rootNode.Center);
|
|
if (++count > 20)
|
|
{
|
|
Debug.LogError("Aborted Add operation as it seemed to be going on forever (" + (count - 1) +
|
|
") attempts at growing the octree.");
|
|
return;
|
|
}
|
|
}
|
|
Count++;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove an object. Makes the assumption that the object only exists once in the tree.
|
|
/// </summary>
|
|
/// <param name="obj">Object to remove.</param>
|
|
/// <returns>True if the object was removed successfully.</returns>
|
|
public bool Remove(T obj)
|
|
{
|
|
bool removed = rootNode.Remove(obj);
|
|
|
|
// See if we can shrink the octree down now that we've removed the item
|
|
if (removed)
|
|
{
|
|
Count--;
|
|
Shrink();
|
|
}
|
|
|
|
return removed;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes the specified object at the given position. Makes the assumption that the object only exists once in the tree.
|
|
/// </summary>
|
|
/// <param name="obj">Object to remove.</param>
|
|
/// <param name="objBounds">3D bounding box around the object.</param>
|
|
/// <returns>True if the object was removed successfully.</returns>
|
|
public bool Remove(T obj, Bounds objBounds)
|
|
{
|
|
bool removed = rootNode.Remove(obj, objBounds);
|
|
|
|
// See if we can shrink the octree down now that we've removed the item
|
|
if (removed)
|
|
{
|
|
Count--;
|
|
Shrink();
|
|
}
|
|
|
|
return removed;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if the specified bounds intersect with anything in the tree. See also: GetColliding.
|
|
/// </summary>
|
|
/// <param name="checkBounds">bounds to check.</param>
|
|
/// <returns>True if there was a collision.</returns>
|
|
public bool IsColliding(Bounds checkBounds)
|
|
{
|
|
#if UNITY_EDITOR
|
|
// For debugging
|
|
//AddCollisionCheck(checkBounds);
|
|
#endif
|
|
return rootNode.IsColliding(ref checkBounds);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if the specified ray intersects with anything in the tree. See also: GetColliding.
|
|
/// </summary>
|
|
/// <param name="checkRay">ray to check.</param>
|
|
/// <param name="maxDistance">distance to check.</param>
|
|
/// <returns>True if there was a collision.</returns>
|
|
public bool IsColliding(Ray checkRay, float maxDistance)
|
|
{
|
|
#if UNITY_EDITOR
|
|
// For debugging
|
|
//AddCollisionCheck(checkRay);
|
|
#endif
|
|
return rootNode.IsColliding(ref checkRay, maxDistance);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns an array of objects that intersect with the specified bounds, if any. Otherwise returns an empty array. See also: IsColliding.
|
|
/// </summary>
|
|
/// <param name="collidingWith">list to store intersections.</param>
|
|
/// <param name="checkBounds">bounds to check.</param>
|
|
/// <returns>Objects that intersect with the specified bounds.</returns>
|
|
public void GetColliding(List<T> collidingWith, Bounds checkBounds)
|
|
{
|
|
#if UNITY_EDITOR
|
|
// For debugging
|
|
//AddCollisionCheck(checkBounds);
|
|
#endif
|
|
rootNode.GetColliding(ref checkBounds, collidingWith);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns an array of objects that intersect with the specified ray, if any. Otherwise returns an empty array. See also: IsColliding.
|
|
/// </summary>
|
|
/// <param name="collidingWith">list to store intersections.</param>
|
|
/// <param name="checkRay">ray to check.</param>
|
|
/// <param name="maxDistance">distance to check.</param>
|
|
/// <returns>Objects that intersect with the specified ray.</returns>
|
|
public void GetColliding(List<T> collidingWith, Ray checkRay, float maxDistance = float.PositiveInfinity)
|
|
{
|
|
#if UNITY_EDITOR
|
|
// For debugging
|
|
//AddCollisionCheck(checkRay);
|
|
#endif
|
|
rootNode.GetColliding(ref checkRay, collidingWith, maxDistance);
|
|
}
|
|
|
|
public Bounds GetMaxBounds()
|
|
{
|
|
return rootNode.GetBounds();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draws node boundaries visually for debugging.
|
|
/// Must be called from OnDrawGizmos externally. See also: DrawAllObjects.
|
|
/// </summary>
|
|
public void DrawAllBounds()
|
|
{
|
|
rootNode.DrawAllBounds();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draws the bounds of all objects in the tree visually for debugging.
|
|
/// Must be called from OnDrawGizmos externally. See also: DrawAllBounds.
|
|
/// </summary>
|
|
public void DrawAllObjects()
|
|
{
|
|
rootNode.DrawAllObjects();
|
|
}
|
|
|
|
// Intended for debugging. Must be called from OnDrawGizmos externally
|
|
// See also DrawAllBounds and DrawAllObjects
|
|
/// <summary>
|
|
/// Visualises collision checks from IsColliding and GetColliding.
|
|
/// Collision visualisation code is automatically removed from builds so that collision checks aren't slowed down.
|
|
/// </summary>
|
|
#if UNITY_EDITOR
|
|
public void DrawCollisionChecks()
|
|
{
|
|
int count = 0;
|
|
foreach (Bounds collisionCheck in lastBoundsCollisionChecks)
|
|
{
|
|
Gizmos.color = new Color(1.0f, 1.0f - ((float) count / numCollisionsToSave), 1.0f);
|
|
Gizmos.DrawCube(collisionCheck.center, collisionCheck.size);
|
|
count++;
|
|
}
|
|
|
|
foreach (Ray collisionCheck in lastRayCollisionChecks)
|
|
{
|
|
Gizmos.color = new Color(1.0f, 1.0f - ((float) count / numCollisionsToSave), 1.0f);
|
|
Gizmos.DrawRay(collisionCheck.origin, collisionCheck.direction);
|
|
count++;
|
|
}
|
|
Gizmos.color = Color.white;
|
|
}
|
|
|
|
#endif
|
|
|
|
// #### PRIVATE METHODS ####
|
|
|
|
/// <summary>
|
|
/// Used for visualising collision checks with DrawCollisionChecks.
|
|
/// Automatically removed from builds so that collision checks aren't slowed down.
|
|
/// </summary>
|
|
/// <param name="checkBounds">bounds that were passed in to check for collisions.</param>
|
|
#if UNITY_EDITOR
|
|
void AddCollisionCheck(Bounds checkBounds)
|
|
{
|
|
lastBoundsCollisionChecks.Enqueue(checkBounds);
|
|
if (lastBoundsCollisionChecks.Count > numCollisionsToSave)
|
|
{
|
|
lastBoundsCollisionChecks.Dequeue();
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Used for visualising collision checks with DrawCollisionChecks.
|
|
/// Automatically removed from builds so that collision checks aren't slowed down.
|
|
/// </summary>
|
|
/// <param name="checkRay">ray that was passed in to check for collisions.</param>
|
|
#if UNITY_EDITOR
|
|
void AddCollisionCheck(Ray checkRay)
|
|
{
|
|
lastRayCollisionChecks.Enqueue(checkRay);
|
|
if (lastRayCollisionChecks.Count > numCollisionsToSave)
|
|
{
|
|
lastRayCollisionChecks.Dequeue();
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Grow the octree to fit in all objects.
|
|
/// </summary>
|
|
/// <param name="direction">Direction to grow.</param>
|
|
void Grow(Vector3 direction)
|
|
{
|
|
int xDirection = direction.x >= 0 ? 1 : -1;
|
|
int yDirection = direction.y >= 0 ? 1 : -1;
|
|
int zDirection = direction.z >= 0 ? 1 : -1;
|
|
BoundsOctreeNode<T> oldRoot = rootNode;
|
|
float half = rootNode.BaseLength / 2;
|
|
float newLength = rootNode.BaseLength * 2;
|
|
Vector3 newCenter = rootNode.Center + new Vector3(xDirection * half, yDirection * half, zDirection * half);
|
|
|
|
// Create a new, bigger octree root node
|
|
rootNode = new BoundsOctreeNode<T>(newLength, minSize, looseness, newCenter);
|
|
|
|
if (oldRoot.HasAnyObjects())
|
|
{
|
|
// Create 7 new octree children to go with the old root as children of the new root
|
|
int rootPos = GetRootPosIndex(xDirection, yDirection, zDirection);
|
|
BoundsOctreeNode<T>[] children = new BoundsOctreeNode<T>[8];
|
|
for (int i = 0; i < 8; i++)
|
|
{
|
|
if (i == rootPos)
|
|
{
|
|
children[i] = oldRoot;
|
|
}
|
|
else
|
|
{
|
|
xDirection = i % 2 == 0 ? -1 : 1;
|
|
yDirection = i > 3 ? -1 : 1;
|
|
zDirection = (i < 2 || (i > 3 && i < 6)) ? -1 : 1;
|
|
children[i] = new BoundsOctreeNode<T>(rootNode.BaseLength, minSize, looseness,
|
|
newCenter + new Vector3(xDirection * half, yDirection * half, zDirection * half));
|
|
}
|
|
}
|
|
|
|
// Attach the new children to the new root node
|
|
rootNode.SetChildren(children);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Shrink the octree if possible, else leave it the same.
|
|
/// </summary>
|
|
void Shrink()
|
|
{
|
|
rootNode = rootNode.ShrinkIfPossible(initialSize);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Used when growing the octree. Works out where the old root node would fit inside a new, larger root node.
|
|
/// </summary>
|
|
/// <param name="xDir">X direction of growth. 1 or -1.</param>
|
|
/// <param name="yDir">Y direction of growth. 1 or -1.</param>
|
|
/// <param name="zDir">Z direction of growth. 1 or -1.</param>
|
|
/// <returns>Octant where the root node should be.</returns>
|
|
static int GetRootPosIndex(int xDir, int yDir, int zDir)
|
|
{
|
|
int result = xDir > 0 ? 1 : 0;
|
|
if (yDir < 0) result += 4;
|
|
if (zDir > 0) result += 2;
|
|
return result;
|
|
}
|
|
}
|
|
}
|