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}