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}