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(
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}