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
//! View frustum for visibility determination

use Plane;
use bound::*;
use cgmath::{Matrix, Matrix4};
use cgmath::{Ortho, Perspective, PerspectiveFov};
use cgmath::BaseFloat;
use cgmath::Point3;

/// View frustum, used for frustum culling
#[derive(Copy, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Frustum<S: BaseFloat> {
    /// Left plane
    pub left: Plane<S>,
    /// Right plane
    pub right: Plane<S>,
    /// Bottom plane
    pub bottom: Plane<S>,
    /// Top plane
    pub top: Plane<S>,
    /// Near plane
    pub near: Plane<S>,
    /// Far plane
    pub far: Plane<S>,
}

impl<S: BaseFloat> Frustum<S> {
    /// Construct a frustum.
    pub fn new(
        left: Plane<S>,
        right: Plane<S>,
        bottom: Plane<S>,
        top: Plane<S>,
        near: Plane<S>,
        far: Plane<S>,
    ) -> Frustum<S> {
        Frustum {
            left,
            right,
            bottom,
            top,
            near,
            far,
        }
    }

    /// Extract frustum planes from a projection matrix.
    pub fn from_matrix4(mat: Matrix4<S>) -> Option<Frustum<S>> {
        Some(Frustum::new(
            match Plane::from_vector4_alt(mat.row(3) + mat.row(0)).normalize() {
                Some(p) => p,
                None => return None,
            },
            match Plane::from_vector4_alt(mat.row(3) - mat.row(0)).normalize() {
                Some(p) => p,
                None => return None,
            },
            match Plane::from_vector4_alt(mat.row(3) + mat.row(1)).normalize() {
                Some(p) => p,
                None => return None,
            },
            match Plane::from_vector4_alt(mat.row(3) - mat.row(1)).normalize() {
                Some(p) => p,
                None => return None,
            },
            match Plane::from_vector4_alt(mat.row(3) + mat.row(2)).normalize() {
                Some(p) => p,
                None => return None,
            },
            match Plane::from_vector4_alt(mat.row(3) - mat.row(2)).normalize() {
                Some(p) => p,
                None => return None,
            },
        ))
    }

    /// Find the spatial relation of a bound inside this frustum.
    pub fn contains<B: PlaneBound<S>>(&self, bound: &B) -> Relation {
        [
            self.left,
            self.right,
            self.top,
            self.bottom,
            self.near,
            self.far,
        ].iter()
            .fold(Relation::In, |cur, p| {
                use std::cmp::max;
                let r = bound.relate_plane(*p);
                // If any of the planes are `Out`, the bound is outside.
                // Otherwise, if any are `Cross`, the bound is crossing.
                // Otherwise, the bound is fully inside.
                max(cur, r)
            })
    }
}

/// View frustum corner points
#[derive(Copy, Clone, PartialEq, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct FrustumPoints<S> {
    /// Near top left point
    pub near_top_left: Point3<S>,
    /// Near top right point
    pub near_top_right: Point3<S>,
    /// Near bottom left point
    pub near_bottom_left: Point3<S>,
    /// Near bottom right point
    pub near_bottom_right: Point3<S>,
    /// Far top left point
    pub far_top_left: Point3<S>,
    /// Far top right point
    pub far_top_right: Point3<S>,
    /// Far bottom left point
    pub far_bottom_left: Point3<S>,
    /// Far bottom right point
    pub far_bottom_right: Point3<S>,
}

/// Conversion trait for converting cgmath projection types into a view frustum
pub trait Projection<S: BaseFloat>: Into<Matrix4<S>> {
    /// Create a view frustum
    fn to_frustum(&self) -> Frustum<S>;
}

impl<S: BaseFloat> Projection<S> for PerspectiveFov<S> {
    fn to_frustum(&self) -> Frustum<S> {
        // TODO: Could this be faster?
        Frustum::from_matrix4(self.clone().into()).unwrap()
    }
}

impl<S: BaseFloat> Projection<S> for Perspective<S> {
    fn to_frustum(&self) -> Frustum<S> {
        // TODO: Could this be faster?
        Frustum::from_matrix4(self.clone().into()).unwrap()
    }
}

impl<S: BaseFloat> Projection<S> for Ortho<S> {
    fn to_frustum(&self) -> Frustum<S> {
        Frustum {
            left: Plane::from_abcd(S::one(), S::zero(), S::zero(), self.left),
            right: Plane::from_abcd(-S::one(), S::zero(), S::zero(), self.right),
            bottom: Plane::from_abcd(S::zero(), S::one(), S::zero(), self.bottom),
            top: Plane::from_abcd(S::zero(), -S::one(), S::zero(), self.top),
            near: Plane::from_abcd(S::zero(), S::zero(), -S::one(), self.near),
            far: Plane::from_abcd(S::zero(), S::zero(), S::one(), self.far),
        }
    }
}