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}