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}