parry3d_f64/shape/cone.rs
1//! Support mapping based Cone shape.
2
3use crate::math::{Real, Vector};
4use crate::shape::SupportMap;
5
6#[cfg(feature = "alloc")]
7use either::Either;
8
9/// A 3D cone shape with apex pointing upward along the Y axis.
10///
11/// A cone is a shape that tapers from a circular base to a point (apex). In Parry,
12/// cones are always aligned with the Y axis, with the base at y = -half_height and
13/// the apex at y = +half_height.
14///
15/// # Structure
16///
17/// - **Axis**: Always aligned with Y axis (apex points up)
18/// - **Base**: Circular base at y = -half_height with the given radius
19/// - **Apex**: Vector at y = +half_height
20/// - **Total height**: `2 * half_height`
21///
22/// # Properties
23///
24/// - **3D only**: Only available with the `dim3` feature
25/// - **Convex**: Yes, cones are convex shapes
26/// - **Apex**: Sharp point at the top
27/// - **Flat base**: Circular base (not rounded)
28/// - **Sharp edge**: Rim where cone surface meets the base
29///
30/// # Coordinate System
31///
32/// The cone is centered at the origin with:
33/// - Base center at `(0, -half_height, 0)`
34/// - Apex at `(0, half_height, 0)`
35/// - Circular base in the XZ plane
36///
37/// # Use Cases
38///
39/// - Projectile nose cones
40/// - Traffic cones and markers
41/// - Funnel shapes
42/// - Spotlight or vision cone representations
43/// - Conical collision bounds
44///
45/// # Example
46///
47/// ```rust
48/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
49/// use parry3d::shape::Cone;
50///
51/// // Create a cone: base radius 3.0, total height 8.0
52/// let cone = Cone::new(4.0, 3.0);
53///
54/// assert_eq!(cone.half_height, 4.0);
55/// assert_eq!(cone.radius, 3.0);
56///
57/// // The cone:
58/// // - Base at y = -4.0 with radius 3.0
59/// // - Apex at y = +4.0 (sharp point)
60/// // - Total height = 8.0
61/// # }
62/// ```
63#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
64#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
65#[cfg_attr(
66 feature = "rkyv",
67 derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)
68)]
69#[derive(PartialEq, Debug, Copy, Clone)]
70#[repr(C)]
71pub struct Cone {
72 /// Half the total height of the cone.
73 ///
74 /// The cone extends from y = -half_height (base center) to
75 /// y = +half_height (apex). Must be positive.
76 pub half_height: Real,
77
78 /// The radius of the circular base.
79 ///
80 /// The base is a circle in the XZ plane at y = -half_height.
81 /// Must be positive.
82 pub radius: Real,
83}
84
85impl Cone {
86 /// Creates a new cone with apex pointing upward along the Y axis.
87 ///
88 /// # Arguments
89 ///
90 /// * `half_height` - Half the total height (apex to base center distance / 2)
91 /// * `radius` - The radius of the circular base
92 ///
93 /// # Example
94 ///
95 /// ```
96 /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
97 /// use parry3d::shape::Cone;
98 ///
99 /// // Create a cone with height 6.0 and base radius 2.0
100 /// let cone = Cone::new(3.0, 2.0);
101 ///
102 /// assert_eq!(cone.half_height, 3.0);
103 /// assert_eq!(cone.radius, 2.0);
104 ///
105 /// // The cone structure:
106 /// // - Apex at (0, 3, 0) - the top point
107 /// // - Base center at (0, -3, 0)
108 /// // - Base radius 2.0 in the XZ plane
109 /// // - Total height from apex to base = 6.0
110 /// # }
111 /// ```
112 pub fn new(half_height: Real, radius: Real) -> Cone {
113 Cone {
114 half_height,
115 radius,
116 }
117 }
118
119 /// Computes a scaled version of this cone.
120 ///
121 /// If the scaling factor is non-uniform, then it can’t be represented as
122 /// cone. Instead, a convex polyhedral approximation (with `nsubdivs`
123 /// subdivisions) is returned. Returns `None` if that approximation had degenerate
124 /// normals (for example if the scaling factor along one axis is zero).
125 #[cfg(feature = "alloc")]
126 #[inline]
127 pub fn scaled(
128 self,
129 scale: Vector,
130 nsubdivs: u32,
131 ) -> Option<Either<Self, super::ConvexPolyhedron>> {
132 // NOTE: if the y scale is negative, the result cone points downwards,
133 // which can’t be represented with this Cone (without a transform).
134 if scale.x != scale.z || scale.y < 0.0 {
135 // The scaled shape isn't a cone.
136 let (mut vtx, idx) = self.to_trimesh(nsubdivs);
137 vtx.iter_mut().for_each(|pt| *pt *= scale);
138 Some(Either::Right(super::ConvexPolyhedron::from_convex_mesh(
139 vtx, &idx,
140 )?))
141 } else {
142 Some(Either::Left(Self::new(
143 self.half_height * scale.y,
144 self.radius * scale.x,
145 )))
146 }
147 }
148}
149
150impl SupportMap for Cone {
151 #[inline]
152 fn local_support_point(&self, dir: Vector) -> Vector {
153 let mut vres = dir;
154
155 vres[1] = 0.0;
156 let (mut vres, length) = vres.normalize_and_length();
157
158 if length == 0.0 {
159 vres = Vector::ZERO;
160 vres[1] = self.half_height.copysign(dir[1]);
161 } else {
162 vres *= self.radius;
163 vres[1] = -self.half_height;
164
165 if dir.dot(vres) < dir[1] * self.half_height {
166 vres = Vector::ZERO;
167 vres[1] = self.half_height
168 }
169 }
170
171 vres
172 }
173}