parry3d/transformation/voxelization/mod.rs
1//! Voxelization of 2D and 3D shapes.
2//!
3//! # What is Voxelization?
4//!
5//! Voxelization is the process of converting a continuous geometric shape (like a triangle mesh in 3D
6//! or a polyline in 2D) into a discrete grid of cubic cells called **voxels**. Think of voxels as
7//! 3D pixels - just like pixels make up 2D images, voxels make up 3D volumes. In 2D, voxels are
8//! actually square cells (sometimes called "pixels" in other contexts).
9//!
10//! This is similar to how a photograph discretizes a continuous scene into pixels, except voxelization
11//! works in 3D space (or 2D for 2D shapes).
12//!
13//! # When Should You Use Voxelization?
14//!
15//! Voxelization is useful when you need to:
16//!
17//! - **Approximate complex shapes** with a simpler grid-based representation
18//! - **Compute volume** of complex meshes (by counting filled voxels)
19//! - **Perform spatial queries** efficiently using a regular grid structure
20//! - **Generate collision proxies** for physics simulations (using the V-HACD algorithm)
21//! - **Simplify mesh operations** like boolean operations or distance field computation
22//! - **Analyze shape properties** like connectivity, cavities, or internal structure
23//!
24//! # Key Concepts
25//!
26//! ## Voxel Grid
27//!
28//! A voxel grid divides space into a regular array of cubic (or square in 2D) cells. Each cell is
29//! identified by integer coordinates `(i, j)` in 2D or `(i, j, k)` in 3D. The physical size of each
30//! voxel is controlled by the `scale` parameter.
31//!
32//! ## Resolution vs Voxel Size
33//!
34//! You can control the granularity of voxelization in two ways:
35//!
36//! - **By resolution**: Specify how many subdivisions to make along the longest axis. The voxel size
37//! is automatically computed to maintain cubic voxels.
38//! - **By voxel size**: Directly specify the physical size of each voxel. The resolution is
39//! automatically computed based on the shape's bounding box.
40//!
41//! Higher resolution (or smaller voxel size) gives more accurate approximation but uses more memory.
42//!
43//! ## Fill Modes
44//!
45//! The [`FillMode`](crate::transformation::voxelization::FillMode) enum controls which voxels are considered "filled":
46//!
47//! - **`SurfaceOnly`**: Only voxels that intersect the surface boundary are marked as filled.
48//! This gives you a hollow shell representation.
49//!
50//! - **`FloodFill`**: Fills voxels on the surface AND all voxels inside the volume. This uses a
51//! flood-fill algorithm starting from outside the shape. Options include:
52//! - `detect_cavities`: Properly handles internal holes/cavities
53//! - `detect_self_intersections` (2D only): Attempts to handle self-intersecting boundaries
54//!
55//! # Main Types
56//!
57//! - [`VoxelSet`](crate::transformation::voxelization::VoxelSet): A sparse set containing only filled voxels (memory-efficient output format)
58//! - [`VoxelizedVolume`](crate::transformation::voxelization::VoxelizedVolume): A dense 3D grid of all voxels (intermediate format used during voxelization)
59//! - [`Voxel`](crate::transformation::voxelization::Voxel): A single voxel with its grid coordinates and metadata
60//! - [`FillMode`](crate::transformation::voxelization::FillMode): Configuration for how to determine filled vs empty voxels
61//! - [`VoxelValue`](crate::transformation::voxelization::VoxelValue): The state of a voxel (inside, outside, or on surface)
62//!
63//! # Examples
64//!
65//! ## Basic 3D Voxelization (Surface Only)
66//!
67//! ```
68//! # #[cfg(all(feature = "dim3", feature = "f32"))]
69//! # {
70//! use parry3d::transformation::voxelization::{FillMode, VoxelSet};
71//! use parry3d::shape::Cuboid;
72//! use nalgebra::{Point3, Vector3};
73//!
74//! // Create a simple cuboid and convert it to a triangle mesh
75//! let cuboid = Cuboid::new(Vector3::new(1.0, 0.5, 0.3));
76//! let (vertices, indices) = cuboid.to_trimesh();
77//!
78//! // Voxelize with resolution of 10 voxels along the longest axis
79//! let voxels = VoxelSet::voxelize(
80//! &vertices,
81//! &indices,
82//! 10, // resolution
83//! FillMode::SurfaceOnly, // only surface voxels
84//! false, // don't track voxel-to-triangle mapping
85//! );
86//!
87//! println!("Created {} surface voxels", voxels.len());
88//! println!("Each voxel has volume: {}", voxels.voxel_volume());
89//!
90//! // Access individual voxels
91//! for voxel in voxels.voxels() {
92//! println!("Voxel at grid position: {:?}", voxel.coords);
93//! println!(" World position: {:?}", voxels.get_voxel_point(voxel));
94//! println!(" On surface: {}", voxel.is_on_surface);
95//! }
96//! # }
97//! ```
98//!
99//! ## 3D Voxelization with Interior Fill
100//!
101//! ```
102//! # #[cfg(all(feature = "dim3", feature = "f32"))]
103//! # {
104//! use parry3d::transformation::voxelization::{FillMode, VoxelSet};
105//! use parry3d::shape::Ball;
106//! use nalgebra::Point3;
107//!
108//! // Create a sphere mesh
109//! let ball = Ball::new(1.0);
110//! let (vertices, indices) = ball.to_trimesh(20, 20); // subdivisions for sphere
111//!
112//! // Voxelize with flood-fill to get solid interior
113//! let voxels = VoxelSet::voxelize(
114//! &vertices,
115//! &indices,
116//! 15, // resolution
117//! FillMode::FloodFill {
118//! detect_cavities: false, // simple solid shape, no cavities
119//! },
120//! false,
121//! );
122//!
123//! // Compute the approximate volume
124//! let volume = voxels.compute_volume();
125//! let expected_volume = 4.0 / 3.0 * std::f32::consts::PI * 1.0_f32.powi(3);
126//! println!("Voxel volume: {}, Expected: {}", volume, expected_volume);
127//!
128//! // Count surface vs interior voxels
129//! let surface_count = voxels.voxels().iter().filter(|v| v.is_on_surface).count();
130//! let interior_count = voxels.voxels().iter().filter(|v| !v.is_on_surface).count();
131//! println!("Surface voxels: {}, Interior voxels: {}", surface_count, interior_count);
132//! # }
133//! ```
134//!
135//! ## 2D Voxelization of a Polygon
136//!
137//! ```
138//! # #[cfg(all(feature = "dim2", feature = "f32"))]
139//! # {
140//! use parry2d::transformation::voxelization::{FillMode, VoxelSet};
141//! use nalgebra::Point2;
142//!
143//! // Define a simple square as a polyline (boundary)
144//! let vertices = vec![
145//! Point2::origin(),
146//! Point2::new(2.0, 0.0),
147//! Point2::new(2.0, 2.0),
148//! Point2::new(0.0, 2.0),
149//! ];
150//!
151//! // Create index buffer for the polyline segments (closed loop)
152//! let indices = vec![
153//! [0, 1], // bottom edge
154//! [1, 2], // right edge
155//! [2, 3], // top edge
156//! [3, 0], // left edge
157//! ];
158//!
159//! // Voxelize with specific voxel size
160//! let voxels = VoxelSet::with_voxel_size(
161//! &vertices,
162//! &indices,
163//! 0.2, // voxel size
164//! FillMode::FloodFill {
165//! detect_cavities: false,
166//! detect_self_intersections: false,
167//! },
168//! false,
169//! );
170//!
171//! println!("2D voxel area: {}", voxels.compute_volume());
172//! # }
173//! ```
174//!
175//! ## Computing Convex Hull from Voxels
176//!
177//! ```
178//! # #[cfg(all(feature = "dim3", feature = "f32"))]
179//! # {
180//! use parry3d::transformation::voxelization::{FillMode, VoxelSet};
181//! use parry3d::shape::Cuboid;
182//! use nalgebra::Vector3;
183//!
184//! let cuboid = Cuboid::new(Vector3::new(1.0, 1.0, 1.0));
185//! let (vertices, indices) = cuboid.to_trimesh();
186//!
187//! let voxels = VoxelSet::voxelize(
188//! &vertices,
189//! &indices,
190//! 8,
191//! FillMode::SurfaceOnly,
192//! false,
193//! );
194//!
195//! // Compute convex hull from the voxel centers
196//! // sampling=1 means use every voxel, higher values skip voxels for performance
197//! let (hull_vertices, hull_indices) = voxels.compute_convex_hull(1);
198//!
199//! println!("Convex hull has {} vertices and {} triangles",
200//! hull_vertices.len(), hull_indices.len());
201//! # }
202//! ```
203//!
204//! ## Tracking Which Triangles Intersect Each Voxel
205//!
206//! ```
207//! # #[cfg(all(feature = "dim3", feature = "f32"))]
208//! # {
209//! use parry3d::transformation::voxelization::{FillMode, VoxelSet};
210//! use parry3d::shape::Cuboid;
211//! use nalgebra::Vector3;
212//!
213//! let cuboid = Cuboid::new(Vector3::new(1.0, 1.0, 1.0));
214//! let (vertices, indices) = cuboid.to_trimesh();
215//!
216//! // Enable voxel-to-primitives mapping
217//! let voxels = VoxelSet::voxelize(
218//! &vertices,
219//! &indices,
220//! 10,
221//! FillMode::SurfaceOnly,
222//! true, // keep_voxel_to_primitives_map = true
223//! );
224//!
225//! // Now we can compute exact intersections between voxels and triangles
226//! let intersection_points = voxels.compute_primitive_intersections(&vertices, &indices);
227//! println!("Generated {} intersection points", intersection_points.len());
228//!
229//! // Or compute a more accurate convex hull using the triangle intersections
230//! let (hull_verts, hull_indices) = voxels.compute_exact_convex_hull(&vertices, &indices);
231//! println!("Exact convex hull: {} vertices", hull_verts.len());
232//! # }
233//! ```
234//!
235//! # Performance Considerations
236//!
237//! - **Resolution**: Doubling the resolution increases memory usage by 4× in 2D or 8× in 3D
238//! - **VoxelSet vs VoxelizedVolume**: `VoxelSet` is sparse (only stores filled voxels), while
239//! `VoxelizedVolume` is dense (stores all grid cells). For sparse shapes, `VoxelSet` is much
240//! more memory-efficient.
241//! - **Voxel-to-primitives map**: Setting `keep_voxel_to_primitives_map = true` adds memory overhead
242//! but enables more precise operations like `compute_exact_convex_hull()`
243//! - **Flood-fill**: Using `FloodFill` mode is more expensive than `SurfaceOnly` but gives you
244//! solid volumes instead of hollow shells
245//!
246//! # Use in Convex Decomposition
247//!
248//! The voxelization system is used internally by Parry's V-HACD implementation for approximate
249//! convex decomposition. See the [`vhacd`](crate::transformation::vhacd) module for details.
250
251pub use self::voxel_set::{Voxel, VoxelSet};
252pub use self::voxelized_volume::{FillMode, VoxelValue, VoxelizedVolume};
253
254mod voxel_set;
255mod voxelized_volume;