parry2d/shape/
ball.rs

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