├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── .rustfmt.toml ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── codecov.yml ├── examples └── simple.rs ├── fuzz ├── .gitignore ├── Cargo.toml └── fuzz_targets │ └── fuzz.rs ├── justfile ├── media └── sponza.obj ├── release.toml └── src ├── aabb ├── aabb_impl.rs ├── intersection.rs └── mod.rs ├── ball.rs ├── bounding_hierarchy.rs ├── bvh ├── bvh_impl.rs ├── bvh_node.rs ├── child_distance_traverse.rs ├── distance_traverse.rs ├── iter.rs ├── mod.rs └── optimization.rs ├── flat_bvh.rs ├── lib.rs ├── point_query.rs ├── ray ├── intersect_default.rs ├── intersect_simd.rs ├── mod.rs └── ray_impl.rs ├── testbase.rs └── utils.rs /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: svenstaro 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: monthly 7 | groups: 8 | all-dependencies: 9 | patterns: 10 | - "*" 11 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | check-fmt: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | - uses: dtolnay/rust-toolchain@master 11 | with: 12 | toolchain: nightly 13 | components: rustfmt 14 | - name: Check formatting 15 | run: cargo fmt --all -- --check 16 | - name: Check formatting (fuzzer) 17 | run: cargo fmt --manifest-path fuzz/Cargo.toml --all -- --check 18 | 19 | clippy: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v4 23 | - uses: dtolnay/rust-toolchain@master 24 | with: 25 | toolchain: nightly 26 | components: clippy 27 | - name: Run clippy 28 | run: cargo clippy --all-targets --all-features -- -D warnings 29 | - name: Run clippy (fuzzer) 30 | run: cargo clippy --manifest-path fuzz/Cargo.toml --all-targets --all-features -- -D warnings 31 | 32 | msrv: 33 | runs-on: ubuntu-latest 34 | steps: 35 | - uses: actions/checkout@v4 36 | - uses: dtolnay/rust-toolchain@master 37 | with: 38 | toolchain: nightly 39 | 40 | - name: Install cargo-msrv 41 | uses: baptiste0928/cargo-install@v3 42 | with: 43 | crate: cargo-msrv 44 | locked: false 45 | 46 | - name: Verify MSRV 47 | run: cargo msrv verify --ignore-lockfile 48 | 49 | coverage: 50 | runs-on: ubuntu-latest 51 | steps: 52 | - uses: actions/checkout@v4 53 | - uses: dtolnay/rust-toolchain@master 54 | with: 55 | toolchain: nightly 56 | components: clippy 57 | - run: cargo install cargo-tarpaulin 58 | - run: cargo tarpaulin --out xml 59 | - name: Upload coverage report to codecov.io 60 | uses: codecov/codecov-action@v4 61 | with: 62 | fail_ci_if_error: true 63 | token: ${{ secrets.CODECOV_TOKEN }} 64 | 65 | fuzz: 66 | name: fuzz 67 | runs-on: ubuntu-latest 68 | 69 | steps: 70 | - uses: actions/checkout@v4 71 | 72 | - uses: dtolnay/rust-toolchain@master 73 | with: 74 | # use a known good version (e.g. on Jan 14 2025, a faulty `rustc` nightly panicked) 75 | toolchain: nightly-2025-02-17 76 | components: rust-src 77 | 78 | - name: Install cargo-fuzz 79 | uses: baptiste0928/cargo-install@v3 80 | with: 81 | crate: cargo-fuzz 82 | locked: false 83 | 84 | - name: Fuzz for a limited time 85 | run: | 86 | RUST_BACKTRACE=1 cargo fuzz run fuzz -- -max_total_time=60 87 | 88 | build-and-test-no-simd: 89 | name: CI with ${{ matrix.rust }} on ${{ matrix.os }} [no SIMD] 90 | runs-on: ${{ matrix.os }} 91 | strategy: 92 | matrix: 93 | os: [ubuntu-latest, windows-latest, macos-latest] 94 | rust: [stable, nightly] 95 | 96 | steps: 97 | - uses: actions/checkout@v4 98 | - uses: dtolnay/rust-toolchain@master 99 | with: 100 | toolchain: ${{ matrix.rust }} 101 | 102 | - name: cargo build 103 | run: cargo build 104 | 105 | - name: cargo test 106 | run: cargo test 107 | 108 | build-and-test-simd: 109 | name: CI with nightly on ${{ matrix.os }} [SIMD] 110 | runs-on: ${{ matrix.os }} 111 | strategy: 112 | matrix: 113 | # Had to remove macos-latest, not sure why that is failing. 114 | os: [ubuntu-latest, windows-latest] 115 | 116 | steps: 117 | - uses: actions/checkout@v4 118 | - uses: dtolnay/rust-toolchain@nightly 119 | 120 | - name: cargo build 121 | run: cargo build --features simd 122 | 123 | - name: cargo test 124 | run: cargo test --features simd 125 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | Cargo.lock 3 | proptest-regressions 4 | .idea/ 5 | .vscode/ 6 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | error_on_line_overflow = false 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/). 7 | 8 | ## 0.12.0 - 2025-??-?? 9 | - Replace hand-written x86_64 SIMD with safe and portable [`wide`](https://crates.io/crates/wide) SIMD. [#158](https://github.com/svenstaro/bvh/pull/158) (thanks @finnbear) 10 | 11 | ## 0.11.0 - 2025-02-18 12 | - **Breaking change:** BVH traversal now accepts a `Query: IntersectsAabb` rather than a `Ray`, 13 | allowing points, AABB's, and circles/spheres to be tested, too. Most use-cases involving `Ray` 14 | will continue to compile as-is. If you previously wrote `BvhTraverseIterator`, you'll 15 | need to change it to `BvhTraverseIterator`. [#128](https://github.com/svenstaro/bvh/pull/128) (thanks @finnbear) 16 | - **Breaking change:** `Ray::intersection_slice_for_aabb` now returns `None` instead of `(-1.0, -1.0)` in the case of no 17 | intersection, and `Some((entry, exit))` in the case of intersection. [#133](https://github.com/svenstaro/bvh/pull/133) [#142](https://github.com/svenstaro/bvh/pull/142) (thanks @finnbear) 18 | - `Bvh::nearest_traverse_iterator` and `Bvh::farthest_traverse_iterator` now output correctly ordered results when the children 19 | of an internal node overlap, resulting in them taking more time and requiring heap allocation. 20 | The new iterators `Bvh::nearest_child_traverse_iterator` and `Bvh::farthest_child_traverse_iterator` use the old algorithm. [#133](https://github.com/svenstaro/bvh/pull/139) (thanks @dashedman) 21 | - Fix panic on empty `DistanceTraverseIterator` [#117](https://github.com/svenstaro/bvh/pull/117) (thanks @finnbear) 22 | - Fix center() for very large AABBs [#118](https://github.com/svenstaro/bvh/pull/118) (thanks @finnbear) 23 | - Fix more cases where an empty BVH would panic [#116](https://github.com/svenstaro/bvh/pull/116) (thanks @finnbear) 24 | - Add fuzzing suite [#113](https://github.com/svenstaro/bvh/pull/113) (thanks @finnbear) 25 | - Fix some assertions [#129](https://github.com/svenstaro/bvh/pull/129) (thanks @finnbear) 26 | - Fix traversal in case of single-node BVH [#130](https://github.com/svenstaro/bvh/pull/130) (thanks @finnbear) 27 | - Fix intersection between rays and AABBs that lack depth. [#131](https://github.com/svenstaro/bvh/pull/131) (thanks @finnbear) 28 | - Document the limitations of distance traversal best-effort ordering. [#135](https://github.com/svenstaro/bvh/pull/135) (thanks @finnbear) 29 | - Add `Bvh::nearest_to`, which returns the nearest shape to a point. [#108](https://github.com/svenstaro/bvh/pull/108) (thanks @Azkellas) 30 | - Fix ray-AABB intersection such that a ray in the plane of an AABB face is never 31 | considered intersecting, rather than returning an arbitrary answer in the case of 32 | `Ray::intersects_aabb` or an erroneous answer in the case of 33 | `Ray::intersection_slice_for_aabb`. [#149](https://github.com/svenstaro/bvh/pull/149) (thanks @finnbear) 34 | 35 | ## 0.10.0 - 2024-07-06 36 | - Don't panic when traversing empty BVH [#106](https://github.com/svenstaro/bvh/pull/106) (thanks @finnbear) 37 | - Implement ordered traverse [#98](https://github.com/svenstaro/bvh/pull/98) (thanks @dashedman) 38 | 39 | ## 0.9.0 - 2024-03-16 40 | - Added an API for allowing the BVH build process to be parallelized and provided an implementation using Rayon under the `rayon` feature flag [#103](https://github.com/svenstaro/bvh/pull/103) (thanks @dbenson24) 41 | - Another round of performance optimizations for the Build operation. Single threaded builds are 4-5x faster and large BVHs with parallelization 42 | are able to build 4-5x faster. There was an almost 15x speedup for building a 120k triangle BVH. [#103](https://github.com/svenstaro/bvh/pull/103) (thanks @dbenson24) 43 | - Trait bounds were consolidated to the BHShape trait instead of being spread across various functions, should have no major implications. [#103](https://github.com/svenstaro/bvh/pull/103) (thanks @dbenson24) 44 | 45 | ## 0.8.0 - 2024-02-17 46 | - Added ability to incrementally add/remove nodes from tree [#99](https://github.com/svenstaro/bvh/pull/99) (thanks @dbenson24) 47 | - Move math types from glam over to nalgebra with Generic dimensions > 2 and f32/f64 support [#96](https://github.com/svenstaro/bvh/pull/96) (thanks @marstaik) 48 | - BVH now works with 2d+ dimensions 49 | - BVH now works with f32/f64 50 | - `simd` feature flag, allows for optimizations via explicit SIMD instructions on nightly 51 | - Added comments to previously undocumented functions 52 | - Update Rust edition to 2021 53 | - Major performance improvements on BVH optimization 54 | - Code uppercase acronyms changed to API conventions (BVH -> Bvh) 55 | - Fixed all clippy warnings 56 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bvh" 3 | description = "A fast BVH using SAH" 4 | version = "0.11.0" 5 | edition = "2021" 6 | rust-version = "1.65" # may raise to versions that are at least 1y old. 7 | authors = [ 8 | "Sven-Hendrik Haase ", 9 | "Alexander Dmitriev ", 10 | "Marios Staikopoulos ", 11 | ] 12 | readme = "README.md" 13 | repository = "https://github.com/svenstaro/bvh" 14 | documentation = "https://docs.rs/crate/bvh" 15 | keywords = ["bvh", "bounding", "volume", "sah", "aabb"] 16 | license = "MIT" 17 | 18 | [dependencies] 19 | approx = "0.5" 20 | log = "0.4" 21 | serde = { optional = true, version = "1", features = ["derive"] } 22 | num = "0.4.3" 23 | nalgebra = { version = "0.33.0", features = ["default", "serde-serialize"] } 24 | rayon = { optional = true, version = "1.8.1" } 25 | wide = "0.7.32" 26 | 27 | [dev-dependencies] 28 | proptest = "1.0" 29 | obj-rs = "0.7" 30 | rand = "0.9" 31 | float_eq = "1" 32 | doc-comment = "0.3" 33 | 34 | [features] 35 | default = ["rayon"] 36 | bench = [] 37 | simd = [] 38 | 39 | [profile.release] 40 | lto = true 41 | codegen-units = 1 42 | 43 | [profile.bench] 44 | lto = true 45 | codegen-units = 1 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Sven-Hendrik Haase 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 | # bvh 2 | [![CI](https://github.com/svenstaro/bvh/workflows/CI/badge.svg)](https://github.com/svenstaro/bvh/actions) 3 | [![Docs Status](https://docs.rs/bvh/badge.svg)](https://docs.rs/bvh) 4 | [![codecov](https://codecov.io/gh/svenstaro/bvh/branch/master/graph/badge.svg)](https://codecov.io/gh/svenstaro/bvh) 5 | [![license](http://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/svenstaro/bvh/blob/master/LICENSE) 6 | [![Crates.io](https://img.shields.io/crates/v/bvh.svg)](https://crates.io/crates/bvh) 7 | [![Crates.io](https://img.shields.io/crates/d/bvh.svg)](https://crates.io/crates/bvh) 8 | 9 | **A crate which exports rays, axis-aligned bounding boxes, and binary bounding 10 | volume hierarchies.** 11 | 12 | ## About 13 | 14 | This crate can be used for applications which contain intersection computations of rays 15 | with primitives. For this purpose a binary tree BVH (Bounding Volume Hierarchy) is of great 16 | use if the scene which the ray traverses contains a huge number of primitives. With a BVH the 17 | intersection test complexity is reduced from O(n) to O(log2(n)) at the cost of building 18 | the BVH once in advance. This technique is especially useful in ray/path tracers. For 19 | use in a shader this module also exports a flattening procedure, which allows for 20 | iterative traversal of the BVH. 21 | 22 | It uses `rayon` by default to parallelize building the BVH. 23 | 24 | ## Example 25 | 26 | ```rust 27 | use bvh::aabb::{Aabb, Bounded}; 28 | use bvh::bounding_hierarchy::{BHShape, BoundingHierarchy}; 29 | use bvh::bvh::Bvh; 30 | use bvh::ray::Ray; 31 | use nalgebra::{Point3, Vector3}; 32 | 33 | let origin = Point3::new(0.0,0.0,0.0); 34 | let direction = Vector3::new(1.0,0.0,0.0); 35 | let ray = Ray::new(origin, direction); 36 | 37 | struct Sphere { 38 | position: Point3, 39 | radius: f32, 40 | node_index: usize, 41 | } 42 | 43 | impl Bounded for Sphere { 44 | fn aabb(&self) -> Aabb { 45 | let half_size = Vector3::new(self.radius, self.radius, self.radius); 46 | let min = self.position - half_size; 47 | let max = self.position + half_size; 48 | Aabb::with_bounds(min, max) 49 | } 50 | } 51 | 52 | impl BHShape for Sphere { 53 | fn set_bh_node_index(&mut self, index: usize) { 54 | self.node_index = index; 55 | } 56 | fn bh_node_index(&self) -> usize { 57 | self.node_index 58 | } 59 | } 60 | 61 | let mut spheres = Vec::new(); 62 | for i in 0..1000u32 { 63 | let position = Point3::new(i as f32, i as f32, i as f32); 64 | let radius = (i % 10) as f32 + 1.0; 65 | spheres.push(Sphere { 66 | position: position, 67 | radius: radius, 68 | node_index: 0, 69 | }); 70 | } 71 | 72 | let bvh = Bvh::build_par(&mut spheres); 73 | let hit_sphere_aabbs = bvh.traverse(&ray, &spheres); 74 | ``` 75 | 76 | ## Explicit SIMD 77 | 78 | This crate features some SIMD operations. See [`wide`](https://crates.io/crates/wide) documentation for supported architectures. 79 | While nalgebra provides us with generic SIMD optimization (and it does a great job for the most part) - 80 | some important functions, such as ray-aabb-intersection have been optimized by hand. 81 | 82 | The currently optimized intersections for ray-aabb are: 83 | Type: f32, Dimension: 2,3,4 84 | Type: f64, Dimension: 2,3,4 85 | 86 | To enable these optimziations, you must build with the `nightly` toolchain and enable the `simd` flag. 87 | 88 | ## Optimization 89 | 90 | This crate provides BVH updating, which is also called optimization. With BVH optimization 91 | you can mutate the shapes on which the BVH is built and update the tree accordingly without rebuilding it completely. 92 | This method is very useful when there are only very few changes to a huge scene. When the major part of the scene is static, 93 | it is faster to update the tree, instead of rebuilding it from scratch. 94 | 95 | ### Drawbacks 96 | 97 | First of all, optimizing is not helpful if more than half of the scene is not static. 98 | This is due to how optimizing takes place: 99 | Given a set of indices of all shapes which have changed, the optimize procedure tries to rotate fixed constellations 100 | in search for a better surface area heuristic (SAH) value. This is done recursively from bottom to top while fixing the AABBs 101 | in the inner nodes of the BVH. Which is why it is inefficient to update the BVH in comparison to rebuilding, when a lot 102 | of shapes have moved. 103 | 104 | Another problem with updated BVHs is, that the resulting BVH is not optimal. Assume that the scene is composed of two major 105 | groups separated by a large gap. When one shape moves from one group to another, the updating procedure will not be able to 106 | find a sequence of bottom-up rotations which inserts the shape deeply into the other branch. 107 | 108 | The following benchmarks are run with two different datasets: 109 | * A randomly generated scene with unit sized cubes containing a total of (1200, 12000, and 120000 triangles). 110 | * Sponza, a popular scene for benchmarking graphics engines. 111 | 112 | All benchmarks were taken on a Ryzen 3900x. 113 | 114 | All benchmarks unless otherwise noted were captured with the `simd` feature off. 115 | 116 | ### Intersection via traversal of the list of triangles 117 | 118 | ```C 119 | test testbase::bench_intersect_120k_triangles_list ... bench: 570,717 ns/iter (+/- 21,689) 120 | test testbase::bench_intersect_sponza_list ... bench: 510,683 ns/iter (+/- 9,525) 121 | 122 | // simd enabled 123 | test testbase::bench_intersect_120k_triangles_list ... bench: 566,916 ns/iter (+/- 22,024) 124 | test testbase::bench_intersect_sponza_list ... bench: 518,821 ns/iter (+/- 12,191) 125 | ``` 126 | 127 | This is the most naive approach to intersecting a scene with a ray. It defines the baseline. 128 | 129 | ### Intersection via traversal of the list of triangles with AABB checks 130 | 131 | ```C 132 | test testbase::bench_intersect_120k_triangles_list_aabb ... bench: 687,660 ns/iter (+/- 6,850) 133 | test testbase::bench_intersect_sponza_list_aabb ... bench: 381,037 ns/iter (+/- 1,416) 134 | 135 | // simd enabled 136 | test testbase::bench_intersect_120k_triangles_list_aabb ... bench: 295,810 ns/iter (+/- 3,309) 137 | test testbase::bench_intersect_sponza_list_aabb ... bench: 163,738 ns/iter (+/- 1,822) 138 | ``` 139 | 140 | AABB checks are cheap, compared to triangle-intersection algorithms. Therefore, preceeding AABB checks 141 | increase intersection speed by filtering negative results a lot faster. 142 | 143 | ### Build of a BVH from scratch 144 | 145 | ```C 146 | test flat_bvh::bench::bench_build_1200_triangles_flat_bvh ... bench: 242,357 ns/iter (+/- 1,882) 147 | test flat_bvh::bench::bench_build_12k_triangles_flat_bvh ... bench: 3,681,965 ns/iter (+/- 223,716) 148 | test flat_bvh::bench::bench_build_120k_triangles_flat_bvh ... bench: 46,415,590 ns/iter (+/- 3,226,404) 149 | test bvh::bvh_impl::bench::bench_build_1200_triangles_bvh ... bench: 239,473 ns/iter (+/- 2,456) 150 | test bvh::bvh_impl::bench::bench_build_1200_triangles_bvh_rayon ... bench: 123,387 ns/iter (+/- 9,427) 151 | test bvh::bvh_impl::bench::bench_build_12k_triangles_bvh ... bench: 2,903,150 ns/iter (+/- 54,318) 152 | test bvh::bvh_impl::bench::bench_build_12k_triangles_bvh_rayon ... bench: 1,073,300 ns/iter (+/- 89,530) 153 | test bvh::bvh_impl::bench::bench_build_120k_triangles_bvh ... bench: 37,390,480 ns/iter (+/- 2,789,280) 154 | test bvh::bvh_impl::bench::bench_build_120k_triangles_bvh_rayon ... bench: 8,935,320 ns/iter (+/- 1,780,231) 155 | test bvh::bvh_impl::bench::bench_build_sponza_bvh ... bench: 23,828,070 ns/iter (+/- 1,307,461) 156 | test bvh::bvh_impl::bench::bench_build_sponza_bvh_rayon ... bench: 4,764,530 ns/iter (+/- 950,640) 157 | ``` 158 | 159 | ### Flatten a BVH 160 | 161 | ```C 162 | test flat_bvh::bench::bench_flatten_120k_triangles_bvh ... bench: 9,806,060 ns/iter (+/- 1,771,861) 163 | ``` 164 | 165 | As you can see, building a BVH takes a long time. Building a BVH is only useful if the number of intersections performed on the 166 | scene exceeds the build duration. This is the case in applications such as ray and path tracing, where the minimum 167 | number of intersections is `1280 * 720` for an HD image. 168 | 169 | ### Intersection via BVH traversal 170 | 171 | ```C 172 | test flat_bvh::bench::bench_intersect_1200_triangles_flat_bvh ... bench: 144 ns/iter (+/- 1) 173 | test flat_bvh::bench::bench_intersect_12k_triangles_flat_bvh ... bench: 370 ns/iter (+/- 4) 174 | test flat_bvh::bench::bench_intersect_120k_triangles_flat_bvh ... bench: 866 ns/iter (+/- 16) 175 | test bvh::bvh_impl::bench::bench_intersect_1200_triangles_bvh ... bench: 146 ns/iter (+/- 2) 176 | test bvh::bvh_impl::bench::bench_intersect_12k_triangles_bvh ... bench: 367 ns/iter (+/- 5) 177 | test bvh::bvh_impl::bench::bench_intersect_120k_triangles_bvh ... bench: 853 ns/iter (+/- 12) 178 | test bvh::bvh_impl::bench::bench_intersect_sponza_bvh ... bench: 1,381 ns/iter (+/- 20) 179 | test ray::ray_impl::bench::bench_intersects_aabb ... bench: 4,404 ns/iter (+/- 17) 180 | 181 | // simd enabled 182 | test bvh::bvh_impl::bench::bench_intersect_1200_triangles_bvh ... bench: 121 ns/iter (+/- 2) 183 | test bvh::bvh_impl::bench::bench_intersect_12k_triangles_bvh ... bench: 309 ns/iter (+/- 3) 184 | test bvh::bvh_impl::bench::bench_intersect_120k_triangles_bvh ... bench: 749 ns/iter (+/- 15) 185 | test bvh::bvh_impl::bench::bench_intersect_sponza_bvh ... bench: 1,216 ns/iter (+/- 16) 186 | test ray::ray_impl::bench::bench_intersects_aabb ... bench: 2,447 ns/iter (+/- 18) 187 | ``` 188 | 189 | These performance measurements show that traversing a BVH is much faster than traversing a list. 190 | 191 | ### Optimization 192 | 193 | The benchmarks for how long it takes to update the scene also contain a randomization process which takes some time. 194 | 195 | ```C 196 | test bvh::optimization::bench::bench_update_shapes_bvh_120k_00p ... bench: 1,057,965 ns/iter (+/- 76,098) 197 | test bvh::optimization::bench::bench_update_shapes_bvh_120k_01p ... bench: 2,535,682 ns/iter (+/- 80,231) 198 | test bvh::optimization::bench::bench_update_shapes_bvh_120k_10p ... bench: 18,840,970 ns/iter (+/- 3,432,867) 199 | test bvh::optimization::bench::bench_update_shapes_bvh_120k_50p ... bench: 76,025,200 ns/iter (+/- 3,899,770) 200 | test bvh::optimization::bench::bench_randomize_120k_50p ... bench: 5,361,645 ns/iter (+/- 436,340) 201 | ``` 202 | 203 | This is the place where you have to differentiate between rebuilding the tree from scratch or trying to optimize the old one. 204 | These tests show the impact of moving around a particular percentage of shapes (`10p` => `10%`). 205 | It is important to note that the randomization process here moves triangles around indiscriminately. 206 | This will also lead to cases where the BVH would have to be restructured completely. 207 | 208 | ### Intersection after the optimization 209 | 210 | These intersection tests are grouped by dataset and by the BVH generation method. 211 | * `_after_optimize` uses a BVH which was kept up to date with calls to `optimize`, while 212 | * `_with_rebuild` uses the same triangle data as `_after_optimize`, but constructs a BVH from scratch. 213 | 214 | *120K Triangles* 215 | ```C 216 | test bvh::optimization::bench::bench_intersect_120k_after_update_shapes_00p ... bench: 855 ns/iter (+/- 15) 217 | test bvh::optimization::bench::bench_intersect_120k_after_update_shapes_01p ... bench: 921 ns/iter (+/- 13) 218 | test bvh::optimization::bench::bench_intersect_120k_after_update_shapes_10p ... bench: 2,677 ns/iter (+/- 191) 219 | test bvh::optimization::bench::bench_intersect_120k_after_update_shapes_50p ... bench: 2,992 ns/iter (+/- 103) 220 | 221 | test bvh::optimization::bench::bench_intersect_120k_with_rebuild_00p ... bench: 852 ns/iter (+/- 13) 222 | test bvh::optimization::bench::bench_intersect_120k_with_rebuild_01p ... bench: 918 ns/iter (+/- 13) 223 | test bvh::optimization::bench::bench_intersect_120k_with_rebuild_10p ... bench: 1,920 ns/iter (+/- 266) 224 | test bvh::optimization::bench::bench_intersect_120k_with_rebuild_50p ... bench: 2,075 ns/iter (+/- 318) 225 | ``` 226 | 227 | *Sponza* 228 | ```C 229 | test bvh::optimization::bench::bench_intersect_sponza_after_update_shapes_00p ... bench: 2,023 ns/iter (+/- 52) 230 | test bvh::optimization::bench::bench_intersect_sponza_after_update_shapes_01p ... bench: 3,444 ns/iter (+/- 120) 231 | test bvh::optimization::bench::bench_intersect_sponza_after_update_shapes_10p ... bench: 16,873 ns/iter (+/- 2,123) 232 | test bvh::optimization::bench::bench_intersect_sponza_after_update_shapes_50p ... bench: 9,075 ns/iter (+/- 486) 233 | 234 | test bvh::optimization::bench::bench_intersect_sponza_with_rebuild_00p ... bench: 1,388 ns/iter (+/- 46) 235 | test bvh::optimization::bench::bench_intersect_sponza_with_rebuild_01p ... bench: 1,529 ns/iter (+/- 102) 236 | test bvh::optimization::bench::bench_intersect_sponza_with_rebuild_10p ... bench: 1,920 ns/iter (+/- 120) 237 | test bvh::optimization::bench::bench_intersect_sponza_with_rebuild_50p ... bench: 2,505 ns/iter (+/- 88) 238 | ``` 239 | 240 | This set of tests shows the impact of randomly moving triangles around and producing degenerated trees. 241 | The *120K Triangles* dataset has been updated randomly. The *Sponza* scene was updated using a method 242 | which has a maximum offset distance for shapes. This simulates a more realistic scenario. 243 | 244 | We also see that the *Sponza* scene by itself contains some structures which can be tightly wrapped in a BVH. 245 | By mowing those structures around we destroy the locality of the triangle groups which leads to more branches in the 246 | BVH requiring a check, thus leading to a higher intersection duration. 247 | 248 | ### Running the benchmark suite 249 | 250 | The benchmark suite uses features from the [test crate](https://doc.rust-lang.org/unstable-book/library-features/test.html) and therefore cannot be run on stable rust. 251 | Using a nightly toolchain, run `cargo bench --features bench`. 252 | 253 | ## Testing 254 | 255 | ### Unit tests 256 | 257 | This project aspires to be fully tested, and that starts with a unit test suite. Use 258 | `cargo test` to run all the tests. The tests automatically run in CI, too. Contributors 259 | are expected to add tests for all new functionality. 260 | 261 | ### Proptest 262 | 263 | This project uses [`proptest`](https://altsysrq.github.io/proptest-book/) as a second 264 | line of defense against bugs, allowing random instances of certain tests to be tested. 265 | These tests run alongside unit-tests. 266 | 267 | ### Fuzzer 268 | 269 | This project uses [`cargo fuzz`](https://rust-fuzz.github.io/book/cargo-fuzz.html) as 270 | a third line of defense against bugs, meaning that the `fuzz/` directory was generated 271 | using `cargo fuzz init`. At the moment, there is a single fuzz target, and running 272 | `cargo fuzz run fuzz` will fuzz until an assertion is violated. The fuzzer automatically 273 | runs in CI, too. 274 | 275 | ## Contributing 276 | 277 | We recommend you install the [`just`](https://github.com/casey/just) command runner, to 278 | run [commands](/justfile) including: 279 | - `just lint` - checks style, syntax, etc. 280 | - `just bench` - runs the benchmarks 281 | - `just test` - runs the unit tests and prop tests 282 | - `just fuzz` - runs the fuzzer -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | target: auto 6 | threshold: 5% 7 | -------------------------------------------------------------------------------- /examples/simple.rs: -------------------------------------------------------------------------------- 1 | use bvh::aabb::{Aabb, Bounded}; 2 | use bvh::bounding_hierarchy::BHShape; 3 | use bvh::bvh::Bvh; 4 | use bvh::ray::Ray; 5 | use nalgebra::{Point, SVector}; 6 | 7 | #[derive(Debug)] 8 | struct Sphere { 9 | position: Point, 10 | radius: f32, 11 | node_index: usize, 12 | } 13 | 14 | impl Bounded for Sphere { 15 | fn aabb(&self) -> Aabb { 16 | let half_size = SVector::::new(self.radius, self.radius, self.radius); 17 | let min = self.position - half_size; 18 | let max = self.position + half_size; 19 | Aabb::with_bounds(min, max) 20 | } 21 | } 22 | 23 | impl BHShape for Sphere { 24 | fn set_bh_node_index(&mut self, index: usize) { 25 | self.node_index = index; 26 | } 27 | 28 | fn bh_node_index(&self) -> usize { 29 | self.node_index 30 | } 31 | } 32 | 33 | pub fn main() { 34 | let mut spheres = Vec::new(); 35 | for i in 0..1000000u32 { 36 | let position = Point::::new(i as f32, i as f32, i as f32); 37 | let radius = (i % 10) as f32 + 1.0; 38 | spheres.push(Sphere { 39 | position, 40 | radius, 41 | node_index: 0, 42 | }); 43 | } 44 | let bvh = Bvh::build(&mut spheres); 45 | 46 | let origin = Point::::new(0.0, 0.0, 0.0); 47 | let direction = SVector::::new(1.0, 0.0, 0.0); 48 | let ray = Ray::new(origin, direction); 49 | let hit_sphere_aabbs = bvh.traverse(&ray, &spheres); 50 | dbg!(hit_sphere_aabbs); 51 | } 52 | -------------------------------------------------------------------------------- /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | corpus 3 | artifacts 4 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bvh-fuzz" 3 | version = "0.0.0" 4 | authors = ["Automatically generated"] 5 | publish = false 6 | edition = "2021" 7 | 8 | [package.metadata] 9 | cargo-fuzz = true 10 | 11 | [dependencies] 12 | arbitrary = { version = "1.4.1", features = ["derive"] } 13 | approx = "0.5" 14 | libfuzzer-sys = "0.4" 15 | nalgebra = "0.33" 16 | ordered-float = { version = "4.6.0", features = ["arbitrary"] } 17 | 18 | [dependencies.bvh] 19 | path = ".." 20 | 21 | # Prevent this from interfering with workspaces 22 | [workspace] 23 | members = ["."] 24 | 25 | [[bin]] 26 | name = "fuzz" 27 | path = "fuzz_targets/fuzz.rs" 28 | test = false 29 | doc = false 30 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/fuzz.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | //! This fuzz target is a third line of defense against bugs, after unit tests and prop 4 | //! tests. 5 | //! 6 | //! It starts by generating an arbitrary collection of shapes with which to build a BVH, 7 | //! an arbitrary collection of mutations with which to mutate the BVH, and an arbitrary 8 | //! ray with which to traverse the BVH. There are some constraints imposed on the input, 9 | //! such as numbers needing to be finite (not NaN or infinity). 10 | //! 11 | //! Next, all applicable API's of the BVH are exercised to ensure they don't panic and 12 | //! simple properties are tested. 13 | //! 14 | //! Finally, if there are any mutations left, one is applied, and the API's are tested 15 | //! again. 16 | 17 | use std::cmp::Ordering; 18 | use std::collections::HashSet; 19 | use std::env; 20 | use std::fmt::{self, Debug, Formatter}; 21 | use std::hash::{Hash, Hasher}; 22 | use std::str::FromStr; 23 | 24 | use arbitrary::Arbitrary; 25 | use bvh::aabb::{Aabb, Bounded, IntersectsAabb}; 26 | use bvh::ball::Ball; 27 | use bvh::bounding_hierarchy::{BHShape, BoundingHierarchy}; 28 | use bvh::bvh::Bvh; 29 | use bvh::flat_bvh::FlatBvh; 30 | use bvh::point_query::PointDistance; 31 | use bvh::ray::Ray; 32 | use libfuzzer_sys::fuzz_target; 33 | use nalgebra::{Point, SimdPartialOrd}; 34 | use ordered_float::NotNan; 35 | 36 | type Float = f32; 37 | 38 | /// Coordinate magnitude should not exceed this which prevents 39 | /// certain degenerate cases like infinity, both in inputs 40 | /// and internal computations in the BVH. For `Mode::Grid`, 41 | /// offsets of 1/3 should be representable. 42 | const LIMIT: Float = 5_000.0; 43 | 44 | // The entry point for `cargo fuzz`. 45 | fuzz_target!(|workload: Workload<3>| { 46 | workload.fuzz(); 47 | }); 48 | 49 | /// The input for an arbitrary point, with finite coordinates, 50 | /// each with a magnitude bounded by `LIMIT`. 51 | #[derive(Clone, Arbitrary)] 52 | struct ArbitraryPoint { 53 | coordinates: [NotNan; D], 54 | mode: Mode, 55 | } 56 | 57 | impl Debug for ArbitraryPoint { 58 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 59 | Debug::fmt(&self.point(), f) 60 | } 61 | } 62 | 63 | impl ArbitraryPoint { 64 | /// Produces the corresponding point from the input. 65 | fn point(&self) -> Point { 66 | Point::<_, D>::from_slice(&self.coordinates).map(|f| { 67 | // Float may be large or infinite, but is guaranteed to not be NaN due 68 | // to the `NotNan` wrapper. 69 | // 70 | // Clamp it to a smaller range so that offsets of 1/3 are easily representable, 71 | // which helps `Mode::Grid`. 72 | let ret = f.into_inner().clamp(-LIMIT, LIMIT); 73 | 74 | if self.mode.is_grid() { 75 | // Round each coordinate to an integer, as per `Mode::Grid` docs. 76 | ret.round() 77 | } else { 78 | ret 79 | } 80 | }) 81 | } 82 | } 83 | 84 | /// An arbitrary shape, with `ArbitraryPoint` corners, guaranteed to have an AABB with 85 | /// non-zero volume. 86 | #[derive(Clone, Arbitrary)] 87 | struct ArbitraryShape { 88 | a: ArbitraryPoint, 89 | b: ArbitraryPoint, 90 | /// This will end up being mutated, but initializing it arbitrarily could catch bugs. 91 | bh_node_index: usize, 92 | } 93 | 94 | impl Debug for ArbitraryShape { 95 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 96 | Debug::fmt(&self.aabb(), f) 97 | } 98 | } 99 | 100 | impl Bounded for ArbitraryShape { 101 | fn aabb(&self) -> Aabb { 102 | let mut a = self.a.point(); 103 | let b = self.b.point(); 104 | 105 | // Ensure some separation so volume is non-zero. 106 | a.iter_mut().enumerate().for_each(|(i, a)| { 107 | if *a == b[i] { 108 | *a += 1.0; 109 | } 110 | }); 111 | 112 | let mut aabb = Aabb::with_bounds(a.simd_min(b), a.simd_max(b)); 113 | 114 | if self.mode_is_grid() { 115 | let min = aabb.min; 116 | aabb.max.iter_mut().enumerate().for_each(|(i, f)| { 117 | // Coordinate should already be an integer, because `max` is in grid mode. 118 | // 119 | // Use `max` to ensure the AABB has volume, and add a margin described by `Mode::Grid`. 120 | *f = f.max(min[i]) + 1.0 / 3.0; 121 | }); 122 | aabb.min.iter_mut().for_each(|f| { 123 | // Coordinate should already be an integer, because `min` is in grid mode. 124 | // 125 | // Add a margin described by `Mode::Grid`. 126 | *f -= 1.0 / 3.0; 127 | }); 128 | } 129 | 130 | aabb 131 | } 132 | } 133 | 134 | impl BHShape for ArbitraryShape { 135 | fn bh_node_index(&self) -> usize { 136 | self.bh_node_index 137 | } 138 | 139 | fn set_bh_node_index(&mut self, value: usize) { 140 | self.bh_node_index = value; 141 | } 142 | } 143 | 144 | impl ArbitraryShape { 145 | fn mode_is_grid(&self) -> bool { 146 | self.a.mode.is_grid() && self.b.mode.is_grid() 147 | } 148 | } 149 | 150 | impl PointDistance for ArbitraryShape { 151 | fn distance_squared(&self, query_point: Point) -> Float { 152 | let center = self.aabb().center(); 153 | let offset = query_point - center; 154 | offset.dot(&offset) 155 | } 156 | } 157 | 158 | /// The input for arbitrary ray, starting at an `ArbitraryPoint` and having a precisely 159 | /// normalized direction. 160 | #[derive(Clone, Arbitrary)] 161 | struct ArbitraryRay { 162 | origin: ArbitraryPoint, 163 | destination: ArbitraryPoint, 164 | } 165 | 166 | impl Debug for ArbitraryRay { 167 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 168 | Debug::fmt(&self.ray(), f) 169 | } 170 | } 171 | 172 | impl ArbitraryRay { 173 | fn mode_is_grid(&self) -> bool { 174 | self.origin.mode.is_grid() && self.destination.mode.is_grid() 175 | } 176 | 177 | /// Produces the corresponding ray from the input. 178 | fn ray(&self) -> Ray { 179 | // Note that this eventually gets normalized in `Ray::new`. We don't expect precision issues 180 | // becaues `LIMIT` limits the magnitude of each point's coordinates, so the length of `direction` 181 | // is bounded. 182 | let mut direction = self.destination.point() - self.origin.point(); 183 | 184 | // All components are zero or close to zero, resulting in 185 | // either NaN or a near-zero normalized vector. Replace with a 186 | // different vector so that `Ray::new` is able to normalize. 187 | if (direction.normalize().magnitude() - 1.0).partial_cmp(&0.1) != Some(Ordering::Less) { 188 | direction[0] = 1.0; 189 | } 190 | 191 | let mut ray = Ray::new(self.origin.point(), direction); 192 | 193 | if self.mode_is_grid() { 194 | // Algorithm to find the closest unit-vector parallel to one of the axes. For fuzzing purposes, 195 | // we just want all 6 unit-vectors parallel to an axis (in the 3D case) to be *possible*. 196 | // 197 | // See: https://stackoverflow.com/a/25825980/3064544 198 | let (axis_number, axis_direction) = ray 199 | .direction 200 | .iter() 201 | .enumerate() 202 | .max_by(|(_, a), (_, b)| a.abs().partial_cmp(&b.abs()).unwrap()) 203 | .map(|(i, f)| (i, 1f32.copysign(*f))) 204 | .unwrap(); 205 | 206 | // The resulting ray will be parallel to the axis numbered `axis_number`. 207 | // 208 | // `axis_direction` will be -1 or 1, indicating whether the vector points 209 | // in the negative or positive direction on that axis. 210 | ray.direction.iter_mut().enumerate().for_each(|(i, f)| { 211 | *f = if i == axis_number { 212 | axis_direction 213 | } else { 214 | 0.0 215 | } 216 | }); 217 | } 218 | 219 | ray 220 | } 221 | } 222 | 223 | /// The input for arbitrary ray, starting at an `ArbitraryPoint` and having a precisely 224 | /// normalized direction. 225 | #[derive(Clone, Arbitrary)] 226 | struct ArbitraryBall { 227 | center: ArbitraryPoint, 228 | radius: NotNan, 229 | } 230 | 231 | impl Debug for ArbitraryBall { 232 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 233 | Debug::fmt(&self.ball(), f) 234 | } 235 | } 236 | 237 | impl ArbitraryBall { 238 | fn ball(&self) -> Ball { 239 | Ball { 240 | center: self.center.point(), 241 | radius: self.radius.into_inner().max(0.0), 242 | } 243 | } 244 | } 245 | 246 | /// An arbitrary mutation to apply to the BVH to fuzz BVH optimization. 247 | #[derive(Debug, Arbitrary)] 248 | enum ArbitraryMutation { 249 | Remove(usize), 250 | Add(ArbitraryShape), 251 | } 252 | 253 | #[derive(Copy, Clone, Debug, Arbitrary)] 254 | /// Controls whether the input is modified to help test certain properties. 255 | enum Mode { 256 | /// AABB's may have mostly arbitrary bounds, and ray may have mostly arbitrary 257 | /// origin and direction. 258 | Chaos, 259 | /// AABB's bound integer coordinates with a margin of 1/3. Twice the margin, 2/3, 260 | /// is less than 1, so AABB's can either deeply intersect or will have a gap at 261 | /// least 1/3 wide. Ray must have an origin consisting of integer coordinates and 262 | /// a direction that is parallel to one of the axes. Point must have integer 263 | /// coordinates. 264 | /// 265 | /// In this mode, all types of ray, AABB, and point traversal are expected to 266 | /// yield the same results, except when bugs exist that have yet to be fixed. 267 | Grid, 268 | } 269 | 270 | impl Mode { 271 | fn is_grid(self) -> bool { 272 | matches!(self, Self::Grid) 273 | } 274 | } 275 | 276 | /// The complete set of inputs for a single fuzz iteration. 277 | #[derive(Debug, Arbitrary)] 278 | struct Workload { 279 | shapes: Vec>, 280 | /// Traverse by ray. 281 | ray: ArbitraryRay, 282 | /// Traverse by point and for nearest_to query. 283 | point: ArbitraryPoint, 284 | /// Traverse by AABB. 285 | aabb: ArbitraryShape, 286 | /// Traverse by ball. 287 | ball: ArbitraryBall, 288 | mutations: Vec>, 289 | } 290 | 291 | impl Workload { 292 | /// Compares normal, iterative, and flat-BVH traversal of the same query. 293 | /// 294 | /// Returns the result of normal traversal. 295 | /// 296 | /// # Panics 297 | /// If `assert_agreement` is true, panics if the results differ in an 298 | /// unexpected way. 299 | fn fuzz_traversal<'a>( 300 | &'a self, 301 | bvh: &'a Bvh, 302 | flat_bvh: &'a FlatBvh, 303 | query: &impl IntersectsAabb, 304 | assert_agreement: bool, 305 | ) -> HashSet>> { 306 | let traverse = bvh 307 | .traverse(query, &self.shapes) 308 | .into_iter() 309 | .map(ByPtr) 310 | .collect::>(); 311 | let traverse_iterator = bvh 312 | .traverse_iterator(query, &self.shapes) 313 | .map(ByPtr) 314 | .collect::>(); 315 | let traverse_flat = flat_bvh 316 | .traverse(query, &self.shapes) 317 | .into_iter() 318 | .map(ByPtr) 319 | .collect::>(); 320 | 321 | if assert_agreement { 322 | assert_eq!(traverse, traverse_iterator); 323 | assert_eq!(traverse, traverse_flat); 324 | } else { 325 | // Fails, probably due to normal rounding errors. 326 | } 327 | 328 | traverse 329 | } 330 | 331 | /// Compares normal and flat-BVH nearest_to query. 332 | /// 333 | /// # Panics 334 | /// Panics if the results differ in an unexpected way. 335 | fn fuzz_nearest_to<'a>( 336 | &'a self, 337 | bvh: &'a Bvh, 338 | flat_bvh: &'a FlatBvh, 339 | query_point: Point, 340 | ) { 341 | let best = bvh.nearest_to(query_point, &self.shapes); 342 | let flat_best = flat_bvh.nearest_to(query_point, &self.shapes); 343 | 344 | // Assert we get None if and only if the bvh is empty. 345 | assert_eq!(best.is_none(), self.shapes.is_empty()); 346 | assert_eq!(flat_best.is_none(), self.shapes.is_empty()); 347 | 348 | // Asserting equality between the two results return many false positives 349 | // where two differents shapes give the same distance due to numerical approximations 350 | // e.g. 351 | // left: Some((ByPtr((Aabb { min: [-5000.0, 0.0, 0.0], max: [5000.0, 1.0, 1.0] }, [5000.0, 0.0, 0.0])), 5000.0)) 352 | // right: Some((ByPtr((Aabb { min: [5.418294e-39, -5000.0, 9.1834e-41], max: [5.418294e-39, -5000.0, 9.1834e-41] }, [-5000.0, 9.1834e-41, 0.0])), 5000.0)) 353 | // assert_eq!(best, flat_best); 354 | // as such we only assert for distance equivalence if they are not None 355 | if let Some((best, flat_best)) = best.zip(flat_best) { 356 | const EPSILON: f32 = 0.001; 357 | 358 | approx::assert_abs_diff_eq!(best.1, flat_best.1, epsilon = EPSILON); 359 | 360 | // Brute force search for the ground truth, shapes is not empty since the results are not None. 361 | let mut bruteforce = ( 362 | &self.shapes[0], 363 | self.shapes[0].distance_squared(query_point), 364 | ); 365 | for shape in &self.shapes[1..] { 366 | let distance = shape.distance_squared(query_point); 367 | if distance < bruteforce.1 { 368 | bruteforce = (shape, distance); 369 | } 370 | } 371 | bruteforce = (bruteforce.0, bruteforce.1.sqrt()); 372 | // Again we only test for distances. 373 | approx::assert_abs_diff_eq!(best.1, bruteforce.1, epsilon = EPSILON); 374 | approx::assert_abs_diff_eq!(flat_best.1, bruteforce.1, epsilon = EPSILON); 375 | } 376 | } 377 | 378 | /// Called directly from the `cargo fuzz` entry point. Code in this function is 379 | /// easier for `rust-analyzer`` than code in that macro. 380 | /// 381 | /// The contents are explained in the module-level comment up above. 382 | fn fuzz(mut self) { 383 | let ray = self.ray.ray(); 384 | let aabb = self.aabb.aabb(); 385 | 386 | // Make sure these functions agree and that the latter doesn't return NaN/infinity. 387 | let intersects = ray.intersects_aabb(&aabb); 388 | if let Some((entry, exit)) = ray.intersection_slice_for_aabb(&aabb) { 389 | assert!( 390 | intersects, 391 | "intersection_slice_for_aabb returned Some for non-intersection" 392 | ); 393 | assert!(entry >= 0.0, "entry ({entry}) must be non-negative"); 394 | assert!( 395 | entry <= exit, 396 | "entry ({entry}) must not exceed exit ({exit})" 397 | ); 398 | assert!( 399 | entry.is_finite() && exit.is_finite(), 400 | "neither entry ({entry}) nor exit ({exit}) may be infinite or NaN for AABB's and rays with finite coordinates" 401 | ); 402 | } else { 403 | assert!( 404 | !intersects, 405 | "intersection_slice_for_aabb returned None for intersection" 406 | ); 407 | } 408 | 409 | // If this environment variable is present, only test limited-size BVH's without mutations. 410 | // 411 | // This way, problems detected with more complex BVH's might be able to be minimized 412 | // to simple BVH's. 413 | // 414 | // This is accessible via commands in the project's `justfile`. 415 | if let Some(max_shapes) = env::var("MAX_SHAPES") 416 | .ok() 417 | .map(|s| usize::from_str(&s).expect("MAX_SHAPES")) 418 | { 419 | if !self.mutations.is_empty() || self.shapes.len() > max_shapes { 420 | return; 421 | } 422 | } 423 | 424 | let mut bvh = Bvh::build(&mut self.shapes); 425 | 426 | if self.shapes.len() 427 | + self 428 | .mutations 429 | .iter() 430 | .filter(|m| matches!(m, ArbitraryMutation::Add(_))) 431 | .count() 432 | > 32 433 | { 434 | // Prevent traversal stack overflow by limiting max BVH depth to the traversal 435 | // stack size limit. 436 | return; 437 | } 438 | 439 | loop { 440 | // `self.shapes` are all in grid mode. 441 | let all_shapes_grid = self.shapes.iter().all(|s| s.mode_is_grid()); 442 | 443 | // Under these circumstances, the ray either definitively hits an AABB or it definitively 444 | // doesn't. The lack of near hits and near misses prevents rounding errors that could cause 445 | // different traversal algorithms to disagree. 446 | // 447 | // This relates to the current state of the BVH. It may change after each mutation is applied 448 | // e.g. we could add the first non-grid shape or remove the last non-grid shape. 449 | let assert_ray_traversal_agreement = self.ray.mode_is_grid() && all_shapes_grid; 450 | 451 | // Under these circumstances, the `self.aabb` either definitively intersects with an AABB or 452 | // it definitively doesn't. 453 | // 454 | // Similar meaning to `assert_ray_traversal_agreement`. 455 | let assert_aabb_traversal_agreement = self.aabb.mode_is_grid() && all_shapes_grid; 456 | 457 | // Under these circumstances, the `self.point` is either definitively contained by an AABB or 458 | // definitively not contained. 459 | // 460 | // Similar meaning to `assert_ray_traversal_agreement`. 461 | let assert_point_traversal_agreement = self.point.mode.is_grid() && all_shapes_grid; 462 | 463 | // Check that these don't panic. 464 | bvh.assert_consistent(&self.shapes); 465 | bvh.assert_tight(); 466 | let flat_bvh = bvh.flatten(); 467 | 468 | let traverse_ray = 469 | self.fuzz_traversal(&bvh, &flat_bvh, &ray, assert_ray_traversal_agreement); 470 | self.fuzz_traversal(&bvh, &flat_bvh, &aabb, assert_aabb_traversal_agreement); 471 | self.fuzz_traversal( 472 | &bvh, 473 | &flat_bvh, 474 | &self.point.point(), 475 | assert_point_traversal_agreement, 476 | ); 477 | // Due to sphere geometry, `Mode::Grid` doesn't imply traversals will agree. 478 | self.fuzz_traversal(&bvh, &flat_bvh, &self.ball.ball(), false); 479 | 480 | // In addition to collecting the output into a `HashSet`, make sure each subsequent 481 | // item is further (or equally close) with respect to the ray, thus testing the 482 | // correctness of the iterator. 483 | let mut last_distance = f32::NEG_INFINITY; 484 | let nearest_traverse_iterator = bvh 485 | .nearest_traverse_iterator(&ray, &self.shapes) 486 | .inspect(|shape| { 487 | // Use `.0` to get the entry distance, which the iterator is based on. 488 | let distance = ray.intersection_slice_for_aabb(&shape.aabb()) 489 | .expect("nearest_traverse_iterator returned aabb for which no intersection slice exists") 490 | .0; 491 | // Until https://github.com/svenstaro/bvh/issues/140 is completely fixed, the `distance` 492 | // and the relationship between `distance` and `last_distance` only necessarily holds if 493 | // ray-AABB intersection is definitive, so don't panic if `!assert_ray_traversal_agreement`. 494 | assert!( 495 | !assert_ray_traversal_agreement || distance >= last_distance, 496 | "nearest_traverse_iterator returned shapes out of order: {distance} {last_distance}" 497 | ); 498 | last_distance = distance; 499 | }) 500 | .map(ByPtr) 501 | .collect::>(); 502 | 503 | // Same as the above, but in the opposite direction. 504 | let mut last_distance = f32::INFINITY; 505 | let farthest_traverse_iterator = bvh 506 | .farthest_traverse_iterator(&ray, &self.shapes) 507 | .inspect(|shape| { 508 | // Use `.1` to get the exit distance, which the iterator is based on. 509 | let distance = ray.intersection_slice_for_aabb(&shape.aabb()) 510 | .expect("farthest_traverse_iterator returned aabb for which no intersection slice exists") 511 | .1; 512 | assert!( 513 | !assert_ray_traversal_agreement || distance <= last_distance, 514 | "farthest_traverse_iterator returned shapes out of order: {distance} {last_distance}" 515 | ); 516 | last_distance = distance; 517 | }) 518 | .map(ByPtr) 519 | .collect::>(); 520 | 521 | // We do not assert the order, because we didn't check if any children overlap, the 522 | // condition under which order isn't guaranteed. TODO: check this. 523 | let nearest_child_traverse_iterator = bvh 524 | .nearest_child_traverse_iterator(&ray, &self.shapes) 525 | .map(ByPtr) 526 | .collect::>(); 527 | 528 | // Same as the above, but in the opposite direction. 529 | let farthest_child_traverse_iterator = bvh 530 | .farthest_child_traverse_iterator(&ray, &self.shapes) 531 | .map(ByPtr) 532 | .collect::>(); 533 | 534 | if assert_ray_traversal_agreement { 535 | assert_eq!(traverse_ray, nearest_traverse_iterator); 536 | assert_eq!(traverse_ray, nearest_child_traverse_iterator); 537 | } else { 538 | // Fails, probably due to normal rounding errors. 539 | } 540 | 541 | // Since the algorithm is similar, these should agree regardless of mode. 542 | assert_eq!(nearest_traverse_iterator, farthest_traverse_iterator); 543 | assert_eq!( 544 | nearest_child_traverse_iterator, 545 | farthest_child_traverse_iterator 546 | ); 547 | 548 | // Check that these don't panic and return the same value. 549 | self.fuzz_nearest_to(&bvh, &flat_bvh, self.point.point()); 550 | 551 | if let Some(mutation) = self.mutations.pop() { 552 | match mutation { 553 | ArbitraryMutation::Add(shape) => { 554 | let new_shape_index = self.shapes.len(); 555 | self.shapes.push(shape); 556 | bvh.add_shape(&mut self.shapes, new_shape_index); 557 | } 558 | ArbitraryMutation::Remove(index) => { 559 | if index < self.shapes.len() { 560 | bvh.remove_shape(&mut self.shapes, index, true); 561 | self.shapes.pop().unwrap(); 562 | } 563 | } 564 | } 565 | } else { 566 | break; 567 | } 568 | } 569 | } 570 | } 571 | 572 | /// Makes it easy to compare sets of intersected shapes. Comparing by 573 | /// value would be ambiguous if multiple shapes shared the same AABB. 574 | #[derive(Debug)] 575 | struct ByPtr<'a, T>(&'a T); 576 | 577 | impl PartialEq for ByPtr<'_, T> { 578 | fn eq(&self, other: &Self) -> bool { 579 | std::ptr::eq(self.0, other.0) 580 | } 581 | } 582 | 583 | impl Eq for ByPtr<'_, T> {} 584 | 585 | impl Hash for ByPtr<'_, T> { 586 | fn hash(&self, state: &mut H) { 587 | state.write_usize(self.0 as *const _ as usize); 588 | } 589 | } 590 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | # quick sanity check, useful to run before committing 2 | lint: fmt check clippy 3 | 4 | # format src and fuzzer 5 | fmt: 6 | cargo fmt 7 | cargo fmt --manifest-path fuzz/Cargo.toml 8 | 9 | # type-check src and the fuzzer 10 | # 11 | # Type-checking with and without features helps catch, 12 | # for example, an import statement that is `#[cfg(_)]`'d 13 | # to a different feature than the code that relies on it. 14 | check: 15 | cargo check 16 | cargo check --all-features 17 | cargo check --manifest-path fuzz/Cargo.toml 18 | 19 | # run clippy on src and fuzzer 20 | clippy: 21 | cargo clippy 22 | cargo clippy --manifest-path fuzz/Cargo.toml 23 | 24 | # test default features 25 | test: 26 | cargo test 27 | 28 | # run benchmarks (without SIMD) 29 | bench: 30 | cargo bench --features bench 31 | 32 | # run benchmarks (with SIMD) 33 | bench_simd: 34 | cargo bench --features bench,simd 35 | 36 | # fuzz the library 37 | fuzz: 38 | cargo fuzz run fuzz 39 | 40 | # fuzz the library, but stick to BVH's with at most 3 shapes 41 | # that do not undergo mutations 42 | fuzz-max-three-shapes: 43 | MAX_SHAPES=3 cargo fuzz run fuzz 44 | 45 | # fuzz the library, but stick to BVH's with at most 5 shapes 46 | # that do not undergo mutations 47 | fuzz-max-five-shapes: 48 | MAX_SHAPES=5 cargo fuzz run fuzz 49 | 50 | # find the current MSRV, if it is greater than or equal to 51 | # the rust-version in Cargo.toml 52 | find-msrv: 53 | cargo msrv find --ignore-lockfile 54 | 55 | # verify the rust-version in Cargo.toml is compatible 56 | verify-msrv: 57 | cargo msrv verify --ignore-lockfile -------------------------------------------------------------------------------- /release.toml: -------------------------------------------------------------------------------- 1 | sign-commit = true 2 | sign-tag = true 3 | # Get rid of the default cargo-release "chore: " prefix in messages as we don't 4 | # use semantic commits in this repository. 5 | pre-release-commit-message = "Release {{crate_name}} version {{version}}" 6 | tag-message = "Release {{crate_name}} version {{version}}" 7 | -------------------------------------------------------------------------------- /src/aabb/intersection.rs: -------------------------------------------------------------------------------- 1 | use nalgebra::Point; 2 | 3 | use crate::{aabb::Aabb, bounding_hierarchy::BHValue}; 4 | 5 | /// A trait implemented by things that may or may not intersect an AABB and, by extension, 6 | /// things that can be used to traverse a BVH. 7 | pub trait IntersectsAabb { 8 | /// Returns whether this object intersects an [`Aabb`]. For the purpose of intersection, 9 | /// the [`Aabb`] is a solid with area/volume. As a result, intersecting an [`Aabb`] 10 | /// implies intersecting any [`Aabb`] that contains that [`Aabb`]. 11 | /// 12 | /// # Examples 13 | /// ``` 14 | /// use bvh::aabb::{Aabb, IntersectsAabb}; 15 | /// use nalgebra::Point3; 16 | /// 17 | /// struct XyPlane; 18 | /// 19 | /// impl IntersectsAabb for XyPlane { 20 | /// fn intersects_aabb(&self, aabb: &Aabb) -> bool { 21 | /// aabb.min[2] <= 0.0 && aabb.max[2] >= 0.0 22 | /// } 23 | /// } 24 | /// 25 | /// let xy_plane = XyPlane; 26 | /// let aabb = Aabb::with_bounds(Point3::new(-1.0,-1.0,-1.0), Point3::new(1.0,1.0,1.0)); 27 | /// assert!(xy_plane.intersects_aabb(&aabb)); 28 | /// ``` 29 | /// 30 | /// [`Aabb`]: struct.Aabb.html 31 | /// 32 | fn intersects_aabb(&self, aabb: &Aabb) -> bool; 33 | } 34 | 35 | impl IntersectsAabb for Aabb { 36 | fn intersects_aabb(&self, aabb: &Aabb) -> bool { 37 | self.intersects_aabb(aabb) 38 | } 39 | } 40 | 41 | impl IntersectsAabb for Point { 42 | fn intersects_aabb(&self, aabb: &Aabb) -> bool { 43 | aabb.contains(self) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/aabb/mod.rs: -------------------------------------------------------------------------------- 1 | //! Axis Aligned Bounding Boxes. 2 | 3 | mod aabb_impl; 4 | mod intersection; 5 | 6 | pub use aabb_impl::*; 7 | pub use intersection::*; 8 | -------------------------------------------------------------------------------- /src/ball.rs: -------------------------------------------------------------------------------- 1 | //! Balls, including circles and spheres. 2 | 3 | use crate::{ 4 | aabb::{Aabb, IntersectsAabb}, 5 | bounding_hierarchy::BHValue, 6 | }; 7 | use nalgebra::Point; 8 | 9 | /// A circle, which can be used for traversing 2D BVHs. 10 | pub type Circle = Ball; 11 | 12 | /// A sphere, which can be used for traversing 3D BVHs. 13 | pub type Sphere = Ball; 14 | 15 | /// In 2D, a circle. In 3D, a sphere. This can be used for traversing BVHs. 16 | #[derive(Debug, Clone, Copy)] 17 | pub struct Ball { 18 | /// The center of the ball. 19 | pub center: Point, 20 | /// The radius of the ball. 21 | pub radius: T, 22 | } 23 | 24 | impl Ball { 25 | /// Creates a [`Ball`] with the given `center` and `radius`. 26 | /// 27 | /// # Panics 28 | /// Panics, in debug mode, if the radius is negative. 29 | /// 30 | /// # Examples 31 | /// ``` 32 | /// use bvh::ball::Ball; 33 | /// use nalgebra::Point3; 34 | /// 35 | /// let ball = Ball::new(Point3::new(1.0, 1.0, 1.0), 1.0); 36 | /// assert_eq!(ball.center, Point3::new(1.0, 1.0, 1.0)); 37 | /// assert_eq!(ball.radius, 1.0) 38 | /// ``` 39 | /// 40 | /// [`Ball`]: struct.Ball.html 41 | pub fn new(center: Point, radius: T) -> Self { 42 | debug_assert!(radius >= T::from_f32(0.0).unwrap()); 43 | Self { center, radius } 44 | } 45 | 46 | /// Returns true if this [`Ball`] contains the [`Point`]. 47 | /// 48 | /// # Examples 49 | /// ``` 50 | /// use bvh::ball::Ball; 51 | /// use nalgebra::Point3; 52 | /// 53 | /// let ball = Ball::new(Point3::new(1.0, 1.0, 1.0), 1.0); 54 | /// let point = Point3::new(1.25, 1.25, 1.25); 55 | /// 56 | /// assert!(ball.contains(&point)); 57 | /// ``` 58 | /// 59 | /// [`Ball`]: struct.Ball.html 60 | pub fn contains(&self, point: &Point) -> bool { 61 | let mut distance_squared = T::zero(); 62 | for i in 0..D { 63 | distance_squared += (point[i] - self.center[i]).powi(2); 64 | } 65 | // Squaring the RHS is faster than computing the square root of the LHS. 66 | distance_squared <= self.radius.powi(2) 67 | } 68 | 69 | /// Returns true if this [`Ball`] intersects the [`Aabb`]. 70 | /// 71 | /// # Examples 72 | /// ``` 73 | /// use bvh::{aabb::Aabb, ball::Ball}; 74 | /// use nalgebra::Point3; 75 | /// 76 | /// let ball = Ball::new(Point3::new(1.0, 1.0, 1.0), 1.0); 77 | /// let aabb = Aabb::with_bounds(Point3::new(1.25, 1.25, 1.25), Point3::new(3.0, 3.0, 3.0)); 78 | /// 79 | /// assert!(ball.intersects_aabb(&aabb)); 80 | /// ``` 81 | /// 82 | /// [`Aabb`]: struct.Aabb.html 83 | /// [`Ball`]: struct.Ball.html 84 | pub fn intersects_aabb(&self, aabb: &Aabb) -> bool { 85 | // https://gamemath.com/book/geomtests.html#intersection_sphere_aabb 86 | // Finding the point in/on the AABB that is closest to the ball's center or, 87 | // more specifically, find the squared distance between that point and the 88 | // ball's center. 89 | let mut distance_squared = T::zero(); 90 | for i in 0..D { 91 | let closest_on_aabb = self.center[i].clamp(aabb.min[i], aabb.max[i]); 92 | distance_squared += (closest_on_aabb - self.center[i]).powi(2); 93 | } 94 | 95 | // Then test if that point is in/on the ball. Squaring the RHS is faster than computing 96 | // the square root of the LHS. 97 | distance_squared <= self.radius.powi(2) 98 | } 99 | } 100 | 101 | impl IntersectsAabb for Ball { 102 | fn intersects_aabb(&self, aabb: &Aabb) -> bool { 103 | self.intersects_aabb(aabb) 104 | } 105 | } 106 | 107 | #[cfg(test)] 108 | mod tests { 109 | use super::Ball; 110 | use crate::testbase::TPoint3; 111 | 112 | #[test] 113 | fn ball_contains() { 114 | let ball = Ball::new(TPoint3::new(3.0, 4.0, 5.0), 1.5); 115 | 116 | // Ball should contain its own center. 117 | assert!(ball.contains(&ball.center)); 118 | 119 | // Test some manually-selected points. 120 | let just_inside = TPoint3::new(3.04605, 3.23758, 3.81607); 121 | let just_outside = TPoint3::new(3.06066, 3.15813, 3.70917); 122 | assert!(ball.contains(&just_inside)); 123 | assert!(!ball.contains(&just_outside)); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/bounding_hierarchy.rs: -------------------------------------------------------------------------------- 1 | //! This module defines the [`BoundingHierarchy`] trait. 2 | 3 | use nalgebra::{ 4 | ClosedAddAssign, ClosedDivAssign, ClosedMulAssign, ClosedSubAssign, Point, Scalar, 5 | SimdPartialOrd, 6 | }; 7 | use num::{Float, FromPrimitive, Signed}; 8 | 9 | use crate::aabb::{Bounded, IntersectsAabb}; 10 | #[cfg(feature = "rayon")] 11 | use crate::bvh::rayon_executor; 12 | use crate::bvh::BvhNodeBuildArgs; 13 | use crate::point_query::PointDistance; 14 | 15 | /// Encapsulates the required traits for the value type used in the Bvh. 16 | pub trait BHValue: 17 | Scalar 18 | + Copy 19 | + FromPrimitive 20 | + ClosedSubAssign 21 | + ClosedAddAssign 22 | + SimdPartialOrd 23 | + ClosedMulAssign 24 | + ClosedDivAssign 25 | + Float 26 | + Signed 27 | + std::fmt::Display 28 | { 29 | } 30 | 31 | impl BHValue for T where 32 | T: Scalar 33 | + Copy 34 | + FromPrimitive 35 | + ClosedSubAssign 36 | + ClosedAddAssign 37 | + SimdPartialOrd 38 | + ClosedMulAssign 39 | + ClosedDivAssign 40 | + Float 41 | + Signed 42 | + std::fmt::Display 43 | { 44 | } 45 | 46 | /// Describes a shape as referenced by a [`BoundingHierarchy`] leaf node. 47 | /// Knows the index of the node in the [`BoundingHierarchy`] it is in. 48 | /// 49 | /// [`BoundingHierarchy`]: struct.BoundingHierarchy.html 50 | /// 51 | pub trait BHShape: Bounded { 52 | /// Sets the index of the referenced [`BoundingHierarchy`] node. 53 | /// 54 | /// [`BoundingHierarchy`]: struct.BoundingHierarchy.html 55 | /// 56 | fn set_bh_node_index(&mut self, _: usize); 57 | 58 | /// Gets the index of the referenced [`BoundingHierarchy`] node. 59 | /// 60 | /// [`BoundingHierarchy`]: struct.BoundingHierarchy.html 61 | /// 62 | fn bh_node_index(&self) -> usize; 63 | } 64 | 65 | impl> BHShape for &mut S { 66 | fn set_bh_node_index(&mut self, idx: usize) { 67 | S::set_bh_node_index(self, idx) 68 | } 69 | 70 | fn bh_node_index(&self) -> usize { 71 | S::bh_node_index(self) 72 | } 73 | } 74 | 75 | impl> BHShape for Box { 76 | fn set_bh_node_index(&mut self, idx: usize) { 77 | S::set_bh_node_index(self, idx) 78 | } 79 | 80 | fn bh_node_index(&self) -> usize { 81 | S::bh_node_index(self) 82 | } 83 | } 84 | 85 | /// This trait defines an acceleration structure with space partitioning. 86 | /// This structure is used to efficiently compute ray-scene intersections. 87 | pub trait BoundingHierarchy { 88 | /// Creates a new [`BoundingHierarchy`] from the `shapes` slice. 89 | /// 90 | /// # Examples 91 | /// 92 | /// ``` 93 | /// use bvh::aabb::{Aabb, Bounded}; 94 | /// use bvh::bounding_hierarchy::BoundingHierarchy; 95 | /// use nalgebra::{Point3, Vector3}; 96 | /// # use bvh::bounding_hierarchy::BHShape; 97 | /// # pub struct UnitBox { 98 | /// # pub id: i32, 99 | /// # pub pos: Point3, 100 | /// # node_index: usize, 101 | /// # } 102 | /// # 103 | /// # impl UnitBox { 104 | /// # pub fn new(id: i32, pos: Point3) -> UnitBox { 105 | /// # UnitBox { 106 | /// # id: id, 107 | /// # pos: pos, 108 | /// # node_index: 0, 109 | /// # } 110 | /// # } 111 | /// # } 112 | /// # 113 | /// # impl Bounded for UnitBox { 114 | /// # fn aabb(&self) -> Aabb { 115 | /// # let min = self.pos + Vector3::new(-0.5, -0.5, -0.5); 116 | /// # let max = self.pos + Vector3::new(0.5, 0.5, 0.5); 117 | /// # Aabb::with_bounds(min, max) 118 | /// # } 119 | /// # } 120 | /// # 121 | /// # impl BHShape for UnitBox { 122 | /// # fn set_bh_node_index(&mut self, index: usize) { 123 | /// # self.node_index = index; 124 | /// # } 125 | /// # 126 | /// # fn bh_node_index(&self) -> usize { 127 | /// # self.node_index 128 | /// # } 129 | /// # } 130 | /// # 131 | /// # fn create_bhshapes() -> Vec { 132 | /// # let mut shapes = Vec::new(); 133 | /// # for i in 0..1000 { 134 | /// # let position = Point3::new(i as f32, i as f32, i as f32); 135 | /// # shapes.push(UnitBox::new(i, position)); 136 | /// # } 137 | /// # shapes 138 | /// # } 139 | /// 140 | /// let mut shapes = create_bhshapes(); 141 | /// // Construct a normal `Bvh`. 142 | /// { 143 | /// use bvh::bvh::Bvh; 144 | /// let bvh = Bvh::build(&mut shapes); 145 | /// } 146 | /// 147 | /// // Or construct a `FlatBvh`. 148 | /// { 149 | /// use bvh::flat_bvh::FlatBvh; 150 | /// let bvh = FlatBvh::build(&mut shapes); 151 | /// } 152 | /// ``` 153 | /// 154 | /// [`BoundingHierarchy`]: trait.BoundingHierarchy.html 155 | /// 156 | fn build>(shapes: &mut [Shape]) -> Self; 157 | 158 | /// Builds the bvh with a custom executor. 159 | fn build_with_executor< 160 | Shape: BHShape, 161 | Executor: FnMut(BvhNodeBuildArgs<'_, Shape, T, D>, BvhNodeBuildArgs<'_, Shape, T, D>), 162 | >( 163 | shapes: &mut [Shape], 164 | executor: Executor, 165 | ) -> Self; 166 | 167 | /// Builds the bvh with a rayon based executor. 168 | #[cfg(feature = "rayon")] 169 | fn build_par + Send>(shapes: &mut [Shape]) -> Self 170 | where 171 | T: Send, 172 | Self: Sized, 173 | { 174 | Self::build_with_executor(shapes, rayon_executor) 175 | } 176 | 177 | /// Traverses the [`BoundingHierarchy`]. 178 | /// Returns a subset of `shapes`, in which the [`Aabb`]s of the elements were hit by `ray`. 179 | /// 180 | /// # Examples 181 | /// 182 | /// ``` 183 | /// use bvh::aabb::{Aabb, Bounded}; 184 | /// use bvh::bounding_hierarchy::BoundingHierarchy; 185 | /// use bvh::bvh::Bvh; 186 | /// use nalgebra::{Point3, Vector3}; 187 | /// use bvh::ray::Ray; 188 | /// # use bvh::bounding_hierarchy::BHShape; 189 | /// # pub struct UnitBox { 190 | /// # pub id: i32, 191 | /// # pub pos: Point3, 192 | /// # node_index: usize, 193 | /// # } 194 | /// # 195 | /// # impl UnitBox { 196 | /// # pub fn new(id: i32, pos: Point3) -> UnitBox { 197 | /// # UnitBox { 198 | /// # id: id, 199 | /// # pos: pos, 200 | /// # node_index: 0, 201 | /// # } 202 | /// # } 203 | /// # } 204 | /// # 205 | /// # impl Bounded for UnitBox { 206 | /// # fn aabb(&self) -> Aabb { 207 | /// # let min = self.pos + Vector3::new(-0.5, -0.5, -0.5); 208 | /// # let max = self.pos + Vector3::new(0.5, 0.5, 0.5); 209 | /// # Aabb::with_bounds(min, max) 210 | /// # } 211 | /// # } 212 | /// # 213 | /// # impl BHShape for UnitBox { 214 | /// # fn set_bh_node_index(&mut self, index: usize) { 215 | /// # self.node_index = index; 216 | /// # } 217 | /// # 218 | /// # fn bh_node_index(&self) -> usize { 219 | /// # self.node_index 220 | /// # } 221 | /// # } 222 | /// # 223 | /// # fn create_bvh() -> (Bvh, Vec) { 224 | /// # let mut shapes = Vec::new(); 225 | /// # for i in 0..1000 { 226 | /// # let position = Point3::new(i as f32, i as f32, i as f32); 227 | /// # shapes.push(UnitBox::new(i, position)); 228 | /// # } 229 | /// # let bvh = Bvh::build(&mut shapes); 230 | /// # (bvh, shapes) 231 | /// # } 232 | /// 233 | /// let (bvh, shapes) = create_bvh(); 234 | /// 235 | /// let origin = Point3::new(0.0, 0.0, 0.0); 236 | /// let direction = Vector3::new(1.0, 0.0, 0.0); 237 | /// let ray = Ray::new(origin, direction); 238 | /// let hit_shapes = bvh.traverse(&ray, &shapes); 239 | /// ``` 240 | /// 241 | /// [`BoundingHierarchy`]: trait.BoundingHierarchy.html 242 | /// [`Aabb`]: ../aabb/struct.Aabb.html 243 | /// 244 | fn traverse<'a, Query: IntersectsAabb, Shape: BHShape>( 245 | &'a self, 246 | query: &Query, 247 | shapes: &'a [Shape], 248 | ) -> Vec<&'a Shape>; 249 | 250 | /// Traverses the [`BoundingHierarchy`]. 251 | /// Returns the shape closest to the query point. 252 | /// 253 | /// # Examples 254 | /// 255 | /// ``` 256 | /// use bvh::aabb::{Aabb, Bounded}; 257 | /// use bvh::bounding_hierarchy::BoundingHierarchy; 258 | /// use bvh::bvh::Bvh; 259 | /// use bvh::point_query::PointDistance; 260 | /// use nalgebra::{Point3, Vector3}; 261 | /// use bvh::ray::Ray; 262 | /// # use bvh::bounding_hierarchy::BHShape; 263 | /// # pub struct UnitBox { 264 | /// # pub id: i32, 265 | /// # pub pos: Point3, 266 | /// # node_index: usize, 267 | /// # } 268 | /// # 269 | /// # impl UnitBox { 270 | /// # pub fn new(id: i32, pos: Point3) -> UnitBox { 271 | /// # UnitBox { 272 | /// # id: id, 273 | /// # pos: pos, 274 | /// # node_index: 0, 275 | /// # } 276 | /// # } 277 | /// # } 278 | /// # 279 | /// # impl Bounded for UnitBox { 280 | /// # fn aabb(&self) -> Aabb { 281 | /// # let min = self.pos + Vector3::new(-0.5, -0.5, -0.5); 282 | /// # let max = self.pos + Vector3::new(0.5, 0.5, 0.5); 283 | /// # Aabb::with_bounds(min, max) 284 | /// # } 285 | /// # } 286 | /// # 287 | /// # impl BHShape for UnitBox { 288 | /// # fn set_bh_node_index(&mut self, index: usize) { 289 | /// # self.node_index = index; 290 | /// # } 291 | /// # 292 | /// # fn bh_node_index(&self) -> usize { 293 | /// # self.node_index 294 | /// # } 295 | /// # } 296 | /// # 297 | /// # impl PointDistance for UnitBox { 298 | /// # fn distance_squared(&self, query_point: Point3) -> f32 { 299 | /// # self.aabb().min_distance_squared(query_point) 300 | /// # } 301 | /// } 302 | /// # fn create_bvh() -> (Bvh, Vec) { 303 | /// # let mut shapes = Vec::new(); 304 | /// # for i in 0..1000 { 305 | /// # let position = Point3::new(i as f32, i as f32, i as f32); 306 | /// # shapes.push(UnitBox::new(i, position)); 307 | /// # } 308 | /// # let bvh = Bvh::build(&mut shapes); 309 | /// # (bvh, shapes) 310 | /// # } 311 | /// 312 | /// let (bvh, shapes) = create_bvh(); 313 | /// 314 | /// let query = Point3::new(5.0, 5.7, 5.3); 315 | /// let nearest_shape = bvh.nearest_to(query, &shapes); 316 | /// 317 | /// # assert_eq!(nearest_shape.unwrap().0.id, 5); 318 | /// ``` 319 | /// 320 | /// [`BoundingHierarchy`]: trait.BoundingHierarchy.html 321 | /// [`Aabb`]: ../aabb/struct.Aabb.html 322 | /// 323 | fn nearest_to<'a, Shape: BHShape + PointDistance>( 324 | &'a self, 325 | query: Point, 326 | shapes: &'a [Shape], 327 | ) -> Option<(&'a Shape, T)>; 328 | 329 | /// Prints the [`BoundingHierarchy`] in a tree-like visualization. 330 | /// 331 | /// [`BoundingHierarchy`]: trait.BoundingHierarchy.html 332 | /// 333 | fn pretty_print(&self) {} 334 | } 335 | 336 | impl> BoundingHierarchy for Box { 337 | fn build>(shapes: &mut [Shape]) -> Self { 338 | Box::new(H::build(shapes)) 339 | } 340 | 341 | fn traverse<'a, Query: IntersectsAabb, Shape: BHShape>( 342 | &'a self, 343 | query: &Query, 344 | shapes: &'a [Shape], 345 | ) -> Vec<&'a Shape> { 346 | H::traverse(self, query, shapes) 347 | } 348 | 349 | fn nearest_to<'a, Shape: BHShape + PointDistance>( 350 | &'a self, 351 | query: Point, 352 | shapes: &'a [Shape], 353 | ) -> Option<(&'a Shape, T)> { 354 | H::nearest_to(self, query, shapes) 355 | } 356 | 357 | fn build_with_executor< 358 | Shape: BHShape, 359 | Executor: FnMut(BvhNodeBuildArgs<'_, Shape, T, D>, BvhNodeBuildArgs<'_, Shape, T, D>), 360 | >( 361 | shapes: &mut [Shape], 362 | executor: Executor, 363 | ) -> Self { 364 | Box::new(H::build_with_executor(shapes, executor)) 365 | } 366 | } 367 | -------------------------------------------------------------------------------- /src/bvh/bvh_node.rs: -------------------------------------------------------------------------------- 1 | use crate::aabb::{Aabb, Bounded, IntersectsAabb}; 2 | use crate::bounding_hierarchy::{BHShape, BHValue}; 3 | use crate::point_query::PointDistance; 4 | use crate::utils::{joint_aabb_of_shapes, Bucket}; 5 | use std::cell::RefCell; 6 | use std::marker::PhantomData; 7 | use std::mem::MaybeUninit; 8 | 9 | const NUM_BUCKETS: usize = 6; 10 | 11 | thread_local! { 12 | /// Thread local for the buckets used while building to reduce allocations during build 13 | static BUCKETS: RefCell<[Vec; NUM_BUCKETS]> = RefCell::new(Default::default()); 14 | } 15 | 16 | /// The [`BvhNode`] enum that describes a node in a [`Bvh`]. 17 | /// It's either a leaf node and references a shape (by holding its index) 18 | /// or a regular node that has two child nodes. 19 | /// The non-leaf node stores the [`Aabb`]s of its children. 20 | /// 21 | /// [`Aabb`]: ../aabb/struct.Aabb.html 22 | /// [`Bvh`]: struct.Bvh.html 23 | /// [`Bvh`]: struct.BvhNode.html 24 | /// 25 | #[derive(Debug, Copy, Clone)] 26 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 27 | pub enum BvhNode { 28 | /// Leaf node. 29 | Leaf { 30 | /// The node's parent. 31 | parent_index: usize, 32 | 33 | /// The shape contained in this leaf. 34 | shape_index: usize, 35 | }, 36 | /// Inner node. 37 | Node { 38 | /// The node's parent. 39 | parent_index: usize, 40 | 41 | /// Index of the left subtree's root node. 42 | child_l_index: usize, 43 | 44 | /// The convex hull of the shapes' [`Aabb`]'s in child_l. 45 | child_l_aabb: Aabb, 46 | 47 | /// Index of the right subtree's root node. 48 | child_r_index: usize, 49 | 50 | /// The convex hull of the shapes' [`Aabb`]'s in child_r. 51 | child_r_aabb: Aabb, 52 | }, 53 | } 54 | 55 | impl BvhNode { 56 | /// Builds a [`BvhNode`] recursively using SAH partitioning. 57 | /// 58 | /// [`BvhNode`]: enum.BvhNode.html 59 | /// 60 | pub fn build>(args: BvhNodeBuildArgs) { 61 | if let Some((left, right)) = Self::prep_build(args) { 62 | Self::build(left); 63 | Self::build(right); 64 | } 65 | } 66 | 67 | /// Builds a [`BvhNode`] with a custom executor function using SAH partitioning. 68 | /// 69 | /// [`BvhNode`]: enum.BvhNode.html 70 | /// 71 | pub fn build_with_executor>( 72 | args: BvhNodeBuildArgs, 73 | mut executor: impl FnMut(BvhNodeBuildArgs, BvhNodeBuildArgs), 74 | ) { 75 | if let Some((left, right)) = Self::prep_build(args) { 76 | executor(left, right); 77 | } 78 | } 79 | 80 | /// Builds a single [`BvhNode`] in the [`Bvh`] heirarchy. 81 | /// Returns the arguments needed to call this function and build the future 82 | /// children of this node. If you do not call this function using the arguments 83 | /// returned then the Bvh will not be completely built. 84 | /// 85 | /// [`BvhNode`]: enum.BvhNode.html 86 | /// 87 | fn prep_build>( 88 | args: BvhNodeBuildArgs, 89 | ) -> Option<(BvhNodeBuildArgs, BvhNodeBuildArgs)> { 90 | let BvhNodeBuildArgs { 91 | shapes, 92 | indices, 93 | nodes, 94 | parent_index, 95 | depth, 96 | node_index, 97 | aabb_bounds, 98 | centroid_bounds, 99 | } = args; 100 | // If there is only one element left, don't split anymore 101 | if indices.len() == 1 { 102 | let shape_index = indices[0]; 103 | nodes[0].write(BvhNode::Leaf { 104 | parent_index, 105 | shape_index: shape_index.0, 106 | }); 107 | // Let the shape know the index of the node that represents it. 108 | shapes.set_node_index(shape_index, node_index); 109 | return None; 110 | } 111 | 112 | // Find the axis along which the shapes are spread the most. 113 | let split_axis = centroid_bounds.largest_axis(); 114 | let split_axis_size = centroid_bounds.max[split_axis] - centroid_bounds.min[split_axis]; 115 | 116 | // The following `if` partitions `indices` for recursively calling `Bvh::build`. 117 | let ( 118 | (child_l_aabb, child_l_centroid, child_l_indices), 119 | (child_r_aabb, child_r_centroid, child_r_indices), 120 | ) = if split_axis_size < T::epsilon() { 121 | // In this branch the shapes lie too close together so that splitting them in a 122 | // sensible way is not possible. Instead we just split the list of shapes in half. 123 | let (child_l_indices, child_r_indices) = indices.split_at_mut(indices.len() / 2); 124 | let (child_l_aabb, child_l_centroid) = joint_aabb_of_shapes(child_l_indices, shapes); 125 | let (child_r_aabb, child_r_centroid) = joint_aabb_of_shapes(child_r_indices, shapes); 126 | 127 | ( 128 | (child_l_aabb, child_l_centroid, child_l_indices), 129 | (child_r_aabb, child_r_centroid, child_r_indices), 130 | ) 131 | } else { 132 | BvhNode::build_buckets( 133 | shapes, 134 | indices, 135 | split_axis, 136 | split_axis_size, 137 | ¢roid_bounds, 138 | &aabb_bounds, 139 | ) 140 | }; 141 | 142 | // Since the Bvh is a full binary tree, we can calculate exactly how many indices each side of the tree 143 | // will occupy with the formula 2 * (num_shapes) - 1. 144 | let left_len = child_l_indices.len() * 2 - 1; 145 | // Place the left child right after the current node. 146 | let child_l_index = node_index + 1; 147 | // Place the right child after all of the nodes in the tree under the left child. 148 | let child_r_index = child_l_index + left_len; 149 | 150 | // Construct the actual data structure and replace the dummy node. 151 | nodes[0].write(BvhNode::Node { 152 | parent_index, 153 | child_l_aabb, 154 | child_l_index, 155 | child_r_aabb, 156 | child_r_index, 157 | }); 158 | 159 | // Remove the current node from the future build steps. 160 | let next_nodes = &mut nodes[1..]; 161 | // Split the remaining nodes in the slice based on how many nodes we will need for each subtree. 162 | let (l_nodes, r_nodes) = next_nodes.split_at_mut(left_len); 163 | 164 | Some(( 165 | BvhNodeBuildArgs { 166 | shapes, 167 | indices: child_l_indices, 168 | nodes: l_nodes, 169 | parent_index: node_index, 170 | depth: depth + 1, 171 | node_index: child_l_index, 172 | aabb_bounds: child_l_aabb, 173 | centroid_bounds: child_l_centroid, 174 | }, 175 | BvhNodeBuildArgs { 176 | shapes, 177 | indices: child_r_indices, 178 | nodes: r_nodes, 179 | parent_index: node_index, 180 | depth: depth + 1, 181 | node_index: child_r_index, 182 | aabb_bounds: child_r_aabb, 183 | centroid_bounds: child_r_centroid, 184 | }, 185 | )) 186 | } 187 | 188 | #[allow(clippy::type_complexity)] 189 | fn build_buckets<'a, S: BHShape>( 190 | shapes: &Shapes, 191 | indices: &'a mut [ShapeIndex], 192 | split_axis: usize, 193 | split_axis_size: T, 194 | centroid_bounds: &Aabb, 195 | aabb_bounds: &Aabb, 196 | ) -> ( 197 | (Aabb, Aabb, &'a mut [ShapeIndex]), 198 | (Aabb, Aabb, &'a mut [ShapeIndex]), 199 | ) { 200 | // Use fixed size arrays of `Bucket`s, and thread local index assignment vectors. 201 | BUCKETS.with(move |buckets_ref| { 202 | let bucket_assignments = &mut *buckets_ref.borrow_mut(); 203 | let mut buckets = [Bucket::empty(); NUM_BUCKETS]; 204 | buckets.fill(Bucket::empty()); 205 | for b in bucket_assignments.iter_mut() { 206 | b.clear(); 207 | } 208 | 209 | // In this branch the `split_axis_size` is large enough to perform meaningful splits. 210 | // We start by assigning the shapes to `Bucket`s. 211 | for idx in indices.iter() { 212 | let shape = shapes.get(*idx); 213 | let shape_aabb = shape.aabb(); 214 | let shape_center = shape_aabb.center(); 215 | 216 | // Get the relative position of the shape centroid `[0.0..1.0]`. 217 | let bucket_num_relative = 218 | (shape_center[split_axis] - centroid_bounds.min[split_axis]) / split_axis_size; 219 | 220 | // Convert that to the actual `Bucket` number. 221 | let bucket_num = (bucket_num_relative 222 | * (T::from(NUM_BUCKETS).unwrap() - T::from(0.01).unwrap())) 223 | .to_usize() 224 | .unwrap(); 225 | 226 | // Extend the selected `Bucket` and add the index to the actual bucket. 227 | buckets[bucket_num].add_aabb(&shape_aabb); 228 | bucket_assignments[bucket_num].push(*idx); 229 | } 230 | 231 | // Compute the costs for each configuration and select the best configuration. 232 | let mut min_bucket = 0; 233 | let mut min_cost = T::infinity(); 234 | let mut child_l_aabb = Aabb::empty(); 235 | let mut child_l_centroid = Aabb::empty(); 236 | let mut child_r_aabb = Aabb::empty(); 237 | let mut child_r_centroid = Aabb::empty(); 238 | for i in 0..(NUM_BUCKETS - 1) { 239 | let (l_buckets, r_buckets) = buckets.split_at(i + 1); 240 | let child_l = l_buckets.iter().fold(Bucket::empty(), Bucket::join_bucket); 241 | let child_r = r_buckets.iter().fold(Bucket::empty(), Bucket::join_bucket); 242 | 243 | let cost = (T::from(child_l.size).unwrap() * child_l.aabb.surface_area() 244 | + T::from(child_r.size).unwrap() * child_r.aabb.surface_area()) 245 | / aabb_bounds.surface_area(); 246 | if cost < min_cost { 247 | min_bucket = i; 248 | min_cost = cost; 249 | child_l_aabb = child_l.aabb; 250 | child_l_centroid = child_l.centroid; 251 | child_r_aabb = child_r.aabb; 252 | child_r_centroid = child_r.centroid; 253 | } 254 | } 255 | // Join together all index buckets. 256 | // split input indices, loop over assignments and assign 257 | let (l_assignments, r_assignments) = bucket_assignments.split_at_mut(min_bucket + 1); 258 | 259 | let mut l_count = 0; 260 | for group in l_assignments.iter() { 261 | l_count += group.len(); 262 | } 263 | 264 | let (child_l_indices, child_r_indices) = indices.split_at_mut(l_count); 265 | 266 | for (l_i, shape_index) in l_assignments 267 | .iter() 268 | .flat_map(|group| group.iter()) 269 | .enumerate() 270 | { 271 | child_l_indices[l_i] = *shape_index; 272 | } 273 | for (r_i, shape_index) in r_assignments 274 | .iter() 275 | .flat_map(|group| group.iter()) 276 | .enumerate() 277 | { 278 | child_r_indices[r_i] = *shape_index; 279 | } 280 | 281 | ( 282 | (child_l_aabb, child_l_centroid, child_l_indices), 283 | (child_r_aabb, child_r_centroid, child_r_indices), 284 | ) 285 | }) 286 | } 287 | 288 | /// Traverses the [`Bvh`] recursively and returns all shapes whose [`Aabb`] is 289 | /// intersected by the given [`Ray`]. 290 | /// 291 | /// [`Aabb`]: ../aabb/struct.Aabb.html 292 | /// [`Bvh`]: struct.Bvh.html 293 | /// [`Ray`]: ../ray/struct.Ray.html 294 | /// 295 | pub(crate) fn traverse_recursive, Shape: Bounded>( 296 | nodes: &[BvhNode], 297 | node_index: usize, 298 | shapes: &[Shape], 299 | query: &Query, 300 | indices: &mut Vec, 301 | ) { 302 | match nodes[node_index] { 303 | BvhNode::Node { 304 | ref child_l_aabb, 305 | child_l_index, 306 | ref child_r_aabb, 307 | child_r_index, 308 | .. 309 | } => { 310 | if query.intersects_aabb(child_l_aabb) { 311 | BvhNode::traverse_recursive(nodes, child_l_index, shapes, query, indices); 312 | } 313 | if query.intersects_aabb(child_r_aabb) { 314 | BvhNode::traverse_recursive(nodes, child_r_index, shapes, query, indices); 315 | } 316 | } 317 | BvhNode::Leaf { shape_index, .. } => { 318 | // Either we got to a non-root node recursively, in which case the caller 319 | // checked our AABB, or we are processing the root node, in which case we 320 | // need to check the AABB. 321 | if node_index != 0 || query.intersects_aabb(&shapes[shape_index].aabb()) { 322 | indices.push(shape_index); 323 | } 324 | } 325 | } 326 | } 327 | 328 | /// Traverses the [`Bvh`] recursively and updates the given `best_candidate` with 329 | /// the nearest shape found so far. 330 | /// 331 | /// [`Aabb`]: ../aabb/struct.Aabb.html 332 | /// [`Bvh`]: struct.Bvh.html 333 | /// 334 | pub(crate) fn nearest_to_recursive<'a, Shape: Bounded + PointDistance>( 335 | nodes: &[BvhNode], 336 | node_index: usize, 337 | query: nalgebra::Point, 338 | shapes: &'a [Shape], 339 | best_candidate: &mut Option<(&'a Shape, T)>, 340 | ) { 341 | match nodes[node_index] { 342 | BvhNode::Node { 343 | ref child_l_aabb, 344 | child_l_index, 345 | ref child_r_aabb, 346 | child_r_index, 347 | .. 348 | } => { 349 | // Compute the min dist for both children 350 | let mut children = [ 351 | (child_l_index, child_l_aabb.min_distance_squared(query)), 352 | (child_r_index, child_r_aabb.min_distance_squared(query)), 353 | ]; 354 | 355 | // Sort children to go to the best candidate first and have a better chance of pruning 356 | if children[0].1 > children[1].1 { 357 | children.swap(0, 1); 358 | } 359 | 360 | // Traverse children 361 | for (index, child_dist) in children { 362 | // Node might contain a better shape: check it. 363 | // TODO: to be replaced by `Option::is_none_or` after 2025-10 for 1 year MSRV. 364 | #[allow(clippy::unnecessary_map_or)] 365 | if best_candidate.map_or(true, |(_, best_dist)| child_dist < best_dist) { 366 | Self::nearest_to_recursive(nodes, index, query, shapes, best_candidate); 367 | } 368 | } 369 | } 370 | BvhNode::Leaf { shape_index, .. } => { 371 | // This leaf might contain a better shape: check it directly with its exact distance (squared). 372 | let dist = shapes[shape_index].distance_squared(query); 373 | 374 | // TODO: to be replaced by `Option::is_none_or` after 2025-10 for 1 year MSRV. 375 | #[allow(clippy::unnecessary_map_or)] 376 | if best_candidate.map_or(true, |(_, best_dist)| dist < best_dist) { 377 | *best_candidate = Some((&shapes[shape_index], dist)); 378 | } 379 | } 380 | } 381 | } 382 | } 383 | 384 | /// Shapes holds a mutable ptr to the slice of Shapes passed in to build. It is accessed only through a ShapeIndex. 385 | /// These are a set of unique indices into Shapes that are generated at the start of the build process. Because they 386 | /// are all unique they guarantee that access into Shapes is safe to do in parallel. 387 | pub struct Shapes<'a, S> { 388 | ptr: *mut S, 389 | len: usize, 390 | marker: PhantomData<&'a S>, 391 | } 392 | 393 | #[repr(transparent)] 394 | #[derive(Copy, Clone, Debug)] 395 | /// ShapeIndex represents an entry into the Shapes struct. It is used to help ensure that we are only accessing Shapes 396 | /// with unique indices. 397 | pub(crate) struct ShapeIndex(pub usize); 398 | 399 | impl Shapes<'_, S> { 400 | /// Calls set_bh_node_index on the Shape found at shape_index. 401 | pub(crate) fn set_node_index( 402 | &self, 403 | shape_index: ShapeIndex, 404 | node_index: usize, 405 | ) where 406 | S: BHShape, 407 | { 408 | assert!(shape_index.0 < self.len); 409 | unsafe { 410 | self.ptr 411 | .add(shape_index.0) 412 | .as_mut() 413 | .unwrap() 414 | .set_bh_node_index(node_index); 415 | } 416 | } 417 | 418 | /// Returns a reference to the Shape found at shape_index. 419 | pub(crate) fn get(&self, shape_index: ShapeIndex) -> &S 420 | where 421 | S: BHShape, 422 | { 423 | assert!(shape_index.0 < self.len); 424 | unsafe { self.ptr.add(shape_index.0).as_ref().unwrap() } 425 | } 426 | 427 | /// Creates a [`Shapes`] that inherits its lifetime from the slice. 428 | pub(crate) fn from_slice(slice: &mut [S]) -> Shapes 429 | where 430 | S: BHShape, 431 | { 432 | Shapes { 433 | ptr: slice.as_mut_ptr(), 434 | len: slice.len(), 435 | marker: PhantomData, 436 | } 437 | } 438 | } 439 | 440 | unsafe impl Send for Shapes<'_, S> {} 441 | unsafe impl Sync for Shapes<'_, S> {} 442 | 443 | /// Holds the arguments for calling build. 444 | pub struct BvhNodeBuildArgs<'a, S, T: BHValue, const D: usize> { 445 | pub(crate) shapes: &'a Shapes<'a, S>, 446 | pub(crate) indices: &'a mut [ShapeIndex], 447 | pub(crate) nodes: &'a mut [MaybeUninit>], 448 | pub(crate) parent_index: usize, 449 | pub(crate) depth: u32, 450 | pub(crate) node_index: usize, 451 | pub(crate) aabb_bounds: Aabb, 452 | pub(crate) centroid_bounds: Aabb, 453 | } 454 | 455 | impl BvhNodeBuildArgs<'_, S, T, D> { 456 | /// Finish building this portion of the bvh. 457 | pub fn build(self) 458 | where 459 | S: BHShape, 460 | { 461 | BvhNode::::build(self) 462 | } 463 | 464 | /// Finish building this portion of the bvh using a custom executor. 465 | pub fn build_with_executor( 466 | self, 467 | executor: impl FnMut(BvhNodeBuildArgs<'_, S, T, D>, BvhNodeBuildArgs<'_, S, T, D>), 468 | ) where 469 | S: BHShape, 470 | { 471 | BvhNode::::build_with_executor(self, executor) 472 | } 473 | 474 | /// Returns the number of nodes that are part of this build. 475 | pub fn node_count(&self) -> usize { 476 | self.indices.len() 477 | } 478 | 479 | /// Returns the current depth in the Bvh. 480 | pub fn depth(&self) -> usize { 481 | self.depth as usize 482 | } 483 | } 484 | 485 | impl PartialEq for BvhNode { 486 | // TODO Consider also comparing [`Aabbs`] 487 | fn eq(&self, other: &BvhNode) -> bool { 488 | match (self, other) { 489 | ( 490 | &BvhNode::Node { 491 | parent_index: self_parent_index, 492 | child_l_index: self_child_l_index, 493 | child_r_index: self_child_r_index, 494 | .. 495 | }, 496 | &BvhNode::Node { 497 | parent_index: other_parent_index, 498 | child_l_index: other_child_l_index, 499 | child_r_index: other_child_r_index, 500 | .. 501 | }, 502 | ) => { 503 | self_parent_index == other_parent_index 504 | && self_child_l_index == other_child_l_index 505 | && self_child_r_index == other_child_r_index 506 | } 507 | ( 508 | &BvhNode::Leaf { 509 | parent_index: self_parent_index, 510 | shape_index: self_shape_index, 511 | }, 512 | &BvhNode::Leaf { 513 | parent_index: other_parent_index, 514 | shape_index: other_shape_index, 515 | }, 516 | ) => self_parent_index == other_parent_index && self_shape_index == other_shape_index, 517 | _ => false, 518 | } 519 | } 520 | } 521 | 522 | impl BvhNode { 523 | /// Returns the index of the parent node. 524 | pub fn parent(&self) -> usize { 525 | match *self { 526 | BvhNode::Node { parent_index, .. } | BvhNode::Leaf { parent_index, .. } => parent_index, 527 | } 528 | } 529 | 530 | /// Returns a mutable reference to the parent node index. 531 | pub fn parent_mut(&mut self) -> &mut usize { 532 | match *self { 533 | BvhNode::Node { 534 | ref mut parent_index, 535 | .. 536 | } 537 | | BvhNode::Leaf { 538 | ref mut parent_index, 539 | .. 540 | } => parent_index, 541 | } 542 | } 543 | 544 | /// Returns the index of the left child node. 545 | pub fn child_l(&self) -> usize { 546 | match *self { 547 | BvhNode::Node { child_l_index, .. } => child_l_index, 548 | _ => panic!("Tried to get the left child of a leaf node."), 549 | } 550 | } 551 | 552 | /// Returns the index of the left child node. 553 | pub fn child_l_mut(&mut self) -> &mut usize { 554 | match *self { 555 | BvhNode::Node { 556 | ref mut child_l_index, 557 | .. 558 | } => child_l_index, 559 | _ => panic!("Tried to get the left child of a leaf node."), 560 | } 561 | } 562 | 563 | /// Returns the `Aabb` of the right child node. 564 | pub fn child_l_aabb(&self) -> Aabb { 565 | match *self { 566 | BvhNode::Node { child_l_aabb, .. } => child_l_aabb, 567 | _ => panic!(), 568 | } 569 | } 570 | 571 | /// Returns a mutable reference to the [`Aabb`] of the left child node. 572 | pub fn child_l_aabb_mut(&mut self) -> &mut Aabb { 573 | match *self { 574 | BvhNode::Node { 575 | ref mut child_l_aabb, 576 | .. 577 | } => child_l_aabb, 578 | _ => panic!("Tried to get the left child's `Aabb` of a leaf node."), 579 | } 580 | } 581 | 582 | /// Returns the index of the right child node. 583 | pub fn child_r(&self) -> usize { 584 | match *self { 585 | BvhNode::Node { child_r_index, .. } => child_r_index, 586 | _ => panic!("Tried to get the right child of a leaf node."), 587 | } 588 | } 589 | 590 | /// Returns the index of the right child node. 591 | pub fn child_r_mut(&mut self) -> &mut usize { 592 | match *self { 593 | BvhNode::Node { 594 | ref mut child_r_index, 595 | .. 596 | } => child_r_index, 597 | _ => panic!("Tried to get the right child of a leaf node."), 598 | } 599 | } 600 | 601 | /// Returns the [`Aabb`] of the right child node. 602 | pub fn child_r_aabb(&self) -> Aabb { 603 | match *self { 604 | BvhNode::Node { child_r_aabb, .. } => child_r_aabb, 605 | _ => panic!(), 606 | } 607 | } 608 | 609 | /// Returns a mutable reference to the [`Aabb`] of the right child node. 610 | pub fn child_r_aabb_mut(&mut self) -> &mut Aabb { 611 | match *self { 612 | BvhNode::Node { 613 | ref mut child_r_aabb, 614 | .. 615 | } => child_r_aabb, 616 | _ => panic!("Tried to get the right child's `Aabb` of a leaf node."), 617 | } 618 | } 619 | 620 | /// Gets the [`Aabb`] for a [`BvhNode`]. 621 | /// Returns the shape's [`Aabb`] for leaves, and the joined [`Aabb`] of 622 | /// the two children's [`Aabb`]'s for non-leaves. 623 | pub fn get_node_aabb>(&self, shapes: &[Shape]) -> Aabb { 624 | match *self { 625 | BvhNode::Node { 626 | child_l_aabb, 627 | child_r_aabb, 628 | .. 629 | } => child_l_aabb.join(&child_r_aabb), 630 | BvhNode::Leaf { shape_index, .. } => shapes[shape_index].aabb(), 631 | } 632 | } 633 | 634 | /// Returns the index of the shape contained within the node if is a leaf, 635 | /// or [`None`] if it is an interior node. 636 | pub fn shape_index(&self) -> Option { 637 | match *self { 638 | BvhNode::Leaf { shape_index, .. } => Some(shape_index), 639 | _ => None, 640 | } 641 | } 642 | 643 | /// Returns the index of the shape contained within the node if is a leaf, 644 | /// or `None` if it is an interior node. 645 | pub fn shape_index_mut(&mut self) -> Option<&mut usize> { 646 | match *self { 647 | BvhNode::Leaf { 648 | ref mut shape_index, 649 | .. 650 | } => Some(shape_index), 651 | _ => None, 652 | } 653 | } 654 | 655 | /// Returns `true` if the node is a leaf node. 656 | pub fn is_leaf(&self) -> bool { 657 | matches!(self, Self::Leaf { .. }) 658 | } 659 | } 660 | -------------------------------------------------------------------------------- /src/bvh/child_distance_traverse.rs: -------------------------------------------------------------------------------- 1 | use crate::aabb::Bounded; 2 | use crate::bounding_hierarchy::BHValue; 3 | use crate::bvh::{iter_initially_has_node, Bvh, BvhNode}; 4 | use crate::ray::Ray; 5 | 6 | #[derive(Debug, Clone, Copy)] 7 | enum RestChild { 8 | Left, 9 | Right, 10 | None, 11 | } 12 | 13 | /// Iterator to traverse a [`Bvh`] in order from nearest [`Aabb`] to farthest for [`Ray`], 14 | /// or vice versa, without memory allocations. 15 | /// 16 | /// This is a best-effort iterator that orders interior parent nodes before ordering child 17 | /// nodes, so the output is not necessarily perfectly sorted. 18 | pub struct ChildDistanceTraverseIterator< 19 | 'bvh, 20 | 'shape, 21 | T: BHValue, 22 | const D: usize, 23 | Shape: Bounded, 24 | const ASCENDING: bool, 25 | > { 26 | /// Reference to the Bvh to traverse 27 | bvh: &'bvh Bvh, 28 | /// Reference to the input ray 29 | ray: &'bvh Ray, 30 | /// Reference to the input shapes array 31 | shapes: &'shape [Shape], 32 | /// Traversal stack. 4 billion items seems enough? 33 | stack: [(usize, RestChild); 32], 34 | /// Position of the iterator in bvh.nodes 35 | node_index: usize, 36 | /// Size of the traversal stack 37 | stack_size: usize, 38 | /// Whether or not we have a valid node (or leaf) 39 | has_node: bool, 40 | } 41 | 42 | impl<'bvh, 'shape, T, const D: usize, Shape: Bounded, const ASCENDING: bool> 43 | ChildDistanceTraverseIterator<'bvh, 'shape, T, D, Shape, ASCENDING> 44 | where 45 | T: BHValue, 46 | { 47 | /// Creates a new [`DistanceTraverseIterator`] 48 | pub fn new(bvh: &'bvh Bvh, ray: &'bvh Ray, shapes: &'shape [Shape]) -> Self { 49 | ChildDistanceTraverseIterator { 50 | bvh, 51 | ray, 52 | shapes, 53 | stack: [(0, RestChild::None); 32], 54 | node_index: 0, 55 | stack_size: 0, 56 | has_node: iter_initially_has_node(bvh, ray, shapes), 57 | } 58 | } 59 | 60 | /// Return `true` if stack is empty. 61 | fn is_stack_empty(&self) -> bool { 62 | self.stack_size == 0 63 | } 64 | 65 | /// Push node onto stack. 66 | /// 67 | /// # Panics 68 | /// 69 | /// Panics if `stack[stack_size]` is out of bounds. 70 | fn stack_push(&mut self, nodes: (usize, RestChild)) { 71 | self.stack[self.stack_size] = nodes; 72 | self.stack_size += 1; 73 | } 74 | 75 | /// Pop the stack and return the node. 76 | /// 77 | /// # Panics 78 | /// 79 | /// Panics if `stack_size` underflows. 80 | fn stack_pop(&mut self) -> (usize, RestChild) { 81 | self.stack_size -= 1; 82 | self.stack[self.stack_size] 83 | } 84 | 85 | /// Attempt to move to the child node that is closest/furthest to the ray, depending on `ASCENDING`, 86 | /// relative to the current node. 87 | /// If it is a leaf, or the [`Ray`] does not intersect the any node [`Aabb`], `has_node` will become `false`. 88 | fn move_first_priority(&mut self) -> (usize, RestChild) { 89 | let current_node_index = self.node_index; 90 | match self.bvh.nodes[current_node_index] { 91 | BvhNode::Node { 92 | child_l_index, 93 | ref child_l_aabb, 94 | child_r_index, 95 | ref child_r_aabb, 96 | .. 97 | } => { 98 | let left_dist = self 99 | .ray 100 | .intersection_slice_for_aabb(child_l_aabb) 101 | .map(|(d, _)| d); 102 | let right_dist = self 103 | .ray 104 | .intersection_slice_for_aabb(child_r_aabb) 105 | .map(|(d, _)| d); 106 | 107 | match (left_dist, right_dist) { 108 | (None, None) => { 109 | // no intersections with any children 110 | self.has_node = false; 111 | (current_node_index, RestChild::None) 112 | } 113 | (Some(_), None) => { 114 | // has intersection only with left child 115 | self.has_node = true; 116 | self.node_index = child_l_index; 117 | let rest_child = RestChild::None; 118 | (current_node_index, rest_child) 119 | } 120 | (None, Some(_)) => { 121 | // has intersection only with right child 122 | self.has_node = true; 123 | self.node_index = child_r_index; 124 | let rest_child = RestChild::None; 125 | (current_node_index, rest_child) 126 | } 127 | (Some(left_dist), Some(right_dist)) => { 128 | if (left_dist > right_dist) ^ !ASCENDING { 129 | // right is higher priority than left 130 | self.has_node = true; 131 | self.node_index = child_r_index; 132 | let rest_child = RestChild::Left; 133 | (current_node_index, rest_child) 134 | } else { 135 | // left is higher priority than right 136 | self.has_node = true; 137 | self.node_index = child_l_index; 138 | let rest_child = RestChild::Right; 139 | (current_node_index, rest_child) 140 | } 141 | } 142 | } 143 | } 144 | BvhNode::Leaf { .. } => { 145 | self.has_node = false; 146 | (current_node_index, RestChild::None) 147 | } 148 | } 149 | } 150 | 151 | /// Attempt to move to the rest not visited child of the current node. 152 | /// If it is a leaf, or the [`Ray`] does not intersect the node [`Aabb`], `has_node` will become `false`. 153 | fn move_rest(&mut self, rest_child: RestChild) { 154 | match self.bvh.nodes[self.node_index] { 155 | BvhNode::Node { 156 | child_r_index, 157 | child_l_index, 158 | .. 159 | } => match rest_child { 160 | RestChild::Left => { 161 | self.node_index = child_l_index; 162 | self.has_node = true; 163 | } 164 | RestChild::Right => { 165 | self.node_index = child_r_index; 166 | self.has_node = true; 167 | } 168 | RestChild::None => { 169 | self.has_node = false; 170 | } 171 | }, 172 | BvhNode::Leaf { .. } => { 173 | self.has_node = false; 174 | } 175 | } 176 | } 177 | } 178 | 179 | impl<'shape, T, const D: usize, Shape: Bounded, const ASCENDING: bool> Iterator 180 | for ChildDistanceTraverseIterator<'_, 'shape, T, D, Shape, ASCENDING> 181 | where 182 | T: BHValue, 183 | { 184 | type Item = &'shape Shape; 185 | 186 | fn next(&mut self) -> Option<&'shape Shape> { 187 | loop { 188 | if self.is_stack_empty() && !self.has_node { 189 | // Completed traversal. 190 | break; 191 | } 192 | 193 | if self.has_node { 194 | // If we have any node, attempt to move to its nearest child. 195 | let stack_info = self.move_first_priority(); 196 | // Save current node and farthest child 197 | self.stack_push(stack_info) 198 | } else { 199 | // Go back up the stack and see if a node or leaf was pushed. 200 | let (node_index, rest_child) = self.stack_pop(); 201 | self.node_index = node_index; 202 | match self.bvh.nodes[self.node_index] { 203 | BvhNode::Node { .. } => { 204 | // If a node was pushed, now move to `unvisited` rest child, next in order. 205 | self.move_rest(rest_child); 206 | } 207 | BvhNode::Leaf { shape_index, .. } => { 208 | // We previously pushed a leaf node. This is the "visit" of the in-order traverse. 209 | // Next time we call `next()` we try to pop the stack again. 210 | return Some(&self.shapes[shape_index]); 211 | } 212 | } 213 | } 214 | } 215 | None 216 | } 217 | } 218 | 219 | #[cfg(test)] 220 | mod tests { 221 | use crate::aabb::Bounded; 222 | use crate::bvh::Bvh; 223 | use crate::ray::Ray; 224 | use crate::testbase::{generate_aligned_boxes, TBvh3, TPoint3, TVector3, UnitBox}; 225 | use std::collections::HashSet; 226 | 227 | /// Create a `Bvh` for a fixed scene structure. 228 | pub fn build_some_bvh() -> (Vec, TBvh3) { 229 | let mut boxes = generate_aligned_boxes(); 230 | let bvh = Bvh::build(&mut boxes); 231 | (boxes, bvh) 232 | } 233 | 234 | /// Create a `Bvh` for an empty scene structure. 235 | pub fn build_empty_bvh() -> (Vec, TBvh3) { 236 | let mut boxes = Vec::new(); 237 | let bvh = Bvh::build(&mut boxes); 238 | (boxes, bvh) 239 | } 240 | 241 | fn traverse_distance_and_verify_order( 242 | ray_origin: TPoint3, 243 | ray_direction: TVector3, 244 | all_shapes: &[UnitBox], 245 | bvh: &TBvh3, 246 | expected_shapes: &HashSet, 247 | ) { 248 | let ray = Ray::new(ray_origin, ray_direction); 249 | let near_it = bvh.nearest_traverse_iterator(&ray, all_shapes); 250 | let far_it = bvh.farthest_traverse_iterator(&ray, all_shapes); 251 | 252 | let mut count = 0; 253 | let mut prev_near_dist = -1.0; 254 | let mut prev_far_dist = f32::INFINITY; 255 | 256 | for (near_shape, far_shape) in near_it.zip(far_it) { 257 | let (intersect_near_dist, _) = 258 | ray.intersection_slice_for_aabb(&near_shape.aabb()).unwrap(); 259 | let (intersect_far_dist, _) = 260 | ray.intersection_slice_for_aabb(&far_shape.aabb()).unwrap(); 261 | 262 | assert!(expected_shapes.contains(&near_shape.id)); 263 | assert!(expected_shapes.contains(&far_shape.id)); 264 | assert!(prev_near_dist <= intersect_near_dist); 265 | assert!(prev_far_dist >= intersect_far_dist); 266 | 267 | count += 1; 268 | prev_near_dist = intersect_near_dist; 269 | prev_far_dist = intersect_far_dist; 270 | } 271 | assert_eq!(expected_shapes.len(), count); 272 | } 273 | 274 | /// Perform some fixed intersection tests on BH structures. 275 | pub fn traverse_some_bvh() { 276 | let (all_shapes, bvh) = build_some_bvh(); 277 | 278 | { 279 | // Define a ray which traverses the x-axis from afar. 280 | let origin = TPoint3::new(-1000.0, 0.0, 0.0); 281 | let direction = TVector3::new(1.0, 0.0, 0.0); 282 | let mut expected_shapes = HashSet::new(); 283 | 284 | // It should hit everything. 285 | for id in -10..11 { 286 | expected_shapes.insert(id); 287 | } 288 | traverse_distance_and_verify_order( 289 | origin, 290 | direction, 291 | &all_shapes, 292 | &bvh, 293 | &expected_shapes, 294 | ); 295 | } 296 | 297 | { 298 | // Define a ray which intersects the x-axis diagonally. 299 | let origin = TPoint3::new(6.0, 0.5, 0.0); 300 | let direction = TVector3::new(-2.0, -1.0, 0.0); 301 | 302 | // It should hit exactly three boxes. 303 | let mut expected_shapes = HashSet::new(); 304 | expected_shapes.insert(4); 305 | expected_shapes.insert(5); 306 | expected_shapes.insert(6); 307 | traverse_distance_and_verify_order( 308 | origin, 309 | direction, 310 | &all_shapes, 311 | &bvh, 312 | &expected_shapes, 313 | ); 314 | } 315 | } 316 | 317 | #[test] 318 | /// Runs some primitive tests for intersections of a ray with a fixed scene given as a Bvh. 319 | fn test_traverse_bvh() { 320 | traverse_some_bvh(); 321 | } 322 | 323 | #[test] 324 | fn test_traverse_empty_bvh() { 325 | let (shapes, bvh) = build_empty_bvh(); 326 | 327 | // Define an arbitrary ray. 328 | let origin = TPoint3::new(0.0, 0.0, 0.0); 329 | let direction = TVector3::new(1.0, 0.0, 0.0); 330 | let ray = Ray::new(origin, direction); 331 | 332 | // Ensure distance traversal doesn't panic. 333 | assert_eq!(bvh.nearest_traverse_iterator(&ray, &shapes).count(), 0); 334 | assert_eq!(bvh.farthest_traverse_iterator(&ray, &shapes).count(), 0); 335 | } 336 | } 337 | -------------------------------------------------------------------------------- /src/bvh/distance_traverse.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::Ordering; 2 | use std::collections::BinaryHeap; 3 | 4 | use crate::aabb::{Aabb, Bounded}; 5 | use crate::bounding_hierarchy::BHValue; 6 | use crate::bvh::{iter_initially_has_node, Bvh, BvhNode}; 7 | use crate::ray::Ray; 8 | 9 | #[derive(Debug, Clone, Copy)] 10 | struct DistNodePair { 11 | dist: T, 12 | node_index: usize, 13 | } 14 | 15 | impl Eq for DistNodePair {} 16 | 17 | impl PartialEq for DistNodePair { 18 | fn eq(&self, other: &Self) -> bool { 19 | self.cmp(other) == Ordering::Equal 20 | } 21 | } 22 | 23 | impl PartialOrd for DistNodePair { 24 | fn partial_cmp(&self, other: &Self) -> Option { 25 | Some(self.cmp(other)) 26 | } 27 | } 28 | 29 | impl Ord for DistNodePair { 30 | fn cmp(&self, other: &Self) -> Ordering { 31 | self.dist.partial_cmp(&other.dist).unwrap() 32 | } 33 | } 34 | 35 | /// Iterator to traverse a [`Bvh`] in order from nearest [`Aabb`] to farthest for [`Ray`], 36 | /// or vice versa, without memory allocations. 37 | /// 38 | /// This is a best-effort iterator that orders interior parent nodes before ordering child 39 | /// nodes, so the output is not necessarily perfectly sorted. 40 | pub struct DistanceTraverseIterator< 41 | 'bvh, 42 | 'shape, 43 | T: BHValue, 44 | const D: usize, 45 | Shape: Bounded, 46 | const ASCENDING: bool, 47 | > { 48 | /// Reference to the Bvh to traverse 49 | bvh: &'bvh Bvh, 50 | /// Reference to the input ray 51 | ray: &'bvh Ray, 52 | /// Reference to the input shapes array 53 | shapes: &'shape [Shape], 54 | /// Traversal heap. Store distances and nodes 55 | heap: BinaryHeap>, 56 | } 57 | 58 | impl<'bvh, 'shape, T, const D: usize, Shape: Bounded, const ASCENDING: bool> 59 | DistanceTraverseIterator<'bvh, 'shape, T, D, Shape, ASCENDING> 60 | where 61 | T: BHValue, 62 | { 63 | /// Creates a new [`DistanceTraverseIterator `] 64 | pub fn new(bvh: &'bvh Bvh, ray: &'bvh Ray, shapes: &'shape [Shape]) -> Self { 65 | let mut iterator = DistanceTraverseIterator { 66 | bvh, 67 | ray, 68 | shapes, 69 | heap: BinaryHeap::new(), 70 | }; 71 | 72 | if iter_initially_has_node(bvh, ray, shapes) { 73 | // init starting node. Distance doesn't matter 74 | iterator.add_to_heap(T::zero(), 0); 75 | } 76 | 77 | iterator 78 | } 79 | 80 | /// Unpack node. 81 | /// If it is a leaf returns shape index, else - add childs to heap 82 | fn unpack_node(&mut self, node_index: usize) -> Option { 83 | match self.bvh.nodes[node_index] { 84 | BvhNode::Node { 85 | child_l_index, 86 | ref child_l_aabb, 87 | child_r_index, 88 | ref child_r_aabb, 89 | .. 90 | } => { 91 | self.process_child_intersection(child_l_index, child_l_aabb); 92 | self.process_child_intersection(child_r_index, child_r_aabb); 93 | None 94 | } 95 | BvhNode::Leaf { shape_index, .. } => Some(shape_index), 96 | } 97 | } 98 | 99 | /// Intersect child node with a ray and add it to the heap. 100 | fn process_child_intersection(&mut self, child_node_index: usize, child_aabb: &Aabb) { 101 | let dists_opt = self.ray.intersection_slice_for_aabb(child_aabb); 102 | 103 | // if there is an intersection 104 | if let Some((dist_to_entry_point, dist_to_exit_point)) = dists_opt { 105 | let dist_to_compare = if ASCENDING { 106 | // cause our iterator from nearest to farthest shapes, 107 | // we compare them by first intersection point - entry point 108 | dist_to_entry_point 109 | } else { 110 | // cause our iterator from farthest to nearest shapes, 111 | // we compare them by second intersection point - exit point 112 | dist_to_exit_point 113 | }; 114 | self.add_to_heap(dist_to_compare, child_node_index); 115 | }; 116 | } 117 | 118 | fn add_to_heap(&mut self, dist_to_node: T, node_index: usize) { 119 | // cause we use max-heap, it store max value on the top 120 | if ASCENDING { 121 | // we need the smallest distance, so we negate value 122 | self.heap.push(DistNodePair { 123 | dist: dist_to_node.neg(), 124 | node_index, 125 | }); 126 | } else { 127 | // we need the biggest distance, so everything fine 128 | self.heap.push(DistNodePair { 129 | dist: dist_to_node, 130 | node_index, 131 | }); 132 | }; 133 | } 134 | } 135 | 136 | impl<'shape, T, const D: usize, Shape: Bounded, const ASCENDING: bool> Iterator 137 | for DistanceTraverseIterator<'_, 'shape, T, D, Shape, ASCENDING> 138 | where 139 | T: BHValue, 140 | { 141 | type Item = &'shape Shape; 142 | 143 | fn next(&mut self) -> Option<&'shape Shape> { 144 | while let Some(heap_leader) = self.heap.pop() { 145 | // Get favorite (nearest/farthest) node and unpack 146 | let DistNodePair { 147 | dist: _, 148 | node_index, 149 | } = heap_leader; 150 | 151 | if let Some(shape_index) = self.unpack_node(node_index) { 152 | // unpacked leaf 153 | return Some(&self.shapes[shape_index]); 154 | } 155 | } 156 | None 157 | } 158 | } 159 | 160 | #[cfg(test)] 161 | mod tests { 162 | use std::collections::HashSet; 163 | 164 | use crate::aabb::{Aabb, Bounded}; 165 | use crate::bounding_hierarchy::{BHShape, BHValue}; 166 | use crate::bvh::Bvh; 167 | use crate::ray::Ray; 168 | use crate::testbase::{ 169 | generate_aligned_boxes, TAabb3, TBvh3, TPoint3, TRay3, TVector3, UnitBox, 170 | }; 171 | 172 | /// Create a `Bvh` for a fixed scene structure. 173 | pub fn build_some_bvh() -> (Vec, TBvh3) { 174 | let mut boxes = generate_aligned_boxes(); 175 | let bvh = Bvh::build(&mut boxes); 176 | (boxes, bvh) 177 | } 178 | 179 | /// Create a `Bvh` for an empty scene structure. 180 | pub fn build_empty_bvh() -> (Vec, TBvh3) { 181 | let mut boxes = Vec::new(); 182 | let bvh = Bvh::build(&mut boxes); 183 | (boxes, bvh) 184 | } 185 | 186 | fn traverse_distance_and_verify_order( 187 | ray_origin: TPoint3, 188 | ray_direction: TVector3, 189 | all_shapes: &[UnitBox], 190 | bvh: &TBvh3, 191 | expected_shapes: &HashSet, 192 | ) { 193 | let ray = Ray::new(ray_origin, ray_direction); 194 | let near_it = bvh.nearest_traverse_iterator(&ray, all_shapes); 195 | let far_it = bvh.farthest_traverse_iterator(&ray, all_shapes); 196 | 197 | let mut count = 0; 198 | let mut prev_near_dist = -1.0; 199 | let mut prev_far_dist = f32::INFINITY; 200 | 201 | for (near_shape, far_shape) in near_it.zip(far_it) { 202 | let (intersect_near_dist, _) = 203 | ray.intersection_slice_for_aabb(&near_shape.aabb()).unwrap(); 204 | let (intersect_far_dist, _) = 205 | ray.intersection_slice_for_aabb(&far_shape.aabb()).unwrap(); 206 | 207 | assert!(expected_shapes.contains(&near_shape.id)); 208 | assert!(expected_shapes.contains(&far_shape.id)); 209 | assert!(prev_near_dist <= intersect_near_dist); 210 | assert!(prev_far_dist >= intersect_far_dist); 211 | 212 | count += 1; 213 | prev_near_dist = intersect_near_dist; 214 | prev_far_dist = intersect_far_dist; 215 | } 216 | assert_eq!(expected_shapes.len(), count); 217 | } 218 | 219 | /// Perform some fixed intersection tests on BH structures. 220 | pub fn traverse_some_bvh() { 221 | let (all_shapes, bvh) = build_some_bvh(); 222 | 223 | { 224 | // Define a ray which traverses the x-axis from afar. 225 | let origin = TPoint3::new(-1000.0, 0.0, 0.0); 226 | let direction = TVector3::new(1.0, 0.0, 0.0); 227 | let mut expected_shapes = HashSet::new(); 228 | 229 | // It should hit everything. 230 | for id in -10..11 { 231 | expected_shapes.insert(id); 232 | } 233 | traverse_distance_and_verify_order( 234 | origin, 235 | direction, 236 | &all_shapes, 237 | &bvh, 238 | &expected_shapes, 239 | ); 240 | } 241 | 242 | { 243 | // Define a ray which intersects the x-axis diagonally. 244 | let origin = TPoint3::new(6.0, 0.5, 0.0); 245 | let direction = TVector3::new(-2.0, -1.0, 0.0); 246 | 247 | // It should hit exactly three boxes. 248 | let mut expected_shapes = HashSet::new(); 249 | expected_shapes.insert(4); 250 | expected_shapes.insert(5); 251 | expected_shapes.insert(6); 252 | traverse_distance_and_verify_order( 253 | origin, 254 | direction, 255 | &all_shapes, 256 | &bvh, 257 | &expected_shapes, 258 | ); 259 | } 260 | } 261 | 262 | #[test] 263 | /// Runs some primitive tests for intersections of a ray with a fixed scene given as a Bvh. 264 | fn test_traverse_bvh() { 265 | traverse_some_bvh(); 266 | } 267 | 268 | #[test] 269 | fn test_traverse_empty_bvh() { 270 | let (shapes, bvh) = build_empty_bvh(); 271 | 272 | // Define an arbitrary ray. 273 | let origin = TPoint3::new(0.0, 0.0, 0.0); 274 | let direction = TVector3::new(1.0, 0.0, 0.0); 275 | let ray = Ray::new(origin, direction); 276 | 277 | // Ensure distance traversal doesn't panic. 278 | assert_eq!(bvh.nearest_traverse_iterator(&ray, &shapes).count(), 0); 279 | assert_eq!(bvh.farthest_traverse_iterator(&ray, &shapes).count(), 0); 280 | } 281 | 282 | impl BHShape for Aabb { 283 | fn bh_node_index(&self) -> usize { 284 | unimplemented!(); 285 | } 286 | 287 | fn set_bh_node_index(&mut self, _: usize) { 288 | // No-op. 289 | } 290 | } 291 | 292 | #[test] 293 | fn test_overlapping_child_order() { 294 | let mut aabbs = [ 295 | TAabb3 { 296 | min: TPoint3::new(-0.33333334, -5000.3335, -5000.3335), 297 | max: TPoint3::new(1.3333334, 0.33333334, 0.33333334), 298 | }, 299 | TAabb3 { 300 | min: TPoint3::new(-5000.3335, -5000.3335, -5000.3335), 301 | max: TPoint3::new(0.33333334, 0.33333334, -4998.6665), 302 | }, 303 | TAabb3 { 304 | min: TPoint3::new(-5000.3335, -5000.3335, -5000.3335), 305 | max: TPoint3::new(0.33333334, 0.33333334, 5000.3335), 306 | }, 307 | ]; 308 | let ray = TRay3::new( 309 | TPoint3::new(-5000.0, -5000.0, -5000.0), 310 | TVector3::new(1.0, 0.0, 0.0), 311 | ); 312 | 313 | let bvh = TBvh3::build(&mut aabbs); 314 | assert!(bvh 315 | .nearest_traverse_iterator(&ray, &aabbs) 316 | .is_sorted_by(|a, b| { 317 | let (a, _) = ray.intersection_slice_for_aabb(a).unwrap(); 318 | let (b, _) = ray.intersection_slice_for_aabb(b).unwrap(); 319 | a <= b 320 | })); 321 | } 322 | } 323 | -------------------------------------------------------------------------------- /src/bvh/iter.rs: -------------------------------------------------------------------------------- 1 | use crate::aabb::{Bounded, IntersectsAabb}; 2 | use crate::bounding_hierarchy::BHValue; 3 | use crate::bvh::{Bvh, BvhNode}; 4 | 5 | /// Iterator to traverse a [`Bvh`] without memory allocations 6 | pub struct BvhTraverseIterator< 7 | 'bvh, 8 | 'shape, 9 | T: BHValue, 10 | const D: usize, 11 | Query: IntersectsAabb, 12 | Shape: Bounded, 13 | > { 14 | /// Reference to the [`Bvh`] to traverse 15 | bvh: &'bvh Bvh, 16 | /// Reference to the input query 17 | query: &'bvh Query, 18 | /// Reference to the input shapes array 19 | shapes: &'shape [Shape], 20 | /// Traversal stack. 4 billion items seems enough? 21 | stack: [usize; 32], 22 | /// Position of the iterator in bvh.nodes 23 | node_index: usize, 24 | /// Size of the traversal stack 25 | stack_size: usize, 26 | /// Whether or not we have a valid node (or leaf) 27 | has_node: bool, 28 | } 29 | 30 | impl< 31 | 'bvh, 32 | 'shape, 33 | T: BHValue, 34 | const D: usize, 35 | Query: IntersectsAabb, 36 | Shape: Bounded, 37 | > BvhTraverseIterator<'bvh, 'shape, T, D, Query, Shape> 38 | { 39 | /// Creates a new [`BvhTraverseIterator`] 40 | pub fn new(bvh: &'bvh Bvh, query: &'bvh Query, shapes: &'shape [Shape]) -> Self { 41 | BvhTraverseIterator { 42 | bvh, 43 | query, 44 | shapes, 45 | stack: [0; 32], 46 | node_index: 0, 47 | stack_size: 0, 48 | has_node: iter_initially_has_node(bvh, query, shapes), 49 | } 50 | } 51 | 52 | /// Test if stack is empty. 53 | fn is_stack_empty(&self) -> bool { 54 | self.stack_size == 0 55 | } 56 | 57 | /// Push node onto stack. 58 | /// 59 | /// # Panics 60 | /// 61 | /// Panics if `stack[stack_size]` is out of bounds. 62 | fn stack_push(&mut self, node: usize) { 63 | self.stack[self.stack_size] = node; 64 | self.stack_size += 1; 65 | } 66 | 67 | /// Pop the stack and return the node. 68 | /// 69 | /// # Panics 70 | /// 71 | /// Panics if `stack_size` underflows. 72 | fn stack_pop(&mut self) -> usize { 73 | self.stack_size -= 1; 74 | self.stack[self.stack_size] 75 | } 76 | 77 | /// Attempt to move to the left node child of the current node. 78 | /// If it is a leaf, or the ray does not intersect the node [`Aabb`], `has_node` will become false. 79 | fn move_left(&mut self) { 80 | match self.bvh.nodes[self.node_index] { 81 | BvhNode::Node { 82 | child_l_index, 83 | ref child_l_aabb, 84 | .. 85 | } => { 86 | if self.query.intersects_aabb(child_l_aabb) { 87 | self.node_index = child_l_index; 88 | self.has_node = true; 89 | } else { 90 | self.has_node = false; 91 | } 92 | } 93 | BvhNode::Leaf { .. } => { 94 | self.has_node = false; 95 | } 96 | } 97 | } 98 | 99 | /// Attempt to move to the right node child of the current node. 100 | /// If it is a leaf, or the ray does not intersect the node [`Aabb`], `has_node` will become false. 101 | fn move_right(&mut self) { 102 | match self.bvh.nodes[self.node_index] { 103 | BvhNode::Node { 104 | child_r_index, 105 | ref child_r_aabb, 106 | .. 107 | } => { 108 | if self.query.intersects_aabb(child_r_aabb) { 109 | self.node_index = child_r_index; 110 | self.has_node = true; 111 | } else { 112 | self.has_node = false; 113 | } 114 | } 115 | BvhNode::Leaf { .. } => { 116 | self.has_node = false; 117 | } 118 | } 119 | } 120 | } 121 | 122 | impl<'shape, T: BHValue, const D: usize, Query: IntersectsAabb, Shape: Bounded> Iterator 123 | for BvhTraverseIterator<'_, 'shape, T, D, Query, Shape> 124 | { 125 | type Item = &'shape Shape; 126 | 127 | fn next(&mut self) -> Option<&'shape Shape> { 128 | loop { 129 | if self.is_stack_empty() && !self.has_node { 130 | // Completed traversal. 131 | break; 132 | } 133 | if self.has_node { 134 | // If we have any node, save it and attempt to move to its left child. 135 | self.stack_push(self.node_index); 136 | self.move_left(); 137 | } else { 138 | // Go back up the stack and see if a node or leaf was pushed. 139 | self.node_index = self.stack_pop(); 140 | match self.bvh.nodes[self.node_index] { 141 | BvhNode::Node { .. } => { 142 | // If a node was pushed, now attempt to move to its right child. 143 | self.move_right(); 144 | } 145 | BvhNode::Leaf { shape_index, .. } => { 146 | // We previously pushed a leaf node. This is the "visit" of the in-order traverse. 147 | // Next time we call `next()` we try to pop the stack again. 148 | self.has_node = false; 149 | return Some(&self.shapes[shape_index]); 150 | } 151 | } 152 | } 153 | } 154 | None 155 | } 156 | } 157 | 158 | /// This computation is common to all `Bvh` traversal iterators. 159 | /// 160 | /// It is designed to handle two extraordinary cases: 161 | /// 1. Empty BVH. This case requires an empty iterator. This is accomplished by setting `has_node` 162 | /// to `false` to indicate the absence of any nodes. 163 | /// 2. Single-node BVH, in which the root node is a leaf node. This case requires returning the 164 | /// root shape, if and only if its AABB is intersected by the ray. This is accomplished by 165 | /// setting `has_node` based on manually checking intersection. 166 | /// 167 | /// Finally, if the root is an interior node, that is the normal case. We set `has_node` to true so 168 | /// the iterator can visit the root node and decide what to do next based on the root node's child 169 | /// AABB's. 170 | pub(crate) fn iter_initially_has_node< 171 | T: BHValue, 172 | const D: usize, 173 | Query: IntersectsAabb, 174 | Shape: Bounded, 175 | >( 176 | bvh: &Bvh, 177 | query: &Query, 178 | shapes: &[Shape], 179 | ) -> bool { 180 | match bvh.nodes.first() { 181 | // Only process the root leaf node if the shape's AABB is intersected. 182 | Some(BvhNode::Leaf { shape_index, .. }) => { 183 | query.intersects_aabb(&shapes[*shape_index].aabb()) 184 | } 185 | Some(_) => true, 186 | None => false, 187 | } 188 | } 189 | 190 | // Copy of part of the BH testing in testbase. 191 | // TODO: Once iterators are part of the BoundingHierarchy trait we can move all this to testbase. 192 | #[cfg(test)] 193 | mod tests { 194 | use crate::ray::Ray; 195 | use crate::testbase::{generate_aligned_boxes, TBvh3, TPoint3, TVector3, UnitBox}; 196 | use nalgebra::{OPoint, OVector}; 197 | use std::collections::HashSet; 198 | 199 | /// Creates an empty [`Bvh`]. 200 | pub fn build_empty_bvh() -> ([UnitBox; 0], TBvh3) { 201 | let mut empty_array = []; 202 | let bvh = TBvh3::build(&mut empty_array); 203 | (empty_array, bvh) 204 | } 205 | 206 | /// Creates a [`Bvh`] for a fixed scene structure. 207 | pub fn build_some_bvh() -> (Vec, TBvh3) { 208 | let mut boxes = generate_aligned_boxes(); 209 | let bvh = TBvh3::build(&mut boxes); 210 | (boxes, bvh) 211 | } 212 | 213 | /// Given a ray, a bounding hierarchy, the complete list of shapes in the scene and a list of 214 | /// expected hits, verifies, whether the ray hits only the expected shapes. 215 | fn traverse_and_verify_vec( 216 | ray_origin: TPoint3, 217 | ray_direction: TVector3, 218 | all_shapes: &[UnitBox], 219 | bvh: &TBvh3, 220 | expected_shapes: &HashSet, 221 | ) { 222 | let ray = Ray::new(ray_origin, ray_direction); 223 | let hit_shapes = bvh.traverse(&ray, all_shapes); 224 | 225 | assert_eq!(expected_shapes.len(), hit_shapes.len()); 226 | for shape in hit_shapes { 227 | assert!(expected_shapes.contains(&shape.id)); 228 | } 229 | } 230 | 231 | fn traverse_and_verify_iterator( 232 | ray_origin: TPoint3, 233 | ray_direction: TVector3, 234 | all_shapes: &[UnitBox], 235 | bvh: &TBvh3, 236 | expected_shapes: &HashSet, 237 | ) { 238 | let ray = Ray::new(ray_origin, ray_direction); 239 | let it = bvh.traverse_iterator(&ray, all_shapes); 240 | 241 | let mut count = 0; 242 | for shape in it { 243 | assert!(expected_shapes.contains(&shape.id)); 244 | count += 1; 245 | } 246 | assert_eq!(expected_shapes.len(), count); 247 | } 248 | 249 | fn traverse_and_verify_base( 250 | ray_origin: TPoint3, 251 | ray_direction: TVector3, 252 | all_shapes: &[UnitBox], 253 | bvh: &TBvh3, 254 | expected_shapes: &HashSet, 255 | ) { 256 | traverse_and_verify_vec(ray_origin, ray_direction, all_shapes, bvh, expected_shapes); 257 | traverse_and_verify_iterator(ray_origin, ray_direction, all_shapes, bvh, expected_shapes); 258 | } 259 | 260 | /// Perform some fixed intersection tests on an empty BH structure. 261 | pub fn traverse_empty_bvh() { 262 | let (empty_array, bvh) = build_empty_bvh(); 263 | traverse_and_verify_base( 264 | OPoint::origin(), 265 | OVector::x(), 266 | &empty_array, 267 | &bvh, 268 | &HashSet::new(), 269 | ); 270 | } 271 | 272 | /// Perform some fixed intersection tests on BH structures. 273 | pub fn traverse_some_bvh() { 274 | let (all_shapes, bvh) = build_some_bvh(); 275 | 276 | { 277 | // Define a ray which traverses the x-axis from afar. 278 | let origin = TPoint3::new(-1000.0, 0.0, 0.0); 279 | let direction = TVector3::new(1.0, 0.0, 0.0); 280 | let mut expected_shapes = HashSet::new(); 281 | 282 | // It should hit everything. 283 | for id in -10..11 { 284 | expected_shapes.insert(id); 285 | } 286 | traverse_and_verify_base(origin, direction, &all_shapes, &bvh, &expected_shapes); 287 | } 288 | 289 | { 290 | // Define a ray which traverses the y-axis from afar. 291 | let origin = TPoint3::new(0.0, -1000.0, 0.0); 292 | let direction = TVector3::new(0.0, 1.0, 0.0); 293 | 294 | // It should hit only one box. 295 | let mut expected_shapes = HashSet::new(); 296 | expected_shapes.insert(0); 297 | traverse_and_verify_base(origin, direction, &all_shapes, &bvh, &expected_shapes); 298 | } 299 | 300 | { 301 | // Define a ray which intersects the x-axis diagonally. 302 | let origin = TPoint3::new(6.0, 0.5, 0.0); 303 | let direction = TVector3::new(-2.0, -1.0, 0.0); 304 | 305 | // It should hit exactly three boxes. 306 | let mut expected_shapes = HashSet::new(); 307 | expected_shapes.insert(4); 308 | expected_shapes.insert(5); 309 | expected_shapes.insert(6); 310 | traverse_and_verify_base(origin, direction, &all_shapes, &bvh, &expected_shapes); 311 | } 312 | } 313 | 314 | #[test] 315 | /// Runs some primitive tests for intersections of a ray with a fixed scene given as a Bvh. 316 | fn test_traverse_bvh() { 317 | traverse_empty_bvh(); 318 | traverse_some_bvh(); 319 | } 320 | } 321 | 322 | #[cfg(all(feature = "bench", test))] 323 | mod bench { 324 | use crate::testbase::{create_ray, load_sponza_scene, TBvh3}; 325 | 326 | #[bench] 327 | /// Benchmark the traversal of a [`Bvh`] with the Sponza scene with [`Vec`] return. 328 | fn bench_intersect_128rays_sponza_vec(b: &mut ::test::Bencher) { 329 | let (mut triangles, bounds) = load_sponza_scene(); 330 | let bvh = TBvh3::build(&mut triangles); 331 | 332 | let mut seed = 0; 333 | b.iter(|| { 334 | for _ in 0..128 { 335 | let ray = create_ray(&mut seed, &bounds); 336 | 337 | // Traverse the `Bvh` recursively. 338 | let hits = bvh.traverse(&ray, &triangles); 339 | 340 | // Traverse the resulting list of positive `Aabb` tests 341 | for triangle in &hits { 342 | ray.intersects_triangle(&triangle.a, &triangle.b, &triangle.c); 343 | } 344 | } 345 | }); 346 | } 347 | 348 | #[bench] 349 | /// Benchmark the traversal of a `Bvh` with the Sponza scene with [`BvhTraverseIterator`]. 350 | fn bench_intersect_128rays_sponza_iter(b: &mut ::test::Bencher) { 351 | let (mut triangles, bounds) = load_sponza_scene(); 352 | let bvh = TBvh3::build(&mut triangles); 353 | 354 | let mut seed = 0; 355 | b.iter(|| { 356 | for _ in 0..128 { 357 | let ray = create_ray(&mut seed, &bounds); 358 | 359 | // Traverse the `Bvh` recursively. 360 | let hits = bvh.traverse_iterator(&ray, &triangles); 361 | 362 | // Traverse the resulting list of positive `Aabb` tests 363 | for triangle in hits { 364 | ray.intersects_triangle(&triangle.a, &triangle.b, &triangle.c); 365 | } 366 | } 367 | }); 368 | } 369 | } 370 | -------------------------------------------------------------------------------- /src/bvh/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module defines a [`Bvh`]. 2 | //! 3 | //! [`Bvh`]: struct.Bvh.html 4 | //! 5 | 6 | mod bvh_impl; 7 | mod bvh_node; 8 | mod child_distance_traverse; 9 | mod distance_traverse; 10 | mod iter; 11 | mod optimization; 12 | 13 | pub use self::bvh_impl::*; 14 | pub use self::bvh_node::*; 15 | pub use self::child_distance_traverse::*; 16 | pub use self::distance_traverse::*; 17 | pub use self::iter::*; 18 | -------------------------------------------------------------------------------- /src/flat_bvh.rs: -------------------------------------------------------------------------------- 1 | //! This module exports methods to flatten the [`Bvh`] into a [`FlatBvh`] and traverse it iteratively. 2 | use crate::aabb::{Aabb, Bounded, IntersectsAabb}; 3 | use crate::bounding_hierarchy::{BHShape, BHValue, BoundingHierarchy}; 4 | use crate::bvh::{Bvh, BvhNode}; 5 | use crate::point_query::PointDistance; 6 | 7 | use nalgebra::Point; 8 | use num::Float; 9 | 10 | /// A structure of a node of a flat [`Bvh`]. The structure of the nodes allows for an 11 | /// iterative traversal approach without the necessity to maintain a stack or queue. 12 | /// 13 | /// [`Bvh`]: ../bvh/struct.Bvh.html 14 | /// 15 | pub struct FlatNode { 16 | /// The [`Aabb`] of the [`Bvh`] node. Prior to testing the [`Aabb`] bounds, 17 | /// the `entry_index` must be checked. In case the entry_index is [`u32::MAX`], 18 | /// the [`Aabb`] is undefined. 19 | /// 20 | /// [`Aabb`]: ../aabb/struct.Aabb.html 21 | /// [`Bvh`]: ../bvh/struct.Bvh.html 22 | /// [`u32::MAX`]: https://doc.rust-lang.org/std/u32/constant.MAX.html 23 | /// 24 | pub aabb: Aabb, 25 | 26 | /// The index of the `FlatNode` to jump to, if the [`Aabb`] test is positive. 27 | /// If this value is [`u32::MAX`] then the current node is a leaf node. 28 | /// Leaf nodes contain a shape index and an exit index. In leaf nodes the 29 | /// [`Aabb`] is undefined. 30 | /// 31 | /// [`Aabb`]: ../aabb/struct.Aabb.html 32 | /// [`u32::MAX`]: https://doc.rust-lang.org/std/u32/constant.MAX.html 33 | /// 34 | pub entry_index: u32, 35 | 36 | /// The index of the [`FlatNode`] to jump to, if the [`Aabb`] test is negative. 37 | /// 38 | /// [`Aabb`]: ../aabb/struct.Aabb.html 39 | /// 40 | pub exit_index: u32, 41 | 42 | /// The index of the shape in the shapes array. 43 | pub shape_index: u32, 44 | } 45 | 46 | impl FlatNode { 47 | /// Returns whether the node is a leaf node. 48 | #[must_use] 49 | pub fn is_leaf(&self) -> bool { 50 | self.entry_index == u32::MAX 51 | } 52 | } 53 | 54 | impl BvhNode { 55 | /// Creates a flat node from a `Bvh` inner node and its [`Aabb`]. Returns the next free index. 56 | /// TODO: change the algorithm which pushes `FlatNode`s to a vector to not use indices this 57 | /// much. Implement an algorithm which writes directly to a writable slice. 58 | fn create_flat_branch( 59 | &self, 60 | nodes: &[BvhNode], 61 | this_aabb: &Aabb, 62 | vec: &mut Vec, 63 | next_free: usize, 64 | constructor: &F, 65 | ) -> usize 66 | where 67 | F: Fn(&Aabb, u32, u32, u32) -> FNodeType, 68 | { 69 | // Create dummy node. 70 | let dummy = constructor(&Aabb::empty(), 0, 0, 0); 71 | vec.push(dummy); 72 | assert_eq!(vec.len() - 1, next_free); 73 | 74 | // Create subtree. 75 | let index_after_subtree = self.flatten_custom(nodes, vec, next_free + 1, constructor); 76 | 77 | // Replace dummy node by actual node with the entry index pointing to the subtree 78 | // and the exit index pointing to the next node after the subtree. 79 | let navigator_node = constructor( 80 | this_aabb, 81 | (next_free + 1) as u32, 82 | index_after_subtree as u32, 83 | u32::MAX, 84 | ); 85 | vec[next_free] = navigator_node; 86 | index_after_subtree 87 | } 88 | 89 | /// Flattens the [`Bvh`], so that it can be traversed in an iterative manner. 90 | /// This method constructs custom flat nodes using the `constructor`. 91 | /// 92 | /// [`Bvh`]: ../bvh/struct.Bvh.html 93 | /// 94 | pub fn flatten_custom( 95 | &self, 96 | nodes: &[BvhNode], 97 | vec: &mut Vec, 98 | next_free: usize, 99 | constructor: &F, 100 | ) -> usize 101 | where 102 | F: Fn(&Aabb, u32, u32, u32) -> FNodeType, 103 | { 104 | match *self { 105 | BvhNode::Node { 106 | ref child_l_aabb, 107 | child_l_index, 108 | ref child_r_aabb, 109 | child_r_index, 110 | .. 111 | } => { 112 | let index_after_child_l = nodes[child_l_index].create_flat_branch( 113 | nodes, 114 | child_l_aabb, 115 | vec, 116 | next_free, 117 | constructor, 118 | ); 119 | nodes[child_r_index].create_flat_branch( 120 | nodes, 121 | child_r_aabb, 122 | vec, 123 | index_after_child_l, 124 | constructor, 125 | ) 126 | } 127 | BvhNode::Leaf { shape_index, .. } => { 128 | let mut next_shape = next_free; 129 | next_shape += 1; 130 | let leaf_node = constructor( 131 | &Aabb::empty(), 132 | u32::MAX, 133 | next_shape as u32, 134 | shape_index as u32, 135 | ); 136 | vec.push(leaf_node); 137 | 138 | next_shape 139 | } 140 | } 141 | } 142 | } 143 | 144 | /// A flat [`Bvh`]. Represented by a vector of [`FlatNode`]s. The [`FlatBvh`] is designed for use 145 | /// where a recursive traversal of a data structure is not possible, for example shader programs. 146 | /// 147 | /// [`Bvh`]: ../bvh/struct.Bvh.html 148 | /// [`FlatNode`]: struct.FlatNode.html 149 | /// [`FlatBvh`]: struct.FlatBvh.html 150 | /// 151 | pub type FlatBvh = Vec>; 152 | 153 | impl Bvh { 154 | /// Flattens the [`Bvh`] so that it can be traversed iteratively. 155 | /// Constructs the flat nodes using the supplied function. 156 | /// This function can be used, when the flat bvh nodes should be of some particular 157 | /// non-default structure. 158 | /// The `constructor` is fed the following arguments in this order: 159 | /// 160 | /// 1 - &Aabb: The enclosing [`Aabb`] 161 | /// 2 - u32: The index of the nested node 162 | /// 3 - u32: The exit index 163 | /// 4 - u32: The shape index 164 | /// 165 | /// [`Bvh`]: ../bvh/struct.Bvh.html 166 | /// 167 | /// # Example 168 | /// 169 | /// ``` 170 | /// use bvh::aabb::{Aabb, Bounded}; 171 | /// use bvh::bvh::Bvh; 172 | /// use nalgebra::{Point3, Vector3}; 173 | /// use bvh::ray::Ray; 174 | /// # use bvh::bounding_hierarchy::BHShape; 175 | /// # pub struct UnitBox { 176 | /// # pub id: i32, 177 | /// # pub pos: Point3, 178 | /// # node_index: usize, 179 | /// # } 180 | /// # 181 | /// # impl UnitBox { 182 | /// # pub fn new(id: i32, pos: Point3) -> UnitBox { 183 | /// # UnitBox { 184 | /// # id: id, 185 | /// # pos: pos, 186 | /// # node_index: 0, 187 | /// # } 188 | /// # } 189 | /// # } 190 | /// # 191 | /// # impl Bounded for UnitBox { 192 | /// # fn aabb(&self) -> Aabb { 193 | /// # let min = self.pos + Vector3::new(-0.5, -0.5, -0.5); 194 | /// # let max = self.pos + Vector3::new(0.5, 0.5, 0.5); 195 | /// # Aabb::with_bounds(min, max) 196 | /// # } 197 | /// # } 198 | /// # 199 | /// # impl BHShape for UnitBox { 200 | /// # fn set_bh_node_index(&mut self, index: usize) { 201 | /// # self.node_index = index; 202 | /// # } 203 | /// # 204 | /// # fn bh_node_index(&self) -> usize { 205 | /// # self.node_index 206 | /// # } 207 | /// # } 208 | /// # 209 | /// # fn create_bhshapes() -> Vec { 210 | /// # let mut shapes = Vec::new(); 211 | /// # for i in 0..1000 { 212 | /// # let position = Point3::new(i as f32, i as f32, i as f32); 213 | /// # shapes.push(UnitBox::new(i, position)); 214 | /// # } 215 | /// # shapes 216 | /// # } 217 | /// 218 | /// struct CustomStruct { 219 | /// aabb: Aabb, 220 | /// entry_index: u32, 221 | /// exit_index: u32, 222 | /// shape_index: u32, 223 | /// } 224 | /// 225 | /// let custom_constructor = |aabb: &Aabb, entry, exit, shape_index| { 226 | /// CustomStruct { 227 | /// aabb: *aabb, 228 | /// entry_index: entry, 229 | /// exit_index: exit, 230 | /// shape_index: shape_index, 231 | /// } 232 | /// }; 233 | /// 234 | /// let mut shapes = create_bhshapes(); 235 | /// let bvh = Bvh::build(&mut shapes); 236 | /// let custom_flat_bvh = bvh.flatten_custom(&custom_constructor); 237 | /// ``` 238 | pub fn flatten_custom(&self, constructor: &F) -> Vec 239 | where 240 | F: Fn(&Aabb, u32, u32, u32) -> FNodeType, 241 | { 242 | let mut vec = Vec::new(); 243 | if self.nodes.is_empty() { 244 | // There is no node_index=0. 245 | return vec; 246 | } 247 | self.nodes[0].flatten_custom(&self.nodes, &mut vec, 0, constructor); 248 | vec 249 | } 250 | 251 | /// Flattens the [`Bvh`] so that it can be traversed iteratively. 252 | /// 253 | /// [`Bvh`]: ../bvh/struct.Bvh.html 254 | /// 255 | /// # Example 256 | /// 257 | /// ``` 258 | /// use bvh::aabb::{Aabb, Bounded}; 259 | /// use bvh::bvh::Bvh; 260 | /// use nalgebra::{Point3, Vector3}; 261 | /// use bvh::ray::Ray; 262 | /// # use bvh::bounding_hierarchy::BHShape; 263 | /// # pub struct UnitBox { 264 | /// # pub id: i32, 265 | /// # pub pos: Point3, 266 | /// # node_index: usize, 267 | /// # } 268 | /// # 269 | /// # impl UnitBox { 270 | /// # pub fn new(id: i32, pos: Point3) -> UnitBox { 271 | /// # UnitBox { 272 | /// # id: id, 273 | /// # pos: pos, 274 | /// # node_index: 0, 275 | /// # } 276 | /// # } 277 | /// # } 278 | /// # 279 | /// # impl Bounded for UnitBox { 280 | /// # fn aabb(&self) -> Aabb { 281 | /// # let min = self.pos + Vector3::new(-0.5, -0.5, -0.5); 282 | /// # let max = self.pos + Vector3::new(0.5, 0.5, 0.5); 283 | /// # Aabb::with_bounds(min, max) 284 | /// # } 285 | /// # } 286 | /// # 287 | /// # impl BHShape for UnitBox { 288 | /// # fn set_bh_node_index(&mut self, index: usize) { 289 | /// # self.node_index = index; 290 | /// # } 291 | /// # 292 | /// # fn bh_node_index(&self) -> usize { 293 | /// # self.node_index 294 | /// # } 295 | /// # } 296 | /// # 297 | /// # fn create_bhshapes() -> Vec { 298 | /// # let mut shapes = Vec::new(); 299 | /// # for i in 0..1000 { 300 | /// # let position = Point3::new(i as f32, i as f32, i as f32); 301 | /// # shapes.push(UnitBox::new(i, position)); 302 | /// # } 303 | /// # shapes 304 | /// # } 305 | /// 306 | /// let mut shapes = create_bhshapes(); 307 | /// let bvh = Bvh::build(&mut shapes); 308 | /// let flat_bvh = bvh.flatten(); 309 | /// ``` 310 | pub fn flatten(&self) -> FlatBvh { 311 | self.flatten_custom(&|aabb, entry, exit, shape| FlatNode { 312 | aabb: *aabb, 313 | entry_index: entry, 314 | exit_index: exit, 315 | shape_index: shape, 316 | }) 317 | } 318 | } 319 | 320 | impl BoundingHierarchy for FlatBvh { 321 | /// A [`FlatBvh`] is built from a regular [`Bvh`] using the [`Bvh::flatten`] method. 322 | /// 323 | /// [`FlatBvh`]: struct.FlatBvh.html 324 | /// [`Bvh`]: ../bvh/struct.Bvh.html 325 | /// 326 | fn build>(shapes: &mut [Shape]) -> FlatBvh { 327 | let bvh = Bvh::build(shapes); 328 | bvh.flatten() 329 | } 330 | 331 | /// Traverses a [`FlatBvh`] structure iteratively. 332 | /// 333 | /// [`FlatBvh`]: struct.FlatBvh.html 334 | /// 335 | /// # Examples 336 | /// 337 | /// ``` 338 | /// use bvh::aabb::{Aabb, Bounded}; 339 | /// use bvh::bounding_hierarchy::BoundingHierarchy; 340 | /// use bvh::flat_bvh::FlatBvh; 341 | /// use nalgebra::{Point3, Vector3}; 342 | /// use bvh::ray::Ray; 343 | /// # use bvh::bounding_hierarchy::BHShape; 344 | /// # pub struct UnitBox { 345 | /// # pub id: i32, 346 | /// # pub pos: Point3, 347 | /// # node_index: usize, 348 | /// # } 349 | /// # 350 | /// # impl UnitBox { 351 | /// # pub fn new(id: i32, pos: Point3) -> UnitBox { 352 | /// # UnitBox { 353 | /// # id: id, 354 | /// # pos: pos, 355 | /// # node_index: 0, 356 | /// # } 357 | /// # } 358 | /// # } 359 | /// # 360 | /// # impl Bounded for UnitBox { 361 | /// # fn aabb(&self) -> Aabb { 362 | /// # let min = self.pos + Vector3::new(-0.5, -0.5, -0.5); 363 | /// # let max = self.pos + Vector3::new(0.5, 0.5, 0.5); 364 | /// # Aabb::with_bounds(min, max) 365 | /// # } 366 | /// # } 367 | /// # 368 | /// # impl BHShape for UnitBox { 369 | /// # fn set_bh_node_index(&mut self, index: usize) { 370 | /// # self.node_index = index; 371 | /// # } 372 | /// # 373 | /// # fn bh_node_index(&self) -> usize { 374 | /// # self.node_index 375 | /// # } 376 | /// # } 377 | /// # 378 | /// # fn create_bhshapes() -> Vec { 379 | /// # let mut shapes = Vec::new(); 380 | /// # for i in 0..1000 { 381 | /// # let position = Point3::new(i as f32, i as f32, i as f32); 382 | /// # shapes.push(UnitBox::new(i, position)); 383 | /// # } 384 | /// # shapes 385 | /// # } 386 | /// 387 | /// let origin = Point3::new(0.0,0.0,0.0); 388 | /// let direction = Vector3::new(1.0,0.0,0.0); 389 | /// let ray = Ray::new(origin, direction); 390 | /// let mut shapes = create_bhshapes(); 391 | /// let flat_bvh = FlatBvh::build(&mut shapes); 392 | /// let hit_shapes = flat_bvh.traverse(&ray, &shapes); 393 | /// ``` 394 | fn traverse<'a, Q: IntersectsAabb, B: Bounded>( 395 | &'a self, 396 | query: &Q, 397 | shapes: &'a [B], 398 | ) -> Vec<&'a B> { 399 | let mut hit_shapes = Vec::new(); 400 | let mut index = 0; 401 | 402 | // The traversal loop should terminate when `max_length` is set as the next node index. 403 | let max_length = self.len(); 404 | 405 | // Iterate while the node index is valid. 406 | while index < max_length { 407 | let node = &self[index]; 408 | 409 | if node.is_leaf() { 410 | let shape = &shapes[node.shape_index as usize]; 411 | if query.intersects_aabb(&shape.aabb()) { 412 | hit_shapes.push(shape); 413 | } 414 | 415 | // Exit the current node. 416 | index = node.exit_index as usize; 417 | } else if query.intersects_aabb(&node.aabb) { 418 | // If entry_index is not MAX_UINT32 and the Aabb test passes, then 419 | // proceed to the node in entry_index (which goes down the bvh branch). 420 | index = node.entry_index as usize; 421 | } else { 422 | // If entry_index is not MAX_UINT32 and the Aabb test fails, then 423 | // proceed to the node in exit_index (which defines the next untested partition). 424 | index = node.exit_index as usize; 425 | } 426 | } 427 | 428 | hit_shapes 429 | } 430 | 431 | /// Traverses a [`FlatBvh`] structure iteratively. 432 | /// Returns the nearest shape to the query point and the distance to it. 433 | /// 434 | /// Note: the flat bvh is not optimized for nearest queries. It is recommended to use 435 | /// a [`Bvh`] for nearest queries when possible. 436 | /// The flat bvh will still be much faster than a linear search. 437 | /// 438 | /// [`FlatBvh`]: struct.FlatBvh.html 439 | /// 440 | /// # Examples 441 | /// 442 | /// ``` 443 | /// use bvh::aabb::{Aabb, Bounded}; 444 | /// use bvh::bounding_hierarchy::BoundingHierarchy; 445 | /// use bvh::flat_bvh::FlatBvh; 446 | /// use bvh::point_query::PointDistance; 447 | /// use nalgebra::{Point3, Vector3}; 448 | /// use bvh::ray::Ray; 449 | /// # use bvh::bounding_hierarchy::BHShape; 450 | /// # pub struct UnitBox { 451 | /// # pub id: i32, 452 | /// # pub pos: Point3, 453 | /// # node_index: usize, 454 | /// # } 455 | /// # 456 | /// # impl UnitBox { 457 | /// # pub fn new(id: i32, pos: Point3) -> UnitBox { 458 | /// # UnitBox { 459 | /// # id: id, 460 | /// # pos: pos, 461 | /// # node_index: 0, 462 | /// # } 463 | /// # } 464 | /// # } 465 | /// # 466 | /// # impl Bounded for UnitBox { 467 | /// # fn aabb(&self) -> Aabb { 468 | /// # let min = self.pos + Vector3::new(-0.5, -0.5, -0.5); 469 | /// # let max = self.pos + Vector3::new(0.5, 0.5, 0.5); 470 | /// # Aabb::with_bounds(min, max) 471 | /// # } 472 | /// # } 473 | /// # 474 | /// # impl BHShape for UnitBox { 475 | /// # fn set_bh_node_index(&mut self, index: usize) { 476 | /// # self.node_index = index; 477 | /// # } 478 | /// # 479 | /// # fn bh_node_index(&self) -> usize { 480 | /// # self.node_index 481 | /// # } 482 | /// # } 483 | /// # 484 | /// # impl PointDistance for UnitBox { 485 | /// # fn distance_squared(&self, query_point: Point3) -> f32 { 486 | /// # self.aabb().min_distance_squared(query_point) 487 | /// # } 488 | /// # } 489 | /// # 490 | /// # fn create_bhshapes() -> Vec { 491 | /// # let mut shapes = Vec::new(); 492 | /// # for i in 0..1000 { 493 | /// # let position = Point3::new(i as f32, i as f32, i as f32); 494 | /// # shapes.push(UnitBox::new(i, position)); 495 | /// # } 496 | /// # shapes 497 | /// # } 498 | /// 499 | /// let mut shapes = create_bhshapes(); 500 | /// let flat_bvh = FlatBvh::build(&mut shapes); 501 | /// 502 | /// let query = Point3::new(5.0, 5.7, 5.3); 503 | /// let nearest_shape = flat_bvh.nearest_to(query, &shapes); 504 | /// 505 | /// # assert_eq!(nearest_shape.unwrap().0.id, 5); 506 | /// ``` 507 | /// 508 | /// [`BoundingHierarchy`]: trait.BoundingHierarchy.html 509 | /// [`Aabb`]: ../aabb/struct.Aabb.html 510 | /// 511 | fn nearest_to<'a, Shape: BHShape + PointDistance>( 512 | &'a self, 513 | query: Point, 514 | shapes: &'a [Shape], 515 | ) -> Option<(&'a Shape, T)> { 516 | if self.is_empty() { 517 | return None; 518 | } 519 | 520 | let mut best_element = None; 521 | 522 | let mut index = 0; 523 | 524 | // The traversal loop should terminate when `max_length` is set as the next node index. 525 | let max_length = self.len(); 526 | 527 | // Iterate while the node index is valid. 528 | while index < max_length { 529 | let node = &self[index]; 530 | 531 | if node.is_leaf() { 532 | let shape = &shapes[node.shape_index as usize]; 533 | let dist = shape.distance_squared(query); 534 | 535 | // TODO: to be replaced by `Option::is_none_or` after 2025-10 for 1 year MSRV. 536 | #[allow(clippy::unnecessary_map_or)] 537 | if best_element.map_or(true, |(_, best_dist)| dist < best_dist) { 538 | best_element = Some((shape, dist)); 539 | } 540 | 541 | // Exit the current node. 542 | index = node.exit_index as usize; 543 | } else { 544 | let min_dist = node.aabb.min_distance_squared(query); 545 | 546 | // TODO: to be replaced by `Option::is_none_or` after 2025-10 for 1 year MSRV. 547 | #[allow(clippy::unnecessary_map_or)] 548 | if best_element.map_or(true, |(_, best_dist)| min_dist < best_dist) { 549 | // This node needs further traversal. 550 | index = node.entry_index as usize; 551 | } else { 552 | // This node can't contain a candidate shape. 553 | index = node.exit_index as usize; 554 | } 555 | } 556 | } 557 | 558 | // Return the best shape and its distance. We had a distance squared previously. 559 | best_element.map(|best| (best.0, best.1.sqrt())) 560 | } 561 | 562 | /// Prints a textual representation of a [`FlatBvh`]. 563 | /// 564 | /// [`FlatBvh`]: struct.FlatBvh.html 565 | /// 566 | fn pretty_print(&self) { 567 | for (i, node) in self.iter().enumerate() { 568 | println!( 569 | "{}\tentry {}\texit {}\tshape {}", 570 | i, node.entry_index, node.exit_index, node.shape_index 571 | ); 572 | } 573 | } 574 | 575 | fn build_with_executor< 576 | Shape: BHShape, 577 | Executor: FnMut( 578 | crate::bvh::BvhNodeBuildArgs<'_, Shape, T, D>, 579 | crate::bvh::BvhNodeBuildArgs<'_, Shape, T, D>, 580 | ), 581 | >( 582 | shapes: &mut [Shape], 583 | executor: Executor, 584 | ) -> Self { 585 | let bvh = Bvh::build_with_executor(shapes, executor); 586 | bvh.flatten() 587 | } 588 | } 589 | 590 | #[cfg(test)] 591 | mod tests { 592 | use crate::testbase::{ 593 | build_empty_bh, build_some_bh, nearest_to_some_bh, traverse_some_bh, TBvh3, TFlatBvh3, 594 | }; 595 | 596 | #[test] 597 | /// Tests whether the building procedure succeeds in not failing. 598 | fn test_build_flat_bvh() { 599 | build_some_bh::(); 600 | } 601 | 602 | #[test] 603 | /// Runs some primitive tests for intersections of a ray with a fixed scene given 604 | /// as a `FlatBvh`. 605 | fn test_traverse_flat_bvh() { 606 | traverse_some_bh::(); 607 | } 608 | 609 | #[test] 610 | /// Runs some primitive tests for distance query of a point with a fixed scene given as a [`Bvh`]. 611 | fn test_nearest_to_flat_bvh() { 612 | nearest_to_some_bh::(); 613 | } 614 | #[test] 615 | fn test_flatten_empty_bvh() { 616 | let (_, bvh) = build_empty_bh::(); 617 | let flat = bvh.flatten(); 618 | assert!(flat.is_empty()); 619 | } 620 | } 621 | 622 | #[cfg(all(feature = "bench", test))] 623 | mod bench { 624 | use crate::testbase::{ 625 | build_1200_triangles_bh, build_120k_triangles_bh, build_12k_triangles_bh, create_n_cubes, 626 | default_bounds, intersect_1200_triangles_bh, intersect_120k_triangles_bh, 627 | intersect_12k_triangles_bh, nearest_to_1200_triangles_bh, nearest_to_120k_triangles_bh, 628 | nearest_to_12k_triangles_bh, TBvh3, TFlatBvh3, 629 | }; 630 | 631 | #[bench] 632 | /// Benchmark the flattening of a [`Bvh`] with 120,000 triangles. 633 | fn bench_flatten_120k_triangles_bvh(b: &mut ::test::Bencher) { 634 | let bounds = default_bounds(); 635 | let mut triangles = create_n_cubes(10_000, &bounds); 636 | let bvh = TBvh3::build(&mut triangles); 637 | 638 | b.iter(|| { 639 | bvh.flatten(); 640 | }); 641 | } 642 | #[bench] 643 | /// Benchmark the construction of a [`FlatBvh`] with 1,200 triangles. 644 | fn bench_build_1200_triangles_flat_bvh(b: &mut ::test::Bencher) { 645 | build_1200_triangles_bh::(b); 646 | } 647 | 648 | #[bench] 649 | /// Benchmark the construction of a [`FlatBvh`] with 12,000 triangles. 650 | fn bench_build_12k_triangles_flat_bvh(b: &mut ::test::Bencher) { 651 | build_12k_triangles_bh::(b); 652 | } 653 | 654 | #[bench] 655 | /// Benchmark the construction of a [`FlatBvh`] with 120,000 triangles. 656 | fn bench_build_120k_triangles_flat_bvh(b: &mut ::test::Bencher) { 657 | build_120k_triangles_bh::(b); 658 | } 659 | 660 | #[bench] 661 | /// Benchmark intersecting 1,200 triangles using the recursive [`FlatBvh`]. 662 | fn bench_intersect_1200_triangles_flat_bvh(b: &mut ::test::Bencher) { 663 | intersect_1200_triangles_bh::(b); 664 | } 665 | 666 | #[bench] 667 | /// Benchmark intersecting 12,000 triangles using the recursive [`FlatBvh`]. 668 | fn bench_intersect_12k_triangles_flat_bvh(b: &mut ::test::Bencher) { 669 | intersect_12k_triangles_bh::(b); 670 | } 671 | 672 | #[bench] 673 | /// Benchmark intersecting 120,000 triangles using the recursive [`FlatBvh`]. 674 | fn bench_intersect_120k_triangles_flat_bvh(b: &mut ::test::Bencher) { 675 | intersect_120k_triangles_bh::(b); 676 | } 677 | 678 | #[bench] 679 | /// Benchmark nearest_to on 1,200 triangles using the recursive [`FlatBvh`]. 680 | fn bench_nearest_to_1200_triangles_bvh(b: &mut ::test::Bencher) { 681 | nearest_to_1200_triangles_bh::(b); 682 | } 683 | 684 | #[bench] 685 | /// Benchmark nearest_to on 12,000 triangles using the recursive [`FlatBvh`]. 686 | fn bench_nearest_to_12k_triangles_bvh(b: &mut ::test::Bencher) { 687 | nearest_to_12k_triangles_bh::(b); 688 | } 689 | 690 | #[bench] 691 | /// Benchmark nearest_to on 120,000 triangles using the recursive [`FlatBvh`]. 692 | fn bench_nearest_to_120k_triangles_bvh(b: &mut ::test::Bencher) { 693 | nearest_to_120k_triangles_bh::(b); 694 | } 695 | } 696 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A crate which exports rays, axis-aligned bounding boxes, and binary bounding 2 | //! volume hierarchies. 3 | //! 4 | //! ## About 5 | //! 6 | //! This crate can be used for applications which contain intersection computations of rays 7 | //! with primitives. For this purpose a binary tree [`Bvh`](bvh::Bvh) (Bounding Volume Hierarchy) is of great 8 | //! use if the scene which the ray traverses contains a huge number of primitives. With a [`Bvh`](bvh::Bvh) the 9 | //! intersection test complexity is reduced from O(n) to O(log2(n)) at the cost of building 10 | //! the [`Bvh`](bvh::Bvh) once in advance. This technique is especially useful in ray/path tracers. For 11 | //! use in a shader this module also exports a flattening procedure, which allows for 12 | //! iterative traversal of the [`Bvh`](bvh::Bvh). 13 | //! 14 | //! ## Note 15 | //! 16 | //! If you are concerned about performance and do not mind using nightly, it is recommended to 17 | //! use the `simd` feature as it introduces explicitly written simd to optimize certain areas 18 | //! of the BVH. 19 | //! 20 | //! ## Example 21 | //! 22 | //! ``` 23 | //! use bvh::aabb::{Aabb, Bounded}; 24 | //! use bvh::bounding_hierarchy::{BHShape, BoundingHierarchy}; 25 | //! use bvh::bvh::Bvh; 26 | //! use nalgebra::{Point3, Vector3}; 27 | //! use bvh::ray::Ray; 28 | //! 29 | //! let origin = Point3::new(0.0,0.0,0.0); 30 | //! let direction = Vector3::new(1.0,0.0,0.0); 31 | //! let ray = Ray::new(origin, direction); 32 | //! 33 | //! struct Sphere { 34 | //! position: Point3, 35 | //! radius: f32, 36 | //! node_index: usize, 37 | //! } 38 | //! 39 | //! impl Bounded for Sphere { 40 | //! fn aabb(&self) -> Aabb { 41 | //! let half_size = Vector3::new(self.radius, self.radius, self.radius); 42 | //! let min = self.position - half_size; 43 | //! let max = self.position + half_size; 44 | //! Aabb::with_bounds(min, max) 45 | //! } 46 | //! } 47 | //! 48 | //! impl BHShape for Sphere { 49 | //! fn set_bh_node_index(&mut self, index: usize) { 50 | //! self.node_index = index; 51 | //! } 52 | //! 53 | //! fn bh_node_index(&self) -> usize { 54 | //! self.node_index 55 | //! } 56 | //! } 57 | //! 58 | //! let mut spheres = Vec::new(); 59 | //! for i in 0..1000u32 { 60 | //! let position = Point3::new(i as f32, i as f32, i as f32); 61 | //! let radius = (i % 10) as f32 + 1.0; 62 | //! spheres.push(Sphere { 63 | //! position: position, 64 | //! radius: radius, 65 | //! node_index: 0, 66 | //! }); 67 | //! } 68 | //! 69 | //! let bvh = Bvh::build_par(&mut spheres); 70 | //! let hit_sphere_aabbs = bvh.traverse(&ray, &spheres); 71 | //! ``` 72 | //! 73 | //! ## Features 74 | //! 75 | //! - `serde` (default **disabled**) - adds `Serialize` and `Deserialize` implementations for some types 76 | //! - `simd` (default **disabled**) - adds explicitly written SIMD instructions for certain architectures (requires nightly) 77 | //! 78 | 79 | #![deny(missing_docs)] 80 | #![cfg_attr(feature = "bench", feature(test))] 81 | #![cfg_attr(feature = "simd", feature(min_specialization))] 82 | 83 | #[cfg(all(feature = "bench", test))] 84 | extern crate test; 85 | 86 | pub mod aabb; 87 | pub mod ball; 88 | pub mod bounding_hierarchy; 89 | pub mod bvh; 90 | pub mod flat_bvh; 91 | pub mod point_query; 92 | pub mod ray; 93 | mod utils; 94 | 95 | #[cfg(test)] 96 | mod testbase; 97 | 98 | #[cfg(doctest)] 99 | doc_comment::doctest!("../README.md", readme); 100 | -------------------------------------------------------------------------------- /src/point_query.rs: -------------------------------------------------------------------------------- 1 | //! Contains the `PointDistance` trait used for querying the distance to a point to a bvh. 2 | use crate::bounding_hierarchy::BHValue; 3 | 4 | /// A trait implemented by shapes that can be queried for their distance to a point. 5 | /// 6 | /// Used for the `Bvh::nearest_to` method that returns the nearest shape to a point. 7 | pub trait PointDistance { 8 | /// Returns the squared distance from this point to the Shape. 9 | fn distance_squared(&self, query_point: nalgebra::Point) -> T; 10 | } 11 | -------------------------------------------------------------------------------- /src/ray/intersect_default.rs: -------------------------------------------------------------------------------- 1 | //! This file contains the generic implementation of [`RayIntersection`] 2 | 3 | use super::Ray; 4 | use crate::{ 5 | aabb::Aabb, 6 | bounding_hierarchy::BHValue, 7 | utils::{fast_max, has_nan}, 8 | }; 9 | 10 | /// The [`RayIntersection`] trait allows for generic implementation of ray intersection 11 | /// useful for our SIMD optimizations. 12 | pub trait RayIntersection { 13 | fn ray_intersects_aabb(&self, aabb: &Aabb) -> bool; 14 | } 15 | 16 | #[cfg(not(feature = "simd"))] 17 | impl RayIntersection for Ray { 18 | fn ray_intersects_aabb(&self, aabb: &Aabb) -> bool { 19 | let lbr = (aabb[0].coords - self.origin.coords).component_mul(&self.inv_direction); 20 | let rtr = (aabb[1].coords - self.origin.coords).component_mul(&self.inv_direction); 21 | 22 | if has_nan(&lbr) | has_nan(&rtr) { 23 | // Assumption: the ray is in the plane of an AABB face. Be consistent and 24 | // consider this a non-intersection. This avoids making the result depend 25 | // on which axis/axes have NaN (min/max in the code that follows are not 26 | // commutative). 27 | return false; 28 | } 29 | 30 | let (inf, sup) = lbr.inf_sup(&rtr); 31 | 32 | let tmin = inf.max(); 33 | let tmax = sup.min(); 34 | 35 | tmax >= fast_max(tmin, T::zero()) 36 | } 37 | } 38 | 39 | #[cfg(feature = "simd")] 40 | impl RayIntersection for Ray { 41 | default fn ray_intersects_aabb(&self, aabb: &Aabb) -> bool { 42 | let lbr = (aabb[0].coords - self.origin.coords).component_mul(&self.inv_direction); 43 | let rtr = (aabb[1].coords - self.origin.coords).component_mul(&self.inv_direction); 44 | 45 | if has_nan(&lbr) | has_nan(&rtr) { 46 | return false; 47 | } 48 | 49 | let (inf, sup) = lbr.inf_sup(&rtr); 50 | 51 | let tmin = inf.max(); 52 | let tmax = sup.min(); 53 | 54 | tmax >= fast_max(tmin, T::zero()) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/ray/intersect_simd.rs: -------------------------------------------------------------------------------- 1 | //! This file contains overrides for SIMD implementations of [`RayIntersection`] 2 | //! for any architectures supported by the `wide` crate. 3 | 4 | use nalgebra::SVector; 5 | use wide::*; 6 | 7 | use crate::{ 8 | aabb::Aabb, 9 | utils::{fast_max, fast_min, has_nan}, 10 | }; 11 | 12 | use super::{intersect_default::RayIntersection, Ray}; 13 | 14 | trait ToRegisterType { 15 | type Register; 16 | 17 | fn to_register(&self) -> Self::Register; 18 | } 19 | 20 | impl ToRegisterType for SVector { 21 | type Register = f32x4; 22 | 23 | #[inline(always)] 24 | fn to_register(&self) -> Self::Register { 25 | f32x4::new([self.y, self.y, self.y, self.x]) 26 | } 27 | } 28 | 29 | impl ToRegisterType for SVector { 30 | type Register = f32x4; 31 | 32 | #[inline(always)] 33 | fn to_register(&self) -> Self::Register { 34 | f32x4::new([self.z, self.z, self.y, self.x]) 35 | } 36 | } 37 | 38 | impl ToRegisterType for SVector { 39 | type Register = f32x4; 40 | 41 | #[inline(always)] 42 | fn to_register(&self) -> Self::Register { 43 | f32x4::new([self.w, self.z, self.y, self.x]) 44 | } 45 | } 46 | 47 | /// Compute the horizontal maximum of the SIMD vector 48 | #[inline(always)] 49 | fn max_elem_f32x4(v: f32x4) -> f32 { 50 | let a = v.to_array(); 51 | fast_max(fast_max(a[0], a[1]), fast_max(a[2], a[3])) 52 | } 53 | 54 | /// Compute the horizontal minimum of the SIMD vector 55 | #[inline(always)] 56 | fn min_elem_f32x4(v: f32x4) -> f32 { 57 | let a = v.to_array(); 58 | fast_min(fast_min(a[0], a[1]), fast_min(a[2], a[3])) 59 | } 60 | 61 | #[inline(always)] 62 | fn has_nan_f32x4(v: f32x4) -> bool { 63 | has_nan(&v.to_array()) 64 | } 65 | 66 | #[inline(always)] 67 | fn has_nan_f64x2(v: f64x2) -> bool { 68 | has_nan(&v.to_array()) 69 | } 70 | 71 | #[inline(always)] 72 | fn has_nan_f64x4(v: f64x4) -> bool { 73 | has_nan(&v.to_array()) 74 | } 75 | 76 | #[inline(always)] 77 | fn ray_intersects_aabb_f32x4( 78 | ray_origin: f32x4, 79 | ray_inv_dir: f32x4, 80 | aabb_0: f32x4, 81 | aabb_1: f32x4, 82 | ) -> bool { 83 | let v1 = (aabb_0 - ray_origin) * ray_inv_dir; 84 | let v2 = (aabb_1 - ray_origin) * ray_inv_dir; 85 | 86 | if has_nan_f32x4(v1) | has_nan_f32x4(v2) { 87 | return false; 88 | } 89 | 90 | let inf = v1.fast_min(v2); 91 | let sup = v1.fast_max(v2); 92 | 93 | let tmin = max_elem_f32x4(inf); 94 | let tmax = min_elem_f32x4(sup); 95 | 96 | tmax >= fast_max(tmin, 0.0) 97 | } 98 | 99 | impl RayIntersection for Ray { 100 | #[inline(always)] 101 | fn ray_intersects_aabb(&self, aabb: &Aabb) -> bool { 102 | let ro = self.origin.coords.to_register(); 103 | let ri = self.inv_direction.to_register(); 104 | let aabb_0 = aabb[0].coords.to_register(); 105 | let aabb_1 = aabb[1].coords.to_register(); 106 | 107 | ray_intersects_aabb_f32x4(ro, ri, aabb_0, aabb_1) 108 | } 109 | } 110 | 111 | impl RayIntersection for Ray { 112 | #[inline(always)] 113 | fn ray_intersects_aabb(&self, aabb: &Aabb) -> bool { 114 | let ro = self.origin.coords.to_register(); 115 | let ri = self.inv_direction.to_register(); 116 | let aabb_0 = aabb[0].coords.to_register(); 117 | let aabb_1 = aabb[1].coords.to_register(); 118 | 119 | ray_intersects_aabb_f32x4(ro, ri, aabb_0, aabb_1) 120 | } 121 | } 122 | 123 | impl RayIntersection for Ray { 124 | #[inline(always)] 125 | fn ray_intersects_aabb(&self, aabb: &Aabb) -> bool { 126 | let ro = self.origin.coords.to_register(); 127 | let ri = self.inv_direction.to_register(); 128 | let aabb_0 = aabb[0].coords.to_register(); 129 | let aabb_1 = aabb[1].coords.to_register(); 130 | 131 | ray_intersects_aabb_f32x4(ro, ri, aabb_0, aabb_1) 132 | } 133 | } 134 | 135 | impl ToRegisterType for SVector { 136 | type Register = f64x2; 137 | 138 | #[inline(always)] 139 | fn to_register(&self) -> Self::Register { 140 | f64x2::new([self.y, self.x]) 141 | } 142 | } 143 | 144 | /// Compute the horizontal maximum of the SIMD vector 145 | #[inline(always)] 146 | fn max_elem_f64x2(v: f64x2) -> f64 { 147 | let a = v.to_array(); 148 | fast_max(a[0], a[1]) 149 | } 150 | 151 | /// Compute the horizontal minimum of the SIMD vector 152 | #[inline(always)] 153 | fn min_elem_f64x2(v: f64x2) -> f64 { 154 | let a = v.to_array(); 155 | fast_min(a[0], a[1]) 156 | } 157 | 158 | #[inline(always)] 159 | fn ray_intersects_aabb_f64x2( 160 | ray_origin: f64x2, 161 | ray_inv_dir: f64x2, 162 | aabb_0: f64x2, 163 | aabb_1: f64x2, 164 | ) -> bool { 165 | let v1 = (aabb_0 - ray_origin) * ray_inv_dir; 166 | let v2 = (aabb_1 - ray_origin) * ray_inv_dir; 167 | 168 | if has_nan_f64x2(v1) | has_nan_f64x2(v2) { 169 | return false; 170 | } 171 | 172 | let inf = v1.min(v2); 173 | let sup = v1.max(v2); 174 | 175 | let tmin = max_elem_f64x2(inf); 176 | let tmax = min_elem_f64x2(sup); 177 | 178 | tmax >= fast_max(tmin, 0.0) 179 | } 180 | 181 | impl RayIntersection for Ray { 182 | #[inline(always)] 183 | fn ray_intersects_aabb(&self, aabb: &Aabb) -> bool { 184 | let ro = self.origin.coords.to_register(); 185 | let ri = self.inv_direction.to_register(); 186 | let aabb_0 = aabb[0].coords.to_register(); 187 | let aabb_1 = aabb[1].coords.to_register(); 188 | 189 | ray_intersects_aabb_f64x2(ro, ri, aabb_0, aabb_1) 190 | } 191 | } 192 | 193 | impl ToRegisterType for SVector { 194 | type Register = f64x4; 195 | 196 | #[inline(always)] 197 | fn to_register(&self) -> Self::Register { 198 | f64x4::new([self.z, self.z, self.y, self.x]) 199 | } 200 | } 201 | 202 | impl ToRegisterType for SVector { 203 | type Register = f64x4; 204 | 205 | #[inline(always)] 206 | fn to_register(&self) -> Self::Register { 207 | f64x4::new([self.w, self.z, self.y, self.x]) 208 | } 209 | } 210 | 211 | /// Compute the horizontal maximum of the SIMD vector 212 | #[inline(always)] 213 | fn max_elem_f64x4(v: f64x4) -> f64 { 214 | let a = v.to_array(); 215 | fast_max(fast_max(a[0], a[1]), fast_max(a[2], a[3])) 216 | } 217 | 218 | /// Compute the horizontal minimum of the SIMD vector 219 | #[inline(always)] 220 | fn min_elem_f64x4(v: f64x4) -> f64 { 221 | let a = v.to_array(); 222 | fast_min(fast_min(a[0], a[1]), fast_min(a[2], a[3])) 223 | } 224 | 225 | #[inline(always)] 226 | fn ray_intersects_aabb_f64x4( 227 | ray_origin: f64x4, 228 | ray_inv_dir: f64x4, 229 | aabb_0: f64x4, 230 | aabb_1: f64x4, 231 | ) -> bool { 232 | let v1 = (aabb_0 - ray_origin) * ray_inv_dir; 233 | let v2 = (aabb_1 - ray_origin) * ray_inv_dir; 234 | 235 | if has_nan_f64x4(v1) | has_nan_f64x4(v2) { 236 | return false; 237 | } 238 | 239 | let inf = v1.min(v2); 240 | let sup = v1.max(v2); 241 | 242 | let tmin = max_elem_f64x4(inf); 243 | let tmax = min_elem_f64x4(sup); 244 | 245 | tmax >= fast_max(tmin, 0.0) 246 | } 247 | 248 | impl RayIntersection for Ray { 249 | #[inline(always)] 250 | fn ray_intersects_aabb(&self, aabb: &Aabb) -> bool { 251 | let ro = self.origin.coords.to_register(); 252 | let ri = self.inv_direction.to_register(); 253 | let aabb_0 = aabb[0].coords.to_register(); 254 | let aabb_1 = aabb[1].coords.to_register(); 255 | 256 | ray_intersects_aabb_f64x4(ro, ri, aabb_0, aabb_1) 257 | } 258 | } 259 | 260 | impl RayIntersection for Ray { 261 | #[inline(always)] 262 | fn ray_intersects_aabb(&self, aabb: &Aabb) -> bool { 263 | let ro = self.origin.coords.to_register(); 264 | let ri = self.inv_direction.to_register(); 265 | let aabb_0 = aabb[0].coords.to_register(); 266 | let aabb_1 = aabb[1].coords.to_register(); 267 | 268 | ray_intersects_aabb_f64x4(ro, ri, aabb_0, aabb_1) 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /src/ray/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module holds the [`Ray`] definition, and `RayIntersection` functions. 2 | mod intersect_default; 3 | mod ray_impl; 4 | 5 | #[cfg(feature = "simd")] 6 | mod intersect_simd; 7 | 8 | pub use self::ray_impl::*; 9 | -------------------------------------------------------------------------------- /src/ray/ray_impl.rs: -------------------------------------------------------------------------------- 1 | //! This module defines a Ray structure and intersection algorithms 2 | //! for axis aligned bounding boxes and triangles. 3 | 4 | use std::cmp::Ordering; 5 | 6 | use crate::aabb::IntersectsAabb; 7 | use crate::utils::{fast_max, has_nan}; 8 | use crate::{aabb::Aabb, bounding_hierarchy::BHValue}; 9 | use nalgebra::{ 10 | ClosedAddAssign, ClosedMulAssign, ClosedSubAssign, ComplexField, Point, SVector, SimdPartialOrd, 11 | }; 12 | use num::{Float, One, Zero}; 13 | 14 | use super::intersect_default::RayIntersection; 15 | 16 | /// A struct which defines a ray and some of its cached values. 17 | #[derive(Debug, Clone, Copy)] 18 | pub struct Ray { 19 | /// The ray origin. 20 | pub origin: Point, 21 | 22 | /// The ray direction. 23 | pub direction: SVector, 24 | 25 | /// Inverse (1/x) ray direction. Cached for use in [`Aabb`] intersections. 26 | /// 27 | /// [`Aabb`]: struct.Aabb.html 28 | /// 29 | pub inv_direction: SVector, 30 | } 31 | 32 | /// A struct which is returned by the [`Ray::intersects_triangle()`] method. 33 | pub struct Intersection { 34 | /// Distance from the ray origin to the intersection point. 35 | pub distance: T, 36 | 37 | /// U coordinate of the intersection. 38 | pub u: T, 39 | 40 | /// V coordinate of the intersection. 41 | pub v: T, 42 | } 43 | 44 | impl Intersection { 45 | /// Constructs an [`Intersection`]. `distance` should be set to positive infinity, 46 | /// if the intersection does not occur. 47 | pub fn new(distance: T, u: T, v: T) -> Intersection { 48 | Intersection { distance, u, v } 49 | } 50 | } 51 | 52 | impl Ray { 53 | /// Creates a new [`Ray`] from an `origin` and a `direction`. 54 | /// `direction` will be normalized. 55 | /// 56 | /// # Examples 57 | /// ``` 58 | /// use bvh::ray::Ray; 59 | /// use nalgebra::{Point3,Vector3}; 60 | /// 61 | /// let origin = Point3::new(0.0,0.0,0.0); 62 | /// let direction = Vector3::new(1.0,0.0,0.0); 63 | /// let ray = Ray::new(origin, direction); 64 | /// 65 | /// assert_eq!(ray.origin, origin); 66 | /// assert_eq!(ray.direction, direction); 67 | /// ``` 68 | /// 69 | /// [`Ray`]: struct.Ray.html 70 | /// 71 | pub fn new(origin: Point, direction: SVector) -> Ray 72 | where 73 | T: One + ComplexField, 74 | { 75 | let direction = direction.normalize(); 76 | Ray { 77 | origin, 78 | direction, 79 | inv_direction: direction.map(|x| T::one() / x), 80 | } 81 | } 82 | 83 | /// Tests the intersection of a [`Ray`] with an [`Aabb`] using the optimized algorithm 84 | /// from [this paper](http://www.cs.utah.edu/~awilliam/box/box.pdf). 85 | /// 86 | /// # Examples 87 | /// ``` 88 | /// use bvh::aabb::Aabb; 89 | /// use bvh::ray::Ray; 90 | /// use nalgebra::{Point3,Vector3}; 91 | /// 92 | /// let origin = Point3::new(0.0,0.0,0.0); 93 | /// let direction = Vector3::new(1.0,0.0,0.0); 94 | /// let ray = Ray::new(origin, direction); 95 | /// 96 | /// let point1 = Point3::new(99.9,-1.0,-1.0); 97 | /// let point2 = Point3::new(100.1,1.0,1.0); 98 | /// let aabb = Aabb::with_bounds(point1, point2); 99 | /// 100 | /// assert!(ray.intersects_aabb(&aabb)); 101 | /// ``` 102 | /// 103 | /// [`Ray`]: struct.Ray.html 104 | /// [`Aabb`]: struct.Aabb.html 105 | /// 106 | pub fn intersects_aabb(&self, aabb: &Aabb) -> bool 107 | where 108 | T: ClosedSubAssign + ClosedMulAssign + Zero + PartialOrd + SimdPartialOrd, 109 | { 110 | self.ray_intersects_aabb(aabb) 111 | } 112 | 113 | /// Intersect [`Aabb`] by [`Ray`] 114 | /// Returns slice of intersections, two numbers `T` 115 | /// where the first number is the distance from [`Ray`] to the nearest intersection point 116 | /// and the second number is the distance from [`Ray`] to the farthest intersection point 117 | /// 118 | /// If there are no intersections, it returns `None`. 119 | pub fn intersection_slice_for_aabb(&self, aabb: &Aabb) -> Option<(T, T)> 120 | where 121 | T: BHValue, 122 | { 123 | // Copied from the default `RayIntersection` implementation. TODO: abstract and add SIMD. 124 | let lbr = (aabb[0].coords - self.origin.coords).component_mul(&self.inv_direction); 125 | let rtr = (aabb[1].coords - self.origin.coords).component_mul(&self.inv_direction); 126 | 127 | if has_nan(&lbr) | has_nan(&rtr) { 128 | // Assumption: the ray is in the plane of an AABB face. Be consistent and 129 | // consider this a non-intersection. This avoids making the result depend 130 | // on which axis/axes have NaN (min/max in the code that follows are not 131 | // commutative). 132 | return None; 133 | } 134 | 135 | let (inf, sup) = lbr.inf_sup(&rtr); 136 | 137 | let tmin = fast_max(inf.max(), T::zero()); 138 | let tmax = sup.min(); 139 | 140 | if matches!(tmin.partial_cmp(&tmax), Some(Ordering::Greater) | None) { 141 | // tmin > tmax or either was NaN, meaning no intersection. 142 | return None; 143 | } 144 | 145 | Some((tmin, tmax)) 146 | } 147 | 148 | /// Implementation of the 149 | /// [Möller-Trumbore triangle/ray intersection algorithm](https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm). 150 | /// Returns the distance to the intersection, as well as 151 | /// the u and v coordinates of the intersection. 152 | /// The distance is set to +INFINITY if the ray does not intersect the triangle, or hits 153 | /// it from behind. 154 | #[allow(clippy::many_single_char_names)] 155 | pub fn intersects_triangle( 156 | &self, 157 | a: &Point, 158 | b: &Point, 159 | c: &Point, 160 | ) -> Intersection 161 | where 162 | T: ClosedAddAssign + ClosedSubAssign + ClosedMulAssign + Zero + One + Float, 163 | { 164 | let a_to_b = *b - *a; 165 | let a_to_c = *c - *a; 166 | 167 | // Begin calculating determinant - also used to calculate u parameter 168 | // u_vec lies in view plane 169 | // length of a_to_c in view_plane = |u_vec| = |a_to_c|*sin(a_to_c, dir) 170 | let u_vec = self.direction.cross(&a_to_c); 171 | 172 | // If determinant is near zero, ray lies in plane of triangle 173 | // The determinant corresponds to the parallelepiped volume: 174 | // det = 0 => [dir, a_to_b, a_to_c] not linearly independant 175 | let det = a_to_b.dot(&u_vec); 176 | 177 | // Only testing positive bound, thus enabling backface culling 178 | // If backface culling is not desired write: 179 | // det < EPSILON && det > -EPSILON 180 | if det < T::epsilon() { 181 | return Intersection::new(T::infinity(), T::zero(), T::zero()); 182 | } 183 | 184 | let inv_det = T::one() / det; 185 | 186 | // Vector from point a to ray origin 187 | let a_to_origin = self.origin - *a; 188 | 189 | // Calculate u parameter 190 | let u = a_to_origin.dot(&u_vec) * inv_det; 191 | 192 | // Test bounds: u < 0 || u > 1 => outside of triangle 193 | if !(T::zero()..=T::one()).contains(&u) { 194 | return Intersection::new(T::infinity(), u, T::zero()); 195 | } 196 | 197 | // Prepare to test v parameter 198 | let v_vec = a_to_origin.cross(&a_to_b); 199 | 200 | // Calculate v parameter and test bound 201 | let v = self.direction.dot(&v_vec) * inv_det; 202 | // The intersection lies outside of the triangle 203 | if v < T::zero() || u + v > T::one() { 204 | return Intersection::new(T::infinity(), u, v); 205 | } 206 | 207 | let dist = a_to_c.dot(&v_vec) * inv_det; 208 | 209 | if dist > T::epsilon() { 210 | Intersection::new(dist, u, v) 211 | } else { 212 | Intersection::new(T::infinity(), u, v) 213 | } 214 | } 215 | } 216 | 217 | #[cfg(test)] 218 | mod tests { 219 | use std::cmp; 220 | 221 | use crate::{ 222 | aabb::Bounded, 223 | testbase::{ 224 | tuple_to_point, tuplevec_small_strategy, TAabb3, TPoint3, TRay3, TVector3, TupleVec, 225 | UnitBox, 226 | }, 227 | }; 228 | 229 | use proptest::prelude::*; 230 | 231 | /// Generates a random [`Ray`] which points at at a random [`Aabb`]. 232 | fn gen_ray_to_aabb(data: (TupleVec, TupleVec, TupleVec)) -> (TRay3, TAabb3) { 233 | // Generate a random `Aabb` 234 | let aabb = TAabb3::empty() 235 | .grow(&tuple_to_point(&data.0)) 236 | .grow(&tuple_to_point(&data.1)); 237 | 238 | // Get its center 239 | let center = aabb.center(); 240 | 241 | // Generate random ray pointing at the center 242 | let pos = tuple_to_point(&data.2); 243 | let ray = TRay3::new(pos, center - pos); 244 | (ray, aabb) 245 | } 246 | 247 | /// Make sure a ray can intersect an AABB with no depth. 248 | #[test] 249 | fn ray_hits_zero_depth_aabb() { 250 | let origin = TPoint3::new(0.0, 0.0, 0.0); 251 | let direction = TVector3::new(0.0, 0.0, 1.0); 252 | let ray = TRay3::new(origin, direction); 253 | let min = TPoint3::new(-1.0, -1.0, 1.0); 254 | let max = TPoint3::new(1.0, 1.0, 1.0); 255 | let aabb = TAabb3::with_bounds(min, max); 256 | assert!(ray.intersects_aabb(&aabb)); 257 | } 258 | 259 | /// Ensure slice has correct min and max distance for a particular case. 260 | #[test] 261 | fn test_ray_slice_distance_accuracy() { 262 | let aabb = TAabb3::empty() 263 | .grow(&TPoint3::new(-3.0, -4.0, -5.0)) 264 | .grow(&TPoint3::new(-6.0, -8.0, 5.0)); 265 | let ray = TRay3::new( 266 | TPoint3::new(2.0, 2.0, 2.0), 267 | TVector3::new(-5.0, -8.66666, -3.666666), 268 | ); 269 | let expected_min = 10.6562; 270 | let expected_max = 12.3034; 271 | let (min, max) = ray.intersection_slice_for_aabb(&aabb.aabb()).unwrap(); 272 | assert!((min - expected_min).abs() < 0.01); 273 | assert!((max - expected_max).abs() < 0.01); 274 | } 275 | 276 | /// Ensure no slice is returned when the ray is parallel to AABB faces but 277 | /// doesn't hit, which is a special case due to infinities in the computation. 278 | #[test] 279 | fn test_parallel_ray_slice() { 280 | let aabb = UnitBox::new(0, TPoint3::new(-50.0, -50.0, -25.0)); 281 | let ray = TRay3::new( 282 | TPoint3::new(-50.0, -50.0, -50.0), 283 | TVector3::new(1.0, 0.0, 0.0), 284 | ); 285 | assert!(ray.intersection_slice_for_aabb(&aabb.aabb()).is_none()); 286 | } 287 | 288 | /// Ensure no slice is returned when the ray is in the plane of an AABB 289 | /// face, which is a special case due to NaN's in the computation. 290 | #[test] 291 | fn test_in_plane_ray_slice() { 292 | let aabb = UnitBox::new(0, TPoint3::new(0.0, 0.0, 0.0)).aabb(); 293 | let ray = TRay3::new(TPoint3::new(0.0, 0.0, -0.5), TVector3::new(1.0, 0.0, 0.0)); 294 | assert!(!ray.intersects_aabb(&aabb)); 295 | assert!(ray.intersection_slice_for_aabb(&aabb).is_none()); 296 | 297 | // Test a different ray direction, to ensure that order of `fast_max` (relevant 298 | // to the result when NaN's are involved) doesn't matter. 299 | let ray = TRay3::new(TPoint3::new(0.0, 0.5, 0.0), TVector3::new(0.0, 0.0, 1.0)); 300 | assert!(!ray.intersects_aabb(&aabb)); 301 | assert!(ray.intersection_slice_for_aabb(&aabb).is_none()); 302 | } 303 | 304 | proptest! { 305 | // Test whether a `Ray` which points at the center of an `Aabb` intersects it. 306 | #[test] 307 | fn test_ray_points_at_aabb_center(data in (tuplevec_small_strategy(), 308 | tuplevec_small_strategy(), 309 | tuplevec_small_strategy())) { 310 | let (ray, aabb) = gen_ray_to_aabb(data); 311 | assert!(ray.intersects_aabb(&aabb)); 312 | } 313 | 314 | // Test whether a `Ray` which points away from the center of an `Aabb` 315 | // does not intersect it, unless its origin is inside the `Aabb`. 316 | #[test] 317 | fn test_ray_points_from_aabb_center(data in (tuplevec_small_strategy(), 318 | tuplevec_small_strategy(), 319 | tuplevec_small_strategy())) { 320 | let (mut ray, aabb) = gen_ray_to_aabb(data); 321 | 322 | // Invert the direction of the ray 323 | ray.direction = -ray.direction; 324 | ray.inv_direction = -ray.inv_direction; 325 | assert!(!ray.intersects_aabb(&aabb) || aabb.contains(&ray.origin)); 326 | } 327 | 328 | // Test whether a `Ray` which points at the center of an `Aabb` takes intersection slice. 329 | #[test] 330 | fn test_ray_slice_at_aabb_center(data in (tuplevec_small_strategy(), 331 | tuplevec_small_strategy(), 332 | tuplevec_small_strategy())) { 333 | let (ray, aabb) = gen_ray_to_aabb(data); 334 | let (start_dist, end_dist) = ray.intersection_slice_for_aabb(&aabb).unwrap(); 335 | assert!(start_dist < end_dist); 336 | assert!(start_dist >= 0.0); 337 | } 338 | 339 | // Test whether a `Ray` which points away from the center of an `Aabb` 340 | // cannot take intersection slice of it, unless its origin is inside the `Aabb`. 341 | #[test] 342 | fn test_ray_slice_from_aabb_center(data in (tuplevec_small_strategy(), 343 | tuplevec_small_strategy(), 344 | tuplevec_small_strategy())) { 345 | let (mut ray, aabb) = gen_ray_to_aabb(data); 346 | 347 | // Invert the direction of the ray 348 | ray.direction = -ray.direction; 349 | ray.inv_direction = -ray.inv_direction; 350 | 351 | let slice = ray.intersection_slice_for_aabb(&aabb); 352 | if aabb.contains(&ray.origin) { 353 | let (start_dist, end_dist) = slice.unwrap(); 354 | // ray inside of aabb 355 | assert!(start_dist < end_dist); 356 | assert!(start_dist >= 0.0); 357 | } else { 358 | // ray outside of aabb and doesn't intersect it 359 | assert!(slice.is_none()); 360 | } 361 | } 362 | 363 | // Test whether a `Ray` which points at the center of a triangle 364 | // intersects it, unless it sees the back face, which is culled. 365 | #[test] 366 | fn test_ray_hits_triangle(a in tuplevec_small_strategy(), 367 | b in tuplevec_small_strategy(), 368 | c in tuplevec_small_strategy(), 369 | origin in tuplevec_small_strategy(), 370 | u: u16, 371 | v: u16) { 372 | // Define a triangle, u/v vectors and its normal 373 | let triangle = (tuple_to_point(&a), tuple_to_point(&b), tuple_to_point(&c)); 374 | let u_vec = triangle.1 - triangle.0; 375 | let v_vec = triangle.2 - triangle.0; 376 | let normal = u_vec.cross(&v_vec); 377 | 378 | // Get some u and v coordinates such that u+v <= 1 379 | let u = u % 101; 380 | let v = cmp::min(100 - u, v % 101); 381 | let u = u as f32 / 100.0; 382 | let v = v as f32 / 100.0; 383 | 384 | // Define some point on the triangle 385 | let point_on_triangle = triangle.0 + u * u_vec + v * v_vec; 386 | 387 | // Define a ray which points at the triangle 388 | let origin = tuple_to_point(&origin); 389 | let ray = TRay3::new(origin, point_on_triangle - origin); 390 | let on_back_side = normal.dot(&(ray.origin - triangle.0)) <= 0.0; 391 | 392 | // Perform the intersection test 393 | let intersects = ray.intersects_triangle(&triangle.0, &triangle.1, &triangle.2); 394 | let uv_sum = intersects.u + intersects.v; 395 | 396 | // Either the intersection is in the back side (including the triangle-plane) 397 | if on_back_side { 398 | // Intersection must be INFINITY, u and v are undefined 399 | assert!(intersects.distance == f32::INFINITY); 400 | } else { 401 | // Or it is on the front side 402 | // Either the intersection is inside the triangle, which it should be 403 | // for all u, v such that u+v <= 1.0 404 | let intersection_inside = (0.0..=1.0).contains(&uv_sum) && intersects.distance < f32::INFINITY; 405 | 406 | // Or the input data was close to the border 407 | let close_to_border = 408 | u.abs() < f32::EPSILON || (u - 1.0).abs() < f32::EPSILON || v.abs() < f32::EPSILON || 409 | (v - 1.0).abs() < f32::EPSILON || (u + v - 1.0).abs() < f32::EPSILON; 410 | 411 | if !(intersection_inside || close_to_border) { 412 | println!("uvsum {uv_sum}"); 413 | println!("intersects.0 {}", intersects.distance); 414 | println!("intersects.1 (u) {}", intersects.u); 415 | println!("intersects.2 (v) {}", intersects.v); 416 | println!("u {u}"); 417 | println!("v {v}"); 418 | } 419 | 420 | assert!(intersection_inside || close_to_border); 421 | } 422 | } 423 | } 424 | } 425 | 426 | impl IntersectsAabb for Ray { 427 | fn intersects_aabb(&self, aabb: &Aabb) -> bool { 428 | self.intersects_aabb(aabb) 429 | } 430 | } 431 | 432 | #[cfg(all(feature = "bench", test))] 433 | mod bench { 434 | use rand::rngs::StdRng; 435 | use rand::{Rng, SeedableRng}; 436 | use test::{black_box, Bencher}; 437 | 438 | use crate::testbase::{tuple_to_point, tuple_to_vector, TAabb3, TRay3, TupleVec}; 439 | 440 | /// Generate a random deterministic `Ray`. 441 | fn random_ray(rng: &mut StdRng) -> TRay3 { 442 | let a = tuple_to_point(&rng.random::()); 443 | let b = tuple_to_vector(&rng.random::()); 444 | TRay3::new(a, b) 445 | } 446 | 447 | /// Generate a random deterministic `Aabb`. 448 | fn random_aabb(rng: &mut StdRng) -> TAabb3 { 449 | let a = tuple_to_point(&rng.random::()); 450 | let b = tuple_to_point(&rng.random::()); 451 | 452 | TAabb3::empty().grow(&a).grow(&b) 453 | } 454 | 455 | /// Generate the ray and boxes used for benchmarks. 456 | fn random_ray_and_boxes() -> (TRay3, Vec) { 457 | let seed = [0; 32]; 458 | let mut rng = StdRng::from_seed(seed); 459 | 460 | let ray = random_ray(&mut rng); 461 | let boxes = (0..1000).map(|_| random_aabb(&mut rng)).collect::>(); 462 | 463 | black_box((ray, boxes)) 464 | } 465 | 466 | /// Benchmark for the optimized intersection algorithm. 467 | #[bench] 468 | fn bench_intersects_aabb(b: &mut Bencher) { 469 | let (ray, boxes) = random_ray_and_boxes(); 470 | 471 | b.iter(|| { 472 | for aabb in &boxes { 473 | black_box(ray.intersects_aabb(aabb)); 474 | } 475 | }); 476 | } 477 | } 478 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | //! Utilities module. 2 | 3 | use crate::bounding_hierarchy::{BHShape, BHValue}; 4 | use crate::bvh::ShapeIndex; 5 | use crate::{aabb::Aabb, bvh::Shapes}; 6 | 7 | use nalgebra::Scalar; 8 | use num::Float; 9 | 10 | /// Fast floating point minimum. This function matches the semantics of 11 | /// 12 | /// ```no_compile 13 | /// if x < y { x } else { y } 14 | /// ``` 15 | /// 16 | /// which has efficient instruction sequences on many platforms (1 instruction on x86). For most 17 | /// values, it matches the semantics of `x.min(y)`; the special cases are: 18 | /// 19 | /// ```text 20 | /// min(-0.0, +0.0); +0.0 21 | /// min(+0.0, -0.0): -0.0 22 | /// min( NaN, 1.0): 1.0 23 | /// min( 1.0, NaN): NaN 24 | /// ``` 25 | /// 26 | /// Note: This exists because [`std::cmp::min`] requires Ord which floating point types do not satisfy 27 | #[inline(always)] 28 | #[allow(dead_code)] 29 | pub fn fast_min(x: T, y: T) -> T { 30 | if x < y { 31 | x 32 | } else { 33 | y 34 | } 35 | } 36 | 37 | /// Fast floating point maximum. This function matches the semantics of 38 | /// 39 | /// ```no_compile 40 | /// if x > y { x } else { y } 41 | /// ``` 42 | /// 43 | /// which has efficient instruction sequences on many platforms (1 instruction on x86). For most 44 | /// values, it matches the semantics of `x.max(y)`; the special cases are: 45 | /// 46 | /// ```text 47 | /// max(-0.0, +0.0); +0.0 48 | /// max(+0.0, -0.0): -0.0 49 | /// max( NaN, 1.0): 1.0 50 | /// max( 1.0, NaN): NaN 51 | /// ``` 52 | /// 53 | /// Note: This exists because [`std::cmp::max`] requires Ord which floating point types do not satisfy 54 | #[inline(always)] 55 | #[allow(dead_code)] 56 | pub fn fast_max(x: T, y: T) -> T { 57 | if x > y { 58 | x 59 | } else { 60 | y 61 | } 62 | } 63 | 64 | /// Defines a Bucket utility object. Used to store the properties of shape-partitions 65 | /// in the [`Bvh`] build procedure using SAH. 66 | #[derive(Clone, Copy)] 67 | pub struct Bucket { 68 | /// The number of shapes in this [`Bucket`]. 69 | pub size: usize, 70 | 71 | /// The joint [`Aabb`] of the shapes in this [`Bucket`]. 72 | pub aabb: Aabb, 73 | 74 | /// The [`Aabb`] of the centers of the shapes in this [`Bucket`] 75 | pub centroid: Aabb, 76 | } 77 | 78 | impl Bucket { 79 | /// Returns an empty bucket. 80 | pub fn empty() -> Bucket { 81 | Bucket { 82 | size: 0, 83 | aabb: Aabb::empty(), 84 | centroid: Aabb::empty(), 85 | } 86 | } 87 | 88 | /// Extend this [`Bucket`] by a shape with the given [`Aabb`]. 89 | pub fn add_aabb(&mut self, aabb: &Aabb) { 90 | self.size += 1; 91 | self.aabb = self.aabb.join(aabb); 92 | self.centroid.grow_mut(&aabb.center()); 93 | } 94 | 95 | /// Join the contents of two [`Bucket`]'s. 96 | pub fn join_bucket(a: Bucket, b: &Bucket) -> Bucket { 97 | Bucket { 98 | size: a.size + b.size, 99 | aabb: a.aabb.join(&b.aabb), 100 | centroid: a.centroid.join(&b.centroid), 101 | } 102 | } 103 | } 104 | 105 | pub(crate) fn joint_aabb_of_shapes>( 106 | indices: &[ShapeIndex], 107 | shapes: &Shapes, 108 | ) -> (Aabb, Aabb) { 109 | let mut aabb = Aabb::empty(); 110 | let mut centroid = Aabb::empty(); 111 | for index in indices { 112 | let shape = shapes.get(*index); 113 | aabb.join_mut(&shape.aabb()); 114 | centroid.grow_mut(&shape.aabb().center()); 115 | } 116 | (aabb, centroid) 117 | } 118 | 119 | /// Returns `true` if and only if any of the floats returned by `iter` are NaN. 120 | #[inline(always)] 121 | pub(crate) fn has_nan<'a, T: Float + 'a>(iter: impl IntoIterator) -> bool { 122 | iter.into_iter().any(|f| f.is_nan()) 123 | } 124 | --------------------------------------------------------------------------------