treeculler 0.4.0

Utilities to help with frustum culling.
Documentation
use crate::{Float, Frustum, Vec3, Vec4};
use core::fmt::Debug;

/// Bounding volume trait.
pub trait BVol<T: Float + Debug> {
    /// Returns an AABB that contains the bounding volume.
    fn get_aabb(&self) -> AABB<T>;
    /// Checks if bounding volume intersects with a plane.
    ///
    /// Returns true if it does, false otherwise.
    fn test_against_plane(&self, plane: &Vec4<T>) -> bool;
    /// Checks if bounding volume is outside the frustum.
    ///
    /// Sets a bit if the bounding volume is outside that bit's plane. Returns `1` if it is fully outside.
    fn test_against_frustum(&self, frustum: &Frustum<T>, mut mask: u8) -> u8 {
        for i in 0..6 {
            if self.test_against_plane(&frustum.planes[i as usize]) {
                return u8::max_value();
            } else {
                // This piece of code first shifts the hardcoded byte by i, which is used as an
                // index, and then OR is used to set the index bit to one.
                mask |= 0b1000_0000u8 >> i;
            }
        }

        if !frustum.test_against_aabb(&self.get_aabb()) {
            return u8::max_value();
        }

        mask
    }
    /// Checks if bounding volume is outside the frustum coherently.
    /// Coherence is provided by the `lpindex` argument, which should be the last plane
    /// this volume got culled.
    ///
    /// Returns false if the volume is outside, true otherwise. Returns the last plane
    /// this volume has been culled as an `u8`, to save it and use it later again.
    fn coherent_test_against_frustum(&self, frustum: &Frustum<T>, lpindex: u8) -> (bool, u8) {
        if self.test_against_plane(&frustum.planes[lpindex as usize]) {
            return (false, lpindex);
        }

        for i in 0..6 {
            if (i != lpindex) && self.test_against_plane(&frustum.planes[i as usize]) {
                return (false, i);
            }
        }

        if !frustum.test_against_aabb(&self.get_aabb()) {
            return (false, lpindex);
        }

        (true, lpindex)
    }
}

/// A bounding sphere.
pub struct BoundingSphere<T: Float + Debug> {
    pub center: Vec3<T>,
    pub radius: T,
}

impl<T: Float + Debug> BoundingSphere<T> {
    /// Creates a new bounding sphere.
    pub fn new(center: impl Into<Vec3<T>>, radius: T) -> BoundingSphere<T> {
        BoundingSphere {
            center: center.into(),
            radius,
        }
    }
}

impl<T: Float + Debug> BVol<T> for BoundingSphere<T> {
    fn get_aabb(&self) -> AABB<T> {
        AABB {
            min: Vec3::new(
                self.center[0] - self.radius,
                self.center[1] - self.radius,
                self.center[2] - self.radius,
            ),
            max: Vec3::new(
                self.center[0] + self.radius,
                self.center[1] + self.radius,
                self.center[2] + self.radius,
            ),
        }
    }
    fn test_against_plane(&self, plane: &Vec4<T>) -> bool {
        dist_bpp(plane, self.center) < -self.radius
    }
}

/// An axis aligned bounding box.
pub struct AABB<T: Float + Debug> {
    pub min: Vec3<T>,
    pub max: Vec3<T>,
}

impl<T: Float + Debug> AABB<T> {
    /// Creates a new AABB.
    pub fn new<P>(min: P, max: P) -> AABB<T>
    where
        P: Into<Vec3<T>>,
    {
        AABB {
            min: min.into(),
            max: max.into(),
        }
    }
}

impl<T: Float + Debug> BVol<T> for AABB<T> {
    fn get_aabb(&self) -> AABB<T> {
        AABB {
            min: self.min,
            max: self.max,
        }
    }
    fn test_against_plane(&self, plane: &Vec4<T>) -> bool {
        dist_bpp(plane, mi_vertex(plane, self)) < T::zero()
    }
}

impl<T: Float + Debug> From<[(T, T, T); 2]> for AABB<T> {
    fn from(v: [(T, T, T); 2]) -> Self {
        Self::new([v[0].0, v[0].1, v[0].2], [v[1].0, v[1].1, v[1].2])
    }
}

/// Calculates distance between plane and point
pub fn dist_bpp<T: Float>(plane: &Vec4<T>, point: Vec3<T>) -> T {
    plane.x * point.x + plane.y * point.y + plane.z * point.z + plane.w
}

/// Calculates the most inside vertex of an AABB.
pub fn mi_vertex<T: Float + Debug>(plane: &Vec4<T>, aabb: &AABB<T>) -> Vec3<T> {
    Vec3::new(
        if plane.x >= T::zero() {
            aabb.max.x
        } else {
            aabb.min.x
        },
        if plane.y >= T::zero() {
            aabb.max.y
        } else {
            aabb.min.y
        },
        if plane.z >= T::zero() {
            aabb.max.z
        } else {
            aabb.min.z
        },
    )
}

/// Calculates the most outside vertex of an AABB.
pub fn mo_vertex<T: Float + Debug>(plane: &Vec4<T>, aabb: &AABB<T>) -> Vec3<T> {
    Vec3::new(
        if plane.x >= T::zero() {
            aabb.min.x
        } else {
            aabb.max.x
        },
        if plane.y >= T::zero() {
            aabb.min.y
        } else {
            aabb.max.y
        },
        if plane.z >= T::zero() {
            aabb.min.z
        } else {
            aabb.max.z
        },
    )
}