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}