parry2d/shape/voxels/
voxels.rs

1use super::{
2    VoxelIndex, EMPTY_FACE_MASK, FACES_TO_FEATURE_MASKS, FACES_TO_OCTANT_MASKS,
3    FACES_TO_VOXEL_TYPES, INTERIOR_FACE_MASK,
4};
5use crate::bounding_volume::{Aabb, BoundingVolume};
6use crate::math::{Point, Real, Vector};
7use crate::partitioning::{Bvh, BvhBuildStrategy, BvhNode};
8use crate::shape::voxels::voxels_chunk::{VoxelsChunk, VoxelsChunkHeader};
9use crate::shape::VoxelsChunkRef;
10use crate::utils::hashmap::HashMap;
11use alloc::{vec, vec::Vec};
12#[cfg(not(feature = "std"))]
13use na::ComplexField;
14
15/// Categorization of a voxel based on its neighbors.
16#[derive(Copy, Clone, Debug, PartialEq, Eq)]
17pub enum VoxelType {
18    /// The voxel is empty.
19    Empty,
20    /// The voxel is a vertex if all three coordinate axis directions have at
21    /// least one empty neighbor.
22    Vertex,
23    /// The voxel is on an edge if it has non-empty neighbors in both directions of
24    /// a single coordinate axis.
25    #[cfg(feature = "dim3")]
26    Edge,
27    /// The voxel is on an edge if it has non-empty neighbors in both directions of
28    /// two coordinate axes.
29    Face,
30    /// The voxel is on an edge if it has non-empty neighbors in both directions of
31    /// all coordinate axes.
32    Interior,
33}
34
35#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
36/// The status of the cell of an heightfield.
37pub struct AxisMask(u8);
38
39bitflags::bitflags! {
40    /// Flags for identifying signed directions along coordinate axes, or faces of a voxel.
41    impl AxisMask: u8 {
42        /// The direction or face along the `+x` coordinate axis.
43        const X_POS = 1 << 0;
44        /// The direction or face along the `-x` coordinate axis.
45        const X_NEG = 1 << 1;
46        /// The direction or face along the `+y` coordinate axis.
47        const Y_POS = 1 << 2;
48        /// The direction or face along the `-y` coordinate axis.
49        const Y_NEG = 1 << 3;
50        /// The direction or face along the `+z` coordinate axis.
51        #[cfg(feature= "dim3")]
52        const Z_POS = 1 << 4;
53        /// The direction or face along the `-z` coordinate axis.
54        #[cfg(feature= "dim3")]
55        const Z_NEG = 1 << 5;
56    }
57}
58
59/// Indicates the local shape of a voxel on each octant.
60///
61/// This provides geometric information of the shape’s exposed features on each octant.
62// This is an alternative to `FACES_TO_FEATURE_MASKS` that can be more convenient for some
63// collision-detection algorithms.
64#[derive(Copy, Clone, Debug, PartialEq, Eq)]
65pub struct OctantPattern;
66
67// NOTE: it is important that the max value of any OctantPattern variant
68//       is 7 because we don’t allocate more than 3 bits to store it in
69//      `FACES_TO_OCTANT_MASKS`.
70/// Indicates the local shape of a voxel on each octant.
71///
72/// This provides geometric information of the shape’s exposed features on each octant.
73// This is an alternative to `FACES_TO_FEATURE_MASKS` that can be more convenient for some
74// collision-detection algorithms.
75#[cfg(feature = "dim3")]
76impl OctantPattern {
77    /// The voxel doesn't have any exposed feature on the octant with this mask.
78    pub const INTERIOR: u32 = 0;
79    /// The voxel has an exposed vertex on the octant with this mask.
80    pub const VERTEX: u32 = 1;
81    /// The voxel has an exposed edges with direction X on the octant with this mask.
82    pub const EDGE_X: u32 = 2;
83    /// The voxel has an exposed edges with direction Y on the octant with this mask.
84    pub const EDGE_Y: u32 = 3;
85    /// The voxel has an exposed edges with direction Z on the octant with this mask.
86    pub const EDGE_Z: u32 = 4;
87    /// The voxel has an exposed face with normal X on the octant with this mask.
88    pub const FACE_X: u32 = 5;
89    /// The voxel has an exposed face with normal Y on the octant with this mask.
90    pub const FACE_Y: u32 = 6;
91    /// The voxel has an exposed face with normal Z on the octant with this mask.
92    pub const FACE_Z: u32 = 7;
93}
94
95// NOTE: it is important that the max value of any OctantPattern variant
96//       is 7 because we don’t allocate more than 3 bits to store it in
97//      `FACES_TO_OCTANT_MASKS`.
98/// Indicates the local shape of a voxel on each octant.
99///
100/// This provides geometric information of the shape’s exposed features on each octant.
101/// This is an alternative to `FACES_TO_FEATURE_MASKS` that can be more convenient for some
102/// collision-detection algorithms.
103#[cfg(feature = "dim2")]
104impl OctantPattern {
105    /// The voxel doesn't have any exposed feature on the octant with this mask.
106    pub const INTERIOR: u32 = 0;
107    /// The voxel has an exposed vertex on the octant with this mask.
108    pub const VERTEX: u32 = 1;
109    /// The voxel has an exposed face with normal X on the octant with this mask.
110    pub const FACE_X: u32 = 2;
111    /// The voxel has an exposed face with normal Y on the octant with this mask.
112    pub const FACE_Y: u32 = 3;
113}
114
115// The local neighborhood information is encoded in a 8-bits number in groups of two bits
116// per coordinate axis: `0bwwzzyyxx`. In each group of two bits, e.g. `xx`, the rightmost (resp.
117// leftmost) bit set to 1 means that the neighbor voxel with coordinate `+1` (resp `-1`) relative
118// to the current voxel along the `x` axis is filled. If the bit is 0, then the corresponding
119// neighbor is empty. See the `AxisMask` bitflags.
120// For example, in 2D, the mask `0b00_00_10_01` matches the following configuration (assuming +y goes
121// up, and +x goes right):
122//
123// ```txt
124//  0 0 0
125//  0 x 1
126//  0 1 0
127// ```
128//
129// The special value `0b01000000` indicates that the voxel is empty.
130// And the value `0b00111111` (`0b00001111` in 2D) indicates that the voxel is an interior voxel (its whole neighborhood
131// is filled).
132/// A description of the local neighborhood of a voxel.
133#[derive(Copy, Clone, Debug, PartialEq, Eq)]
134#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
135pub struct VoxelState(pub(super) u8);
136
137impl VoxelState {
138    /// The value of empty voxels.
139    pub const EMPTY: VoxelState = VoxelState(EMPTY_FACE_MASK);
140    /// The value of a voxel with non-empty neighbors in all directions.
141    pub const INTERIOR: VoxelState = VoxelState(INTERIOR_FACE_MASK);
142
143    pub(crate) const fn new(state: u8) -> Self {
144        Self(state)
145    }
146
147    /// Is this voxel empty?
148    pub const fn is_empty(self) -> bool {
149        self.0 == EMPTY_FACE_MASK
150    }
151
152    /// A bit mask indicating which faces of the voxel don’t have any
153    /// adjacent non-empty voxel.
154    pub const fn free_faces(self) -> AxisMask {
155        if self.0 == INTERIOR_FACE_MASK || self.0 == EMPTY_FACE_MASK {
156            AxisMask::empty()
157        } else {
158            AxisMask::from_bits_truncate((!self.0) & INTERIOR_FACE_MASK)
159        }
160    }
161
162    /// The [`VoxelType`] of this voxel.
163    pub const fn voxel_type(self) -> VoxelType {
164        FACES_TO_VOXEL_TYPES[self.0 as usize]
165    }
166
167    // Bitmask indicating what vertices, edges, or faces of the voxel are "free".
168    pub(crate) const fn feature_mask(self) -> u16 {
169        FACES_TO_FEATURE_MASKS[self.0 as usize]
170    }
171
172    pub(crate) const fn octant_mask(self) -> u32 {
173        FACES_TO_OCTANT_MASKS[self.0 as usize]
174    }
175}
176
177/// Information associated to a voxel.
178#[derive(Copy, Clone, Debug, PartialEq)]
179pub struct VoxelData {
180    /// The temporary index in the internal voxels’ storage.
181    ///
182    /// This index can be invalidated after a call to [`Voxels::set_voxel`], or
183    /// [`Voxels::crop`].
184    pub linear_id: VoxelIndex,
185    /// The voxel’s integer grid coordinates.
186    pub grid_coords: Point<i32>,
187    /// The voxel’s center position in the local-space of the [`Voxels`] shape it is part of.
188    pub center: Point<Real>,
189    /// The voxel’s state, indicating if it’s empty or full.
190    pub state: VoxelState,
191}
192
193/// A shape made of axis-aligned, uniformly sized, cubes (aka. voxels).
194///
195/// This shape is specialized to handle voxel worlds and voxelized obojects efficiently why ensuring
196/// that collision-detection isn’t affected by the so-called "internal edges problem" that can create
197/// artifacts when another object rolls or slides against a flat voxelized surface.
198///
199/// The internal storage is compact (but not sparse at the moment), storing only one byte per voxel
200/// in the allowed domain. This has a generally smaller memory footprint than a mesh representation
201/// of the voxels.
202#[derive(Clone, Debug)]
203#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
204pub struct Voxels {
205    /// A BVH of chunk keys.
206    ///
207    /// The bounding boxes are the ones of the chunk’s voxels **keys**. This is equivalent to a bvh
208    /// of the chunks with a uniform voxel size of 1.
209    pub(super) chunk_bvh: Bvh,
210    pub(super) chunk_headers: HashMap<Point<i32>, VoxelsChunkHeader>,
211    pub(super) chunk_keys: Vec<Point<i32>>,
212    pub(super) chunks: Vec<VoxelsChunk>,
213    pub(super) free_chunks: Vec<usize>,
214    pub(super) voxel_size: Vector<Real>,
215}
216
217impl Voxels {
218    /// Initializes a voxel shapes from the voxels grid coordinates.
219    ///
220    /// Each voxel will have its bottom-left-back corner located at
221    /// `grid_coordinates * voxel_size`; and its center at `(grid_coordinates + 0.5) * voxel_size`.
222    pub fn new(voxel_size: Vector<Real>, grid_coordinates: &[Point<i32>]) -> Self {
223        let mut result = Self {
224            chunk_bvh: Bvh::new(),
225            chunk_headers: HashMap::default(),
226            chunk_keys: vec![],
227            chunks: vec![],
228            free_chunks: vec![],
229            voxel_size,
230        };
231
232        for vox in grid_coordinates {
233            let (chunk_key, id_in_chunk) = Self::chunk_key_and_id_in_chunk(*vox);
234            let chunk_header = result.chunk_headers.entry(chunk_key).or_insert_with(|| {
235                let id = result.chunks.len();
236                result.chunks.push(VoxelsChunk::default());
237                result.chunk_keys.push(chunk_key);
238                VoxelsChunkHeader { id, len: 0 }
239            });
240            chunk_header.len += 1;
241            result.chunks[chunk_header.id].states[id_in_chunk] = VoxelState::INTERIOR;
242        }
243
244        result.chunk_bvh = Bvh::from_iter(
245            BvhBuildStrategy::Ploc,
246            result.chunk_headers.iter().map(|(chunk_key, chunk_id)| {
247                (
248                    chunk_id.id,
249                    VoxelsChunk::aabb(chunk_key, &result.voxel_size),
250                )
251            }),
252        );
253
254        result.recompute_all_voxels_states();
255        result
256    }
257
258    /// Computes a voxels shape from the set of `points`.
259    ///
260    /// The points are mapped to a regular grid centered at the provided point with smallest
261    /// coordinates, and with grid cell size equal to `scale`. It is OK if multiple points
262    /// fall into the same grid cell.
263    pub fn from_points(voxel_size: Vector<Real>, points: &[Point<Real>]) -> Self {
264        let voxels: Vec<_> = points
265            .iter()
266            .map(|pt| {
267                Point::from(
268                    pt.coords
269                        .component_div(&voxel_size)
270                        .map(|x| x.floor() as i32),
271                )
272            })
273            .collect();
274        Self::new(voxel_size, &voxels)
275    }
276
277    pub(crate) fn chunk_bvh(&self) -> &Bvh {
278        &self.chunk_bvh
279    }
280
281    /// The semi-open range of voxels in shape.
282    ///
283    /// This provides conservative bounds on the range of voxel indices that might be set to filled.
284    pub fn domain(&self) -> [Point<i32>; 2] {
285        let aabb = self.chunk_bvh.root_aabb();
286
287        // NOTE that we shift the AABB’s bounds so the endpoint matches a voxel center
288        //      to avoid rounding errors.
289        let half_sz = self.voxel_size() / 2.0;
290        let mins = self.voxel_at_point(aabb.mins + half_sz);
291        // + 1 because the range is semi-open.
292        let maxs = self.voxel_at_point(aabb.maxs - half_sz) + Vector::repeat(1);
293        [mins, maxs]
294    }
295
296    // /// The number of voxels along each coordinate axis.
297    // pub fn dimensions(&self) -> Vector<u32> {
298    //     (self.domain_maxs - self.domain_mins).map(|e| e as u32)
299    // }
300
301    /// The size of each voxel part this [`Voxels`] shape.
302    pub fn voxel_size(&self) -> Vector<Real> {
303        self.voxel_size
304    }
305
306    /// Scale this shape.
307    pub fn scaled(mut self, scale: &Vector<Real>) -> Self {
308        self.voxel_size.component_mul_assign(scale);
309        self
310    }
311
312    /// A reference to the chunk with id `chunk_id`.
313    ///
314    /// Panics if the chunk doesn’t exist.
315    pub fn chunk_ref(&self, chunk_id: u32) -> VoxelsChunkRef<'_> {
316        VoxelsChunkRef {
317            my_id: chunk_id as usize,
318            parent: self,
319            states: &self.chunks[chunk_id as usize].states,
320            key: &self.chunk_keys[chunk_id as usize],
321        }
322    }
323
324    /// The AABB of the voxel with the given quantized `key`.
325    pub fn voxel_aabb(&self, key: Point<i32>) -> Aabb {
326        let center = self.voxel_center(key);
327        let hext = self.voxel_size / 2.0;
328        Aabb::from_half_extents(center, hext)
329    }
330
331    /// Returns the state of a given voxel.
332    pub fn voxel_state(&self, key: Point<i32>) -> Option<VoxelState> {
333        let vid = self.linear_index(key)?;
334        Some(self.chunks[vid.chunk_id].states[vid.id_in_chunk])
335    }
336
337    /// Calculates the grid coordinates of the voxel containing the given `point`, regardless
338    /// of whether this voxel is filled oor empty.
339    pub fn voxel_at_point(&self, point: Point<Real>) -> Point<i32> {
340        point
341            .coords
342            .component_div(&self.voxel_size)
343            .map(|x| x.floor() as i32)
344            .into()
345    }
346
347    /// Gets the voxel at the given flat voxel index.
348    pub fn voxel_at_flat_id(&self, id: u32) -> Option<Point<i32>> {
349        let vid = VoxelIndex::from_flat_id(id as usize);
350        let chunk_key = self.chunk_keys.get(vid.chunk_id)?;
351        if *chunk_key == VoxelsChunk::INVALID_CHUNK_KEY {
352            return None;
353        }
354
355        Some(VoxelsChunk::voxel_key_at_id(
356            *chunk_key,
357            vid.id_in_chunk as u32,
358        ))
359    }
360
361    /// The range of grid coordinates of voxels intersecting the given AABB.
362    ///
363    /// The returned range covers both empty and non-empty voxels, and is not limited to the
364    /// bounds defined by [`Self::domain`].
365    /// The range is semi, open, i.e., the range along each dimension `i` is understood as
366    /// the semi-open interval: `range[0][i]..range[1][i]`.
367    pub fn voxel_range_intersecting_local_aabb(&self, aabb: &Aabb) -> [Point<i32>; 2] {
368        let mins = aabb
369            .mins
370            .coords
371            .component_div(&self.voxel_size)
372            .map(|x| x.floor() as i32);
373        let maxs = aabb
374            .maxs
375            .coords
376            .component_div(&self.voxel_size)
377            .map(|x| x.ceil() as i32);
378        [mins.into(), maxs.into()]
379    }
380
381    /// The AABB of a given range of voxels.
382    ///
383    /// The AABB is computed independently of [`Self::domain`] and independently of whether
384    /// the voxels contained within are empty or not.
385    pub fn voxel_range_aabb(&self, mins: Point<i32>, maxs: Point<i32>) -> Aabb {
386        Aabb {
387            mins: mins
388                .cast::<Real>()
389                .coords
390                .component_mul(&self.voxel_size)
391                .into(),
392            maxs: maxs
393                .cast::<Real>()
394                .coords
395                .component_mul(&self.voxel_size)
396                .into(),
397        }
398    }
399
400    /// Aligns the given AABB with the voxelized grid.
401    ///
402    /// The aligned is calculated such that the returned AABB has corners lying at the grid
403    /// intersections (i.e. matches voxel corners) and fully contains the input `aabb`.
404    pub fn align_aabb_to_grid(&self, aabb: &Aabb) -> Aabb {
405        let mins = aabb
406            .mins
407            .coords
408            .zip_map(&self.voxel_size, |m, sz| (m / sz).floor() * m)
409            .into();
410        let maxs = aabb
411            .maxs
412            .coords
413            .zip_map(&self.voxel_size, |m, sz| (m / sz).ceil() * m)
414            .into();
415        Aabb { mins, maxs }
416    }
417
418    /// Iterates through every voxel intersecting the given aabb.
419    ///
420    /// Returns the voxel’s linearized id, center, and state.
421    pub fn voxels_intersecting_local_aabb(
422        &self,
423        aabb: &Aabb,
424    ) -> impl Iterator<Item = VoxelData> + '_ {
425        let [mins, maxs] = self.voxel_range_intersecting_local_aabb(aabb);
426        self.voxels_in_range(mins, maxs)
427    }
428
429    /// The center point of all the voxels in this shape (including empty ones).
430    ///
431    /// The voxel data associated to each center is provided to determine what kind of voxel
432    /// it is (and, in particular, if it is empty or full).
433    pub fn voxels(&self) -> impl Iterator<Item = VoxelData> + '_ {
434        let aabb = self.chunk_bvh.root_aabb();
435        self.voxels_in_range(
436            self.voxel_at_point(aabb.mins),
437            self.voxel_at_point(aabb.maxs),
438        )
439    }
440
441    /// Iterate through the data of all the voxels within the given (semi-open) voxel grid indices.
442    ///
443    /// Note that this yields both empty and non-empty voxels within the range. This does not
444    /// include any voxel that falls outside [`Self::domain`].
445    pub fn voxels_in_range(
446        &self,
447        mins: Point<i32>,
448        maxs: Point<i32>,
449    ) -> impl Iterator<Item = VoxelData> + '_ {
450        let range_aabb = Aabb::new(self.voxel_center(mins), self.voxel_center(maxs));
451
452        self.chunk_bvh
453            .leaves(move |node: &BvhNode| node.aabb().intersects(&range_aabb))
454            .flat_map(move |chunk_id| {
455                let chunk = self.chunk_ref(chunk_id);
456                chunk.voxels_in_range(mins, maxs)
457            })
458    }
459
460    fn voxel_to_chunk_key(voxel_key: Point<i32>) -> Point<i32> {
461        fn div_floor(a: i32, b: usize) -> i32 {
462            let sign = (a < 0) as i32;
463            (a + sign) / b as i32 - sign
464        }
465
466        voxel_key.map(|e| div_floor(e, VoxelsChunk::VOXELS_PER_CHUNK_DIM))
467    }
468
469    /// Given a voxel key, returns the key of the voxel chunk that contains it, as well as the
470    /// linear index of the voxel within that chunk.
471    #[cfg(feature = "dim2")]
472    pub(super) fn chunk_key_and_id_in_chunk(voxel_key: Point<i32>) -> (Point<i32>, usize) {
473        let chunk_key = Self::voxel_to_chunk_key(voxel_key);
474        // NOTE: always positive since we subtracted the smallest possible key on that chunk.
475        let voxel_key_in_chunk = voxel_key - chunk_key * VoxelsChunk::VOXELS_PER_CHUNK_DIM as i32;
476        let id_in_chunk = (voxel_key_in_chunk.x
477            + voxel_key_in_chunk.y * VoxelsChunk::VOXELS_PER_CHUNK_DIM as i32)
478            as usize;
479        (chunk_key, id_in_chunk)
480    }
481
482    /// Given a voxel key, returns the key of the voxel chunk that contains it, as well as the
483    /// linear index of the voxel within that chunk.
484    #[cfg(feature = "dim3")]
485    pub(super) fn chunk_key_and_id_in_chunk(voxel_key: Point<i32>) -> (Point<i32>, usize) {
486        let chunk_key = Self::voxel_to_chunk_key(voxel_key);
487        // NOTE: always positive since we subtracted the smallest possible key on that chunk.
488        let voxel_key_in_chunk = voxel_key - chunk_key * VoxelsChunk::VOXELS_PER_CHUNK_DIM as i32;
489        let id_in_chunk = (voxel_key_in_chunk.x
490            + voxel_key_in_chunk.y * VoxelsChunk::VOXELS_PER_CHUNK_DIM as i32
491            + voxel_key_in_chunk.z
492                * VoxelsChunk::VOXELS_PER_CHUNK_DIM as i32
493                * VoxelsChunk::VOXELS_PER_CHUNK_DIM as i32) as usize;
494        (chunk_key, id_in_chunk)
495    }
496
497    /// The linearized index associated to the given voxel key.
498    pub fn linear_index(&self, voxel_key: Point<i32>) -> Option<VoxelIndex> {
499        let (chunk_key, id_in_chunk) = Self::chunk_key_and_id_in_chunk(voxel_key);
500        let chunk_id = self.chunk_headers.get(&chunk_key)?.id;
501        Some(VoxelIndex {
502            chunk_id,
503            id_in_chunk,
504        })
505    }
506
507    /// The center of the voxel with the given key.
508    pub fn voxel_center(&self, key: Point<i32>) -> Point<Real> {
509        (key.cast::<Real>() + Vector::repeat(0.5))
510            .coords
511            .component_mul(&self.voxel_size)
512            .into()
513    }
514}