Skip to main content

tinybvh_rs/layouts/
bvh.rs

1//! "Wald" BVH layout
2//!
3//! Main BVH layout used to construct other layouts.
4//!
5//! This layout is split into:
6//! - [`BVHData`]: Read-only operations
7//! - [`BVH`]: Read-write operations
8//!     - Read operations mixing layout and referenced data, such as vertices
9//!     - Build, refit
10
11use crate::{ffi, layouts::impl_bvh_deref, Error};
12use std::{fmt::Debug, marker::PhantomData};
13
14/// "Traditional" 32-bytes BVH node layout, as proposed by Ingo Wald.
15///
16/// Node layout used by [`BVH`].
17#[repr(C)]
18#[derive(Clone, Copy, Default, Debug, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
19pub struct Node {
20    /// AABB min position.
21    pub min: [f32; 3],
22    /// If the node is a leaf, this is the start index of the primitive.
23    /// Otherwise, this is the start index of the first child node.
24    pub left_first: u32,
25    /// AABB max position.
26    pub max: [f32; 3],
27    /// If the node is a leaf, number of triangles in the node.
28    /// `0` otherwise.
29    pub tri_count: u32,
30}
31
32impl Node {
33    /// Returns `true` if the node is a leaf.
34    pub fn is_leaf(&self) -> bool {
35        self.tri_count > 0
36    }
37}
38
39/// Read-only BVH data.
40pub struct BVHData {
41    pub(crate) inner: cxx::UniquePtr<ffi::BVH>,
42    pub(crate) max_primitives_per_leaf: Option<u32>,
43    /// Primitive slice len, **not** primitive count.
44    pub(crate) primitives_len: u32,
45}
46
47impl BVHData {
48    fn new() -> Self {
49        Self {
50            inner: ffi::BVH_new(),
51            max_primitives_per_leaf: None,
52            primitives_len: 0,
53        }
54    }
55
56    /// Move this instance into a writable BVH.
57    ///
58    /// Note: `primitives` slice must have a length multiple of 3.
59    ///
60    /// # Example
61    ///
62    /// ```rust
63    /// # use tinybvh_rs::bvh;
64    ///
65    /// let triangles = vec![[-1.0, 1.0, 0.0, 0.0], [1.0, 1.0, 0.0, 0.0], [-1.0, 0.0, 0.0, 0.0]];
66    /// # let bvh = bvh::BVH::new(triangles.as_slice().into()).unwrap();
67    /// let data = bvh.data();
68    /// let mut bvh = data.bvh(triangles.as_slice().into()).unwrap();
69    /// bvh.refit();
70    /// ```
71    pub fn bvh<'a>(self, primitives: crate::Positions<'a>) -> Result<BVH<'a>, Error> {
72        BVH {
73            bvh: self,
74            _phantom: PhantomData,
75        }
76        .bind(primitives)
77    }
78
79    /// Number of primitives for a given node.
80    pub fn primitive_count(&self, id: u32) -> u32 {
81        self.inner.PrimCount(id) as u32
82    }
83
84    /// SAH cost for a subtree.
85    pub fn sah_cost(&self, id: u32) -> f32 {
86        self.inner.SAHCost(id)
87    }
88
89    /// BVH nodes.
90    ///
91    /// Useful to upload to the BVH to the GPU.
92    pub fn nodes(&self) -> &[Node] {
93        ffi::BVH_nodes(&self.inner)
94    }
95
96    /// BVH indices.
97    ///
98    /// Map from primitive index to first vertex index.
99    ///
100    /// # Example
101    ///
102    /// ```rust
103    /// # use tinybvh_rs::bvh;
104    /// # let primitives = vec![[0.0; 4], [0.0; 4], [0.0; 4]];
105    /// # let bvh = bvh::BVH::new(primitives.as_slice().into()).unwrap();
106    /// # let node = bvh.nodes()[0];
107    /// for i in 0..node.tri_count {
108    ///     let vertex_start = bvh.indices()[(node.left_first + i) as usize] as usize * 3;
109    ///     let vertex = [
110    ///         primitives[vertex_start],
111    ///         primitives[vertex_start + 1],
112    ///         primitives[vertex_start + 2]
113    ///     ];
114    ///     println!("Vertex {:?}", vertex);
115    /// }
116    /// ```
117    pub fn indices(&self) -> &[u32] {
118        ffi::BVH_indices(&self.inner)
119    }
120
121    /// Returns `true` if the BVH can be refitted.
122    ///
123    /// A BVH is refittable if it was built without spatial splits (i.e., not an SBVH).
124    pub fn refittable(&self) -> bool {
125        ffi::BVH_refittable(&self.inner)
126    }
127}
128
129/// Read-write BVH data.
130///
131/// The BVH is bound to the positions until moved into a [`BVHData`].
132/// More information on the [`BVH::bind`] method.
133///
134/// # Examples
135///
136/// ```rust
137/// use tinybvh_rs::bvh;
138///
139/// let triangles = vec![[-1.0, 1.0, 0.0, 0.0], [1.0, 1.0, 0.0, 0.0], [-1.0, 0.0, 0.0, 0.0]];
140/// let bvh = bvh::BVH::new(triangles.as_slice().into());
141/// ```
142pub struct BVH<'a> {
143    pub(crate) bvh: BVHData,
144    _phantom: PhantomData<&'a [f32; 4]>,
145}
146impl_bvh_deref!(BVH<'a>, BVHData);
147
148impl<'a> BVH<'a> {
149    /// Create a new BVH and build it using [`BVH::build`].
150    pub fn new(primitives: crate::Positions<'a>) -> Result<Self, Error> {
151        Self {
152            bvh: BVHData::new(),
153            _phantom: PhantomData,
154        }
155        .build(primitives)
156    }
157
158    /// Create a new BVH and build it using [`BVH::build_hq`].
159    pub fn new_hq(primitives: crate::Positions<'a>) -> Result<Self, Error> {
160        Self {
161            bvh: BVHData::new(),
162            _phantom: PhantomData,
163        }
164        .build_hq(primitives)
165    }
166
167    /// Build the BVH and bind it to `primitives`.
168    ///
169    /// More information on the tinybvh repository (`BVH::Build()` method).
170    ///
171    /// # Examples
172    ///
173    /// ```rust
174    /// use tinybvh_rs::bvh;
175    ///
176    /// let triangles = vec![[-1.0, 1.0, 0.0, 0.0], [1.0, 1.0, 0.0, 0.0], [-1.0, 0.0, 0.0, 0.0]];
177    /// # let bvh = bvh::BVH::new(triangles.as_slice().into()).unwrap();
178    /// let bvh = bvh.build(triangles.as_slice().into());
179    /// ```
180    pub fn build<'b>(self, primitives: crate::Positions<'b>) -> Result<BVH<'b>, Error> {
181        Error::validate_triangulated(primitives.len())?;
182        let slice = primitives.into();
183        let mut bvh = self.bvh;
184        bvh.inner.pin_mut().Build(&slice);
185        Ok(BVH::build_internal(bvh, primitives))
186    }
187
188    /// Build the BVH and bind it to `primitives`.
189    ///
190    /// More information on the tinybvh repository (`BVH::BuildHQ()` method).
191    pub fn build_hq<'b>(self, primitives: crate::Positions<'b>) -> Result<BVH<'b>, Error> {
192        Error::validate_triangulated(primitives.len())?;
193        let slice = primitives.into();
194        let mut bvh = self.bvh;
195        bvh.inner.pin_mut().BuildHQ(&slice);
196        Ok(BVH::build_internal(bvh, primitives))
197    }
198
199    /// Update the primitives reference.
200    ///
201    /// Note: At the opposite of [`BVH::build`], no rebuild is performed.
202    ///
203    /// # Examples
204    ///
205    /// ```rust
206    /// # use tinybvh_rs::{bvh, Intersector, Ray};
207    ///
208    /// let mut triangles = vec![
209    ///     [-1.0, 1.0, 0.0, 0.0],
210    ///     [1.0, 1.0, 0.0, 0.0],
211    ///     [-1.0, 0.0, 0.0, 0.0],
212    /// ];
213    /// let bvh = bvh::BVH::new(triangles.as_slice().into()).unwrap();
214    ///
215    /// let mut other_triangles = vec![
216    ///     [-2.0, 2.0, 0.0, 0.0],
217    ///     [2.0, 2.0, 0.0, 0.0],
218    ///     [-2.0, 0.0, 0.0, 0.0]
219    /// ];
220    /// let bvh = bvh.bind(other_triangles.as_slice().into()).unwrap();
221    ///
222    /// let mut ray = Ray::new([0.0; 3], [0.0, 0.0, -1.0]);
223    /// bvh.intersect(&mut ray);
224    /// ```
225    pub fn bind<'b>(self, primitives: crate::Positions<'b>) -> Result<BVH<'b>, Error> {
226        let mut bvh = self.bvh;
227        Error::validate_primitives_len(bvh.primitives_len, primitives.len() as u32)?;
228
229        let slice = primitives.into();
230        ffi::BVH_setPrimitives(bvh.inner.pin_mut(), &slice);
231        Ok(BVH {
232            bvh,
233            _phantom: PhantomData,
234        })
235    }
236
237    /// Remove unused nodes and reduce BVH size.
238    ///
239    /// More information on the tinybvh repository (`BVH::Compact()` method).
240    pub fn compact(&mut self) {
241        self.bvh.inner.pin_mut().Compact();
242    }
243
244    /// Split BVH leaves into a at most `max_primitives` primitives.
245    ///
246    /// More information on the tinybvh repository (`BVH::SplitLeafs()` method).
247    pub fn split_leaves(&mut self, max_primitives: u32) {
248        self.bvh.inner.pin_mut().SplitLeafs(max_primitives);
249
250        let max_prim = self.bvh.max_primitives_per_leaf.unwrap_or(u32::MAX);
251        self.bvh.max_primitives_per_leaf = Some(u32::min(max_prim, max_primitives));
252    }
253
254    /// Refits the BVH.
255    ///
256    /// Note: This library diverges from tinybvh here that asserts if it's not refittable.
257    /// tinybvh_rs skips refitting instead.
258    ///
259    /// More information on the tinybvh repository (`BVH::Refit()` method).
260    pub fn refit(&mut self) {
261        if self.refittable() {
262            self.bvh.inner.pin_mut().Refit(0);
263        }
264    }
265
266    /// Move the BVH into read-only state.
267    ///
268    /// Allows to relax the lifetime bound, for instance to modify
269    /// the positions before a refit:
270    ///
271    /// ```rust
272    /// # use tinybvh_rs::bvh;
273    /// let data = {
274    ///     let mut triangles = vec![[-1.0, 1.0, 0.0, 0.0], [1.0, 1.0, 0.0, 0.0], [-1.0, 0.0, 0.0, 0.0]];
275    ///     let bvh = bvh::BVH::new(triangles.as_slice().into()).unwrap();
276    ///     bvh.data()
277    /// }; // `triangles` is dropped by now
278    /// println!("Is leaf: {}", data.nodes()[0].is_leaf()); // false
279    /// ```
280    pub fn data(self) -> BVHData {
281        self.bvh
282    }
283
284    fn build_internal<'b>(mut bvh: BVHData, primitives: crate::Positions<'b>) -> BVH<'b> {
285        bvh.max_primitives_per_leaf = None;
286        bvh.primitives_len = primitives.len() as u32;
287        BVH {
288            bvh,
289            _phantom: PhantomData,
290        }
291    }
292}
293
294impl crate::Intersector for BVH<'_> {
295    fn intersect(&self, ray: &mut crate::Ray) -> u32 {
296        self.bvh.inner.Intersect(ray) as u32
297    }
298}