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}