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}