valence_spatial 0.2.0-alpha.1+mc.1.20.1

Spatial partitioning for entities in Valence
Documentation
#![doc = include_str!("../README.md")]
#![deny(
    rustdoc::broken_intra_doc_links,
    rustdoc::private_intra_doc_links,
    rustdoc::missing_crate_level_docs,
    rustdoc::invalid_codeblock_attributes,
    rustdoc::invalid_rust_codeblocks,
    rustdoc::bare_urls,
    rustdoc::invalid_html_tags
)]
#![warn(
    trivial_casts,
    trivial_numeric_casts,
    unused_lifetimes,
    unused_import_braces,
    unreachable_pub,
    clippy::dbg_macro
)]

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