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}