parry3d/mass_properties/
mass_properties_voxels.rs

1use crate::mass_properties::MassProperties;
2use crate::math::{Point, Real};
3use crate::shape::Voxels;
4
5impl MassProperties {
6    /// Computes the mass properties of a voxel grid.
7    ///
8    /// Voxels (volumetric pixels) represent a 3D shape as a grid of small cubes. This
9    /// function treats each non-empty voxel as a small cuboid and combines their mass
10    /// properties. It's useful for volumetric data, destructible terrain, or shapes that
11    /// are difficult to represent with traditional geometry.
12    ///
13    /// # Arguments
14    ///
15    /// * `density` - The material density
16    ///   - In 3D: kg/m³ (mass per unit volume)
17    ///   - In 2D: kg/m² (mass per unit area)
18    /// * `voxels` - A `Voxels` structure containing the voxel grid
19    ///   - Each voxel is a small cube/square of uniform size
20    ///   - Voxels can be empty or filled
21    ///   - Since v0.25.0, uses sparse storage internally for efficiency
22    ///
23    /// # Returns
24    ///
25    /// A `MassProperties` struct containing:
26    /// - **mass**: Total mass of all non-empty voxels
27    /// - **local_com**: Center of mass (weighted average of voxel centers)
28    /// - **inv_principal_inertia**: Combined angular inertia
29    ///
30    /// # Physics Background
31    ///
32    /// The algorithm:
33    /// 1. Compute mass properties of a single voxel (small cuboid)
34    /// 2. For each non-empty voxel, shift its mass properties to its position
35    /// 3. Sum all contributions using parallel axis theorem
36    /// 4. Empty voxels contribute nothing (zero mass)
37    ///
38    /// # Example (3D) - Simple Voxel Object
39    ///
40    /// ```
41    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
42    /// use parry3d::mass_properties::MassProperties;
43    /// use parry3d::shape::Voxels;
44    /// use nalgebra::{Point3, Vector3};
45    ///
46    /// // Create a 3×3×3 voxel grid with 1m voxels
47    /// let voxel_size = Vector3::new(1.0, 1.0, 1.0);
48    ///
49    /// // Fill some voxels to create an L-shape
50    /// let voxels = &[
51    ///     Point3::new(0, 0, 0), // Bottom bar
52    ///     Point3::new(1, 0, 0),
53    ///     Point3::new(2, 0, 0),
54    ///     Point3::new(0, 1, 0), // Vertical part
55    ///     Point3::new(0, 2, 0),
56    /// ];
57    /// let voxels = Voxels::new(voxel_size, voxels);
58    ///
59    /// let density = 1000.0; // Water density
60    /// let voxel_props = MassProperties::from_voxels(density, &voxels);
61    ///
62    /// // 5 voxels × 1m³ each × 1000 kg/m³ = 5000 kg
63    /// println!("Voxel object mass: {:.2} kg", voxel_props.mass());
64    /// println!("Center of mass: {:?}", voxel_props.local_com);
65    /// # }
66    /// ```
67    ///
68    /// # Example (3D) - Destructible Terrain
69    ///
70    /// ```
71    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
72    /// use parry3d::mass_properties::MassProperties;
73    /// use parry3d::shape::Voxels;
74    /// use nalgebra::{Point3, Vector3};
75    ///
76    /// // Create a chunk of destructible terrain
77    /// let voxel_size = Vector3::new(0.5, 0.5, 0.5); // 50cm voxels
78    /// let mut voxels = vec![];
79    ///
80    /// // Fill a 4×4×4 solid block
81    /// for x in 0..4 {
82    ///     for y in 0..4 {
83    ///         for z in 0..4 {
84    ///             voxels.push(Point3::new(x, y, z));
85    ///         }
86    ///     }
87    /// }
88    ///
89    /// let mut terrain = Voxels::new(voxel_size, &voxels);
90    ///
91    /// let density = 2400.0; // Concrete
92    /// let terrain_props = MassProperties::from_voxels(density, &terrain);
93    ///
94    /// println!("Terrain chunk mass: {:.2} kg", terrain_props.mass());
95    /// # }
96    /// ```
97    ///
98    /// # Example - Sparse Voxel Grid (Efficient)
99    ///
100    /// ```
101    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
102    /// use parry3d::mass_properties::MassProperties;
103    /// use parry3d::shape::Voxels;
104    /// use nalgebra::{Point3, Vector3};
105    ///
106    /// // Large sparse grid (only stores filled voxels since v0.25.0)
107    /// let voxel_size = Vector3::new(0.1, 0.1, 0.1);
108    ///
109    /// // Scatter some voxels in a large space (efficient with sparse storage)
110    /// let voxels = &[
111    ///     Point3::new(0, 0, 0),
112    ///     Point3::new(100, 50, 75),
113    ///     Point3::new(-50, 200, -30),
114    /// ];
115    /// let voxels = Voxels::new(voxel_size, voxels);
116    /// let density = 1000.0;
117    /// let props = MassProperties::from_voxels(density, &voxels);
118    ///
119    /// // Only 3 voxels contribute to mass
120    /// println!("Sparse voxel mass: {:.4} kg", props.mass());
121    /// # }
122    /// ```
123    ///
124    /// # Use Cases
125    ///
126    /// - **Destructible terrain**: Voxel-based environments (Minecraft-style)
127    /// - **Medical imaging**: CT scans, MRI data volumetric analysis
128    /// - **Procedural generation**: Voxel-based world generation
129    /// - **Simulation**: Granular materials, fluids represented as voxels
130    /// - **Dynamic shapes**: Objects that change shape at runtime
131    /// - **Complex geometry**: Shapes difficult to represent with meshes
132    ///
133    /// # Performance Considerations
134    ///
135    /// - **Sparse storage** (v0.25.0+): Only filled voxels consume memory
136    /// - **Computation time**: O(n) where n = number of filled voxels
137    /// - **For large grids**: Prefer coarser voxel sizes when possible
138    /// - **Memory usage**: Each voxel stores position and state
139    /// - **Alternative**: For static shapes, consider using triangle meshes
140    ///
141    /// # Voxel Size Trade-offs
142    ///
143    /// **Smaller voxels**:
144    /// - More accurate representation of curved surfaces
145    /// - More voxels = longer computation time
146    /// - Higher memory usage (more voxels to store)
147    ///
148    /// **Larger voxels**:
149    /// - Faster computation
150    /// - Less memory
151    /// - Blockier appearance (lower resolution)
152    ///
153    /// # Accuracy Notes
154    ///
155    /// - Voxel representation is an approximation of the true shape
156    /// - Smooth curves become staircase patterns
157    /// - Mass properties accuracy depends on voxel resolution
158    /// - For exact results with smooth shapes, use primitive shapes or meshes
159    ///
160    /// # Empty vs Filled Voxels
161    ///
162    /// - Only non-empty voxels contribute to mass
163    /// - Empty voxels are ignored (zero mass, no inertia)
164    /// - The voxel state is checked using `vox.state.is_empty()`
165    ///
166    /// # See Also
167    ///
168    /// - `Voxels::new()`: Create a new voxel grid
169    /// - `Voxels::set_voxel()`: Add or remove voxels
170    /// - `from_trimesh()`: Alternative for precise shapes
171    /// - `from_compound()`: Combine multiple shapes efficiently
172    pub fn from_voxels(density: Real, voxels: &Voxels) -> Self {
173        let mut com = Point::origin();
174        let mut num_not_empty = 0;
175        let mut angular_inertia = na::zero();
176        let block_ref_mprops = MassProperties::from_cuboid(density, voxels.voxel_size() / 2.0);
177
178        for vox in voxels.voxels() {
179            if !vox.state.is_empty() {
180                com += vox.center.coords;
181                num_not_empty += 1;
182            }
183        }
184
185        com.coords /= num_not_empty as Real;
186
187        for vox in voxels.voxels() {
188            if !vox.state.is_empty() {
189                angular_inertia +=
190                    block_ref_mprops.construct_shifted_inertia_matrix(vox.center - com);
191            }
192        }
193
194        let mass = block_ref_mprops.mass() * num_not_empty as Real;
195
196        #[cfg(feature = "dim2")]
197        return Self::new(com, mass, angular_inertia);
198        #[cfg(feature = "dim3")]
199        return Self::with_inertia_matrix(com, mass, angular_inertia);
200    }
201}