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