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