Skip to main content

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}