dimforge / nalgebra

Linear algebra library for Rust.
https://nalgebra.org
Apache License 2.0
4.04k stars 483 forks source link

Planes and Culling Frustums #1337

Open ggadwa opened 11 months ago

ggadwa commented 11 months ago

Any thoughts about adding in planes and culling frustums (or just frustums with the right functions.) I'm using nalgebra for a game I'm working on and planes + frustums are nice for optimizations, so others might desire them.

I basically made my own and if you think this is something useful, feel free to use my code if it's up to your standards, at least as a start. Otherwise, it's not hard to create, just something that could get wrapped into your lib and be one less piece of code I'm maintaining.

If you already have this and I somehow missed it, I apologize for the issue!

Plane:

` use num_traits::real::Real; use nalgebra::*;

pub struct Plane { pub a: T, pub b: T, pub c: T, pub d: T }

impl<T: Real + std::fmt::Debug + 'static> Plane { pub fn new( a: T, b: T, c: T, d: T ) -> Self { Self { a, b, c, d } }

pub fn normalize(
    &mut self
) {
    let f = ((self.a * self.a) + (self.b * self.b) + (self.c * self.c)).sqrt();
    if f != num_traits::zero() {
        self.a = self.a / f;
        self.b = self.b / f;
        self.c = self.c / f;
        self.d = self.d / f;
    }
}

pub fn bound_box_outside_plane(
    &mut self,
    min: &Point3<T>,
    max: &Point3<T>
) -> bool {
    if (self.a * min.x) + (self.b * min.y) + (self.c * min.z) + self.d > num_traits::zero() {
        return true;
    }
    if (self.a * max.x) + (self.b * min.y) + (self.c * min.z) + self.d > num_traits::zero() {
        return true;
    }
    if (self.a * min.x) + (self.b * max.y) + (self.c * min.z) + self.d > num_traits::zero() {
        return true;
    }
    if (self.a * max.x) + (self.b * max.y) + (self.c * min.z) + self.d > num_traits::zero() {
        return true;
    }
    if (self.a * min.x) + (self.b * min.y) + (self.c * max.z) + self.d > num_traits::zero() {
        return true;
    }
    if (self.a * max.x) + (self.b * min.y) + (self.c * max.z) + self.d > num_traits::zero() {
        return true;
    }
    if (self.a * min.x) + (self.b * max.y) + (self.c * max.z) + self.d > num_traits::zero() {
        return true;
    }
    (self.a * max.x) + (self.b * max.y) + (self.c * max.z) + self.d > num_traits::zero()
}

}

impl<T: Real + std::fmt::Debug + 'static> Default for Plane { fn default() -> Self { Self { a: num_traits::zero(), b: num_traits::zero(), c: num_traits::zero(), d: num_traits::zero() } } } `

Culling Frustum:

` use num_traits::real::Real; use nalgebra::*;

use crate::game::view_plane::Plane; pub struct CullingFrustum { pub left: Plane, pub right: Plane, pub top: Plane, pub bottom: Plane, pub near: Plane, pub far: Plane, }

impl<T: Real + std::fmt::Debug + 'static> CullingFrustum { pub fn new( perspective_matrix: &Matrix4, view_matrix: &Matrix4 ) -> Self { let mut clip_plane: [T; 16] = [num_traits::zero(); 16];

    clip_plane[0]=(view_matrix[(0,0)]*perspective_matrix[(0,0)])+(view_matrix[(1,0)]*perspective_matrix[(0,1)])+(view_matrix[(2,0)]*perspective_matrix[(0,2)])+(view_matrix[(3,0)]*perspective_matrix[(0,3)]);
    clip_plane[1]=(view_matrix[(0,0)]*perspective_matrix[(1,0)])+(view_matrix[(1,0)]*perspective_matrix[(1,1)])+(view_matrix[(2,0)]*perspective_matrix[(1,2)])+(view_matrix[(3,0)]*perspective_matrix[(1,3)]);
    clip_plane[2]=(view_matrix[(0,0)]*perspective_matrix[(2,0)])+(view_matrix[(1,0)]*perspective_matrix[(2,1)])+(view_matrix[(2,0)]*perspective_matrix[(2,2)])+(view_matrix[(3,0)]*perspective_matrix[(2,3)]);
    clip_plane[3]=(view_matrix[(0,0)]*perspective_matrix[(3,0)])+(view_matrix[(1,0)]*perspective_matrix[(3,1)])+(view_matrix[(2,0)]*perspective_matrix[(3,2)])+(view_matrix[(3,0)]*perspective_matrix[(3,3)]);

    clip_plane[4]=(view_matrix[(0,1)]*perspective_matrix[(0,0)])+(view_matrix[(1,1)]*perspective_matrix[(0,1)])+(view_matrix[(2,1)]*perspective_matrix[(0,2)])+(view_matrix[(3,1)]*perspective_matrix[(0,3)]);
    clip_plane[5]=(view_matrix[(0,1)]*perspective_matrix[(1,0)])+(view_matrix[(1,1)]*perspective_matrix[(1,1)])+(view_matrix[(2,1)]*perspective_matrix[(1,2)])+(view_matrix[(3,1)]*perspective_matrix[(1,3)]);
    clip_plane[6]=(view_matrix[(0,1)]*perspective_matrix[(2,0)])+(view_matrix[(1,1)]*perspective_matrix[(2,1)])+(view_matrix[(2,1)]*perspective_matrix[(2,2)])+(view_matrix[(3,1)]*perspective_matrix[(2,3)]);
    clip_plane[7]=(view_matrix[(0,1)]*perspective_matrix[(3,0)])+(view_matrix[(1,1)]*perspective_matrix[(3,1)])+(view_matrix[(2,1)]*perspective_matrix[(3,2)])+(view_matrix[(3,1)]*perspective_matrix[(3,3)]);

    clip_plane[8]=(view_matrix[(0,2)]*perspective_matrix[(0,0)])+(view_matrix[(1,2)]*perspective_matrix[(0,1)])+(view_matrix[(2,2)]*perspective_matrix[(0,2)])+(view_matrix[(3,2)]*perspective_matrix[(0,3)]);
    clip_plane[9]=(view_matrix[(0,2)]*perspective_matrix[(1,0)])+(view_matrix[(1,2)]*perspective_matrix[(1,1)])+(view_matrix[(2,2)]*perspective_matrix[(1,2)])+(view_matrix[(3,2)]*perspective_matrix[(1,3)]);
    clip_plane[10]=(view_matrix[(0,2)]*perspective_matrix[(2,0)])+(view_matrix[(1,2)]*perspective_matrix[(2,1)])+(view_matrix[(2,2)]*perspective_matrix[(2,2)])+(view_matrix[(3,2)]*perspective_matrix[(2,3)]);
    clip_plane[11]=(view_matrix[(0,2)]*perspective_matrix[(3,0)])+(view_matrix[(1,2)]*perspective_matrix[(3,1)])+(view_matrix[(2,2)]*perspective_matrix[(3,2)])+(view_matrix[(3,2)]*perspective_matrix[(3,3)]);

    clip_plane[12]=(view_matrix[(0,3)]*perspective_matrix[(0,0)])+(view_matrix[(1,3)]*perspective_matrix[(0,1)])+(view_matrix[(2,3)]*perspective_matrix[(0,2)])+(view_matrix[(3,3)]*perspective_matrix[(0,3)]);
    clip_plane[13]=(view_matrix[(0,3)]*perspective_matrix[(1,0)])+(view_matrix[(1,3)]*perspective_matrix[(1,1)])+(view_matrix[(2,3)]*perspective_matrix[(1,2)])+(view_matrix[(3,3)]*perspective_matrix[(1,3)]);
    clip_plane[14]=(view_matrix[(0,3)]*perspective_matrix[(2,0)])+(view_matrix[(1,3)]*perspective_matrix[(2,1)])+(view_matrix[(2,3)]*perspective_matrix[(2,2)])+(view_matrix[(3,3)]*perspective_matrix[(2,3)]);
    clip_plane[15]=(view_matrix[(0,3)]*perspective_matrix[(3,0)])+(view_matrix[(1,3)]*perspective_matrix[(3,1)])+(view_matrix[(2,3)]*perspective_matrix[(3,2)])+(view_matrix[(3,3)]*perspective_matrix[(3,3)]);

    // left plane
    let mut left: Plane<T> = Plane::new(
        clip_plane[3]+clip_plane[0],
        clip_plane[7]+clip_plane[4],
        clip_plane[11]+clip_plane[8],
        clip_plane[15]+clip_plane[12]
    );
    left.normalize();

    // right plane
    let mut right: Plane<T> = Plane::new(
        clip_plane[3]-clip_plane[0],
        clip_plane[7]-clip_plane[4],
        clip_plane[11]-clip_plane[8],
        clip_plane[15]-clip_plane[12]
    );
    right.normalize();

    // top plane
    let mut top: Plane<T> = Plane::new(
        clip_plane[3]-clip_plane[1],
        clip_plane[7]-clip_plane[5],
        clip_plane[11]-clip_plane[9],
        clip_plane[15]-clip_plane[13]
    );
    top.normalize();

    // bottom plane
    let mut bottom: Plane<T> = Plane::new(
        clip_plane[3]+clip_plane[1],
        clip_plane[7]+clip_plane[5],
        clip_plane[11]+clip_plane[9],
        clip_plane[15]+clip_plane[13]
    );
    bottom.normalize();

    // near plane
    let mut near: Plane<T> = Plane::new(
        clip_plane[3]+clip_plane[2],
        clip_plane[7]+clip_plane[6],
        clip_plane[11]+clip_plane[10],
        clip_plane[15]+clip_plane[14]
    );
    near.normalize();

    // far plane
    let mut far: Plane<T> = Plane::new(
        clip_plane[3]-clip_plane[2],
        clip_plane[7]-clip_plane[6],
        clip_plane[11]-clip_plane[10],
        clip_plane[15]-clip_plane[14]
    );
    far.normalize();

    Self {
        left,
        right,
        top,
        bottom,
        near,
        far
    }
}

pub fn bound_box_in_frustum(
    &mut self,
    min: &Point3<T>,
    max: &Point3<T>
) -> bool {
    // check if outside the plane, if it is,
    // then it's considered outside the bounds
    if !self.left.bound_box_outside_plane(min, max) {
        return false;
    }
    if !self.right.bound_box_outside_plane(min, max) {
        return false;
    }
    if !self.top.bound_box_outside_plane(min, max) {
        return false;
    }
    if !self.bottom.bound_box_outside_plane(min, max) {
        return false;
    }
    if !self.near.bound_box_outside_plane(min, max) {
        return false;
    }
    self.far.bound_box_outside_plane(min, max)
}

}

impl<T: Real + std::fmt::Debug + 'static> Default for CullingFrustum { fn default() -> Self { Self { left: Plane::default(), right: Plane::default(), top: Plane::default(), bottom: Plane::default(), near: Plane::default(), far: Plane::default(), } } } `

tpdickso commented 9 months ago

I think nalgebra's built-in geometric types tend to be constrained to more algebraic types that are related to vectors/matrices -- however, I think the HalfSpace structure from nalgebra's sister library parry could be what you're looking for! It has a focus specifically on collision detection and so could be useful for your use-case. I don't think parry currently has a structure for a 6-plane frustum, though.