parry2d/mass_properties/mass_properties_convex_polygon.rs
1use crate::mass_properties::MassProperties;
2use crate::math::{Real, Vector};
3use crate::shape::Triangle;
4
5impl MassProperties {
6 /// Computes the mass properties of a convex polygon (2D only).
7 ///
8 /// A convex polygon is a 2D shape where all interior angles are less than 180 degrees
9 /// and all vertices point outward. This function decomposes the polygon into triangles
10 /// from the center of mass and sums their mass properties.
11 ///
12 /// # Arguments
13 ///
14 /// * `density` - The material density in kg/m² (mass per unit area)
15 /// * `vertices` - A slice of points defining the polygon vertices
16 /// - Must form a convex shape
17 /// - Vertices should be ordered counter-clockwise
18 /// - At least 3 vertices required
19 ///
20 /// # Returns
21 ///
22 /// A `MassProperties` struct containing:
23 /// - **mass**: Total mass calculated from area and density
24 /// - **local_com**: Center of mass (weighted average of triangle centroids)
25 /// - **inv_principal_inertia**: Inverse angular inertia (scalar in 2D)
26 ///
27 /// # Physics Background
28 ///
29 /// The algorithm:
30 /// 1. Computes the geometric center of all vertices
31 /// 2. Creates triangles from the center to each edge
32 /// 3. Calculates area and inertia for each triangle
33 /// 4. Combines results using weighted averages
34 ///
35 /// # Example - Pentagon
36 ///
37 /// ```
38 /// # #[cfg(all(feature = "dim2", feature = "f32"))] {
39 /// use parry2d::mass_properties::MassProperties;
40 /// use parry2d::math::Vector;
41 /// use std::f32::consts::PI;
42 ///
43 /// // Create a regular pentagon with radius 1.0
44 /// let mut vertices = Vec::new();
45 /// for i in 0..5 {
46 /// let angle = (i as f32) * 2.0 * PI / 5.0;
47 /// vertices.push(Vector::new(angle.cos(), angle.sin()));
48 /// }
49 ///
50 /// let density = 100.0;
51 /// let pentagon_props = MassProperties::from_convex_polygon(density, &vertices);
52 ///
53 /// println!("Pentagon mass: {:.2} kg", pentagon_props.mass());
54 /// println!("Center of mass: {:?}", pentagon_props.local_com);
55 ///
56 /// // For a regular polygon centered at origin, COM should be near origin
57 /// assert!(pentagon_props.local_com.length() < 0.01);
58 /// # }
59 /// ```
60 ///
61 /// # Example - Trapezoid
62 ///
63 /// ```
64 /// # #[cfg(all(feature = "dim2", feature = "f32"))] {
65 /// use parry2d::mass_properties::MassProperties;
66 /// use parry2d::math::Vector;
67 ///
68 /// // Create a trapezoid (4 vertices)
69 /// let vertices = vec![
70 /// Vector::ZERO, // Bottom left
71 /// Vector::new(4.0, 0.0), // Bottom right
72 /// Vector::new(3.0, 2.0), // Top right
73 /// Vector::new(1.0, 2.0), // Top left
74 /// ];
75 ///
76 /// let density = 50.0;
77 /// let trap_props = MassProperties::from_convex_polygon(density, &vertices);
78 ///
79 /// // Area of trapezoid = ((b1 + b2) / 2) × h = ((4 + 2) / 2) × 2 = 6 m²
80 /// // Mass = area × density = 300 kg
81 /// let mass = trap_props.mass();
82 /// println!("Trapezoid mass: {:.2} kg", mass);
83 /// # }
84 /// ```
85 ///
86 /// # Example - Custom Convex Shape
87 ///
88 /// ```
89 /// # #[cfg(all(feature = "dim2", feature = "f32"))] {
90 /// use parry2d::mass_properties::MassProperties;
91 /// use parry2d::math::Vector;
92 ///
93 /// // Arbitrary convex polygon
94 /// let vertices = vec![
95 /// Vector::ZERO,
96 /// Vector::new(2.0, 0.0),
97 /// Vector::new(3.0, 1.0),
98 /// Vector::new(2.0, 2.5),
99 /// Vector::new(0.0, 2.0),
100 /// ];
101 ///
102 /// let density = 200.0;
103 /// let props = MassProperties::from_convex_polygon(density, &vertices);
104 ///
105 /// println!("Custom polygon mass: {:.2} kg", props.mass());
106 /// println!("Moment of inertia: {:.2}", props.principal_inertia());
107 /// # }
108 /// ```
109 ///
110 /// # Use Cases
111 ///
112 /// - **Custom 2D shapes**: Game objects with specific geometry
113 /// - **Simplified collision**: Convex approximations of complex shapes
114 /// - **Platforms**: Angled or irregular platforms in 2D games
115 /// - **Polygonal wheels**: Multi-sided rotating objects
116 /// - **Terrain segments**: Ground pieces with varying slopes
117 ///
118 /// # Important Requirements
119 ///
120 /// - **Must be convex**: Non-convex (concave) polygons will produce incorrect results
121 /// - **Vertex ordering**: Counter-clockwise order is conventional but either works
122 /// - **Minimum vertices**: At least 3 vertices (forms a triangle)
123 /// - **No self-intersection**: Vertices must not cross each other
124 ///
125 /// # For Non-Convex Shapes
126 ///
127 /// If your polygon is concave (not convex):
128 /// 1. Use convex decomposition algorithms to break it into convex parts
129 /// 2. Compute mass properties for each convex part
130 /// 3. Combine using `from_compound()` or by summing MassProperties
131 ///
132 /// Alternatively, use `from_trimesh()` with a triangulated version of the shape.
133 ///
134 /// # Performance Note
135 ///
136 /// Computation time is O(n) where n is the number of vertices. The polygon is
137 /// decomposed into n triangles, each processed independently.
138 pub fn from_convex_polygon(density: Real, vertices: &[Vector]) -> MassProperties {
139 let (area, com) = convex_polygon_area_and_center_of_mass(vertices);
140
141 if area == 0.0 {
142 return MassProperties::new(com, 0.0, 0.0);
143 }
144
145 let mut itot = 0.0;
146
147 let mut iterpeek = vertices.iter().peekable();
148 let first_element = *iterpeek.peek().unwrap(); // store first element to close the cycle in the end with unwrap_or
149 while let Some(elem) = iterpeek.next() {
150 let triangle = Triangle::new(com, *elem, **iterpeek.peek().unwrap_or(&first_element));
151 let area = triangle.area();
152 let ipart = triangle.unit_angular_inertia();
153 itot += ipart * area;
154 }
155
156 Self::new(com, area * density, itot * density)
157 }
158}
159
160/// Computes the area and center-of-mass of a convex polygon.
161pub fn convex_polygon_area_and_center_of_mass(convex_polygon: &[Vector]) -> (Real, Vector) {
162 let mut geometric_center = convex_polygon.iter().fold(Vector::ZERO, |e1, e2| e1 + e2);
163 geometric_center /= convex_polygon.len() as Real;
164 let mut res = Vector::ZERO;
165 let mut areasum = 0.0;
166
167 let mut iterpeek = convex_polygon.iter().peekable();
168 let first_element = *iterpeek.peek().unwrap(); // Stores first element to close the cycle in the end with unwrap_or.
169 while let Some(elem) = iterpeek.next() {
170 let (a, b, c) = (
171 elem,
172 iterpeek.peek().unwrap_or(&first_element),
173 &geometric_center,
174 );
175 let area = Triangle::new(*a, **b, *c).area();
176 let center = (*a + **b + *c) / 3.0;
177
178 res += center * area;
179 areasum += area;
180 }
181
182 if areasum == 0.0 {
183 (areasum, geometric_center)
184 } else {
185 res /= areasum;
186 (areasum, res)
187 }
188}