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}