parry2d/shape/
ball.rs

1use crate::math::{Pose, Real, Vector};
2use crate::shape::SupportMap;
3
4/// A ball shape, also known as a sphere in 3D or a circle in 2D.
5///
6/// A ball is one of the simplest shapes in collision detection, defined by a single
7/// parameter: its radius. The center of the ball is always at the origin of its local
8/// coordinate system.
9///
10/// # Properties
11///
12/// - **In 2D**: Represents a circle (all points at distance `radius` from the center)
13/// - **In 3D**: Represents a sphere (all points at distance `radius` from the center)
14/// - **Convex**: Yes, balls are always convex shapes
15/// - **Support mapping**: Extremely efficient (constant time)
16///
17/// # Use Cases
18///
19/// Balls are ideal for:
20/// - Projectiles (bullets, cannonballs)
21/// - Spherical objects (planets, marbles, balls)
22/// - Bounding volumes for fast collision detection
23/// - Dynamic objects that need to roll
24///
25/// # Example
26///
27/// ```rust
28/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
29/// use parry3d::shape::Ball;
30/// use parry3d::math::Vector;
31///
32/// // Create a ball with radius 2.0
33/// let ball = Ball::new(2.0);
34/// assert_eq!(ball.radius, 2.0);
35/// # }
36/// ```
37#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
38#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
39#[cfg_attr(feature = "encase", derive(encase::ShaderType))]
40#[cfg_attr(
41    feature = "rkyv",
42    derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)
43)]
44#[derive(PartialEq, Debug, Copy, Clone)]
45#[repr(C)]
46pub struct Ball {
47    /// The radius of the ball.
48    ///
49    /// This must be a positive value. A radius of 0.0 is valid but represents
50    /// a degenerate ball (a single point).
51    pub radius: Real,
52}
53
54impl Ball {
55    /// Creates a new ball with the given radius.
56    ///
57    /// # Arguments
58    ///
59    /// * `radius` - The radius of the ball. Should be positive.
60    ///
61    /// # Example
62    ///
63    /// ```
64    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
65    /// use parry3d::shape::Ball;
66    ///
67    /// // Create a ball with radius 5.0
68    /// let ball = Ball::new(5.0);
69    /// assert_eq!(ball.radius, 5.0);
70    ///
71    /// // You can also create very small balls
72    /// let tiny_ball = Ball::new(0.001);
73    /// assert_eq!(tiny_ball.radius, 0.001);
74    /// # }
75    /// ```
76    #[inline]
77    pub fn new(radius: Real) -> Ball {
78        Ball { radius }
79    }
80
81    /// Computes a scaled version of this ball.
82    ///
83    /// **Uniform scaling** (same scale factor on all axes) produces another ball.
84    /// **Non-uniform scaling** (different scale factors) produces an ellipse, which
85    /// is approximated as a convex polygon.
86    ///
87    /// # Arguments
88    ///
89    /// * `scale` - The scaling factors for each axis (x, y in 2D)
90    /// * `nsubdivs` - Number of subdivisions for polygon approximation when scaling is non-uniform
91    ///
92    /// # Returns
93    ///
94    /// * `Some(Either::Left(Ball))` - If scaling is uniform, returns a scaled ball
95    /// * `Some(Either::Right(ConvexPolygon))` - If scaling is non-uniform, returns a polygon approximation
96    /// * `None` - If the approximation failed (e.g., zero scaling on an axis)
97    ///
98    /// # Example
99    ///
100    /// ```
101    /// # #[cfg(all(feature = "dim2", feature = "alloc", feature = "f32"))] {
102    /// use parry2d::shape::Ball;
103    /// use parry2d::math::Vector;
104    /// use either::Either;
105    ///
106    /// let ball = Ball::new(2.0);
107    ///
108    /// // Uniform scaling: produces another ball
109    /// let uniform_scale = Vector::new(3.0, 3.0);
110    /// if let Some(Either::Left(scaled_ball)) = ball.scaled(uniform_scale, 32) {
111    ///     assert_eq!(scaled_ball.radius, 6.0); // 2.0 * 3.0
112    /// }
113    ///
114    /// // Non-uniform scaling: produces a polygon (ellipse approximation)
115    /// let non_uniform_scale = Vector::new(2.0, 1.0);
116    /// if let Some(Either::Right(polygon)) = ball.scaled(non_uniform_scale, 32) {
117    ///     // The polygon approximates an ellipse with radii 4.0 and 2.0
118    ///     assert!(polygon.points().len() >= 32);
119    /// }
120    /// # }
121    /// # #[cfg(all(feature = "dim2", feature = "alloc", feature = "f64"))] {
122    /// use parry2d_f64::shape::Ball;
123    /// use parry2d_f64::math::Vector;
124    /// use either::Either;
125    ///
126    /// let ball = Ball::new(2.0);
127    ///
128    /// // Uniform scaling: produces another ball
129    /// let uniform_scale = Vector::new(3.0, 3.0);
130    /// if let Some(Either::Left(scaled_ball)) = ball.scaled(uniform_scale, 32) {
131    ///     assert_eq!(scaled_ball.radius, 6.0); // 2.0 * 3.0
132    /// }
133    ///
134    /// // Non-uniform scaling: produces a polygon (ellipse approximation)
135    /// let non_uniform_scale = Vector::new(2.0, 1.0);
136    /// if let Some(Either::Right(polygon)) = ball.scaled(non_uniform_scale, 32) {
137    ///     // The polygon approximates an ellipse with radii 4.0 and 2.0
138    ///     assert!(polygon.points().len() >= 32);
139    /// }
140    /// # }
141    /// ```
142    #[cfg(all(feature = "dim2", feature = "alloc"))]
143    #[inline]
144    pub fn scaled(
145        self,
146        scale: Vector,
147        nsubdivs: u32,
148    ) -> Option<either::Either<Self, super::ConvexPolygon>> {
149        if scale.x != scale.y {
150            // The scaled shape isn't a ball.
151            let mut vtx = self.to_polyline(nsubdivs);
152            vtx.iter_mut().for_each(|pt| *pt *= scale);
153            Some(either::Either::Right(
154                super::ConvexPolygon::from_convex_polyline(vtx)?,
155            ))
156        } else {
157            let uniform_scale = scale.x;
158            Some(either::Either::Left(Self::new(
159                self.radius * uniform_scale.abs(),
160            )))
161        }
162    }
163
164    /// Computes a scaled version of this ball.
165    ///
166    /// **Uniform scaling** (same scale factor on all axes) produces another ball.
167    /// **Non-uniform scaling** (different scale factors) produces an ellipsoid, which
168    /// is approximated as a convex polyhedron.
169    ///
170    /// # Arguments
171    ///
172    /// * `scale` - The scaling factors for each axis (x, y, z in 3D)
173    /// * `nsubdivs` - Number of subdivisions for polyhedron approximation when scaling is non-uniform
174    ///
175    /// # Returns
176    ///
177    /// * `Some(Either::Left(Ball))` - If scaling is uniform, returns a scaled ball
178    /// * `Some(Either::Right(ConvexPolyhedron))` - If scaling is non-uniform, returns a polyhedron approximation
179    /// * `None` - If the approximation failed (e.g., zero scaling on an axis)
180    ///
181    /// # Example
182    ///
183    /// ```
184    /// # #[cfg(all(feature = "dim3", feature = "f32", feature = "alloc"))] {
185    /// use parry3d::shape::Ball;
186    /// use parry3d::math::Vector;
187    /// use either::Either;
188    ///
189    /// let ball = Ball::new(5.0);
190    ///
191    /// // Uniform scaling: produces another ball
192    /// let uniform_scale = Vector::new(2.0, 2.0, 2.0);
193    /// if let Some(Either::Left(scaled_ball)) = ball.scaled(uniform_scale, 10) {
194    ///     assert_eq!(scaled_ball.radius, 10.0); // 5.0 * 2.0
195    /// }
196    ///
197    /// // Non-uniform scaling: produces a polyhedron (ellipsoid approximation)
198    /// let non_uniform_scale = Vector::new(2.0, 1.0, 1.5);
199    /// if let Some(Either::Right(polyhedron)) = ball.scaled(non_uniform_scale, 10) {
200    ///     // The polyhedron approximates an ellipsoid
201    ///     assert!(polyhedron.points().len() > 0);
202    /// }
203    /// # }
204    /// ```
205    #[cfg(all(feature = "dim3", feature = "alloc"))]
206    #[inline]
207    pub fn scaled(
208        self,
209        scale: Vector,
210        nsubdivs: u32,
211    ) -> Option<either::Either<Self, super::ConvexPolyhedron>> {
212        if scale.x != scale.y || scale.x != scale.z || scale.y != scale.z {
213            // The scaled shape isn't a ball.
214            let (mut vtx, idx) = self.to_trimesh(nsubdivs, nsubdivs);
215            vtx.iter_mut().for_each(|pt| *pt *= scale);
216            Some(either::Either::Right(
217                super::ConvexPolyhedron::from_convex_mesh(vtx, &idx)?,
218            ))
219        } else {
220            let uniform_scale = scale.x;
221            Some(either::Either::Left(Self::new(
222                self.radius * uniform_scale.abs(),
223            )))
224        }
225    }
226}
227
228impl SupportMap for Ball {
229    #[inline]
230    fn support_point(&self, m: &Pose, dir: Vector) -> Vector {
231        self.support_point_toward(m, dir.normalize())
232    }
233
234    #[inline]
235    fn support_point_toward(&self, m: &Pose, dir: Vector) -> Vector {
236        m.translation + dir * self.radius
237    }
238
239    #[inline]
240    fn local_support_point(&self, dir: Vector) -> Vector {
241        self.local_support_point_toward(dir.normalize())
242    }
243
244    #[inline]
245    fn local_support_point_toward(&self, dir: Vector) -> Vector {
246        dir * self.radius
247    }
248}