parry3d/shape/capsule.rs
1use crate::math::{Isometry, Point, Real, Rotation, Vector};
2use crate::shape::{Segment, SupportMap};
3use na::Unit;
4
5#[cfg(feature = "alloc")]
6use either::Either;
7
8#[cfg(feature = "rkyv")]
9use rkyv::{bytecheck, CheckBytes};
10
11#[derive(Copy, Clone, Debug)]
12#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
13#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
14#[cfg_attr(
15    feature = "rkyv",
16    derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, CheckBytes),
17    archive(as = "Self")
18)]
19#[repr(C)]
20/// A capsule shape, also known as a pill or capped cylinder.
21///
22/// A capsule is defined by a line segment (its central axis) and a radius. It can be
23/// visualized as a cylinder with hemispherical (2D: semicircular) caps on both ends.
24/// This makes it perfect for representing elongated round objects.
25///
26/// # Structure
27///
28/// - **Segment**: The central axis from point `a` to point `b`
29/// - **Radius**: The thickness around the segment
30/// - **In 2D**: Looks like a rounded rectangle or "stadium" shape
31/// - **In 3D**: Looks like a cylinder with spherical caps (a pill)
32///
33/// # Properties
34///
35/// - **Convex**: Yes, capsules are always convex
36/// - **Smooth**: Completely smooth surface (no edges or corners)
37/// - **Support mapping**: Efficient (constant time)
38/// - **Rolling**: Excellent for objects that need to roll smoothly
39///
40/// # Use Cases
41///
42/// Capsules are ideal for:
43/// - Characters and humanoid figures (torso, limbs)
44/// - Pills, medicine capsules
45/// - Elongated projectiles (missiles, torpedoes)
46/// - Smooth rolling objects
47/// - Any object that's "cylinder-like" but needs smooth collision at ends
48///
49/// # Advantages Over Cylinders
50///
51/// - **No sharp edges**: Smoother collision response
52/// - **Better for characters**: More natural movement and rotation
53/// - **Simpler collision detection**: Easier to compute contacts than cylinders
54///
55/// # Example
56///
57/// ```rust
58/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
59/// use parry3d::shape::Capsule;
60/// use nalgebra::Point3;
61///
62/// // Create a vertical capsule (aligned with Y axis)
63/// // Half-height of 2.0 means the segment is 4.0 units long
64/// let capsule = Capsule::new_y(2.0, 0.5);
65/// assert_eq!(capsule.radius, 0.5);
66/// assert_eq!(capsule.height(), 4.0);
67///
68/// // Create a custom capsule between two points
69/// let a = Point3::origin();
70/// let b = Point3::new(3.0, 4.0, 0.0);
71/// let custom = Capsule::new(a, b, 1.0);
72/// assert_eq!(custom.height(), 5.0); // Distance from a to b
73/// # }
74/// ```
75pub struct Capsule {
76    /// The line segment forming the capsule's central axis.
77    ///
78    /// The capsule extends from `segment.a` to `segment.b`, with hemispherical
79    /// caps centered at each endpoint.
80    pub segment: Segment,
81
82    /// The radius of the capsule.
83    ///
84    /// This is the distance from the central axis to the surface. Must be positive.
85    /// The total "thickness" of the capsule is `2 * radius`.
86    pub radius: Real,
87}
88
89impl Capsule {
90    /// Creates a new capsule aligned with the X axis.
91    ///
92    /// The capsule is centered at the origin and extends along the X axis.
93    ///
94    /// # Arguments
95    ///
96    /// * `half_height` - Half the length of the central segment (total length = `2 * half_height`)
97    /// * `radius` - The radius of the capsule
98    ///
99    /// # Example
100    ///
101    /// ```
102    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
103    /// use parry3d::shape::Capsule;
104    ///
105    /// // Create a capsule extending 6 units along X axis (3 units in each direction)
106    /// // with radius 0.5
107    /// let capsule = Capsule::new_x(3.0, 0.5);
108    /// assert_eq!(capsule.height(), 6.0);
109    /// assert_eq!(capsule.radius, 0.5);
110    ///
111    /// // The center is at the origin
112    /// let center = capsule.center();
113    /// assert!(center.coords.norm() < 1e-6);
114    /// # }
115    /// ```
116    pub fn new_x(half_height: Real, radius: Real) -> Self {
117        let b = Point::from(Vector::x() * half_height);
118        Self::new(-b, b, radius)
119    }
120
121    /// Creates a new capsule aligned with the Y axis.
122    ///
123    /// The capsule is centered at the origin and extends along the Y axis.
124    /// This is the most common orientation for character capsules (standing upright).
125    ///
126    /// # Arguments
127    ///
128    /// * `half_height` - Half the length of the central segment
129    /// * `radius` - The radius of the capsule
130    ///
131    /// # Example
132    ///
133    /// ```
134    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
135    /// use parry3d::shape::Capsule;
136    ///
137    /// // Create a typical character capsule: 2 units tall with 0.3 radius
138    /// let character = Capsule::new_y(1.0, 0.3);
139    /// assert_eq!(character.height(), 2.0);
140    /// assert_eq!(character.radius, 0.3);
141    ///
142    /// // Total height including the spherical caps: 2.0 + 2 * 0.3 = 2.6
143    /// # }
144    /// ```
145    pub fn new_y(half_height: Real, radius: Real) -> Self {
146        let b = Point::from(Vector::y() * half_height);
147        Self::new(-b, b, radius)
148    }
149
150    /// Creates a new capsule aligned with the Z axis.
151    ///
152    /// The capsule is centered at the origin and extends along the Z axis.
153    ///
154    /// # Arguments
155    ///
156    /// * `half_height` - Half the length of the central segment
157    /// * `radius` - The radius of the capsule
158    ///
159    /// # Example
160    ///
161    /// ```
162    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
163    /// use parry3d::shape::Capsule;
164    ///
165    /// // Create a capsule for a torpedo extending along Z axis
166    /// let torpedo = Capsule::new_z(5.0, 0.4);
167    /// assert_eq!(torpedo.height(), 10.0);
168    /// assert_eq!(torpedo.radius, 0.4);
169    /// # }
170    /// ```
171    #[cfg(feature = "dim3")]
172    pub fn new_z(half_height: Real, radius: Real) -> Self {
173        let b = Point::from(Vector::z() * half_height);
174        Self::new(-b, b, radius)
175    }
176
177    /// Creates a new capsule with custom endpoints and radius.
178    ///
179    /// This is the most flexible constructor, allowing you to create a capsule
180    /// with any orientation and position.
181    ///
182    /// # Arguments
183    ///
184    /// * `a` - The first endpoint of the central segment
185    /// * `b` - The second endpoint of the central segment
186    /// * `radius` - The radius of the capsule
187    ///
188    /// # Example
189    ///
190    /// ```
191    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
192    /// use parry3d::shape::Capsule;
193    /// use nalgebra::Point3;
194    ///
195    /// // Create a diagonal capsule
196    /// let a = Point3::origin();
197    /// let b = Point3::new(3.0, 4.0, 0.0);
198    /// let capsule = Capsule::new(a, b, 0.5);
199    ///
200    /// // Height is the distance between a and b
201    /// assert_eq!(capsule.height(), 5.0); // 3-4-5 triangle
202    ///
203    /// // Center is the midpoint
204    /// let center = capsule.center();
205    /// assert_eq!(center, Point3::new(1.5, 2.0, 0.0));
206    /// # }
207    /// ```
208    pub fn new(a: Point<Real>, b: Point<Real>, radius: Real) -> Self {
209        let segment = Segment::new(a, b);
210        Self { segment, radius }
211    }
212
213    /// Returns the length of the capsule's central segment.
214    ///
215    /// This is the distance between the two endpoints, **not** including the
216    /// hemispherical caps. The total length of the capsule including caps is
217    /// `height() + 2 * radius`.
218    ///
219    /// # Example
220    ///
221    /// ```
222    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
223    /// use parry3d::shape::Capsule;
224    ///
225    /// let capsule = Capsule::new_y(3.0, 0.5);
226    ///
227    /// // Height of the central segment
228    /// assert_eq!(capsule.height(), 6.0);
229    ///
230    /// // Total length including spherical caps
231    /// let total_length = capsule.height() + 2.0 * capsule.radius;
232    /// assert_eq!(total_length, 7.0);
233    /// # }
234    /// ```
235    pub fn height(&self) -> Real {
236        (self.segment.b - self.segment.a).norm()
237    }
238
239    /// Returns half the length of the capsule's central segment.
240    ///
241    /// This is equivalent to `height() / 2.0`.
242    ///
243    /// # Example
244    ///
245    /// ```
246    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
247    /// use parry3d::shape::Capsule;
248    ///
249    /// let capsule = Capsule::new_y(3.0, 0.5);
250    /// assert_eq!(capsule.half_height(), 3.0);
251    /// assert_eq!(capsule.half_height(), capsule.height() / 2.0);
252    /// # }
253    /// ```
254    pub fn half_height(&self) -> Real {
255        self.height() / 2.0
256    }
257
258    /// Returns the center point of the capsule.
259    ///
260    /// This is the midpoint between the two endpoints of the central segment.
261    ///
262    /// # Example
263    ///
264    /// ```
265    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
266    /// use parry3d::shape::Capsule;
267    /// use nalgebra::Point3;
268    ///
269    /// let a = Point3::new(-2.0, 0.0, 0.0);
270    /// let b = Point3::new(4.0, 0.0, 0.0);
271    /// let capsule = Capsule::new(a, b, 1.0);
272    ///
273    /// let center = capsule.center();
274    /// assert_eq!(center, Point3::new(1.0, 0.0, 0.0));
275    /// # }
276    /// ```
277    pub fn center(&self) -> Point<Real> {
278        na::center(&self.segment.a, &self.segment.b)
279    }
280
281    /// Creates a new capsule equal to `self` with all its endpoints transformed by `pos`.
282    ///
283    /// This applies a rigid transformation (translation and rotation) to the capsule.
284    ///
285    /// # Arguments
286    ///
287    /// * `pos` - The isometry (rigid transformation) to apply
288    ///
289    /// # Example
290    ///
291    /// ```
292    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
293    /// use parry3d::shape::Capsule;
294    /// use nalgebra::{Isometry3, Vector3};
295    ///
296    /// let capsule = Capsule::new_y(1.0, 0.5);
297    ///
298    /// // Translate the capsule 5 units along X axis
299    /// let transform = Isometry3::translation(5.0, 0.0, 0.0);
300    /// let transformed = capsule.transform_by(&transform);
301    ///
302    /// // Center moved by 5 units
303    /// assert_eq!(transformed.center().x, 5.0);
304    /// // Radius unchanged
305    /// assert_eq!(transformed.radius, 0.5);
306    /// # }
307    /// ```
308    pub fn transform_by(&self, pos: &Isometry<Real>) -> Self {
309        Self::new(pos * self.segment.a, pos * self.segment.b, self.radius)
310    }
311
312    /// The transformation such that `t * Y` is collinear with `b - a` and `t * origin` equals
313    /// the capsule's center.
314    pub fn canonical_transform(&self) -> Isometry<Real> {
315        let tra = self.center().coords;
316        let rot = self.rotation_wrt_y();
317        Isometry::from_parts(tra.into(), rot)
318    }
319
320    /// The rotation `r` such that `r * Y` is collinear with `b - a`.
321    pub fn rotation_wrt_y(&self) -> Rotation<Real> {
322        let mut dir = self.segment.b - self.segment.a;
323        if dir.y < 0.0 {
324            dir = -dir;
325        }
326
327        #[cfg(feature = "dim2")]
328        {
329            Rotation::rotation_between(&Vector::y(), &dir)
330        }
331
332        #[cfg(feature = "dim3")]
333        {
334            Rotation::rotation_between(&Vector::y(), &dir).unwrap_or(Rotation::identity())
335        }
336    }
337
338    /// The transform `t` such that `t * Y` is collinear with `b - a` and such that `t * origin = (b + a) / 2.0`.
339    pub fn transform_wrt_y(&self) -> Isometry<Real> {
340        let rot = self.rotation_wrt_y();
341        Isometry::from_parts(self.center().coords.into(), rot)
342    }
343
344    /// Computes a scaled version of this capsule.
345    ///
346    /// If the scaling factor is non-uniform, then it can’t be represented as
347    /// capsule. Instead, a convex polygon approximation (with `nsubdivs`
348    /// subdivisions) is returned. Returns `None` if that approximation had degenerate
349    /// normals (for example if the scaling factor along one axis is zero).
350    #[cfg(all(feature = "dim2", feature = "alloc"))]
351    pub fn scaled(
352        self,
353        scale: &Vector<Real>,
354        nsubdivs: u32,
355    ) -> Option<Either<Self, super::ConvexPolygon>> {
356        if scale.x != scale.y {
357            // The scaled shape is not a capsule.
358            let mut vtx = self.to_polyline(nsubdivs);
359            vtx.iter_mut()
360                .for_each(|pt| pt.coords = pt.coords.component_mul(scale));
361            Some(Either::Right(super::ConvexPolygon::from_convex_polyline(
362                vtx,
363            )?))
364        } else {
365            let uniform_scale = scale.x;
366            Some(Either::Left(Self::new(
367                self.segment.a * uniform_scale,
368                self.segment.b * uniform_scale,
369                self.radius * uniform_scale.abs(),
370            )))
371        }
372    }
373
374    /// Computes a scaled version of this capsule.
375    ///
376    /// If the scaling factor is non-uniform, then it can’t be represented as
377    /// capsule. Instead, a convex polygon approximation (with `nsubdivs`
378    /// subdivisions) is returned. Returns `None` if that approximation had degenerate
379    /// normals (for example if the scaling factor along one axis is zero).
380    #[cfg(all(feature = "dim3", feature = "alloc"))]
381    pub fn scaled(
382        self,
383        scale: &Vector<Real>,
384        nsubdivs: u32,
385    ) -> Option<Either<Self, super::ConvexPolyhedron>> {
386        if scale.x != scale.y || scale.x != scale.z || scale.y != scale.z {
387            // The scaled shape is not a capsule.
388            let (mut vtx, idx) = self.to_trimesh(nsubdivs, nsubdivs);
389            vtx.iter_mut()
390                .for_each(|pt| pt.coords = pt.coords.component_mul(scale));
391            Some(Either::Right(super::ConvexPolyhedron::from_convex_mesh(
392                vtx, &idx,
393            )?))
394        } else {
395            let uniform_scale = scale.x;
396            Some(Either::Left(Self::new(
397                self.segment.a * uniform_scale,
398                self.segment.b * uniform_scale,
399                self.radius * uniform_scale.abs(),
400            )))
401        }
402    }
403}
404
405impl SupportMap for Capsule {
406    fn local_support_point(&self, dir: &Vector<Real>) -> Point<Real> {
407        let dir = Unit::try_new(*dir, 0.0).unwrap_or(Vector::y_axis());
408        self.local_support_point_toward(&dir)
409    }
410
411    fn local_support_point_toward(&self, dir: &Unit<Vector<Real>>) -> Point<Real> {
412        if dir.dot(&self.segment.a.coords) > dir.dot(&self.segment.b.coords) {
413            self.segment.a + **dir * self.radius
414        } else {
415            self.segment.b + **dir * self.radius
416        }
417    }
418}