1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
#![doc = include_str!("../README.md")]
use vek::{Aabb, Vec3};
pub mod bvh;
pub trait SpatialIndex<N = f64> {
type Object: Bounded3D<N>;
/// Invokes `f` with every object in the spatial index considered
/// colliding according to `collides` in an arbitrary order.
///
/// `collides` takes an AABB and returns whether or not a collision
/// occurred with the given AABB.
///
/// `f` is called with every object considered colliding. If `f` returns
/// with `Some(x)`, then `query` exits early with `Some(x)`. If `f` never
/// returns with `Some`, then query returns `None`.
fn query<C, F, T>(&self, collides: C, f: F) -> Option<T>
where
C: FnMut(Aabb<N>) -> bool,
F: FnMut(&Self::Object) -> Option<T>;
/// Casts a ray defined by `origin` and `direction` through object AABBs
/// and returns the closest intersection for which `f` returns `true`.
///
/// `f` is a predicate used to filter intersections. For instance, if a ray
/// is shot from a player's eye position, you probably don't want the
/// ray to intersect with the player's own hitbox.
///
/// If no intersections are found or if `f` never returns `true` then `None`
/// is returned. Additionally, the given ray direction must be
/// normalized.
fn raycast<F>(
&self,
origin: Vec3<f64>,
direction: Vec3<f64>,
f: F,
) -> Option<RaycastHit<Self::Object, N>>
where
F: FnMut(RaycastHit<Self::Object, N>) -> bool;
}
pub trait Bounded3D<N = f64> {
fn aabb(&self) -> Aabb<N>;
}
/// Represents an intersection between a ray and an entity's axis-aligned
/// bounding box (hitbox).
#[derive(PartialEq, Eq, Debug)]
pub struct RaycastHit<'a, O, N = f64> {
/// The object that was hit by the ray.
pub object: &'a O,
/// The distance from the ray origin to the closest intersection point.
/// If the origin of the ray is inside the bounding box, then this will be
/// zero.
pub near: N,
/// The distance from the ray origin to the second intersection point. This
/// represents the point at which the ray exits the bounding box.
pub far: N,
}
impl<O, N: Clone> Clone for RaycastHit<'_, O, N> {
fn clone(&self) -> Self {
Self {
object: self.object,
near: self.near.clone(),
far: self.far.clone(),
}
}
}
impl<O, N: Copy> Copy for RaycastHit<'_, O, N> {}
impl<N: Clone> Bounded3D<N> for Aabb<N> {
fn aabb(&self) -> Aabb<N> {
self.clone()
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Default, Debug)]
pub struct WithAabb<O, N = f64> {
pub object: O,
pub aabb: Aabb<N>,
}
impl<O, N> WithAabb<O, N> {
pub fn new(object: O, aabb: Aabb<N>) -> Self {
Self { object, aabb }
}
}
impl<O, N: Clone> Bounded3D<N> for WithAabb<O, N> {
fn aabb(&self) -> Aabb<N> {
self.aabb.clone()
}
}
/// Calculates the intersection between an axis-aligned bounding box and a ray
/// defined by its origin `ro` and direction `rd`.
///
/// If an intersection occurs, `Some((near, far))` is returned. `near` and `far`
/// are the distance from the origin to the closest and furthest intersection
/// points respectively. If the intersection occurs inside the bounding box,
/// then `near` is zero.
pub fn ray_box_intersect(ro: Vec3<f64>, rd: Vec3<f64>, bb: Aabb<f64>) -> Option<(f64, f64)> {
let mut near = -f64::INFINITY;
let mut far = f64::INFINITY;
for i in 0..3 {
// Rust's definition of min and max properly handle the NaNs that these
// computations might produce.
let t0 = (bb.min[i] - ro[i]) / rd[i];
let t1 = (bb.max[i] - ro[i]) / rd[i];
near = near.max(t0.min(t1));
far = far.min(t0.max(t1));
}
if near <= far && far >= 0.0 {
Some((near.max(0.0), far))
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ray_box_edge_cases() {
let bb = Aabb {
min: Vec3::new(0.0, 0.0, 0.0),
max: Vec3::new(1.0, 1.0, 1.0),
};
let ros = [
// On a corner
Vec3::new(0.0, 0.0, 0.0),
// Outside
Vec3::new(-0.5, 0.5, -0.5),
// In the center
Vec3::new(0.5, 0.5, 0.5),
// On an edge
Vec3::new(0.0, 0.5, 0.0),
// On a face
Vec3::new(0.0, 0.5, 0.5),
// Outside slabs
Vec3::new(-2.0, -2.0, -2.0),
];
let rds = [
Vec3::new(1.0, 0.0, 0.0),
Vec3::new(-1.0, 0.0, 0.0),
Vec3::new(0.0, 1.0, 0.0),
Vec3::new(0.0, -1.0, 0.0),
Vec3::new(0.0, 0.0, 1.0),
Vec3::new(0.0, 0.0, -1.0),
];
assert!(rds.iter().all(|d| d.is_normalized()));
for ro in ros {
for rd in rds {
if let Some((near, far)) = ray_box_intersect(ro, rd, bb) {
assert!(near.is_finite());
assert!(far.is_finite());
assert!(near <= far);
assert!(near >= 0.0);
assert!(far >= 0.0);
}
}
}
}
}