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