parry3d/mass_properties/
mass_properties_cylinder.rs

1use crate::mass_properties::MassProperties;
2use crate::math::{PrincipalAngularInertia, Real, Vector};
3#[cfg(feature = "dim3")]
4use crate::math::{RealField, Rotation};
5
6impl MassProperties {
7    pub(crate) fn cylinder_y_volume_unit_inertia(
8        half_height: Real,
9        radius: Real,
10    ) -> (Real, PrincipalAngularInertia) {
11        #[cfg(feature = "dim2")]
12        {
13            Self::cuboid_volume_unit_inertia(Vector::new(radius, half_height))
14        }
15
16        #[cfg(feature = "dim3")]
17        {
18            let volume = half_height * radius * radius * Real::pi() * 2.0;
19            let sq_radius = radius * radius;
20            let sq_height = half_height * half_height * 4.0;
21            let off_principal = (sq_radius * 3.0 + sq_height) / 12.0;
22
23            let inertia = Vector::new(off_principal, sq_radius / 2.0, off_principal);
24            (volume, inertia)
25        }
26    }
27
28    /// Computes the mass properties of a cylinder (3D only).
29    ///
30    /// A cylinder is a 3D shape with circular cross-section and flat ends, aligned along
31    /// the Y-axis and centered at the origin. Unlike a capsule, a cylinder has sharp edges
32    /// at the top and bottom.
33    ///
34    /// **Note**: In 2D, this function is used internally but cylinders don't exist as a
35    /// distinct 2D shape (rectangles are used instead).
36    ///
37    /// # Arguments
38    ///
39    /// * `density` - The material density in kg/m³ (e.g., aluminum = 2700, plastic = 950)
40    /// * `half_height` - Half the total height of the cylinder (center to top/bottom)
41    /// * `radius` - The radius of the circular cross-section
42    ///
43    /// # Returns
44    ///
45    /// A `MassProperties` struct containing:
46    /// - **mass**: Total mass calculated from volume and density
47    /// - **local_com**: Center of mass at the origin (cylinders are symmetric)
48    /// - **inv_principal_inertia**: Inverse angular inertia (different for each axis)
49    /// - **principal_inertia_local_frame**: Identity rotation (aligned with Y-axis)
50    ///
51    /// # Physics Background
52    ///
53    /// Cylinders have rotational symmetry around the Y-axis:
54    /// - Volume = π × radius² × height
55    /// - Center of mass is at the geometric center (origin)
56    /// - Angular inertia around Y-axis (spinning like a top): I_y = (1/2) × mass × radius²
57    /// - Angular inertia around X/Z axes (tipping over): I_x = I_z = (1/12) × mass × (3×radius² + height²)
58    /// - Easier to spin around the central axis than to tip over
59    ///
60    /// # Example - Aluminum Can
61    ///
62    /// ```
63    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
64    /// use parry3d::mass_properties::MassProperties;
65    /// use parry3d::math::Vector;
66    ///
67    /// // Standard soda can: 12.3cm tall, 6.6cm diameter
68    /// // Aluminum density: ~2700 kg/m³
69    /// let half_height = 0.0615; // 6.15 cm in meters
70    /// let radius = 0.033;        // 3.3 cm in meters
71    /// let density = 2700.0;
72    ///
73    /// let can_props = MassProperties::from_cylinder(density, half_height, radius);
74    ///
75    /// let mass = can_props.mass();
76    /// println!("Can mass: {:.2} kg", mass); // Approximately 0.15 kg (150 grams)
77    ///
78    /// // Center of mass at origin
79    /// assert_eq!(can_props.local_com, Vector::ZERO);
80    ///
81    /// // Check inertia differences
82    /// let inertia = can_props.principal_inertia();
83    /// println!("Spin inertia (Y): {:.6}", inertia.y); // Low (easy to spin)
84    /// println!("Tip inertia (X): {:.6}", inertia.x);  // Higher (harder to tip)
85    /// assert!(inertia.y < inertia.x); // Easier to spin than tip
86    /// # }
87    /// ```
88    ///
89    /// # Example - Concrete Column
90    ///
91    /// ```
92    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
93    /// use parry3d::mass_properties::MassProperties;
94    ///
95    /// // Concrete support column: 3m tall, 0.5m diameter
96    /// // Concrete density: ~2400 kg/m³
97    /// let half_height = 1.5;  // 3m / 2
98    /// let radius = 0.25;      // 0.5m / 2
99    /// let density = 2400.0;
100    ///
101    /// let column_props = MassProperties::from_cylinder(density, half_height, radius);
102    ///
103    /// // Volume = π × (0.25)² × 3 = 0.589 m³
104    /// // Mass = 0.589 × 2400 = 1414 kg
105    /// let mass = column_props.mass();
106    /// assert!((mass - 1414.0).abs() < 10.0);
107    ///
108    /// println!("Column mass: {:.0} kg", mass);
109    /// # }
110    /// ```
111    ///
112    /// # Example - Comparing Cylinder vs Capsule
113    ///
114    /// ```
115    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
116    /// use parry3d::mass_properties::MassProperties;
117    /// use parry3d::math::Vector;
118    ///
119    /// let half_height = 1.0;
120    /// let radius = 0.5;
121    /// let density = 1000.0;
122    ///
123    /// // Cylinder has flat ends (sharp edges)
124    /// let cylinder = MassProperties::from_cylinder(density, half_height, radius);
125    ///
126    /// // Capsule has rounded ends (smooth)
127    /// let a = Vector::new(0.0, -half_height, 0.0);
128    /// let b = Vector::new(0.0, half_height, 0.0);
129    /// let capsule = MassProperties::from_capsule(density, a, b, radius);
130    ///
131    /// // Capsule has more mass due to hemispherical caps
132    /// println!("Cylinder mass: {:.2} kg", cylinder.mass());
133    /// println!("Capsule mass: {:.2} kg", capsule.mass());
134    /// assert!(capsule.mass() > cylinder.mass());
135    /// # }
136    /// ```
137    ///
138    /// # Use Cases
139    ///
140    /// - **Structural elements**: Columns, pillars, posts
141    /// - **Containers**: Cans, drums, barrels, tanks
142    /// - **Mechanical parts**: Shafts, pistons, rollers
143    /// - **Tree trunks**: Natural cylindrical objects
144    /// - **Wheels**: When viewed from the side (use with proper orientation)
145    ///
146    /// # Cylinder vs Capsule
147    ///
148    /// **Use Cylinder when**:
149    /// - Sharp edges are acceptable or desired
150    /// - Object is truly flat-ended (cans, pipes)
151    /// - Static/kinematic objects (don't need smooth rolling)
152    ///
153    /// **Use Capsule when**:
154    /// - Smooth collision response is needed
155    /// - Object needs to roll or slide smoothly
156    /// - Character controllers or dynamic objects
157    ///
158    /// # Common Mistakes
159    ///
160    /// - **Wrong axis**: Cylinders are aligned with Y-axis by default. Use
161    ///   `transform_by()` or create with proper orientation if you need X or Z alignment.
162    /// - **Half height confusion**: Total height is `2 × half_height`, not just `half_height`
163    ///
164    /// # Performance Note
165    ///
166    /// Cylinder collision detection is more expensive than capsules due to sharp edges,
167    /// but still reasonably efficient. For dynamic objects, prefer capsules.
168    #[cfg(feature = "dim3")]
169    pub fn from_cylinder(density: Real, half_height: Real, radius: Real) -> Self {
170        let (cyl_vol, cyl_unit_i) = Self::cylinder_y_volume_unit_inertia(half_height, radius);
171        let cyl_mass = cyl_vol * density;
172
173        Self::with_principal_inertia_frame(
174            Vector::ZERO,
175            cyl_mass,
176            cyl_unit_i * cyl_mass,
177            Rotation::IDENTITY,
178        )
179    }
180}