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}