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}