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}