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}