#![deny(missing_docs)]
mod bvh;
mod mesh;
mod triangle;
pub use mesh::{TriangleHit, TriangleMesh};
use collide_capsule::Capsule;
use collide_ray::Ray;
use ga3::Vector;
use bvh::Bvh;
#[derive(Clone, Copy)]
pub struct Aabb {
pub min: [f32; 3],
pub max: [f32; 3],
}
impl Aabb {
pub const EMPTY: Self = Self {
min: [f32::INFINITY; 3],
max: [f32::NEG_INFINITY; 3],
};
pub fn overlaps(&self, other: &Self) -> bool {
(0..3).all(|axis| self.max[axis] >= other.min[axis] && self.min[axis] <= other.max[axis])
}
pub fn include(&mut self, point: [f32; 3]) {
for ((minimum, maximum), value) in self.min.iter_mut().zip(&mut self.max).zip(point) {
*minimum = minimum.min(value);
*maximum = maximum.max(value);
}
}
pub fn merged(&self, other: &Self) -> Self {
let mut result = *self;
result.include(other.min);
result.include(other.max);
result
}
}
pub struct CollisionResult {
pub grounded: bool,
pub ground_y: f32,
pub ground_normal: Vector<f32>,
pub push: Vector<f32>,
}
pub struct CollisionWorld {
meshes: Vec<TriangleMesh>,
bvh: Bvh,
}
impl CollisionWorld {
pub fn new(meshes: Vec<TriangleMesh>) -> Self {
let bounds: Vec<Aabb> = meshes.iter().map(|mesh| *mesh.bounds()).collect();
Self {
bvh: Bvh::build(&bounds),
meshes,
}
}
pub fn collide_capsule(
&self,
capsule: &Capsule<Vector<f32>>,
velocity_y: f32,
) -> CollisionResult {
let radius = capsule.rad;
let position = capsule.start - Vector::y(radius);
let height = capsule.end.y - capsule.start.y + 2.0 * radius;
let mut result = CollisionResult {
grounded: false,
ground_y: f32::NEG_INFINITY,
ground_normal: Vector::y(1.0),
push: Vector::new(0.0, 0.0, 0.0),
};
let capsule_bounds = Aabb {
min: [position.x - radius, position.y, position.z - radius],
max: [
position.x + radius,
position.y + height,
position.z + radius,
],
};
for mesh_index in self.bvh.overlapping(&capsule_bounds) {
let Some(mesh) = self.meshes.get(mesh_index) else {
continue;
};
let Some(hit) = mesh.collide_capsule(position, velocity_y, radius, height) else {
continue;
};
if hit.grounded && hit.ground_y > result.ground_y {
result.grounded = true;
result.ground_y = hit.ground_y;
result.ground_normal = hit.ground_normal;
}
result.push += hit.push;
}
result
}
pub fn raycast(&self, ray: &Ray<Vector<f32>>, max_distance: f32) -> Option<f32> {
let origin: [f32; 3] = ray.origin.into();
let inverse_direction = <[f32; 3]>::from(ray.direction).map(f32::recip);
let mut closest: Option<f32> = None;
for mesh_index in self
.bvh
.ray_overlapping(origin, inverse_direction, max_distance)
{
let Some(mesh) = self.meshes.get(mesh_index) else {
continue;
};
if let Some(distance) = mesh.raycast(ray.origin, ray.direction, max_distance)
&& closest.is_none_or(|best| distance < best)
{
closest = Some(distance);
}
}
closest
}
}