parry3d_f64/mass_properties/
mass_properties_cone.rs

1use crate::mass_properties::MassProperties;
2use crate::math::{PrincipalAngularInertia, Real, RealField, Rotation, Vector};
3
4impl MassProperties {
5    pub(crate) fn cone_y_volume_unit_inertia(
6        half_height: Real,
7        radius: Real,
8    ) -> (Real, PrincipalAngularInertia) {
9        let volume = radius * radius * Real::pi() * half_height * 2.0 / 3.0;
10        let sq_radius = radius * radius;
11        let sq_height = half_height * half_height * 4.0;
12        let off_principal = sq_radius * 3.0 / 20.0 + sq_height * 3.0 / 80.0;
13        let principal = sq_radius * 3.0 / 10.0;
14
15        (volume, Vector::new(off_principal, principal, off_principal))
16    }
17
18    /// Computes the mass properties of a cone (3D only).
19    ///
20    /// A cone is a 3D shape with a circular base and a point (apex) at the top, aligned
21    /// along the Y-axis. The base is at y = -half_height, the apex is at y = +half_height,
22    /// and the center of the base is at the origin.
23    ///
24    /// # Arguments
25    ///
26    /// * `density` - The material density in kg/m³ (mass per unit volume)
27    /// * `half_height` - Half the total height of the cone (center to apex/base)
28    /// * `radius` - The radius of the circular base
29    ///
30    /// # Returns
31    ///
32    /// A `MassProperties` struct containing:
33    /// - **mass**: Total mass calculated from volume and density
34    /// - **local_com**: Center of mass at `(0, -half_height/2, 0)` - shifted toward the base
35    /// - **inv_principal_inertia**: Inverse angular inertia (varies by axis)
36    /// - **principal_inertia_local_frame**: Identity rotation (aligned with Y-axis)
37    ///
38    /// # Physics Background
39    ///
40    /// Cones have unique mass distribution due to tapering shape:
41    /// - Volume = (1/3) × π × radius² × height
42    /// - Center of mass is NOT at the geometric center
43    /// - Center of mass is located 1/4 of the height from the base (toward the base)
44    /// - The wider base contains more mass than the narrow top
45    /// - Angular inertia around Y-axis (spinning): I_y = (3/10) × mass × radius²
46    /// - Angular inertia around X/Z axes: includes both base radius and height terms
47    ///
48    /// # Example - Traffic Cone
49    ///
50    /// ```
51    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
52    /// use parry3d::mass_properties::MassProperties;
53    /// use parry3d::math::Vector;
54    ///
55    /// // Standard traffic cone: 70cm tall, 30cm base diameter
56    /// // Made of flexible plastic, density ~950 kg/m³
57    /// let half_height = 0.35; // 70cm / 2 = 35cm
58    /// let radius = 0.15;      // 30cm / 2 = 15cm
59    /// let density = 950.0;
60    ///
61    /// let cone_props = MassProperties::from_cone(density, half_height, radius);
62    ///
63    /// let mass = cone_props.mass();
64    /// println!("Traffic cone mass: {:.3} kg", mass); // About 1-2 kg
65    ///
66    /// // Center of mass is shifted toward the base (negative Y)
67    /// let com_y = cone_props.local_com.y;
68    /// println!("Center of mass Y: {:.3} m", com_y);
69    /// assert!(com_y < 0.0, "COM should be below origin, toward the base");
70    /// assert!((com_y - (-half_height / 2.0)).abs() < 0.01);
71    /// # }
72    /// ```
73    ///
74    /// # Example - Ice Cream Cone
75    ///
76    /// ```
77    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
78    /// use parry3d::mass_properties::MassProperties;
79    ///
80    /// // Small ice cream cone (wafer): 12cm tall, 5cm diameter
81    /// let half_height = 0.06; // 6cm
82    /// let radius = 0.025;     // 2.5cm
83    /// let density = 400.0;    // Wafer is light and porous
84    ///
85    /// let wafer_props = MassProperties::from_cone(density, half_height, radius);
86    ///
87    /// // Volume = (1/3) × π × (0.025)² × 0.12 ≈ 0.0000785 m³
88    /// let mass = wafer_props.mass();
89    /// println!("Wafer mass: {:.1} grams", mass * 1000.0); // About 30 grams
90    /// # }
91    /// ```
92    ///
93    /// # Example - Center of Mass Position
94    ///
95    /// ```
96    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
97    /// use parry3d::mass_properties::MassProperties;
98    ///
99    /// // Demonstrate that COM is 1/4 height from base
100    /// let half_height = 2.0;
101    /// let radius = 1.0;
102    /// let density = 1000.0;
103    ///
104    /// let cone_props = MassProperties::from_cone(density, half_height, radius);
105    ///
106    /// // Base is at y = -2.0, apex is at y = +2.0
107    /// // COM should be at y = -2.0 + (4.0 / 4.0) = -1.0
108    /// // Or equivalently: y = -half_height / 2 = -1.0
109    /// let expected_com_y = -half_height / 2.0;
110    /// assert!((cone_props.local_com.y - expected_com_y).abs() < 0.001);
111    /// println!("Base at: {}", -half_height);
112    /// println!("Apex at: {}", half_height);
113    /// println!("COM at: {:.3}", cone_props.local_com.y);
114    /// # }
115    /// ```
116    ///
117    /// # Example - Cone vs Cylinder Comparison
118    ///
119    /// ```
120    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
121    /// use parry3d::mass_properties::MassProperties;
122    ///
123    /// let half_height = 1.0;
124    /// let radius = 0.5;
125    /// let density = 1000.0;
126    ///
127    /// let cone = MassProperties::from_cone(density, half_height, radius);
128    /// let cylinder = MassProperties::from_cylinder(density, half_height, radius);
129    ///
130    /// // Cone has 1/3 the volume of a cylinder with same dimensions
131    /// println!("Cone mass: {:.2} kg", cone.mass());
132    /// println!("Cylinder mass: {:.2} kg", cylinder.mass());
133    /// assert!((cylinder.mass() / cone.mass() - 3.0).abs() < 0.1);
134    ///
135    /// // Cone's COM is offset, cylinder's is at origin
136    /// println!("Cone COM Y: {:.3}", cone.local_com.y);
137    /// println!("Cylinder COM Y: {:.3}", cylinder.local_com.y);
138    /// # }
139    /// ```
140    ///
141    /// # Use Cases
142    ///
143    /// - **Traffic cones**: Road safety markers
144    /// - **Funnels**: Pouring devices
145    /// - **Party hats**: Conical decorations
146    /// - **Volcanic mountains**: Natural cone-shaped terrain
147    /// - **Rocket noses**: Aerodynamic cone shapes
148    /// - **Drills and bits**: Conical tool tips
149    ///
150    /// # Important Notes
151    ///
152    /// - **Orientation**: Cone points upward (+Y), base is downward (-Y)
153    /// - **Asymmetric COM**: Unlike cylinder or ball, center of mass is NOT at origin
154    /// - **Volume**: Remember it's only 1/3 of an equivalent cylinder's volume
155    /// - **Base position**: The base center is at the origin, not the geometric center
156    ///
157    /// # Common Mistakes
158    ///
159    /// - **Expecting COM at origin**: The center of mass is shifted toward the base by
160    ///   `half_height/2` in the negative Y direction
161    /// - **Confusing orientation**: The apex points in +Y direction, base faces -Y
162    /// - **Volume estimation**: Cone volume is much smaller than you might expect
163    ///   (only 1/3 of a cylinder with the same dimensions)
164    ///
165    /// # Performance Note
166    ///
167    /// Cone collision detection is moderately expensive due to the tapered shape and
168    /// curved surface. For simpler simulations, consider using a cylinder or compound
169    /// shape approximation.
170    pub fn from_cone(density: Real, half_height: Real, radius: Real) -> Self {
171        let (cyl_vol, cyl_unit_i) = Self::cone_y_volume_unit_inertia(half_height, radius);
172        let cyl_mass = cyl_vol * density;
173
174        Self::with_principal_inertia_frame(
175            Vector::new(0.0, -half_height / 2.0, 0.0),
176            cyl_mass,
177            cyl_unit_i * cyl_mass,
178            Rotation::IDENTITY,
179        )
180    }
181}