parry2d/mass_properties/mass_properties_convex_polygon.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 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 nalgebra::Point2;
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(Point2::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.coords.norm() < 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 nalgebra::Point2;
67 ///
68 /// // Create a trapezoid (4 vertices)
69 /// let vertices = vec![
70 /// Point2::origin(), // Bottom left
71 /// Point2::new(4.0, 0.0), // Bottom right
72 /// Point2::new(3.0, 2.0), // Top right
73 /// Point2::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 nalgebra::Point2;
92 ///
93 /// // Arbitrary convex polygon
94 /// let vertices = vec![
95 /// Point2::origin(),
96 /// Point2::new(2.0, 0.0),
97 /// Point2::new(3.0, 1.0),
98 /// Point2::new(2.0, 2.5),
99 /// Point2::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: &[Point<Real>]) -> 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(
162 convex_polygon: &[Point<Real>],
163) -> (Real, Point<Real>) {
164 let geometric_center = convex_polygon
165 .iter()
166 .fold(Point::origin(), |e1, e2| e1 + e2.coords)
167 / convex_polygon.len() as Real;
168 let mut res = Point::origin();
169 let mut areasum = 0.0;
170
171 let mut iterpeek = convex_polygon.iter().peekable();
172 let first_element = *iterpeek.peek().unwrap(); // Stores first element to close the cycle in the end with unwrap_or.
173 while let Some(elem) = iterpeek.next() {
174 let (a, b, c) = (
175 elem,
176 iterpeek.peek().unwrap_or(&first_element),
177 &geometric_center,
178 );
179 let area = Triangle::new(*a, **b, *c).area();
180 let center = (a.coords + b.coords + c.coords) / 3.0;
181
182 res += center * area;
183 areasum += area;
184 }
185
186 if areasum == 0.0 {
187 (areasum, geometric_center)
188 } else {
189 (areasum, res / areasum)
190 }
191}