├── .gitmodules ├── .gitignore ├── src ├── traversal.rs ├── lib.rs ├── ray.rs ├── cxx_ffi.rs └── layouts │ ├── wald.rs │ ├── mod.rs │ └── cwbvh.rs ├── .vscode └── launch.json ├── .github └── workflows │ └── ci.yml ├── Cargo.toml ├── ffi ├── include │ └── tinybvh.h └── src │ └── tinybvh.cpp ├── LICENSE ├── README.md └── tests └── tests.rs /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ffi/tinybvh"] 2 | path = ffi/tinybvh 3 | url = git@github.com:jbikker/tinybvh.git 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Cargo 2 | debug/ 3 | target/ 4 | Cargo.lock 5 | 6 | # MSVC Windows builds of rustc generate these, which store debugging information 7 | *.pdb 8 | 9 | # VSCode 10 | 11 | .vscode/settings.json 12 | -------------------------------------------------------------------------------- /src/traversal.rs: -------------------------------------------------------------------------------- 1 | use crate::Ray; 2 | 3 | /// Intersector for BVH and nodes intersection. 4 | pub trait Intersector { 5 | /// Intersect this instance with a ray. 6 | /// 7 | /// [`Ray::hit`] is mutated with the intersection data. 8 | /// 9 | /// Returns the number of steps (A.K.A intersections) performed. 10 | fn intersect(&self, ray: &mut Ray) -> u32; 11 | } 12 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "lldb", 6 | "request": "launch", 7 | "name": "Integration Tests", 8 | "cargo": { 9 | "args": [ 10 | "test", 11 | "--no-run", 12 | ], 13 | "filter": { 14 | "kind": "test" 15 | } 16 | }, 17 | "args": [], 18 | "cwd": "${workspaceFolder}" 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: tinybv-rs 2 | 3 | on: 4 | pull_request: 5 | 6 | env: 7 | CARGO_TERM_COLOR: always 8 | 9 | jobs: 10 | build_and_test: 11 | name: Build and Test 12 | strategy: 13 | matrix: 14 | platform: [macos-15, windows-latest, ubuntu-latest] 15 | runs-on: ${{ matrix.platform }} 16 | steps: 17 | - uses: actions/checkout@v4 18 | with: 19 | submodules: recursive 20 | - name: Rustup 21 | run: rustup update stable && rustup default stable 22 | - name: Build 23 | run: cargo build --verbose 24 | - name: Test 25 | run: cargo test --verbose 26 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tinybvh-rs" 3 | description = "Rust wrapper for tinybvh" 4 | repository = "https://github.com/DavidPeicho/tinybvh-rs" 5 | version = "0.1.0-beta.2" 6 | edition = "2021" 7 | license = "MIT" 8 | readme = "README.md" 9 | keywords = ["graphics", "rendering", "tinybvh", "bvh"] 10 | include = [ 11 | "build.rs", 12 | "src/**/*.rs", 13 | "ffi/include/**/*.h", 14 | "ffi/src/**/*.cpp", 15 | "ffi/tinybvh/tiny_bvh.h", 16 | "Cargo.toml", 17 | ] 18 | 19 | [dependencies] 20 | bytemuck = { version = "1.20.0", features = ["derive"] } 21 | cxx = "1.0.158" 22 | pas = { version = "0.3.0" } 23 | 24 | [build-dependencies] 25 | cxx-build = "1.0.158" 26 | 27 | [dev-dependencies] 28 | approx = "0.5.1" 29 | -------------------------------------------------------------------------------- /ffi/include/tinybvh.h: -------------------------------------------------------------------------------- 1 | #ifndef TINYBVH_RUST 2 | #define TINYBVH_RUST 3 | 4 | #include 5 | #include 6 | 7 | #include "rust/cxx.h" 8 | #include "tinybvh-rs/ffi/tinybvh/tiny_bvh.h" 9 | 10 | namespace tinybvh { 11 | 12 | /* Math */ 13 | Ray ray_new(const std::array& origin, const std::array& dir); 14 | 15 | /* BVH Wald 32 */ 16 | 17 | using BVHNode = BVH::BVHNode; 18 | std::unique_ptr BVH_new(); 19 | rust::Slice BVH_nodes(const BVH&); 20 | rust::Slice BVH_indices(const BVH&); 21 | 22 | /* CWBVH */ 23 | 24 | struct NodeCWBVH; // TODO: Remove once tinybvh provides a struct for CWBVH node. 25 | 26 | std::unique_ptr CWBVH_new(); 27 | const uint8_t* CWBVH_nodes(const BVH8_CWBVH&); 28 | uint32_t CWBVH_nodes_count(const BVH8_CWBVH&); 29 | const uint8_t* CWBVH_primitives(const BVH8_CWBVH&); 30 | uint32_t CWBVH_primitives_count(const BVH8_CWBVH&); 31 | 32 | } 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(doctest), doc = include_str!("../README.md"))] 2 | 3 | //! # Notes 4 | //! 5 | //! All constructed BVH have a lifetime bound required by tinybvh, which holds to the primitives slice. 6 | 7 | mod cxx_ffi; 8 | mod layouts; 9 | mod ray; 10 | mod traversal; 11 | 12 | pub(crate) use cxx_ffi::ffi; 13 | pub use layouts::*; 14 | pub use ray::*; 15 | pub use traversal::*; 16 | 17 | /// Infinite value used for intersection. 18 | /// 19 | /// **NOTE**: This is not the same as `f32::MAX`. 20 | pub const INFINITE: f32 = 1e30; // Actual valid ieee range: 3.40282347E+38 21 | 22 | /// Alias for a strided slice of positions. 23 | /// 24 | /// Positions do not need to be strided, but the API accepts a strided 25 | /// slice to support both use cases. 26 | /// 27 | /// tinybvh-rs internally requires positions to be vectors of size **4** 28 | /// and not **3**. This is a requirement of the underlying tinybvh library. 29 | pub type Positions<'a> = pas::Slice<'a, [f32; 4]>; 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Albedo 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 | -------------------------------------------------------------------------------- /ffi/src/tinybvh.cpp: -------------------------------------------------------------------------------- 1 | #define TINYBVH_IMPLEMENTATION 2 | #include "tinybvh-rs/ffi/include/tinybvh.h" 3 | 4 | namespace tinybvh { 5 | 6 | /** Utils */ 7 | 8 | Ray ray_new(const std::array& origin, const std::array& dir) { 9 | bvhvec3 o{origin[0], origin[1], origin[2]}; 10 | bvhvec3 d{dir[0], dir[1], dir[2]}; 11 | return tinybvh::Ray{o, d}; 12 | } 13 | 14 | /** Wald BVH */ 15 | 16 | std::unique_ptr BVH_new() { return std::make_unique(); } 17 | rust::Slice BVH_nodes(const BVH& bvh) { 18 | return rust::Slice{const_cast(bvh.bvhNode), bvh.usedNodes}; 19 | } 20 | rust::Slice BVH_indices(const BVH& bvh) { 21 | return rust::Slice{const_cast(bvh.primIdx), bvh.idxCount}; 22 | } 23 | 24 | /** CWBVH */ 25 | 26 | std::unique_ptr CWBVH_new() { return std::make_unique(); } 27 | const uint8_t* CWBVH_nodes(const BVH8_CWBVH& bvh) { return reinterpret_cast(bvh.bvh8Data); } 28 | uint32_t CWBVH_nodes_count(const BVH8_CWBVH& bvh) { 29 | /* tinybvh `usedBlocks` is the number of `vec4`, **not** the number of nodes. */ 30 | return bvh.usedBlocks / 5; 31 | } 32 | const uint8_t* CWBVH_primitives(const BVH8_CWBVH& bvh) { return reinterpret_cast(bvh.bvh8Tris); } 33 | uint32_t CWBVH_primitives_count(const BVH8_CWBVH& bvh) { return bvh.idxCount; } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/ray.rs: -------------------------------------------------------------------------------- 1 | use core::f32; 2 | 3 | use crate::ffi; 4 | 5 | /// Intersection data. 6 | /// 7 | /// Contains intersection distance, as well as barycentric coordinates. 8 | #[repr(C)] 9 | #[derive(Clone, Copy, Debug, Default, PartialEq, bytemuck::Pod, bytemuck::Zeroable)] 10 | pub struct Intersection { 11 | /// Intersection distance. [`crate::INFINITE`] when empty. 12 | pub t: f32, 13 | /// Barycentric weight along the first edge. 14 | pub u: f32, 15 | /// Barycentric weight along the second edge. 16 | pub v: f32, 17 | /// Primitive index. 18 | pub prim: u32, 19 | } 20 | 21 | impl Intersection { 22 | /// Create a new intersection. 23 | /// 24 | /// The intersection distance defaults to[`crate::INFINITE`] with empty 25 | /// barycentric coordinates, and primitive. 26 | pub fn new() -> Self { 27 | Self { 28 | t: crate::INFINITE, 29 | ..Default::default() 30 | } 31 | } 32 | } 33 | 34 | /// Ray data. 35 | /// 36 | /// Origin, distance, and [`Intersection`]. 37 | /// 38 | /// # Notes 39 | /// 40 | /// Padding is unused and required for optimal alignment and performance. 41 | #[repr(C, align(16))] 42 | #[derive(Clone, Copy, Debug, Default, PartialEq, bytemuck::Pod, bytemuck::Zeroable)] 43 | pub struct Ray { 44 | /// Ray origin 45 | pub origin: [f32; 3], 46 | pub padding_0: u32, 47 | /// Ray direction 48 | pub dir: [f32; 3], 49 | pub padding_1: u32, 50 | /// Ray inverse direction. 51 | /// Automatically computed when using [`Ray::new`]. 52 | pub r_d: [f32; 3], 53 | pub padding_2: u32, 54 | /// Ray intersection data. 55 | pub hit: Intersection, 56 | } 57 | 58 | impl Ray { 59 | /// Createa new ray. 60 | /// 61 | /// Automatically computes [`Ray::r_d`]. 62 | pub fn new(origin: [f32; 3], dir: [f32; 3]) -> Self { 63 | ffi::ray_new(&origin, &dir) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tinybvh-rs 2 | 3 | Rust wrapper for [tinybvh](https://github.com/jbikker/tinybvh). 4 | 5 | ## Features 6 | 7 | Provides BVH (Bounding Volume Hierarchy) construction and intersection: 8 | - Construction: [`BVH`], [`BVH4`], [`CWBVH`] 9 | - Intersection 10 | 11 | For more information about each layout: [tinybvh](https://github.com/jbikker/tinybvh). 12 | 13 | ## Examples 14 | 15 | ### BVH Wald 16 | 17 | ```rust 18 | use tinybvh_rs::{Intersector, Ray}; 19 | 20 | let primitives = vec![ 21 | [-2.0, 1.0, -1.0, 0.0], // 22 | [-1.0, 1.0, -1.0, 0.0], // Left triangle 23 | [-2.0, 0.0, -1.0, 0.0], // 24 | 25 | [2.0, 1.0, -1.0, 0.0], // 26 | [2.0, 0.0, -1.0, 0.0], // Right triangle 27 | [1.0, 0.0, -1.0, 0.0], // 28 | ]; 29 | 30 | let bvh = wald::BVH::new(&primitives); 31 | 32 | // No intersection, ray pass between the primitives 33 | let mut ray = Ray::new([0.0, 0.0, 0.0], [0.0, 0.0, -1.0]); 34 | bvh.intersect(&mut ray); 35 | println!("Hit distance: {}", ray.hit.t); // 1e30 36 | 37 | // Intersects left primitive 38 | let mut ray = Ray::new([-1.5, 0.5, 0.0], [0.0, 0.0, -1.0]); 39 | bvh.intersect(&mut ray); 40 | println!("Hit distance & primtive: {} / {}", ray.hit.t, ray.hit.prim); // 1.0 / 0 41 | 42 | // Intersects right primitive 43 | let mut ray = Ray::new([1.5, 0.45, 0.0], [0.0, 0.0, -1.0]); 44 | bvh.intersect(&mut ray); 45 | println!("Hit distance & primtive: {} / {}", ray.hit.t, ray.hit.prim); // 1.0 / 1 46 | ``` 47 | 48 | ### Strided 49 | 50 | If the vertices position are strided (located in a `Vertex` struct for instance), 51 | you can enable the `strided` feature and use: 52 | 53 | ```rust 54 | use tinybvh_rs::{Intersector, Ray}; 55 | 56 | #[repr(C)] 57 | #[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] 58 | struct Vertex { 59 | position: [f32; 4], 60 | normal: [f32; 3], 61 | } 62 | 63 | let vertices = [ 64 | Vertex { 65 | position: [-1.0, 1.0, -1.0, 0.0], 66 | normal: [0.0, 0.0, 1.0] 67 | }, 68 | Vertex { 69 | position: [-0.5, 1.0, -1.0, 0.0], 70 | normal: [0.0, 0.0, 1.0] 71 | }, 72 | Vertex { 73 | position: [-1.0, 0.0, -1.0, 0.0], 74 | normal: [0.0, 0.0, 1.0] 75 | }, 76 | ]; 77 | let positions = pas::slice_attr!(vertices, [0].position); 78 | let bvh = wald::BVH::new(positions); 79 | ``` 80 | -------------------------------------------------------------------------------- /src/cxx_ffi.rs: -------------------------------------------------------------------------------- 1 | #[repr(C)] 2 | #[derive(Clone, Copy, Debug, PartialEq)] 3 | pub struct Vec4Slice { 4 | data: *const i32, 5 | count: u32, 6 | stride: u32, 7 | } 8 | 9 | impl From> for Vec4Slice { 10 | fn from(value: pas::Slice<[f32; 4]>) -> Self { 11 | Self { 12 | data: value.as_ptr() as *const i32, 13 | count: value.len() as u32, 14 | stride: value.stride() as u32, 15 | } 16 | } 17 | } 18 | 19 | // Ensure `bvhvec4slice` always has a trivial move ctor and no destructor 20 | unsafe impl cxx::ExternType for Vec4Slice { 21 | type Id = cxx::type_id!("tinybvh::bvhvec4slice"); 22 | type Kind = cxx::kind::Trivial; 23 | } 24 | // Ensure `Intersection` always has a trivial move ctor and no destructor 25 | unsafe impl cxx::ExternType for crate::Intersection { 26 | type Id = cxx::type_id!("tinybvh::Intersection"); 27 | type Kind = cxx::kind::Trivial; 28 | } 29 | // Ensure `Ray` always has a trivial move ctor and no destructor 30 | unsafe impl cxx::ExternType for crate::Ray { 31 | type Id = cxx::type_id!("tinybvh::Ray"); 32 | type Kind = cxx::kind::Trivial; 33 | } 34 | // Ensure `BVH::BVHNode` always has a trivial move ctor and no destructor 35 | unsafe impl cxx::ExternType for crate::wald::Node { 36 | type Id = cxx::type_id!("tinybvh::BVHNode"); 37 | type Kind = cxx::kind::Trivial; 38 | } 39 | 40 | #[cxx::bridge(namespace = "tinybvh")] 41 | pub(crate) mod ffi { 42 | unsafe extern "C++" { 43 | include!("tinybvh-rs/ffi/include/tinybvh.h"); 44 | 45 | // Utils 46 | pub type bvhvec4slice = super::Vec4Slice; 47 | pub type Ray = crate::Ray; 48 | pub fn ray_new(origin: &[f32; 3], dir: &[f32; 3]) -> Ray; 49 | 50 | // BVH 51 | pub type BVH; 52 | pub type BVHNode = crate::wald::Node; 53 | pub fn BVH_new() -> UniquePtr; 54 | pub fn BVH_nodes(bvh: &BVH) -> &[BVHNode]; 55 | pub fn BVH_indices(bvh: &BVH) -> &[u32]; 56 | pub fn Build(self: Pin<&mut BVH>, primitives: &bvhvec4slice); 57 | pub fn BuildHQ(self: Pin<&mut BVH>, primitives: &bvhvec4slice); 58 | pub fn Compact(self: Pin<&mut BVH>); 59 | pub fn SAHCost(self: &BVH, node_idx: u32) -> f32; 60 | pub fn PrimCount(self: &BVH, node_idx: u32) -> i32; 61 | pub fn Intersect(self: &BVH, original: &mut Ray) -> i32; 62 | 63 | // CWBVH 64 | pub type BVH8_CWBVH; 65 | pub fn CWBVH_new() -> UniquePtr; 66 | pub fn CWBVH_nodes(bvh: &BVH8_CWBVH) -> *const u8; 67 | pub fn CWBVH_nodes_count(bvh: &BVH8_CWBVH) -> u32; 68 | pub fn CWBVH_primitives(bvh: &BVH8_CWBVH) -> *const u8; 69 | pub fn CWBVH_primitives_count(bvh: &BVH8_CWBVH) -> u32; 70 | pub fn Build(self: Pin<&mut BVH8_CWBVH>, primitives: &bvhvec4slice); 71 | pub fn BuildHQ(self: Pin<&mut BVH8_CWBVH>, primitives: &bvhvec4slice); 72 | pub fn Intersect(self: &BVH8_CWBVH, original: &mut Ray) -> i32; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/layouts/wald.rs: -------------------------------------------------------------------------------- 1 | use crate::ffi; 2 | use std::{fmt::Debug, marker::PhantomData}; 3 | 4 | /// "Traditional" 32-bytes BVH node layout, as proposed by Ingo Wald. 5 | /// 6 | /// Node layout used by [`BVH`]. 7 | #[repr(C)] 8 | #[derive(Clone, Copy, Default, Debug, PartialEq, bytemuck::Pod, bytemuck::Zeroable)] 9 | pub struct Node { 10 | /// AABB min position. 11 | pub min: [f32; 3], 12 | /// If the node is a leaf, this is the start index of the primitive. 13 | /// Otherwise, this is the start index of the first child node. 14 | pub left_first: u32, 15 | /// AABB max position. 16 | pub max: [f32; 3], 17 | /// If the node is a leaf, number of triangles in the node. 18 | /// `0` otherwise. 19 | pub tri_count: u32, 20 | } 21 | 22 | impl Node { 23 | /// Returns `true` if the node is a leaf. 24 | pub fn is_leaf(&self) -> bool { 25 | self.tri_count > 0 26 | } 27 | } 28 | 29 | /// BVH with node layout [`Node`]. 30 | /// 31 | /// # Examples 32 | /// 33 | /// ``` 34 | /// use tinybvh_rs::wald; 35 | /// 36 | /// let triangles = vec![ 37 | /// [-1.0, 1.0, 0.0, 0.0], 38 | /// [1.0, 1.0, 0.0, 0.0], 39 | /// [-1.0, 0.0, 0.0, 0.0] 40 | /// ]; 41 | /// let bvh = wald::BVH::new(&triangles); 42 | /// ``` 43 | pub struct BVH<'a> { 44 | inner: cxx::UniquePtr, 45 | _phantom: PhantomData<&'a [f32; 4]>, 46 | } 47 | 48 | impl<'a> BVH<'a> { 49 | // Remove unused nodes and reduce the size of the BVH. 50 | pub fn compact(&mut self) { 51 | self.inner.pin_mut().Compact(); 52 | } 53 | 54 | /// Number of primitives for a given node. 55 | pub fn primitive_count(&self, id: u32) -> u32 { 56 | self.inner.PrimCount(id) as u32 57 | } 58 | 59 | /// SAH cost for a subtree. 60 | pub fn sah_cost(&self, id: u32) -> f32 { 61 | self.inner.SAHCost(id) 62 | } 63 | 64 | /// BVH nodes. 65 | /// 66 | /// Useful to upload to the BVH to the GPU. 67 | pub fn nodes(&self) -> &[Node] { 68 | ffi::BVH_nodes(&self.inner) 69 | } 70 | 71 | /// BVH indices. 72 | /// 73 | /// Map from primitive index to first vertex index. 74 | /// 75 | /// # Examples 76 | /// 77 | /// ```ignore 78 | /// for i in 0..node.tri_count { 79 | /// let vertex_start = bvh.indices()[node.left_first + i] * 3; 80 | /// let vertex = [ 81 | /// primitives[vertex_start], 82 | /// primitives[vertex_start + 1], 83 | /// primitives[vertex_start + 2] 84 | /// ]; 85 | /// println!("Vertex {:?}", vertex); 86 | /// } 87 | /// ``` 88 | pub fn indices(&self) -> &[u32] { 89 | ffi::BVH_indices(&self.inner) 90 | } 91 | 92 | pub fn new_internal() -> Self { 93 | Self { 94 | inner: ffi::BVH_new(), 95 | _phantom: PhantomData, 96 | } 97 | } 98 | } 99 | super::impl_bvh!(BVH, BVH); 100 | 101 | impl crate::Intersector for BVH<'_> { 102 | fn intersect(&self, ray: &mut crate::Ray) -> u32 { 103 | self.inner.Intersect(ray) as u32 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/layouts/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod cwbvh; 2 | pub mod wald; 3 | 4 | /// Holds BVH data without lifetfime bound. 5 | /// 6 | /// This is safe because the BHV canno't be used while captured. 7 | pub struct Capture { 8 | inner: T, 9 | } 10 | 11 | /// Implement shared BVH layout. 12 | /// 13 | /// - Temporarily move the BVH to edit the triangles 14 | /// - `update()` 15 | macro_rules! impl_bvh { 16 | ($name: ident, $ffi_name: ident) => { 17 | impl<'a> $name<'a> { 18 | /// Create a new BVH from a strided slice of positions. 19 | /// 20 | /// # Notes 21 | /// 22 | /// The `primitives` slice must contain 3 positions per primitive. 23 | pub fn new>>(primitives: S) -> Self { 24 | Self::new_internal().build(primitives) 25 | } 26 | 27 | /// Create a new BVH from positions. 28 | /// 29 | /// # Notes 30 | /// 31 | /// Uses [`Self::build_hq`] 32 | pub fn new_hq>>(primitives: S) -> Self { 33 | Self::new_internal().build_hq(primitives) 34 | } 35 | 36 | /// Create the BVH from a capture. 37 | /// 38 | /// At the opposite of [`$name:new`], this method might not re-allocate 39 | /// the BVH data, and instead re-use the captured ones. 40 | pub fn from_capture>>( 41 | capture: crate::Capture>, 42 | primitives: S, 43 | ) -> Self { 44 | Self { 45 | inner: capture.inner, 46 | _phantom: PhantomData, 47 | } 48 | .build(primitives) 49 | } 50 | 51 | /// Rebuild the BVH layout. 52 | /// 53 | /// For complex BVH types, this can result in multiple builds. 54 | pub fn build>>(mut self, primitives: S) -> Self { 55 | let slice = primitives.into(); 56 | if slice.len() % 3 != 0 { 57 | panic!("primitives slice must triangulated (size multiple of 3)") 58 | } 59 | self.inner.pin_mut().Build(&slice.into()); 60 | Self { 61 | inner: self.inner, 62 | _phantom: PhantomData, 63 | } 64 | } 65 | 66 | /// Rebuild the BVH layout using a high quality builder. 67 | /// 68 | /// For more_hq information: [tinybvh README.md](https://github.com/jbikker/tinybvh/blob/main/README.md). 69 | pub fn build_hq>>(mut self, primitives: S) -> Self { 70 | let slice = primitives.into(); 71 | if slice.len() % 3 != 0 { 72 | panic!("primitives slice must triangulated (size multiple of 3)") 73 | } 74 | self.inner.pin_mut().BuildHQ(&slice.into()); 75 | Self { 76 | inner: self.inner, 77 | _phantom: PhantomData, 78 | } 79 | } 80 | 81 | /// Temporarily move the BVH to loosen the primitives lifetime. 82 | /// 83 | /// Useful if editing the primitives is required, without re-allocating 84 | /// the entire BVH. 85 | /// 86 | /// # Examples 87 | /// 88 | /// ``` 89 | /// use tinybvh_rs::wald::BVH; 90 | /// 91 | /// let mut triangles = vec![ 92 | /// [-1.0, 1.0, 0.0, 0.0], 93 | /// [1.0, 1.0, 0.0, 0.0], 94 | /// [-1.0, 0.0, 0.0, 0.0] 95 | /// ]; 96 | /// let bvh = BVH::new(&triangles); 97 | /// let capture = bvh.capture(); 98 | /// triangles[0][0] = -10.0; 99 | /// let bvh = BVH::from_capture(capture, &triangles); 100 | /// ``` 101 | pub fn capture(self) -> crate::Capture> { 102 | crate::Capture { inner: self.inner } 103 | } 104 | } 105 | }; 106 | } 107 | pub(super) use impl_bvh; 108 | -------------------------------------------------------------------------------- /src/layouts/cwbvh.rs: -------------------------------------------------------------------------------- 1 | use crate::ffi; 2 | use std::{fmt::Debug, marker::PhantomData}; 3 | 4 | pub struct PrimitiveIter { 5 | primitive_base_index: u32, 6 | child_meta: [u8; 8], 7 | 8 | curr_meta_idx: u8, 9 | curr_tri_count: u8, 10 | } 11 | 12 | impl PrimitiveIter { 13 | fn new(base_index: u32, meta: [u8; 8]) -> Self { 14 | Self { 15 | primitive_base_index: base_index, 16 | child_meta: meta, 17 | 18 | curr_meta_idx: 0, 19 | curr_tri_count: 0, 20 | } 21 | } 22 | } 23 | 24 | impl Iterator for PrimitiveIter { 25 | type Item = u32; 26 | 27 | fn next(&mut self) -> Option { 28 | if self.curr_meta_idx as usize >= self.child_meta.len() { 29 | return None; 30 | } 31 | while self.curr_meta_idx < self.child_meta.len() as u8 { 32 | let meta = self.child_meta[self.curr_meta_idx as usize]; 33 | let triangles_count = (meta & 0b11100000).count_ones() as u8; 34 | let current_tri_count = self.curr_tri_count; 35 | self.curr_tri_count += 1; 36 | if current_tri_count < triangles_count { 37 | let start = meta & 0b00011111; 38 | return Some(self.primitive_base_index + start as u32 + current_tri_count as u32); 39 | } 40 | self.curr_meta_idx += 1; 41 | self.curr_tri_count = 0; 42 | } 43 | None 44 | } 45 | } 46 | 47 | /// Format specified in: 48 | /// "Efficient Incoherent Ray Traversal on GPUs Through Compressed Wide BVHs", Ylitie et al. 2017. 49 | /// 50 | /// Node layout used by [`BVH`]. 51 | #[repr(C)] 52 | #[derive(Clone, Copy, Debug, Default, PartialEq, bytemuck::Pod, bytemuck::Zeroable)] 53 | pub struct Node { 54 | /// AABB min. 55 | pub min: [f32; 3], 56 | /// Exponent used for child AABB decompression. 57 | pub exyz: [u8; 3], 58 | /// `1` for node, `0` for leaf. 59 | pub imask: u8, 60 | /// First child index. 61 | pub child_base_idx: u32, 62 | // First primitive index. 63 | pub primitive_base_idx: u32, 64 | /// Child [0..7] metadata. 65 | pub child_meta: [u8; 8], 66 | // AABB minimum x-axis compressed bound, one entry per child. 67 | pub qlo_x: [u8; 8], 68 | // AABB minimum y-axis compressed bound, one entry per child. 69 | pub qlo_y: [u8; 8], 70 | // AABB minimum z-axis compressed bound, one entry per child. 71 | pub qlo_z: [u8; 8], 72 | // AABB maximum x-axis compressed bound, one entry per child. 73 | pub qhi_x: [u8; 8], 74 | // AABB maximum y-axis compressed bound, one entry per child. 75 | pub qhi_y: [u8; 8], 76 | // AABB maximum z-axis compressed bound, one entry per child. 77 | pub qhi_z: [u8; 8], 78 | } 79 | 80 | impl Node { 81 | /// Returns `true` if the node is a leaf. 82 | pub fn is_leaf(&self) -> bool { 83 | self.imask == 0 84 | } 85 | 86 | pub fn primitives(&self) -> PrimitiveIter { 87 | if !self.is_leaf() { 88 | return PrimitiveIter::new(0, [0, 0, 0, 0, 0, 0, 0, 0]); 89 | } 90 | PrimitiveIter::new(self.primitive_base_idx, self.child_meta) 91 | } 92 | } 93 | 94 | /// Custom primitive used by [`BVH`]. 95 | #[repr(C)] 96 | #[derive(Clone, Copy, Default, PartialEq, bytemuck::Pod, bytemuck::Zeroable)] 97 | pub struct Primitive { 98 | pub edge_1: [f32; 3], 99 | pub padding_0: u32, 100 | pub edge_2: [f32; 3], 101 | pub padding_1: u32, 102 | pub vertex_0: [f32; 3], 103 | pub original_primitive: u32, 104 | } 105 | 106 | impl Debug for Primitive { 107 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 108 | f.debug_struct("cwbvh::Primitive") 109 | .field("vertex_0", &self.vertex_0) 110 | .field("edge_1", &self.edge_1) 111 | .field("edge_2", &self.edge_2) 112 | .field("original_primitive", &self.original_primitive) 113 | .finish() 114 | } 115 | } 116 | 117 | /// CWBVH with node layout [`Node`]. 118 | pub struct BVH<'a> { 119 | inner: cxx::UniquePtr, 120 | _phantom: PhantomData<&'a [f32; 4]>, 121 | } 122 | 123 | impl<'a> BVH<'a> { 124 | pub fn nodes(&self) -> &[Node] { 125 | // TODO: Create CWBVH node in tinybvh to avoid that. 126 | let ptr = ffi::CWBVH_nodes(&self.inner) as *const Node; 127 | let count = ffi::CWBVH_nodes_count(&self.inner); 128 | unsafe { std::slice::from_raw_parts(ptr, count as usize) } 129 | } 130 | 131 | /// Encoded primitive data. 132 | /// 133 | /// This layout is intersected using a custom primitive array 134 | /// instead of the original list used during building. 135 | pub fn primitives(&self) -> &[Primitive] { 136 | // TODO: Create struct in tinybvh to avoid that. 137 | let ptr = ffi::CWBVH_primitives(&self.inner) as *const Primitive; 138 | let count = ffi::CWBVH_primitives_count(&self.inner); 139 | unsafe { std::slice::from_raw_parts(ptr, count as usize) } 140 | } 141 | 142 | pub fn new_internal() -> Self { 143 | Self { 144 | inner: ffi::CWBVH_new(), 145 | _phantom: PhantomData, 146 | } 147 | } 148 | } 149 | super::impl_bvh!(BVH, BVH8_CWBVH); 150 | -------------------------------------------------------------------------------- /tests/tests.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | use core::f32; 4 | 5 | use approx::assert_relative_eq; 6 | use tinybvh_rs::*; 7 | 8 | fn split_triangles() -> Vec<[f32; 4]> { 9 | vec![ 10 | [-2.0, 1.0, -1.0, 0.0], 11 | [-1.0, 1.0, -1.0, 0.0], 12 | [-2.0, 0.0, -1.0, 0.0], 13 | [2.0, 1.0, -1.0, 0.0], 14 | [2.0, 0.0, -1.0, 0.0], 15 | [1.0, 0.0, -1.0, 0.0], 16 | ] 17 | } 18 | 19 | fn test_intersection(bvh: &B) { 20 | let mut ray: Ray = Ray::new([0.0, 0.0, 0.0], [0.0, 0.0, -1.0]); 21 | assert_eq!(bvh.intersect(&mut ray), 1); 22 | assert_relative_eq!(ray.hit.t, INFINITE); 23 | 24 | let mut ray: Ray = Ray::new([-1.5, 0.5, 0.0], [0.0, 0.0, -1.0]); 25 | bvh.intersect(&mut ray); 26 | assert_relative_eq!(ray.hit.t, 1.0); 27 | assert_eq!(ray.hit.prim, 0); 28 | 29 | let mut ray: Ray = Ray::new([1.5, 0.45, 0.0], [0.0, 0.0, -1.0]); 30 | bvh.intersect(&mut ray); 31 | assert_relative_eq!(ray.hit.t, 1.0); 32 | assert_eq!(ray.hit.prim, 1); 33 | } 34 | 35 | #[repr(C)] 36 | #[derive(Clone, Copy, Default, Debug, PartialEq, bytemuck::Pod, bytemuck::Zeroable)] 37 | struct Vertex { 38 | normal: [f32; 3], 39 | position: [f32; 4], 40 | uv: [f32; 2], 41 | } 42 | #[test] 43 | fn layout_wald32() { 44 | let triangles = split_triangles(); 45 | let mut bvh = wald::BVH::new(triangles.as_slice()); 46 | let expected = [ 47 | wald::Node { 48 | min: [-2.0, 0.0, -1.0], 49 | max: [2.0, 1.0, -1.0], 50 | left_first: 2, 51 | tri_count: 0, 52 | }, 53 | wald::Node::default(), 54 | wald::Node { 55 | min: [-2.0, 0.0, -1.0], 56 | max: [-1.0, 1.0, -1.0], 57 | left_first: 0, 58 | tri_count: 1, 59 | }, 60 | wald::Node { 61 | min: [1.0, 0.0, -1.0], 62 | max: [2.0, 1.0, -1.0], 63 | left_first: 1, 64 | tri_count: 1, 65 | }, 66 | ]; 67 | assert_eq!(bvh.nodes().len(), expected.len()); 68 | assert_eq!(bvh.nodes(), expected); 69 | assert_eq!(bvh.indices(), [0, 1]); 70 | test_intersection(&bvh); 71 | bvh.compact(); 72 | 73 | { 74 | use pas::slice_attr; 75 | let primitives = [ 76 | Vertex { 77 | position: [-2.0, 1.0, -1.0, 0.0], 78 | ..Default::default() 79 | }, 80 | Vertex { 81 | position: [-1.0, 1.0, -1.0, 0.0], 82 | ..Default::default() 83 | }, 84 | Vertex { 85 | position: [-2.0, 0.0, -1.0, 0.0], 86 | ..Default::default() 87 | }, 88 | Vertex { 89 | position: [2.0, 1.0, -1.0, 0.0], 90 | ..Default::default() 91 | }, 92 | Vertex { 93 | position: [2.0, 0.0, -1.0, 0.0], 94 | ..Default::default() 95 | }, 96 | Vertex { 97 | position: [1.0, 0.0, -1.0, 0.0], 98 | ..Default::default() 99 | }, 100 | ]; 101 | let positions = slice_attr!(primitives, [0].position); 102 | let bvh = wald::BVH::new(positions); 103 | assert_eq!(bvh.nodes().len(), expected.len()); 104 | assert_eq!(bvh.nodes(), expected); 105 | assert_eq!(bvh.indices(), [0, 1]); 106 | test_intersection(&bvh); 107 | } 108 | } 109 | 110 | #[test] 111 | fn layout_cwbvh() { 112 | let primitives = split_triangles(); 113 | let bvh = cwbvh::BVH::new(primitives.as_slice()); 114 | assert_eq!(bvh.nodes().len(), 1); 115 | assert_eq!(bvh.nodes()[0].primitives().collect::>(), [0, 1]); 116 | 117 | assert_eq!( 118 | bvh.primitives(), 119 | [ 120 | cwbvh::Primitive { 121 | vertex_0: [-2.0, 1.0, -1.0], 122 | edge_1: [0.0, -1.0, 0.0], 123 | edge_2: [1.0, 0.0, 0.0], 124 | original_primitive: 0, 125 | ..Default::default() 126 | }, 127 | cwbvh::Primitive { 128 | vertex_0: [2.0, 1.0, -1.0], 129 | edge_1: [-1.0, -1.0, 0.0], 130 | edge_2: [0.0, -1.0, 0.0], 131 | original_primitive: 1, 132 | ..Default::default() 133 | } 134 | ] 135 | ); 136 | } 137 | 138 | #[test] 139 | fn capture() { 140 | let mut triangles = split_triangles(); 141 | let bvh = wald::BVH::new(&triangles); 142 | assert_relative_eq!(bvh.nodes()[0].min[0], -2.0); 143 | 144 | let capture = bvh.capture(); 145 | triangles[0][0] = -5.0; 146 | 147 | let bvh = wald::BVH::from_capture(capture, &triangles); 148 | assert_relative_eq!(bvh.nodes()[0].min[0], -5.0); 149 | } 150 | 151 | #[test] 152 | #[should_panic] 153 | fn panic_non_triangulated() { 154 | let primitives = [ 155 | [1.0, 0.0, 0.0, 0.0], 156 | [1.0, 0.0, 0.0, 0.0], 157 | [1.0, 0.0, 0.0, 0.0], 158 | [1.0, 0.0, 0.0, 0.0], 159 | ]; 160 | let _ = wald::BVH::new(&primitives); 161 | } 162 | } 163 | --------------------------------------------------------------------------------