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}