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