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}