//
// Distributed under the BSD Licence (see LICENCE file).
//
// Copyright (c) 2014, Nition, http://www.momentstudio.co.nz/
// Copyright (c) 2017, Máté Cserép, http://codenet.hu
// All rights reserved.
//
namespace Octree
{
using System.Collections.Generic;
///
/// A Dynamic Octree for storing any objects that can be described as a single point
///
///
///
/// 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 - - which sets the amount of items allowed in a node before it splits.
///
/// See also BoundsOctree, where objects are described by AABB bounds.
///
/// The content of the octree can be anything, since the bounds data is supplied separately.
public partial class PointOctree
{
///
/// Root node of the octree
///
private Node _rootNode;
///
/// Size that the octree was on creation
///
private readonly double _initialSize;
///
/// Minimum side length that a node can be - essentially an alternative to having a max depth
///
private readonly double _minSize;
///
/// The total amount of objects currently in the tree
///
public int Count { get; private set; }
///
/// Gets the bounding box that represents the whole octree
///
/// The bounding box of the root node.
public BoundingBox MaxBounds
{
get { return new BoundingBox(_rootNode.Center, new Point(_rootNode.SideLength, _rootNode.SideLength, _rootNode.SideLength)); }
}
///
/// Constructor for the point octree.
///
/// Size of the sides of the initial node. The octree will never shrink smaller than this.
/// Position of the centre of the initial node.
/// Nodes will stop splitting if the new nodes would be smaller than this.
public PointOctree(double initialWorldSize, Point initialWorldPos, double minNodeSize)
{
if (minNodeSize > initialWorldSize)
{
System.Diagnostics.Debug.WriteLine(
"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;
_rootNode = new Node(_initialSize, _minSize, initialWorldPos);
}
// #### PUBLIC METHODS ####
///
/// Add an object.
///
/// Object to add.
/// Position of the object.
public void Add(T obj, Point objPos)
{
// Add object or expand the octree until it can be added
int count = 0; // Safety check against infinite/excessive growth
while (!_rootNode.Add(obj, objPos))
{
Grow(objPos - _rootNode.Center);
if (++count > 20)
{
throw new System.Exception(
"Aborted Add operation as it seemed to be going on forever (" + (count - 1)
+ ") attempts at growing the octree.");
}
}
Count++;
}
///
/// Remove an object. Makes the assumption that the object only exists once in the tree.
///
/// Object to remove.
/// True if the object was removed successfully.
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;
}
///
/// Removes the specified object at the given position. Makes the assumption that the object only exists once in the tree.
///
/// Object to remove.
/// Position of the object.
/// True if the object was removed successfully.
public bool Remove(T obj, Point objPos)
{
bool removed = _rootNode.Remove(obj, objPos);
// See if we can shrink the octree down now that we've removed the item
if (removed)
{
Count--;
Shrink();
}
return removed;
}
///
/// Returns objects that are within of the specified ray.
/// If none, returns an empty array (not null).
///
/// The ray. Passing as ref to improve performance since it won't have to be copied.
/// Maximum distance from the ray to consider.
/// Objects within range.
public T[] GetNearby(Ray ray, double maxDistance)
{
List collidingWith = new List();
_rootNode.GetNearby(ref ray, maxDistance, collidingWith);
return collidingWith.ToArray();
}
///
/// Returns objects that are within of the specified position.
/// If none, returns an empty array (not null).
///
/// The position. Passing as ref to improve performance since it won't have to be copied.
/// Maximum distance from the position to consider.
/// Objects within range.
public T[] GetNearby(Point position, double maxDistance)
{
List collidingWith = new List();
_rootNode.GetNearby(ref position, maxDistance, collidingWith);
return collidingWith.ToArray();
}
///
/// Return objects that are within of the specified position.
///
/// The position.
/// Maximum distance from the position to consider.
/// List result.
/// Objects within range.
public void GetObjectsSplitByPlane(ref Plane plane, List front, List onPlane, List back)
{
_rootNode.GetObjectsSplitByPlane(ref plane, front, onPlane, back);
}
///
/// Returns all objects in the tree.
/// If none, returns an empty array (not null).
///
/// All objects.
public ICollection GetAll()
{
List objects = new List(Count);
_rootNode.GetAll(objects);
return objects;
}
// #### PRIVATE METHODS ####
///
/// Grow the octree to fit in all objects.
///
/// Direction to grow.
private void Grow(Point direction)
{
int xDirection = direction.X >= 0 ? 1 : -1;
int yDirection = direction.Y >= 0 ? 1 : -1;
int zDirection = direction.Z >= 0 ? 1 : -1;
Node oldRoot = _rootNode;
double half = _rootNode.SideLength / 2;
double newLength = _rootNode.SideLength * 2;
Point newCenter = _rootNode.Center + new Point(xDirection * half, yDirection * half, zDirection * half);
// Create a new, bigger octree root node
_rootNode = new Node(newLength, _minSize, newCenter);
if (oldRoot.HasAnyObjects())
{
// Create 7 new octree children to go with the old root as children of the new root
int rootPos = _rootNode.BestFitChild(oldRoot.Center);
Node[] children = new Node[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 Node(
oldRoot.SideLength,
_minSize,
newCenter + new Point(xDirection * half, yDirection * half, zDirection * half));
}
}
// Attach the new children to the new root node
_rootNode.SetChildren(children);
}
}
///
/// Shrink the octree if possible, else leave it the same.
///
private void Shrink()
{
_rootNode = _rootNode.ShrinkIfPossible(_initialSize);
}
}
}