Last active
August 29, 2015 14:10
-
-
Save SeijiEmery/ac95492e455a869ba3d6 to your computer and use it in GitHub Desktop.
Quadtree (wip)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright (c) 2014 Seiji Emery. All rights reserved. | |
#pragma once | |
#include "aabb.hpp" | |
#include "vec2.hpp" | |
// Quadtree impl. | |
// Note: This implementation relies on bulk insertion (list of objects is known ahead of time); | |
// incremental insertion is not yet supported, and tree rebalancing w/ changing object bounds is not, | |
// and will never be supported (just rebuild the tree instead). | |
// Also, T is expected to be a non-owning ptr type (T*, shared_ptr, or weak_ptr (recommended); NOT unique_ptr), | |
// and MUST have a bounds() method that returns an AABB with x1, x2, y1, y2 member values. | |
// | |
// Performance wise, full rebuilds should be fast since state gets reused (node / bucket memory doesn't | |
// get reallocated), and insertions are processed in bulk at each tree level in a cache friendly manner | |
// (as opposed to deep recursion). | |
// | |
// storeUnique: if enabled, each element is only stored once*in the tree, and iteration (via the forEach | |
// fcns) will never alias. This can cause performance problems, though, as any object overlapping the split | |
// point gets stored in that node (instead of a leaf), which means that you can have hundreds of objects in | |
// the root node that will never get ignored (could really slow down collision, etc). | |
// If it's turned off, an alternate impl is used, where objects are only stored in leaf nodes (no nodes | |
// get stored in the root). Since objects can span multiple nodes however, multiple leaves can get references | |
// to the same object. This is allowed (since the tree doesn't own the objects anyways), and should speed | |
// up collision detection, *but* this can mess up iteration (unless planned for), so use at your own risk. | |
template <typename T, unsigned threshold = 16, bool storeUnique = true> | |
class Quadtree { | |
protected: | |
struct Node { | |
std::array<Node*, 4> children; | |
std::vector<T> objects; | |
AABB bounds, innerBounds; | |
Vec2 center; | |
bool isLeaf = true; | |
// Note: when isLeaf is set, children contains garbage values (not necessarily nullptr), | |
// and objects is just a bucket of objects. When it is not set (non-leaf), objects contains | |
// only those objects that do not fit within a child node (ie. bounds overlaps split point). | |
Node (const AABB & bounds) | |
: bounds(bounds), center(bounds.center()) {} | |
Node (Node && other) | |
: children(std::move(other.children)), objects(std::move(other.objects)), | |
bounds(other.bounds), center(other.center), isLeaf(other.isLeaf) {} | |
Node (const Node & other) = delete; // disable copy constructor and assignment | |
Node & operator= (const Node & other) = delete; | |
// Resets the node state (should have same effect as calling the constructor again) | |
// Other members can have garbage values atm and are expected to be set by Quadtree::rebuild() | |
Node & rebuild (const AABB & bounds_) { | |
bounds = bounds_; | |
center = bounds_.center(); | |
objects.clear(); | |
return *this; | |
} | |
}; | |
// All nodes in the quadtree are stored in a vector, and are reused when the tree is | |
// rebuilt (ie. none of the nodes get deallocated until the tree destructor is called) | |
std::vector<Node> nodes; | |
// temp state used by rebuild | |
std::vector<T> tmp; | |
unsigned nextNode = 1; | |
Node * createNode (const AABB & bounds) { | |
if (nextNode < nodes.size()) { | |
return &(nodes[nextNode++].rebuild(bounds)); | |
} else { | |
nodes.emplace_back(bounds); | |
++nextNode; | |
return &(nodes.back()); | |
} | |
} | |
void rebuild (Node & node) { | |
if (node.objects.size() < threshold) { | |
node.isLeaf = true; | |
} else { | |
node.isLeaf = false; | |
node.children[0] = createNode({ bounds.x1, center.x, bounds.y1, center.y }); | |
node.children[1] = createNode({ center.x, bounds.x2, bounds.y1, center.y }); | |
node.children[2] = createNode({ bounds.x1, center.x, center.y, bounds.y2 }); | |
node.children[3] = createNode({ bounds.x, bounds.x2, center.y, bounds.y2 }); | |
if (storeUnique) { | |
innerBounds.x1 = innerBounds.x2 = center.x; | |
innerBounds.y1 = innerBounds.y2 = center.y; | |
tmp.clear(); | |
for (auto obj : objects) { | |
if (obj->bounds().x2 < node.center.x) { | |
if (obj->bounds().y2 < node.center.y) { | |
node.children[0]->objects.push_back(std::move(obj)); | |
} else if (obj->bounds().y1 > node.center.y) { | |
node.children[2]->objects.push_back(std::move(obj)); | |
} else { | |
innerBounds.grow(obj->bounds()); | |
tmp.push_back(std::move(obj)); | |
} | |
} else if (obj->bounds().x1 > node.center.y) { | |
if (obj->bounds().y2 < node.center.y) { | |
node.children[1]->objects.push_back(std::move(obj)); | |
} else if (obj->bounds().y1 > node.center.y) { | |
node.children[3]->objects.push_back(std::move(obj)); | |
} else { | |
innerBounds.grow(obj->bounds()); | |
tmp.push_back(std::move(obj)); | |
} | |
} else { | |
innerBounds.grow(obj->bounds()); | |
tmp.push_back(std::move(obj)); | |
} | |
} | |
std::swap(tmp, node.objects); | |
} else { | |
// Alternate impl: objs are stored *only* in leaves. | |
// The tree may contain duplicate ptrs to each obj, but that's fine since the tree | |
// never has ownership over the objects it contains anyways. | |
for (auto obj : objects) { | |
if (obj->bounds().x1 < node.center.x) { | |
if (obj->bounds().y1 < node.center.y) | |
node.children[0]->objects.push_back(obj); | |
if (obj->bounds().y2 > node.center.y) | |
node.children[2]->objects.push_back(obj); | |
} | |
if (obj->bounds().x2 > node.center.x) { | |
if (obj->bounds().y1 < node.center.y) | |
node.children[1]->objects.push_back(obj); | |
if (obj->bounds().y2 > node.center.y) | |
node.children[3]->objects.push_back(obj); | |
} | |
} | |
objects.clear(); | |
} | |
for (auto i = 0; i < 4; ++i) { | |
assert(node.children[i] != nullptr); | |
rebuild(*(node.children[i])); | |
} | |
} | |
void iterElementsOverlappingPoint (Node & node, Vec2 point, std::function<void(const T &)> callback) { | |
// if (node.isLeaf || node.innerBounds.contains(point)) { | |
if (node.isLeaf || (node.innerBounds.contains(point) && storeUnique)) { | |
for (auto obj : node.objects) { | |
if (obj->bounds().contains(point)) | |
callback(obj); | |
} | |
} | |
if (!node.isLeaf) { | |
if (point.x < center.x) | |
if (point.y < center.y) | |
iterElementsOverlappingPoint(node.children[0], point, callback); | |
else | |
iterElementsOverlappingPoint(node.children[2], point, callback); | |
else | |
if (point.y < center.y) | |
iterElementsOverlappingPoint(node.children[1], point, callback); | |
else | |
iterElementsOverlappingPoint(node.children[3], point, callback); | |
} | |
} | |
void iterElementsOverlappingBounds (Node & node, Vec2 point, std::function<void(const T &)> callback) { | |
// if (node.isLeaf || node.innerBounds.contains(bounds)) { | |
if (node.isLeaf || (node.innerBounds.contains(point) && storeUnique)) { | |
for (auto obj : node.objects) { | |
if (obj->bounds().contains(bounds)) | |
callback(obj); | |
} | |
} | |
// if (!node.isLeaf) { | |
else { | |
for (auto i = 0; i < 4; ++i) { | |
assert(node.children[i] != nullptr) | |
if (bounds.contains(node.children[i]->bounds)) { | |
iterElementsOverlappingBounds(*node.children[i], point, callback); | |
} | |
} | |
} | |
} | |
} | |
void iterPotentialCollisions (Node & node, const std::function<void(const T &, const T &)> & callback) { | |
if (!storeUnique) { | |
// Note how this MASSIVELY simplifies collision detection xD | |
if (!node.isLeaf) { | |
for (auto i = 0; i < 4; ++i) { | |
assert(node.children[i] != nullptr); | |
iterPotentialCollisions(*node.children[i], callback); | |
} | |
} else { | |
for (auto i = 0; i < node.objects.size(); ++i) { | |
for (auto j = i + 1; j < node.objects.size(); ++j) { | |
if (node.objects[i].bounds().contains(node.objects[j].bounds())) { | |
callback(node.objects[i], node.objects[j]); | |
} | |
} | |
} | |
} | |
} else { | |
// more complicated... | |
} | |
} | |
public: | |
void rebuild (const std::vector<T> & objects) { | |
// Determine bounds | |
AABB bounds; | |
for (auto obj : objects) | |
bounds.grow(obj->bounds()); | |
// Allocate root node | |
nextNode = 0; | |
createNode(bounds); | |
assert(nodes.size() > 0); | |
std::copy(objects.begin(), objects.end(), nodes[0].objects.begin()); | |
rebuild(nodes[0], bounds); | |
} | |
void forEachElementInBounds (const AABB & bounds, const std::function<void (const T &)> & callback) { | |
assert(nodes.size() > 0); | |
iterElementsOverlappingBounds(nodes[0], bounds, callback); | |
// Note: this now aliases. Fuck. | |
// Possible workaround: add a visited flag to T that alternates w/ each pass. When visited, flip the flag, | |
// and only execute the visitor logic when the flag is set to an expected value. | |
// This is pretty intrusive though... maybe we should add a template switch to select between two impls | |
// (either each obj only stored *once* (unique, but can have tons of objs stored in the root), or aliased | |
// (iteration becomes problematic, but performance should be better if there's lots of objs that span any | |
// of the split points)) | |
// edit: sort of fixed (see storeUnique) | |
} | |
void forEachElementContainingPoint (const Vec2 & bounds, const std::function<void (const T &)> & callback) { | |
assert(nodes.size() > 0); | |
iterElementsOverlappingPoint(nodes[0], bounds, callback); | |
} | |
}; | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment