#![deny(missing_docs)]
use collide::{BoundingVolume, Collider, CollisionInfo, Transform, Transformable};
use inner_space::{InnerSpace, VectorSpace};
use scalars::{One, Sqrt, Zero};
#[derive(Copy, Clone, Debug)]
pub struct Sphere<V: VectorSpace> {
pub center: V,
pub radius: V::Scalar,
}
impl<V: Copy + InnerSpace> Sphere<V> {
pub fn new(center: V, radius: V::Scalar) -> Self {
Self { center, radius }
}
pub fn point(center: V) -> Self {
Self {
center,
radius: V::Scalar::zero(),
}
}
}
impl<V: Copy + InnerSpace> Collider for Sphere<V> {
type Vector = V;
fn check_collision(&self, other: &Self) -> bool {
let displacement = other.center - self.center;
let combined_radius = self.radius + other.radius;
displacement.magnitude2() <= combined_radius * combined_radius
}
fn collision_info(&self, other: &Self) -> Option<CollisionInfo<V>> {
let displacement = other.center - self.center;
let distance_squared = displacement.magnitude2();
let combined_radius = self.radius + other.radius;
if distance_squared > combined_radius * combined_radius {
return None;
}
let distance = distance_squared.sqrt();
let direction = displacement / distance;
Some(CollisionInfo {
self_contact: self.center + direction * self.radius,
other_contact: other.center - direction * other.radius,
vector: direction * (distance - combined_radius),
})
}
}
impl<V: Copy + InnerSpace> BoundingVolume for Sphere<V> {
fn overlaps(&self, other: &Self) -> bool {
self.check_collision(other)
}
fn merged(&self, other: &Self) -> Self {
let displacement = other.center - self.center;
let distance = displacement.magnitude();
if distance + other.radius <= self.radius {
return *self;
}
if distance + self.radius <= other.radius {
return *other;
}
let new_radius =
(distance + self.radius + other.radius) / (V::Scalar::one() + V::Scalar::one());
let direction = if distance.is_zero() {
V::zero()
} else {
displacement / distance
};
Self {
center: self.center + direction * (new_radius - self.radius),
radius: new_radius,
}
}
}
impl<V: Copy + InnerSpace, T: Transform<V>> Transformable<T> for Sphere<V> {
fn transformed(&self, transform: &T) -> Self {
Self {
center: transform.apply_point(self.center),
radius: self.radius,
}
}
}
#[cfg(feature = "ray")]
mod ray;
#[cfg(test)]
mod tests {
use super::*;
use simple_vectors::Vector;
type Vec3 = Vector<f32, 3>;
#[test]
fn spheres_collide() {
let a = Sphere::new(Vec3::from([0.0, 0.0, 0.0]), 1.0);
let b = Sphere::new(Vec3::from([1.5, 0.0, 0.0]), 1.0);
let info = a.collision_info(&b).unwrap();
assert!((info.vector.magnitude() - 0.5).abs() < 0.001);
}
#[test]
fn spheres_dont_collide() {
let a = Sphere::new(Vec3::from([0.0, 0.0, 0.0]), 1.0);
let b = Sphere::new(Vec3::from([3.0, 0.0, 0.0]), 1.0);
assert!(a.collision_info(&b).is_none());
}
#[test]
fn check_collision_matches_collision_info() {
let a = Sphere::new(Vec3::from([0.0, 0.0, 0.0]), 1.0);
let close = Sphere::new(Vec3::from([1.5, 0.0, 0.0]), 1.0);
let far = Sphere::new(Vec3::from([3.0, 0.0, 0.0]), 1.0);
assert_eq!(
a.check_collision(&close),
a.collision_info(&close).is_some()
);
assert_eq!(a.check_collision(&far), a.collision_info(&far).is_some());
}
}