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