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}