parry3d/mass_properties/
mass_properties_cone.rs

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