Lightweight, parallelizable C++ implementation of an Octree/Quadtree/N-d orthotree using Morton Z curve-based location code ordering.
What is the Octree and what is good for? https://en.wikipedia.org/wiki/Octree
What is Morton-Z space-filling curve? https://en.wikipedia.org/wiki/Z-order_curve
What is Loose Octree? https://anteru.net/blog/2008/loose-octrees/
CHANGELOG | DOCUMENTATION | BENCHMARKS
- Adaptable to any existing geometric system
- Adaptable to the original container of geometrical entities
- Arbitrary number of dimensions for other scientific usages
- Differentiated Static and Dynamic solution
- Optionally Loose octree
- Static BVH tree
- Parallelization is available (via
std::executionpolicies) - Edit functions to Insert/Update/Erase entities
- Wide range of search functions for both AABBs and points
- Range search
- Pick search
- K - Nearest neighbor search
- Ray-traced search
- Plane intersection
- Frustum culling
- Collision detection
- Additional entity testers for the search functions
- Nodes can be accessed in O(1) time
- Search is accelerated by Morton Z curve based location code
- Both the non-owning
Coreand theManagedwrappers are provided
- Maximum number of dimensions is 63.
- Maximum depth of octree solutions is 21.
- Language standard: C++20 or above
- Parallel execution via the
PAR_EXECtag requires support forstd::executionpolicies (usually requiring TBB on Linux and macOS).
You can include OrthoTree in your project using CMake's FetchContent:
include(FetchContent)
FetchContent_Declare(
OrthoTree
GIT_REPOSITORY https://github.com/attcs/Octree.git
GIT_TAG v3.1.0 # Or use a specific commit hash
)
FetchContent_MakeAvailable(OrthoTree)
target_link_libraries(your_project PRIVATE OrthoTree::OrthoTree)Or using CPM.cmake:
CPMAddPackage(
NAME OrthoTree
GITHUB_REPOSITORY attcs/Octree
GIT_TAG v3.1.0
)
target_link_libraries(your_project PRIVATE OrthoTree::OrthoTree)Since it's a header-only library, you can also just download the include folder and add it to your project's include paths.
Alternatively, use the provided CMake install target:
cmake -B build
cmake --install build --prefix /your/install/path- Use
AdaptorBasicsConceptorAdaptorConceptto adapt the actual geometric system. It is not a necessary step, basic point/vector and bounding box objects are available. - Decide to let the geometry management for octree or not.
Managedtypes could manage the geometries life-cycle, meanwhile thenon-managedtypes are just update the relevant metadata about the changes. - Use
PAR_EXECtag as first parameter of constructor for parallel execution - Use
PickSearch()/RangeSearch()member functions to collect the wanted id-s - Use
PlaneSearch()/PlaneIntersection()/PlanePositiveSegmentation()member functions for hyperplane related searches - Use
FrustumCulling()to get entities in the multi-plane-bounded space/frustum - Use
Coreedit functionsInsert(),Update(),UpdateIndexes(),Erase()if some of the underlying geometrical elements were changed or reordered - Use
InsertUnique()if tolerance-based unique insertion is needed for points - Use
Insert()for split overflown nodes during entity insertion - Use
Managededit functionsAdd(),Update(),Erase()if one of the underlying geometrical element was changed - Use
CollisionDetection()member function for bounding box overlap examination. - Use
TraverseNodesBreadthFirst()/TraverseNodesDepthFirst()/TraverseEntitiesByPriority()to traverse the tree from up to down (former is breadth-first search) with user-definedprocedure(). - Use
GetNearestNeighbors()for kNN search. https://en.wikipedia.org/wiki/K-nearest_neighbors_algorithm - Use
RayIntersectedFirst()orRayIntersectedAll()to get intersected bounding boxes in order by a ray.
- Header only implementation.
- Point and Bounding box-based solutions are distinguished.
- Core types store only the entity ids; use Managed types to manage both the tree and the original entity data.
- Naming
- Managed types have "M" postfix (e.g.,
OctreeBoxM). Mapnamed aliases are declared forstd::unordered_mapgeometry containers (e.g.:QuadtreeBoxMap,OctreeBoxMap,OctreeBoxMapM). Non-Mapnamed aliases usesstd::span, which is compatible withstd::vector,std::arrayor any contiguous container.smeans adjustableLOOSE_TREEfor box-types (e.g.,OctreeBoxCs).
- Managed types have "M" postfix (e.g.,
- For box types 2.0 loose tree is the default.
- Edit functions are available but not recommended to fully build the tree with them.
- If less element is collected in a node than the max element then the child node won't be created.
- Original geometry data is not stored in Core types, so any search function needs them as an input.
- Tested compilers: MSVC 2022, Clang 12.0.0, GCC 11.3, AppleClang 16.0.0
- Default: 2D, 3D...63D;
std::arraybased structures (PointND,VectorND,BoundingBoxND,RayND,PlaneND) - CGAL: 2D, 3D;
CGAL::OctreePoint,OctreeBox,OctreePointM,OctreeBoxM, etc. (adaptor.cgal.h) - Eigen: 2D, 3D;
Eigen::OctreePoint3d,OctreePointC3d,OctreeBox3d,OctreeBoxC3d, etc. (adaptor.eigen.h) - glm: 2D, 3D, 4D;
glm::octree_point,octree_box,octree_point_m,octree_box_m, etc. (adaptor.glm.h) - Unreal Engine: 2D, 3D;
FOctreePoint,FOctreePointM,FOctreeBox,FOctreeBoxM, etc. (adaptor.unreal.h) - Boost: 2D, 3D;
boost::geometry::octree_point,octree_box, etc. (adaptor.boost.h) struct{x,y,z}: 2D, 3D; (adaptor.xyz.h)
CoreOrthoTreetypes- Static:
- Contiguous:
StaticQuadtreePoint,StaticOctreePoint,StaticOrthoTreePointND<dim, ...>,StaticQuadtreeBox,StaticOctreeBox,StaticOrthoTreeBoxND<dim, ...> - Custom keyed:
StaticQuadtreePointMap,StaticOctreePointMap,StaticQuadtreeBoxMap,StaticOctreeBoxMap
- Contiguous:
- Dynamic:
- Contiguous:
QuadtreePoint,OctreePoint,OrthoTreePointND<dim, ...>,QuadtreeBox,OctreeBox,OrthoTreeBoxND<dim, ...> - Custom keyed:
QuadtreePointMap,OctreePointMap,QuadtreeBoxMap,OctreeBoxMap
- Contiguous:
- Static:
Managedtypes:- Static:
- Contiguous:
StaticQuadtreePointM,StaticOctreePointM,StaticQuadtreeBoxM,StaticOctreeBoxM,StaticOrthoTreePointManagedND<dim, ...>,StaticOrthoTreeBoxManagedND<dim, ...> - Custom keyed:
StaticQuadtreePointMapM,StaticOctreePointMapM,StaticQuadtreeBoxMapM,StaticOctreeBoxMapM
- Contiguous:
- Dynamic:
- Contiguous:
QuadtreePointM,OctreePointM,QuadtreeBoxM,OctreeBoxM,OrthoTreePointManagedND<dim, ...>,OrthoTreeBoxManagedND<dim, ...> - Custom keyed:
QuadtreePointMapM,OctreePointMapM,QuadtreeBoxMapM,OctreeBoxMapM
- Contiguous:
- Static:
CoreBVHtypes- Static:
- Contiguous:
StaticBVHPoint2D,StaticBVHPoint3D,StaticBVHPointND<dim, ...>,StaticBVHBox2D,StaticBVHBox3D,StaticBVHBoxND<dim, ...> - Custom keyed:
StaticBVHPointMap2D,StaticBVHPointMap3D,StaticBVHPointND<dim, ..., false>,StaticBVHBoxMap2D,StaticBVHBoxMap3D,StaticBVHBoxND<dim, ..., false>
- Contiguous:
- Static:
Managedtypes:- Static:
- Contiguous:
StaticBVHPoint2DM,StaticBVHPoint3DM,StaticBVHPointManagedND<dim, ...>,StaticBVHBox2DM,StaticBVHBox3DM,StaticBVHBoxManagedND<dim, ...> - Custom keyed:
StaticBVHPointMap2DM,StaticBVHPointMap3DM,StaticBVHPointManagedND<dim, ..., false>,StaticBVHBoxMap2DM,StaticBVHBoxMap3DM,StaticBVHBoxManagedND<dim, ..., false>
- Contiguous:
- Static:
See the full list of the default aliases in core/ot_aliases.h or core/bvh_aliases.h
Usage of Managed types (Recommended)
#include "octree.h"
using namespace OrthoTree;
// Example #1: Octree for points
{
auto constexpr points = std::array{ Point3D{0,0,0}, Point3D{1,1,1}, Point3D{2,2,2} };
auto const octree = OctreePointM(points, 3 /*max depth*/);
auto const searchBox = BoundingBox3D{ {0.5, 0.5, 0.5}, {2.5, 2.5, 2.5} };
auto const pointIDs = octree.RangeSearch(searchBox); //: { 1, 2 }
auto neighborNo = 2;
auto pointIDsByKNN = octree.GetNearestNeighbors(Point3D{ 1.1, 1.1, 1.1 }
, neighborNo
); //: { 1, 2 }
}
// Example #2: Quadtree for bounding boxes
{
auto boxes = std::vector
{
BoundingBox2D{ { 0.0, 0.0 }, { 1.0, 1.0 } },
BoundingBox2D{ { 1.0, 1.0 }, { 2.0, 2.0 } },
BoundingBox2D{ { 2.0, 2.0 }, { 3.0, 3.0 } },
BoundingBox2D{ { 3.0, 3.0 }, { 4.0, 4.0 } },
BoundingBox2D{ { 1.2, 1.2 }, { 2.8, 2.8 } }
};
auto quadtree = QuadtreeBoxM(boxes
, 3 // max depth
, std::nullopt // user-provided bounding Box for all
, 1 // max element in a node
);
auto collidingIDPairs = quadtree.CollisionDetection(); //: { {1,4}, {2,4} }
auto searchBox = BoundingBox2D{ { 1.0, 1.0 }, { 3.1, 3.1 } };
// Boxes within the range
auto insideBoxIDs = quadtree.RangeSearch(searchBox); //: { 1, 2, 4 }
// Overlapping Boxes with the range
auto overlappingBoxIDs = quadtree.RangeSearch(searchBox, RangeSearchMode::Overlap);
//: { 1, 2, 3, 4 }
// Picked boxes
auto pickPoint = Point2D{ 2.5, 2.5 };
auto pickedIDs = quadtree.PickSearch(pickPoint); //: { 2, 4 }
}
// Example #3: Parallel creation of octree for bounding boxes
{
auto boxes = std::vector
{
BoundingBox3D{ { 0.0, 0.0, 0.0 }, { 1.0, 1.0, 1.0 } }
/* and more... */
};
auto octreeUsingCtor = OctreeBoxM(PAR_EXEC
, boxes
, 3
, std::nullopt
, OctreeBox::DEFAULT_MAX_ELEMENT
);
auto octreeUsingCreate = OctreeBoxM::Create<ParExec>(boxes, 3);
}
// Example #4: Using std::unordered_map-based container
{
auto boxes = std::unordered_map<OrthoTree::index_t, BoundingBox2D>{
{ 10, BoundingBox2D{{ 0.0, 0.0 }, { 1.0, 1.0 }}},
{ 13, BoundingBox2D{{ 3.0, 3.0 }, { 4.0, 4.0 }}},
{ 11, BoundingBox2D{{ 1.0, 1.0 }, { 2.0, 2.0 }}},
{ 14, BoundingBox2D{{ 1.2, 1.2 }, { 2.8, 2.8 }}},
{ 12, BoundingBox2D{{ 2.0, 2.0 }, { 3.0, 3.0 }}}
};
auto qt = QuadtreeBoxMap(
boxes,
3, // max depth
std::nullopt, // user-provided bounding Box for all
1 // max element in a node
);
}Usage of Core types
#include "octree.h"
using namespace OrthoTree;
// Example #1: Octree for points
{
auto constexpr points = std::array{ Point3D{0,0,0}, Point3D{1,1,1}, Point3D{2,2,2} };
auto const octree = OctreePoint(points, 3 /*max depth*/);
auto const searchBox = BoundingBox3D{ {0.5, 0.5, 0.5}, {2.5, 2.5, 2.5} };
auto pointIDsByRange = octree.RangeSearch(searchBox, points); //: { 1, 2 }
auto pointIDsByKNN = octree.GetNearestNeighbors(Point3D{ 1.1,1.1,1.1 }
, 2 // k neighbor
, points
); //: { 1, 2 }
}
// Example #2: Quadtree for bounding boxes
{
auto boxes = std::vector
{
BoundingBox2D{ { 0.0, 0.0 }, { 1.0, 1.0 } },
BoundingBox2D{ { 1.0, 1.0 }, { 2.0, 2.0 } },
BoundingBox2D{ { 2.0, 2.0 }, { 3.0, 3.0 } },
BoundingBox2D{ { 3.0, 3.0 }, { 4.0, 4.0 } },
BoundingBox2D{ { 1.2, 1.2 }, { 2.8, 2.8 } }
};
auto qt = QuadtreeBox(boxes
, 3 // max depth
, std::nullopt // user-provided bounding Box for all
, 1 // max element in a node
);
auto collidingIDPairs = qt.CollisionDetection(boxes); //: { {1,4}, {2,4} }
auto searchBox = BoundingBox2D{ { 1.0, 1.0 }, { 3.1, 3.1 } };
// Boxes within the range
auto insideBoxIDs = qt.RangeSearch(searchBox, boxes); //: { 1, 2, 4 }
// Overlapping Boxes with the range
auto overlappingBoxIDs = qt.RangeSearch(searchBox, boxes, RangeSearchMode::Overlap); //: { 1, 2, 3, 4 }
// Picked boxes
auto pickPoint = Point2D{ 2.5, 2.5 };
auto pickedBoxIDs = qt.PickSearch(pickPoint, boxes); //: { 2, 4 }
}For more examples, see the unit tests. E.g., example.tests.cpp.
| Library | Header-only | Dependencies | N-Dimensions | C++ | License | Adaptability | Key Focus / Features |
|---|---|---|---|---|---|---|---|
| OrthoTree (this) | ✓ | None (STL) | 1D - 63D (compile-time) |
20 | MIT | High | CAD / Simulation / Research / Robotics |
| nanoflann | ✓ | None (STL) | Full (compile-time) |
11 | BSD | High | Fast KD-trees and k-NN search. |
| libspatialindex | ✗ | None | N-D (runtime) |
11 | MIT | Moderate | R*-tree, MVR-tree, TPR-tree, C-API. |
| madmann91/bvh | ✓ | None (STL) | N-D (compile-time) |
17 | MIT | High | High performance BVH, Ray tracing. |
| Open3D | ✗ | Eigen, TBB | 3D only (runtime) |
17 | MIT | ✗ | 3D Point Cloud geometry processing. |
| Boost.Geometry | (✓) | Boost | Full (compile-time) |
03 | BSL-1.0 | Moderate | R-trees, heavy header dependency. |
| CGAL | ✗ | GMP, MPFR | Full (compile-time) |
14 | GPL/LGPL | Moderate | High precision, complex algorithms. |
| PCL | ✗ | Eigen, FLANN | 3D only (runtime) |
14 | BSD | ✗ | 3D Point Cloud processing. |
| Intel Embree | ✗ | Binary | 3D only (runtime) |
11 | Apache 2.0 | ✗ | Ray Tracing kernels, BVH-centric. |
| Unreal TOctree | ✗ | Engine | 3D / 2D (runtime) |
17 | Proprietary | ✗ | Engine-specific, non-standalone. |
Detailed Feature Comparison
| Feature | OrthoTree | nanoflann | spatialindex | madmann/bvh | Open3D | Boost | CGAL | PCL | Embree | Unreal |
|---|---|---|---|---|---|---|---|---|---|---|
| Point support | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | ✓ |
| AABB / Box support | ✓ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | (✓) | ✓ | ✓ |
| Ray / Raycast | ✓ | ✗ | ✗ | ✓ | ✓ | (✓) | ✓ | ✗ | ✓ | ✓ |
| Frustum / Plane queries | ✓ | ✗ | ✗ | ✗ | ✓ | (✓) | ✓ | ✗ | ✗ | ✗ |
| kNN search | Point, Box | Point | Point, Box | ✗ | Point | Point, Box | Point, Box | Point | ✗ | ✗ |
| Collision detection | ✓ | ✗ | ✗ | ✗ | (✓) | ✓ | ✓ | ✗ | ✗ | ✓ |
| User-defined traversal | ✓ | (✓) | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Entity tester predicate for Queries | ✓ | (✓) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Spatial structures | Loose- / Octree / BVH | KD | R*-Tree / MVR | BVH | Octree / KD | R*-Tree | KD / AABB | Octree / KD | BVH | Loose Octree |
| Static / Dynamic | Both | Static | Both | Static | Both | Both | Both | Both | Both | Both |
| Sparse support | ✓ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Native C API | ✗ | ✗ | ✓ | ✗ | ✓ | ✗ | ✗ | ✗ | ✓ | ✗ |
| Serialize | ✗ | Bin | Bin, Custom | Bin | Bin, JSON | ✗ | (✓) Text/Bin | Bin, Text | ✗ | (✓) Bin |
| Parallel build | ✓ (STL) | ✗ | ✗ | ✓ | ✓ | ✗ | ✓ | ✓ | ✓ | (✓) |
| GPU acceleration | ✗ | ✗ | ✗ | ✗ | ✓ | ✗ | ✗ | ✗ | ✓ | ✓ |