Skip to main content

collide_sphere/
lib.rs

1#![deny(missing_docs)]
2
3//! Sphere collider implementation for the `collide` crate.
4//!
5//! A sphere is the simplest collider: just a center point and a radius.
6//! Collision detection reduces to a single distance check, making it
7//! the most efficient collider type.
8//!
9//! When using a `CollisionManager` with only spheres, you get a dedicated
10//! layer with optimal performance — no unnecessary branching or memory
11//! overhead compared to more general collider types like capsules or convex shapes.
12//!
13//! Enable the `ray` feature to get `Collider<Ray<V>>` for `Sphere<V>`.
14
15use collide::{BoundingVolume, Collider, CollisionInfo, Transform, Transformable};
16use inner_space::{InnerSpace, VectorSpace};
17use scalars::{One, Sqrt, Zero};
18
19/// A sphere collider defined by a center point and radius.
20#[derive(Copy, Clone, Debug)]
21pub struct Sphere<V: VectorSpace> {
22    /// The center of the sphere.
23    pub center: V,
24    /// The radius of the sphere.
25    pub radius: V::Scalar,
26}
27
28impl<V: Copy + InnerSpace> Sphere<V> {
29    /// Creates a new sphere collider.
30    pub fn new(center: V, radius: V::Scalar) -> Self {
31        Self { center, radius }
32    }
33
34    /// Creates a sphere with zero radius, representing a single point.
35    pub fn point(center: V) -> Self {
36        Self {
37            center,
38            radius: V::Scalar::zero(),
39        }
40    }
41}
42
43impl<V: Copy + InnerSpace> Collider for Sphere<V> {
44    type Vector = V;
45
46    fn check_collision(&self, other: &Self) -> bool {
47        let displacement = other.center - self.center;
48        let combined_radius = self.radius + other.radius;
49        displacement.magnitude2() <= combined_radius * combined_radius
50    }
51
52    fn collision_info(&self, other: &Self) -> Option<CollisionInfo<V>> {
53        let displacement = other.center - self.center;
54        let distance_squared = displacement.magnitude2();
55        let combined_radius = self.radius + other.radius;
56
57        if distance_squared > combined_radius * combined_radius {
58            return None;
59        }
60
61        let distance = distance_squared.sqrt();
62        let direction = displacement / distance;
63
64        Some(CollisionInfo {
65            self_contact: self.center + direction * self.radius,
66            other_contact: other.center - direction * other.radius,
67            vector: direction * (distance - combined_radius),
68        })
69    }
70}
71
72impl<V: Copy + InnerSpace> BoundingVolume for Sphere<V> {
73    fn overlaps(&self, other: &Self) -> bool {
74        self.check_collision(other)
75    }
76
77    fn merged(&self, other: &Self) -> Self {
78        let displacement = other.center - self.center;
79        let distance = displacement.magnitude();
80        if distance + other.radius <= self.radius {
81            return *self;
82        }
83        if distance + self.radius <= other.radius {
84            return *other;
85        }
86        let new_radius =
87            (distance + self.radius + other.radius) / (V::Scalar::one() + V::Scalar::one());
88        let direction = if distance.is_zero() {
89            V::zero()
90        } else {
91            displacement / distance
92        };
93        Self {
94            center: self.center + direction * (new_radius - self.radius),
95            radius: new_radius,
96        }
97    }
98}
99
100impl<V: Copy + InnerSpace, T: Transform<V>> Transformable<T> for Sphere<V> {
101    fn transformed(&self, transform: &T) -> Self {
102        Self {
103            center: transform.apply_point(self.center),
104            radius: self.radius,
105        }
106    }
107}
108
109#[cfg(feature = "ray")]
110mod ray;
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115    use simple_vectors::Vector;
116
117    type Vec3 = Vector<f32, 3>;
118
119    #[test]
120    fn spheres_collide() {
121        let a = Sphere::new(Vec3::from([0.0, 0.0, 0.0]), 1.0);
122        let b = Sphere::new(Vec3::from([1.5, 0.0, 0.0]), 1.0);
123
124        let info = a.collision_info(&b).unwrap();
125        assert!((info.vector.magnitude() - 0.5).abs() < 0.001);
126    }
127
128    #[test]
129    fn spheres_dont_collide() {
130        let a = Sphere::new(Vec3::from([0.0, 0.0, 0.0]), 1.0);
131        let b = Sphere::new(Vec3::from([3.0, 0.0, 0.0]), 1.0);
132
133        assert!(a.collision_info(&b).is_none());
134    }
135
136    #[test]
137    fn check_collision_matches_collision_info() {
138        let a = Sphere::new(Vec3::from([0.0, 0.0, 0.0]), 1.0);
139        let close = Sphere::new(Vec3::from([1.5, 0.0, 0.0]), 1.0);
140        let far = Sphere::new(Vec3::from([3.0, 0.0, 0.0]), 1.0);
141
142        assert_eq!(
143            a.check_collision(&close),
144            a.collision_info(&close).is_some()
145        );
146        assert_eq!(a.check_collision(&far), a.collision_info(&far).is_some());
147    }
148}