1#![deny(missing_docs)]
2
3use collide::{BoundingVolume, Collider, CollisionInfo, Transform, Transformable};
16use inner_space::{InnerSpace, VectorSpace};
17use scalars::{One, Sqrt, Zero};
18
19#[derive(Copy, Clone, Debug)]
21pub struct Sphere<V: VectorSpace> {
22 pub center: V,
24 pub radius: V::Scalar,
26}
27
28impl<V: Copy + InnerSpace> Sphere<V> {
29 pub fn new(center: V, radius: V::Scalar) -> Self {
31 Self { center, radius }
32 }
33
34 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}