parry3d/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};
6#[cfg(not(feature = "std"))]
7use crate::math::ComplexField;
8use crate::math::{ivect_to_vect, vect_to_ivect, IVector, Int, Vector};
9use crate::partitioning::{Bvh, BvhBuildStrategy, BvhNode};
10use crate::shape::voxels::voxels_chunk::{VoxelsChunk, VoxelsChunkHeader};
11use crate::shape::VoxelsChunkRef;
12use crate::utils::hashmap::HashMap;
13use alloc::{vec, vec::Vec};
14
15/// Categorization of a voxel based on its neighbors.
16///
17/// This enum describes the local topology of a filled voxel by examining which of its
18/// immediate neighbors (along coordinate axes) are also filled. This information is crucial
19/// for collision detection to avoid the "internal edges problem" where objects can get
20/// caught on edges between adjacent voxels.
21///
22/// # Type Classification
23///
24/// - **Empty**: The voxel is not filled.
25/// - **Interior**: All axis-aligned neighbors are filled (safest for collision).
26/// - **Face**: Missing neighbors in one coordinate direction (e.g., top face exposed).
27/// - **Edge** (3D only): Missing neighbors in two coordinate directions (e.g., corner edge exposed).
28/// - **Vertex**: Missing neighbors in all coordinate directions (isolated corner).
29///
30/// # Examples
31///
32/// ```
33/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
34/// use parry3d::shape::{Voxels, VoxelType};
35/// use parry3d::math::{Vector, IVector};
36///
37/// // Create a small 2x2x2 cube of voxels
38/// let voxels = Voxels::new(
39/// Vector::new(1.0, 1.0, 1.0),
40/// &[
41/// IVector::new(0, 0, 0),
42/// IVector::new(1, 0, 0),
43/// IVector::new(0, 1, 0),
44/// IVector::new(1, 1, 0),
45/// IVector::new(0, 0, 1),
46/// IVector::new(1, 0, 1),
47/// IVector::new(0, 1, 1),
48/// IVector::new(1, 1, 1),
49/// ],
50/// );
51///
52/// // Check voxel type - interior voxels are fully surrounded
53/// let state = voxels.voxel_state(IVector::new(0, 0, 0)).unwrap();
54/// let voxel_type = state.voxel_type();
55/// println!("Voxel type: {:?}", voxel_type);
56/// # }
57/// ```
58#[derive(Copy, Clone, Debug, PartialEq, Eq)]
59pub enum VoxelType {
60 /// The voxel is empty.
61 Empty,
62 /// The voxel is a vertex if all three coordinate axis directions have at
63 /// least one empty neighbor.
64 Vertex,
65 /// The voxel is on an edge if it has non-empty neighbors in both directions of
66 /// a single coordinate axis.
67 #[cfg(feature = "dim3")]
68 Edge,
69 /// The voxel is on an edge if it has non-empty neighbors in both directions of
70 /// two coordinate axes.
71 Face,
72 /// The voxel is on an edge if it has non-empty neighbors in both directions of
73 /// all coordinate axes.
74 Interior,
75}
76
77#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
78/// The status of the cell of an heightfield.
79pub struct AxisMask(u8);
80
81bitflags::bitflags! {
82 /// Flags for identifying signed directions along coordinate axes, or faces of a voxel.
83 impl AxisMask: u8 {
84 /// The direction or face along the `+x` coordinate axis.
85 const X_POS = 1 << 0;
86 /// The direction or face along the `-x` coordinate axis.
87 const X_NEG = 1 << 1;
88 /// The direction or face along the `+y` coordinate axis.
89 const Y_POS = 1 << 2;
90 /// The direction or face along the `-y` coordinate axis.
91 const Y_NEG = 1 << 3;
92 /// The direction or face along the `+z` coordinate axis.
93 #[cfg(feature= "dim3")]
94 const Z_POS = 1 << 4;
95 /// The direction or face along the `-z` coordinate axis.
96 #[cfg(feature= "dim3")]
97 const Z_NEG = 1 << 5;
98 }
99}
100
101/// Indicates the local shape of a voxel on each octant.
102///
103/// This provides geometric information of the shape’s exposed features on each octant.
104// This is an alternative to `FACES_TO_FEATURE_MASKS` that can be more convenient for some
105// collision-detection algorithms.
106#[derive(Copy, Clone, Debug, PartialEq, Eq)]
107pub struct OctantPattern;
108
109// NOTE: it is important that the max value of any OctantPattern variant
110// is 7 because we don’t allocate more than 3 bits to store it in
111// `FACES_TO_OCTANT_MASKS`.
112/// Indicates the local shape of a voxel on each octant.
113///
114/// This provides geometric information of the shape’s exposed features on each octant.
115// This is an alternative to `FACES_TO_FEATURE_MASKS` that can be more convenient for some
116// collision-detection algorithms.
117#[cfg(feature = "dim3")]
118impl OctantPattern {
119 /// The voxel doesn't have any exposed feature on the octant with this mask.
120 pub const INTERIOR: u32 = 0;
121 /// The voxel has an exposed vertex on the octant with this mask.
122 pub const VERTEX: u32 = 1;
123 /// The voxel has an exposed edges with direction X on the octant with this mask.
124 pub const EDGE_X: u32 = 2;
125 /// The voxel has an exposed edges with direction Y on the octant with this mask.
126 pub const EDGE_Y: u32 = 3;
127 /// The voxel has an exposed edges with direction Z on the octant with this mask.
128 pub const EDGE_Z: u32 = 4;
129 /// The voxel has an exposed face with normal X on the octant with this mask.
130 pub const FACE_X: u32 = 5;
131 /// The voxel has an exposed face with normal Y on the octant with this mask.
132 pub const FACE_Y: u32 = 6;
133 /// The voxel has an exposed face with normal Z on the octant with this mask.
134 pub const FACE_Z: u32 = 7;
135}
136
137// NOTE: it is important that the max value of any OctantPattern variant
138// is 7 because we don’t allocate more than 3 bits to store it in
139// `FACES_TO_OCTANT_MASKS`.
140/// Indicates the local shape of a voxel on each octant.
141///
142/// This provides geometric information of the shape’s exposed features on each octant.
143/// This is an alternative to `FACES_TO_FEATURE_MASKS` that can be more convenient for some
144/// collision-detection algorithms.
145#[cfg(feature = "dim2")]
146impl OctantPattern {
147 /// The voxel doesn't have any exposed feature on the octant with this mask.
148 pub const INTERIOR: u32 = 0;
149 /// The voxel has an exposed vertex on the octant with this mask.
150 pub const VERTEX: u32 = 1;
151 /// The voxel has an exposed face with normal X on the octant with this mask.
152 pub const FACE_X: u32 = 2;
153 /// The voxel has an exposed face with normal Y on the octant with this mask.
154 pub const FACE_Y: u32 = 3;
155}
156
157// The local neighborhood information is encoded in a 8-bits number in groups of two bits
158// per coordinate axis: `0bwwzzyyxx`. In each group of two bits, e.g. `xx`, the rightmost (resp.
159// leftmost) bit set to 1 means that the neighbor voxel with coordinate `+1` (resp `-1`) relative
160// to the current voxel along the `x` axis is filled. If the bit is 0, then the corresponding
161// neighbor is empty. See the `AxisMask` bitflags.
162// For example, in 2D, the mask `0b00_00_10_01` matches the following configuration (assuming +y goes
163// up, and +x goes right):
164//
165// ```txt
166// 0 0 0
167// 0 x 1
168// 0 1 0
169// ```
170//
171// The special value `0b01000000` indicates that the voxel is empty.
172// And the value `0b00111111` (`0b00001111` in 2D) indicates that the voxel is an interior voxel (its whole neighborhood
173// is filled).
174/// A description of the local neighborhood of a voxel.
175///
176/// This compact representation stores which immediate neighbors (along coordinate axes) of a
177/// voxel are filled. This information is essential for proper collision detection between voxels
178/// and other shapes, as it helps avoid the "internal edges problem."
179///
180/// The state is encoded as a single byte where each pair of bits represents one coordinate axis,
181/// indicating whether neighbors in the positive and negative directions are filled.
182///
183/// # Special States
184///
185/// - [`VoxelState::EMPTY`]: The voxel itself is empty (not part of the shape).
186/// - [`VoxelState::INTERIOR`]: All neighbors are filled (completely surrounded voxel).
187///
188/// # Examples
189///
190/// ```
191/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
192/// use parry3d::shape::{Voxels, VoxelState, AxisMask};
193/// use parry3d::math::{Vector, IVector};
194///
195/// // Create a simple voxel shape
196/// let voxels = Voxels::new(
197/// Vector::new(1.0, 1.0, 1.0),
198/// &[IVector::new(0, 0, 0), IVector::new(1, 0, 0)],
199/// );
200///
201/// // Query the state of a voxel
202/// let state = voxels.voxel_state(IVector::new(0, 0, 0)).unwrap();
203///
204/// // Check if empty
205/// assert!(!state.is_empty());
206///
207/// // Get which faces are exposed (not adjacent to other voxels)
208/// let free_faces = state.free_faces();
209/// if free_faces.contains(AxisMask::X_NEG) {
210/// println!("The -X face is exposed");
211/// }
212///
213/// // Get the voxel type based on neighborhood
214/// println!("Voxel type: {:?}", state.voxel_type());
215/// # }
216/// ```
217#[derive(Copy, Clone, Debug, PartialEq, Eq)]
218#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
219pub struct VoxelState(pub(super) u8);
220
221impl VoxelState {
222 /// The value of empty voxels.
223 pub const EMPTY: VoxelState = VoxelState(EMPTY_FACE_MASK);
224 /// The value of a voxel with non-empty neighbors in all directions.
225 pub const INTERIOR: VoxelState = VoxelState(INTERIOR_FACE_MASK);
226
227 pub(crate) const fn new(state: u8) -> Self {
228 Self(state)
229 }
230
231 /// Is this voxel empty?
232 pub const fn is_empty(self) -> bool {
233 self.0 == EMPTY_FACE_MASK
234 }
235
236 /// A bit mask indicating which faces of the voxel don’t have any
237 /// adjacent non-empty voxel.
238 pub const fn free_faces(self) -> AxisMask {
239 if self.0 == INTERIOR_FACE_MASK || self.0 == EMPTY_FACE_MASK {
240 AxisMask::empty()
241 } else {
242 AxisMask::from_bits_truncate((!self.0) & INTERIOR_FACE_MASK)
243 }
244 }
245
246 /// The [`VoxelType`] of this voxel.
247 pub const fn voxel_type(self) -> VoxelType {
248 FACES_TO_VOXEL_TYPES[self.0 as usize]
249 }
250
251 // Bitmask indicating what vertices, edges, or faces of the voxel are "free".
252 pub(crate) const fn feature_mask(self) -> u16 {
253 FACES_TO_FEATURE_MASKS[self.0 as usize]
254 }
255
256 pub(crate) const fn octant_mask(self) -> u32 {
257 FACES_TO_OCTANT_MASKS[self.0 as usize]
258 }
259}
260
261/// Information associated to a voxel.
262///
263/// This structure provides complete information about a single voxel including its position
264/// in both grid coordinates and world space, as well as its state (empty/filled and neighborhood).
265///
266/// # Note
267///
268/// The `linear_id` field is an internal implementation detail that can become invalidated when
269/// the voxel structure is modified (e.g., via [`Voxels::set_voxel`] or [`Voxels::crop`]).
270/// For stable references to voxels, always use `grid_coords`.
271///
272/// # Examples
273///
274/// ```
275/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
276/// use parry3d::shape::Voxels;
277/// use parry3d::math::{Vector, IVector};
278///
279/// let voxels = Voxels::new(
280/// Vector::new(0.5, 0.5, 0.5),
281/// &[IVector::new(0, 0, 0), IVector::new(1, 0, 0)],
282/// );
283///
284/// // Iterate through all voxels
285/// for voxel in voxels.voxels() {
286/// if !voxel.state.is_empty() {
287/// println!("Voxel at grid position {:?}", voxel.grid_coords);
288/// println!(" World center: {:?}", voxel.center);
289/// println!(" Type: {:?}", voxel.state.voxel_type());
290/// }
291/// }
292/// # }
293/// ```
294#[derive(Copy, Clone, Debug, PartialEq)]
295pub struct VoxelData {
296 /// The temporary index in the internal voxels' storage.
297 ///
298 /// This index can be invalidated after a call to [`Voxels::set_voxel`], or
299 /// [`Voxels::crop`].
300 pub linear_id: VoxelIndex,
301 /// The voxel's integer grid coordinates.
302 pub grid_coords: IVector,
303 /// The voxel's center position in the local-space of the [`Voxels`] shape it is part of.
304 pub center: Vector,
305 /// The voxel's state, indicating if it's empty or full.
306 pub state: VoxelState,
307}
308
309/// A shape made of axis-aligned, uniformly sized cubes (aka. voxels).
310///
311/// # What are Voxels?
312///
313/// Voxels (volumetric pixels) are 3D cubes (or 2D squares) arranged on a regular grid. Think of
314/// them as 3D building blocks, like LEGO bricks or Minecraft blocks. Each voxel has:
315/// - A position on an integer grid (e.g., `(0, 0, 0)`, `(1, 2, 3)`)
316/// - A uniform size (e.g., 1.0 × 1.0 × 1.0 meters)
317/// - A state: filled (solid) or empty (air)
318///
319/// # When to Use Voxels?
320///
321/// Voxels are ideal for:
322/// - **Minecraft-style worlds**: Block-based terrain and structures
323/// - **Destructible environments**: Easy to add/remove individual blocks
324/// - **Procedural generation**: Grid-based algorithms for caves, terrain, dungeons
325/// - **Volumetric data**: Medical imaging, scientific simulations
326/// - **Retro aesthetics**: Pixel art style in 3D
327///
328/// Voxels may NOT be ideal for:
329/// - Smooth organic shapes (use meshes instead)
330/// - Very large sparse worlds (consider octrees or chunk-based systems)
331/// - Scenes requiring fine geometric detail at all scales
332///
333/// # The Internal Edges Problem
334///
335/// When an object slides across a flat surface made of voxels, it can snag on the edges between
336/// adjacent voxels, causing jerky motion. Parry's `Voxels` shape solves this by tracking neighbor
337/// relationships: it knows which voxel faces are internal (adjacent to another voxel) vs external
338/// (exposed to air), allowing smooth collision response.
339///
340/// # Memory Efficiency
341///
342/// The internal storage uses sparse chunks, storing only one byte per voxel for neighborhood
343/// information. Empty regions consume minimal memory. This is much more efficient than storing
344/// a triangle mesh representation of all voxel surfaces.
345///
346/// # Examples
347///
348/// ## Basic Usage: Creating a Voxel Shape
349///
350/// ```
351/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
352/// use parry3d::shape::Voxels;
353/// use parry3d::math::{Vector, IVector};
354///
355/// // Create a simple 3×3×3 cube of voxels
356/// let voxel_size = Vector::new(1.0, 1.0, 1.0);
357/// let mut coords = Vec::new();
358/// for x in 0..3 {
359/// for y in 0..3 {
360/// for z in 0..3 {
361/// coords.push(IVector::new(x, y, z));
362/// }
363/// }
364/// }
365///
366/// let voxels = Voxels::new(voxel_size, &coords);
367/// println!("Created voxel shape with {} voxels", coords.len());
368/// # }
369/// ```
370///
371/// ## Creating Voxels from World-Space Vectors
372///
373/// ```
374/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
375/// use parry3d::shape::Voxels;
376/// use parry3d::math::Vector;
377///
378/// // Sample points in world space (e.g., from a point cloud)
379/// let points = vec![
380/// Vector::new(0.1, 0.2, 0.3),
381/// Vector::new(1.5, 2.1, 3.7),
382/// Vector::new(0.8, 0.9, 1.2),
383/// ];
384///
385/// // Create voxels with 0.5 unit size - nearby points merge into same voxel
386/// let voxels = Voxels::from_points(Vector::new(0.5, 0.5, 0.5), &points);
387/// println!("Created voxel shape from {} points", points.len());
388/// # }
389/// ```
390///
391/// ## Querying Voxel State
392///
393/// ```
394/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
395/// use parry3d::shape::Voxels;
396/// use parry3d::math::{Vector, IVector};
397///
398/// let voxels = Voxels::new(
399/// Vector::new(1.0, 1.0, 1.0),
400/// &[IVector::new(0, 0, 0), IVector::new(1, 0, 0)],
401/// );
402///
403/// // Check if a specific grid position is filled
404/// if let Some(state) = voxels.voxel_state(IVector::new(0, 0, 0)) {
405/// println!("Voxel is filled!");
406/// println!("Type: {:?}", state.voxel_type());
407/// println!("Free faces: {:?}", state.free_faces());
408/// } else {
409/// println!("Voxel is empty or outside the domain");
410/// }
411///
412/// // Convert world-space point to grid coordinates
413/// let world_point = Vector::new(1.3, 0.7, 0.2);
414/// let grid_coord = voxels.voxel_at_point(world_point);
415/// println!("Vector at {:?} is in voxel {:?}", world_point, grid_coord);
416/// # }
417/// ```
418///
419/// ## Iterating Through Voxels
420///
421/// ```
422/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
423/// use parry3d::shape::Voxels;
424/// use parry3d::math::{Vector, IVector};
425///
426/// let voxels = Voxels::new(
427/// Vector::new(0.5, 0.5, 0.5),
428/// &[IVector::new(0, 0, 0), IVector::new(1, 0, 0), IVector::new(0, 1, 0)],
429/// );
430///
431/// // Iterate through all non-empty voxels
432/// for voxel in voxels.voxels() {
433/// if !voxel.state.is_empty() {
434/// println!("Voxel at grid {:?}, world center {:?}",
435/// voxel.grid_coords, voxel.center);
436/// }
437/// }
438/// # }
439/// ```
440///
441/// ## Modifying Voxels Dynamically
442///
443/// ```
444/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
445/// use parry3d::shape::Voxels;
446/// use parry3d::math::{Vector, IVector};
447///
448/// let mut voxels = Voxels::new(
449/// Vector::new(1.0, 1.0, 1.0),
450/// &[IVector::new(0, 0, 0)],
451/// );
452///
453/// // Add a new voxel
454/// voxels.set_voxel(IVector::new(1, 0, 0), true);
455///
456/// // Remove a voxel
457/// voxels.set_voxel(IVector::new(0, 0, 0), false);
458///
459/// // Check the result
460/// assert!(voxels.voxel_state(IVector::new(0, 0, 0)).unwrap().is_empty());
461/// assert!(!voxels.voxel_state(IVector::new(1, 0, 0)).unwrap().is_empty());
462/// # }
463/// ```
464///
465/// ## Spatial Queries
466///
467/// ```
468/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
469/// use parry3d::shape::Voxels;
470/// use parry3d::bounding_volume::Aabb;
471/// use parry3d::math::{Vector, IVector};
472///
473/// let voxels = Voxels::new(
474/// Vector::new(1.0, 1.0, 1.0),
475/// &[IVector::new(0, 0, 0), IVector::new(1, 0, 0), IVector::new(2, 0, 0)],
476/// );
477///
478/// // Find voxels intersecting an AABB
479/// let query_aabb = Aabb::new(Vector::new(-0.5, -0.5, -0.5), Vector::new(1.5, 1.5, 1.5));
480/// let count = voxels.voxels_intersecting_local_aabb(&query_aabb)
481/// .filter(|v| !v.state.is_empty())
482/// .count();
483/// println!("Found {} voxels in AABB", count);
484///
485/// // Get the overall domain bounds
486/// let [mins, maxs] = voxels.domain();
487/// println!("Voxel grid spans from {:?} to {:?}", mins, maxs);
488/// # }
489/// ```
490///
491/// # See Also
492///
493/// - [`VoxelState`]: Information about a voxel's neighbors
494/// - [`VoxelType`]: Classification of voxels by their topology
495/// - [`VoxelData`]: Complete information about a single voxel
496/// - [`crate::transformation::voxelization`]: Convert meshes to voxels
497#[derive(Clone, Debug)]
498#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
499pub struct Voxels {
500 /// A BVH of chunk keys.
501 ///
502 /// The bounding boxes are the ones of the chunk’s voxels **keys**. This is equivalent to a bvh
503 /// of the chunks with a uniform voxel size of 1.
504 pub(super) chunk_bvh: Bvh,
505 pub(super) chunk_headers: HashMap<IVector, VoxelsChunkHeader>,
506 pub(super) chunk_keys: Vec<IVector>,
507 pub(super) chunks: Vec<VoxelsChunk>,
508 pub(super) free_chunks: Vec<usize>,
509 pub(super) voxel_size: Vector,
510}
511
512impl Voxels {
513 /// Initializes a voxel shape from grid coordinates.
514 ///
515 /// This is the primary constructor for creating a `Voxels` shape. You provide:
516 /// - `voxel_size`: The physical dimensions of each voxel (e.g., `1.0 × 1.0 × 1.0` meters)
517 /// - `grid_coordinates`: Integer grid positions for each filled voxel
518 ///
519 /// # Coordinate System
520 ///
521 /// Each voxel with grid coordinates `(x, y, z)` will be positioned such that:
522 /// - Its minimum corner (bottom-left-back) is at `(x, y, z) * voxel_size`
523 /// - Its center is at `((x, y, z) + 0.5) * voxel_size`
524 /// - Its maximum corner is at `((x, y, z) + 1) * voxel_size`
525 ///
526 /// For example, with `voxel_size = 2.0` and grid coord `(1, 0, 0)`:
527 /// - Minimum corner: `(2.0, 0.0, 0.0)`
528 /// - Center: `(3.0, 1.0, 1.0)`
529 /// - Maximum corner: `(4.0, 2.0, 2.0)`
530 ///
531 /// # Examples
532 ///
533 /// ```
534 /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
535 /// use parry3d::shape::Voxels;
536 /// use parry3d::math::{Vector, IVector};
537 ///
538 /// // Create a 2×2×2 cube of voxels with 1.0 unit size
539 /// let voxels = Voxels::new(
540 /// Vector::new(1.0, 1.0, 1.0),
541 /// &[
542 /// IVector::new(0, 0, 0), IVector::new(1, 0, 0),
543 /// IVector::new(0, 1, 0), IVector::new(1, 1, 0),
544 /// IVector::new(0, 0, 1), IVector::new(1, 0, 1),
545 /// IVector::new(0, 1, 1), IVector::new(1, 1, 1),
546 /// ],
547 /// );
548 ///
549 /// // Verify the first voxel's center position
550 /// let center = voxels.voxel_center(IVector::new(0, 0, 0));
551 /// assert_eq!(center, Vector::new(0.5, 0.5, 0.5));
552 /// # }
553 /// ```
554 ///
555 /// ```
556 /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
557 /// use parry3d::shape::Voxels;
558 /// use parry3d::math::{Vector, IVector};
559 ///
560 /// // Create a line of voxels along the X axis
561 /// let voxels = Voxels::new(
562 /// Vector::new(0.5, 0.5, 0.5),
563 /// &[IVector::new(0, 0, 0), IVector::new(1, 0, 0), IVector::new(2, 0, 0)],
564 /// );
565 ///
566 /// // Query the domain (bounding grid coordinates)
567 /// // Note: domain is aligned to internal chunk boundaries for efficiency
568 /// let [mins, maxs] = voxels.domain();
569 /// assert_eq!(mins, IVector::new(0, 0, 0));
570 /// // maxs will be chunk-aligned (chunks are 8x8x8), so it includes more space
571 /// assert!(maxs.x >= 3 && maxs.y >= 1 && maxs.z >= 1);
572 /// # }
573 /// ```
574 pub fn new(voxel_size: Vector, grid_coordinates: &[IVector]) -> Self {
575 let mut result = Self {
576 chunk_bvh: Bvh::new(),
577 chunk_headers: HashMap::default(),
578 chunk_keys: vec![],
579 chunks: vec![],
580 free_chunks: vec![],
581 voxel_size,
582 };
583
584 for vox in grid_coordinates {
585 let (chunk_key, id_in_chunk) = Self::chunk_key_and_id_in_chunk(*vox);
586 let chunk_header = result.chunk_headers.entry(chunk_key).or_insert_with(|| {
587 let id = result.chunks.len();
588 result.chunks.push(VoxelsChunk::default());
589 result.chunk_keys.push(chunk_key);
590 VoxelsChunkHeader { id, len: 0 }
591 });
592 chunk_header.len += 1;
593 result.chunks[chunk_header.id].states[id_in_chunk] = VoxelState::INTERIOR;
594 }
595
596 result.chunk_bvh = Bvh::from_iter(
597 BvhBuildStrategy::Ploc,
598 result.chunk_headers.iter().map(|(chunk_key, chunk_id)| {
599 (chunk_id.id, VoxelsChunk::aabb(chunk_key, result.voxel_size))
600 }),
601 );
602
603 result.recompute_all_voxels_states();
604 result
605 }
606
607 /// Computes a voxel shape from a set of world-space points.
608 ///
609 /// This constructor converts continuous world-space coordinates into discrete grid coordinates
610 /// by snapping each point to the voxel grid. Multiple points can map to the same voxel.
611 ///
612 /// # How it Works
613 ///
614 /// Each point is converted to grid coordinates by:
615 /// 1. Dividing the point's coordinates by `voxel_size`
616 /// 2. Taking the floor to get integer grid coordinates
617 /// 3. Removing duplicates (multiple points in the same voxel become one voxel)
618 ///
619 /// For example, with `voxel_size = 1.0`:
620 /// - Vector `(0.3, 0.7, 0.9)` → Grid `(0, 0, 0)`
621 /// - Vector `(1.1, 0.2, 0.5)` → Grid `(1, 0, 0)`
622 /// - Vector `(0.9, 0.1, 0.8)` → Grid `(0, 0, 0)` (merges with first)
623 ///
624 /// # Use Cases
625 ///
626 /// - Converting point clouds into voxel representations
627 /// - Creating voxel shapes from scattered data
628 /// - Simplifying complex point sets into uniform grids
629 ///
630 /// # Examples
631 ///
632 /// ```
633 /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
634 /// use parry3d::shape::Voxels;
635 /// use parry3d::math::Vector;
636 ///
637 /// // Sample points in world space
638 /// let points = vec![
639 /// Vector::new(0.1, 0.2, 0.3), // → Grid (0, 0, 0)
640 /// Vector::new(0.7, 0.8, 0.9), // → Grid (0, 0, 0) - same voxel!
641 /// Vector::new(1.2, 0.3, 0.1), // → Grid (1, 0, 0)
642 /// Vector::new(0.5, 1.5, 0.2), // → Grid (0, 1, 0)
643 /// ];
644 ///
645 /// // Create voxels with 1.0 unit size
646 /// let voxels = Voxels::from_points(Vector::new(1.0, 1.0, 1.0), &points);
647 ///
648 /// // Only 3 unique voxels created (first two points merged)
649 /// let filled_count = voxels.voxels()
650 /// .filter(|v| !v.state.is_empty())
651 /// .count();
652 /// assert_eq!(filled_count, 3);
653 /// # }
654 /// ```
655 ///
656 /// ```
657 /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
658 /// use parry3d::shape::Voxels;
659 /// use parry3d::math::{Vector, IVector};
660 ///
661 /// // Higher resolution voxelization
662 /// let points = vec![
663 /// Vector::ZERO,
664 /// Vector::new(1.0, 1.0, 1.0),
665 /// ];
666 ///
667 /// // Smaller voxels = finer detail
668 /// let voxels = Voxels::from_points(Vector::new(0.5, 0.5, 0.5), &points);
669 ///
670 /// // First point at grid (0,0,0), second at grid (2,2,2) due to smaller voxel size
671 /// assert!(voxels.voxel_state(IVector::new(0, 0, 0)).is_some());
672 /// assert!(voxels.voxel_state(IVector::new(2, 2, 2)).is_some());
673 /// # }
674 /// ```
675 pub fn from_points(voxel_size: Vector, points: &[Vector]) -> Self {
676 let voxels: Vec<_> = points
677 .iter()
678 .map(|pt| vect_to_ivect((*pt / voxel_size).floor()))
679 .collect();
680 Self::new(voxel_size, &voxels)
681 }
682
683 pub(crate) fn chunk_bvh(&self) -> &Bvh {
684 &self.chunk_bvh
685 }
686
687 /// The semi-open range of grid coordinates covered by this voxel shape.
688 ///
689 /// Returns `[mins, maxs]` where the domain is the semi-open interval `[mins, maxs)`,
690 /// meaning `mins` is included but `maxs` is excluded. This provides conservative bounds
691 /// on the range of voxel grid coordinates that might be filled.
692 ///
693 /// This is useful for:
694 /// - Determining the spatial extent of the voxel shape
695 /// - Pre-allocating storage for processing voxels
696 /// - Clipping operations to valid regions
697 ///
698 /// # Examples
699 ///
700 /// ```
701 /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
702 /// use parry3d::shape::Voxels;
703 /// use parry3d::math::{Vector, IVector};
704 ///
705 /// let voxels = Voxels::new(
706 /// Vector::new(1.0, 1.0, 1.0),
707 /// &[IVector::new(0, 0, 0), IVector::new(2, 3, 1)],
708 /// );
709 ///
710 /// let [mins, maxs] = voxels.domain();
711 /// assert_eq!(mins, IVector::new(0, 0, 0));
712 /// // Domain is conservative and chunk-aligned
713 /// assert!(maxs.x > 2 && maxs.y > 3 && maxs.z > 1);
714 ///
715 /// // Iterate through filled voxels (more efficient than iterating domain)
716 /// for voxel in voxels.voxels() {
717 /// if !voxel.state.is_empty() {
718 /// println!("Filled voxel at {:?}", voxel.grid_coords);
719 /// }
720 /// }
721 /// # }
722 /// ```
723 pub fn domain(&self) -> [IVector; 2] {
724 let aabb = self.chunk_bvh.root_aabb();
725
726 // NOTE that we shift the AABB's bounds so the endpoint matches a voxel center
727 // to avoid rounding errors.
728 let half_sz = self.voxel_size() / 2.0;
729 let mins = self.voxel_at_point(aabb.mins + half_sz);
730 // + 1 because the range is semi-open.
731 let maxs = self.voxel_at_point(aabb.maxs - half_sz);
732 let one = IVector::splat(1);
733 [mins, maxs + one]
734 }
735
736 // /// The number of voxels along each coordinate axis.
737 // pub fn dimensions(&self) -> Vector<u32> {
738 // (self.domain_maxs - self.domain_mins).map(|e| e as u32)
739 // }
740
741 /// The size of each voxel part this [`Voxels`] shape.
742 pub fn voxel_size(&self) -> Vector {
743 self.voxel_size
744 }
745
746 /// Scale this shape.
747 pub fn scaled(mut self, scale: Vector) -> Self {
748 self.voxel_size *= scale;
749 self
750 }
751
752 /// A reference to the chunk with id `chunk_id`.
753 ///
754 /// Panics if the chunk doesn’t exist.
755 pub fn chunk_ref(&self, chunk_id: u32) -> VoxelsChunkRef<'_> {
756 VoxelsChunkRef {
757 my_id: chunk_id as usize,
758 parent: self,
759 states: &self.chunks[chunk_id as usize].states,
760 key: &self.chunk_keys[chunk_id as usize],
761 }
762 }
763
764 /// The AABB of the voxel with the given quantized `key`.
765 pub fn voxel_aabb(&self, key: IVector) -> Aabb {
766 let center = self.voxel_center(key);
767 let hext = self.voxel_size / 2.0;
768 Aabb::from_half_extents(center, hext)
769 }
770
771 /// Returns the state of the voxel at the given grid coordinates.
772 ///
773 /// Returns `None` if the voxel doesn't exist in this shape's internal storage,
774 /// or `Some(VoxelState)` containing information about whether the voxel is filled
775 /// and which of its neighbors are also filled.
776 ///
777 /// # Examples
778 ///
779 /// ```
780 /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
781 /// use parry3d::shape::Voxels;
782 /// use parry3d::math::{Vector, IVector};
783 ///
784 /// let voxels = Voxels::new(
785 /// Vector::new(1.0, 1.0, 1.0),
786 /// &[IVector::new(0, 0, 0), IVector::new(1, 0, 0)],
787 /// );
788 ///
789 /// // Query an existing voxel
790 /// if let Some(state) = voxels.voxel_state(IVector::new(0, 0, 0)) {
791 /// assert!(!state.is_empty());
792 /// println!("Voxel type: {:?}", state.voxel_type());
793 /// }
794 ///
795 /// // Query a non-existent voxel
796 /// assert!(voxels.voxel_state(IVector::new(10, 10, 10)).is_none());
797 /// # }
798 /// ```
799 pub fn voxel_state(&self, key: IVector) -> Option<VoxelState> {
800 let vid = self.linear_index(key)?;
801 Some(self.chunks[vid.chunk_id].states[vid.id_in_chunk])
802 }
803
804 /// Calculates the grid coordinates of the voxel containing the given world-space point.
805 ///
806 /// This conversion is independent of whether the voxel is actually filled or empty - it
807 /// simply determines which grid cell the point falls into based on the voxel size.
808 ///
809 /// # Examples
810 ///
811 /// ```
812 /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
813 /// use parry3d::shape::Voxels;
814 /// use parry3d::math::{Vector, IVector};
815 ///
816 /// let voxels = Voxels::new(
817 /// Vector::new(1.0, 1.0, 1.0),
818 /// &[IVector::new(0, 0, 0)],
819 /// );
820 ///
821 /// // Vector in first voxel (center at 0.5, 0.5, 0.5)
822 /// assert_eq!(voxels.voxel_at_point(Vector::new(0.3, 0.7, 0.2)), IVector::new(0, 0, 0));
823 ///
824 /// // Vector just inside second voxel boundary
825 /// assert_eq!(voxels.voxel_at_point(Vector::new(1.0, 0.0, 0.0)), IVector::new(1, 0, 0));
826 ///
827 /// // Negative coordinates work too
828 /// assert_eq!(voxels.voxel_at_point(Vector::new(-0.5, -0.5, -0.5)), IVector::new(-1, -1, -1));
829 /// # }
830 /// ```
831 ///
832 /// ```
833 /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
834 /// use parry3d::shape::Voxels;
835 /// use parry3d::math::{Vector, IVector};
836 ///
837 /// // With non-uniform voxel size
838 /// let voxels = Voxels::new(
839 /// Vector::new(2.0, 0.5, 1.0),
840 /// &[],
841 /// );
842 ///
843 /// // X coordinate divided by 2.0, Y by 0.5, Z by 1.0
844 /// assert_eq!(voxels.voxel_at_point(Vector::new(3.0, 1.2, 0.8)), IVector::new(1, 2, 0));
845 /// # }
846 /// ```
847 pub fn voxel_at_point(&self, point: Vector) -> IVector {
848 vect_to_ivect((point / self.voxel_size).floor())
849 }
850
851 /// Gets the voxel at the given flat voxel index.
852 pub fn voxel_at_flat_id(&self, id: u32) -> Option<IVector> {
853 let vid = VoxelIndex::from_flat_id(id as usize);
854 let chunk_key = self.chunk_keys.get(vid.chunk_id)?;
855 if *chunk_key == VoxelsChunk::INVALID_CHUNK_KEY {
856 return None;
857 }
858
859 Some(VoxelsChunk::voxel_key_at_id(
860 *chunk_key,
861 vid.id_in_chunk as u32,
862 ))
863 }
864
865 /// The range of grid coordinates of voxels intersecting the given AABB.
866 ///
867 /// The returned range covers both empty and non-empty voxels, and is not limited to the
868 /// bounds defined by [`Self::domain`].
869 /// The range is semi, open, i.e., the range along each dimension `i` is understood as
870 /// the semi-open interval: `range[0][i]..range[1][i]`.
871 pub fn voxel_range_intersecting_local_aabb(&self, aabb: &Aabb) -> [IVector; 2] {
872 let mins = vect_to_ivect((aabb.mins / self.voxel_size).floor());
873 let maxs = vect_to_ivect((aabb.maxs / self.voxel_size).ceil());
874 [mins, maxs]
875 }
876
877 /// The AABB of a given range of voxels.
878 ///
879 /// The AABB is computed independently of [`Self::domain`] and independently of whether
880 /// the voxels contained within are empty or not.
881 pub fn voxel_range_aabb(&self, mins: IVector, maxs: IVector) -> Aabb {
882 Aabb {
883 mins: ivect_to_vect(mins) * self.voxel_size,
884 maxs: ivect_to_vect(maxs) * self.voxel_size,
885 }
886 }
887
888 /// Aligns the given AABB with the voxelized grid.
889 ///
890 /// The aligned is calculated such that the returned AABB has corners lying at the grid
891 /// intersections (i.e. matches voxel corners) and fully contains the input `aabb`.
892 pub fn align_aabb_to_grid(&self, aabb: &Aabb) -> Aabb {
893 let mins = (aabb.mins / self.voxel_size).floor() * self.voxel_size;
894 let maxs = (aabb.maxs / self.voxel_size).ceil() * self.voxel_size;
895 Aabb { mins, maxs }
896 }
897
898 /// Iterates through every voxel intersecting the given aabb.
899 ///
900 /// Returns the voxel’s linearized id, center, and state.
901 pub fn voxels_intersecting_local_aabb(
902 &self,
903 aabb: &Aabb,
904 ) -> impl Iterator<Item = VoxelData> + '_ {
905 let [mins, maxs] = self.voxel_range_intersecting_local_aabb(aabb);
906 self.voxels_in_range(mins, maxs)
907 }
908
909 /// The center point of all the voxels in this shape (including empty ones).
910 ///
911 /// The voxel data associated to each center is provided to determine what kind of voxel
912 /// it is (and, in particular, if it is empty or full).
913 pub fn voxels(&self) -> impl Iterator<Item = VoxelData> + '_ {
914 let aabb = self.chunk_bvh.root_aabb();
915 self.voxels_in_range(
916 self.voxel_at_point(aabb.mins),
917 self.voxel_at_point(aabb.maxs),
918 )
919 }
920
921 /// Iterate through the data of all the voxels within the given (semi-open) voxel grid indices.
922 ///
923 /// Note that this yields both empty and non-empty voxels within the range. This does not
924 /// include any voxel that falls outside [`Self::domain`].
925 pub fn voxels_in_range(
926 &self,
927 mins: IVector,
928 maxs: IVector,
929 ) -> impl Iterator<Item = VoxelData> + '_ {
930 let range_aabb = Aabb::new(self.voxel_center(mins), self.voxel_center(maxs));
931
932 self.chunk_bvh
933 .leaves(move |node: &BvhNode| node.aabb().intersects(&range_aabb))
934 .flat_map(move |chunk_id| {
935 let chunk = self.chunk_ref(chunk_id);
936 chunk.voxels_in_range(mins, maxs)
937 })
938 }
939
940 fn voxel_to_chunk_key(voxel_key: IVector) -> IVector {
941 fn div_floor(a: Int, b: usize) -> Int {
942 let sign = (a < 0) as Int;
943 (a + sign) / b as Int - sign
944 }
945
946 #[cfg(feature = "dim2")]
947 {
948 IVector::new(
949 div_floor(voxel_key.x, VoxelsChunk::VOXELS_PER_CHUNK_DIM),
950 div_floor(voxel_key.y, VoxelsChunk::VOXELS_PER_CHUNK_DIM),
951 )
952 }
953 #[cfg(feature = "dim3")]
954 {
955 IVector::new(
956 div_floor(voxel_key.x, VoxelsChunk::VOXELS_PER_CHUNK_DIM),
957 div_floor(voxel_key.y, VoxelsChunk::VOXELS_PER_CHUNK_DIM),
958 div_floor(voxel_key.z, VoxelsChunk::VOXELS_PER_CHUNK_DIM),
959 )
960 }
961 }
962
963 /// Given a voxel key, returns the key of the voxel chunk that contains it, as well as the
964 /// linear index of the voxel within that chunk.
965 #[cfg(feature = "dim2")]
966 pub(super) fn chunk_key_and_id_in_chunk(voxel_key: IVector) -> (IVector, usize) {
967 let chunk_key = Self::voxel_to_chunk_key(voxel_key);
968 // NOTE: always positive since we subtracted the smallest possible key on that chunk.
969 let voxel_key_in_chunk = voxel_key - chunk_key * VoxelsChunk::VOXELS_PER_CHUNK_DIM as Int;
970 let id_in_chunk = (voxel_key_in_chunk.x
971 + voxel_key_in_chunk.y * VoxelsChunk::VOXELS_PER_CHUNK_DIM as Int)
972 as usize;
973 (chunk_key, id_in_chunk)
974 }
975
976 /// Given a voxel key, returns the key of the voxel chunk that contains it, as well as the
977 /// linear index of the voxel within that chunk.
978 #[cfg(feature = "dim3")]
979 pub(super) fn chunk_key_and_id_in_chunk(voxel_key: IVector) -> (IVector, usize) {
980 let chunk_key = Self::voxel_to_chunk_key(voxel_key);
981 // NOTE: always positive since we subtracted the smallest possible key on that chunk.
982 let voxel_key_in_chunk = voxel_key - chunk_key * VoxelsChunk::VOXELS_PER_CHUNK_DIM as Int;
983 let id_in_chunk = (voxel_key_in_chunk.x
984 + voxel_key_in_chunk.y * VoxelsChunk::VOXELS_PER_CHUNK_DIM as Int
985 + voxel_key_in_chunk.z
986 * VoxelsChunk::VOXELS_PER_CHUNK_DIM as Int
987 * VoxelsChunk::VOXELS_PER_CHUNK_DIM as Int) as usize;
988 (chunk_key, id_in_chunk)
989 }
990
991 /// The linearized index associated to the given voxel key.
992 pub fn linear_index(&self, voxel_key: IVector) -> Option<VoxelIndex> {
993 let (chunk_key, id_in_chunk) = Self::chunk_key_and_id_in_chunk(voxel_key);
994 let chunk_id = self.chunk_headers.get(&chunk_key)?.id;
995 Some(VoxelIndex {
996 chunk_id,
997 id_in_chunk,
998 })
999 }
1000
1001 /// The world-space center position of the voxel with the given grid coordinates.
1002 ///
1003 /// Returns the center point regardless of whether the voxel is actually filled.
1004 ///
1005 /// # Examples
1006 ///
1007 /// ```
1008 /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
1009 /// use parry3d::shape::Voxels;
1010 /// use parry3d::math::{Vector, IVector};
1011 ///
1012 /// let voxels = Voxels::new(
1013 /// Vector::new(1.0, 1.0, 1.0),
1014 /// &[IVector::new(0, 0, 0)],
1015 /// );
1016 ///
1017 /// // Center of voxel at origin
1018 /// assert_eq!(voxels.voxel_center(IVector::new(0, 0, 0)), Vector::new(0.5, 0.5, 0.5));
1019 ///
1020 /// // Center of voxel at (1, 2, 3)
1021 /// assert_eq!(voxels.voxel_center(IVector::new(1, 2, 3)), Vector::new(1.5, 2.5, 3.5));
1022 /// # }
1023 /// ```
1024 pub fn voxel_center(&self, key: IVector) -> Vector {
1025 (ivect_to_vect(key) + Vector::splat(0.5)) * self.voxel_size
1026 }
1027}