parry2d/mass_properties/
mass_properties_voxels.rs

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