cgmath/
projection.rs

1// Copyright 2013-2014 The CGMath Developers. For a full listing of the authors,
2// refer to the Cargo.toml file at the top-level directory of this distribution.
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8//     http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use num_traits::cast;
17use num_traits::Zero;
18
19use structure::Angle;
20
21use angle::Rad;
22use matrix::Matrix4;
23use num::BaseFloat;
24
25/// Create a perspective projection matrix.
26///
27/// This is the equivalent to the [`gluPerspective`] function.
28///
29/// [`gluPerspective`]: https://www.opengl.org/sdk/docs/man2/xhtml/gluPerspective.xml
30pub fn perspective<S: BaseFloat, A: Into<Rad<S>>>(
31    fovy: A,
32    aspect: S,
33    near: S,
34    far: S,
35) -> Matrix4<S> {
36    PerspectiveFov {
37        fovy: fovy.into(),
38        aspect: aspect,
39        near: near,
40        far: far,
41    }
42    .into()
43}
44
45/// Create a perspective matrix from a view frustum.
46///
47/// This is the equivalent of the now deprecated [`glFrustum`] function.
48///
49/// [`glFrustum`]: http://www.opengl.org/sdk/docs/man2/xhtml/glFrustum.xml
50pub fn frustum<S: BaseFloat>(left: S, right: S, bottom: S, top: S, near: S, far: S) -> Matrix4<S> {
51    Perspective {
52        left: left,
53        right: right,
54        bottom: bottom,
55        top: top,
56        near: near,
57        far: far,
58    }
59    .into()
60}
61
62/// Create an orthographic projection matrix.
63///
64/// This is the equivalent of the now deprecated [`glOrtho`] function.
65///
66/// [`glOrtho`]: http://www.opengl.org/sdk/docs/man2/xhtml/glOrtho.xml
67pub fn ortho<S: BaseFloat>(left: S, right: S, bottom: S, top: S, near: S, far: S) -> Matrix4<S> {
68    Ortho {
69        left: left,
70        right: right,
71        bottom: bottom,
72        top: top,
73        near: near,
74        far: far,
75    }
76    .into()
77}
78
79/// A perspective projection based on a vertical field-of-view angle.
80#[derive(Copy, Clone, Debug, PartialEq)]
81#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))]
82#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
83pub struct PerspectiveFov<S> {
84    pub fovy: Rad<S>,
85    pub aspect: S,
86    pub near: S,
87    pub far: S,
88}
89
90impl<S: BaseFloat> PerspectiveFov<S> {
91    pub fn to_perspective(&self) -> Perspective<S> {
92        let two: S = cast(2).unwrap();
93        let angle = self.fovy / two;
94        let ymax = self.near * Rad::tan(angle);
95        let xmax = ymax * self.aspect;
96
97        Perspective {
98            left: -xmax,
99            right: xmax,
100            bottom: -ymax,
101            top: ymax,
102            near: self.near.clone(),
103            far: self.far.clone(),
104        }
105    }
106}
107
108impl<S: BaseFloat> From<PerspectiveFov<S>> for Matrix4<S> {
109    fn from(persp: PerspectiveFov<S>) -> Matrix4<S> {
110        assert!(
111            persp.fovy > Rad::zero(),
112            "The vertical field of view cannot be below zero, found: {:?}",
113            persp.fovy
114        );
115        assert!(
116            persp.fovy < Rad::turn_div_2(),
117            "The vertical field of view cannot be greater than a half turn, found: {:?}",
118            persp.fovy
119        );
120
121        assert!(
122            abs_diff_ne!(persp.aspect.abs(), S::zero()),
123            "The absolute aspect ratio cannot be zero, found: {:?}",
124            persp.aspect.abs()
125        );
126        assert!(
127            persp.near > S::zero(),
128            "The near plane distance cannot be below zero, found: {:?}",
129            persp.near
130        );
131        assert!(
132            persp.far > S::zero(),
133            "The far plane distance cannot be below zero, found: {:?}",
134            persp.far
135        );
136        assert!(
137            abs_diff_ne!(persp.far, persp.near),
138            "The far plane and near plane are too close, found: far: {:?}, near: {:?}",
139            persp.far,
140            persp.near
141        );
142
143        let two: S = cast(2).unwrap();
144        let f = Rad::cot(persp.fovy / two);
145
146        let c0r0 = f / persp.aspect;
147        let c0r1 = S::zero();
148        let c0r2 = S::zero();
149        let c0r3 = S::zero();
150
151        let c1r0 = S::zero();
152        let c1r1 = f;
153        let c1r2 = S::zero();
154        let c1r3 = S::zero();
155
156        let c2r0 = S::zero();
157        let c2r1 = S::zero();
158        let c2r2 = (persp.far + persp.near) / (persp.near - persp.far);
159        let c2r3 = -S::one();
160
161        let c3r0 = S::zero();
162        let c3r1 = S::zero();
163        let c3r2 = (two * persp.far * persp.near) / (persp.near - persp.far);
164        let c3r3 = S::zero();
165
166        #[cfg_attr(rustfmt, rustfmt_skip)]
167        Matrix4::new(
168            c0r0, c0r1, c0r2, c0r3,
169            c1r0, c1r1, c1r2, c1r3,
170            c2r0, c2r1, c2r2, c2r3,
171            c3r0, c3r1, c3r2, c3r3,
172        )
173    }
174}
175
176/// A perspective projection with arbitrary left/right/bottom/top distances
177#[derive(Copy, Clone, Debug, PartialEq)]
178#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
179pub struct Perspective<S> {
180    pub left: S,
181    pub right: S,
182    pub bottom: S,
183    pub top: S,
184    pub near: S,
185    pub far: S,
186}
187
188impl<S: BaseFloat> From<Perspective<S>> for Matrix4<S> {
189    fn from(persp: Perspective<S>) -> Matrix4<S> {
190        assert!(
191            persp.left <= persp.right,
192            "`left` cannot be greater than `right`, found: left: {:?} right: {:?}",
193            persp.left,
194            persp.right
195        );
196        assert!(
197            persp.bottom <= persp.top,
198            "`bottom` cannot be greater than `top`, found: bottom: {:?} top: {:?}",
199            persp.bottom,
200            persp.top
201        );
202        assert!(
203            persp.near <= persp.far,
204            "`near` cannot be greater than `far`, found: near: {:?} far: {:?}",
205            persp.near,
206            persp.far
207        );
208
209        let two: S = cast(2i8).unwrap();
210
211        let c0r0 = (two * persp.near) / (persp.right - persp.left);
212        let c0r1 = S::zero();
213        let c0r2 = S::zero();
214        let c0r3 = S::zero();
215
216        let c1r0 = S::zero();
217        let c1r1 = (two * persp.near) / (persp.top - persp.bottom);
218        let c1r2 = S::zero();
219        let c1r3 = S::zero();
220
221        let c2r0 = (persp.right + persp.left) / (persp.right - persp.left);
222        let c2r1 = (persp.top + persp.bottom) / (persp.top - persp.bottom);
223        let c2r2 = -(persp.far + persp.near) / (persp.far - persp.near);
224        let c2r3 = -S::one();
225
226        let c3r0 = S::zero();
227        let c3r1 = S::zero();
228        let c3r2 = -(two * persp.far * persp.near) / (persp.far - persp.near);
229        let c3r3 = S::zero();
230
231        #[cfg_attr(rustfmt, rustfmt_skip)]
232        Matrix4::new(
233            c0r0, c0r1, c0r2, c0r3,
234            c1r0, c1r1, c1r2, c1r3,
235            c2r0, c2r1, c2r2, c2r3,
236            c3r0, c3r1, c3r2, c3r3,
237        )
238    }
239}
240
241/// An orthographic projection with arbitrary left/right/bottom/top distances
242#[derive(Copy, Clone, Debug, PartialEq)]
243#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
244pub struct Ortho<S> {
245    pub left: S,
246    pub right: S,
247    pub bottom: S,
248    pub top: S,
249    pub near: S,
250    pub far: S,
251}
252
253impl<S: BaseFloat> From<Ortho<S>> for Matrix4<S> {
254    fn from(ortho: Ortho<S>) -> Matrix4<S> {
255        let two: S = cast(2).unwrap();
256
257        let c0r0 = two / (ortho.right - ortho.left);
258        let c0r1 = S::zero();
259        let c0r2 = S::zero();
260        let c0r3 = S::zero();
261
262        let c1r0 = S::zero();
263        let c1r1 = two / (ortho.top - ortho.bottom);
264        let c1r2 = S::zero();
265        let c1r3 = S::zero();
266
267        let c2r0 = S::zero();
268        let c2r1 = S::zero();
269        let c2r2 = -two / (ortho.far - ortho.near);
270        let c2r3 = S::zero();
271
272        let c3r0 = -(ortho.right + ortho.left) / (ortho.right - ortho.left);
273        let c3r1 = -(ortho.top + ortho.bottom) / (ortho.top - ortho.bottom);
274        let c3r2 = -(ortho.far + ortho.near) / (ortho.far - ortho.near);
275        let c3r3 = S::one();
276
277        #[cfg_attr(rustfmt, rustfmt_skip)]
278        Matrix4::new(
279            c0r0, c0r1, c0r2, c0r3,
280            c1r0, c1r1, c1r2, c1r3,
281            c2r0, c2r1, c2r2, c2r3,
282            c3r0, c3r1, c3r2, c3r3,
283        )
284    }
285}