parry3d/shape/
round_shape.rs

1//! Rounded shapes are shapes with smoothed/rounded borders.
2//!
3//! This module provides the `RoundShape` wrapper that adds a border radius to any shape,
4//! effectively creating a "padded" or "rounded" version of the original shape.
5
6use crate::math::{Real, Vector};
7use crate::shape::SupportMap;
8
9#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
10#[cfg_attr(
11    feature = "rkyv",
12    derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)
13)]
14#[derive(Copy, Clone, Debug)]
15#[repr(C)]
16/// A shape with rounded borders.
17///
18/// # What is a Rounded Shape?
19///
20/// A `RoundShape` wraps an existing shape and adds a "border radius" around it. This creates
21/// a smooth, rounded version of the original shape by effectively expanding it outward by
22/// the border radius distance. Think of it as adding padding or a cushion around the shape.
23///
24/// The rounding is achieved by using Minkowski sum operations: any point on the surface of
25/// the rounded shape is computed by taking a point on the original shape's surface and moving
26/// it outward along the surface normal by the border radius distance.
27///
28/// # Common Use Cases
29///
30/// - **Creating softer collisions**: Rounded shapes can make collision detection more forgiving
31///   and realistic, as sharp corners and edges are smoothed out.
32/// - **Capsule-like shapes**: You can create capsule variations of any shape by adding a
33///   border radius (e.g., a rounded cuboid becomes similar to a capsule).
34/// - **Visual aesthetics**: Rounded shapes often look more pleasing and natural than sharp-edged
35///   shapes.
36/// - **Improved numerical stability**: Rounded shapes can sometimes be more numerically stable
37///   in collision detection algorithms since they avoid sharp corners.
38///
39/// # Examples
40///
41/// ## Creating a Rounded Cuboid
42///
43/// ```rust
44/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
45/// # #[cfg(feature = "f32")]
46/// use parry3d::shape::{RoundShape, Cuboid};
47/// # #[cfg(feature = "f64")]
48/// use parry3d_f64::shape::{RoundShape, Cuboid};
49/// use parry3d::math::Vector;
50///
51/// // Create a cuboid with half-extents of 1.0 in each direction
52/// let cuboid = Cuboid::new(Vector::new(1.0, 1.0, 1.0));
53///
54/// // Add a border radius of 0.2 to create a rounded cuboid
55/// let rounded_cuboid = RoundShape {
56///     inner_shape: cuboid,
57///     border_radius: 0.2,
58/// };
59///
60/// // The effective size is now 1.2 in each direction from the center
61/// // (1.0 from the cuboid + 0.2 from the border)
62/// assert_eq!(rounded_cuboid.inner_shape.half_extents.x, 1.0);
63/// assert_eq!(rounded_cuboid.border_radius, 0.2);
64/// # }
65/// ```
66///
67/// ## Creating a Rounded Triangle (2D)
68///
69/// ```rust
70/// # #[cfg(all(feature = "dim2", feature = "f32"))] {
71/// # #[cfg(feature = "f32")]
72/// use parry2d::shape::{RoundShape, Triangle};
73/// # #[cfg(feature = "f64")]
74/// use parry2d_f64::shape::{RoundShape, Triangle};
75/// use parry2d::math::Vector;
76///
77/// // Create a triangle
78/// let triangle = Triangle::new(
79///     Vector::ZERO,
80///     Vector::new(1.0, 0.0),
81///     Vector::new(0.0, 1.0),
82/// );
83///
84/// // Add rounding with a 0.1 border radius
85/// let rounded_triangle = RoundShape {
86///     inner_shape: triangle,
87///     border_radius: 0.1,
88/// };
89///
90/// // The rounded triangle will have smooth, curved edges instead of sharp corners
91/// assert_eq!(rounded_triangle.border_radius, 0.1);
92/// # }
93/// ```
94///
95/// ## Comparing Support Vectors
96///
97/// This example shows how the border radius affects the support point calculation:
98///
99/// ```rust
100/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
101/// # #[cfg(feature = "f32")]
102/// use parry3d::shape::{RoundShape, Cuboid, SupportMap};
103/// # #[cfg(feature = "f64")]
104/// use parry3d_f64::shape::{RoundShape, Cuboid, SupportMap};
105/// use parry3d::math::Vector;
106///
107/// let cuboid = Cuboid::new(Vector::splat(1.0));
108/// let rounded_cuboid = RoundShape {
109///     inner_shape: cuboid,
110///     border_radius: 0.5,
111/// };
112///
113/// // Query the support point in the direction (1, 1, 1)
114/// let direction = Vector::new(1.0, 1.0, 1.0);
115/// let support_point = rounded_cuboid.local_support_point(direction);
116///
117/// // The support point will be further out than the original cuboid's support point
118/// // due to the border radius
119/// let cuboid_support = cuboid.local_support_point(direction);
120///
121/// // The rounded shape extends further in all directions
122/// assert!(support_point.x > cuboid_support.x);
123/// assert!(support_point.y > cuboid_support.y);
124/// assert!(support_point.z > cuboid_support.z);
125/// # }
126/// ```
127///
128/// ## Using with Different Shape Types
129///
130/// `RoundShape` can wrap any shape that implements the `SupportMap` trait:
131///
132/// ```rust
133/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
134/// # #[cfg(feature = "f32")]
135/// use parry3d::shape::{RoundShape, Ball, Segment, SupportMap};
136/// # #[cfg(feature = "f64")]
137/// use parry3d_f64::shape::{RoundShape, Ball, Segment, SupportMap};
138/// use parry3d::math::Vector;
139///
140/// // Rounded ball (creates a slightly larger sphere)
141/// let ball = Ball::new(1.0);
142/// let rounded_ball = RoundShape {
143///     inner_shape: ball,
144///     border_radius: 0.1,
145/// };
146/// // Effective radius is now 1.1
147///
148/// // Rounded segment (creates a capsule)
149/// let segment = Segment::new(
150///     Vector::ZERO,
151///     Vector::new(0.0, 2.0, 0.0),
152/// );
153/// let rounded_segment = RoundShape {
154///     inner_shape: segment,
155///     border_radius: 0.5,
156/// };
157/// // This creates a capsule with radius 0.5
158/// # }
159/// ```
160///
161/// # Performance Considerations
162///
163/// - The computational cost of queries on a `RoundShape` is essentially the same as for the
164///   inner shape, plus a small constant overhead to apply the border radius.
165/// - `RoundShape` is most efficient when used with shapes that already implement `SupportMap`
166///   efficiently (like primitives: Ball, Cuboid, Capsule, etc.).
167/// - The struct is `Copy` when the inner shape is `Copy`, making it efficient to pass around.
168///
169/// # Technical Details
170///
171/// The `RoundShape` implements the `SupportMap` trait by computing the support point of the
172/// inner shape and then offsetting it by the border radius in the query direction. This is
173/// mathematically equivalent to computing the Minkowski sum of the inner shape with a ball
174/// of radius equal to the border radius.
175pub struct RoundShape<S> {
176    /// The shape being rounded.
177    ///
178    /// This is the original, "inner" shape before the border radius is applied.
179    /// The rounded shape's surface will be at a distance of `border_radius` from
180    /// this inner shape's surface.
181    pub inner_shape: S,
182
183    /// The radius of the rounded border.
184    ///
185    /// This value determines how much the shape is expanded outward. A larger border
186    /// radius creates a more "padded" shape. Must be non-negative (typically positive).
187    ///
188    /// For example, if `border_radius` is 0.5, every point on the original shape's
189    /// surface will be moved 0.5 units outward along its surface normal.
190    pub border_radius: Real,
191}
192
193impl<S: SupportMap> SupportMap for RoundShape<S> {
194    /// Computes the support point of the rounded shape in the given direction.
195    ///
196    /// The support point is the point on the shape's surface that is furthest in the
197    /// given direction. For a rounded shape, this is computed by:
198    /// 1. Finding the support point of the inner shape in the given direction
199    /// 2. Moving that point outward by `border_radius` units along the direction
200    ///
201    /// # Parameters
202    ///
203    /// * `dir` - The direction vector (will be normalized internally)
204    ///
205    /// # Returns
206    ///
207    /// The point on the rounded shape's surface that is furthest in the given direction.
208    ///
209    /// # Examples
210    ///
211    /// ```
212    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
213    /// # #[cfg(feature = "f32")]
214    /// use parry3d::shape::{RoundShape, Cuboid, SupportMap};
215    /// # #[cfg(feature = "f64")]
216    /// use parry3d_f64::shape::{RoundShape, Cuboid, SupportMap};
217    /// use parry3d::math::Vector;
218    ///
219    /// let cuboid = Cuboid::new(Vector::splat(1.0));
220    /// let rounded = RoundShape {
221    ///     inner_shape: cuboid,
222    ///     border_radius: 0.5,
223    /// };
224    ///
225    /// // Support point in the positive X direction
226    /// let dir = Vector::new(1.0, 0.0, 0.0);
227    /// let support = rounded.local_support_point(dir);
228    ///
229    /// // The X coordinate is the cuboid's half-extent plus the border radius
230    /// assert!((support.x - 1.5).abs() < 1e-6);
231    /// # }
232    /// ```
233    fn local_support_point(&self, dir: Vector) -> Vector {
234        self.local_support_point_toward(dir.normalize())
235    }
236
237    /// Computes the support point of the rounded shape toward the given unit direction.
238    ///
239    /// This is similar to `local_support_point` but takes a pre-normalized direction vector,
240    /// which can be more efficient when the direction is already normalized.
241    ///
242    /// The implementation adds the border radius offset to the inner shape's support point:
243    /// `support_point = inner_support_point + direction * border_radius`
244    ///
245    /// # Parameters
246    ///
247    /// * `dir` - A unit-length direction vector
248    ///
249    /// # Returns
250    ///
251    /// The point on the rounded shape's surface that is furthest in the given direction.
252    ///
253    /// # Examples
254    ///
255    /// ```
256    /// # #[cfg(all(feature = "dim2", feature = "f32"))] {
257    /// use parry2d::shape::{RoundShape, Ball, SupportMap};
258    /// use parry2d::math::Vector;
259    ///
260    /// let ball = Ball::new(1.0);
261    /// let rounded = RoundShape {
262    ///     inner_shape: ball,
263    ///     border_radius: 0.3,
264    /// };
265    ///
266    /// // Create a unit direction
267    /// let dir = Vector::new(1.0, 1.0).normalize();
268    /// let support = rounded.local_support_point_toward(dir);
269    ///
270    /// // The distance from origin should be ball radius + border radius
271    /// let distance = (support.x.powi(2) + support.y.powi(2)).sqrt();
272    /// assert!((distance - 1.3).abs() < 1e-6);
273    /// # }
274    /// ```
275    fn local_support_point_toward(&self, dir: Vector) -> Vector {
276        self.inner_shape.local_support_point_toward(dir) + dir * self.border_radius
277    }
278}
279
280/// A shape reference with rounded borders.
281///
282/// This is an internal helper struct that provides the same rounding functionality as
283/// `RoundShape`, but works with a borrowed reference to a shape instead of owning the shape.
284/// This is useful in contexts where you want to temporarily treat a shape reference as
285/// a rounded shape without creating a new allocation.
286///
287/// # Differences from `RoundShape`
288///
289/// - `RoundShapeRef` stores a reference (`&'a S`) instead of owning the shape
290/// - It's marked `pub(crate)`, meaning it's only used internally within the Parry library
291/// - The lifetime `'a` ensures the reference remains valid for the duration of use
292/// - Works with unsized types (`S: ?Sized`), allowing it to work with trait objects
293///
294/// # Internal Use
295///
296/// This struct is primarily used internally by Parry for implementing collision detection
297/// algorithms efficiently, where creating temporary rounded views of shapes is needed without
298/// the overhead of cloning or moving data.
299pub(crate) struct RoundShapeRef<'a, S: ?Sized> {
300    /// The shape being rounded (borrowed reference).
301    ///
302    /// This is a reference to the inner shape, allowing the rounded view to be created
303    /// without taking ownership or cloning the shape data.
304    pub inner_shape: &'a S,
305
306    /// The radius of the rounded border.
307    ///
308    /// Same semantics as in `RoundShape` - determines how much the shape is expanded outward.
309    pub border_radius: Real,
310}
311
312impl<S: ?Sized + SupportMap> SupportMap for RoundShapeRef<'_, S> {
313    /// Computes the support point of the rounded shape reference in the given direction.
314    ///
315    /// Behaves identically to `RoundShape::local_support_point`, but operates on a
316    /// borrowed shape reference.
317    ///
318    /// # Parameters
319    ///
320    /// * `dir` - The direction vector (will be normalized internally)
321    ///
322    /// # Returns
323    ///
324    /// The point on the rounded shape's surface that is furthest in the given direction.
325    fn local_support_point(&self, dir: Vector) -> Vector {
326        self.local_support_point_toward(dir.normalize())
327    }
328
329    /// Computes the support point of the rounded shape reference toward the given unit direction.
330    ///
331    /// Behaves identically to `RoundShape::local_support_point_toward`, but operates on a
332    /// borrowed shape reference. The implementation is the same: add the border radius offset
333    /// to the inner shape's support point.
334    ///
335    /// # Parameters
336    ///
337    /// * `dir` - A unit-length direction vector
338    ///
339    /// # Returns
340    ///
341    /// The point on the rounded shape's surface that is furthest in the given direction.
342    fn local_support_point_toward(&self, dir: Vector) -> Vector {
343        self.inner_shape.local_support_point_toward(dir) + dir * self.border_radius
344    }
345}