parry3d/mass_properties/
mass_properties_capsule.rs

1use crate::mass_properties::MassProperties;
2use crate::math::{Point, Real};
3#[cfg(feature = "dim3")]
4use crate::shape::Capsule;
5
6impl MassProperties {
7    /// Computes the mass properties of a capsule (pill-shaped object).
8    ///
9    /// A capsule is a cylinder with hemispherical caps on both ends. It's defined by two
10    /// endpoint centers (a line segment) and a radius. Capsules are commonly used for
11    /// character controllers and elongated objects because they provide smooth collision
12    /// handling without sharp edges.
13    ///
14    /// # Arguments
15    ///
16    /// * `density` - The material density (mass per unit volume/area). Higher values make heavier objects.
17    ///   - In 3D: units are typically kg/m³
18    ///   - In 2D: units are typically kg/m² (mass per unit area)
19    /// * `a` - First endpoint of the capsule's central axis (center of first hemisphere)
20    /// * `b` - Second endpoint of the capsule's central axis (center of second hemisphere)
21    /// * `radius` - The radius of the capsule (distance from the axis to the surface)
22    ///
23    /// # Returns
24    ///
25    /// A `MassProperties` struct containing:
26    /// - **mass**: Total mass calculated from volume and density
27    /// - **local_com**: Center of mass at the midpoint between `a` and `b`
28    /// - **inv_principal_inertia**: Inverse angular inertia (varies by axis)
29    /// - **principal_inertia_local_frame** (3D only): Rotation aligning capsule axis with Y
30    ///
31    /// # Physics Background
32    ///
33    /// A capsule consists of:
34    /// - A cylindrical body connecting the two endpoints
35    /// - Two hemispherical caps (which together form one complete sphere)
36    /// - The total length is: `distance(a, b) + 2 * radius`
37    /// - Mass and inertia are computed by combining cylinder + sphere components
38    ///
39    /// # Example (3D) - Character Controller
40    ///
41    /// ```
42    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
43    /// use parry3d::mass_properties::MassProperties;
44    /// use nalgebra::Point3;
45    ///
46    /// // Create a capsule for a standing character (height ~2m, radius 0.3m)
47    /// // Endpoints at (0, 0, 0) and (0, 2, 0) form vertical capsule
48    /// let a = Point3::origin();
49    /// let b = Point3::new(0.0, 2.0, 0.0);
50    /// let radius = 0.3;
51    /// let density = 985.0; // Similar to human body density
52    ///
53    /// let character_props = MassProperties::from_capsule(density, a, b, radius);
54    ///
55    /// // Center of mass is at the midpoint
56    /// let expected_com = Point3::new(0.0, 1.0, 0.0);
57    /// assert!((character_props.local_com - expected_com).norm() < 0.01);
58    ///
59    /// let mass = character_props.mass();
60    /// println!("Character mass: {:.2} kg", mass); // Approximately 70-80 kg
61    ///
62    /// // Inertia is higher around horizontal axes (harder to tip over)
63    /// let inertia = character_props.principal_inertia();
64    /// println!("Inertia X: {:.2}, Y: {:.2}, Z: {:.2}", inertia.x, inertia.y, inertia.z);
65    /// # }
66    /// ```
67    ///
68    /// # Example (3D) - Horizontal Capsule (Lying Down)
69    ///
70    /// ```
71    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
72    /// use parry3d::mass_properties::MassProperties;
73    /// use nalgebra::Point3;
74    ///
75    /// // Create a horizontal capsule along the X-axis
76    /// let a = Point3::new(-1.0, 0.0, 0.0);
77    /// let b = Point3::new(1.0, 0.0, 0.0);
78    /// let radius = 0.5;
79    /// let density = 1000.0;
80    ///
81    /// let capsule_props = MassProperties::from_capsule(density, a, b, radius);
82    ///
83    /// // Center of mass at midpoint (origin)
84    /// assert_eq!(capsule_props.local_com, Point3::origin());
85    ///
86    /// // Total length = distance + 2*radius = 2.0 + 1.0 = 3.0 meters
87    /// println!("Mass: {:.2} kg", capsule_props.mass());
88    /// # }
89    /// ```
90    ///
91    /// # Example (2D) - Stadium Shape
92    ///
93    /// ```
94    /// # #[cfg(all(feature = "dim2", feature = "f32"))] {
95    /// use parry2d::mass_properties::MassProperties;
96    /// use nalgebra::Point2;
97    ///
98    /// // Create a horizontal 2D capsule (stadium/discorectangle shape)
99    /// let a = Point2::new(-2.0, 0.0);
100    /// let b = Point2::new(2.0, 0.0);
101    /// let radius = 1.0;
102    /// let density = 100.0; // kg/m²
103    ///
104    /// let stadium_props = MassProperties::from_capsule(density, a, b, radius);
105    ///
106    /// println!("Stadium mass: {:.2} kg", stadium_props.mass());
107    /// println!("Moment of inertia: {:.2}", stadium_props.principal_inertia());
108    /// # }
109    /// ```
110    ///
111    /// # Use Cases
112    ///
113    /// - **Character controllers**: Humanoid characters, NPCs
114    /// - **Vehicles**: Simplified car or boat bodies
115    /// - **Projectiles**: Bullets, missiles, arrows
116    /// - **Limbs**: Arms, legs in ragdoll physics
117    /// - **Cylinders with rounded ends**: Pipes, rods, poles
118    ///
119    /// # Common Mistakes
120    ///
121    /// - **Total length confusion**: The visual length is `distance(a, b) + 2 * radius`,
122    ///   not just `distance(a, b)`. The hemispheres add extra length.
123    /// - **Endpoint placement**: Points `a` and `b` are centers of the hemispherical caps,
124    ///   not the extreme ends of the capsule.
125    ///
126    /// # Performance Note
127    ///
128    /// Capsules are very efficient for collision detection (almost as fast as spheres)
129    /// and provide smooth rolling behavior. They're preferred over cylinders for
130    /// dynamic objects that need to move smoothly.
131    pub fn from_capsule(density: Real, a: Point<Real>, b: Point<Real>, radius: Real) -> Self {
132        let half_height = (b - a).norm() / 2.0;
133        let (cyl_vol, cyl_unit_i) = Self::cylinder_y_volume_unit_inertia(half_height, radius);
134        let (ball_vol, ball_unit_i) = Self::ball_volume_unit_angular_inertia(radius);
135        let cap_vol = cyl_vol + ball_vol;
136        let cap_mass = cap_vol * density;
137        let mut cap_i = (cyl_unit_i * cyl_vol + ball_unit_i * ball_vol) * density;
138        let local_com = na::center(&a, &b);
139
140        #[cfg(feature = "dim2")]
141        {
142            let h = half_height * 2.0;
143            let extra = (h * h * 0.25 + h * radius * 3.0 / 8.0) * ball_vol * density;
144            cap_i += extra;
145            Self::new(local_com, cap_mass, cap_i)
146        }
147
148        #[cfg(feature = "dim3")]
149        {
150            let h = half_height * 2.0;
151            let extra = (h * h * 0.25 + h * radius * 3.0 / 8.0) * ball_vol * density;
152            cap_i.x += extra;
153            cap_i.z += extra;
154            let local_frame = Capsule::new(a, b, radius).rotation_wrt_y();
155            Self::with_principal_inertia_frame(local_com, cap_mass, cap_i, local_frame)
156        }
157    }
158}