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}