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