Skip to main content

fast_mesh_bvh/
options.rs

1use crate::{BvhError, Result};
2
3/// How the tree is split when it is built.
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum Quality {
6    /// Binned surface-area heuristic: slower to build, faster to query.
7    Sah,
8    /// Morton-code (LBVH) build: faster to build, somewhat slower to query.
9    Fast,
10}
11
12/// Options for building (and rebuilding parts of) a BVH.
13#[derive(Debug, Clone, Copy, PartialEq)]
14pub struct BuildOptions {
15    pub quality: Quality,
16    /// Most triangles per leaf, 1 to 16.
17    pub max_leaf_size: u32,
18    /// Bins per axis for the SAH build, 4 to 64.
19    pub sah_bins: u32,
20    /// Most triangles in a treelet, the unit of partial rebuilds. At least `max_leaf_size`.
21    pub treelet_size: u32,
22}
23
24impl Default for BuildOptions {
25    fn default() -> Self {
26        Self {
27            quality: Quality::Sah,
28            max_leaf_size: 8,
29            sah_bins: 16,
30            treelet_size: 4096,
31        }
32    }
33}
34
35impl BuildOptions {
36    pub(crate) fn validate(&self) -> Result<()> {
37        if !(1..=16).contains(&self.max_leaf_size) {
38            return Err(BvhError::InvalidOptions("max_leaf_size must be 1 to 16"));
39        }
40        if !(4..=64).contains(&self.sah_bins) {
41            return Err(BvhError::InvalidOptions("sah_bins must be 4 to 64"));
42        }
43        if self.treelet_size < self.max_leaf_size {
44            return Err(BvhError::InvalidOptions(
45                "treelet_size must be at least max_leaf_size",
46            ));
47        }
48        Ok(())
49    }
50}
51
52/// When `update` falls back to heavier strategies.
53#[derive(Debug, Clone, Copy, PartialEq)]
54pub struct UpdatePolicy {
55    /// Rebuild everything when more than this fraction of alive triangles is
56    /// dirty (added, removed, rewritten or moved), 0 to 1.
57    pub full_rebuild_fraction: f32,
58    /// Rebuild a refit treelet when its SAH cost exceeds this factor times its
59    /// cost when it was built. At least 1.
60    pub degradation_factor: f32,
61}
62
63impl Default for UpdatePolicy {
64    fn default() -> Self {
65        Self {
66            full_rebuild_fraction: 0.30,
67            degradation_factor: 1.5,
68        }
69    }
70}
71
72impl UpdatePolicy {
73    /// Checks the policy, so that it can be refused before a build or an
74    /// update starts. [`Bvh::set_policy`](crate::Bvh::set_policy) checks it
75    /// again.
76    ///
77    /// # Errors
78    /// [`BvhError::InvalidOptions`] when a value is out of range.
79    pub fn validate(&self) -> Result<()> {
80        if !(0.0..=1.0).contains(&self.full_rebuild_fraction) {
81            return Err(BvhError::InvalidOptions(
82                "full_rebuild_fraction must be 0 to 1",
83            ));
84        }
85        if self.degradation_factor.is_nan() || self.degradation_factor < 1.0 {
86            return Err(BvhError::InvalidOptions(
87                "degradation_factor must be at least 1",
88            ));
89        }
90        Ok(())
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn defaults_are_valid() {
100        assert!(BuildOptions::default().validate().is_ok());
101        assert!(UpdatePolicy::default().validate().is_ok());
102    }
103
104    #[test]
105    fn out_of_range_options_are_rejected() {
106        let bad_leaf = BuildOptions {
107            max_leaf_size: 0,
108            ..Default::default()
109        };
110        assert!(matches!(
111            bad_leaf.validate(),
112            Err(BvhError::InvalidOptions(_))
113        ));
114        let bad_bins = BuildOptions {
115            sah_bins: 65,
116            ..Default::default()
117        };
118        assert!(bad_bins.validate().is_err());
119        let bad_treelet = BuildOptions {
120            treelet_size: 4,
121            ..Default::default()
122        };
123        assert!(bad_treelet.validate().is_err());
124        let nan = UpdatePolicy {
125            degradation_factor: f32::NAN,
126            ..Default::default()
127        };
128        assert!(nan.validate().is_err());
129        let fraction = UpdatePolicy {
130            full_rebuild_fraction: 1.5,
131            ..Default::default()
132        };
133        assert!(fraction.validate().is_err());
134    }
135}