369 lines
14 KiB
C++
369 lines
14 KiB
C++
#pragma once
|
|
|
|
#include <vector>
|
|
#include <cstdint>
|
|
#include <J3ML/LinearAlgebra/Vector2.hpp>
|
|
#include <J3ML/Geometry/AABB2D.hpp>
|
|
|
|
namespace J3ML::Geometry {
|
|
|
|
|
|
using LinearAlgebra::Vector2;
|
|
|
|
template<typename T>
|
|
class QuadTree {
|
|
/// A fixed split rule for all QuadTrees: A QuadTree leaf node is only ever split if the leaf contains at least this many objects.
|
|
/// Leaves containing fewer than this many objects are always kept as leaves until the object count is exceeded.
|
|
constexpr static const int minQuadTreeNodeObjectCount = 16;
|
|
|
|
/// A fixed split limit rule for all QuadTrees: If the QuadTree node side length is smaller than this, the node will
|
|
/// never be split again into smaller subnodes. This provides a hard limit safety net for infinite/extra long recursion
|
|
/// in case multiple identical overlapping objects are placed into the tree.
|
|
constexpr static const float minQuadTreeQuadrantSize = 0.05f;
|
|
|
|
public:
|
|
struct Node {
|
|
Node *parent;
|
|
uint32_t childIndex;
|
|
std::vector<T> objects;
|
|
|
|
Vector2 center;
|
|
Vector2 radius;
|
|
|
|
bool IsLeaf() const { return childIndex == 0xFFFFFFFF; }
|
|
|
|
uint32_t TopLeftChildIndex() const { return childIndex; }
|
|
|
|
uint32_t TopRightChildIndex() const { return childIndex + 1; }
|
|
|
|
uint32_t BottomLeftChildIndex() const { return childIndex + 2; }
|
|
|
|
uint32_t BottomRightChildIndex() const { return childIndex + 3; }
|
|
|
|
/// This assumes that the QuadTree contains unique objects and never duplicates
|
|
void Remove(const T &object) {
|
|
for (size_t i = 0; i < objects.size(); ++i) {
|
|
if (objects[i] == object) {
|
|
AssociateQuadTreeNode(object,
|
|
0); // Mark in the object that it has been removed from the QuadTree.
|
|
std::swap(objects[i], objects.back());
|
|
objects.pop_back();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
AABB2D ComputeAABB() {}
|
|
|
|
float DistanceSq(const Vector2 &point) const {
|
|
Vector2 centered = point - center;
|
|
Vector2 closestPoint = centered.Clamp(-radius, radius);
|
|
return closestPoint.DistanceSq(centered);
|
|
}
|
|
|
|
};
|
|
|
|
// Helper struct used when traversing through the tree
|
|
struct TraversalStackItem {
|
|
Node *node;
|
|
};
|
|
|
|
QuadTree() :
|
|
rootNodeIndex(-1),
|
|
boundingAABB(Vector2(0, 0), Vector2(0, 0)) {
|
|
// TODO: currently storing persistent raw pointers to this array outside the array
|
|
nodes.reserve(200000);
|
|
}
|
|
|
|
/// Removes all nodes and objects in this tree and reintializes the tree to a single root node.
|
|
void Clear(const Vector2 &minXY = Vector2(-1, -1), const Vector2 &maxXY = Vector2(1, 1));
|
|
|
|
/// Places the given object onto the proper (leaf) node of the tree. After placing, if the leaf split rule is
|
|
/// satisfied, subdivides the leaf node into 4 subquadrants and reassings the objects to the new leaves.
|
|
void Add(const T &object);
|
|
|
|
/// Removes the given object from this tree.
|
|
/// To call this function, you must define a function QuadTree<T>::Node *GetQuadTreeNode(const T& object)
|
|
/// which returns the node of this quadtree where the object resides in.
|
|
void Remove(const T &object);
|
|
|
|
/// @return The bounding rectangle for the whole tree.
|
|
/// @note This bounding rectangle does not tightly bound the objects themselves, only the root node of the tree.
|
|
AABB2D BoundingAABB() const { return boundingAABB; }
|
|
|
|
/// @return The topmost node in the tree.
|
|
Node *Root();
|
|
|
|
const Node *Root() const;
|
|
|
|
/// Returns the total number of nodes (all nodes, i.e. inner nodes + leaves) in the tree.
|
|
/// Runs in constant time.
|
|
int NumNodes() const;
|
|
|
|
/// Returns the total number of leaf nodes in the tree.
|
|
/// @warning Runs in time linear 'O(n)' to the number of nodes in the tree.
|
|
int NumLeaves() const;
|
|
|
|
/// Returns the total number of inner nodes in the tree.
|
|
/// @warning Runs in time linear 'O(n)' to the number of nodes in the tree.
|
|
int NumInnerNodes() const;
|
|
|
|
/// Returns the total number of objects stored in the tree.
|
|
/// @warning Runs in time linear 'O(n)' to the number of nodes in the tree.
|
|
int NumObjects() const;
|
|
|
|
/// Returns the maximum height of the whole tree (the path from the root to the farthest leaf node).
|
|
int TreeHeight() const;
|
|
|
|
/// Returns the height of the subtree rooted at 'node'.
|
|
int TreeHeight(const Node *node) const;
|
|
|
|
/// Performs an AABB intersection query in this Quadtreee, and calls the given callback function for each non-empty
|
|
/// node of the tree which intersects the given AABB.
|
|
/** @param aabb The axis-aligned bounding box to intersect this QuadTree with.
|
|
@param callback A function or a function object of prototype
|
|
bool callbackFunction(QuadTree<T> &tree, const AABB2D &queryAABB, QuadTree<T>::Node &node);
|
|
If the callback function returns true, the execution of the query is stopped and this function immediately
|
|
returns afterwards. If the callback function returns false, the execution of the query continues. */
|
|
template<typename Func>
|
|
inline void AABBQuery(const AABB2D &aabb, Func &callback);
|
|
|
|
/// Finds all object pairs inside the given AABB which have colliding AABBs. For each such pair, calls the
|
|
/// specified callback function.
|
|
template<typename Func>
|
|
inline void CollidingPairsQuery(const AABB2D &aabb, Func &callback);
|
|
|
|
/// Performs various consistency checks on the given node. Use only for debugging purposes.
|
|
void DebugSanityCheckNode(Node *n);
|
|
|
|
private:
|
|
void Add(const T &object, Node *n);
|
|
|
|
/// Allocates a sequential 4-tuple of QuadtreeNodes, contiguous in memory.
|
|
int AllocateNodeGroup(Node *parent);
|
|
|
|
void SplitLeaf(Node *leaf);
|
|
|
|
std::vector<Node> nodes;
|
|
int rootNodeIndex;
|
|
AABB2D boundingAABB;
|
|
|
|
void GrowRootTopLeft();
|
|
|
|
void GrowRootTopRight();
|
|
|
|
void GrowRootBottomLeft();
|
|
|
|
void GrowRootBottomRight();
|
|
|
|
void GrowImpl(int quadrantForRoot);
|
|
};
|
|
|
|
// NOTE: Keep members defined here. Template-parameterized classes
|
|
// can't be split across header and implementation files
|
|
// but the presence of the implementation file is a requirement
|
|
|
|
|
|
template<typename T>
|
|
void QuadTree<T>::Clear(const Vector2 &minXY, const Vector2 &maxXY) {
|
|
nodes.clear();
|
|
|
|
boundingAABB.minPoint = minXY;
|
|
boundingAABB.maxPoint = maxXY;
|
|
|
|
rootNodeIndex = AllocateNodeGroup(0);
|
|
|
|
Node *root = Root();
|
|
root->center = (minXY + maxXY) * 0.5f;
|
|
root->radius = maxXY - root->center;
|
|
}
|
|
|
|
template<typename T>
|
|
void QuadTree<T>::Add(const T &object) {
|
|
Node *n = Root();
|
|
AABB2D objectAABB = GetAABB2D(object);
|
|
|
|
// Ramen Noodle Bowls of nested if statements are generally bad practice
|
|
// Unfortunately, sometimes the problem domain makes this unavoidable
|
|
|
|
if (objectAABB.minPoint.x >= boundingAABB.minPoint.x) {
|
|
// object fits left.
|
|
if (objectAABB.maxPoint.x <= boundingAABB.maxPoint.x) {
|
|
// object fits left and right.
|
|
if (objectAABB.minPoint.y >= boundingAABB.minPoint.y) {
|
|
// Object fits left, right, and top.
|
|
if (objectAABB.maxPoint.y <= boundingAABB.maxPoint.y) {
|
|
// Object fits the whole root AABB. Can safely add into the existing tree size.
|
|
AddObject(object, n);
|
|
return;
|
|
} else {
|
|
// Object fits left, right, and top, but not bottom.
|
|
GrowRootBottomRight(); // Could grow bottom-left as well, wouldn't matter here.
|
|
}
|
|
} else {
|
|
// Object fits left and right, but not to top.
|
|
GrowRootTopRight(); // Could grow top-left as well, wouldn't matter here.
|
|
}
|
|
} else {
|
|
// Object fits left, but not to right. We must grow right. Check whether to grow top or bottom;
|
|
if (objectAABB.minPoint.y < boundingAABB.minPoint.y)
|
|
GrowRootTopRight();
|
|
else
|
|
GrowRootBottomRight();
|
|
}
|
|
} else {
|
|
// We must grow left. Check whether to grow top or bottom.
|
|
if (objectAABB.minPoint.y < boundingAABB.minPoint.y)
|
|
GrowRootTopLeft();
|
|
else
|
|
GrowRootBottomLeft();
|
|
}
|
|
// Now that we have grown the tree root node, try adding again.
|
|
Add(object);
|
|
}
|
|
|
|
template<typename T>
|
|
void QuadTree<T>::Remove(const T &object) {
|
|
Node *n = GetQuadTreeNode(object);
|
|
if (n) {
|
|
n->Remove(object);
|
|
}
|
|
}
|
|
|
|
template<typename T>
|
|
void QuadTree<T>::Add(const T &object, Node *n) {
|
|
for (;;) {
|
|
// Traverse the QuadTree to decide which quad to place this object on.
|
|
float left = n->center.x - MinX(object); // If left > 0.f, then the object overlaps with the left quadrant.
|
|
float right = MaxX(object) - n->center.x; // If right > 0.f, then the object overlaps with the right quadrant.
|
|
|
|
float top = n->center.y - MinY(object); // If top > 0.f, then the object overlaps with the top quadrant
|
|
float bottom =
|
|
MaxY(object) - n->center.y; // If bottom > 0.f, then the object overlaps with the bottom quadrant
|
|
float leftAndRight = std::min(left, right); // If > 0.f, then the object straddles left-right halves.
|
|
float topAndBottom = std::min(top, bottom); // If > 0.f, then the object straddles top-bottom halves.
|
|
float straddledEitherOne = std::max(leftAndRight,
|
|
topAndBottom); // If > 0.f, thne the object is in two or more quadrants.
|
|
|
|
// Note: It can happen that !left && !right, or !top && !bottom.
|
|
// but the if()s are setup below so that right/bottom is taken if no left/top, so that is ok.
|
|
|
|
// We must put the object onto this node if
|
|
// a) the object straddled the parent->child split lines.
|
|
// b) this object is a leaf
|
|
if (straddledEitherOne > 0.f) {
|
|
n->objects.push_back(object);
|
|
AssociateQuadTreeNode(object, n);
|
|
return;
|
|
}
|
|
if (n->IsLeaf()) {
|
|
n->objects.push_back(object);
|
|
AssociateQuadTreeNode(object, n);
|
|
|
|
if ((int) n->objects.size() > minQuadTreeNodeObjectCount &&
|
|
std::min(n->radius.x, n->radius.y) >= minQuadTreeQuadrantSize) {
|
|
SplitLeaf(n);
|
|
}
|
|
|
|
return;
|
|
}
|
|
if (left > 0.f) {
|
|
if (top > 0.f) {
|
|
n = &nodes[n->TopLeftChildIndex()];
|
|
} else {
|
|
n = &nodes[n->BottomLeftChildIndex()];
|
|
}
|
|
} else {
|
|
if (top > 0.f) {
|
|
n = &nodes[n->TopRightChildIndex()];
|
|
} else {
|
|
n = &nodes[n->BottomRightChildIndex()];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
template<typename T>
|
|
typename QuadTree<T>::Node *QuadTree<T>::Root() {
|
|
return nodes.empty() ? 0 : &nodes[rootNodeIndex];
|
|
}
|
|
|
|
template<typename T>
|
|
const typename QuadTree<T>::Node *QuadTree<T>::Root() const {
|
|
return nodes.empty() ? 0 : &nodes[rootNodeIndex];
|
|
}
|
|
|
|
template <typename T>
|
|
int QuadTree<T>::AllocateNodeGroup(Node* parent) {
|
|
size_t oldCap = nodes.capacity();
|
|
|
|
int index = (int)nodes.size();
|
|
Node n;
|
|
n.parent = parent;
|
|
n.childIndex = 0xFFFFFFFF;
|
|
if (parent) {
|
|
n.radius = parent->radius * 0.5f;
|
|
n.center = parent->center - n.radius;
|
|
}
|
|
|
|
nodes.push_back(n);
|
|
if (parent)
|
|
n.center.x = parent->center.x + n.radius.x;
|
|
nodes.push_back(n);
|
|
|
|
if (parent) {
|
|
n.center.x = parent->center.x - n.radius.x;
|
|
n.center.y = parent->center.y + n.radius.y;
|
|
}
|
|
|
|
nodes.push_back(n);
|
|
if (parent)
|
|
n.center.x = parent->center.x + n.radius.x;
|
|
nodes.push_back(n);
|
|
|
|
return index;
|
|
}
|
|
|
|
template <typename T>
|
|
void QuadTree<T>::SplitLeaf(Node *leaf) {
|
|
leaf->childIndex = AllocateNodeGroup(leaf);
|
|
|
|
size_t i = 0;
|
|
while (i < leaf->objects.size()) {
|
|
const T& object = leaf->objects[i];
|
|
|
|
// Traverse the QuadTree to decide which quad to place this object into
|
|
float left = leaf->center.x - MinX(object);
|
|
float right = MaxX(object) - leaf->center;
|
|
float top = leaf->center.y - MinY(object);
|
|
float bottom = MaxY(object) - leaf->center.y;
|
|
float leftAndRight = std::min(left, right);
|
|
float topAndBottom = std::min(top, bottom);
|
|
|
|
float straddledEitherOne = std::max(leftAndRight, topAndBottom);
|
|
|
|
if (straddledEitherOne > 0.f) {
|
|
++i;
|
|
continue;
|
|
}
|
|
|
|
if (left > 0.f) {
|
|
if (top > 0.f) {
|
|
Add(object, &nodes[leaf->TopLeftChildIndex()]);
|
|
} else {
|
|
Add(object, &nodes[leaf->BottomLeftChildIndex()]);
|
|
}
|
|
} else {
|
|
if (top > 0.f) {
|
|
Add(object, &nodes[leaf->TopRightChildIndex()]);
|
|
} else {
|
|
Add(object, &nodes[leaf->BottomRightChildIndex()]);
|
|
}
|
|
}
|
|
|
|
// Remove the object we added to a child from this node.
|
|
leaf->objects[i] = leaf->objects.back();
|
|
leaf->objects.pop_back();
|
|
}
|
|
}
|
|
} |