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