bevy_sculpter/
field.rs

1//! Generic field trait for 3D voxel data storage.
2//!
3//! This module provides the [`Field`] trait which abstracts over different
4//! types of volumetric data (density, materials, etc.) with a common interface.
5
6use bevy::prelude::*;
7
8/// A 3D grid of voxel data with a fixed size.
9///
10/// This trait provides a common interface for volumetric data storage,
11/// including density fields (f32), material fields (u8), and any other
12/// per-voxel data types.
13///
14/// # Type Parameters
15/// * `T` - The element type stored at each voxel
16///
17/// # Coordinate System
18/// Uses X-Y-Z ordering where X varies fastest in the underlying storage.
19pub trait Field<T: Copy + Default> {
20    /// The size of the field grid as a `UVec3`.
21    const SIZE: UVec3;
22
23    /// The total number of voxels in the field.
24    const VOLUME: usize = (Self::SIZE.x * Self::SIZE.y * Self::SIZE.z) as usize;
25
26    /// The default value for out-of-bounds access.
27    const DEFAULT: T;
28
29    /// Returns a reference to the underlying data slice.
30    fn data(&self) -> &[T];
31
32    /// Returns a mutable reference to the underlying data slice.
33    fn data_mut(&mut self) -> &mut [T];
34
35    /// Computes the flat array index for a given (x, y, z) coordinate.
36    ///
37    /// Uses X-Y-Z ordering where X varies fastest.
38    #[inline]
39    fn index(x: u32, y: u32, z: u32) -> usize {
40        (x + y * Self::SIZE.x + z * Self::SIZE.x * Self::SIZE.y) as usize
41    }
42
43    /// Converts a flat index back to (x, y, z) coordinates.
44    #[inline]
45    fn coords(index: usize) -> UVec3 {
46        let index = index as u32;
47        let z = index / (Self::SIZE.x * Self::SIZE.y);
48        let rem = index % (Self::SIZE.x * Self::SIZE.y);
49        let y = rem / Self::SIZE.x;
50        let x = rem % Self::SIZE.x;
51        uvec3(x, y, z)
52    }
53
54    /// Checks if unsigned coordinates are within the field bounds.
55    #[inline]
56    fn in_bounds_unsigned(x: u32, y: u32, z: u32) -> bool {
57        x < Self::SIZE.x && y < Self::SIZE.y && z < Self::SIZE.z
58    }
59
60    /// Checks if signed coordinates are within the field bounds.
61    ///
62    /// Useful when sampling neighbors that might be outside the grid.
63    #[inline]
64    fn in_bounds(x: i32, y: i32, z: i32) -> bool {
65        x >= 0
66            && y >= 0
67            && z >= 0
68            && (x as u32) < Self::SIZE.x
69            && (y as u32) < Self::SIZE.y
70            && (z as u32) < Self::SIZE.z
71    }
72
73    /// Gets the value at the given unsigned coordinates.
74    ///
75    /// Returns [`Self::DEFAULT`] for out-of-bounds coordinates.
76    #[inline]
77    fn get(&self, x: u32, y: u32, z: u32) -> T {
78        if Self::in_bounds_unsigned(x, y, z) {
79            self.data()[Self::index(x, y, z)]
80        } else {
81            Self::DEFAULT
82        }
83    }
84
85    /// Gets the value at the given `UVec3` coordinates.
86    #[inline]
87    fn get_uvec3(&self, pos: UVec3) -> T {
88        self.get(pos.x, pos.y, pos.z)
89    }
90
91    /// Gets the value using signed coordinates.
92    ///
93    /// Returns `None` for out-of-bounds coordinates, useful for neighbor sampling.
94    #[inline]
95    fn get_signed(&self, x: i32, y: i32, z: i32) -> Option<T> {
96        if Self::in_bounds(x, y, z) {
97            Some(self.data()[Self::index(x as u32, y as u32, z as u32)])
98        } else {
99            None
100        }
101    }
102
103    /// Gets the value at the given `IVec3` coordinates.
104    ///
105    /// Returns `None` for out-of-bounds coordinates.
106    #[inline]
107    fn get_ivec3(&self, pos: IVec3) -> Option<T> {
108        self.get_signed(pos.x, pos.y, pos.z)
109    }
110
111    /// Sets the value at the given unsigned coordinates.
112    ///
113    /// Silently ignores out-of-bounds coordinates.
114    #[inline]
115    fn set(&mut self, x: u32, y: u32, z: u32, value: T) {
116        if Self::in_bounds_unsigned(x, y, z) {
117            self.data_mut()[Self::index(x, y, z)] = value;
118        }
119    }
120
121    /// Sets the value at the given `UVec3` coordinates.
122    #[inline]
123    fn set_uvec3(&mut self, pos: UVec3, value: T) {
124        self.set(pos.x, pos.y, pos.z, value);
125    }
126
127    /// Sets the value using signed coordinates.
128    ///
129    /// Returns `true` if the value was set, `false` if out of bounds.
130    #[inline]
131    fn set_signed(&mut self, x: i32, y: i32, z: i32, value: T) -> bool {
132        if Self::in_bounds(x, y, z) {
133            self.data_mut()[Self::index(x as u32, y as u32, z as u32)] = value;
134            true
135        } else {
136            false
137        }
138    }
139
140    /// Sets the value at the given `IVec3` coordinates.
141    ///
142    /// Returns `true` if the value was set, `false` if out of bounds.
143    #[inline]
144    fn set_ivec3(&mut self, pos: IVec3, value: T) -> bool {
145        self.set_signed(pos.x, pos.y, pos.z, value)
146    }
147
148    /// Fills the entire field with a single value.
149    #[inline]
150    fn fill(&mut self, value: T) {
151        self.data_mut().fill(value);
152    }
153
154    /// Returns an iterator over all (position, value) pairs.
155    fn iter(&self) -> FieldIter<'_, T, Self>
156    where
157        Self: Sized,
158    {
159        FieldIter {
160            field: self,
161            index: 0,
162            _marker: std::marker::PhantomData,
163        }
164    }
165
166    /// Returns an iterator over all positions in the field.
167    fn positions() -> FieldPositionIter {
168        FieldPositionIter {
169            size: Self::SIZE,
170            index: 0,
171        }
172    }
173}
174
175/// Iterator over (position, value) pairs in a field.
176pub struct FieldIter<'a, T: Copy + Default, F: Field<T>> {
177    field: &'a F,
178    index: usize,
179    _marker: std::marker::PhantomData<T>,
180}
181
182impl<'a, T: Copy + Default, F: Field<T>> Iterator for FieldIter<'a, T, F> {
183    type Item = (UVec3, T);
184
185    fn next(&mut self) -> Option<Self::Item> {
186        if self.index < F::VOLUME {
187            let pos = F::coords(self.index);
188            let value = self.field.data()[self.index];
189            self.index += 1;
190            Some((pos, value))
191        } else {
192            None
193        }
194    }
195
196    fn size_hint(&self) -> (usize, Option<usize>) {
197        let remaining = F::VOLUME - self.index;
198        (remaining, Some(remaining))
199    }
200}
201
202impl<'a, T: Copy + Default, F: Field<T>> ExactSizeIterator for FieldIter<'a, T, F> {}
203
204/// Iterator over all positions in a field.
205pub struct FieldPositionIter {
206    size: UVec3,
207    index: u32,
208}
209
210impl Iterator for FieldPositionIter {
211    type Item = UVec3;
212
213    fn next(&mut self) -> Option<Self::Item> {
214        let volume = self.size.x * self.size.y * self.size.z;
215        if self.index < volume {
216            let z = self.index / (self.size.x * self.size.y);
217            let rem = self.index % (self.size.x * self.size.y);
218            let y = rem / self.size.x;
219            let x = rem % self.size.x;
220            self.index += 1;
221            Some(uvec3(x, y, z))
222        } else {
223            None
224        }
225    }
226
227    fn size_hint(&self) -> (usize, Option<usize>) {
228        let volume = (self.size.x * self.size.y * self.size.z) as usize;
229        let remaining = volume - self.index as usize;
230        (remaining, Some(remaining))
231    }
232}
233
234impl ExactSizeIterator for FieldPositionIter {}
235
236/// Extension trait for fields that support spherical operations.
237pub trait FieldSphereOps<T: Copy + Default>: Field<T> {
238    /// Applies an operation to all voxels within a sphere.
239    ///
240    /// # Arguments
241    /// * `center` - Center of the sphere in grid coordinates
242    /// * `radius` - Radius in grid units
243    /// * `op` - Operation to apply: receives (current_value, distance_from_center) and returns new value
244    fn apply_sphere<F>(&mut self, center: Vec3, radius: f32, op: F)
245    where
246        F: Fn(T, f32) -> T,
247    {
248        let min = (center - Vec3::splat(radius + 1.0))
249            .max(Vec3::ZERO)
250            .as_ivec3();
251        let max = (center + Vec3::splat(radius + 1.0))
252            .min(Self::SIZE.as_vec3() - Vec3::ONE)
253            .as_ivec3();
254
255        for z in min.z..=max.z {
256            for y in min.y..=max.y {
257                for x in min.x..=max.x {
258                    let pos = vec3(x as f32, y as f32, z as f32);
259                    let dist = pos.distance(center);
260                    if dist <= radius {
261                        let current = self.get(x as u32, y as u32, z as u32);
262                        let new_value = op(current, dist);
263                        self.set(x as u32, y as u32, z as u32, new_value);
264                    }
265                }
266            }
267        }
268    }
269
270    /// Fills a sphere with a constant value.
271    fn fill_sphere(&mut self, center: Vec3, radius: f32, value: T) {
272        self.apply_sphere(center, radius, |_, _| value);
273    }
274}
275
276/// Extension trait for fields that support box operations.
277pub trait FieldBoxOps<T: Copy + Default>: Field<T> {
278    /// Applies an operation to all voxels within an axis-aligned box.
279    ///
280    /// # Arguments
281    /// * `min` - Minimum corner (inclusive)
282    /// * `max` - Maximum corner (inclusive)
283    /// * `op` - Operation to apply: receives current value and returns new value
284    fn apply_box<F>(&mut self, min: IVec3, max: IVec3, op: F)
285    where
286        F: Fn(T) -> T,
287    {
288        let min = min.max(IVec3::ZERO);
289        let max = max.min(Self::SIZE.as_ivec3() - IVec3::ONE);
290
291        for z in min.z..=max.z {
292            for y in min.y..=max.y {
293                for x in min.x..=max.x {
294                    let current = self.get(x as u32, y as u32, z as u32);
295                    let new_value = op(current);
296                    self.set(x as u32, y as u32, z as u32, new_value);
297                }
298            }
299        }
300    }
301
302    /// Fills a box with a constant value.
303    fn fill_box(&mut self, min: IVec3, max: IVec3, value: T) {
304        self.apply_box(min, max, |_| value);
305    }
306}
307
308// Blanket implementations for all Field types
309impl<T: Copy + Default, F: Field<T>> FieldSphereOps<T> for F {}
310impl<T: Copy + Default, F: Field<T>> FieldBoxOps<T> for F {}
311
312#[cfg(test)]
313mod tests {
314    use super::*;
315
316    // Test implementation
317    struct TestField(Vec<f32>);
318
319    impl Field<f32> for TestField {
320        const SIZE: UVec3 = uvec3(8, 8, 8);
321        const DEFAULT: f32 = 0.0;
322
323        fn data(&self) -> &[f32] {
324            &self.0
325        }
326
327        fn data_mut(&mut self) -> &mut [f32] {
328            &mut self.0
329        }
330    }
331
332    #[test]
333    fn test_index_coords_roundtrip() {
334        for z in 0..8u32 {
335            for y in 0..8u32 {
336                for x in 0..8u32 {
337                    let idx = TestField::index(x, y, z);
338                    let coords = TestField::coords(idx);
339                    assert_eq!(coords, uvec3(x, y, z));
340                }
341            }
342        }
343    }
344
345    #[test]
346    fn test_get_set() {
347        let mut field = TestField(vec![0.0; 512]);
348        field.set(3, 4, 5, 42.0);
349        assert_eq!(field.get(3, 4, 5), 42.0);
350        assert_eq!(field.get(0, 0, 0), 0.0);
351    }
352
353    #[test]
354    fn test_out_of_bounds() {
355        let field = TestField(vec![1.0; 512]);
356        assert_eq!(field.get(100, 0, 0), 0.0); // DEFAULT
357        assert_eq!(field.get_signed(-1, 0, 0), None);
358    }
359
360    #[test]
361    fn test_fill_sphere() {
362        let mut field = TestField(vec![0.0; 512]);
363        field.fill_sphere(vec3(4.0, 4.0, 4.0), 2.0, 1.0);
364        assert_eq!(field.get(4, 4, 4), 1.0); // Center
365        assert_eq!(field.get(0, 0, 0), 0.0); // Outside
366    }
367
368    #[test]
369    fn test_iter() {
370        let field = TestField(vec![0.0; 512]);
371        assert_eq!(field.iter().count(), 512);
372    }
373}