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