saft/
grid3.rs

1use macaw::Vec3;
2
3use crate::SignedDistance;
4
5// TODO: use u32 as index? Should be large enough.
6// Or replace with IVec3?
7pub type Index3 = [usize; 3];
8
9/// Stores values on a 3D cube lattice on the coordinates \[0,0,0\] - \[w-1, h-1, d-1\].
10/// A 3D tensor, basically.
11///
12/// TODO (nummelin): Should we separate storage so we can store just a bitset of inside/outside (which is enough for most uses).
13pub struct Grid3<T = f32> {
14    size: Index3,
15    data: Vec<T>,
16}
17
18impl<T> Grid3<T> {
19    /// flat data
20    pub fn data(&self) -> &[T] {
21        &self.data
22    }
23
24    pub fn size(&self) -> Index3 {
25        self.size
26    }
27}
28
29impl<T> std::ops::Index<Index3> for Grid3<T> {
30    type Output = T;
31
32    #[inline]
33    fn index(&self, p: Index3) -> &Self::Output {
34        debug_assert!(p[0] < self.size[0]);
35        debug_assert!(p[1] < self.size[1]);
36        debug_assert!(p[2] < self.size[2]);
37        &self.data[p[0] + self.size[0] * (p[1] + self.size[1] * p[2])]
38    }
39}
40
41impl<T> std::ops::IndexMut<Index3> for Grid3<T> {
42    #[inline]
43    fn index_mut(&mut self, p: Index3) -> &mut Self::Output {
44        debug_assert!(p[0] < self.size[0]);
45        debug_assert!(p[1] < self.size[1]);
46        debug_assert!(p[2] < self.size[2]);
47        &mut self.data[p[0] + self.size[0] * (p[1] + self.size[1] * p[2])]
48    }
49}
50
51impl<T: std::cmp::PartialEq> std::cmp::PartialEq for Grid3<T> {
52    fn eq(&self, other: &Self) -> bool {
53        self.size == other.size && self.data == other.data
54    }
55}
56
57impl<T: SignedDistance + Copy + Clone + Default> Grid3<T> {
58    pub fn new(size: Index3) -> Self {
59        Self {
60            size,
61            data: vec![T::default(); size[0] * size[1] * size[2]],
62        }
63    }
64
65    /// Set the grid values using the given function.
66    pub fn set(&mut self, mut f: impl FnMut(Index3) -> T) {
67        let mut index = 0;
68        for z in 0..self.size[2] {
69            for y in 0..self.size[1] {
70                for x in 0..self.size[0] {
71                    self.data[index] = f([x, y, z]);
72                    index += 1;
73                }
74            }
75        }
76    }
77}
78
79impl<T> Grid3<T>
80where
81    T: SignedDistance,
82{
83    /// Returns the distance gradient at the given coordinate.
84    /// Coordinate must be within the grid.
85    #[inline]
86    pub fn gradient_clamped(&self, p: Index3) -> Vec3 {
87        let p = [
88            p[0].clamp(1, self.size[0] - 2),
89            p[1].clamp(1, self.size[1] - 2),
90            p[2].clamp(1, self.size[2] - 2),
91        ];
92        let dx = self[[p[0] + 1, p[1], p[2]]].distance() - self[[p[0] - 1, p[1], p[2]]].distance();
93        let dy = self[[p[0], p[1] + 1, p[2]]].distance() - self[[p[0], p[1] - 1, p[2]]].distance();
94        let dz = self[[p[0], p[1], p[2] + 1]].distance() - self[[p[0], p[1], p[2] - 1]].distance();
95        Vec3::new(dx, dy, dz) / 2.0
96    }
97
98    /// Returns a fast approximation of the distance gradient at the given coordinate.
99    ///
100    /// Coordinate must be within the grid.
101    #[inline]
102    pub fn fast_gradient(
103        &self,
104        x: usize,
105        y: usize,
106        z: usize,
107        i: usize,  // index of x, y, z
108        ys: usize, // y stride
109        zs: usize, // z stride
110    ) -> Vec3 {
111        let sx = self.size[0];
112        let sy = self.size[1];
113        let sz = self.size[2];
114
115        let x1 = if x < sx - 1 { i + 1 } else { i };
116        let x2 = if x > 0 { i - 1 } else { i };
117        let y1 = if y < sy - 1 { i + ys } else { i };
118        let y2 = if y > 0 { i - ys } else { i };
119        let z1 = if z < sz - 1 { i + zs } else { i };
120        let z2 = if z > 0 { i - zs } else { i };
121
122        let dx = self.data[x1].distance() - self.data[x2].distance();
123        let dy = self.data[y1].distance() - self.data[y2].distance();
124        let dz = self.data[z1].distance() - self.data[z2].distance();
125
126        Vec3::new(dx, dy, dz) // (should divide by 2 here, but it doesn't matter as we normalize later)
127    }
128
129    fn set_truncated_span(
130        x_slice: &mut [T],
131        y: usize,
132        z: usize,
133        sdf: impl Fn(Index3) -> T + Send + Sync,
134        truncate_dist: f32,
135    ) {
136        let w = x_slice.len();
137        let mut x = 0;
138
139        while x < w {
140            let distance = sdf([x, y, z]);
141            let abs_distance = distance.distance().abs();
142
143            x_slice[x] = distance;
144            x += 1;
145
146            let mut distance_bound = abs_distance - 1.0;
147            while distance_bound > truncate_dist && x < w {
148                x_slice[x] = distance;
149                x += 1;
150
151                distance_bound -= 1.0;
152            }
153        }
154    }
155
156    /// Will set all values closer than the given truncate distance
157    ///
158    /// Cells outside the given truncate distance will have approximated distances.
159    ///
160    /// Will run synchronously regardless of `with_rayon` feature availability.
161    pub fn set_truncated_sync(
162        &mut self,
163        sdf: impl Fn(Index3) -> T + Send + Sync,
164        truncate_dist: f32,
165    ) {
166        let _d = self.size[2];
167        let h = self.size[1];
168        let w = self.size[0];
169
170        self.data
171            .chunks_mut(w * h)
172            .enumerate()
173            .for_each(|(z, xy_plane)| {
174                xy_plane.chunks_mut(w).enumerate().for_each(|(y, x_slice)| {
175                    Self::set_truncated_span(x_slice, y, z, &sdf, truncate_dist);
176                });
177            });
178    }
179
180    /// Will set all values closer than the given truncate distance
181    ///
182    /// Cells outside the given truncate distance will have approximated distances.
183    #[cfg(not(feature = "with_rayon"))]
184    pub fn set_truncated(&mut self, sdf: impl Fn(Index3) -> T + Send + Sync, truncate_dist: f32) {
185        self.set_truncated_sync(sdf, truncate_dist);
186    }
187
188    /// Will set all values closer than the given truncate distance
189    ///
190    /// Cells outside the given truncate distance will have approximated distances.
191    #[cfg(feature = "with_rayon")]
192    pub fn set_truncated(&mut self, sdf: impl Fn(Index3) -> T + Send + Sync, truncate_dist: f32)
193    where
194        T: Send,
195    {
196        let _d = self.size[2];
197        let h = self.size[1];
198        let w = self.size[0];
199
200        use rayon::prelude::*;
201
202        self.data
203            .par_chunks_mut(w * h)
204            .enumerate()
205            .for_each(|(z, xy_plane)| {
206                xy_plane
207                    .par_chunks_mut(w)
208                    .enumerate()
209                    .for_each(|(y, x_slice)| {
210                        Self::set_truncated_span(x_slice, y, z, &sdf, truncate_dist);
211                    });
212            });
213    }
214}
215
216// TODO: Optimize updating and meshing by
217// evaluating the sdf recursively / divide and conquer
218// Start with big blocks, test sdf. If distance is bigger than block
219// skip it and produce a bitset that can be used when doing the iso surface
220// extraction.