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}