| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258 |
-
- #ifndef DFPSR_OCTREE
- #define DFPSR_OCTREE
- #include "../../DFPSR/includeFramework.h"
- #include <memory>
- namespace dsr {
- enum class LeafAction {
- None,
- Erase // Erase the leaf as soon as possible
- };
- // Used for recursively selecting parts of the tree to enter
- // bool(const IVector3D& minBound, const IVector3D& maxBound)
- using OcTreeFilter = std::function<bool(const IVector3D&, const IVector3D&)>;
- // Used to read data from the tree
- // void(T& content, const IVector3D origin, const IVector3D minBound, const IVector3D maxBound)
- template <typename T>
- using OcTreeLeafOperation = std::function<LeafAction(T&, const IVector3D, const IVector3D, const IVector3D)>;
- // A leaf
- template <typename T>
- class OctreeLeaf {
- public:
- T content;
- IVector3D origin, minBound, maxBound;
- public:
- OctreeLeaf(const T& content, const IVector3D origin, const IVector3D minBound, const IVector3D maxBound)
- : content(content), origin(origin), minBound(minBound), maxBound(maxBound) {}
- public:
- LeafAction find(const OcTreeFilter& boundFilter, const OcTreeLeafOperation<T>& leafOperation) {
- if (boundFilter(this->minBound, this->maxBound)) {
- return leafOperation(this->content, this->origin, this->minBound, this->maxBound);
- } else {
- return LeafAction::None;
- }
- }
- };
- static const uint32_t Octree_MaskX = 1u;
- static const uint32_t Octree_MaskY = 2u;
- static const uint32_t Octree_MaskZ = 4u;
- inline int Octree_getBranchIndex(bool pX, bool pY, bool pZ) {
- return (int)((pX ? Octree_MaskX : 0u) | (pY ? Octree_MaskY : 0u) | (pZ ? Octree_MaskZ : 0u));
- }
- struct IBox3D {
- IVector3D min, max;
- IBox3D(const IVector3D& min, const IVector3D& max) : min(min), max(max) {}
- };
- inline IBox3D splitBound(const IBox3D& parent, int branchIndex) {
- assert(branchIndex >= 0 && branchIndex < 8);
- IVector3D size = (parent.max - parent.min) / 2;
- assert(size.x > 0 && size.y > 0 && size.z > 0);
- IVector3D minBound = parent.min;
- if ((uint32_t)branchIndex & Octree_MaskX) { minBound.x += size.x; }
- if ((uint32_t)branchIndex & Octree_MaskY) { minBound.y += size.y; }
- if ((uint32_t)branchIndex & Octree_MaskZ) { minBound.z += size.z; }
- IVector3D maxBound = minBound + size;
- return IBox3D(minBound, maxBound);
- }
- // A branch
- template <typename T>
- class OctreeNode {
- public:
- // The ownership telling if a leaf of origin belongs here
- IVector3D minOwnedBound, maxOwnedBound;
- // The combined bounding box of all children, which may exceed the owned bound by the largest leaf radius measured from the origin
- IVector3D minLeafBound, maxLeafBound;
- // When divided, any added leaves will try to be inserted into child nodes when possible
- // Leaves that are too large may stay at the parent node
- bool divided = false;
- // One optional child node for each of the 8 sections in the octree
- std::shared_ptr<OctreeNode<T>> childNodes[8]; // Used for branching when getting too crowded with direct leaves
- // The leaves that have not yet been assigned to a specific child node
- List<OctreeLeaf<T>> leaves;
- public:
- // Copy constructor
- OctreeNode(const OctreeNode<T>& original)
- : minOwnedBound(original.minOwnedBound),
- maxOwnedBound(original.maxOwnedBound),
- minLeafBound(original.minLeafBound),
- maxLeafBound(original.maxLeafBound),
- divided(original.divided),
- leaves(original.leaves) {
- for (int n = 0; n < 8; n++) {
- this->childNodes[n] = original.childNodes[n];
- }
- }
- // Create from first leaf
- OctreeNode(const OctreeLeaf<T>& firstLeaf, const IVector3D& minOwnedBound, const IVector3D& maxOwnedBound)
- : minOwnedBound(minOwnedBound), maxOwnedBound(maxOwnedBound), minLeafBound(firstLeaf.minBound), maxLeafBound(firstLeaf.maxBound), divided(false) {
- this->leaves.push(firstLeaf);
- }
- // Create from first child node
- OctreeNode(const OctreeNode<T>& firstBranch, int firstBranchIndex, const IVector3D& minOwnedBound, const IVector3D& maxOwnedBound)
- : minOwnedBound(minOwnedBound), maxOwnedBound(maxOwnedBound), minLeafBound(firstBranch.minLeafBound), maxLeafBound(firstBranch.maxLeafBound), divided(true) {
- this->childNodes[firstBranchIndex] = std::make_shared<OctreeNode<T>>(firstBranch);
- }
- public:
- bool insideLeafBound(const IVector3D& origin) {
- return origin.x >= this->minLeafBound.x && origin.y >= this->minLeafBound.y && origin.z >= this->minLeafBound.z
- && origin.x <= this->maxLeafBound.x && origin.y <= this->maxLeafBound.y && origin.z <= this->maxLeafBound.z;
- }
- bool insideOwnedBound(const IVector3D& origin) {
- return origin.x >= this->minOwnedBound.x && origin.y >= this->minOwnedBound.y && origin.z >= this->minOwnedBound.z
- && origin.x <= this->maxOwnedBound.x && origin.y <= this->maxOwnedBound.y && origin.z <= this->maxOwnedBound.z;
- }
- // Get the branch index closest to the origin
- int getInnerBranchIndex() {
- return Octree_getBranchIndex(
- this->minOwnedBound.x + this->maxOwnedBound.x < 0,
- this->minOwnedBound.y + this->maxOwnedBound.y < 0,
- this->minOwnedBound.z + this->maxOwnedBound.z < 0
- );
- }
- // Returns true iff the given leaf is allowed to create a new branch
- bool mayBranch(const OctreeLeaf<T>& leaf) {
- IVector3D leafDimensions = leaf.maxBound - leaf.minBound;
- IVector3D maxDimensions = (this->maxOwnedBound - this->minOwnedBound) / 4;
- return this->divided == true
- && leafDimensions.x <= maxDimensions.x
- && leafDimensions.y <= maxDimensions.y
- && leafDimensions.z <= maxDimensions.z;
- }
- void insert(const OctreeLeaf<T>& leaf) {
- // Update leaf bounds along each passed node along the way
- if (leaf.minBound.x < this->minLeafBound.x) { this->minLeafBound.x = leaf.minBound.x; }
- if (leaf.minBound.y < this->minLeafBound.y) { this->minLeafBound.y = leaf.minBound.y; }
- if (leaf.minBound.z < this->minLeafBound.z) { this->minLeafBound.z = leaf.minBound.z; }
- if (leaf.maxBound.x > this->maxLeafBound.x) { this->maxLeafBound.x = leaf.maxBound.x; }
- if (leaf.maxBound.y > this->maxLeafBound.y) { this->maxLeafBound.y = leaf.maxBound.y; }
- if (leaf.maxBound.z > this->maxLeafBound.z) { this->maxLeafBound.z = leaf.maxBound.z; }
- // Make sure that the origin is inside of the owned bound by creating new parents until the point is covered
- while (!this->insideOwnedBound(leaf.origin)) {
- if (this->minOwnedBound.x < -100000000 || this->maxOwnedBound.x > 100000000) {
- throwError("Cannot expand (", this->minOwnedBound, ")..(", this->maxOwnedBound, ") to include ", leaf.origin, "! The origin must be given to the correct side of the octree's root.\n");
- }
- // Create a new parent node containing the old one
- OctreeNode newParent = OctreeNode(*this, this->getInnerBranchIndex(), this->minOwnedBound * 2, this->maxOwnedBound * 2);
- // Replace the content of this node with the new parent so that pointers to the old node leads to it
- *this = newParent;
- }
- // Try inserting into any child node
- for (int n = 0; n < 8; n++) {
- if (this->childNodes[n].get() != nullptr && this->childNodes[n]->insideOwnedBound(leaf.origin)) {
- this->childNodes[n]->insert(leaf);
- return; // Avoid inserting into multiple nodes
- }
- }
- // If there's no matching branch that can contain it, check if a new branch should be created for it
- if (this->mayBranch(leaf)) {
- // Create a new branch for the leaf
- IVector3D middle = (this->minOwnedBound + this->maxOwnedBound) / 2;
- int newBranchIndex = Octree_getBranchIndex(leaf.origin.x >= middle.x, leaf.origin.y >= middle.y, leaf.origin.z >= middle.z);
- assert(this->childNodes[newBranchIndex].get() == nullptr);
- IBox3D childRegion = splitBound(IBox3D(this->minOwnedBound, this->maxOwnedBound), newBranchIndex);
- this->childNodes[newBranchIndex] = std::make_shared<OctreeNode<T>>(leaf, childRegion.min, childRegion.max);
- } else {
- // Add the leaf
- this->leaves.push(leaf);
- // Split when the direct leaves are too many
- if (this->leaves.length() > 64) {
- // Split the node into branches
- this->divided = true;
- List<OctreeLeaf<T>> oldLeaves = this->leaves;
- this->leaves.clear();
- for (int l = 0; l < oldLeaves.length(); l++) {
- this->insert(oldLeaves[l]);
- }
- }
- }
- }
- void find(const OcTreeFilter& boundFilter, const OcTreeLeafOperation<T>& leafOperation) {
- if (boundFilter(this->minLeafBound, this->maxLeafBound)) {
- // Looping backwards over leaves allow erasing without stepping into recently moved elements
- for (int l = this->leaves.length() - 1; l >= 0; l--) {
- if (this->leaves[l].find(boundFilter, leafOperation) == LeafAction::Erase) {
- this->leaves.remove(l);
- }
- }
- // Search in the other branches recursively
- for (int n = 0; n < 8; n++) {
- if (this->childNodes[n].get() != nullptr) {
- this->childNodes[n]->find(boundFilter, leafOperation);
- }
- }
- }
- }
- };
- // The root
- template <typename T>
- class Octree {
- private:
- // One start node for each direction to simplify expansion
- std::shared_ptr<OctreeNode<T>> childNodes[8];
- // Settings
- int initialSize; // Should be around the average total world size to create the most balanced trees
- public:
- explicit Octree(int initialSize)
- : initialSize(initialSize) {}
- public:
- // Precondition: minBound <= origin <= maxBound
- void insert(const T& leaf, const IVector3D& origin, const IVector3D& minBound, const IVector3D& maxBound) {
- int sideIndex = Octree_getBranchIndex(origin.x >= 0, origin.y >= 0, origin.z >= 0);
- if (this->childNodes[sideIndex].get() == nullptr) {
- // Calculate minimum required size
- int requiredSize = std::max(abs(origin.x), std::max(abs(origin.y), abs(origin.z)));
- // Calculate final cube size to be stored directly inside of the root
- int size = this->initialSize;
- while(size < requiredSize) {
- size *= 2;
- }
- IVector3D minOwnedBound = IVector3D(
- origin.x < 0 ? -size : 0,
- origin.y < 0 ? -size : 0,
- origin.z < 0 ? -size : 0
- );
- IVector3D maxOwnedBound = IVector3D(
- origin.x < 0 ? 0 : size,
- origin.y < 0 ? 0 : size,
- origin.z < 0 ? 0 : size
- );
- this->childNodes[sideIndex] = std::make_shared<OctreeNode<T>>(OctreeLeaf<T>(leaf, origin, minBound, maxBound), minOwnedBound, maxOwnedBound);
- } else {
- this->childNodes[sideIndex]->insert(OctreeLeaf<T>(leaf, origin, minBound, maxBound));
- }
- }
- // Find leaves using a custom filter
- void map(const OcTreeFilter& boundFilter, const OcTreeLeafOperation<T>& leafOperation) {
- for (int sideIndex = 0; sideIndex < 8; sideIndex++) {
- if (this->childNodes[sideIndex].get() != nullptr) {
- this->childNodes[sideIndex]->find(boundFilter, leafOperation);
- }
- }
- }
- // Find leaves using an axis aligned search box
- // Each leaf who's bounding box is touching the search box will be given as argument to the leafOperation callback
- void map(const IVector3D& searchMinBound, const IVector3D& searchMaxBound, const OcTreeLeafOperation<T>& leafOperation) {
- this->map([searchMinBound, searchMaxBound](const IVector3D& minBound, const IVector3D& maxBound){
- return searchMaxBound.x >= minBound.x && searchMinBound.x <= maxBound.x
- && searchMaxBound.y >= minBound.y && searchMinBound.y <= maxBound.y
- && searchMaxBound.z >= minBound.z && searchMinBound.z <= maxBound.z;
- }, leafOperation);
- }
- };
- }
- #endif
|