parry2d/transformation/voxelization/
voxel_set.rs

1// Rust port, with modifications, of https://github.com/kmammou/v-hacd/blob/master/src/VHACD_Lib/src/vhacdVolume.cpp
2// By Khaled Mamou
3//
4// # License of the original C++ code:
5// > Copyright (c) 2011 Khaled Mamou (kmamou at gmail dot com)
6// > All rights reserved.
7// >
8// >
9// > Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
10// >
11// > 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
12// >
13// > 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
14// >
15// > 3. The names of the contributors may not be used to endorse or promote products derived from this software without specific prior written permission.
16// >
17// > THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
18
19use super::{FillMode, VoxelizedVolume};
20use crate::bounding_volume::Aabb;
21use crate::math::{ivect_to_vect, MatExt, VectorExt};
22use crate::math::{IVector, Matrix, Real, Vector, DIM};
23use crate::transformation::vhacd::CutPlane;
24use alloc::sync::Arc;
25use alloc::{vec, vec::Vec};
26
27#[cfg(feature = "dim2")]
28type ConvexHull = Vec<Vector>;
29#[cfg(feature = "dim3")]
30type ConvexHull = (Vec<Vector>, Vec<[u32; DIM]>);
31
32/// A single voxel in a voxel grid.
33///
34/// A voxel represents a cubic (or square in 2D) cell in a discrete grid. Each voxel is identified
35/// by its integer grid coordinates and contains metadata about its position relative to the
36/// voxelized shape.
37///
38/// # Fields
39///
40/// - `coords`: The grid position `(i, j)` in 2D or `(i, j, k)` in 3D. These are **integer**
41///   coordinates in the voxel grid, not world-space coordinates.
42///
43/// - `is_on_surface`: Whether this voxel intersects the surface boundary of the shape. If `false`,
44///   the voxel is completely inside the shape (only possible when using [`FillMode::FloodFill`]).
45///
46/// # Example
47///
48/// ```
49/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
50/// use parry3d::transformation::voxelization::{FillMode, VoxelSet};
51/// use parry3d::shape::Ball;
52/// ///
53/// let ball = Ball::new(1.0);
54/// let (vertices, indices) = ball.to_trimesh(10, 10);
55///
56/// let voxels = VoxelSet::voxelize(
57///     &vertices,
58///     &indices,
59///     8,
60///     FillMode::FloodFill { detect_cavities: false },
61///     false,
62/// );
63///
64/// // Examine individual voxels
65/// for voxel in voxels.voxels() {
66///     // Grid coordinates (integer position in the voxel grid)
67///     println!("Grid position: {:?}", voxel);
68///
69///     // World-space position (actual 3D coordinates)
70///     let world_pos = voxels.get_voxel_point(voxel);
71///     println!("World position: {:?}", world_pos);
72///
73///     // Surface vs interior
74///     if voxel.is_on_surface {
75///         println!("This voxel is on the surface boundary");
76///     } else {
77///         println!("This voxel is inside the shape");
78///     }
79/// }
80/// # }
81/// ```
82#[derive(Copy, Clone, Debug)]
83pub struct Voxel {
84    /// The integer coordinates of the voxel as part of the voxel grid.
85    pub coords: IVector,
86    /// Is this voxel on the surface of the volume (i.e. not inside of it)?
87    pub is_on_surface: bool,
88    /// Range of indices (to be looked up into the `VoxelSet` primitive map)
89    /// of the primitives intersected by this voxel.
90    pub(crate) intersections_range: (usize, usize),
91}
92
93impl Default for Voxel {
94    fn default() -> Self {
95        Self {
96            coords: IVector::ZERO,
97            is_on_surface: false,
98            intersections_range: (0, 0),
99        }
100    }
101}
102
103/// A sparse set of filled voxels resulting from voxelization.
104///
105/// `VoxelSet` is a memory-efficient storage format that only contains voxels marked as "filled"
106/// during the voxelization process. This is much more efficient than storing a dense 3D array
107/// for shapes that are mostly empty or have a hollow interior.
108///
109/// # Structure
110///
111/// Each `VoxelSet` contains:
112/// - A list of filled voxels with their grid coordinates
113/// - The origin point and scale factor for converting grid coordinates to world space
114/// - Optional metadata tracking which primitives (triangles/segments) intersect each voxel
115///
116/// # Grid Coordinates vs World Coordinates
117///
118/// Voxels are stored with integer grid coordinates `(i, j, k)`. To convert to world-space
119/// coordinates, use:
120///
121/// ```text
122/// world_position = origin + (i, j, k) * scale
123/// ```
124///
125/// The [`get_voxel_point()`](VoxelSet::get_voxel_point) method does this conversion for you.
126///
127/// # Memory Layout
128///
129/// Unlike [`VoxelizedVolume`] which stores a dense 3D array, `VoxelSet` uses sparse storage:
130/// - Only filled voxels are stored (typically much fewer than total grid cells)
131/// - Memory usage is `O(filled_voxels)` instead of `O(resolution^3)`
132/// - Ideal for shapes with low surface-to-volume ratio
133///
134/// # Example: Basic Usage
135///
136/// ```
137/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
138/// use parry3d::transformation::voxelization::{FillMode, VoxelSet};
139/// use parry3d::shape::Cuboid;
140/// use parry3d::math::Vector;
141///
142/// // Create and voxelize a shape
143/// let cuboid = Cuboid::new(Vector::new(1.0, 1.0, 1.0));
144/// let (vertices, indices) = cuboid.to_trimesh();
145///
146/// let voxels = VoxelSet::voxelize(
147///     &vertices,
148///     &indices,
149///     10,                      // resolution
150///     FillMode::SurfaceOnly,   // hollow surface only
151///     false,                   // no primitive mapping
152/// );
153///
154/// // Query the voxel set
155/// println!("Number of voxels: {}", voxels.len());
156/// println!("Voxel size: {}", voxels.scale);
157/// println!("Total volume: {}", voxels.compute_volume());
158///
159/// // Access voxels
160/// for voxel in voxels.voxels() {
161///     let world_pos = voxels.get_voxel_point(voxel);
162///     println!("Voxel at {:?}", world_pos);
163/// }
164/// # }
165/// ```
166///
167/// # Example: Volume Computation
168///
169/// ```
170/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
171/// use parry3d::transformation::voxelization::{FillMode, VoxelSet};
172/// use parry3d::shape::Ball;
173/// ///
174/// let ball = Ball::new(1.0);
175/// let (vertices, indices) = ball.to_trimesh(20, 20);
176///
177/// // Voxelize with interior filling
178/// let voxels = VoxelSet::voxelize(
179///     &vertices,
180///     &indices,
181///     20,
182///     FillMode::FloodFill { detect_cavities: false },
183///     false,
184/// );
185///
186/// // Compute approximate volume
187/// let voxel_volume = voxels.compute_volume();
188/// let expected_volume = 4.0 / 3.0 * std::f32::consts::PI;
189/// println!("Voxel volume: {:.3}, Expected: {:.3}", voxel_volume, expected_volume);
190/// # }
191/// ```
192pub struct VoxelSet {
193    /// The 3D origin of this voxel-set.
194    pub origin: Vector,
195    /// The scale factor between the voxel integer coordinates and their
196    /// actual float world-space coordinates.
197    pub scale: Real,
198    pub(crate) min_bb_voxels: IVector,
199    pub(crate) max_bb_voxels: IVector,
200    pub(crate) voxels: Vec<Voxel>,
201    pub(crate) intersections: Arc<Vec<u32>>,
202    pub(crate) primitive_classes: Arc<Vec<u32>>,
203}
204
205impl Default for VoxelSet {
206    fn default() -> Self {
207        Self::new()
208    }
209}
210
211impl VoxelSet {
212    /// Creates a new empty set of voxels.
213    pub fn new() -> Self {
214        Self {
215            origin: Vector::ZERO,
216            min_bb_voxels: IVector::ZERO,
217            max_bb_voxels: IVector::ONE,
218            scale: 1.0,
219            voxels: Vec::new(),
220            intersections: Arc::new(Vec::new()),
221            primitive_classes: Arc::new(Vec::new()),
222        }
223    }
224
225    /// The volume of a single voxel of this voxel set.
226    #[cfg(feature = "dim2")]
227    pub fn voxel_volume(&self) -> Real {
228        self.scale * self.scale
229    }
230
231    /// The volume of a single voxel of this voxel set.
232    #[cfg(feature = "dim3")]
233    pub fn voxel_volume(&self) -> Real {
234        self.scale * self.scale * self.scale
235    }
236
237    /// Voxelizes a shape by specifying the physical size of each voxel.
238    ///
239    /// This creates a voxelized representation of a shape defined by its boundary:
240    /// - In 3D: A triangle mesh (vertices and triangle indices)
241    /// - In 2D: A polyline (vertices and segment indices)
242    ///
243    /// The resolution is automatically determined based on the shape's bounding box
244    /// and the requested voxel size.
245    ///
246    /// # Parameters
247    ///
248    /// * `points` - Vertex buffer defining the boundary of the shape. These are the
249    ///   vertices of the triangle mesh (3D) or polyline (2D).
250    ///
251    /// * `indices` - Index buffer defining the boundary primitives:
252    ///   - 3D: Each element is `[v0, v1, v2]` defining a triangle
253    ///   - 2D: Each element is `[v0, v1]` defining a line segment
254    ///
255    /// * `voxel_size` - The physical size (edge length) of each cubic voxel. Smaller
256    ///   values give higher resolution but use more memory.
257    ///
258    /// * `fill_mode` - Controls which voxels are marked as filled:
259    ///   - [`FillMode::SurfaceOnly`]: Only voxels intersecting the boundary
260    ///   - [`FillMode::FloodFill`]: Surface voxels plus interior voxels
261    ///
262    /// * `keep_voxel_to_primitives_map` - If `true`, stores which primitives (triangles
263    ///   or segments) intersect each voxel. Required for [`compute_exact_convex_hull()`]
264    ///   and [`compute_primitive_intersections()`], but uses additional memory.
265    ///
266    /// # Returns
267    ///
268    /// A sparse `VoxelSet` containing only the filled voxels.
269    ///
270    /// # Example
271    ///
272    /// ```
273    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
274    /// use parry3d::transformation::voxelization::{FillMode, VoxelSet};
275    /// use parry3d::shape::Ball;
276    ///
277    /// let ball = Ball::new(2.0);  // radius = 2.0
278    /// let (vertices, indices) = ball.to_trimesh(20, 20);
279    ///
280    /// // Create voxels with 0.1 unit size
281    /// let voxels = VoxelSet::with_voxel_size(
282    ///     &vertices,
283    ///     &indices,
284    ///     0.1,                    // each voxel is 0.1 x 0.1 x 0.1
285    ///     FillMode::SurfaceOnly,
286    ///     false,
287    /// );
288    ///
289    /// println!("Created {} voxels", voxels.len());
290    /// println!("Each voxel has volume {}", voxels.voxel_volume());
291    /// # }
292    /// ```
293    ///
294    /// [`compute_exact_convex_hull()`]: VoxelSet::compute_exact_convex_hull
295    /// [`compute_primitive_intersections()`]: VoxelSet::compute_primitive_intersections
296    pub fn with_voxel_size(
297        points: &[Vector],
298        indices: &[[u32; DIM]],
299        voxel_size: Real,
300        fill_mode: FillMode,
301        keep_voxel_to_primitives_map: bool,
302    ) -> Self {
303        VoxelizedVolume::with_voxel_size(
304            points,
305            indices,
306            voxel_size,
307            fill_mode,
308            keep_voxel_to_primitives_map,
309        )
310        .into()
311    }
312
313    /// Voxelizes a shape by specifying the grid resolution along the longest axis.
314    ///
315    /// This creates a voxelized representation of a shape defined by its boundary:
316    /// - In 3D: A triangle mesh (vertices and triangle indices)
317    /// - In 2D: A polyline (vertices and segment indices)
318    ///
319    /// The voxel size is automatically computed to fit the specified number of subdivisions
320    /// along the shape's longest axis, while maintaining cubic (or square in 2D) voxels.
321    /// Other axes will have proportionally determined resolutions.
322    ///
323    /// # Parameters
324    ///
325    /// * `points` - Vertex buffer defining the boundary of the shape. These are the
326    ///   vertices of the triangle mesh (3D) or polyline (2D).
327    ///
328    /// * `indices` - Index buffer defining the boundary primitives:
329    ///   - 3D: Each element is `[v0, v1, v2]` defining a triangle
330    ///   - 2D: Each element is `[v0, v1]` defining a line segment
331    ///
332    /// * `resolution` - Number of voxel subdivisions along the longest axis of the shape's
333    ///   bounding box. Higher values give more detail but use more memory.
334    ///   For example, `resolution = 10` creates approximately 10 voxels along the longest dimension.
335    ///
336    /// * `fill_mode` - Controls which voxels are marked as filled:
337    ///   - [`FillMode::SurfaceOnly`]: Only voxels intersecting the boundary
338    ///   - [`FillMode::FloodFill`]: Surface voxels plus interior voxels
339    ///
340    /// * `keep_voxel_to_primitives_map` - If `true`, stores which primitives (triangles
341    ///   or segments) intersect each voxel. Required for [`compute_exact_convex_hull()`]
342    ///   and [`compute_primitive_intersections()`], but uses additional memory.
343    ///
344    /// # Returns
345    ///
346    /// A sparse `VoxelSet` containing only the filled voxels.
347    ///
348    /// # Example
349    ///
350    /// ```
351    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
352    /// use parry3d::transformation::voxelization::{FillMode, VoxelSet};
353    /// use parry3d::shape::Cuboid;
354    /// use parry3d::math::Vector;
355    ///
356    /// // Create a cuboid: 2 units wide (x), 1 unit tall (y), 0.5 units deep (z)
357    /// let cuboid = Cuboid::new(Vector::new(1.0, 0.5, 0.25));
358    /// let (vertices, indices) = cuboid.to_trimesh();
359    ///
360    /// // Voxelize with 20 subdivisions along the longest axis (x = 2.0)
361    /// // Other axes will be proportionally subdivided to maintain cubic voxels
362    /// let voxels = VoxelSet::voxelize(
363    ///     &vertices,
364    ///     &indices,
365    ///     20,                           // 20 voxels along x-axis
366    ///     FillMode::FloodFill {
367    ///         detect_cavities: false,
368    ///     },
369    ///     false,
370    /// );
371    ///
372    /// println!("Created {} voxels", voxels.len());
373    /// println!("Voxel scale: {}", voxels.scale);  // automatically computed
374    /// println!("Total volume: {}", voxels.compute_volume());
375    /// # }
376    /// ```
377    ///
378    /// # Choosing Resolution
379    ///
380    /// - **Low (5-10)**: Fast, coarse approximation, good for rough collision proxies
381    /// - **Medium (10-30)**: Balanced detail and performance, suitable for most use cases
382    /// - **High (50+)**: Fine detail, high memory usage, used for precise volume computation
383    ///
384    /// [`compute_exact_convex_hull()`]: VoxelSet::compute_exact_convex_hull
385    /// [`compute_primitive_intersections()`]: VoxelSet::compute_primitive_intersections
386    pub fn voxelize(
387        points: &[Vector],
388        indices: &[[u32; DIM]],
389        resolution: u32,
390        fill_mode: FillMode,
391        keep_voxel_to_primitives_map: bool,
392    ) -> Self {
393        VoxelizedVolume::voxelize(
394            points,
395            indices,
396            resolution,
397            fill_mode,
398            keep_voxel_to_primitives_map,
399        )
400        .into()
401    }
402
403    /// The minimal coordinates of the integer bounding-box of the voxels in this set.
404    pub fn min_bb_voxels(&self) -> IVector {
405        self.min_bb_voxels
406    }
407
408    /// The maximal coordinates of the integer bounding-box of the voxels in this set.
409    pub fn max_bb_voxels(&self) -> IVector {
410        self.max_bb_voxels
411    }
412
413    /// Computes the total volume occupied by all voxels in this set.
414    ///
415    /// This calculates the approximate volume by multiplying the number of filled voxels
416    /// by the volume of each individual voxel. The result is an approximation of the
417    /// volume of the original shape.
418    ///
419    /// # Accuracy
420    ///
421    /// The accuracy depends on the voxelization resolution:
422    /// - Higher resolution (smaller voxels) → more accurate volume
423    /// - Lower resolution (larger voxels) → faster but less accurate
424    ///
425    /// # Example
426    ///
427    /// ```
428    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
429    /// use parry3d::transformation::voxelization::{FillMode, VoxelSet};
430    /// use parry3d::shape::Ball;
431    ///
432    /// let ball = Ball::new(1.0);
433    /// let (vertices, indices) = ball.to_trimesh(20, 20);
434    ///
435    /// let voxels = VoxelSet::voxelize(
436    ///     &vertices,
437    ///     &indices,
438    ///     30,  // Higher resolution for better accuracy
439    ///     FillMode::FloodFill { detect_cavities: false },
440    ///     false,
441    /// );
442    ///
443    /// let voxel_volume = voxels.compute_volume();
444    /// let expected_volume = 4.0 / 3.0 * std::f32::consts::PI * 1.0_f32.powi(3);
445    ///
446    /// println!("Voxel volume: {:.3}", voxel_volume);
447    /// println!("Expected volume: {:.3}", expected_volume);
448    /// println!("Error: {:.1}%", ((voxel_volume - expected_volume).abs() / expected_volume * 100.0));
449    /// # }
450    /// ```
451    pub fn compute_volume(&self) -> Real {
452        self.voxel_volume() * self.voxels.len() as Real
453    }
454
455    /// Converts voxel grid coordinates to world-space coordinates.
456    ///
457    /// Given a voxel, this computes the world-space position of the voxel's center.
458    /// The conversion formula is:
459    ///
460    /// ```text
461    /// world_position = origin + (voxel + 0.5) * scale
462    /// ```
463    ///
464    /// Note that we add 0.5 to get the center of the voxel rather than its corner.
465    ///
466    /// # Example
467    ///
468    /// ```
469    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
470    /// use parry3d::transformation::voxelization::{FillMode, VoxelSet};
471    /// use parry3d::shape::Cuboid;
472    /// use parry3d::math::Vector;
473    ///
474    /// let cuboid = Cuboid::new(Vector::new(1.0, 1.0, 1.0));
475    /// let (vertices, indices) = cuboid.to_trimesh();
476    ///
477    /// let voxels = VoxelSet::voxelize(
478    ///     &vertices,
479    ///     &indices,
480    ///     10,
481    ///     FillMode::SurfaceOnly,
482    ///     false,
483    /// );
484    ///
485    /// // Convert grid coordinates to world coordinates
486    /// for voxel in voxels.voxels() {
487    ///     let grid_coords = voxel;
488    ///     let world_coords = voxels.get_voxel_point(voxel);
489    ///
490    ///     println!("Grid: {:?} -> World: {:?}", grid_coords, world_coords);
491    /// }
492    /// # }
493    /// ```
494    pub fn get_voxel_point(&self, voxel: &Voxel) -> Vector {
495        #[cfg(feature = "dim2")]
496        let coords = Vector::new(voxel.coords.x as Real, voxel.coords.y as Real);
497        #[cfg(feature = "dim3")]
498        let coords = Vector::new(
499            voxel.coords.x as Real,
500            voxel.coords.y as Real,
501            voxel.coords.z as Real,
502        );
503        self.get_point(coords)
504    }
505
506    pub(crate) fn get_point(&self, voxel: Vector) -> Vector {
507        self.origin + voxel * self.scale
508    }
509
510    /// Does this voxel not contain any element?
511    pub fn is_empty(&self) -> bool {
512        self.len() == 0
513    }
514
515    /// The number of voxels in this set.
516    pub fn len(&self) -> usize {
517        self.voxels.len()
518    }
519
520    /// The set of voxels.
521    pub fn voxels(&self) -> &[Voxel] {
522        &self.voxels
523    }
524
525    /// Update the bounding box of this voxel set.
526    pub fn compute_bb(&mut self) {
527        let num_voxels = self.voxels.len();
528
529        if num_voxels == 0 {
530            return;
531        }
532
533        self.min_bb_voxels = self.voxels[0].coords;
534        self.max_bb_voxels = self.voxels[0].coords;
535
536        for p in 0..num_voxels {
537            self.min_bb_voxels = self.min_bb_voxels.min(self.voxels[p].coords);
538            self.max_bb_voxels = self.max_bb_voxels.max(self.voxels[p].coords);
539        }
540    }
541
542    /// Computes a precise convex hull by clipping primitives against voxel boundaries.
543    ///
544    /// This method produces a more accurate convex hull than [`compute_convex_hull()`] by:
545    /// 1. Finding which primitives (triangles/segments) intersect each voxel
546    /// 2. Clipping those primitives to the voxel boundaries
547    /// 3. Computing the convex hull from the clipped geometry
548    ///
549    /// This approach gives much tighter convex hulls, especially at lower resolutions, because
550    /// it uses the actual intersection geometry rather than just voxel centers.
551    ///
552    /// # Requirements
553    ///
554    /// This method requires that the voxel set was created with `keep_voxel_to_primitives_map = true`.
555    /// Otherwise, this method will panic.
556    ///
557    /// # Parameters
558    ///
559    /// * `points` - The same vertex buffer used during voxelization
560    /// * `indices` - The same index buffer used during voxelization
561    ///
562    /// # Returns
563    ///
564    /// In 2D: A vector of points forming the convex hull polygon
565    /// In 3D: A tuple of `(vertices, triangle_indices)` forming the convex hull mesh
566    ///
567    /// # Panics
568    ///
569    /// Panics if this `VoxelSet` was created with `keep_voxel_to_primitives_map = false`.
570    ///
571    /// # Example
572    ///
573    /// ```
574    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
575    /// use parry3d::transformation::voxelization::{FillMode, VoxelSet};
576    /// use parry3d::shape::Cuboid;
577    /// use parry3d::math::Vector;
578    ///
579    /// let cuboid = Cuboid::new(Vector::new(1.0, 1.0, 1.0));
580    /// let (vertices, indices) = cuboid.to_trimesh();
581    ///
582    /// // IMPORTANT: Set keep_voxel_to_primitives_map = true
583    /// let voxels = VoxelSet::voxelize(
584    ///     &vertices,
585    ///     &indices,
586    ///     10,
587    ///     FillMode::SurfaceOnly,
588    ///     true,  // Enable primitive mapping
589    /// );
590    ///
591    /// // Compute exact convex hull using triangle clipping
592    /// let (hull_vertices, hull_indices) = voxels.compute_exact_convex_hull(&vertices, &indices);
593    ///
594    /// println!("Exact convex hull: {} vertices, {} triangles",
595    ///          hull_vertices.len(), hull_indices.len());
596    /// # }
597    /// ```
598    ///
599    /// # Comparison with `compute_convex_hull()`
600    ///
601    /// - `compute_exact_convex_hull()`: More accurate, requires primitive mapping, slower
602    /// - `compute_convex_hull()`: Approximate, uses voxel centers, faster
603    ///
604    /// [`compute_convex_hull()`]: VoxelSet::compute_convex_hull
605    #[cfg(feature = "dim2")]
606    pub fn compute_exact_convex_hull(
607        &self,
608        points: &[Vector],
609        indices: &[[u32; DIM]],
610    ) -> Vec<Vector> {
611        self.do_compute_exact_convex_hull(points, indices)
612    }
613
614    /// Computes a precise convex hull by clipping primitives against voxel boundaries.
615    ///
616    /// This method produces a more accurate convex hull than [`compute_convex_hull()`] by:
617    /// 1. Finding which primitives (triangles/segments) intersect each voxel
618    /// 2. Clipping those primitives to the voxel boundaries
619    /// 3. Computing the convex hull from the clipped geometry
620    ///
621    /// This approach gives much tighter convex hulls, especially at lower resolutions, because
622    /// it uses the actual intersection geometry rather than just voxel centers.
623    ///
624    /// # Requirements
625    ///
626    /// This method requires that the voxel set was created with `keep_voxel_to_primitives_map = true`.
627    /// Otherwise, this method will panic.
628    ///
629    /// # Parameters
630    ///
631    /// * `points` - The same vertex buffer used during voxelization
632    /// * `indices` - The same index buffer used during voxelization
633    ///
634    /// # Returns
635    ///
636    /// In 2D: A vector of points forming the convex hull polygon
637    /// In 3D: A tuple of `(vertices, triangle_indices)` forming the convex hull mesh
638    ///
639    /// # Panics
640    ///
641    /// Panics if this `VoxelSet` was created with `keep_voxel_to_primitives_map = false`.
642    ///
643    /// # Example
644    ///
645    /// ```
646    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
647    /// use parry3d::transformation::voxelization::{FillMode, VoxelSet};
648    /// use parry3d::shape::Cuboid;
649    /// use parry3d::math::Vector;
650    ///
651    /// let cuboid = Cuboid::new(Vector::new(1.0, 1.0, 1.0));
652    /// let (vertices, indices) = cuboid.to_trimesh();
653    ///
654    /// // IMPORTANT: Set keep_voxel_to_primitives_map = true
655    /// let voxels = VoxelSet::voxelize(
656    ///     &vertices,
657    ///     &indices,
658    ///     10,
659    ///     FillMode::SurfaceOnly,
660    ///     true,  // Enable primitive mapping
661    /// );
662    ///
663    /// // Compute exact convex hull using triangle clipping
664    /// let (hull_vertices, hull_indices) = voxels.compute_exact_convex_hull(&vertices, &indices);
665    ///
666    /// println!("Exact convex hull: {} vertices, {} triangles",
667    ///          hull_vertices.len(), hull_indices.len());
668    /// # }
669    /// ```
670    ///
671    /// # Comparison with `compute_convex_hull()`
672    ///
673    /// - `compute_exact_convex_hull()`: More accurate, requires primitive mapping, slower
674    /// - `compute_convex_hull()`: Approximate, uses voxel centers, faster
675    ///
676    /// [`compute_convex_hull()`]: VoxelSet::compute_convex_hull
677    #[cfg(feature = "dim3")]
678    pub fn compute_exact_convex_hull(
679        &self,
680        points: &[Vector],
681        indices: &[[u32; DIM]],
682    ) -> (Vec<Vector>, Vec<[u32; DIM]>) {
683        self.do_compute_exact_convex_hull(points, indices)
684    }
685
686    fn do_compute_exact_convex_hull(
687        &self,
688        points: &[Vector],
689        indices: &[[u32; DIM]],
690    ) -> ConvexHull {
691        assert!(!self.intersections.is_empty(),
692                "Cannot compute exact convex hull without voxel-to-primitives-map. Consider passing voxel_to_primitives_map = true to the voxelizer.");
693        let mut surface_points = Vec::new();
694        #[cfg(feature = "dim3")]
695        let (mut polygon, mut workspace) = (Vec::new(), Vec::new());
696        let mut pushed_points = vec![false; points.len()];
697
698        // Grab all the points.
699        for voxel in self.voxels.iter().filter(|v| v.is_on_surface) {
700            let intersections =
701                &self.intersections[voxel.intersections_range.0..voxel.intersections_range.1];
702            for prim_id in intersections {
703                let ia = indices[*prim_id as usize][0] as usize;
704                let ib = indices[*prim_id as usize][1] as usize;
705                #[cfg(feature = "dim3")]
706                let ic = indices[*prim_id as usize][2] as usize;
707
708                // If the primitives have been classified by VHACD, we know that:
709                // - A class equal to Some(u32::MAX) means that the primitives intersects multiple
710                //   convex parts, so we need to split it.
711                // - A class equal to None means that we did not compute any classes (so we
712                //   must assume that each triangle have to be split since it may intersect
713                //   multiple parts.
714                // - A class different from `None` and `Some(u32::MAX)` means that the triangle is
715                //   included in only one convex part. So instead of cutting it, just push the whole
716                //   triangle once.
717                let prim_class = self.primitive_classes.get(*prim_id as usize).copied();
718                if prim_class == Some(u32::MAX) || prim_class.is_none() {
719                    let aabb_center = self.origin + ivect_to_vect(voxel.coords) * self.scale;
720                    let aabb =
721                        Aabb::from_half_extents(aabb_center, Vector::splat(self.scale / 2.0));
722
723                    #[cfg(feature = "dim2")]
724                    if let Some(seg) = aabb.clip_segment(points[ia], points[ib]) {
725                        surface_points.push(seg.a);
726                        surface_points.push(seg.b);
727                    }
728
729                    #[cfg(feature = "dim3")]
730                    {
731                        polygon.clear();
732                        polygon.extend_from_slice(&[points[ia], points[ib], points[ic]]);
733                        aabb.clip_polygon_with_workspace(&mut polygon, &mut workspace);
734                        surface_points.append(&mut polygon);
735                    }
736                } else {
737                    // We know this triangle is only contained by
738                    // one voxel set, i.e., `self`. So we don't
739                    // need to cut it.
740                    //
741                    // Because one triangle may intersect multiple voxels contained by
742                    // the same convex part, we only push vertices we have not pushed
743                    // so far in order to avoid some useless duplicate points (duplicate
744                    // points are OK as far as convex hull computation is concerned, but
745                    // they imply some redundant computations).
746                    let mut push_pt = |i: usize| {
747                        if !pushed_points[i] {
748                            surface_points.push(points[i]);
749                            pushed_points[i] = true;
750                        }
751                    };
752
753                    push_pt(ia);
754                    push_pt(ib);
755                    #[cfg(feature = "dim3")]
756                    push_pt(ic);
757                }
758            }
759
760            if intersections.is_empty() {
761                self.map_voxel_points(voxel, |p| surface_points.push(p));
762            }
763        }
764
765        // Compute the convex-hull.
766        convex_hull(&surface_points)
767    }
768
769    /// Computes the intersections between all the voxels of this voxel set,
770    /// and all the primitives (triangle or segments) it intersected (as per
771    /// the voxel-to-primitives-map computed during voxelization).
772    ///
773    /// Panics if the voxelization was performed without setting the parameter
774    /// `voxel_to_primitives_map = true`.
775    pub fn compute_primitive_intersections(
776        &self,
777        points: &[Vector],
778        indices: &[[u32; DIM]],
779    ) -> Vec<Vector> {
780        assert!(!self.intersections.is_empty(),
781                "Cannot compute primitive intersections voxel-to-primitives-map. Consider passing voxel_to_primitives_map = true to the voxelizer.");
782        let mut surface_points = Vec::new();
783        #[cfg(feature = "dim3")]
784        let (mut polygon, mut workspace) = (Vec::new(), Vec::new());
785
786        // Grab all the points.
787        for voxel in self.voxels.iter().filter(|v| v.is_on_surface) {
788            let intersections =
789                &self.intersections[voxel.intersections_range.0..voxel.intersections_range.1];
790            for prim_id in intersections {
791                let aabb_center = self.origin + ivect_to_vect(voxel.coords) * self.scale;
792                let aabb = Aabb::from_half_extents(aabb_center, Vector::splat(self.scale / 2.0));
793
794                let pa = points[indices[*prim_id as usize][0] as usize];
795                let pb = points[indices[*prim_id as usize][1] as usize];
796                #[cfg(feature = "dim3")]
797                let pc = points[indices[*prim_id as usize][2] as usize];
798
799                #[cfg(feature = "dim2")]
800                if let Some(seg) = aabb.clip_segment(pa, pb) {
801                    surface_points.push(seg.a);
802                    surface_points.push(seg.b);
803                }
804
805                #[cfg(feature = "dim3")]
806                {
807                    workspace.clear();
808                    polygon.clear();
809                    polygon.extend_from_slice(&[pa, pb, pc]);
810                    aabb.clip_polygon_with_workspace(&mut polygon, &mut workspace);
811
812                    if polygon.len() > 2 {
813                        for i in 1..polygon.len() - 1 {
814                            surface_points.push(polygon[0]);
815                            surface_points.push(polygon[i]);
816                            surface_points.push(polygon[i + 1]);
817                        }
818                    }
819                }
820            }
821        }
822
823        surface_points
824    }
825
826    /// Compute the convex-hull of the voxels in this set.
827    ///
828    /// # Parameters
829    /// * `sampling` - The convex-hull computation will ignore `sampling` voxels at
830    ///   regular intervals. Useful to save some computation times if an exact result isn't need.
831    ///   Use `0` to make sure no voxel is being ignored.
832    #[cfg(feature = "dim2")]
833    pub fn compute_convex_hull(&self, sampling: u32) -> Vec<Vector> {
834        let mut points = Vec::new();
835
836        // Grab all the points.
837        for voxel in self
838            .voxels
839            .iter()
840            .filter(|v| v.is_on_surface)
841            .step_by(sampling as usize)
842        {
843            self.map_voxel_points(voxel, |p| points.push(p));
844        }
845
846        // Compute the convex-hull.
847        convex_hull(&points)
848    }
849
850    /// Compute the convex-hull of the voxels in this set.
851    ///
852    /// # Parameters
853    /// * `sampling` - The convex-hull computation will ignore `sampling` voxels at
854    ///   regular intervals. Useful to save some computation times if an exact result isn't need.
855    ///   Use `0` to make sure no voxel is being ignored.
856    #[cfg(feature = "dim3")]
857    pub fn compute_convex_hull(&self, sampling: u32) -> (Vec<Vector>, Vec<[u32; DIM]>) {
858        let mut points = Vec::new();
859
860        // Grab all the points.
861        for voxel in self
862            .voxels
863            .iter()
864            .filter(|v| v.is_on_surface)
865            .step_by(sampling as usize)
866        {
867            self.map_voxel_points(voxel, |p| points.push(p));
868        }
869
870        // Compute the convex-hull.
871        convex_hull(&points)
872    }
873
874    /// Gets the vertices of the given voxel.
875    fn map_voxel_points(&self, voxel: &Voxel, mut f: impl FnMut(Vector)) {
876        let ijk = ivect_to_vect(voxel.coords);
877
878        #[cfg(feature = "dim2")]
879        let shifts = [
880            Vector::new(-0.5, -0.5),
881            Vector::new(0.5, -0.5),
882            Vector::new(0.5, 0.5),
883            Vector::new(-0.5, 0.5),
884        ];
885
886        #[cfg(feature = "dim3")]
887        let shifts = [
888            Vector::new(-0.5, -0.5, -0.5),
889            Vector::new(0.5, -0.5, -0.5),
890            Vector::new(0.5, 0.5, -0.5),
891            Vector::new(-0.5, 0.5, -0.5),
892            Vector::new(-0.5, -0.5, 0.5),
893            Vector::new(0.5, -0.5, 0.5),
894            Vector::new(0.5, 0.5, 0.5),
895            Vector::new(-0.5, 0.5, 0.5),
896        ];
897
898        for shift in &shifts {
899            f(self.origin + (ijk + *shift) * self.scale)
900        }
901    }
902
903    pub(crate) fn intersect(
904        &self,
905        plane: &CutPlane,
906        positive_pts: &mut Vec<Vector>,
907        negative_pts: &mut Vec<Vector>,
908        sampling: u32,
909    ) {
910        let num_voxels = self.voxels.len();
911
912        if num_voxels == 0 {
913            return;
914        }
915
916        let d0 = self.scale;
917        let mut sp = 0;
918        let mut sn = 0;
919
920        for v in 0..num_voxels {
921            let voxel = self.voxels[v];
922            let pt = self.get_voxel_point(&voxel);
923            let d = plane.abc.dot(pt) + plane.d;
924
925            // if      (d >= 0.0 && d <= d0) positive_pts.push(pt);
926            // else if (d < 0.0 && -d <= d0) negative_pts.push(pt);
927
928            if d >= 0.0 {
929                if d <= d0 {
930                    self.map_voxel_points(&voxel, |p| positive_pts.push(p));
931                } else {
932                    sp += 1;
933
934                    if sp == sampling {
935                        self.map_voxel_points(&voxel, |p| positive_pts.push(p));
936                        sp = 0;
937                    }
938                }
939            } else if -d <= d0 {
940                self.map_voxel_points(&voxel, |p| negative_pts.push(p));
941            } else {
942                sn += 1;
943                if sn == sampling {
944                    self.map_voxel_points(&voxel, |p| negative_pts.push(p));
945                    sn = 0;
946                }
947            }
948        }
949    }
950
951    // Returns (negative_volume, positive_volume)
952    pub(crate) fn compute_clipped_volumes(&self, plane: &CutPlane) -> (Real, Real) {
953        if self.voxels.is_empty() {
954            return (0.0, 0.0);
955        }
956
957        let mut num_positive_voxels = 0;
958
959        for voxel in &self.voxels {
960            let pt = self.get_voxel_point(voxel);
961            let d = plane.abc.dot(pt) + plane.d;
962            num_positive_voxels += (d >= 0.0) as usize;
963        }
964
965        let num_negative_voxels = self.voxels.len() - num_positive_voxels;
966        let positive_volume = self.voxel_volume() * (num_positive_voxels as Real);
967        let negative_volume = self.voxel_volume() * (num_negative_voxels as Real);
968
969        (negative_volume, positive_volume)
970    }
971
972    // Set `on_surf` such that it contains only the voxel on surface contained by `self`.
973    pub(crate) fn select_on_surface(&self, on_surf: &mut VoxelSet) {
974        on_surf.origin = self.origin;
975        on_surf.voxels.clear();
976        on_surf.scale = self.scale;
977
978        for voxel in &self.voxels {
979            if voxel.is_on_surface {
980                on_surf.voxels.push(*voxel);
981            }
982        }
983    }
984
985    /// Splits this voxel set into two parts, depending on where the voxel center lies wrt. the given plane.
986    pub(crate) fn clip(
987        &self,
988        plane: &CutPlane,
989        positive_part: &mut VoxelSet,
990        negative_part: &mut VoxelSet,
991    ) {
992        let num_voxels = self.voxels.len();
993
994        if num_voxels == 0 {
995            return;
996        }
997
998        negative_part.origin = self.origin;
999        negative_part.voxels.clear();
1000        negative_part.voxels.reserve(num_voxels);
1001        negative_part.scale = self.scale;
1002
1003        positive_part.origin = self.origin;
1004        positive_part.voxels.clear();
1005        positive_part.voxels.reserve(num_voxels);
1006        positive_part.scale = self.scale;
1007
1008        let d0 = self.scale;
1009
1010        for v in 0..num_voxels {
1011            let mut voxel = self.voxels[v];
1012            let pt = self.get_voxel_point(&voxel);
1013            let d = plane.abc.dot(pt) + plane.d;
1014
1015            if d >= 0.0 {
1016                if voxel.is_on_surface || d <= d0 {
1017                    voxel.is_on_surface = true;
1018                    positive_part.voxels.push(voxel);
1019                } else {
1020                    positive_part.voxels.push(voxel);
1021                }
1022            } else if voxel.is_on_surface || -d <= d0 {
1023                voxel.is_on_surface = true;
1024                negative_part.voxels.push(voxel);
1025            } else {
1026                negative_part.voxels.push(voxel);
1027            }
1028        }
1029    }
1030
1031    /// Convert `self` into a mesh, including only the voxels on the surface or only the voxel
1032    /// inside of the volume.
1033    #[cfg(feature = "dim3")]
1034    pub fn to_trimesh(
1035        &self,
1036        base_index: u32,
1037        is_on_surface: bool,
1038    ) -> (Vec<Vector>, Vec<[u32; DIM]>) {
1039        let mut vertices = Vec::new();
1040        let mut indices = Vec::new();
1041
1042        for voxel in &self.voxels {
1043            if voxel.is_on_surface == is_on_surface {
1044                self.map_voxel_points(voxel, |p| vertices.push(p));
1045
1046                indices.push([base_index, base_index + 2, base_index + 1]);
1047                indices.push([base_index, base_index + 3, base_index + 2]);
1048                indices.push([base_index + 4, base_index + 5, base_index + 6]);
1049                indices.push([base_index + 4, base_index + 6, base_index + 7]);
1050                indices.push([base_index + 7, base_index + 6, base_index + 2]);
1051                indices.push([base_index + 7, base_index + 2, base_index + 3]);
1052                indices.push([base_index + 4, base_index + 1, base_index + 5]);
1053                indices.push([base_index + 4, base_index, base_index + 1]);
1054                indices.push([base_index + 6, base_index + 5, base_index + 1]);
1055                indices.push([base_index + 6, base_index + 1, base_index + 2]);
1056                indices.push([base_index + 7, base_index, base_index + 4]);
1057                indices.push([base_index + 7, base_index + 3, base_index]);
1058            }
1059        }
1060
1061        (vertices, indices)
1062    }
1063
1064    pub(crate) fn compute_principal_axes(&self) -> Vector {
1065        let num_voxels = self.voxels.len();
1066        if num_voxels == 0 {
1067            return Vector::ZERO;
1068        }
1069
1070        // TODO: find a way to reuse crate::utils::cov?
1071        // The difficulty being that we need to iterate through the set of
1072        // points twice. So passing an iterator to crate::utils::cov
1073        // isn't really possible.
1074        let mut center = Vector::ZERO;
1075        let denom = 1.0 / (num_voxels as Real);
1076
1077        for voxel in &self.voxels {
1078            center += ivect_to_vect(voxel.coords) * denom;
1079        }
1080
1081        let mut cov_mat = Matrix::ZERO;
1082        for voxel in &self.voxels {
1083            let xyz = ivect_to_vect(voxel.coords) - center;
1084            cov_mat += xyz.kronecker(xyz * denom);
1085        }
1086        cov_mat.symmetric_eigenvalues()
1087    }
1088
1089    pub(crate) fn compute_axes_aligned_clipping_planes(
1090        &self,
1091        downsampling: u32,
1092        planes: &mut Vec<CutPlane>,
1093    ) {
1094        let min_v = self.min_bb_voxels();
1095        let max_v = self.max_bb_voxels();
1096
1097        for dim in 0..DIM {
1098            let i0 = min_v[dim];
1099            let i1 = max_v[dim];
1100
1101            for i in (i0..=i1).step_by(downsampling as usize) {
1102                let plane = CutPlane {
1103                    abc: Vector::ith(dim, 1.0),
1104                    axis: dim as u8,
1105                    d: -(self.origin[dim] + (i as Real + 0.5) * self.scale),
1106                    index: i as u32,
1107                };
1108
1109                planes.push(plane);
1110            }
1111        }
1112    }
1113}
1114
1115#[cfg(feature = "dim2")]
1116fn convex_hull(vertices: &[Vector]) -> Vec<Vector> {
1117    if vertices.len() > 1 {
1118        crate::transformation::convex_hull(vertices)
1119    } else {
1120        Vec::new()
1121    }
1122}
1123
1124#[cfg(feature = "dim3")]
1125fn convex_hull(vertices: &[Vector]) -> (Vec<Vector>, Vec<[u32; DIM]>) {
1126    if vertices.len() > 2 {
1127        crate::transformation::convex_hull(vertices)
1128    } else {
1129        (Vec::new(), Vec::new())
1130    }
1131}