#![warn(missing_docs)]
#[cfg(doctest)]
#[doc = include_str!("../README.md")]
pub struct ReadmeDocTests;
pub mod broad_phase;
pub mod epa;
pub mod geometry;
pub mod gjk;
pub mod manifold;
pub mod raycast;
pub use broad_phase::sweep_and_prune;
pub use epa::{epa_from_simplex, epa_penetration, epa_penetration_rot};
pub use geometry::{AABB, Geometry};
pub use gjk::{GjkOutcome, gjk_distance, gjk_distance_rot, gjk_rot};
pub use manifold::{
MAX_MANIFOLD_POINTS, Manifold, ManifoldPoint, contact_manifold, contact_manifold_within,
};
pub use raycast::{Ray, RayHit, ray_cast, ray_intersect};
use phyz_math::Vec3;
use phyz_model::Attachment;
#[derive(Debug, Clone)]
pub struct Collision {
pub body_i: usize,
pub body_j: usize,
pub contact_point: Vec3,
pub contact_normal: Vec3,
pub penetration_depth: f64,
}
impl Collision {
pub const WORLD: usize = usize::MAX;
#[inline]
pub fn attachment_j(&self) -> Attachment {
if self.body_j == Self::WORLD {
Attachment::World
} else {
Attachment::Body(self.body_j)
}
}
#[inline]
pub fn is_world_j(&self) -> bool {
self.body_j == Self::WORLD
}
}
#[cfg(test)]
mod tests {
use super::*;
use geometry::Geometry;
use phyz_math::Vec3;
#[test]
fn test_sphere_sphere_separated() {
let s1 = Geometry::Sphere { radius: 1.0 };
let s2 = Geometry::Sphere { radius: 1.0 };
let p1 = Vec3::zeros();
let p2 = Vec3::new(3.0, 0.0, 0.0);
let dist = gjk_distance(&s1, &s2, &p1, &p2);
assert!(
(dist - 1.0).abs() < 1e-6,
"Expected distance ~1.0, got {}",
dist
);
}
#[test]
fn test_sphere_sphere_touching() {
let s1 = Geometry::Sphere { radius: 1.0 };
let s2 = Geometry::Sphere { radius: 1.0 };
let p1 = Vec3::zeros();
let p2 = Vec3::new(2.0, 0.0, 0.0);
let dist = gjk_distance(&s1, &s2, &p1, &p2);
assert!(dist.abs() < 1e-6, "Expected distance ~0.0, got {}", dist);
}
#[test]
fn test_sphere_sphere_penetrating() {
let s1 = Geometry::Sphere { radius: 1.0 };
let s2 = Geometry::Sphere { radius: 1.0 };
let p1 = Vec3::zeros();
let p2 = Vec3::new(1.5, 0.0, 0.0);
let dist = gjk_distance(&s1, &s2, &p1, &p2);
assert!(
dist <= 0.0,
"Expected non-positive distance (penetration), got {}",
dist
);
if let Some(pen) = epa_penetration(&s1, &s2, &p1, &p2) {
assert!(
(pen.0 - 0.5).abs() < 0.1,
"Expected penetration ~0.5, got {}",
pen.0
);
}
}
}
#[cfg(test)]
mod attachment_tests {
use super::*;
fn collision_with(body_j: usize) -> Collision {
Collision {
body_i: 0,
body_j,
contact_point: Vec3::zeros(),
contact_normal: Vec3::z(),
penetration_depth: 0.0,
}
}
#[test]
fn attachment_j_translates_the_sentinel() {
assert_eq!(
collision_with(Collision::WORLD).attachment_j(),
Attachment::World
);
assert!(collision_with(Collision::WORLD).is_world_j());
assert_eq!(collision_with(3).attachment_j(), Attachment::Body(3));
assert!(!collision_with(3).is_world_j());
}
#[test]
fn world_sentinel_is_usize_max() {
assert_eq!(Collision::WORLD, usize::MAX);
}
}