parry2d/transformation/voxelization/voxelized_volume.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 crate::bounding_volume::Aabb;
20#[cfg(not(feature = "std"))]
21use crate::math::ComplexField;
22use crate::math::{IVector, Int, Real, Vector, DIM};
23use crate::query;
24use crate::transformation::voxelization::{Voxel, VoxelSet};
25use alloc::sync::Arc;
26use alloc::vec::Vec;
27
28/// Controls how voxelization determines which voxels are filled vs empty.
29///
30/// The fill mode is a critical parameter that determines the structure of the resulting voxel set.
31/// Choose the appropriate mode based on whether you need just the surface boundary or a solid volume.
32///
33/// # Variants
34///
35/// ## `SurfaceOnly`
36///
37/// Only voxels that intersect the input surface boundary (triangle mesh in 3D, polyline in 2D)
38/// are marked as filled. This creates a hollow shell representation.
39///
40/// **Use this when:**
41/// - You only need the surface boundary
42/// - Memory is limited (fewer voxels to store)
43/// - You're approximating a thin shell or surface
44///
45/// **Example:**
46/// ```
47/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
48/// use parry3d::transformation::voxelization::{FillMode, VoxelSet};
49/// use parry3d::shape::Ball;
50/// ///
51/// let ball = Ball::new(1.0);
52/// let (vertices, indices) = ball.to_trimesh(20, 20);
53///
54/// let surface_voxels = VoxelSet::voxelize(
55/// &vertices,
56/// &indices,
57/// 10,
58/// FillMode::SurfaceOnly, // Only the shell
59/// false,
60/// );
61///
62/// // All voxels are on the surface
63/// assert!(surface_voxels.voxels().iter().all(|v| v.is_on_surface));
64/// # }
65/// ```
66///
67/// ## `FloodFill`
68///
69/// Marks surface voxels AND all interior voxels as filled, creating a solid volume. Uses a
70/// flood-fill algorithm starting from outside the shape to determine inside vs outside.
71///
72/// **Fields:**
73/// - `detect_cavities`: If `true`, properly detects and preserves internal cavities/holes.
74/// If `false`, all enclosed spaces are filled (faster but may fill unintended regions).
75///
76/// - `detect_self_intersections` (2D only): If `true`, attempts to handle self-intersecting
77/// polylines correctly. More expensive but more robust.
78///
79/// **Use this when:**
80/// - You need volume information (mass properties, volume computation)
81/// - You want a solid representation for collision detection
82/// - You need to distinguish between interior and surface voxels
83///
84/// **Example:**
85/// ```
86/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
87/// use parry3d::transformation::voxelization::{FillMode, VoxelSet};
88/// use parry3d::shape::Ball;
89/// ///
90/// let ball = Ball::new(1.0);
91/// let (vertices, indices) = ball.to_trimesh(20, 20);
92///
93/// let solid_voxels = VoxelSet::voxelize(
94/// &vertices,
95/// &indices,
96/// 15,
97/// FillMode::FloodFill {
98/// detect_cavities: false, // No cavities in a sphere
99/// },
100/// false,
101/// );
102///
103/// // Mix of surface and interior voxels
104/// let surface_count = solid_voxels.voxels().iter().filter(|v| v.is_on_surface).count();
105/// let interior_count = solid_voxels.voxels().iter().filter(|v| !v.is_on_surface).count();
106/// assert!(surface_count > 0 && interior_count > 0);
107/// # }
108/// ```
109///
110/// # Performance Notes
111///
112/// - `SurfaceOnly` is faster and uses less memory than `FloodFill`
113/// - `detect_cavities = true` adds computational overhead but gives more accurate results
114/// - For simple convex shapes, `detect_cavities = false` is usually sufficient
115#[derive(Copy, Clone, Debug, PartialEq, Eq)]
116pub enum FillMode {
117 /// Only consider full the voxels intersecting the surface of the
118 /// shape being voxelized.
119 SurfaceOnly,
120 /// Use a flood-fill technique to consider fill the voxels intersecting
121 /// the surface of the shape being voxelized, as well as all the voxels
122 /// bounded of them.
123 FloodFill {
124 /// Detects holes inside of a solid contour.
125 detect_cavities: bool,
126 /// Attempts to properly handle self-intersections.
127 #[cfg(feature = "dim2")]
128 detect_self_intersections: bool,
129 },
130 // RaycastFill
131}
132
133impl Default for FillMode {
134 fn default() -> Self {
135 Self::FloodFill {
136 detect_cavities: false,
137 #[cfg(feature = "dim2")]
138 detect_self_intersections: false,
139 }
140 }
141}
142
143impl FillMode {
144 #[cfg(feature = "dim2")]
145 pub(crate) fn detect_cavities(self) -> bool {
146 match self {
147 FillMode::FloodFill {
148 detect_cavities, ..
149 } => detect_cavities,
150 _ => false,
151 }
152 }
153
154 #[cfg(feature = "dim2")]
155 pub(crate) fn detect_self_intersections(self) -> bool {
156 match self {
157 FillMode::FloodFill {
158 detect_self_intersections,
159 ..
160 } => detect_self_intersections,
161 _ => false,
162 }
163 }
164
165 #[cfg(feature = "dim3")]
166 pub(crate) fn detect_self_intersections(self) -> bool {
167 false
168 }
169}
170
171/// The state of a voxel during and after voxelization.
172///
173/// This enum represents the classification of each voxel in the grid. After voxelization completes,
174/// only three values are meaningful for end-user code: `PrimitiveOutsideSurface`,
175/// `PrimitiveInsideSurface`, and `PrimitiveOnSurface`. The other variants are intermediate
176/// states used during the flood-fill algorithm.
177///
178/// # Usage
179///
180/// Most users will work with [`VoxelSet`] which filters out outside voxels and only stores
181/// filled ones. However, if you're working directly with [`VoxelizedVolume`], you'll encounter
182/// all these values.
183///
184/// # Final States (After Voxelization)
185///
186/// - **`PrimitiveOutsideSurface`**: Voxel is completely outside the shape
187/// - **`PrimitiveInsideSurface`**: Voxel is fully inside the shape (only with [`FillMode::FloodFill`])
188/// - **`PrimitiveOnSurface`**: Voxel intersects the boundary surface
189///
190/// # Intermediate States (During Voxelization)
191///
192/// The following are temporary states used during the flood-fill algorithm and should be
193/// ignored by user code:
194/// - `PrimitiveUndefined`
195/// - `PrimitiveOutsideSurfaceToWalk`
196/// - `PrimitiveInsideSurfaceToWalk`
197/// - `PrimitiveOnSurfaceNoWalk`
198/// - `PrimitiveOnSurfaceToWalk1`
199/// - `PrimitiveOnSurfaceToWalk2`
200///
201/// # Example
202///
203/// ```
204/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
205/// use parry3d::transformation::voxelization::{FillMode, VoxelValue, VoxelizedVolume};
206/// use parry3d::shape::Cuboid;
207/// use parry3d::math::Vector;
208///
209/// let cuboid = Cuboid::new(Vector::new(1.0, 1.0, 1.0));
210/// let (vertices, indices) = cuboid.to_trimesh();
211///
212/// // Create a dense voxelized volume
213/// let volume = VoxelizedVolume::voxelize(
214/// &vertices,
215/// &indices,
216/// 8,
217/// FillMode::FloodFill { detect_cavities: false },
218/// false,
219/// );
220///
221/// // Query individual voxel values
222/// let resolution = volume.resolution();
223/// for i in 0..resolution[0] {
224/// for j in 0..resolution[1] {
225/// for k in 0..resolution[2] {
226/// match volume.voxel(i, j, k) {
227/// VoxelValue::PrimitiveOnSurface => {
228/// // This voxel is on the boundary
229/// }
230/// VoxelValue::PrimitiveInsideSurface => {
231/// // This voxel is inside the shape
232/// }
233/// VoxelValue::PrimitiveOutsideSurface => {
234/// // This voxel is outside the shape
235/// }
236/// _ => {
237/// // Should not happen after voxelization completes
238/// }
239/// }
240/// }
241/// }
242/// }
243/// # }
244/// ```
245#[derive(Copy, Clone, Debug, PartialEq, Eq)]
246pub enum VoxelValue {
247 /// Intermediate value, should be ignored by end-user code.
248 PrimitiveUndefined,
249 /// Intermediate value, should be ignored by end-user code.
250 PrimitiveOutsideSurfaceToWalk,
251 /// Intermediate value, should be ignored by end-user code.
252 PrimitiveInsideSurfaceToWalk,
253 /// Intermediate value, should be ignored by end-user code.
254 PrimitiveOnSurfaceNoWalk,
255 /// Intermediate value, should be ignored by end-user code.
256 PrimitiveOnSurfaceToWalk1,
257 /// Intermediate value, should be ignored by end-user code.
258 PrimitiveOnSurfaceToWalk2,
259 /// A voxel that is outside of the voxelized shape.
260 PrimitiveOutsideSurface,
261 /// A voxel that is on the interior of the voxelized shape.
262 PrimitiveInsideSurface,
263 /// Voxel that intersects the surface of the voxelized shape.
264 PrimitiveOnSurface,
265}
266
267#[derive(Copy, Clone, Debug, PartialEq, Eq)]
268struct VoxelData {
269 #[cfg(feature = "dim2")]
270 multiplicity: u32,
271 num_primitive_intersections: u32,
272}
273
274/// A dense voxel grid storing the state of every voxel in a cubic volume.
275///
276/// `VoxelizedVolume` is the intermediate representation used during the voxelization process.
277/// Unlike [`VoxelSet`] which only stores filled voxels, `VoxelizedVolume` stores a complete
278/// dense 3D array where every grid cell has an associated [`VoxelValue`].
279///
280/// # When to Use
281///
282/// Most users should use [`VoxelSet`] instead, which is converted from `VoxelizedVolume`
283/// automatically and is much more memory-efficient. You might want to use `VoxelizedVolume`
284/// directly if you need to:
285///
286/// - Query the state of ALL voxels, including empty ones
287/// - Implement custom voxel processing algorithms
288/// - Generate visualizations showing both filled and empty voxels
289///
290/// # Memory Usage
291///
292/// `VoxelizedVolume` stores **all** voxels in a dense 3D array:
293/// - Memory usage: `O(resolution^3)` in 3D or `O(resolution^2)` in 2D
294/// - A 100×100×100 grid requires 1 million voxel entries
295///
296/// For this reason, [`VoxelSet`] is usually preferred for storage.
297///
298/// # Conversion
299///
300/// `VoxelizedVolume` automatically converts to `VoxelSet`:
301///
302/// ```
303/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
304/// use parry3d::transformation::voxelization::{FillMode, VoxelSet, VoxelizedVolume};
305/// use parry3d::shape::Ball;
306/// ///
307/// let ball = Ball::new(1.0);
308/// let (vertices, indices) = ball.to_trimesh(10, 10);
309///
310/// // Create dense volume
311/// let volume = VoxelizedVolume::voxelize(
312/// &vertices,
313/// &indices,
314/// 10,
315/// FillMode::SurfaceOnly,
316/// false,
317/// );
318///
319/// // Convert to sparse set (automatic via Into trait)
320/// let voxel_set: VoxelSet = volume.into();
321/// # }
322/// ```
323///
324/// # Example: Querying All Voxels
325///
326/// ```
327/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
328/// use parry3d::transformation::voxelization::{FillMode, VoxelValue, VoxelizedVolume};
329/// use parry3d::shape::Cuboid;
330/// use parry3d::math::Vector;
331///
332/// let cuboid = Cuboid::new(Vector::new(1.0, 1.0, 1.0));
333/// let (vertices, indices) = cuboid.to_trimesh();
334///
335/// let volume = VoxelizedVolume::voxelize(
336/// &vertices,
337/// &indices,
338/// 8,
339/// FillMode::FloodFill { detect_cavities: false },
340/// false,
341/// );
342///
343/// // Count voxels by state
344/// let resolution = volume.resolution();
345/// let mut inside = 0;
346/// let mut surface = 0;
347/// let mut outside = 0;
348///
349/// for i in 0..resolution[0] {
350/// for j in 0..resolution[1] {
351/// for k in 0..resolution[2] {
352/// match volume.voxel(i, j, k) {
353/// VoxelValue::PrimitiveInsideSurface => inside += 1,
354/// VoxelValue::PrimitiveOnSurface => surface += 1,
355/// VoxelValue::PrimitiveOutsideSurface => outside += 1,
356/// _ => {}
357/// }
358/// }
359/// }
360/// }
361///
362/// println!("Inside: {}, Surface: {}, Outside: {}", inside, surface, outside);
363/// # }
364/// ```
365pub struct VoxelizedVolume {
366 origin: Vector,
367 scale: Real,
368 resolution: [u32; DIM],
369 values: Vec<VoxelValue>,
370 data: Vec<VoxelData>,
371 primitive_intersections: Vec<(u32, u32)>,
372}
373
374impl VoxelizedVolume {
375 /// Voxelizes the given shape described by its boundary:
376 /// a triangle mesh (in 3D) or polyline (in 2D).
377 ///
378 /// # Parameters
379 /// * `points` - The vertex buffer of the boundary of the shape to voxelize.
380 /// * `indices` - The index buffer of the boundary of the shape to voxelize.
381 /// * `resolution` - Controls the number of subdivision done along each axis. This number
382 /// is the number of subdivisions along the axis where the input shape has the largest extent.
383 /// The other dimensions will have a different automatically-determined resolution (in order to
384 /// keep the voxels cubic).
385 /// * `fill_mode` - Controls what is being voxelized.
386 /// * `keep_voxel_to_primitives_map` - If set to `true` a map between the voxels
387 /// and the primitives (3D triangles or 2D segments) it intersects will be computed.
388 pub fn with_voxel_size(
389 points: &[Vector],
390 indices: &[[u32; DIM]],
391 voxel_size: Real,
392 fill_mode: FillMode,
393 keep_voxel_to_primitives_map: bool,
394 ) -> Self {
395 let mut result = VoxelizedVolume {
396 resolution: [0; DIM],
397 origin: Vector::ZERO,
398 scale: voxel_size,
399 values: Vec::new(),
400 data: Vec::new(),
401 primitive_intersections: Vec::new(),
402 };
403
404 if points.is_empty() {
405 return result;
406 }
407
408 let aabb = crate::bounding_volume::details::local_point_cloud_aabb_ref(points);
409 result.origin = aabb.mins;
410 let extents = aabb.extents() / voxel_size;
411 #[cfg(feature = "dim2")]
412 {
413 result.resolution = [
414 (extents.x.ceil() as u32).max(2) + 1,
415 (extents.y.ceil() as u32).max(2) + 1,
416 ];
417 }
418 #[cfg(feature = "dim3")]
419 {
420 result.resolution = [
421 (extents.x.ceil() as u32).max(2) + 1,
422 (extents.y.ceil() as u32).max(2) + 1,
423 (extents.z.ceil() as u32).max(2) + 1,
424 ];
425 }
426
427 result.do_voxelize(points, indices, fill_mode, keep_voxel_to_primitives_map);
428 result
429 }
430
431 /// Voxelizes the given shape described by its boundary:
432 /// a triangle mesh (in 3D) or polyline (in 2D).
433 ///
434 /// # Parameters
435 /// * `points` - The vertex buffer of the boundary of the shape to voxelize.
436 /// * `indices` - The index buffer of the boundary of the shape to voxelize.
437 /// * `resolution` - Controls the number of subdivision done along each axis. This number
438 /// is the number of subdivisions along the axis where the input shape has the largest extent.
439 /// The other dimensions will have a different automatically-determined resolution (in order to
440 /// keep the voxels cubic).
441 /// * `fill_mode` - Controls what is being voxelized.
442 /// * `keep_voxel_to_primitives_map` - If set to `true` a map between the voxels
443 /// and the primitives (3D triangles or 2D segments) it intersects will be computed.
444 pub fn voxelize(
445 points: &[Vector],
446 indices: &[[u32; DIM]],
447 resolution: u32,
448 fill_mode: FillMode,
449 keep_voxel_to_primitives_map: bool,
450 ) -> Self {
451 let mut result = VoxelizedVolume {
452 resolution: [0; DIM],
453 origin: Vector::ZERO,
454 scale: 1.0,
455 values: Vec::new(),
456 data: Vec::new(),
457 primitive_intersections: Vec::new(),
458 };
459
460 if points.is_empty() {
461 return result;
462 }
463
464 let aabb = crate::bounding_volume::details::local_point_cloud_aabb_ref(points);
465 result.origin = aabb.mins;
466
467 let d = aabb.maxs - aabb.mins;
468 let r;
469
470 #[cfg(feature = "dim2")]
471 if d[0] > d[1] {
472 r = d[0];
473 result.resolution[0] = resolution;
474 result.resolution[1] = 2 + (resolution as Real * d[1] / d[0]) as u32;
475 } else {
476 r = d[1];
477 result.resolution[1] = resolution;
478 result.resolution[0] = 2 + (resolution as Real * d[0] / d[1]) as u32;
479 }
480
481 #[cfg(feature = "dim3")]
482 if d[0] >= d[1] && d[0] >= d[2] {
483 r = d[0];
484 result.resolution[0] = resolution;
485 result.resolution[1] = 2 + (resolution as Real * d[1] / d[0]) as u32;
486 result.resolution[2] = 2 + (resolution as Real * d[2] / d[0]) as u32;
487 } else if d[1] >= d[0] && d[1] >= d[2] {
488 r = d[1];
489 result.resolution[1] = resolution;
490 result.resolution[0] = 2 + (resolution as Real * d[0] / d[1]) as u32;
491 result.resolution[2] = 2 + (resolution as Real * d[2] / d[1]) as u32;
492 } else {
493 r = d[2];
494 result.resolution[2] = resolution;
495 result.resolution[0] = 2 + (resolution as Real * d[0] / d[2]) as u32;
496 result.resolution[1] = 2 + (resolution as Real * d[1] / d[2]) as u32;
497 }
498
499 result.scale = r / (resolution as Real - 1.0);
500 result.do_voxelize(points, indices, fill_mode, keep_voxel_to_primitives_map);
501 result
502 }
503
504 fn do_voxelize(
505 &mut self,
506 points: &[Vector],
507 indices: &[[u32; DIM]],
508 fill_mode: FillMode,
509 keep_voxel_to_primitives_map: bool,
510 ) {
511 let inv_scale = 1.0 / self.scale;
512 self.allocate();
513
514 let mut tri_pts = [Vector::ZERO; DIM];
515 let box_half_size = Vector::splat(0.5);
516 let mut ijk0 = IVector::ZERO;
517 let mut ijk1 = IVector::ZERO;
518
519 let detect_self_intersections = fill_mode.detect_self_intersections();
520 #[cfg(feature = "dim2")]
521 let lock_high_multiplicities =
522 fill_mode.detect_cavities() && fill_mode.detect_self_intersections();
523
524 for (tri_id, tri) in indices.iter().enumerate() {
525 // Find the range of voxels potentially intersecting the triangle.
526 for c in 0..DIM {
527 let pt = points[tri[c] as usize];
528 tri_pts[c] = (pt - self.origin) * inv_scale;
529
530 let i = (tri_pts[c].x + 0.5) as Int;
531 let j = (tri_pts[c].y + 0.5) as Int;
532 #[cfg(feature = "dim3")]
533 let k = (tri_pts[c].z + 0.5) as Int;
534
535 assert!((i as u32) < self.resolution[0] && (j as u32) < self.resolution[1]);
536 #[cfg(feature = "dim3")]
537 assert!((k as u32) < self.resolution[2]);
538
539 #[cfg(feature = "dim2")]
540 let ijk = IVector::new(i, j);
541 #[cfg(feature = "dim3")]
542 let ijk = IVector::new(i, j, k);
543
544 if c == 0 {
545 ijk0 = ijk;
546 ijk1 = ijk;
547 } else {
548 ijk0 = ijk0.min(ijk);
549 ijk1 = ijk1.max(ijk);
550 }
551 }
552
553 ijk0 = (ijk0 - IVector::ONE).max(IVector::ZERO);
554 #[cfg(feature = "dim2")]
555 let resolution_vec = IVector::new(self.resolution[0] as Int, self.resolution[1] as Int);
556 #[cfg(feature = "dim3")]
557 let resolution_vec = IVector::new(
558 self.resolution[0] as Int,
559 self.resolution[1] as Int,
560 self.resolution[2] as Int,
561 );
562 ijk1 = (ijk1 + IVector::ONE).min(resolution_vec);
563
564 #[cfg(feature = "dim2")]
565 let range_k = 0..1;
566 #[cfg(feature = "dim3")]
567 let range_k = ijk0.z..ijk1.z;
568
569 // Determine exactly what voxel intersect the triangle.
570 for i in ijk0.x..ijk1.x {
571 for j in ijk0.y..ijk1.y {
572 for k in range_k.clone() {
573 #[cfg(feature = "dim2")]
574 let pt = Vector::new(i as Real, j as Real);
575 #[cfg(feature = "dim3")]
576 let pt = Vector::new(i as Real, j as Real, k as Real);
577
578 let id = self.voxel_index(i as u32, j as u32, k as u32);
579 let value = &mut self.values[id as usize];
580 let data = &mut self.data[id as usize];
581
582 if detect_self_intersections
583 || keep_voxel_to_primitives_map
584 || *value == VoxelValue::PrimitiveUndefined
585 {
586 let aabb = Aabb::from_half_extents(pt, box_half_size);
587
588 #[cfg(feature = "dim2")]
589 if !detect_self_intersections {
590 let segment = crate::shape::Segment::from(tri_pts);
591 let intersect =
592 query::details::intersection_test_aabb_segment(&aabb, &segment);
593
594 if intersect {
595 if keep_voxel_to_primitives_map {
596 data.num_primitive_intersections += 1;
597 self.primitive_intersections.push((id, tri_id as u32));
598 }
599
600 *value = VoxelValue::PrimitiveOnSurface;
601 }
602 } else if let Some(params) =
603 aabb.clip_line_parameters(tri_pts[0], tri_pts[1] - tri_pts[0])
604 {
605 let eps = 0.0; // -1.0e-6;
606
607 assert!(params.0 <= params.1);
608 if params.0 > 1.0 + eps || params.1 < 0.0 - eps {
609 continue;
610 }
611
612 if keep_voxel_to_primitives_map {
613 data.num_primitive_intersections += 1;
614 self.primitive_intersections.push((id, tri_id as u32));
615 }
616
617 if data.multiplicity > 4 && lock_high_multiplicities {
618 *value = VoxelValue::PrimitiveOnSurfaceNoWalk;
619 } else {
620 *value = VoxelValue::PrimitiveOnSurface;
621 }
622 };
623
624 #[cfg(feature = "dim3")]
625 {
626 let triangle = crate::shape::Triangle::from(tri_pts);
627 let intersect = query::details::intersection_test_aabb_triangle(
628 &aabb, &triangle,
629 );
630
631 if intersect {
632 *value = VoxelValue::PrimitiveOnSurface;
633
634 if keep_voxel_to_primitives_map {
635 data.num_primitive_intersections += 1;
636 self.primitive_intersections.push((id, tri_id as u32));
637 }
638 }
639 };
640 }
641 }
642 }
643 }
644 }
645
646 match fill_mode {
647 FillMode::SurfaceOnly => {
648 for value in &mut self.values {
649 if *value != VoxelValue::PrimitiveOnSurface {
650 *value = VoxelValue::PrimitiveOutsideSurface
651 }
652 }
653 }
654 FillMode::FloodFill {
655 detect_cavities, ..
656 } => {
657 #[cfg(feature = "dim2")]
658 {
659 self.mark_outside_surface(0, 0, self.resolution[0], 1);
660 self.mark_outside_surface(
661 0,
662 self.resolution[1] - 1,
663 self.resolution[0],
664 self.resolution[1],
665 );
666 self.mark_outside_surface(0, 0, 1, self.resolution[1]);
667 self.mark_outside_surface(
668 self.resolution[0] - 1,
669 0,
670 self.resolution[0],
671 self.resolution[1],
672 );
673 }
674
675 #[cfg(feature = "dim3")]
676 {
677 self.mark_outside_surface(0, 0, 0, self.resolution[0], self.resolution[1], 1);
678 self.mark_outside_surface(
679 0,
680 0,
681 self.resolution[2] - 1,
682 self.resolution[0],
683 self.resolution[1],
684 self.resolution[2],
685 );
686 self.mark_outside_surface(0, 0, 0, self.resolution[0], 1, self.resolution[2]);
687 self.mark_outside_surface(
688 0,
689 self.resolution[1] - 1,
690 0,
691 self.resolution[0],
692 self.resolution[1],
693 self.resolution[2],
694 );
695 self.mark_outside_surface(0, 0, 0, 1, self.resolution[1], self.resolution[2]);
696 self.mark_outside_surface(
697 self.resolution[0] - 1,
698 0,
699 0,
700 self.resolution[0],
701 self.resolution[1],
702 self.resolution[2],
703 );
704 }
705
706 if detect_cavities {
707 let _ = self.propagate_values(
708 VoxelValue::PrimitiveOutsideSurfaceToWalk,
709 VoxelValue::PrimitiveOutsideSurface,
710 None,
711 VoxelValue::PrimitiveOnSurfaceToWalk1,
712 );
713
714 loop {
715 if !self.propagate_values(
716 VoxelValue::PrimitiveInsideSurfaceToWalk,
717 VoxelValue::PrimitiveInsideSurface,
718 Some(VoxelValue::PrimitiveOnSurfaceToWalk1),
719 VoxelValue::PrimitiveOnSurfaceToWalk2,
720 ) {
721 break;
722 }
723
724 if !self.propagate_values(
725 VoxelValue::PrimitiveOutsideSurfaceToWalk,
726 VoxelValue::PrimitiveOutsideSurface,
727 Some(VoxelValue::PrimitiveOnSurfaceToWalk2),
728 VoxelValue::PrimitiveOnSurfaceToWalk1,
729 ) {
730 break;
731 }
732 }
733
734 for voxel in &mut self.values {
735 if *voxel == VoxelValue::PrimitiveOnSurfaceToWalk1
736 || *voxel == VoxelValue::PrimitiveOnSurfaceToWalk2
737 || *voxel == VoxelValue::PrimitiveOnSurfaceNoWalk
738 {
739 *voxel = VoxelValue::PrimitiveOnSurface;
740 }
741 }
742 } else {
743 let _ = self.propagate_values(
744 VoxelValue::PrimitiveOutsideSurfaceToWalk,
745 VoxelValue::PrimitiveOutsideSurface,
746 None,
747 VoxelValue::PrimitiveOnSurface,
748 );
749
750 self.replace_value(
751 VoxelValue::PrimitiveUndefined,
752 VoxelValue::PrimitiveInsideSurface,
753 );
754 }
755 }
756 }
757 }
758
759 /// The number of voxel subdivisions along each coordinate axis.
760 pub fn resolution(&self) -> [u32; DIM] {
761 self.resolution
762 }
763
764 /// The scale factor that needs to be applied to the voxels of `self`
765 /// in order to give them the size matching the original model's size.
766 pub fn scale(&self) -> Real {
767 self.scale
768 }
769
770 fn allocate(&mut self) {
771 #[cfg(feature = "dim2")]
772 let len = self.resolution[0] * self.resolution[1];
773 #[cfg(feature = "dim3")]
774 let len = self.resolution[0] * self.resolution[1] * self.resolution[2];
775 self.values
776 .resize(len as usize, VoxelValue::PrimitiveUndefined);
777 self.data.resize(
778 len as usize,
779 VoxelData {
780 #[cfg(feature = "dim2")]
781 multiplicity: 0,
782 num_primitive_intersections: 0,
783 },
784 );
785 }
786
787 fn voxel_index(&self, i: u32, j: u32, _k: u32) -> u32 {
788 #[cfg(feature = "dim2")]
789 return i + j * self.resolution[0];
790 #[cfg(feature = "dim3")]
791 return i + j * self.resolution[0] + _k * self.resolution[0] * self.resolution[1];
792 }
793
794 fn voxel_mut(&mut self, i: u32, j: u32, k: u32) -> &mut VoxelValue {
795 let idx = self.voxel_index(i, j, k);
796 &mut self.values[idx as usize]
797 }
798
799 /// The value of the given voxel.
800 ///
801 /// In 2D, the `k` argument is ignored.
802 pub fn voxel(&self, i: u32, j: u32, k: u32) -> VoxelValue {
803 let idx = self.voxel_index(i, j, k);
804 self.values[idx as usize]
805 }
806
807 /// Mark all the PrimitiveUndefined voxels within the given bounds as PrimitiveOutsideSurfaceToWalk.
808 #[cfg(feature = "dim2")]
809 fn mark_outside_surface(&mut self, i0: u32, j0: u32, i1: u32, j1: u32) {
810 for i in i0..i1 {
811 for j in j0..j1 {
812 let v = self.voxel_mut(i, j, 0);
813
814 if *v == VoxelValue::PrimitiveUndefined {
815 *v = VoxelValue::PrimitiveOutsideSurfaceToWalk;
816 }
817 }
818 }
819 }
820
821 /// Mark all the PrimitiveUndefined voxels within the given bounds as PrimitiveOutsideSurfaceToWalk.
822 #[cfg(feature = "dim3")]
823 fn mark_outside_surface(&mut self, i0: u32, j0: u32, k0: u32, i1: u32, j1: u32, k1: u32) {
824 for i in i0..i1 {
825 for j in j0..j1 {
826 for k in k0..k1 {
827 let v = self.voxel_mut(i, j, k);
828
829 if *v == VoxelValue::PrimitiveUndefined {
830 *v = VoxelValue::PrimitiveOutsideSurfaceToWalk;
831 }
832 }
833 }
834 }
835 }
836
837 fn walk_forward(
838 primitive_undefined_value_to_set: VoxelValue,
839 on_surface_value_to_set: VoxelValue,
840 start: isize,
841 end: isize,
842 mut ptr: isize,
843 out: &mut [VoxelValue],
844 stride: isize,
845 max_distance: isize,
846 ) {
847 let mut i = start;
848 let mut count = 0;
849
850 while count < max_distance && i < end {
851 if out[ptr as usize] == VoxelValue::PrimitiveUndefined {
852 out[ptr as usize] = primitive_undefined_value_to_set;
853 } else if out[ptr as usize] == VoxelValue::PrimitiveOnSurface {
854 out[ptr as usize] = on_surface_value_to_set;
855 break;
856 } else {
857 break;
858 }
859
860 i += 1;
861 ptr += stride;
862 count += 1;
863 }
864 }
865
866 fn walk_backward(
867 primitive_undefined_value_to_set: VoxelValue,
868 on_surface_value_to_set: VoxelValue,
869 start: isize,
870 end: isize,
871 mut ptr: isize,
872 out: &mut [VoxelValue],
873 stride: isize,
874 max_distance: isize,
875 ) {
876 let mut i = start;
877 let mut count = 0;
878
879 while count < max_distance && i >= end {
880 if out[ptr as usize] == VoxelValue::PrimitiveUndefined {
881 out[ptr as usize] = primitive_undefined_value_to_set;
882 } else if out[ptr as usize] == VoxelValue::PrimitiveOnSurface {
883 out[ptr as usize] = on_surface_value_to_set;
884 break;
885 } else {
886 break;
887 }
888
889 i -= 1;
890 ptr -= stride;
891 count += 1;
892 }
893 }
894
895 fn propagate_values(
896 &mut self,
897 inside_surface_value_to_walk: VoxelValue,
898 inside_surface_value_to_set: VoxelValue,
899 on_surface_value_to_walk: Option<VoxelValue>,
900 on_surface_value_to_set: VoxelValue,
901 ) -> bool {
902 let mut voxels_walked;
903 let mut walked_at_least_once = false;
904 let i0 = self.resolution[0];
905 let j0 = self.resolution[1];
906 #[cfg(feature = "dim2")]
907 let k0 = 1;
908 #[cfg(feature = "dim3")]
909 let k0 = self.resolution[2];
910
911 // Avoid striding too far in each direction to stay in L1 cache as much as possible.
912 // The cache size required for the walk is roughly (4 * walk_distance * 64) since
913 // the k direction doesn't count as it's walking byte per byte directly in a cache lines.
914 // ~16k is required for a walk distance of 64 in each directions.
915 let walk_distance = 64;
916
917 // using the stride directly instead of calling get_voxel for each iterations saves
918 // a lot of multiplications and pipeline stalls due to values dependencies on imul.
919 let istride = self.voxel_index(1, 0, 0) as isize - self.voxel_index(0, 0, 0) as isize;
920 let jstride = self.voxel_index(0, 1, 0) as isize - self.voxel_index(0, 0, 0) as isize;
921 #[cfg(feature = "dim3")]
922 let kstride = self.voxel_index(0, 0, 1) as isize - self.voxel_index(0, 0, 0) as isize;
923
924 // It might seem counter intuitive to go over the whole voxel range multiple times
925 // but since we do the run in memory order, it leaves us with far fewer cache misses
926 // than a BFS algorithm and it has the additional benefit of not requiring us to
927 // store and manipulate a fifo for recursion that might become huge when the number
928 // of voxels is large.
929 // This will outperform the BFS algorithm by several orders of magnitude in practice.
930 loop {
931 voxels_walked = 0;
932
933 for i in 0..i0 {
934 for j in 0..j0 {
935 for k in 0..k0 {
936 let idx = self.voxel_index(i, j, k) as isize;
937 let voxel = self.voxel_mut(i, j, k);
938
939 if *voxel == inside_surface_value_to_walk {
940 voxels_walked += 1;
941 walked_at_least_once = true;
942 *voxel = inside_surface_value_to_set;
943 } else if Some(*voxel) != on_surface_value_to_walk {
944 continue;
945 }
946
947 // walk in each direction to mark other voxel that should be walked.
948 // this will generate a 3d pattern that will help the overall
949 // algorithm converge faster while remaining cache friendly.
950 #[cfg(feature = "dim3")]
951 Self::walk_forward(
952 inside_surface_value_to_walk,
953 on_surface_value_to_set,
954 k as isize + 1,
955 k0 as isize,
956 idx + kstride,
957 &mut self.values,
958 kstride,
959 walk_distance,
960 );
961 #[cfg(feature = "dim3")]
962 Self::walk_backward(
963 inside_surface_value_to_walk,
964 on_surface_value_to_set,
965 k as isize - 1,
966 0,
967 idx - kstride,
968 &mut self.values,
969 kstride,
970 walk_distance,
971 );
972
973 Self::walk_forward(
974 inside_surface_value_to_walk,
975 on_surface_value_to_set,
976 j as isize + 1,
977 j0 as isize,
978 idx + jstride,
979 &mut self.values,
980 jstride,
981 walk_distance,
982 );
983 Self::walk_backward(
984 inside_surface_value_to_walk,
985 on_surface_value_to_set,
986 j as isize - 1,
987 0,
988 idx - jstride,
989 &mut self.values,
990 jstride,
991 walk_distance,
992 );
993
994 Self::walk_forward(
995 inside_surface_value_to_walk,
996 on_surface_value_to_set,
997 (i + 1) as isize,
998 i0 as isize,
999 idx + istride,
1000 &mut self.values,
1001 istride,
1002 walk_distance,
1003 );
1004 Self::walk_backward(
1005 inside_surface_value_to_walk,
1006 on_surface_value_to_set,
1007 i as isize - 1,
1008 0,
1009 idx - istride,
1010 &mut self.values,
1011 istride,
1012 walk_distance,
1013 );
1014 }
1015 }
1016 }
1017
1018 if voxels_walked == 0 {
1019 break;
1020 }
1021 }
1022
1023 walked_at_least_once
1024 }
1025
1026 fn replace_value(&mut self, current_value: VoxelValue, new_value: VoxelValue) {
1027 for voxel in &mut self.values {
1028 if *voxel == current_value {
1029 *voxel = new_value;
1030 }
1031 }
1032 }
1033
1034 /// Naive conversion of all the voxels with the given `value` to a triangle-mesh.
1035 ///
1036 /// This conversion is extremely naive: it will simply collect all the 12 triangles forming
1037 /// the faces of each voxel. No actual boundary extraction is done.
1038 #[cfg(feature = "dim3")]
1039 pub fn to_trimesh(&self, value: VoxelValue) -> (Vec<Vector>, Vec<[u32; DIM]>) {
1040 let mut vertices = Vec::new();
1041 let mut indices = Vec::new();
1042
1043 for i in 0..self.resolution[0] {
1044 for j in 0..self.resolution[1] {
1045 for k in 0..self.resolution[2] {
1046 let voxel = self.voxel(i, j, k);
1047
1048 if voxel == value {
1049 let ijk = Vector::new(i as Real, j as Real, k as Real);
1050
1051 let shifts = [
1052 Vector::new(-0.5, -0.5, -0.5),
1053 Vector::new(0.5, -0.5, -0.5),
1054 Vector::new(0.5, 0.5, -0.5),
1055 Vector::new(-0.5, 0.5, -0.5),
1056 Vector::new(-0.5, -0.5, 0.5),
1057 Vector::new(0.5, -0.5, 0.5),
1058 Vector::new(0.5, 0.5, 0.5),
1059 Vector::new(-0.5, 0.5, 0.5),
1060 ];
1061
1062 for shift in &shifts {
1063 vertices.push(self.origin + (ijk + shift) * self.scale);
1064 }
1065
1066 let s = vertices.len() as u32;
1067 indices.push([s, s + 2, s + 1]);
1068 indices.push([s, s + 3, s + 2]);
1069 indices.push([s + 4, s + 5, s + 6]);
1070 indices.push([s + 4, s + 6, s + 7]);
1071 indices.push([s + 7, s + 6, s + 2]);
1072 indices.push([s + 7, s + 2, s + 3]);
1073 indices.push([s + 4, s + 1, s + 5]);
1074 indices.push([s + 4, s, s + 1]);
1075 indices.push([s + 6, s + 5, s + 1]);
1076 indices.push([s + 6, s + 1, s + 2]);
1077 indices.push([s + 7, s, s + 4]);
1078 indices.push([s + 7, s + 3, s]);
1079 }
1080 }
1081 }
1082 }
1083
1084 (vertices, indices)
1085 }
1086}
1087
1088impl From<VoxelizedVolume> for VoxelSet {
1089 fn from(mut shape: VoxelizedVolume) -> Self {
1090 let mut curr_intersection_index = 0;
1091 let mut vset = VoxelSet::new();
1092 let mut vset_intersections = Vec::new();
1093 vset.origin = shape.origin;
1094 vset.scale = shape.scale;
1095
1096 #[cfg(feature = "dim2")]
1097 let k1 = 1;
1098 #[cfg(feature = "dim3")]
1099 let k1 = shape.resolution[2];
1100
1101 for i in 0..shape.resolution[0] {
1102 for j in 0..shape.resolution[1] {
1103 for k in 0..k1 {
1104 let id = shape.voxel_index(i, j, k) as usize;
1105 let value = shape.values[id];
1106 #[cfg(feature = "dim2")]
1107 let coords = IVector::new(i as Int, j as Int);
1108 #[cfg(feature = "dim3")]
1109 let coords = IVector::new(i as Int, j as Int, k as Int);
1110
1111 if value == VoxelValue::PrimitiveInsideSurface {
1112 let voxel = Voxel {
1113 coords,
1114 is_on_surface: false,
1115 intersections_range: (curr_intersection_index, curr_intersection_index),
1116 };
1117 vset.voxels.push(voxel);
1118 } else if value == VoxelValue::PrimitiveOnSurface {
1119 let mut voxel = Voxel {
1120 coords,
1121 is_on_surface: true,
1122 intersections_range: (curr_intersection_index, curr_intersection_index),
1123 };
1124
1125 if !shape.primitive_intersections.is_empty() {
1126 let num_intersections =
1127 shape.data[id].num_primitive_intersections as usize;
1128 // We store the index where we should write the intersection on the
1129 // vset into num_primitive_intersections. That way we can reuse it
1130 // afterwards when copying the set of intersection into a single
1131 // flat Vec.
1132 shape.data[id].num_primitive_intersections =
1133 curr_intersection_index as u32;
1134 curr_intersection_index += num_intersections;
1135 voxel.intersections_range.1 = curr_intersection_index;
1136 }
1137
1138 vset.voxels.push(voxel);
1139 }
1140 }
1141 }
1142 }
1143
1144 if !shape.primitive_intersections.is_empty() {
1145 vset_intersections.resize(shape.primitive_intersections.len(), 0);
1146 for (voxel_id, prim_id) in shape.primitive_intersections {
1147 let num_inter = &mut shape.data[voxel_id as usize].num_primitive_intersections;
1148 vset_intersections[*num_inter as usize] = prim_id;
1149 *num_inter += 1;
1150 }
1151 }
1152
1153 vset.intersections = Arc::new(vset_intersections);
1154
1155 vset
1156 }
1157}
1158
1159/*
1160fn traceRay(
1161 mesh: &RaycastMesh,
1162 start: Real,
1163 dir: Vector,
1164 inside_count: &mut u32,
1165 outside_count: &mut u32,
1166) {
1167 let out_t;
1168 let u;
1169 let v;
1170 let w;
1171 let face_sign;
1172 let face_index;
1173 let hit = raycast_mesh.raycast(start, dir, out_t, u, v, w, face_sign, face_index);
1174
1175 if hit {
1176 if face_sign >= 0 {
1177 *inside_count += 1;
1178 } else {
1179 *outside_count += 1;
1180 }
1181 }
1182}
1183
1184
1185fn raycast_fill(volume: &Volume, raycast_mesh: &RaycastMesh) {
1186if !raycast_mesh {
1187 return;
1188}
1189
1190let scale = volume.scale;
1191let bmin = volume.min_bb;
1192
1193let i0 = volume.resolution[0];
1194let j0 = volume.resolution[1];
1195let k0 = volume.resolution[2];
1196
1197for i in 0..i0 {
1198 for j in 0..j0 {
1199 for k in 0..k0 {
1200 let voxel = volume.get_voxel(i, j, k);
1201
1202 if voxel != VoxelValue::PrimitiveOnSurface {
1203 let start = Vector::new(
1204 i as Real * scale + bmin[0],
1205 j as Real * scale + bmin[1],
1206 k as Real * scale + bmin[2],
1207 );
1208
1209 let mut inside_count = 0;
1210 let mut outside_count = 0;
1211
1212 let directions = [
1213 Vector::X,
1214 -Vector::X,
1215 Vector::Y,
1216 -Vector::Y,
1217 Vector::Z,
1218 -Vector::Z,
1219 ];
1220
1221 for r in 0..6 {
1222 traceRay(
1223 raycast_mesh,
1224 start,
1225 &directions[r * 3],
1226 &mut inside_count,
1227 &mut outside_count,
1228 );
1229
1230 // Early out if we hit the outside of the mesh
1231 if outside_count != 0 {
1232 break;
1233 }
1234
1235 // Early out if we accumulated 3 inside hits
1236 if inside_count >= 3 {
1237 break;
1238 }
1239 }
1240
1241 if outside_count == 0 && inside_count >= 3 {
1242 volume.set_voxel(i, j, k, VoxelValue::PrimitiveInsideSurface);
1243 } else {
1244 volume.set_voxel(i, j, k, VoxelValue::PrimitiveOutsideSurface);
1245 }
1246 }
1247 }
1248 }
1249}
1250}
1251 */