├── LICENSE ├── Makefile ├── Octree.h ├── OctreePoint.h ├── README.md ├── Stopwatch.h ├── Vec3.h └── main.cpp /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Brandon Pelfrey 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | default: 2 | g++ main.cpp -O3 -o octree 3 | clean: 4 | @rm -f octree 5 | -------------------------------------------------------------------------------- /Octree.h: -------------------------------------------------------------------------------- 1 | #ifndef Octree_H 2 | #define Octree_H 3 | 4 | #include 5 | #include 6 | #include "OctreePoint.h" 7 | 8 | namespace brandonpelfrey { 9 | 10 | /**! 11 | * 12 | */ 13 | class Octree { 14 | // Physical position/size. This implicitly defines the bounding 15 | // box of this node 16 | Vec3 origin; //! The physical center of this node 17 | Vec3 halfDimension; //! Half the width/height/depth of this node 18 | 19 | // The tree has up to eight children and can additionally store 20 | // a point, though in many applications only, the leaves will store data. 21 | Octree *children[8]; //! Pointers to child octants 22 | OctreePoint *data; //! Data point to be stored at a node 23 | 24 | /* 25 | Children follow a predictable pattern to make accesses simple. 26 | Here, - means less than 'origin' in that dimension, + means greater than. 27 | child: 0 1 2 3 4 5 6 7 28 | x: - - - - + + + + 29 | y: - - + + - - + + 30 | z: - + - + - + - + 31 | */ 32 | 33 | public: 34 | Octree(const Vec3& origin, const Vec3& halfDimension) 35 | : origin(origin), halfDimension(halfDimension), data(NULL) { 36 | // Initially, there are no children 37 | for(int i=0; i<8; ++i) 38 | children[i] = NULL; 39 | } 40 | 41 | Octree(const Octree& copy) 42 | : origin(copy.origin), halfDimension(copy.halfDimension), data(copy.data) { 43 | 44 | } 45 | 46 | ~Octree() { 47 | // Recursively destroy octants 48 | for(int i=0; i<8; ++i) 49 | delete children[i]; 50 | } 51 | 52 | // Determine which octant of the tree would contain 'point' 53 | int getOctantContainingPoint(const Vec3& point) const { 54 | int oct = 0; 55 | if(point.x >= origin.x) oct |= 4; 56 | if(point.y >= origin.y) oct |= 2; 57 | if(point.z >= origin.z) oct |= 1; 58 | return oct; 59 | } 60 | 61 | bool isLeafNode() const { 62 | // This is correct, but overkill. See below. 63 | /* 64 | for(int i=0; i<8; ++i) 65 | if(children[i] != NULL) 66 | return false; 67 | return true; 68 | */ 69 | 70 | // We are a leaf iff we have no children. Since we either have none, or 71 | // all eight, it is sufficient to just check the first. 72 | return children[0] == NULL; 73 | } 74 | 75 | void insert(OctreePoint* point) { 76 | // If this node doesn't have a data point yet assigned 77 | // and it is a leaf, then we're done! 78 | if(isLeafNode()) { 79 | if(data==NULL) { 80 | data = point; 81 | return; 82 | } else { 83 | // We're at a leaf, but there's already something here 84 | // We will split this node so that it has 8 child octants 85 | // and then insert the old data that was here, along with 86 | // this new data point 87 | 88 | // Save this data point that was here for a later re-insert 89 | OctreePoint *oldPoint = data; 90 | data = NULL; 91 | 92 | // Split the current node and create new empty trees for each 93 | // child octant. 94 | for(int i=0; i<8; ++i) { 95 | // Compute new bounding box for this child 96 | Vec3 newOrigin = origin; 97 | newOrigin.x += halfDimension.x * (i&4 ? .5f : -.5f); 98 | newOrigin.y += halfDimension.y * (i&2 ? .5f : -.5f); 99 | newOrigin.z += halfDimension.z * (i&1 ? .5f : -.5f); 100 | children[i] = new Octree(newOrigin, halfDimension*.5f); 101 | } 102 | 103 | // Re-insert the old point, and insert this new point 104 | // (We wouldn't need to insert from the root, because we already 105 | // know it's guaranteed to be in this section of the tree) 106 | children[getOctantContainingPoint(oldPoint->getPosition())]->insert(oldPoint); 107 | children[getOctantContainingPoint(point->getPosition())]->insert(point); 108 | } 109 | } else { 110 | // We are at an interior node. Insert recursively into the 111 | // appropriate child octant 112 | int octant = getOctantContainingPoint(point->getPosition()); 113 | children[octant]->insert(point); 114 | } 115 | } 116 | 117 | // This is a really simple routine for querying the tree for points 118 | // within a bounding box defined by min/max points (bmin, bmax) 119 | // All results are pushed into 'results' 120 | void getPointsInsideBox(const Vec3& bmin, const Vec3& bmax, std::vector& results) { 121 | // If we're at a leaf node, just see if the current data point is inside 122 | // the query bounding box 123 | if(isLeafNode()) { 124 | if(data!=NULL) { 125 | const Vec3& p = data->getPosition(); 126 | if(p.x>bmax.x || p.y>bmax.y || p.z>bmax.z) return; 127 | if(p.xorigin + children[i]->halfDimension; 136 | Vec3 cmin = children[i]->origin - children[i]->halfDimension; 137 | 138 | // If the query rectangle is outside the child's bounding box, 139 | // then continue 140 | if(cmax.xbmax.x || cmin.y>bmax.y || cmin.z>bmax.z) continue; 142 | 143 | // At this point, we've determined that this child is intersecting 144 | // the query bounding box 145 | children[i]->getPointsInsideBox(bmin,bmax,results); 146 | } 147 | } 148 | } 149 | 150 | }; 151 | 152 | } 153 | #endif 154 | -------------------------------------------------------------------------------- /OctreePoint.h: -------------------------------------------------------------------------------- 1 | #ifndef OctreePoint_H 2 | #define OctreePoint_H 3 | 4 | #include "Vec3.h" 5 | 6 | // Simple point data type to insert into the tree. 7 | // Have something with more interesting behavior inherit 8 | // from this in order to store other attributes in the tree. 9 | class OctreePoint { 10 | Vec3 position; 11 | public: 12 | OctreePoint() { } 13 | OctreePoint(const Vec3& position) : position(position) { } 14 | inline const Vec3& getPosition() const { return position; } 15 | inline void setPosition(const Vec3& p) { position = p; } 16 | }; 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SimpleOctree 2 | ============ 3 | 4 | A simple octree with good commenting for learning how octrees work. Blog post incoming with description, or read comments in Octree.h 5 | 6 | Usage 7 | ============ 8 | make && ./octree 9 | -------------------------------------------------------------------------------- /Stopwatch.h: -------------------------------------------------------------------------------- 1 | #ifndef _WIN32 2 | #include 3 | 4 | double stopwatch() 5 | { 6 | struct timeval time; 7 | gettimeofday(&time, 0 ); 8 | return 1.0 * time.tv_sec + time.tv_usec / (double)1e6; 9 | } 10 | 11 | #else 12 | 13 | #include 14 | double stopwatch() 15 | { 16 | unsigned long long ticks; 17 | unsigned long long ticks_per_sec; 18 | QueryPerformanceFrequency( (LARGE_INTEGER *)&ticks_per_sec); 19 | QueryPerformanceCounter((LARGE_INTEGER *)&ticks); 20 | return ((float)ticks) / (float)ticks_per_sec; 21 | } 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /Vec3.h: -------------------------------------------------------------------------------- 1 | #ifndef Vec3_h_ 2 | #define Vec3_h_ 3 | 4 | #include 5 | 6 | struct Vec3; 7 | Vec3 operator*(float r, const Vec3& v); 8 | 9 | struct Vec3 { 10 | union { 11 | struct { 12 | float x,y,z; 13 | }; 14 | float D[3]; 15 | }; 16 | 17 | Vec3() { } 18 | Vec3(float _x, float _y, float _z) 19 | :x(_x), y(_y), z(_z) 20 | { } 21 | 22 | float& operator[](unsigned int i) { 23 | return D[i]; 24 | } 25 | 26 | const float& operator[](unsigned int i) const { 27 | return D[i]; 28 | } 29 | 30 | float maxComponent() const { 31 | float r = x; 32 | if(y>r) r = y; 33 | if(z>r) r = z; 34 | return r; 35 | } 36 | 37 | float minComponent() const { 38 | float r = x; 39 | if(y 2 | #include 3 | #include 4 | #include 5 | 6 | #include "Octree.h" 7 | #include "Stopwatch.h" 8 | 9 | using namespace brandonpelfrey; 10 | 11 | // Used for testing 12 | std::vector points; 13 | Octree *octree; 14 | OctreePoint *octreePoints; 15 | Vec3 qmin, qmax; 16 | 17 | float rand11() // Random number between [-1,1] 18 | { return -1.f + (2.f*rand()) * (1.f / RAND_MAX); } 19 | 20 | Vec3 randVec3() // Random vector with components in the range [-1,1] 21 | { return Vec3(rand11(), rand11(), rand11()); } 22 | 23 | // Determine if 'point' is within the bounding box [bmin, bmax] 24 | bool naivePointInBox(const Vec3& point, const Vec3& bmin, const Vec3& bmax) { 25 | return 26 | point.x >= bmin.x && 27 | point.y >= bmin.y && 28 | point.z >= bmin.z && 29 | point.x <= bmax.x && 30 | point.y <= bmax.y && 31 | point.z <= bmax.z; 32 | } 33 | 34 | void init() { 35 | // Create a new Octree centered at the origin 36 | // with physical dimension 2x2x2 37 | octree = new Octree(Vec3(0,0,0), Vec3(1,1,1)); 38 | 39 | // Create a bunch of random points 40 | const int nPoints = 1 * 1000 * 1000; 41 | for(int i=0; iinsert(octreePoints + i); 51 | } 52 | printf("Inserted points to octree\n"); fflush(stdout); 53 | 54 | // Create a very small query box. The smaller this box is 55 | // the less work the octree will need to do. This may seem 56 | // like it is exagerating the benefits, but often, we only 57 | // need to know very nearby objects. 58 | qmin = Vec3(-.05,-.05,-.05); 59 | qmax = Vec3(.05,.05,.05); 60 | 61 | // Remember: In the case where the query is relatively close 62 | // to the size of the whole octree space, the octree will 63 | // actually be a good bit slower than brute forcing every point! 64 | } 65 | 66 | // Query using brute-force 67 | void testNaive() { 68 | double start = stopwatch(); 69 | 70 | std::vector results; 71 | for(int i=0; i results; 86 | octree->getPointsInsideBox(qmin, qmax, results); 87 | 88 | double T = stopwatch() - start; 89 | printf("testOctree found %ld points in %.5f sec.\n", results.size(), T); 90 | } 91 | 92 | 93 | int main(int argc, char **argv) { 94 | init(); 95 | testNaive(); 96 | testOctree(); 97 | 98 | return 0; 99 | } 100 | --------------------------------------------------------------------------------