parry2d/mass_properties/
mass_properties_trimesh2d.rs

1use crate::mass_properties::MassProperties;
2use crate::math::{Point, Real};
3use crate::shape::Triangle;
4
5impl MassProperties {
6    /// Computes the mass properties of a triangle mesh.
7    ///
8    /// A triangle mesh (trimesh) is a collection of triangles that together form a
9    /// 2D or 3D shape. This function works for both convex and non-convex (concave)
10    /// meshes. It decomposes the mesh into individual triangles, computes each
11    /// triangle's mass properties, and combines them.
12    ///
13    /// # Arguments
14    ///
15    /// * `density` - The material density
16    ///   - In 3D: kg/m³ (mass per unit volume) - treats mesh as a solid volume
17    ///   - In 2D: kg/m² (mass per unit area) - treats mesh as a flat surface
18    /// * `vertices` - Array of vertex positions (points in space)
19    /// * `indices` - Array of triangle indices, each element is `[u32; 3]`
20    ///   - Each triplet references three vertices forming a triangle
21    ///   - Indices must be valid: all values < vertices.len()
22    ///
23    /// # Returns
24    ///
25    /// A `MassProperties` struct containing:
26    /// - **mass**: Total mass of all triangles combined
27    /// - **local_com**: Center of mass (area/volume weighted)
28    /// - **inv_principal_inertia**: Combined angular inertia
29    ///
30    /// # Physics Background
31    ///
32    /// For each triangle:
33    /// 1. Compute area (2D) or volume contribution (3D)
34    /// 2. Find center of mass (centroid)
35    /// 3. Calculate moment of inertia
36    /// 4. Use parallel axis theorem to shift to common reference frame
37    /// 5. Sum all contributions
38    ///
39    /// # Example (2D) - L-Shape
40    ///
41    /// ```
42    /// # #[cfg(all(feature = "dim2", feature = "f32"))] {
43    /// use parry2d::mass_properties::MassProperties;
44    /// use nalgebra::Point2;
45    ///
46    /// // Create an L-shaped mesh from two rectangles (4 triangles)
47    /// let vertices = vec![
48    ///     Point2::origin(),
49    ///     Point2::new(2.0, 0.0),
50    ///     Point2::new(2.0, 1.0),
51    ///     Point2::new(1.0, 1.0),
52    ///     Point2::new(1.0, 3.0),
53    ///     Point2::new(0.0, 3.0),
54    /// ];
55    ///
56    /// let indices = vec![
57    ///     [0, 1, 2], // Bottom rectangle (triangle 1)
58    ///     [0, 2, 3], // Bottom rectangle (triangle 2)
59    ///     [0, 3, 4], // Vertical part (triangle 1)
60    ///     [0, 4, 5], // Vertical part (triangle 2)
61    /// ];
62    ///
63    /// let density = 100.0;
64    /// let l_shape_props = MassProperties::from_trimesh(density, &vertices, &indices);
65    ///
66    /// println!("L-shape mass: {:.2} kg", l_shape_props.mass());
67    /// println!("Center of mass: {:?}", l_shape_props.local_com);
68    /// # }
69    /// ```
70    ///
71    /// # Example (3D) - Pyramid
72    ///
73    /// ```
74    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
75    /// use parry3d::mass_properties::MassProperties;
76    /// use nalgebra::Point3;
77    ///
78    /// // Square pyramid: 4 vertices at base + 1 apex
79    /// let vertices = vec![
80    ///     Point3::new(-1.0, 0.0, -1.0), // Base corner 1
81    ///     Point3::new(1.0, 0.0, -1.0),  // Base corner 2
82    ///     Point3::new(1.0, 0.0, 1.0),   // Base corner 3
83    ///     Point3::new(-1.0, 0.0, 1.0),  // Base corner 4
84    ///     Point3::new(0.0, 2.0, 0.0),   // Apex
85    /// ];
86    ///
87    /// let indices = vec![
88    ///     [0, 1, 4], // Side face 1
89    ///     [1, 2, 4], // Side face 2
90    ///     [2, 3, 4], // Side face 3
91    ///     [3, 0, 4], // Side face 4
92    ///     [0, 2, 1], // Base (triangle 1)
93    ///     [0, 3, 2], // Base (triangle 2)
94    /// ];
95    ///
96    /// let density = 1000.0;
97    /// let pyramid_props = MassProperties::from_trimesh(density, &vertices, &indices);
98    ///
99    /// println!("Pyramid mass: {:.2} kg", pyramid_props.mass());
100    /// println!("Center of mass: {:?}", pyramid_props.local_com);
101    /// # }
102    /// ```
103    ///
104    /// # Example (3D) - Loading from File
105    ///
106    /// ```ignore
107    /// # {
108    /// use parry3d::mass_properties::MassProperties;
109    ///
110    /// // Assume you've loaded a mesh from an OBJ file
111    /// let mesh = load_obj_file("complex_model.obj");
112    /// let vertices = mesh.vertices;
113    /// let indices = mesh.indices;
114    ///
115    /// let density = 2700.0; // Aluminum
116    /// let props = MassProperties::from_trimesh(density, &vertices, &indices);
117    ///
118    /// println!("Model mass: {:.2} kg", props.mass());
119    /// # }
120    /// ```
121    ///
122    /// # Use Cases
123    ///
124    /// - **Complex 3D models**: Characters, vehicles, buildings
125    /// - **Terrain**: Height-mapped ground, caves, landscapes
126    /// - **Custom shapes**: Anything representable as triangles
127    /// - **Imported models**: Meshes from modeling software (Blender, Maya, etc.)
128    /// - **Non-convex objects**: Concave shapes that can't use simpler primitives
129    ///
130    /// # Mesh Quality Considerations
131    ///
132    /// - **Watertight meshes** (3D): For accurate volume/mass, mesh should be closed
133    ///   - Open meshes may give incorrect results
134    ///   - Check for holes, gaps, or missing faces
135    /// - **Triangle orientation**: Consistent winding order improves accuracy
136    ///   - Counter-clockwise from outside (right-hand rule)
137    /// - **Degenerate triangles**: Zero-area triangles are automatically handled (skipped)
138    /// - **Overlapping triangles**: Can cause incorrect results; ensure clean mesh
139    ///
140    /// # Performance
141    ///
142    /// Computation time is O(n) where n is the number of triangles. For large meshes
143    /// (thousands of triangles), this can take noticeable time. Consider:
144    /// - Using simpler primitive approximations when possible
145    /// - Pre-computing mass properties and caching results
146    /// - Simplifying meshes for physics (use low-poly collision mesh)
147    ///
148    /// # Trimesh vs Simpler Shapes
149    ///
150    /// For better performance and accuracy, use primitive shapes when possible:
151    /// - Ball, Cuboid, Cylinder: Much faster and more accurate
152    /// - Capsule: Better for elongated objects
153    /// - Compound: Combine multiple primitives
154    ///
155    /// Use trimesh only when the shape is truly complex and can't be approximated.
156    ///
157    /// # See Also
158    ///
159    /// - `from_convex_polyhedron()`: Alias for convex meshes
160    /// - `from_triangle()`: For single triangles
161    /// - `from_compound()`: Combine multiple simpler shapes
162    pub fn from_trimesh(
163        density: Real,
164        vertices: &[Point<Real>],
165        indices: &[[u32; 3]],
166    ) -> MassProperties {
167        let (area, com) = trimesh_area_and_center_of_mass(vertices, indices);
168
169        if area == 0.0 {
170            return MassProperties::new(com, 0.0, 0.0);
171        }
172
173        let mut itot = 0.0;
174
175        for idx in indices {
176            let triangle = Triangle::new(
177                vertices[idx[0] as usize],
178                vertices[idx[1] as usize],
179                vertices[idx[2] as usize],
180            );
181
182            // TODO: is the parallel axis theorem correctly applied here?
183            let area = triangle.area();
184            let ipart = triangle.unit_angular_inertia();
185            itot += ipart * area;
186        }
187
188        Self::new(com, area * density, itot * density)
189    }
190}
191
192/// Computes the area and center-of-mass of a triangle-mesh.
193pub fn trimesh_area_and_center_of_mass(
194    vertices: &[Point<Real>],
195    indices: &[[u32; 3]],
196) -> (Real, Point<Real>) {
197    let mut res = Point::origin();
198    let mut areasum = 0.0;
199
200    for idx in indices {
201        let triangle = Triangle::new(
202            vertices[idx[0] as usize],
203            vertices[idx[1] as usize],
204            vertices[idx[2] as usize],
205        );
206        let area = triangle.area();
207        let center = triangle.center();
208
209        res += center.coords * area;
210        areasum += area;
211    }
212
213    if areasum == 0.0 {
214        (areasum, res)
215    } else {
216        (areasum, res / areasum)
217    }
218}