parry3d/mass_properties/
mass_properties_compound.rs

1use crate::mass_properties::MassProperties;
2use crate::math::{Isometry, Real};
3use crate::shape::SharedShape;
4
5impl MassProperties {
6    /// Computes the mass properties of a compound shape (combination of multiple shapes).
7    ///
8    /// A compound shape is a collection of sub-shapes, each with its own position and
9    /// orientation. This function computes the mass properties of each sub-shape,
10    /// transforms them to their local positions, and combines them using the parallel
11    /// axis theorem to get the total mass properties.
12    ///
13    /// # Arguments
14    ///
15    /// * `density` - The material density applied to all sub-shapes
16    ///   - In 3D: kg/m³ (mass per unit volume)
17    ///   - In 2D: kg/m² (mass per unit area)
18    /// * `shapes` - Array of (position, shape) pairs
19    ///   - Each shape has an `Isometry` (position + rotation)
20    ///   - Shapes can be any type implementing the `Shape` trait
21    ///
22    /// # Returns
23    ///
24    /// A `MassProperties` struct containing:
25    /// - **mass**: Sum of all sub-shape masses
26    /// - **local_com**: Combined center of mass (mass-weighted average)
27    /// - **inv_principal_inertia**: Combined angular inertia
28    ///
29    /// # Physics Background
30    ///
31    /// The parallel axis theorem is used to shift inertia tensors:
32    /// - Each shape's mass properties are computed in its local frame
33    /// - Properties are transformed to the compound's coordinate system
34    /// - Center of mass is the mass-weighted average of all sub-shapes
35    /// - Angular inertia accounts for both local rotation and offset from COM
36    ///
37    /// # Example (3D) - Dumbbell
38    ///
39    /// ```
40    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
41    /// use parry3d::mass_properties::MassProperties;
42    /// use parry3d::shape::{Ball, SharedShape};
43    /// use nalgebra::{Isometry3, Vector3};
44    ///
45    /// // Create a dumbbell: two balls connected by a bar
46    /// let ball = SharedShape::new(Ball::new(0.5));
47    /// let bar = SharedShape::new(parry3d::shape::Cuboid::new(Vector3::new(0.1, 1.0, 0.1)));
48    ///
49    /// let shapes = vec![
50    ///     (Isometry3::translation(0.0, -1.0, 0.0), ball.clone()),  // Left ball
51    ///     (Isometry3::identity(), bar),                             // Center bar
52    ///     (Isometry3::translation(0.0, 1.0, 0.0), ball),            // Right ball
53    /// ];
54    ///
55    /// let density = 1000.0;
56    /// let dumbbell_props = MassProperties::from_compound(density, &shapes);
57    ///
58    /// println!("Dumbbell mass: {:.2} kg", dumbbell_props.mass());
59    /// println!("Center of mass: {:?}", dumbbell_props.local_com);
60    ///
61    /// // Dumbbell has high inertia around X and Z (hard to spin end-over-end)
62    /// // but low inertia around Y (easy to spin along the bar)
63    /// let inertia = dumbbell_props.principal_inertia();
64    /// println!("Inertia: X={:.3}, Y={:.3}, Z={:.3}", inertia.x, inertia.y, inertia.z);
65    /// # }
66    /// ```
67    ///
68    /// # Example (2D) - Table
69    ///
70    /// ```
71    /// # #[cfg(all(feature = "dim2", feature = "f32"))] {
72    /// use parry2d::mass_properties::MassProperties;
73    /// use parry2d::shape::{Cuboid, SharedShape};
74    /// use nalgebra::{Isometry2, Vector2};
75    ///
76    /// // Create a simple table: top surface + legs
77    /// let top = SharedShape::new(Cuboid::new(Vector2::new(2.0, 0.1)));    // Wide, thin top
78    /// let leg = SharedShape::new(Cuboid::new(Vector2::new(0.1, 0.5)));    // Narrow, tall leg
79    ///
80    /// let shapes = vec![
81    ///     (Isometry2::translation(0.0, 0.6), top),                   // Table top
82    ///     (Isometry2::translation(-1.5, 0.0), leg.clone()),          // Left leg
83    ///     (Isometry2::translation(1.5, 0.0), leg),                   // Right leg
84    /// ];
85    ///
86    /// let density = 500.0; // Wood
87    /// let table_props = MassProperties::from_compound(density, &shapes);
88    ///
89    /// println!("Table mass: {:.2} kg", table_props.mass());
90    /// # }
91    /// ```
92    ///
93    /// # Example (3D) - Robot Arm
94    ///
95    /// ```
96    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
97    /// use parry3d::mass_properties::MassProperties;
98    /// use parry3d::shape::{Capsule, Cuboid, SharedShape};
99    /// use nalgebra::{Isometry3, Point3, Vector3};
100    ///
101    /// // Simple robot arm with multiple segments
102    /// let base = SharedShape::new(Cuboid::new(Vector3::new(0.3, 0.2, 0.3)));
103    /// let upper_arm = SharedShape::new(Capsule::new(
104    ///     Point3::origin(),
105    ///     Point3::new(0.0, 1.0, 0.0),
106    ///     0.1
107    /// ));
108    /// let forearm = SharedShape::new(Capsule::new(
109    ///     Point3::origin(),
110    ///     Point3::new(0.0, 0.8, 0.0),
111    ///     0.08
112    /// ));
113    ///
114    /// let shapes = vec![
115    ///     (Isometry3::identity(), base),
116    ///     (Isometry3::translation(0.0, 0.2, 0.0), upper_arm),
117    ///     (Isometry3::translation(0.0, 1.2, 0.0), forearm),
118    /// ];
119    ///
120    /// let density = 2700.0; // Aluminum
121    /// let arm_props = MassProperties::from_compound(density, &shapes);
122    ///
123    /// println!("Robot arm mass: {:.2} kg", arm_props.mass());
124    /// println!("Arm center of mass: {:?}", arm_props.local_com);
125    /// # }
126    /// ```
127    ///
128    /// # Use Cases
129    ///
130    /// - **Complex objects**: Multi-part objects (tables, chairs, vehicles)
131    /// - **Articulated bodies**: Robot arms, character skeletons
132    /// - **Assemblies**: Combining simple shapes into complex forms
133    /// - **Non-convex shapes**: Convex decomposition results
134    /// - **Hierarchical structures**: Nested compound shapes
135    ///
136    /// # Different Densities
137    ///
138    /// To use different densities for different parts:
139    ///
140    /// ```ignore
141    /// // Compute each part separately with its own density
142    /// let heavy_part = ball_shape.mass_properties(5000.0).transform_by(&pos1);
143    /// let light_part = cuboid_shape.mass_properties(100.0).transform_by(&pos2);
144    ///
145    /// // Combine manually
146    /// let total = heavy_part + light_part;
147    /// ```
148    ///
149    /// # Performance Note
150    ///
151    /// The computation time is O(n) where n is the number of sub-shapes. Each shape's
152    /// mass properties are computed once and then combined. This is efficient even for
153    /// large numbers of shapes.
154    ///
155    /// # See Also
156    ///
157    /// - `MassProperties::transform_by()`: Transform mass properties to a new frame
158    /// - `Add` trait: Combine mass properties with `+` operator
159    /// - `Sum` trait: Sum an iterator of mass properties
160    pub fn from_compound(density: Real, shapes: &[(Isometry<Real>, SharedShape)]) -> Self {
161        shapes
162            .iter()
163            .map(|s| s.1.mass_properties(density).transform_by(&s.0))
164            .sum()
165    }
166}