Skip to main content

loam_shape/
field.rs

1#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
2pub enum FieldKind {
3    #[default]
4    ExactDistance,
5    ConservativeBound,
6    Implicit,
7}
8
9impl FieldKind {
10    fn rank(self) -> u8 {
11        match self {
12            FieldKind::ExactDistance => 0,
13            FieldKind::ConservativeBound => 1,
14            FieldKind::Implicit => 2,
15        }
16    }
17
18    pub fn weaker(self, other: Self) -> Self {
19        if other.rank() > self.rank() {
20            other
21        } else {
22            self
23        }
24    }
25}
26
27pub const GRADIENT_STEP: f32 = 1.0e-3;
28
29/// Below this a contact query refuses rather than normalizes noise into a normal.
30pub const MIN_GRADIENT_NORM: f32 = 1.0e-4;
31
32/// `distance` returns the Euclidean value described by `field_kind` in the first `dimension` coordinates, off by at most `error_at` at the query point.
33pub trait DistanceField: Send + Sync {
34    fn field_kind(&self) -> FieldKind;
35
36    fn dimension(&self) -> u32;
37
38    fn distance(&self, point: [f32; 4]) -> f32;
39
40    fn error_at(&self, point: [f32; 4]) -> f32;
41
42    fn gradient(&self, point: [f32; 4]) -> [f32; 4] {
43        let mut gradient = [0.0; 4];
44        for axis in 0..self.dimension().min(gradient.len() as u32) as usize {
45            let mut ahead = point;
46            let mut behind = point;
47            ahead[axis] += GRADIENT_STEP;
48            behind[axis] -= GRADIENT_STEP;
49            gradient[axis] = (self.distance(ahead) - self.distance(behind)) / (2.0 * GRADIENT_STEP);
50        }
51        gradient
52    }
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58
59    struct Ramp;
60
61    impl DistanceField for Ramp {
62        fn field_kind(&self) -> FieldKind {
63            FieldKind::Implicit
64        }
65
66        fn dimension(&self) -> u32 {
67            4
68        }
69
70        fn distance(&self, p: [f32; 4]) -> f32 {
71            p[0] + 2.0 * p[1] + 3.0 * p[2] + 4.0 * p[3] - 5.0
72        }
73
74        fn error_at(&self, _point: [f32; 4]) -> f32 {
75            0.0
76        }
77    }
78
79    #[test]
80    fn the_default_gradient_divides_by_the_full_two_sided_step() {
81        let gradient = Ramp.gradient([1.0, -2.0, 0.5, 3.0]);
82        for (axis, expected) in [1.0f32, 2.0, 3.0, 4.0].into_iter().enumerate() {
83            assert!(
84                (gradient[axis] - expected).abs() < 1.0e-3,
85                "axis {axis}: {} against slope {expected}",
86                gradient[axis]
87            );
88        }
89    }
90}