├── .github └── workflows │ └── main.yml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md ├── benchmark ├── CMakeLists.txt └── OctreeBenchmark.cpp ├── conanfile.txt ├── example ├── CMakeLists.txt ├── demo.cpp ├── query-sphere-or-not-example.png └── sphere-query-example.png ├── include └── octree-cpp │ ├── OctreeCpp.h │ ├── OctreeQuery.h │ └── OctreeUtil.h └── tests ├── CMakeLists.txt └── OctreeCppTests.cpp /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CMake 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 11 | BUILD_TYPE: Release 12 | 13 | jobs: 14 | build-macOS: 15 | # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. 16 | # You can convert this to a matrix build if you need cross-platform coverage. 17 | # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix 18 | runs-on: macos-latest 19 | 20 | steps: 21 | - name: Install gtest manually 22 | run: brew install googletest 23 | 24 | - uses: actions/checkout@v3 25 | 26 | - name: Configure CMake 27 | # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. 28 | # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type 29 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} 30 | 31 | - name: Build 32 | # Build your program with the given configuration 33 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} 34 | 35 | - name: Test 36 | working-directory: ${{github.workspace}}/build/tests/ 37 | # Execute tests defined by the CMake configuration. 38 | # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail 39 | run: ctest -C ${{env.BUILD_TYPE}} 40 | 41 | build-ubuntu: 42 | # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. 43 | # You can convert this to a matrix build if you need cross-platform coverage. 44 | # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix 45 | runs-on: ubuntu-latest 46 | 47 | steps: 48 | - run: | 49 | sudo apt update 50 | sudo apt install gcc-10 g++-10 51 | - name: Install gtest manually 52 | run: sudo apt-get install libgtest-dev && cd /usr/src/gtest && sudo cmake CMakeLists.txt && sudo make && sudo cp lib/*.a /usr/lib && sudo ln -s /usr/lib/libgtest.a /usr/local/lib/libgtest.a && sudo ln -s /usr/lib/libgtest_main.a /usr/local/lib/libgtest_main.a 53 | - uses: actions/checkout@v3 54 | 55 | - name: Configure CMake 56 | # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. 57 | # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type 58 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} 59 | env: 60 | CC: gcc-10 61 | CXX: g++-10 62 | 63 | - name: Build 64 | # Build your program with the given configuration 65 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} 66 | 67 | - name: Test 68 | working-directory: ${{github.workspace}}/build/tests/ 69 | # Execute tests defined by the CMake configuration. 70 | # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail 71 | run: ctest -C ${{env.BUILD_TYPE}} 72 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | cmake-build-* 35 | .idea 36 | .DS_Store -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "example/polyscope"] 2 | path = example/polyscope 3 | url = git@github.com:nmwsharp/polyscope.git 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20.1) 2 | project(octree-cpp) 3 | 4 | set(CMAKE_CXX_STANDARD 20) 5 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 6 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++20") 7 | 8 | add_library(${PROJECT_NAME} INTERFACE) 9 | target_include_directories(${PROJECT_NAME} INTERFACE "include") 10 | 11 | add_subdirectory("tests") 12 | if (false) 13 | add_subdirectory("example") 14 | add_subdirectory("benchmark") 15 | endif() -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Stefan Annell 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT) 2 | ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/annell/octree-cpp/main.yml) 3 | 4 | # octree-cpp 5 | A Octree implementation in C++ built to work with your own vector class and any generic data blob stored along side its position in the world. 6 | The queries are very easily extendable via templates if there is some specific usecase that you want to query on. 7 | 8 | The usecase for this octree is to be able to, quickly do complex queries in 2D or 3D space. 9 | (Technically a octree is a 3D data structure, but it can be used in 2D space as well and it will at compile time convert to a quadtree.) 10 | 11 | ## What is Octree? 12 | > An octree is a tree data structure in which each internal node has exactly eight children. Octrees are most often used to partition a three-dimensional space by recursively subdividing it into eight octants. Octrees are the three-dimensional analog of quadtrees. The word is derived from oct (Greek root meaning "eight") + tree. Octrees are often used in 3D graphics and 3D game engines. 13 | 14 | [- Wikipedia](https://en.wikipedia.org/wiki/Octree) 15 | 16 | ## Features 17 | - Bring your own vector class, no need to adapt to a specific vector class defined in this library. 18 | - Header only implementation. 19 | - Simple cmake library integration. 20 | - Possible to use both at 2D or 3D space, and its automatically set at compile time, transforms between a Octree in 3d space and a Quadtree in 2d space depending on the provided vector. 21 | - Possible to use any generic data blob as payload. 22 | - Possible to extend the queries with your own custom queries, only need to satisfy the IsQuery concept. 23 | - Queries can be combined with AND, OR, NOT and Predicate to build up more complex shapes. 24 | - Very quickly builds up a new tree when the world changes. 25 | - Extensive unit testing of library. 26 | 27 | ## How to use 28 | The octree is very light weight to use, the basic usecase is: 29 | 1. Setup the octree with your vector class and the payload that you want to use. 30 | ```c++ 31 | using Octree = OctreeCpp; 32 | Octree octree({{0, 0, 0}, {1, 1, 1}}); 33 | ``` 34 | Here the vector class is called vec and the payload is a float, and it can be whetever you want. 35 | The min / max values for this octree is set to be [0, 0, 0] -> [1, 1, 1] 36 | 37 | 2. Add data to the octree. 38 | ```c++ 39 | octree.Add({.Vector{0.5f, 0.5f, 0.5f}, .Data{1.0f}}); 40 | ``` 41 | The payload is copied to the octree by default. 42 | 43 | 3. Query the octree for _hits_. 44 | ```c++ 45 | auto hits = octree.Query(Octree::Sphere{{0.5f, 0.5f, 0.5f}, 0.5f}); 46 | ``` 47 | Query returns a list of all objects that was within the given query. 48 | 49 | ### Basic query 50 | A simple usage using a query to find all data points within the given area. 51 | 52 | ```C++ 53 | // Create the octree with its boundry 54 | // First parameter is vector type, second parameter is payload 55 | using Octree = OctreeCpp; 56 | Octree octree({{0, 0, 0}, {1, 1, 1}}); 57 | auto hits = octree.Query(Octree::All()); 58 | EXPECT_EQ(hits.size(), 0); 59 | 60 | // Add data to the octree 61 | octree.Add({.Vector{0.5f, 0.5f, 0.5f}, .Data{1.0f}}); 62 | hits = octree.Query(Octree::All()); 63 | EXPECT_EQ(hits.size(), 1); 64 | 65 | // Query the octree using the SphereQuery 66 | hits = octree.Query(Octree::Sphere{{0.5f, 0.5f, 0.5f}, 0.5f}); 67 | EXPECT_EQ(hits.size(), 1) 68 | ``` 69 | ### Compund query 70 | You can use basic shapes and then also build up more complex queries by compound them with AND, OR, NOT and Predicate to do much more interesting queries while still only doing one pass. 71 | ````c++ 72 | // Basic Sphere query at 0, 0, 0 with 50 radius 73 | auto midQuery = Octree::Sphere{{0, 0, 0}, 50.0f}; 74 | 75 | // A sphere query but Not'ed 76 | auto notQuery = Octree::Not{{25, 25, 25}, 10}; 77 | 78 | // Two queries, one Not'ed, combined with AND 79 | auto result = octree.Query(Octree::And>{midQuery, notQuery}); 80 | ```` 81 | 82 | # To install 83 | ## CMake method 84 | 1. Clone octree-cpp to your project `git clone --recurse-submodules`. 85 | 2. Add `add_subdirectory(path/octree-cpp)` to your CMakeLists.txt. 86 | 3. Link your project to `octree-cpp`. 87 | 4. Include `#include ` in your project. 88 | 89 | ### Dependencies 90 | - C++20 91 | 92 | ### Example on integration 93 | Here you can find a example on how to integrate this library into your project using CMake: [ecs-cpp-example](https://github.com/annell/physim-cpp). 94 | In this example, the library is placed under thirdparty/octree-cpp. 95 | 96 | ## Dependencies 97 | - C++20 98 | - GTest 99 | 100 | ### To install all dependencies using Conan [optional] 101 | This library uses the PackageManager [Conan](https://conan.io) for its dependencies, and all dependencies can be found in `conantfile.txt`. 102 | 1. Install conan `pip3 install conan` 103 | 2. Go to the build folder that cmake generates. 104 | 3. Run `conan install ..` see [installing dependencies](https://docs.conan.io/en/1.7/using_packages/conanfile_txt.html) 105 | 106 | # To run test suite 107 | 1. Clone repo to your project. 108 | 2. Install dependencies. 109 | 3. Include the CMakeList in your cmake structure. 110 | 4. Build & run octree-cpp_test. 111 | 112 | # To run example 113 | 1. Clone repo to your project with submodules recursively `git clone --recurse-submodules` 114 | 2. Install dependencies. 115 | 3. Include the CMakeList in your cmake structure. 116 | 4. Build & run octree-cpp_demo. 117 | 118 | ### Basic sphere query. 119 | ![sphere-query-example.png](example%2Fsphere-query-example.png) 120 | 121 | ### A compound query with more complex geometry. 122 | ![sphere-query-example.png](example%2Fquery-sphere-or-not-example.png) 123 | 124 | -------------------------------------------------------------------------------- /benchmark/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include(FetchContent) 2 | 3 | # Externally provided libraries 4 | FetchContent_Declare(googletest 5 | GIT_REPOSITORY https://github.com/google/googletest.git 6 | GIT_TAG v1.10.x) 7 | 8 | FetchContent_Declare(googlebenchmark 9 | GIT_REPOSITORY https://github.com/google/benchmark.git 10 | GIT_TAG main) # need main for benchmark::benchmark 11 | 12 | FetchContent_MakeAvailable( 13 | googletest 14 | googlebenchmark) 15 | 16 | add_executable(${PROJECT_NAME}_benchmark OctreeBenchmark.cpp) 17 | target_link_libraries(${PROJECT_NAME}_benchmark benchmark::benchmark ${PROJECT_NAME}) 18 | -------------------------------------------------------------------------------- /benchmark/OctreeBenchmark.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Stefan Annell on 2023-05-20. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | struct vec { 10 | float x, y, z; 11 | auto operator<=>(const vec&) const = default; 12 | }; 13 | 14 | struct vec2d { 15 | float x, y; 16 | auto operator<=>(const vec2d&) const = default; 17 | }; 18 | 19 | using BasicOctree = OctreeCpp; 20 | using BasicOctree2d = OctreeCpp; 21 | 22 | 23 | static void BM_OctreeAdd2d(benchmark::State& state) { 24 | using Oct = OctreeCpp; 25 | Oct octree({{0.0f, 0.0f}, {1.0f, 1.0f}}); 26 | for (auto _ : state) 27 | octree.Add({{1.0f, 0.0f}, 5}); 28 | } 29 | BENCHMARK(BM_OctreeAdd2d); 30 | 31 | static void BM_OctreeAdd3d(benchmark::State& state) { 32 | using Oct = OctreeCpp; 33 | Oct octree({{0.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 1.0f}}); 34 | for (auto _ : state) 35 | octree.Add({{1.0f, 0.0f, 0.0f}, 5}); 36 | } 37 | BENCHMARK(BM_OctreeAdd3d); 38 | 39 | static void BM_OctreeQuerySmall2d(benchmark::State& state) { 40 | using Oct = OctreeCpp; 41 | Oct octree({{0.0f, 0.0f}, {1.0f, 1.0f}}); 42 | 43 | std::random_device rd; 44 | std::mt19937 gen(rd()); 45 | std::uniform_real_distribution dis(0.0f, 1.0f); 46 | for (int i = 0; i < state.range(0); i++) { 47 | octree.Add({{dis(gen), dis(gen)}, i}); 48 | } 49 | 50 | for (auto _ : state) { 51 | auto result = octree.Query(CircleQuery{{0.5f, 0.5f}, 0.1f}); 52 | benchmark::DoNotOptimize(result); 53 | benchmark::ClobberMemory(); 54 | } 55 | } 56 | BENCHMARK(BM_OctreeQuerySmall2d)->DenseRange(0, 500000, 50000); 57 | 58 | static void BM_OctreeQuerySmall3d(benchmark::State& state) { 59 | using Oct = OctreeCpp; 60 | Oct octree({{0.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 1.0f}}); 61 | 62 | std::random_device rd; 63 | std::mt19937 gen(rd()); 64 | std::uniform_real_distribution dis(0.0f, 1.0f); 65 | for (int i = 0; i < state.range(0); i++) { 66 | octree.Add({{dis(gen), dis(gen), dis(gen)}, i}); 67 | } 68 | 69 | for (auto _ : state) { 70 | auto result = octree.Query(SphereQuery{{0.5f, 0.5f, 0.5f}, 0.5f}); 71 | benchmark::DoNotOptimize(result); 72 | benchmark::ClobberMemory(); 73 | } 74 | } 75 | 76 | BENCHMARK(BM_OctreeQuerySmall3d)->DenseRange(0, 500000, 50000); 77 | 78 | static void BM_OctreeQueryLarge2d(benchmark::State& state) { 79 | using Oct = OctreeCpp; 80 | Oct octree({{0.0f, 0.0f}, {1.0f, 1.0f}}); 81 | 82 | std::random_device rd; 83 | std::mt19937 gen(rd()); 84 | std::uniform_real_distribution dis(0.0f, 1.0f); 85 | for (int i = 0; i < state.range(0); i++) { 86 | octree.Add({{dis(gen), dis(gen)}, i}); 87 | } 88 | 89 | for (auto _ : state) { 90 | auto result = octree.Query(CircleQuery{{0.5f, 0.5f}, 1.5f}); 91 | benchmark::DoNotOptimize(result); 92 | benchmark::ClobberMemory(); 93 | } 94 | } 95 | BENCHMARK(BM_OctreeQueryLarge2d)->DenseRange(0, 500000, 50000); 96 | 97 | static void BM_OctreeQueryLarge3d(benchmark::State& state) { 98 | using Oct = OctreeCpp; 99 | Oct octree({{0.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 1.0f}}); 100 | 101 | std::random_device rd; 102 | std::mt19937 gen(rd()); 103 | std::uniform_real_distribution dis(0.0f, 1.0f); 104 | for (int i = 0; i < state.range(0); i++) { 105 | octree.Add({{dis(gen), dis(gen), dis(gen)}, i}); 106 | } 107 | 108 | for (auto _ : state) { 109 | auto result = octree.Query(SphereQuery{{0.5f, 0.5f, 0.5f}, 1.5f}); 110 | benchmark::DoNotOptimize(result); 111 | benchmark::ClobberMemory(); 112 | } 113 | } 114 | BENCHMARK(BM_OctreeQueryLarge3d)->DenseRange(0, 500000, 50000); 115 | 116 | BENCHMARK_MAIN(); -------------------------------------------------------------------------------- /conanfile.txt: -------------------------------------------------------------------------------- 1 | [requires] 2 | gtest/cci.20210126 3 | benchmark/1.8.3 4 | 5 | [generators] 6 | CMakeDeps 7 | CMakeToolchain -------------------------------------------------------------------------------- /example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory("polyscope") 2 | 3 | add_executable(${PROJECT_NAME}_demo demo.cpp) 4 | target_link_libraries(${PROJECT_NAME}_demo octree-cpp polyscope) -------------------------------------------------------------------------------- /example/demo.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Stefan Annell on 2023-05-30. 3 | // 4 | 5 | #include "polyscope/polyscope.h" 6 | #include 7 | #include "polyscope/point_cloud.h" 8 | #include 9 | 10 | template<> 11 | bool CheckOverlapp(const Boundary& Box, const glm::vec3& SphereCenter, float SphereRadius) { 12 | const glm::vec3 closestPoint = glm::clamp(SphereCenter, Box.Min, Box.Max); 13 | return glm::distance(closestPoint, SphereCenter) > SphereRadius; 14 | } 15 | 16 | void app1() { 17 | using Octree = OctreeCpp; 18 | Octree octree({{-1000, -1000, -1000}, {1000, 1000, 1000}}); 19 | std::random_device rd; 20 | std::mt19937 gen(rd()); 21 | std::normal_distribution dis(0.0f, 1.0f); 22 | for (int i = 0; i < 1000; i++) { 23 | DataWrapper data = {{dis(gen), dis(gen), dis(gen)}, i}; 24 | octree.Add(data); 25 | } 26 | { 27 | auto pointsWrapper = octree.Query(AllQuery{}); 28 | std::vector points; 29 | for (auto pw : pointsWrapper) { 30 | points.push_back(pw.Vector); 31 | } 32 | polyscope::registerPointCloud("points", points); 33 | auto cloud = polyscope::getPointCloud("points"); 34 | cloud->setPointRenderMode(polyscope::PointRenderMode::Quad); 35 | cloud->setPointRadius(0.00085); 36 | } 37 | { 38 | glm::vec3 midpoint = {10.0f, 10.0f, 10.0f}; 39 | auto pointsWrapper = octree.Query(SphereQuery{midpoint, 50.0f}); 40 | std::vector points; 41 | for (auto pw : pointsWrapper) { 42 | points.push_back(pw.Vector); 43 | } 44 | polyscope::registerPointCloud("q1", points); 45 | auto cloud = polyscope::getPointCloud("q1"); 46 | cloud->setPointRadius(0.002); 47 | cloud->setPointColor({1.0, 0.0, 0.0}); 48 | cloud->setPointRenderMode(polyscope::PointRenderMode::Quad); 49 | 50 | std::vector points2; 51 | points2.push_back(midpoint); 52 | polyscope::registerPointCloud("q1_mid", points2); 53 | auto midcloud = polyscope::getPointCloud("q1_mid"); 54 | midcloud->setPointRadius(50, false); 55 | midcloud->setPointColor({1.0, 0.0, 0.0}); 56 | midcloud->setTransparency(0.5); 57 | } 58 | { 59 | glm::vec3 midpoint = {-10.0f, -10.0f, -10.0f}; 60 | auto pointsWrapper = octree.Query(SphereQuery{midpoint, 50.0f}); 61 | std::vector points; 62 | for (auto pw : pointsWrapper) { 63 | points.push_back(pw.Vector); 64 | } 65 | polyscope::registerPointCloud("q2", points); 66 | auto cloud = polyscope::getPointCloud("q2"); 67 | cloud->setPointRadius(0.002); 68 | cloud->setPointColor({0.0, 1.0, 0.0}); 69 | cloud->setPointRenderMode(polyscope::PointRenderMode::Quad); 70 | 71 | std::vector points2; 72 | points2.push_back(midpoint); 73 | polyscope::registerPointCloud("q2_mid", points2); 74 | auto midcloud = polyscope::getPointCloud("q2_mid"); 75 | midcloud->setPointRadius(50, false); 76 | midcloud->setPointColor({0.0, 1.0, 0.0}); 77 | midcloud->setTransparency(0.5); 78 | } 79 | 80 | 81 | polyscope::show(); 82 | } 83 | // Parameters which we will set in the callback UI. 84 | int nPts = 2000; 85 | glm::vec3 midpoint = {10.0f, 10.0f, 10.0f}; 86 | float radius = 50.0f; 87 | bool notFlag = false; 88 | bool andFlag = true; 89 | int queryTime = 0; 90 | 91 | void mySubroutine() { 92 | using namespace std::chrono; 93 | 94 | 95 | using Octree = OctreeCpp; 96 | Octree octree({{-1000, -1000, -1000}, {1000, 1000, 1000}}); 97 | std::random_device rd; 98 | std::mt19937 gen(rd()); 99 | std::normal_distribution dis(0.0f, 100.0f); 100 | for (int i = 0; i < nPts; i++) { 101 | DataWrapper data = {{dis(gen), dis(gen), dis(gen)}, i}; 102 | octree.Add(data); 103 | } 104 | { 105 | auto pointsWrapper = octree.Query(AllQuery{}); 106 | std::vector points; 107 | for (auto pw : pointsWrapper) { 108 | points.push_back(pw.Vector); 109 | } 110 | polyscope::registerPointCloud("points", points); 111 | auto cloud = polyscope::getPointCloud("points"); 112 | cloud->setPointRenderMode(polyscope::PointRenderMode::Sphere); 113 | } 114 | { 115 | auto start = high_resolution_clock::now(); 116 | std::vector pointsWrapper; 117 | auto midQuery = Octree::Sphere{{0, 0, 0}, 50.0f}; 118 | auto notQuery = Octree::Not{midpoint, radius}; 119 | auto query = Octree::Cylinder{midpoint, {-50, -50, -50}, radius}; 120 | if (andFlag) { 121 | if (notFlag) { 122 | pointsWrapper = octree.Query(Octree::And>{midQuery, notQuery}); 123 | } else { 124 | pointsWrapper = octree.Query(Octree::And{query, midQuery}); 125 | } 126 | } else { 127 | if (notFlag) { 128 | pointsWrapper = octree.Query(Octree::Or>{midQuery, notQuery}); 129 | } else { 130 | pointsWrapper = octree.Query(Octree::Or{query, midQuery}); 131 | } 132 | } 133 | auto stop = high_resolution_clock::now(); 134 | queryTime = duration_cast(stop - start).count(); 135 | 136 | std::vector points; 137 | for (auto pw : pointsWrapper) { 138 | points.push_back(pw.Vector); 139 | } 140 | polyscope::registerPointCloud("q1", points); 141 | auto cloud = polyscope::getPointCloud("q1"); 142 | cloud->setPointRadius(0.006); 143 | cloud->setPointColor({1.0, 0.0, 0.0}); 144 | 145 | return; 146 | std::vector points2; 147 | points2.push_back(midpoint); 148 | polyscope::registerPointCloud("q1_mid", points2); 149 | auto midcloud = polyscope::getPointCloud("q1_mid"); 150 | midcloud->setPointRadius(radius, false); 151 | midcloud->setPointColor({1.0, 0.0, 0.0}); 152 | } 153 | } 154 | 155 | // Your callback functions 156 | void myCallback() { 157 | 158 | // Since options::openImGuiWindowForUserCallback == true by default, 159 | // we can immediately start using ImGui commands to build a UI 160 | 161 | ImGui::PushItemWidth(100); // Make ui elements 100 pixels wide, 162 | // instead of full width. Must have 163 | // matching PopItemWidth() below. 164 | 165 | ImGui::SliderInt("num points", &nPts, 0, 10000); // set a int variable 166 | ImGui::SliderFloat3("midpoint", glm::value_ptr(midpoint), -100, 100); // set a float variable 167 | ImGui::SliderFloat("radius", &radius, 0, 1000); // set a float variable 168 | ImGui::Checkbox("Not", ¬Flag); 169 | ImGui::Checkbox("And/Or", &andFlag); 170 | auto time = "Query time: " + std::to_string(queryTime) + " microseconds"; 171 | ImGui::Text(time.c_str()); 172 | 173 | if (ImGui::Button("Run query")) { 174 | // executes when button is pressed 175 | mySubroutine(); 176 | } 177 | ImGui::PopItemWidth(); 178 | } 179 | 180 | void app2() { 181 | polyscope::state::userCallback = myCallback; 182 | polyscope::show(); 183 | } 184 | 185 | int main([[maybe_unused]] int argc,[[maybe_unused]] const char *argv[]) { 186 | polyscope::init(); 187 | app2(); 188 | return EXIT_SUCCESS; 189 | } -------------------------------------------------------------------------------- /example/query-sphere-or-not-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/annell/octree-cpp/90f428db2fe436ec1564aae4eb0b0123c2c26256/example/query-sphere-or-not-example.png -------------------------------------------------------------------------------- /example/sphere-query-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/annell/octree-cpp/90f428db2fe436ec1564aae4eb0b0123c2c26256/example/sphere-query-example.png -------------------------------------------------------------------------------- /include/octree-cpp/OctreeCpp.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "OctreeUtil.h" 4 | #include "OctreeQuery.h" 5 | #include 6 | 7 | /** 8 | * A octree implementation with Bring your own vector class depending on what you use 9 | * in your project. Capabale of storing whatever type of data positioned in 3d space 10 | * and support complex queries to find whatever data you are looking for quickly. 11 | * 12 | * @tparam TVector "Bring your own", Vector class that you want to use. Needs to fufil VectorLike concept. 13 | * @tparam TData Data blob that should be paired up with the added object. 14 | */ 15 | template 16 | requires VectorLike 17 | class OctreeCpp { 18 | private: 19 | static constexpr size_t MaxData = 8; 20 | using Section = std::conditional_t(), Octant, Quadrant>; 21 | 22 | public: 23 | using TDataWrapper = DataWrapper; 24 | using TBoundary = Boundary; 25 | 26 | /** 27 | * Not query, inverts whatever query it has as input. 28 | */ 29 | template Query> 30 | using Not = NotQuery; 31 | 32 | /** 33 | * Sphere query, for finding objects within given sphere. 34 | */ 35 | using Sphere = SphereQuery; 36 | 37 | /** 38 | * Circle query, for finding objects within given circle. 39 | */ 40 | using Circle = CircleQuery; 41 | 42 | /** 43 | * Cylinder query, for finding objects within given cylinder. 44 | */ 45 | using Cylinder = CylinderQuery; 46 | 47 | /** 48 | * Predicate query to find based on something specific in 49 | * either position or the data. 50 | */ 51 | using Pred = PredQuery; 52 | 53 | /** 54 | * Returns all points. 55 | */ 56 | using All = AllQuery; 57 | 58 | /** 59 | * Composite queries AND / OR that binds together several queries to 60 | * get more interesting results. 61 | */ 62 | template QueryLHS, IsQuery QueryRHS> 63 | using And = AndQuery; 64 | template QueryLHS, IsQuery QueryRHS> 65 | using Or = OrQuery; 66 | 67 | /** 68 | * Constructor to setup the Octree. 69 | * 70 | * @param Boundary min and max X, Y, Z values of the octree. 71 | */ 72 | explicit OctreeCpp(TBoundary Boundary) : BoundaryData(Boundary) { 73 | Data.reserve(MaxData); 74 | } 75 | 76 | /** 77 | * Stores the given data in the octree container. 78 | * @param DataWrapper 79 | */ 80 | void Add(const TDataWrapper& DataWrapper) { 81 | if (!IsPointInBoundrary(DataWrapper.Vector, BoundaryData)) { 82 | throw std::runtime_error("Vector is outside of boundary"); 83 | } 84 | 85 | if (Data.size() < MaxData) { 86 | Data.push_back(DataWrapper); 87 | if (!ValidateInvariant()) { 88 | throw std::runtime_error("Invariant is broken"); 89 | } 90 | NrObjects++; 91 | return; 92 | } 93 | Section section = LocateOctant(DataWrapper.Vector, BoundaryData.GetMidpoint()); 94 | if (!HasChild(section)) { 95 | CreateChild(section); 96 | } 97 | AddToChild(section, DataWrapper); 98 | NrObjects++; 99 | } 100 | 101 | /** 102 | * Queries the octree and returns all results that returns a hit. 103 | * Predefined queries can be user or you can define your own that fufil the concept IsQuery. 104 | * 105 | * @tparam TQueryObject The query type passed in. 106 | * @param QueryObject The object of TQueryObject with the query 107 | * @return A vector of results. 108 | */ 109 | template TQueryObject> 110 | [[nodiscard]] std::vector Query(const TQueryObject& QueryObject) const { 111 | std::vector result; 112 | QueryInternal(QueryObject, result); 113 | return result; 114 | } 115 | 116 | /** 117 | * @return Number of object in container. 118 | */ 119 | [[nodiscard]] size_t Size() const { 120 | return NrObjects; 121 | } 122 | 123 | /** 124 | * @return The boundraries of the octree. 125 | */ 126 | [[nodiscard]] std::vector GetBoundaries() const { 127 | std::vector result; 128 | result.push_back(BoundaryData); 129 | for (const auto& child : Children) { 130 | if (child) { 131 | auto childBoundaries = child->GetBoundaries(); 132 | for (auto & childchild : childBoundaries) { 133 | result.push_back(childchild); 134 | } 135 | } 136 | } 137 | return result; 138 | } 139 | 140 | private: 141 | template TQueryObject> 142 | void QueryInternal(const TQueryObject& QueryObject, std::vector& result) const { 143 | for (const auto& data : Data) { 144 | if (QueryObject.IsInside(data)) { 145 | result.push_back(data); 146 | } 147 | } 148 | if (Data.size() < MaxData) { 149 | return; 150 | } 151 | for (const auto& child : Children) { 152 | if (child && QueryObject.Covers(child->BoundaryData)) { 153 | child->QueryInternal(QueryObject, result); 154 | } 155 | } 156 | } 157 | 158 | void CreateChild(Section section) { 159 | if (HasChild(section)) { 160 | throw std::runtime_error("Child already exists"); 161 | } 162 | Children.at(static_cast(section)) = std::move(std::make_unique>( 163 | GetBoundraryFromSection(section, BoundaryData))); 164 | } 165 | 166 | void AddToChild(Section octant, const TDataWrapper& DataWrapper) { 167 | int index = static_cast(octant); 168 | if (!Children.at(index)) { 169 | throw std::runtime_error("Child does not exist"); 170 | } 171 | Children.at(index)->Add(DataWrapper); 172 | } 173 | 174 | bool HasChild(Section octant) const { 175 | size_t index = static_cast(octant); 176 | if (index > Children.size()) { 177 | throw std::runtime_error("Invalid octant"); 178 | } 179 | return Children.at(index) != nullptr; 180 | } 181 | 182 | [[nodiscard]] bool ValidateInvariant() const { 183 | if (Data.size() > MaxData) { 184 | return false; 185 | } 186 | for (const auto& data : Data) { 187 | if (!IsPointInBoundrary(data.Vector, BoundaryData)) { 188 | return false; 189 | } 190 | } 191 | return true; 192 | } 193 | 194 | std::array>, static_cast(Section::Count)> Children; 195 | std::vector Data; 196 | TBoundary BoundaryData; 197 | size_t NrObjects = 0; 198 | }; 199 | -------------------------------------------------------------------------------- /include/octree-cpp/OctreeQuery.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Stefan Annell on 2024-01-14. 3 | // 4 | #pragma once 5 | 6 | #include "OctreeUtil.h" 7 | 8 | template 9 | struct AllQuery { 10 | bool IsInside([[maybe_unused]] const TDataWrapper& Vector) const { 11 | return true; 12 | } 13 | bool Covers([[maybe_unused]] const Boundary& Boundary) const { 14 | return true; 15 | } 16 | }; 17 | 18 | template 19 | struct SphereQuery { 20 | const typename TDataWrapper::VectorType Midpoint = {0, 0, 0}; 21 | const float Radius = 0.0f; 22 | 23 | bool IsInside(const TDataWrapper& Data) const { 24 | return DistanceSquared(Midpoint, Data.Vector) <= Radius * Radius; 25 | } 26 | 27 | bool Covers(const Boundary& Boundary) const { 28 | return CheckOverlapp(Boundary, Midpoint, Radius); 29 | } 30 | }; 31 | 32 | template 33 | struct CircleQuery { 34 | const typename TDataWrapper::VectorType Midpoint = {0, 0}; 35 | const float Radius = 0.0f; 36 | 37 | bool IsInside(const TDataWrapper& Data) const { 38 | return DistanceSquared(Midpoint, Data.Vector) <= Radius * Radius; 39 | } 40 | 41 | bool Covers(const Boundary& Boundary) const { 42 | return CheckOverlapp(Boundary, Midpoint, Radius); 43 | } 44 | }; 45 | 46 | template 47 | struct CylinderQuery { 48 | const typename TDataWrapper::VectorType Point1 = {0, 0, 0}; 49 | const typename TDataWrapper::VectorType Point2 = {0, 0, 0}; 50 | const float Radius = 0.0f; 51 | 52 | bool IsInside(const TDataWrapper& Data) const { 53 | return DistancePointToLine(Data.Vector, Point1, Point2) <= Radius; 54 | } 55 | 56 | bool Covers(const Boundary& Boundary) const { 57 | return IsBoxInsideCylinder(Boundary, Point1, Point2, Radius); 58 | } 59 | }; 60 | 61 | template 62 | struct PredQuery { 63 | std::function Pred; 64 | 65 | bool IsInside(const TDataWrapper& Data) const { 66 | return Pred(Data); 67 | } 68 | 69 | bool Covers([[maybe_unused]] const Boundary& Boundary) const { 70 | return true; 71 | } 72 | }; 73 | 74 | template QueryLHS, IsQuery QueryRHS> 75 | struct AndQuery { 76 | QueryLHS Query1; 77 | QueryRHS Query2; 78 | 79 | bool IsInside(const TDataWrapper& Data) const { 80 | return Query1.IsInside(Data) && Query2.IsInside(Data); 81 | } 82 | 83 | bool Covers(const Boundary& Boundary) const { 84 | return Query1.Covers(Boundary) && Query2.Covers(Boundary); 85 | } 86 | }; 87 | 88 | template QueryLHS, IsQuery QueryRHS> 89 | struct OrQuery { 90 | QueryLHS Query1; 91 | QueryRHS Query2; 92 | 93 | bool IsInside(const TDataWrapper& Data) const { 94 | return Query1.IsInside(Data) || Query2.IsInside(Data); 95 | } 96 | 97 | bool Covers(const Boundary& Boundary) const { 98 | return Query1.Covers(Boundary) || Query2.Covers(Boundary); 99 | } 100 | }; 101 | 102 | template TQuery> 103 | struct NotQuery { 104 | TQuery Query; 105 | 106 | bool IsInside(const TDataWrapper& Data) const { 107 | return !Query.IsInside(Data); 108 | } 109 | 110 | bool Covers([[maybe_unused]] const Boundary& Boundary) const { 111 | return true; 112 | } 113 | }; 114 | 115 | -------------------------------------------------------------------------------- /include/octree-cpp/OctreeUtil.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Stefan Annell on 2023-12-20. 3 | // 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | 11 | template 12 | concept VectorLike3D = requires(TVector Vector) { 13 | { Vector.x } -> std::convertible_to; 14 | { Vector.y } -> std::convertible_to; 15 | { Vector.z } -> std::convertible_to; 16 | }; 17 | 18 | template 19 | concept VectorLike2D = requires(TVector Vector) { 20 | { Vector.x } -> std::convertible_to; 21 | { Vector.y } -> std::convertible_to; 22 | }; 23 | 24 | template 25 | concept VectorLike2D_t = (not VectorLike3D && VectorLike2D); 26 | 27 | template 28 | constexpr bool isVectorLike3D() { 29 | return requires(T Vector) { 30 | { Vector.x } -> std::convertible_to; 31 | { Vector.y } -> std::convertible_to; 32 | { Vector.z } -> std::convertible_to; 33 | }; 34 | } 35 | 36 | template 37 | concept VectorLike = requires(TVector Vector) { 38 | VectorLike3D || VectorLike2D_t; 39 | }; 40 | template 41 | requires VectorLike3D || VectorLike2D_t 42 | struct Boundary {}; 43 | 44 | template 45 | struct Boundary { 46 | const TVector Min = {}; 47 | const TVector Max = {}; 48 | static size_t constexpr NrCorners = 8; 49 | 50 | std::array Corners() const { 51 | std::array corners; 52 | corners[0] = {Min.x, Min.y, Min.z}; 53 | corners[1] = {Min.x, Min.y, Max.z}; 54 | corners[2] = {Min.x, Max.y, Min.z}; 55 | corners[3] = {Min.x, Max.y, Max.z}; 56 | corners[4] = {Max.x, Min.y, Min.z}; 57 | corners[5] = {Max.x, Min.y, Max.z}; 58 | corners[6] = {Max.x, Max.y, Min.z}; 59 | corners[7] = {Max.x, Max.y, Max.z}; 60 | return corners; 61 | } 62 | 63 | TVector GetMidpoint() const { 64 | return { 65 | (Min.x + Max.x) / 2, 66 | (Min.y + Max.y) / 2, 67 | (Min.z + Max.z) / 2 68 | }; 69 | } 70 | 71 | TVector GetSize() const { 72 | return { 73 | Max.x - Min.x, 74 | Max.y - Min.y, 75 | Max.z - Min.z 76 | }; 77 | } 78 | }; 79 | 80 | template 81 | struct Boundary { 82 | const TVector Min = {}; 83 | const TVector Max = {}; 84 | static size_t constexpr NrCorners = 4; 85 | 86 | std::array Corners() const { 87 | std::array corners; 88 | corners[0] = {Min.x, Min.y}; 89 | corners[1] = {Min.x, Max.y}; 90 | corners[2] = {Max.x, Min.y}; 91 | corners[3] = {Max.x, Max.y}; 92 | return corners; 93 | } 94 | 95 | TVector GetMidpoint() const { 96 | return { 97 | (Min.x + Max.x) / 2, 98 | (Min.y + Max.y) / 2 99 | }; 100 | } 101 | 102 | TVector GetSize() const { 103 | return { 104 | Max.x - Min.x, 105 | Max.y - Min.y 106 | }; 107 | } 108 | }; 109 | 110 | template 111 | concept IsQuery = requires(TQuery Query) { 112 | { Query.IsInside(TDataWrapper()) } -> std::convertible_to; 113 | { Query.Covers(Boundary()) } -> std::convertible_to; 114 | }; 115 | 116 | template 117 | concept IsDataWrapper = requires(TDataWrapper DataWrapper) { 118 | { DataWrapper.Vector } -> std::convertible_to; 119 | { DataWrapper.Data } -> std::convertible_to; 120 | }; 121 | 122 | template 123 | bool IsPointInBoundrary(const TVector& Point, const Boundary& Bound) { 124 | return Point.x >= Bound.Min.x && Point.x <= Bound.Max.x && 125 | Point.y >= Bound.Min.y && Point.y <= Bound.Max.y && 126 | Point.z >= Bound.Min.z && Point.z <= Bound.Max.z; 127 | } 128 | 129 | template 130 | bool IsPointInBoundrary(const TVector& Point, const Boundary& Bound) { 131 | return Point.x >= Bound.Min.x && Point.x <= Bound.Max.x && 132 | Point.y >= Bound.Min.y && Point.y <= Bound.Max.y; 133 | } 134 | 135 | template 136 | inline float DistanceSquared(const TVector& Point1, const TVector& Point2) { 137 | float diffX = Point1.x - Point2.x; 138 | float diffY = Point1.y - Point2.y; 139 | float diffZ = Point1.z - Point2.z; 140 | return diffX * diffX + diffY * diffY + diffZ * diffZ; 141 | } 142 | 143 | template 144 | inline float DistanceSquared(const TVector& Point1, const TVector& Point2) { 145 | float diffX = Point1.x - Point2.x; 146 | float diffY = Point1.y - Point2.y; 147 | return diffX * diffX + diffY * diffY; 148 | } 149 | 150 | template 151 | float Dot(const TVector& v1, const TVector& v2) { 152 | return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z; 153 | } 154 | 155 | template 156 | float Dot(const TVector& v1, const TVector& v2) { 157 | return v1.x * v2.x + v1.y * v2.y; 158 | } 159 | 160 | template 161 | float DistancePointToLine(const TVector& Point, const TVector& LinePoint1, const TVector& LinePoint2) { 162 | TVector v = LinePoint2; 163 | v.x -= LinePoint1.x; 164 | v.y -= LinePoint1.y; 165 | v.z -= LinePoint1.z; 166 | TVector w = Point; 167 | w.x -= LinePoint1.x; 168 | w.y -= LinePoint1.y; 169 | w.z -= LinePoint1.z; 170 | float c1 = Dot(w, v); 171 | float c2 = Dot(v, v); 172 | float b = c1 / c2; 173 | TVector Pb = TVector{LinePoint1}; 174 | Pb.x += v.x * b; 175 | Pb.y += v.y * b; 176 | Pb.z += v.z * b; 177 | 178 | return DistanceSquared(Point, Pb); 179 | } 180 | 181 | template 182 | float DistancePointToLine(const TVector& Point, const TVector& LinePoint1, const TVector& LinePoint2) { 183 | TVector v = LinePoint2; 184 | v.x -= LinePoint1.x; 185 | v.y -= LinePoint1.y; 186 | TVector w = Point; 187 | w.x -= LinePoint1.x; 188 | w.y -= LinePoint1.y; 189 | float c1 = Dot(w, v); 190 | float c2 = Dot(v, v); 191 | float b = c1 / c2; 192 | TVector Pb = TVector{LinePoint1}; 193 | Pb.x += v.x * b; 194 | Pb.y += v.y * b; 195 | 196 | return DistanceSquared(Point, Pb); 197 | } 198 | 199 | template 200 | bool IsBoxInsideCylinder(const Boundary& Boundary, const TVector& CylinderPoint1, const TVector& CylinderPoint2, float CylinderRadius) { 201 | for (const auto& corner : Boundary.Corners()) { 202 | if (DistancePointToLine(corner, CylinderPoint1, CylinderPoint2) <= CylinderRadius * CylinderRadius) { 203 | return true; 204 | } 205 | } 206 | 207 | if (IsPointInBoundrary(CylinderPoint1, Boundary) || IsPointInBoundrary(CylinderPoint2, Boundary)) { 208 | return true; 209 | } 210 | 211 | return false; 212 | } 213 | 214 | template 215 | inline TVector Clamp(const TVector& Value, const TVector& Min, const TVector& Max) { 216 | return std::min(std::max(Value, Min), Max); 217 | } 218 | 219 | inline bool checkOverlap(float R, float Xc, float Yc, float Zc, 220 | float X1, float Y1, float Z1, 221 | float X2, float Y2, float Z2) 222 | { 223 | int Xn = std::max(X1, std::min(Xc, X2)); 224 | int Yn = std::max(Y1, std::min(Yc, Y2)); 225 | int Zn = std::max(Z1, std::min(Zc, Z2)); 226 | 227 | int Dx = Xn - Xc; 228 | int Dy = Yn - Yc; 229 | int Zy = Zn - Zc; 230 | return (Dx * Dx + Dy * Dy + Zy * Zy) <= R * R; 231 | } 232 | 233 | inline bool checkOverlap2d(float R, float Xc, float Yc, 234 | float X1, float Y1, 235 | float X2, float Y2) 236 | { 237 | int Xn = std::max(X1, std::min(Xc, X2)); 238 | int Yn = std::max(Y1, std::min(Yc, Y2)); 239 | 240 | int Dx = Xn - Xc; 241 | int Dy = Yn - Yc; 242 | return (Dx * Dx + Dy * Dy) <= R * R; 243 | } 244 | 245 | template 246 | inline bool CheckOverlapp(const Boundary& Boundary, const TVector& Point1, float Radius) { 247 | return checkOverlap(Radius, Point1.x, Point1.y, Point1.z, Boundary.Min.x, Boundary.Min.y, Boundary.Min.z, Boundary.Max.x, Boundary.Max.y, Boundary.Max.z); 248 | } 249 | 250 | template 251 | inline bool CheckOverlapp(const Boundary& Boundary, const TVector& Point1, float Radius) { 252 | return checkOverlap2d(Radius, Point1.x, Point1.y, Boundary.Min.x, Boundary.Min.y, Boundary.Max.x, Boundary.Max.y); 253 | } 254 | 255 | template 256 | struct DataWrapper { 257 | using VectorType = TVector; 258 | using DataT = TData; 259 | 260 | VectorType Vector; 261 | DataT Data; 262 | }; 263 | 264 | enum class Octant { 265 | TopLeftFront = 0, 266 | TopRightFront, 267 | BottomLeftFront, 268 | BottomRightFront, 269 | TopLeftBack, 270 | TopRightBack, 271 | BottomLeftBack, 272 | BottomRightBack, 273 | Count 274 | }; 275 | 276 | enum class Quadrant { 277 | TopLeft = 0, 278 | TopRight, 279 | BottomLeft, 280 | BottomRight, 281 | Count 282 | }; 283 | 284 | template 285 | Octant LocateOctant(const TVector& Vector, const TVector& Midpoint) { 286 | if (Vector.x <= Midpoint.x) { 287 | if (Vector.y <= Midpoint.y) { 288 | return Vector.z <= Midpoint.z ? Octant::BottomLeftBack : Octant::TopLeftBack; 289 | } 290 | return Vector.z <= Midpoint.z ? Octant::BottomLeftFront : Octant::TopLeftFront; 291 | } 292 | if (Vector.y <= Midpoint.y) { 293 | return Vector.z <= Midpoint.z ? Octant::BottomRightBack : Octant::TopRightBack; 294 | } 295 | return Vector.z <= Midpoint.z ? Octant::BottomRightFront : Octant::TopRightFront; 296 | } 297 | 298 | template 299 | Quadrant LocateOctant(const TVector& Vector, const TVector& Midpoint) { 300 | if (Vector.x <= Midpoint.x) { 301 | return Vector.y <= Midpoint.y ? Quadrant::BottomLeft : Quadrant::TopLeft; 302 | } 303 | return Vector.y <= Midpoint.y ? Quadrant::BottomRight : Quadrant::TopRight; 304 | } 305 | 306 | template 307 | TBoundary GetBoundraryFromSection(Octant octant, const TBoundary& Boundary) { 308 | auto Midpoint = Boundary.GetMidpoint(); 309 | auto min = Boundary.Min; 310 | auto max = Boundary.Max; 311 | switch (octant) { 312 | case Octant::BottomLeftBack: 313 | return {min, Midpoint}; 314 | case Octant::BottomLeftFront: 315 | return {{min.x, Midpoint.y, min.z}, {Midpoint.x, max.y, Midpoint.z}}; 316 | case Octant::BottomRightBack: 317 | return {{Midpoint.x, min.y, min.z}, {max.x, Midpoint.y, Midpoint.z}}; 318 | case Octant::BottomRightFront: 319 | return {{Midpoint.x, Midpoint.y, min.z}, {max.x, max.y, Midpoint.z}}; 320 | case Octant::TopLeftBack: 321 | return {{min.x, min.y, Midpoint.z}, {Midpoint.x, Midpoint.y, max.z}}; 322 | case Octant::TopLeftFront: 323 | return {{min.x, Midpoint.y, Midpoint.z}, {Midpoint.x, max.y, max.z}}; 324 | case Octant::TopRightBack: 325 | return {{Midpoint.x, min.y, Midpoint.z}, {max.x, Midpoint.y, max.z}}; 326 | case Octant::TopRightFront: 327 | return {Midpoint, max}; 328 | default: 329 | throw std::runtime_error("Invalid octant"); 330 | } 331 | return {}; 332 | } 333 | 334 | template 335 | TBoundary GetBoundraryFromSection(Quadrant quadrant, const TBoundary& Boundary) { 336 | auto Midpoint = Boundary.GetMidpoint(); 337 | auto min = Boundary.Min; 338 | auto max = Boundary.Max; 339 | switch (quadrant) { 340 | case Quadrant::BottomLeft: 341 | return {min, Midpoint}; 342 | case Quadrant::BottomRight: 343 | return {{Midpoint.x, min.y}, {max.x, Midpoint.y}}; 344 | case Quadrant::TopLeft: 345 | return {{min.x, Midpoint.y}, {Midpoint.x, max.y}}; 346 | case Quadrant::TopRight: 347 | return {Midpoint, max}; 348 | default: 349 | throw std::runtime_error("Invalid quadrant"); 350 | } 351 | return {}; 352 | } 353 | 354 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(GTest REQUIRED) 2 | 3 | enable_testing() 4 | 5 | add_executable(${PROJECT_NAME}_test OctreeCppTests.cpp) 6 | target_link_libraries(${PROJECT_NAME}_test GTest::gtest GTest::gtest_main ${PROJECT_NAME}) 7 | target_include_directories(${PROJECT_NAME}_test PUBLIC ".") 8 | 9 | include(GoogleTest) 10 | gtest_discover_tests(${PROJECT_NAME}_test) -------------------------------------------------------------------------------- /tests/OctreeCppTests.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Stefan Annell on 2023-05-20. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | struct vec { 10 | float x, y, z; 11 | auto operator<=>(const vec&) const = default; 12 | }; 13 | 14 | struct vec2d { 15 | float x, y; 16 | auto operator<=>(const vec2d&) const = default; 17 | }; 18 | 19 | using BasicOctree = OctreeCpp; 20 | using BasicOctree2d = OctreeCpp; 21 | 22 | TEST(OctreeCppTest, VectorLikeConcept) { 23 | static_assert(VectorLike); 24 | static_assert(VectorLike3D); 25 | static_assert(not VectorLike3D); 26 | static_assert(VectorLike2D_t); 27 | static_assert(not VectorLike2D_t); 28 | static_assert(not VectorLike2D_t); 29 | static_assert(not VectorLike3D); 30 | } 31 | 32 | TEST(OctreeCppTest, BoundaryConcept) { 33 | static_assert(std::is_constructible_v>); 34 | static_assert(std::is_constructible_v>); 35 | [[maybe_unused]] Boundary boundary({{0, 0, 0}, {1, 1, 1}}); 36 | [[maybe_unused]] Boundary boundary2d({{0, 0}, {1, 1}}); 37 | } 38 | 39 | TEST(OctreeCppTest, DataWrapperConcept) { 40 | static_assert(std::is_constructible_v>); 41 | static_assert(std::is_constructible_v>); 42 | [[maybe_unused]] DataWrapper data; 43 | [[maybe_unused]] DataWrapper data2d; 44 | } 45 | 46 | TEST(OctreeCppTest, IsQueryConcept) { 47 | static_assert(IsQuery, BasicOctree::TDataWrapper>); 48 | static_assert(not IsQuery); 49 | static_assert(not IsQuery>, float>); 50 | [[maybe_unused]] SphereQuery query; 51 | } 52 | 53 | TEST(OctreeCppTest, OctreeConstructible) { 54 | static_assert(std::is_constructible_v, Boundary>); 55 | static_assert(std::is_constructible_v, Boundary>); 56 | static_assert(not std::is_constructible_v, Boundary>); 57 | static_assert(not std::is_constructible_v, Boundary>); 58 | [[maybe_unused]] BasicOctree octree({}); 59 | [[maybe_unused]] BasicOctree2d octree2d({}); 60 | } 61 | 62 | TEST(OctreeCppTest, OctreeAdd) { 63 | BasicOctree octree({{0, 0, 0}, {1, 1, 1}}); 64 | 65 | BasicOctree::TDataWrapper data = {{0.5f, 0.5f, 0.5f}, 1.0f}; 66 | octree.Add(data); 67 | EXPECT_EQ(octree.Size(), 1); 68 | } 69 | 70 | TEST(OctreeCppTest, OctreeAddOutsideX) { 71 | BasicOctree octree({{0, 0, 0}, {1, 1, 1}}); 72 | 73 | BasicOctree::TDataWrapper data = {{1.5f, 0.5f, 0.5f}, 1.0f}; 74 | EXPECT_THROW(octree.Add(data), std::runtime_error); 75 | EXPECT_EQ(octree.Size(), 0); 76 | } 77 | 78 | TEST(OctreeCppTest, OctreeAddOutsideY) { 79 | BasicOctree octree({{0, 0, 0}, {1, 1, 1}}); 80 | 81 | BasicOctree::TDataWrapper data = {{0.5f, 1.5f, 0.5f}, 1.0f}; 82 | EXPECT_THROW(octree.Add(data), std::runtime_error); 83 | EXPECT_EQ(octree.Size(), 0); 84 | } 85 | 86 | TEST(OctreeCppTest, OctreeAddOutsideZ) { 87 | BasicOctree octree({{0, 0, 0}, {1, 1, 1}}); 88 | 89 | BasicOctree::TDataWrapper data = {{0.5f, 0.5f, 1.5f}, 1.0f}; 90 | EXPECT_THROW(octree.Add(data), std::runtime_error); 91 | EXPECT_EQ(octree.Size(), 0); 92 | } 93 | 94 | TEST(OctreeCppTest, OctreeAddMany) { 95 | BasicOctree octree({{0, 0, 0}, {1, 1, 1}}); 96 | 97 | std::random_device rd; 98 | std::mt19937 gen(rd()); 99 | std::uniform_real_distribution dis(0.0f, 1.0f); 100 | for (int i = 0; i < 100; i++) { 101 | BasicOctree::TDataWrapper data = {{dis(gen), dis(gen), dis(gen)}, 1.0f}; 102 | octree.Add(data); 103 | } 104 | BasicOctree::TDataWrapper data = {{0.5f, 0.5f, 1.5f}, 1.0f}; 105 | EXPECT_THROW(octree.Add(data), std::runtime_error); 106 | EXPECT_EQ(octree.Size(), 100); 107 | } 108 | 109 | TEST(OctreeCppTest, OctreeQueryCircleEmpty) { 110 | BasicOctree octree({{0, 0, 0}, {1, 1, 1}}); 111 | SphereQuery query = {{0.5f, 0.5f, 0.5f}, 0.5f}; 112 | auto result = octree.Query(query); 113 | EXPECT_EQ(result.size(), 0); 114 | } 115 | 116 | TEST(OctreeCppTest, OctreeQueryCircleHit) { 117 | BasicOctree octree({{0, 0, 0}, {1, 1, 1}}); 118 | EXPECT_EQ(octree.Query(SphereQuery{{0.5f, 0.5f, 0.5f}, 0.5f}).size(), 0); 119 | 120 | octree.Add(BasicOctree::TDataWrapper{{0.5f, 0.5f, 0.5f}, 1.0f}); 121 | EXPECT_EQ(octree.Query(SphereQuery{{0.5f, 0.5f, 0.5f}, 0.5f}).size(), 1); 122 | } 123 | 124 | TEST(OctreeCppTest, OctreeQueryCircleHitNegative) { 125 | BasicOctree octree({{-100, -100, -100}, {100, 100, 100}}); 126 | EXPECT_EQ(octree.Query(SphereQuery{{0.5f, 0.5f, 0.5f}, 0.5f}).size(), 0); 127 | 128 | octree.Add(BasicOctree::TDataWrapper{{-20.0f, -20.5f, -10.5f}, 1.0f}); 129 | octree.Add(BasicOctree::TDataWrapper{{-5.0f, -20.5f, -10.5f}, 1.0f}); 130 | octree.Add(BasicOctree::TDataWrapper{{-5.0f, -5.5f, -5.5f}, 1.0f}); 131 | EXPECT_EQ(octree.Query(SphereQuery{{-20.0f, -70.0f, -10.5f}, 50.0f}).size(), 1); 132 | } 133 | 134 | TEST(OctreeCppTest, OctreeQueryCircleHitNegativeFullOctree) { 135 | OctreeCpp octree({{-100, -100, -100}, {100, 100, 100}}); 136 | 137 | std::random_device rd; 138 | std::mt19937 gen(rd()); 139 | std::uniform_real_distribution dis(-90.0f, 100.0f); 140 | for (int i = 0; i < 10000; i++) { 141 | DataWrapper data = {{dis(gen), dis(gen), dis(gen)}, i}; 142 | octree.Add(data); 143 | } 144 | 145 | EXPECT_EQ(octree.Query(AllQuery>{}).size(), 10000); 146 | 147 | octree.Add(DataWrapper{{-99.0f, -100.0f, -100.0f}, -1}); 148 | octree.Add(DataWrapper{{-95.0f, -100.0f, -100.0f}, -2}); 149 | octree.Add(DataWrapper{{-96.0f, -100.0f, -100.0f}, -3}); 150 | octree.Add(DataWrapper{{-97.0f, -100.0f, -100.0f}, -4}); 151 | EXPECT_EQ(octree.Query(SphereQuery>{{-145.0f, -100.0f, -100.0f}, 50.0f}).size(), 4); 152 | } 153 | 154 | TEST(OctreeCppTest, OctreeQueryCircleMiss) { 155 | BasicOctree octree({{0, 0, 0}, {1, 1, 1}}); 156 | EXPECT_EQ(octree.Query(SphereQuery{{0.5f, 0.5f, 0.5f}, 0.5f}).size(), 0); 157 | 158 | octree.Add(BasicOctree::TDataWrapper{{0.0f, 0.0f, 0.0f}, 1.0f}); 159 | octree.Add(BasicOctree::TDataWrapper{{1.0f, 0.0f, 0.0f}, 1.0f}); 160 | octree.Add(BasicOctree::TDataWrapper{{0.0f, 1.0f, 0.0f}, 1.0f}); 161 | octree.Add(BasicOctree::TDataWrapper{{0.0f, 0.0f, 1.0f}, 1.0f}); 162 | octree.Add(BasicOctree::TDataWrapper{{1.0f, 1.0f, 0.0f}, 1.0f}); 163 | octree.Add(BasicOctree::TDataWrapper{{0.0f, 1.0f, 1.0f}, 1.0f}); 164 | octree.Add(BasicOctree::TDataWrapper{{1.0f, 1.0f, 1.0f}, 1.0f}); 165 | octree.Add(BasicOctree::TDataWrapper{{1.0f, 0.0f, 1.0f}, 1.0f}); 166 | EXPECT_EQ(octree.Query(SphereQuery{{0.5f, 0.5f, 0.5f}, 0.5f}).size(), 0); 167 | 168 | EXPECT_EQ(octree.Query(SphereQuery{{0.5f, 0.5f, 0.5f}, 1.5f}).size(), 8); 169 | } 170 | 171 | TEST(OctreeCppTest, OctreeQueryOutsideBoundary) { 172 | BasicOctree octree({{0, 0, 0}, {1, 1, 1}}); 173 | EXPECT_EQ(octree.Query(SphereQuery{{0.5f, 0.5f, 0.5f}, 0.5f}).size(), 0); 174 | 175 | octree.Add(BasicOctree::TDataWrapper{{0.0f, 0.0f, 0.0f}, 1.0f}); 176 | octree.Add(BasicOctree::TDataWrapper{{1.0f, 0.0f, 0.0f}, 1.0f}); 177 | octree.Add(BasicOctree::TDataWrapper{{0.0f, 1.0f, 0.0f}, 1.0f}); 178 | octree.Add(BasicOctree::TDataWrapper{{0.0f, 0.0f, 1.0f}, 1.0f}); 179 | octree.Add(BasicOctree::TDataWrapper{{1.0f, 1.0f, 0.0f}, 1.0f}); 180 | octree.Add(BasicOctree::TDataWrapper{{0.0f, 1.0f, 1.0f}, 1.0f}); 181 | octree.Add(BasicOctree::TDataWrapper{{1.0f, 1.0f, 1.0f}, 1.0f}); 182 | octree.Add(BasicOctree::TDataWrapper{{1.0f, 0.0f, 1.0f}, 1.0f}); 183 | EXPECT_EQ(octree.Query(SphereQuery{{0.0f, 0.0f, -0.1f}, 0.5f}).size(), 1); 184 | } 185 | 186 | TEST(OctreeCppTest, OctreeQueryRand) { 187 | BasicOctree octree({{0, 0, 0}, {1, 1, 1}}); 188 | EXPECT_EQ(octree.Query(SphereQuery{{0.5f, 0.5f, 0.5f}, 0.5f}).size(), 0); 189 | 190 | octree.Add(BasicOctree::TDataWrapper{{0.0f, 0.0f, 0.0f}, 1.0f}); 191 | octree.Add(BasicOctree::TDataWrapper{{1.0f, 0.0f, 0.0f}, 1.0f}); 192 | octree.Add(BasicOctree::TDataWrapper{{0.0f, 1.0f, 0.0f}, 1.0f}); 193 | octree.Add(BasicOctree::TDataWrapper{{0.0f, 0.0f, 1.0f}, 1.0f}); 194 | octree.Add(BasicOctree::TDataWrapper{{1.0f, 1.0f, 0.0f}, 1.0f}); 195 | octree.Add(BasicOctree::TDataWrapper{{0.0f, 1.0f, 1.0f}, 1.0f}); 196 | octree.Add(BasicOctree::TDataWrapper{{1.0f, 1.0f, 1.0f}, 1.0f}); 197 | octree.Add(BasicOctree::TDataWrapper{{1.0f, 0.0f, 1.0f}, 1.0f}); 198 | EXPECT_EQ(octree.Query(SphereQuery{{0.5f, 0.5f, 0.5f}, 0.9f}).size(), 8); 199 | } 200 | 201 | TEST(OctreeCppTest, OctreeQueryMany) { 202 | BasicOctree octree({{0, 0, 0}, {1, 1, 1}}); 203 | octree.Add(DataWrapper{{0.5f, 0.5f, 0.5f}, 1.0f}); 204 | 205 | for (int i = 0; i < 1000; i++) { 206 | octree.Add(BasicOctree::TDataWrapper{{0.0f, 0.0f, 0.0f}, 1.0f}); 207 | octree.Add(BasicOctree::TDataWrapper{{1.0f, 0.0f, 0.0f}, 1.0f}); 208 | octree.Add(BasicOctree::TDataWrapper{{0.0f, 1.0f, 0.0f}, 1.0f}); 209 | octree.Add(BasicOctree::TDataWrapper{{0.0f, 0.0f, 1.0f}, 1.0f}); 210 | octree.Add(BasicOctree::TDataWrapper{{1.0f, 1.0f, 0.0f}, 1.0f}); 211 | octree.Add(BasicOctree::TDataWrapper{{0.0f, 1.0f, 1.0f}, 1.0f}); 212 | octree.Add(BasicOctree::TDataWrapper{{1.0f, 1.0f, 1.0f}, 1.0f}); 213 | octree.Add(BasicOctree::TDataWrapper{{1.0f, 0.0f, 1.0f}, 1.0f}); 214 | } 215 | 216 | EXPECT_EQ(octree.Query(SphereQuery{{0.5f, 0.5f, 0.5f}, 0.1f}).size(), 1); 217 | EXPECT_EQ(octree.Size(), 8001); 218 | EXPECT_EQ(octree.Query(SphereQuery{{0.0f, 0.0f, 0.0f}, 0.1f}).size(), 1000); 219 | } 220 | 221 | TEST(OctreeCppTest, OctreePredQuery) { 222 | using IntOctree = OctreeCpp; 223 | IntOctree octree({{0, 0, 0}, {1, 1, 1}}); 224 | octree.Add(IntOctree::TDataWrapper{{0.5f, 0.5f, 0.5f}, -1}); 225 | 226 | for (int i = 0; i < 1000; i++) { 227 | octree.Add(IntOctree::TDataWrapper{{0.0f, 0.0f, 0.0f}, i}); 228 | octree.Add(IntOctree::TDataWrapper{{1.0f, 0.0f, 0.0f}, i}); 229 | octree.Add(IntOctree::TDataWrapper{{0.0f, 1.0f, 0.0f}, i}); 230 | octree.Add(IntOctree::TDataWrapper{{0.0f, 0.0f, 1.0f}, i}); 231 | octree.Add(IntOctree::TDataWrapper{{1.0f, 1.0f, 0.0f}, i}); 232 | octree.Add(IntOctree::TDataWrapper{{0.0f, 1.0f, 1.0f}, i}); 233 | octree.Add(IntOctree::TDataWrapper{{1.0f, 1.0f, 1.0f}, i}); 234 | octree.Add(IntOctree::TDataWrapper{{1.0f, 0.0f, 1.0f}, i}); 235 | } 236 | 237 | EXPECT_EQ(octree.Query(SphereQuery{{0.5f, 0.5f, 0.5f}, 0.1f}).size(), 1); 238 | EXPECT_EQ(octree.Size(), 8001); 239 | EXPECT_EQ(octree.Query(SphereQuery{{0.0f, 0.0f, 0.0f}, 0.1f}).size(), 1000); 240 | EXPECT_EQ(octree.Query(PredQuery{[](const auto& Data){return Data.Data < 0;}}).size(), 1); 241 | EXPECT_EQ(octree.Query(PredQuery{[](const auto& Data){return Data.Data >= 0;}}).size(), 8000); 242 | EXPECT_EQ(octree.Query(PredQuery{[](const auto& Data){return Data.Data == 0;}}).size(), 8); 243 | } 244 | 245 | TEST(OctreeCppTest, OctreeAndQuery) { 246 | using Oct = OctreeCpp; 247 | Oct octree({{0, 0, 0}, {1, 1, 1}}); 248 | octree.Add({{0.5f, 0.5f, 0.5f}, -1}); 249 | 250 | for (int i = 0; i < 1000; i++) { 251 | octree.Add({{0.0f, 0.0f, 0.0f}, i}); 252 | octree.Add({{1.0f, 0.0f, 0.0f}, i}); 253 | octree.Add({{0.0f, 1.0f, 0.0f}, i}); 254 | octree.Add({{0.0f, 0.0f, 1.0f}, i}); 255 | octree.Add({{1.0f, 1.0f, 0.0f}, i}); 256 | octree.Add({{0.0f, 1.0f, 1.0f}, i}); 257 | octree.Add({{1.0f, 1.0f, 1.0f}, i}); 258 | octree.Add({{1.0f, 0.0f, 1.0f}, i}); 259 | } 260 | 261 | auto Pred1 = Oct::Pred{[](const auto& Data){return Data.Data > 0;}}; 262 | auto Pred2 = Oct::Pred{[](const auto& Data){return Data.Data < 2;}}; 263 | auto Sphere = Oct::Sphere{{0.5f, 0.5f, 0.5f}, 0.1f}; 264 | auto AQuery = Oct::And < Oct::Pred, Oct::Pred>{Pred1, Pred2}; 265 | auto BQuery = Oct::And < Oct::Sphere, Oct::Pred>{Sphere, Pred2}; 266 | auto CQuery = Oct::And < Oct::Not < Oct::Sphere >, Oct::Pred>{Sphere, Pred2}; 267 | auto DQuery = Oct::Or < Oct::Sphere, Oct::Not>{Sphere, Sphere}; 268 | EXPECT_EQ(octree.Query(AQuery).size(), 8); 269 | EXPECT_EQ(octree.Query(BQuery).size(), 1); 270 | EXPECT_EQ(octree.Query(CQuery).size(), 16); 271 | EXPECT_EQ(octree.Query(DQuery).size(), 8001); 272 | } 273 | 274 | TEST(OctreeCppTest, OctreeNotQuery) { 275 | using Oct = OctreeCpp; 276 | Oct octree({{0, 0, 0}, {1, 1, 1}}); 277 | octree.Add({{0.5f, 0.5f, 0.5f}, 0}); 278 | for (int i = 0; i < 20; i++) { 279 | octree.Add({{0.0f, 0.0f, 0.0f}, 1}); 280 | } 281 | 282 | auto Sphere = Oct::Sphere{{0.5f, 0.5f, 0.5f}, 0.1f}; 283 | auto result = octree.Query(Oct::Not{Sphere}); 284 | EXPECT_EQ(result.size(), 20); 285 | } 286 | 287 | TEST(OctreeCppTest, OctreeQueryCylinderHit) { 288 | BasicOctree octree({{0, 0, 0}, {1, 1, 1}}); 289 | auto query = BasicOctree::Cylinder{{0.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 1.0f}, 0.5f}; 290 | EXPECT_EQ(octree.Query(query).size(), 0); 291 | 292 | octree.Add(BasicOctree::TDataWrapper{{0.5f, 0.5f, 0.5f}, 1.0f}); 293 | EXPECT_EQ(octree.Query(query).size(), 1); 294 | octree.Add(BasicOctree::TDataWrapper{{1.0f, 1.0f, 0.0f}, 1.0f}); 295 | EXPECT_EQ(octree.Query(query).size(), 1); 296 | } 297 | 298 | TEST(OctreeCppTest, OctreeQueryCylinderMiss) { 299 | BasicOctree octree({{0, 0, 0}, {1, 1, 1}}); 300 | auto query = BasicOctree::Cylinder{{0.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 1.0f}, 0.5f}; 301 | EXPECT_EQ(octree.Query(query).size(), 0); 302 | 303 | octree.Add(BasicOctree::TDataWrapper{{1.0f, 1.0f, 0.0f}, 1.0f}); 304 | EXPECT_EQ(octree.Query(query).size(), 0); 305 | 306 | octree.Add(BasicOctree::TDataWrapper{{0.0f, 1.0f, 1.0f}, 1.0f}); 307 | EXPECT_EQ(octree.Query(query).size(), 0); 308 | 309 | octree.Add(BasicOctree::TDataWrapper{{1.0f, 0.0f, 1.0f}, 1.0f}); 310 | EXPECT_EQ(octree.Query(query).size(), 0); 311 | } 312 | 313 | TEST(OctreeCppTest, OctreeQueryCircleHit2d) { 314 | BasicOctree2d octree({{0, 0}, {1, 1}}); 315 | EXPECT_EQ(octree.Query(CircleQuery{{0.5f, 0.5f}, 0.5f}).size(), 0); 316 | 317 | octree.Add({{0.5f, 0.5f}, 1.0f}); 318 | EXPECT_EQ(octree.Query(SphereQuery{{0.5f, 0.5f}, 0.5f}).size(), 1); 319 | } 320 | 321 | 322 | TEST(OctreeCppTest, OctreeQueryCircle2dHitNegative) { 323 | BasicOctree2d octree({{-100, -100}, {100, 100}}); 324 | EXPECT_EQ(octree.Query(SphereQuery{{0.5f, 0.5f}, 0.5f}).size(), 0); 325 | 326 | octree.Add({{-20.0f, -20.5f}, 1.0f}); 327 | octree.Add({{-5.0f, -20.5f}, 1.0f}); 328 | octree.Add({{-5.0f, -5.5f}, 1.0f}); 329 | EXPECT_EQ(octree.Query(SphereQuery{{-20.0f, -70.0f}, 50.0f}).size(), 1); 330 | } 331 | --------------------------------------------------------------------------------