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);
                }
            }
        }
    }
}