// // 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, Loose Octree for storing any objects that can be described with AABB bounds /// /// /// /// 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. /// /// 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. /// /// 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. /// /// See also: PointOctree, where objects are stored as single points and some code can be simplified /// /// The content of the octree can be anything, since the bounds data is supplied separately. public partial class BoundsOctree { /// /// Root node of the octree /// private Node _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 /// private readonly double _looseness; /// /// 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 _rootNode.Bounds; } } /// /// Constructor for the bounds octree. /// /// Size of the sides of the initial node, in metres. The octree will never shrink smaller than this. /// Position of the center of the initial node. /// Nodes will stop splitting if the new nodes would be smaller than this (metres). /// Clamped between 1 and 2. Values > 1 let nodes overlap. public BoundsOctree(double initialWorldSize, Point initialWorldPos, double minNodeSize, double loosenessVal) { 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; _looseness = MathExtensions.Clamp(loosenessVal, 1.0f, 2.0f); _rootNode = new Node(_initialSize, _minSize, _looseness, initialWorldPos); } // #### PUBLIC METHODS #### /// /// Add an object. /// /// Object to add. /// 3D bounding box around the object. public void Add(T obj, BoundingBox 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) { 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. /// 3D bounding box around the object. /// True if the object was removed successfully. public bool Remove(T obj, BoundingBox 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; } /// /// Check if the specified bounds intersect with anything in the tree. See also: GetColliding. /// /// bounds to check. /// True if there was a collision. public bool IsColliding(BoundingBox checkBounds) { return _rootNode.IsColliding(ref checkBounds); } /// /// Check if the specified ray intersects with anything in the tree. See also: GetColliding. /// /// ray to check. /// distance to check. /// True if there was a collision. public bool IsColliding(Ray checkRay, double maxDistance) { return _rootNode.IsColliding(ref checkRay, maxDistance); } /// /// Returns an array of objects that intersect with the specified bounds, if any. Otherwise returns an empty array. See also: IsColliding. /// /// list to store intersections. /// bounds to check. /// Objects that intersect with the specified bounds. public void GetColliding(List collidingWith, BoundingBox checkBounds) { _rootNode.GetColliding(ref checkBounds, collidingWith); } /// /// Returns an array of objects that intersect with the specified ray, if any. Otherwise returns an empty array. See also: IsColliding. /// /// list to store intersections. /// ray to check. /// distance to check. /// Objects that intersect with the specified ray. public void GetColliding(List collidingWith, Ray checkRay, double maxDistance = double.PositiveInfinity) { _rootNode.GetColliding(ref checkRay, collidingWith, maxDistance); } /// /// Returns an array of objects that intersect with the specified plane, if any. Otherwise returns an empty array. /// /// list to store intersections. /// plane to check. /// distance to check. /// Objects that intersect with the specified plane. public void GetColliding(List collidingWith, Plane plane) { _rootNode.GetColliding(ref plane, collidingWith); } public void GetUncollided(Plane plane, bool positiveSide, List uncollided) { _rootNode.GetUncollided(ref plane, positiveSide, uncollided); } // #### 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.BaseLength / 2; double newLength = _rootNode.BaseLength * 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, _looseness, 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.BaseLength, _minSize, _looseness, 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); } } }