parry3d/shape/voxels/
voxels_chunk.rs

1use crate::bounding_volume::Aabb;
2use crate::math::{ivect_to_vect, IVector, Int, Real, Vector};
3use crate::shape::{VoxelData, VoxelState, Voxels};
4
5#[derive(Clone, Debug)]
6#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
7pub(super) struct VoxelsChunkHeader {
8    pub(super) id: usize,
9    // The number of non-empty voxels in the chunk.
10    // This is used for detecting when a chunk can be removed
11    // if it becomes fully empty.
12    pub(super) len: usize,
13}
14
15#[derive(Clone, Debug)]
16#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
17#[repr(C)]
18#[repr(align(64))]
19pub(super) struct VoxelsChunk {
20    #[cfg_attr(feature = "serde-serialize", serde(with = "serde_arrays"))]
21    pub(super) states: [VoxelState; VoxelsChunk::VOXELS_PER_CHUNK],
22}
23
24impl Default for VoxelsChunk {
25    fn default() -> Self {
26        Self {
27            states: [VoxelState::EMPTY; VoxelsChunk::VOXELS_PER_CHUNK],
28        }
29    }
30}
31
32#[derive(Copy, Clone, Debug, PartialEq)]
33pub struct VoxelIndex {
34    pub(super) chunk_id: usize,
35    pub(super) id_in_chunk: usize,
36}
37
38impl VoxelIndex {
39    pub fn flat_id(&self) -> usize {
40        self.chunk_id * VoxelsChunk::VOXELS_PER_CHUNK + self.id_in_chunk
41    }
42
43    pub fn from_flat_id(id: usize) -> Self {
44        Self {
45            chunk_id: id / VoxelsChunk::VOXELS_PER_CHUNK,
46            id_in_chunk: id % VoxelsChunk::VOXELS_PER_CHUNK,
47        }
48    }
49}
50
51impl VoxelsChunk {
52    // TODO: find the ideal number. We want a good balance between cache locality
53    //       and number of BVH leaf nodes.
54    #[cfg(feature = "dim2")]
55    pub(super) const VOXELS_PER_CHUNK_DIM: usize = 16;
56    #[cfg(feature = "dim3")]
57    pub(super) const VOXELS_PER_CHUNK_DIM: usize = 8;
58    #[cfg(feature = "dim2")]
59    pub(super) const VOXELS_PER_CHUNK: usize =
60        Self::VOXELS_PER_CHUNK_DIM * Self::VOXELS_PER_CHUNK_DIM;
61    #[cfg(feature = "dim3")]
62    pub(super) const VOXELS_PER_CHUNK: usize =
63        Self::VOXELS_PER_CHUNK_DIM * Self::VOXELS_PER_CHUNK_DIM * Self::VOXELS_PER_CHUNK_DIM;
64
65    #[cfg(feature = "dim2")]
66    pub(super) const INVALID_CHUNK_KEY: IVector = IVector::new(Int::MAX, Int::MAX);
67    #[cfg(feature = "dim3")]
68    pub(super) const INVALID_CHUNK_KEY: IVector = IVector::new(Int::MAX, Int::MAX, Int::MAX);
69
70    /// The key of the voxel at the given linearized index within this chunk.
71    #[cfg(feature = "dim2")]
72    pub(super) fn voxel_key_at_id(chunk_key: IVector, id_in_chunk: u32) -> IVector {
73        let y = id_in_chunk as Int / Self::VOXELS_PER_CHUNK_DIM as Int;
74        let x = id_in_chunk as Int % Self::VOXELS_PER_CHUNK_DIM as Int;
75        chunk_key * (Self::VOXELS_PER_CHUNK_DIM as Int) + IVector::new(x, y)
76    }
77
78    /// The key of the voxel at the given linearized index.
79    #[cfg(feature = "dim3")]
80    pub(super) fn voxel_key_at_id(chunk_key: IVector, id_in_chunk: u32) -> IVector {
81        let d0d1 = (Self::VOXELS_PER_CHUNK_DIM * Self::VOXELS_PER_CHUNK_DIM) as u32;
82        let z = id_in_chunk / d0d1;
83        let y = (id_in_chunk - z * d0d1) / Self::VOXELS_PER_CHUNK_DIM as u32;
84        let x = id_in_chunk % Self::VOXELS_PER_CHUNK_DIM as u32;
85        chunk_key * (Self::VOXELS_PER_CHUNK_DIM as Int) + IVector::new(x as Int, y as Int, z as Int)
86    }
87
88    /// The semi-open range of valid voxel keys for this chunk.
89    pub(super) fn keys_bounds(chunk_key: &IVector) -> [IVector; 2] {
90        let imins = chunk_key * Self::VOXELS_PER_CHUNK_DIM as Int;
91        let imaxs = imins + IVector::splat(Self::VOXELS_PER_CHUNK_DIM as Int);
92        [imins, imaxs]
93    }
94
95    pub(super) fn aabb(chunk_key: &IVector, voxel_size: Vector) -> Aabb {
96        let [imins, imaxs] = Self::keys_bounds(chunk_key);
97        let aabb = Aabb::new(ivect_to_vect(imins), ivect_to_vect(imaxs));
98        Aabb::new(aabb.mins * voxel_size, aabb.maxs * voxel_size)
99    }
100}
101
102/// A reference to a chunk of voxels within a [`Voxels`] shape.
103///
104/// # What is a Chunk?
105///
106/// To efficiently manage large voxel worlds, Parry internally divides the voxel grid into
107/// fixed-size chunks. Each chunk contains a small region of voxels (e.g., 8×8×8 in 3D or
108/// 16×16 in 2D). This chunking provides:
109///
110/// - **Spatial acceleration**: Quick queries using a BVH of chunks
111/// - **Memory efficiency**: Empty chunks are not stored
112/// - **Cache locality**: Nearby voxels are stored together
113///
114/// A `VoxelsChunkRef` provides read-only access to a single chunk's data, allowing you to
115/// query voxels within that chunk without iterating through the entire voxel shape.
116///
117/// # When to Use
118///
119/// You typically don't create `VoxelsChunkRef` directly. Instead, you get them from:
120/// - [`Voxels::chunk_ref`]: Get a specific chunk by ID
121/// - BVH traversal for spatial queries on large voxel worlds
122///
123/// # Examples
124///
125/// ```
126/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
127/// use parry3d::shape::Voxels;
128/// use parry3d::math::{Vector, IVector};
129///
130/// let voxels = Voxels::new(
131///     Vector::new(1.0, 1.0, 1.0),
132///     &[IVector::new(0, 0, 0), IVector::new(1, 0, 0)],
133/// );
134///
135/// // Get a chunk reference (chunk IDs come from BVH traversal)
136/// let chunk_ref = voxels.chunk_ref(0);
137///
138/// // Query voxels within this chunk
139/// for voxel in chunk_ref.voxels() {
140///     if !voxel.state.is_empty() {
141///         println!("Voxel at {:?}", voxel.grid_coords);
142///     }
143/// }
144///
145/// // Get chunk's AABB
146/// let aabb = chunk_ref.local_aabb();
147/// println!("Chunk bounds: {:?}", aabb);
148/// # }
149/// ```
150#[derive(Copy, Clone)]
151pub struct VoxelsChunkRef<'a> {
152    /// The linear index of this chunk within the `Voxels` shape.
153    pub my_id: usize,
154    /// The voxel shape this chunk is part of.
155    pub parent: &'a Voxels,
156    /// The fill status of each voxel from this chunk.
157    pub states: &'a [VoxelState; VoxelsChunk::VOXELS_PER_CHUNK],
158    /// The spatial index of this chunk.
159    pub key: &'a IVector,
160}
161
162impl<'a> VoxelsChunkRef<'a> {
163    /// The AABB of this chunk of voxels.
164    ///
165    /// Note that this return the AABB of the whole chunk, without taking into account the fact
166    /// that some voxels are empty.
167    pub fn local_aabb(&self) -> Aabb {
168        VoxelsChunk::aabb(self.key, self.parent.voxel_size)
169    }
170
171    /// The domain of this chunk of voxels.
172    pub fn domain(&self) -> [IVector; 2] {
173        VoxelsChunk::keys_bounds(self.key)
174    }
175
176    /// Returns the spatial index of the voxel containing the given point.
177    pub fn voxel_at_point_unchecked(&self, pt: Vector) -> IVector {
178        self.parent.voxel_at_point(pt)
179    }
180
181    /// The state of the voxel with key `voxel_key`.
182    pub fn voxel_state(&self, voxel_key: IVector) -> Option<VoxelState> {
183        let (chunk_key, id_in_chunk) = Voxels::chunk_key_and_id_in_chunk(voxel_key);
184        if &chunk_key != self.key {
185            return None;
186        }
187        Some(self.states[id_in_chunk])
188    }
189
190    /// Clamps the `voxel_key` so it is within the bounds of `self`.
191    pub fn clamp_voxel(&self, voxel_key: IVector) -> IVector {
192        let [mins, maxs] = self.domain();
193        voxel_key.clamp(mins, maxs)
194    }
195
196    /// The AABB of the voxel with this key.
197    ///
198    /// Returns a result even if the voxel doesn’t belong to this chunk.
199    pub fn voxel_aabb_unchecked(&self, voxel_key: IVector) -> Aabb {
200        self.parent.voxel_aabb(voxel_key)
201    }
202
203    /// Convert a voxel index (expressed relative to the main `Voxels` shape, not relative to the
204    /// chunk alone) into a flat index within a voxel chunk.
205    ///
206    /// Returns `None` if the voxel isn’t part of this chunk.
207    pub fn flat_id(&self, voxel_key: IVector) -> Option<u32> {
208        let (chunk_key, id_in_chunk) = Voxels::chunk_key_and_id_in_chunk(voxel_key);
209        if &chunk_key != self.key {
210            return None;
211        }
212
213        Some(
214            VoxelIndex {
215                chunk_id: self.my_id,
216                id_in_chunk,
217            }
218            .flat_id() as u32,
219        )
220    }
221
222    /// Iterates through all the voxels in this chunk.
223    ///
224    /// Note that this only yields non-empty voxels within the range. This does not
225    /// include any voxel that falls outside [`Self::domain`].
226    pub fn voxels(&self) -> impl Iterator<Item = VoxelData> + '_ {
227        let range = self.domain();
228        self.voxels_in_range(range[0], range[1])
229    }
230
231    /// Iterate through the data of all the voxels within the given (semi-open) voxel grid indices.
232    ///
233    /// Note that this only yields non-empty voxels within the range. This does not
234    /// include any voxel that falls outside [`Self::domain`].
235    #[cfg(feature = "dim2")]
236    pub fn voxels_in_range(
237        self,
238        mins: IVector,
239        maxs: IVector,
240    ) -> impl Iterator<Item = VoxelData> + use<'a> {
241        let [chunk_mins, chunk_maxs] = VoxelsChunk::keys_bounds(self.key);
242        let mins = mins.max(chunk_mins);
243        let maxs = maxs.min(chunk_maxs);
244
245        (mins[0]..maxs[0]).flat_map(move |ix| {
246            (mins[1]..maxs[1]).flat_map(move |iy| {
247                let id_in_chunk = (ix - chunk_mins[0]) as usize
248                    + (iy - chunk_mins[1]) as usize * VoxelsChunk::VOXELS_PER_CHUNK_DIM;
249                let state = self.states[id_in_chunk];
250
251                if state.is_empty() {
252                    return None;
253                }
254
255                let grid_coords = IVector::new(ix, iy);
256                let center =
257                    Vector::new(ix as Real + 0.5, iy as Real + 0.5) * (self.parent.voxel_size);
258                Some(VoxelData {
259                    linear_id: VoxelIndex {
260                        chunk_id: self.my_id,
261                        id_in_chunk,
262                    },
263                    grid_coords,
264                    center,
265                    state,
266                })
267            })
268        })
269    }
270
271    /// Iterate through the data of all the voxels within the given (semi-open) voxel grid indices.
272    ///
273    /// Note that this yields both empty and non-empty voxels within the range. This does not
274    /// include any voxel that falls outside [`Self::domain`].
275    #[cfg(feature = "dim3")]
276    pub fn voxels_in_range(
277        self,
278        mins: IVector,
279        maxs: IVector,
280    ) -> impl Iterator<Item = VoxelData> + use<'a> {
281        let [chunk_mins, chunk_maxs] = VoxelsChunk::keys_bounds(self.key);
282        let mins = mins.max(chunk_mins);
283        let maxs = maxs.min(chunk_maxs);
284
285        (mins[0]..maxs[0]).flat_map(move |ix| {
286            (mins[1]..maxs[1]).flat_map(move |iy| {
287                (mins[2]..maxs[2]).filter_map(move |iz| {
288                    let id_in_chunk = (ix - chunk_mins[0]) as usize
289                        + (iy - chunk_mins[1]) as usize * VoxelsChunk::VOXELS_PER_CHUNK_DIM
290                        + (iz - chunk_mins[2]) as usize
291                            * VoxelsChunk::VOXELS_PER_CHUNK_DIM
292                            * VoxelsChunk::VOXELS_PER_CHUNK_DIM;
293                    let state = self.states[id_in_chunk];
294
295                    if state.is_empty() {
296                        return None;
297                    }
298
299                    let grid_coords = IVector::new(ix, iy, iz);
300                    let center = Vector::new(ix as Real + 0.5, iy as Real + 0.5, iz as Real + 0.5)
301                        * (self.parent.voxel_size);
302                    Some(VoxelData {
303                        linear_id: VoxelIndex {
304                            chunk_id: self.my_id,
305                            id_in_chunk,
306                        },
307                        grid_coords,
308                        center,
309                        state,
310                    })
311                })
312            })
313        })
314    }
315}