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}