Make look_at, perspective, and orthographic projection matrices conform to computer-graphics convensions.

The `look_at` method itself has been split into a right-handed and a left-handed variant:
`look_at_rh` and `look_at_lh`.

Fix #171, #158, #88, #79.
This commit is contained in:
Sébastien Crozet 2016-03-31 21:22:02 +02:00
parent 91e14670ed
commit 4c58e37910
5 changed files with 279 additions and 133 deletions

View File

@ -16,7 +16,7 @@ use structs::rot::{Rot2, Rot3};
use quickcheck::{Arbitrary, Gen}; use quickcheck::{Arbitrary, Gen};
/// Two dimensional isometry. /// Two dimensional **direct** isometry.
/// ///
/// This is the composition of a rotation followed by a translation. Vectors `Vec2` are not /// This is the composition of a rotation followed by a translation. Vectors `Vec2` are not
/// affected by the translational component of this transformation while points `Pnt2` are. /// affected by the translational component of this transformation while points `Pnt2` are.
@ -30,7 +30,7 @@ pub struct Iso2<N> {
pub translation: Vec2<N> pub translation: Vec2<N>
} }
/// Three dimensional isometry. /// Three dimensional **direct** isometry.
/// ///
/// This is the composition of a rotation followed by a translation. Vectors `Vec3` are not /// This is the composition of a rotation followed by a translation. Vectors `Vec3` are not
/// affected by the translational component of this transformation while points `Pnt3` are. /// affected by the translational component of this transformation while points `Pnt3` are.
@ -62,20 +62,40 @@ impl<N: Clone + BaseFloat> Iso3<N> {
Iso3::new_with_rotmat(eye.as_vec().clone(), new_rotmat) Iso3::new_with_rotmat(eye.as_vec().clone(), new_rotmat)
} }
/// Builds a look-at view matrix. /// Builds a right-handed look-at view matrix.
/// ///
/// This conforms to the common notion of "look-at" matrix from the computer graphics /// This conforms to the common notion of right handed look-at matrix from the computer
/// community. Its maps the view direction `target - eye` to the **negative** `z` axis and the /// graphics community.
/// `eye` to the origin.
/// ///
/// # Arguments /// # Arguments
/// * eye - The eye position. /// * eye - The eye position.
/// * target - The target position. /// * target - The target position.
/// * up - The vertical view direction. It must not be to collinear to `eye - target`. /// * up - A vector approximately aligned with required the vertical axis. The only
/// requirement of this parameter is to not be collinear to `target - eye`.
#[inline] #[inline]
pub fn new_look_at(eye: &Pnt3<N>, target: &Pnt3<N>, up: &Vec3<N>) -> Iso3<N> { pub fn look_at_rh(eye: &Pnt3<N>, target: &Pnt3<N>, up: &Vec3<N>) -> Iso3<N> {
let new_rotmat = Rot3::new_look_at(&(*target - *eye), up); let rot = Rot3::look_at_rh(&(*target - *eye), up);
Iso3::new_with_rotmat(new_rotmat * (-*eye.as_vec()), new_rotmat) let trans = rot * (-*eye);
Iso3::new_with_rotmat(trans.to_vec(), rot)
}
/// Builds a left-handed look-at view matrix.
///
/// This conforms to the common notion of left handed look-at matrix from the computer
/// graphics community.
///
/// # Arguments
/// * eye - The eye position.
/// * target - The target position.
/// * up - A vector approximately aligned with required the vertical axis. The only
/// requirement of this parameter is to not be collinear to `target - eye`.
#[inline]
pub fn look_at_lh(eye: &Pnt3<N>, target: &Pnt3<N>, up: &Vec3<N>) -> Iso3<N> {
let rot = Rot3::look_at_lh(&(*target - *eye), up);
let trans = rot * (-*eye);
Iso3::new_with_rotmat(trans.to_vec(), rot)
} }
} }

View File

@ -7,18 +7,24 @@ use quickcheck::{Arbitrary, Gen};
/// A 3D orthographic projection stored without any matrix. /// A 3D orthographic projection stored without any matrix.
/// ///
/// Reading or modifying its individual properties is cheap but applying the transformation is costly. /// This flips the `z` axis and maps a axis-aligned cube to the unit cube with corners varying from
/// `(-1, -1, -1)` to `(1, 1, 1)`. Reading or modifying its individual properties is cheap but
/// applying the transformation is costly.
#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Debug, Copy)] #[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Debug, Copy)]
pub struct Ortho3<N> { pub struct Ortho3<N> {
width: N, left: N,
height: N, right: N,
bottom: N,
top: N,
znear: N, znear: N,
zfar: N zfar: N
} }
/// A 3D orthographic projection stored as a 4D matrix. /// A 3D orthographic projection stored as a 4D matrix.
/// ///
/// Reading or modifying its individual properties is costly but applying the transformation is cheap. /// This flips the `z` axis and maps a axis-aligned cube to the unit cube with corners varying from
/// `(-1, -1, -1)` to `(1, 1, 1)`. Reading or modifying its individual properties is costly but
/// applying the transformation is cheap.
#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Debug, Copy)] #[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Debug, Copy)]
pub struct OrthoMat3<N> { pub struct OrthoMat3<N> {
mat: Mat4<N> mat: Mat4<N>
@ -26,14 +32,16 @@ pub struct OrthoMat3<N> {
impl<N: BaseFloat> Ortho3<N> { impl<N: BaseFloat> Ortho3<N> {
/// Creates a new 3D orthographic projection. /// Creates a new 3D orthographic projection.
pub fn new(width: N, height: N, znear: N, zfar: N) -> Ortho3<N> { pub fn new(left: N, right: N, bottom: N, top: N, znear: N, zfar: N) -> Ortho3<N> {
assert!(!::is_zero(&(zfar - znear))); assert!(!::is_zero(&(zfar - znear)));
assert!(!::is_zero(&width)); assert!(!::is_zero(&(left - right)));
assert!(!::is_zero(&height)); assert!(!::is_zero(&(top - bottom)));
Ortho3 { Ortho3 {
width: width, left: left,
height: height, right: right,
bottom: bottom,
top: top,
znear: znear, znear: znear,
zfar: zfar zfar: zfar
} }
@ -41,37 +49,51 @@ impl<N: BaseFloat> Ortho3<N> {
/// Builds a 4D projection matrix (using homogeneous coordinates) for this projection. /// Builds a 4D projection matrix (using homogeneous coordinates) for this projection.
pub fn to_mat(&self) -> Mat4<N> { pub fn to_mat(&self) -> Mat4<N> {
self.to_ortho_mat().mat self.to_persp_mat().mat
} }
/// Build a `OrthoMat3` representing this projection. /// Build a `OrthoMat3` representing this projection.
pub fn to_ortho_mat(&self) -> OrthoMat3<N> { pub fn to_persp_mat(&self) -> OrthoMat3<N> {
OrthoMat3::new(self.width, self.height, self.znear, self.zfar) OrthoMat3::new(self.left, self.right, self.bottom, self.top, self.znear, self.zfar)
} }
} }
#[cfg(feature="arbitrary")] #[cfg(feature="arbitrary")]
impl<N: Arbitrary + BaseFloat> Arbitrary for Ortho3<N> { impl<N: Arbitrary + BaseFloat> Arbitrary for Ortho3<N> {
fn arbitrary<G: Gen>(g: &mut G) -> Ortho3<N> { fn arbitrary<G: Gen>(g: &mut G) -> Ortho3<N> {
let width = reject(g, |x| !::is_zero(x)); let left = Arbitrary::arbitrary(g);
let height = reject(g, |x| !::is_zero(x)); let right = reject(g, |x: &N| *x > left);
let znear = Arbitrary::arbitrary(g); let bottom = Arbitrary::arbitrary(g);
let zfar = reject(g, |&x: &N| !::is_zero(&(x - znear))); let top = reject(g, |x: &N| *x > bottom);
let znear = Arbitrary::arbitrary(g);
let zfar = reject(g, |x: &N| *x > znear);
Ortho3::new(width, height, znear, zfar) Ortho3::new(width, height, znear, zfar)
} }
} }
impl<N: BaseFloat + Clone> Ortho3<N> { impl<N: BaseFloat + Clone> Ortho3<N> {
/// The width of the view cuboid. /// The smallest x-coordinate of the view cuboid.
#[inline] #[inline]
pub fn width(&self) -> N { pub fn left(&self) -> N {
self.width.clone() self.left.clone()
} }
/// The height of the view cuboid. /// The largest x-coordinate of the view cuboid.
#[inline] #[inline]
pub fn height(&self) -> N { pub fn right(&self) -> N {
self.height.clone() self.right.clone()
}
/// The smallest y-coordinate of the view cuboid.
#[inline]
pub fn bottom(&self) -> N {
self.bottom.clone()
}
/// The largest y-coordinate of the view cuboid.
#[inline]
pub fn top(&self) -> N {
self.top.clone()
} }
/// The near plane offset of the view cuboid. /// The near plane offset of the view cuboid.
@ -86,27 +108,45 @@ impl<N: BaseFloat + Clone> Ortho3<N> {
self.zfar.clone() self.zfar.clone()
} }
/// Sets the width of the view cuboid. /// Sets the smallest x-coordinate of the view cuboid.
#[inline] #[inline]
pub fn set_width(&mut self, width: N) { pub fn set_left(&mut self, left: N) {
self.width = width assert!(left < self.right, "The left corner must be farther than the right corner.");
self.left = left
} }
/// Sets the height of the view cuboid. /// Sets the largest x-coordinate of the view cuboid.
#[inline] #[inline]
pub fn set_height(&mut self, height: N) { pub fn set_right(&mut self, right: N) {
self.height = height assert!(right > self.left, "The left corner must be farther than the right corner.");
self.right = right
}
/// Sets the smallest y-coordinate of the view cuboid.
#[inline]
pub fn set_bottom(&mut self, bottom: N) {
assert!(bottom < self.top, "The top corner must be higher than the bottom corner.");
self.bottom = bottom
}
/// Sets the largest y-coordinate of the view cuboid.
#[inline]
pub fn set_top(&mut self, top: N) {
assert!(top > self.bottom, "The top corner must be higher than the left corner.");
self.top = top
} }
/// Sets the near plane offset of the view cuboid. /// Sets the near plane offset of the view cuboid.
#[inline] #[inline]
pub fn set_znear(&mut self, znear: N) { pub fn set_znear(&mut self, znear: N) {
assert!(znear < self.zfar, "The far plane must be farther than the near plane.");
self.znear = znear self.znear = znear
} }
/// Sets the far plane offset of the view cuboid. /// Sets the far plane offset of the view cuboid.
#[inline] #[inline]
pub fn set_zfar(&mut self, zfar: N) { pub fn set_zfar(&mut self, zfar: N) {
assert!(zfar > self.znear, "The far plane must be farther than the near plane.");
self.zfar = zfar self.zfar = zfar
} }
@ -114,34 +154,47 @@ impl<N: BaseFloat + Clone> Ortho3<N> {
#[inline] #[inline]
pub fn project_pnt(&self, p: &Pnt3<N>) -> Pnt3<N> { pub fn project_pnt(&self, p: &Pnt3<N>) -> Pnt3<N> {
// FIXME: optimize that // FIXME: optimize that
self.to_ortho_mat().project_pnt(p) self.to_persp_mat().project_pnt(p)
} }
/// Projects a vector. /// Projects a vector.
#[inline] #[inline]
pub fn project_vec(&self, p: &Vec3<N>) -> Vec3<N> { pub fn project_vec(&self, p: &Vec3<N>) -> Vec3<N> {
// FIXME: optimize that // FIXME: optimize that
self.to_ortho_mat().project_vec(p) self.to_persp_mat().project_vec(p)
} }
} }
impl<N: BaseFloat> OrthoMat3<N> { impl<N: BaseFloat> OrthoMat3<N> {
/// Creates a new orthographic projection matrix from the width, heihgt, znear and zfar planes of the view cuboid. /// Creates a new orthographic projection matrix.
pub fn new(width: N, height: N, znear: N, zfar: N) -> OrthoMat3<N> { pub fn new(left: N, right: N, bottom: N, top: N, znear: N, zfar: N) -> OrthoMat3<N> {
assert!(!::is_zero(&(zfar - znear))); assert!(left < right, "The left corner must be farther than the right corner.");
assert!(!::is_zero(&width)); assert!(bottom < top, "The top corner must be higher than the bottom corner.");
assert!(!::is_zero(&height)); assert!(znear < zfar, "The far plane must be farther than the near plane.");
let mat: Mat4<N> = ::one(); let mat: Mat4<N> = ::one();
let mut res = OrthoMat3 { mat: mat }; let mut res = OrthoMat3 { mat: mat };
res.set_width(width); res.set_left_and_right(left, right);
res.set_height(height); res.set_bottom_and_top(bottom, top);
res.set_znear_and_zfar(znear, zfar); res.set_znear_and_zfar(znear, zfar);
res res
} }
/// Creates a new orthographic projection matrix from an aspect ratio and the vertical field of view.
pub fn new_with_fov(aspect: N, vfov: N, znear: N, zfar: N) -> OrthoMat3<N> {
assert!(znear < zfar, "The far plane must be farther than the near plane.");
assert!(!::is_zero(&aspect));
let _1: N = ::one();
let _2 = _1 + _1;
let width = zfar * (vfov / _2).tan();
let height = width / aspect;
OrthoMat3::new(-width / _2, width / _2, -height / _2, height / _2, znear, zfar)
}
/// Creates a new orthographic matrix from a 4D matrix. /// Creates a new orthographic matrix from a 4D matrix.
/// ///
/// This is unsafe because the input matrix is not checked to be a orthographic projection. /// This is unsafe because the input matrix is not checked to be a orthographic projection.
@ -158,42 +211,68 @@ impl<N: BaseFloat> OrthoMat3<N> {
&self.mat &self.mat
} }
/// The width of the view cuboid. /// The smallest x-coordinate of the view cuboid.
#[inline] #[inline]
pub fn width(&self) -> N { pub fn left(&self) -> N {
<N as Cast<f64>>::from(2.0) / self.mat.m11 (-::one::<N>() - self.mat.m14) / self.mat.m11
} }
/// The height of the view cuboid. /// The largest x-coordinate of the view cuboid.
#[inline] #[inline]
pub fn height(&self) -> N { pub fn right(&self) -> N {
<N as Cast<f64>>::from(2.0) / self.mat.m22 (::one::<N>() - self.mat.m14) / self.mat.m11
}
/// The smallest y-coordinate of the view cuboid.
#[inline]
pub fn bottom(&self) -> N {
(-::one::<N>() - self.mat.m24) / self.mat.m22
}
/// The largest y-coordinate of the view cuboid.
#[inline]
pub fn top(&self) -> N {
(::one::<N>() - self.mat.m24) / self.mat.m22
} }
/// The near plane offset of the view cuboid. /// The near plane offset of the view cuboid.
#[inline] #[inline]
pub fn znear(&self) -> N { pub fn znear(&self) -> N {
(self.mat.m34 + ::one()) / self.mat.m33 (::one::<N>() + self.mat.m34) / self.mat.m33
} }
/// The far plane offset of the view cuboid. /// The far plane offset of the view cuboid.
#[inline] #[inline]
pub fn zfar(&self) -> N { pub fn zfar(&self) -> N {
(self.mat.m34 - ::one()) / self.mat.m33 (-::one::<N>() + self.mat.m34) / self.mat.m33
} }
/// Sets the width of the view cuboid. /// Sets the smallest x-coordinate of the view cuboid.
#[inline] #[inline]
pub fn set_width(&mut self, width: N) { pub fn set_left(&mut self, left: N) {
assert!(!::is_zero(&width)); let right = self.right();
self.mat.m11 = <N as Cast<f64>>::from(2.0) / width; self.set_left_and_right(left, right);
} }
/// Sets the height of the view cuboid. /// Sets the largest x-coordinate of the view cuboid.
#[inline] #[inline]
pub fn set_height(&mut self, height: N) { pub fn set_right(&mut self, right: N) {
assert!(!::is_zero(&height)); let left = self.left();
self.mat.m22 = <N as Cast<f64>>::from(2.0) / height; self.set_left_and_right(left, right);
}
/// Sets the smallest y-coordinate of the view cuboid.
#[inline]
pub fn set_bottom(&mut self, bottom: N) {
let top = self.top();
self.set_bottom_and_top(bottom, top);
}
/// Sets the largest y-coordinate of the view cuboid.
#[inline]
pub fn set_top(&mut self, top: N) {
let bottom = self.bottom();
self.set_bottom_and_top(bottom, top);
} }
/// Sets the near plane offset of the view cuboid. /// Sets the near plane offset of the view cuboid.
@ -210,6 +289,22 @@ impl<N: BaseFloat> OrthoMat3<N> {
self.set_znear_and_zfar(znear, zfar); self.set_znear_and_zfar(znear, zfar);
} }
/// Sets the view cuboid coordinates along the `x` axis.
#[inline]
pub fn set_left_and_right(&mut self, left: N, right: N) {
assert!(left < right, "The left corner must be farther than the right corner.");
self.mat.m11 = <N as Cast<f64>>::from(2.0) / (right - left);
self.mat.m14 = -(right + left) / (right - left);
}
/// Sets the view cuboid coordinates along the `y` axis.
#[inline]
pub fn set_bottom_and_top(&mut self, bottom: N, top: N) {
assert!(bottom < top, "The top corner must be higher than the bottom corner.");
self.mat.m22 = <N as Cast<f64>>::from(2.0) / (top - bottom);
self.mat.m24 = -(top + bottom) / (top - bottom);
}
/// Sets the near and far plane offsets of the view cuboid. /// Sets the near and far plane offsets of the view cuboid.
#[inline] #[inline]
pub fn set_znear_and_zfar(&mut self, znear: N, zfar: N) { pub fn set_znear_and_zfar(&mut self, znear: N, zfar: N) {
@ -222,8 +317,8 @@ impl<N: BaseFloat> OrthoMat3<N> {
#[inline] #[inline]
pub fn project_pnt(&self, p: &Pnt3<N>) -> Pnt3<N> { pub fn project_pnt(&self, p: &Pnt3<N>) -> Pnt3<N> {
Pnt3::new( Pnt3::new(
self.mat.m11 * p.x, self.mat.m11 * p.x + self.mat.m14,
self.mat.m22 * p.y, self.mat.m22 * p.y + self.mat.m24,
self.mat.m33 * p.z + self.mat.m34 self.mat.m33 * p.z + self.mat.m34
) )
} }
@ -251,7 +346,7 @@ impl<N: BaseFloat + Clone> OrthoMat3<N> {
impl<N: Arbitrary + BaseFloat> Arbitrary for OrthoMat3<N> { impl<N: Arbitrary + BaseFloat> Arbitrary for OrthoMat3<N> {
fn arbitrary<G: Gen>(g: &mut G) -> OrthoMat3<N> { fn arbitrary<G: Gen>(g: &mut G) -> OrthoMat3<N> {
let x: Ortho3<N> = Arbitrary::arbitrary(g); let x: Ortho3<N> = Arbitrary::arbitrary(g);
x.to_ortho_mat() x.to_persp_mat()
} }
} }

View File

@ -7,18 +7,22 @@ use quickcheck::{Arbitrary, Gen};
/// A 3D perspective projection stored without any matrix. /// A 3D perspective projection stored without any matrix.
/// ///
/// Reading or modifying its individual properties is cheap but applying the transformation is costly. /// This maps a frustrum cube to the unit cube with corners varying from `(-1, -1, -1)` to
/// `(1, 1, 1)`. Reading or modifying its individual properties is cheap but applying the
/// transformation is costly.
#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Debug, Copy)] #[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Debug, Copy)]
pub struct Persp3<N> { pub struct Persp3<N> {
aspect: N, aspect: N,
fov: N, fovy: N,
znear: N, znear: N,
zfar: N zfar: N
} }
/// A 3D perspective projection stored as a 4D matrix. /// A 3D perspective projection stored as a 4D matrix.
/// ///
/// Reading or modifying its individual properties is costly but applying the transformation is cheap. /// This maps a frustrum to the unit cube with corners varying from `(-1, -1, -1)` to
/// `(1, 1, 1)`. Reading or modifying its individual properties is costly but applying the
/// transformation is cheap.
#[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Debug, Copy)] #[derive(Eq, PartialEq, RustcEncodable, RustcDecodable, Clone, Debug, Copy)]
pub struct PerspMat3<N> { pub struct PerspMat3<N> {
mat: Mat4<N> mat: Mat4<N>
@ -26,13 +30,13 @@ pub struct PerspMat3<N> {
impl<N: BaseFloat> Persp3<N> { impl<N: BaseFloat> Persp3<N> {
/// Creates a new 3D perspective projection. /// Creates a new 3D perspective projection.
pub fn new(aspect: N, fov: N, znear: N, zfar: N) -> Persp3<N> { pub fn new(aspect: N, fovy: N, znear: N, zfar: N) -> Persp3<N> {
assert!(!::is_zero(&(zfar - znear))); assert!(!::is_zero(&(zfar - znear)));
assert!(!::is_zero(&aspect)); assert!(!::is_zero(&aspect));
Persp3 { Persp3 {
aspect: aspect, aspect: aspect,
fov: fov, fovy: fovy,
znear: znear, znear: znear,
zfar: zfar zfar: zfar
} }
@ -45,7 +49,7 @@ impl<N: BaseFloat> Persp3<N> {
/// Build a `PerspMat3` representing this projection. /// Build a `PerspMat3` representing this projection.
pub fn to_persp_mat(&self) -> PerspMat3<N> { pub fn to_persp_mat(&self) -> PerspMat3<N> {
PerspMat3::new(self.aspect, self.fov, self.znear, self.zfar) PerspMat3::new(self.aspect, self.fovy, self.znear, self.zfar)
} }
} }
@ -66,10 +70,10 @@ impl<N: BaseFloat + Clone> Persp3<N> {
self.aspect.clone() self.aspect.clone()
} }
/// Gets the field of view of the view frustrum. /// Gets the y field of view of the view frustrum.
#[inline] #[inline]
pub fn fov(&self) -> N { pub fn fovy(&self) -> N {
self.fov.clone() self.fovy.clone()
} }
/// Gets the near plane offset of the view frustrum. /// Gets the near plane offset of the view frustrum.
@ -92,12 +96,12 @@ impl<N: BaseFloat + Clone> Persp3<N> {
self.aspect = aspect; self.aspect = aspect;
} }
/// Sets the field of view of the view frustrum. /// Sets the y field of view of the view frustrum.
/// ///
/// This method does not build any matrix. /// This method does not build any matrix.
#[inline] #[inline]
pub fn set_fov(&mut self, fov: N) { pub fn set_fovy(&mut self, fovy: N) {
self.fov = fov; self.fovy = fovy;
} }
/// Sets the near plane offset of the view frustrum. /// Sets the near plane offset of the view frustrum.
@ -132,19 +136,19 @@ impl<N: BaseFloat + Clone> Persp3<N> {
} }
impl<N: BaseFloat> PerspMat3<N> { impl<N: BaseFloat> PerspMat3<N> {
/// Creates a new persepctive matrix from the aspect ratio, field of view, and near/far planes. /// Creates a new perspective matrix from the aspect ratio, y field of view, and near/far planes.
pub fn new(aspect: N, fov: N, znear: N, zfar: N) -> PerspMat3<N> { pub fn new(aspect: N, fovy: N, znear: N, zfar: N) -> PerspMat3<N> {
assert!(!::is_zero(&(znear - zfar))); assert!(!::is_zero(&(znear - zfar)));
assert!(!::is_zero(&aspect)); assert!(!::is_zero(&aspect));
let mat: Mat4<N> = ::one(); let mat: Mat4<N> = ::one();
let mut res = PerspMat3 { mat: mat }; let mut res = PerspMat3 { mat: mat };
res.set_fov(fov); res.set_fovy(fovy);
res.set_aspect(aspect); res.set_aspect(aspect);
res.set_znear_and_zfar(znear, zfar); res.set_znear_and_zfar(znear, zfar);
res.mat.m44 = ::zero(); res.mat.m44 = ::zero();
res.mat.m43 = ::one(); res.mat.m43 = -::one::<N>();
res res
} }
@ -168,12 +172,12 @@ impl<N: BaseFloat> PerspMat3<N> {
/// Gets the `width / height` aspect ratio of the view frustrum. /// Gets the `width / height` aspect ratio of the view frustrum.
#[inline] #[inline]
pub fn aspect(&self) -> N { pub fn aspect(&self) -> N {
-self.mat.m22 / self.mat.m11 self.mat.m22 / self.mat.m11
} }
/// Gets the field of view of the view frustrum. /// Gets the y field of view of the view frustrum.
#[inline] #[inline]
pub fn fov(&self) -> N { pub fn fovy(&self) -> N {
let _1: N = ::one(); let _1: N = ::one();
let _2 = _1 + _1; let _2 = _1 + _1;
@ -185,7 +189,7 @@ impl<N: BaseFloat> PerspMat3<N> {
pub fn znear(&self) -> N { pub fn znear(&self) -> N {
let _1: N = ::one(); let _1: N = ::one();
let _2 = _1 + _1; let _2 = _1 + _1;
let ratio = (self.mat.m33 + _1) / (self.mat.m33 - _1); let ratio = (-self.mat.m33 + _1) / (-self.mat.m33 - _1);
self.mat.m34 / (_2 * ratio) - self.mat.m34 / _2 self.mat.m34 / (_2 * ratio) - self.mat.m34 / _2
} }
@ -195,29 +199,29 @@ impl<N: BaseFloat> PerspMat3<N> {
pub fn zfar(&self) -> N { pub fn zfar(&self) -> N {
let _1: N = ::one(); let _1: N = ::one();
let _2 = _1 + _1; let _2 = _1 + _1;
let ratio = (self.mat.m33 + _1) / (self.mat.m33 - _1); let ratio = (-self.mat.m33 + _1) / (-self.mat.m33 - _1);
(self.mat.m34 - ratio * self.mat.m34) / _2 (self.mat.m34 - ratio * self.mat.m34) / _2
} }
// FIXME: add a method to retriev znear and zfar at once ? // FIXME: add a method to retrieve znear and zfar simultaneously?
/// Updates this projection matrix with a new `width / height` aspect ratio of the view /// Updates this projection matrix with a new `width / height` aspect ratio of the view
/// frustrum. /// frustrum.
#[inline] #[inline]
pub fn set_aspect(&mut self, aspect: N) { pub fn set_aspect(&mut self, aspect: N) {
assert!(!::is_zero(&aspect)); assert!(!::is_zero(&aspect));
self.mat.m11 = -self.mat.m22 / aspect; self.mat.m11 = self.mat.m22 / aspect;
} }
/// Updates this projection with a new field of view of the view frustrum. /// Updates this projection with a new y field of view of the view frustrum.
#[inline] #[inline]
pub fn set_fov(&mut self, fov: N) { pub fn set_fovy(&mut self, fovy: N) {
let _1: N = ::one(); let _1: N = ::one();
let _2 = _1 + _1; let _2 = _1 + _1;
let old_m22 = self.mat.m22.clone(); let old_m22 = self.mat.m22.clone();
self.mat.m22 = _1 / (fov / _2).tan(); self.mat.m22 = _1 / (fovy / _2).tan();
self.mat.m11 = self.mat.m11 * (self.mat.m22 / old_m22); self.mat.m11 = self.mat.m11 * (self.mat.m22 / old_m22);
} }
@ -241,7 +245,7 @@ impl<N: BaseFloat> PerspMat3<N> {
let _1: N = ::one(); let _1: N = ::one();
let _2 = _1 + _1; let _2 = _1 + _1;
self.mat.m33 = -(zfar + znear) / (znear - zfar); self.mat.m33 = (zfar + znear) / (znear - zfar);
self.mat.m34 = zfar * znear * _2 / (znear - zfar); self.mat.m34 = zfar * znear * _2 / (znear - zfar);
} }
@ -249,7 +253,7 @@ impl<N: BaseFloat> PerspMat3<N> {
#[inline] #[inline]
pub fn project_pnt(&self, p: &Pnt3<N>) -> Pnt3<N> { pub fn project_pnt(&self, p: &Pnt3<N>) -> Pnt3<N> {
let _1: N = ::one(); let _1: N = ::one();
let inv_denom = _1 / p.z; let inv_denom = -_1 / p.z;
Pnt3::new( Pnt3::new(
self.mat.m11 * p.x * inv_denom, self.mat.m11 * p.x * inv_denom,
self.mat.m22 * p.y * inv_denom, self.mat.m22 * p.y * inv_denom,
@ -261,7 +265,7 @@ impl<N: BaseFloat> PerspMat3<N> {
#[inline] #[inline]
pub fn project_vec(&self, p: &Vec3<N>) -> Vec3<N> { pub fn project_vec(&self, p: &Vec3<N>) -> Vec3<N> {
let _1: N = ::one(); let _1: N = ::one();
let inv_denom = _1 / p.z; let inv_denom = -_1 / p.z;
Vec3::new( Vec3::new(
self.mat.m11 * p.x * inv_denom, self.mat.m11 * p.x * inv_denom,
self.mat.m22 * p.y * inv_denom, self.mat.m22 * p.y * inv_denom,

View File

@ -218,19 +218,36 @@ impl<N: Clone + BaseFloat> Rot3<N> {
} }
} }
/// Builds a look-at view matrix with no translational component.
/// Builds a right-handed look-at view matrix without translation.
/// ///
/// This conforms to the common notion of "look-at" matrix from the computer graphics community. /// This conforms to the common notion of right handed look-at matrix from the computer
/// Its maps the view direction `dir` to the **negative** `z` axis. /// graphics community.
/// ///
/// # Arguments /// # Arguments
/// * dir - The view direction. /// * eye - The eye position.
/// * up - The vertical direction. The only requirement of this parameter is to not be /// * target - The target position.
/// collinear to `dir`. /// * up - A vector approximately aligned with required the vertical axis. The only
/// requirement of this parameter is to not be collinear to `target - eye`.
#[inline] #[inline]
pub fn new_look_at(dir: &Vec3<N>, up: &Vec3<N>) -> Rot3<N> { pub fn look_at_rh(dir: &Vec3<N>, up: &Vec3<N>) -> Rot3<N> {
Rot3::new_observer_frame(&(-*dir), up).inv().unwrap() Rot3::new_observer_frame(&(-*dir), up).inv().unwrap()
} }
/// Builds a left-handed look-at view matrix without translation.
///
/// This conforms to the common notion of left handed look-at matrix from the computer
/// graphics community.
///
/// # Arguments
/// * eye - The eye position.
/// * target - The target position.
/// * up - A vector approximately aligned with required the vertical axis. The only
/// requirement of this parameter is to not be collinear to `target - eye`.
#[inline]
pub fn look_at_lh(dir: &Vec3<N>, up: &Vec3<N>) -> Rot3<N> {
Rot3::new_observer_frame(&(*dir), up).inv().unwrap()
}
} }
impl<N: Clone + BaseFloat + Cast<f64>> impl<N: Clone + BaseFloat + Cast<f64>>

View File

@ -2,8 +2,8 @@ extern crate nalgebra as na;
extern crate rand; extern crate rand;
use rand::random; use rand::random;
use na::{Pnt2, Pnt3, Vec2, Vec3, Vec1, Rot2, Rot3, Persp3, PerspMat3, Ortho3, OrthoMat3, Iso2, use na::{Pnt2, Pnt3, Vec2, Vec3, Vec1, Rot2, Rot3, Persp3, PerspMat3, Ortho3, OrthoMat3,
Iso3, Sim2, Sim3, BaseFloat, Transform}; Iso2, Iso3, Sim2, Sim3, BaseFloat, Transform};
#[test] #[test]
fn test_rotation2() { fn test_rotation2() {
@ -71,25 +71,27 @@ fn test_rot2_angle_between() {
#[test] #[test]
fn test_look_at_iso3() { fn test_look_at_rh_iso3() {
for _ in 0usize .. 10000 { for _ in 0usize .. 10000 {
let eye = random::<Pnt3<f64>>(); let eye = random::<Pnt3<f64>>();
let target = random::<Pnt3<f64>>(); let target = random::<Pnt3<f64>>();
let up = random::<Vec3<f64>>(); let up = random::<Vec3<f64>>();
let viewmat = Iso3::new_look_at(&eye, &target, &up); let viewmat = Iso3::look_at_rh(&eye, &target, &up);
assert_eq!(&(viewmat * eye), &na::orig()); let origin: Pnt3<f64> = na::orig();
assert_eq!(&(viewmat * eye), &origin);
assert!(na::approx_eq(&na::normalize(&(viewmat * (target - eye))), &-Vec3::z())); assert!(na::approx_eq(&na::normalize(&(viewmat * (target - eye))), &-Vec3::z()));
} }
} }
#[test] #[test]
fn test_look_at_rot3() { fn test_look_at_rh_rot3() {
for _ in 0usize .. 10000 { for _ in 0usize .. 10000 {
let dir = random::<Vec3<f64>>(); let dir = random::<Vec3<f64>>();
let up = random::<Vec3<f64>>(); let up = random::<Vec3<f64>>();
let viewmat = Rot3::new_look_at(&dir, &up); let viewmat = Rot3::look_at_rh(&dir, &up);
println!("found: {}", viewmat * dir);
assert!(na::approx_eq(&na::normalize(&(viewmat * dir)), &-Vec3::z())); assert!(na::approx_eq(&na::normalize(&(viewmat * dir)), &-Vec3::z()));
} }
} }
@ -124,16 +126,16 @@ fn test_persp() {
let mut pm = PerspMat3::new(42.0f64, 0.5, 1.5, 10.0); let mut pm = PerspMat3::new(42.0f64, 0.5, 1.5, 10.0);
assert!(p.to_mat() == pm.to_mat()); assert!(p.to_mat() == pm.to_mat());
assert!(p.aspect() == 42.0); assert!(p.aspect() == 42.0);
assert!(p.fov() == 0.5); assert!(p.fovy() == 0.5);
assert!(p.znear() == 1.5); assert!(p.znear() == 1.5);
assert!(p.zfar() == 10.0); assert!(p.zfar() == 10.0);
assert!(na::approx_eq(&pm.aspect(), &42.0)); assert!(na::approx_eq(&pm.aspect(), &42.0));
assert!(na::approx_eq(&pm.fov(), &0.5)); assert!(na::approx_eq(&pm.fovy(), &0.5));
assert!(na::approx_eq(&pm.znear(), &1.5)); assert!(na::approx_eq(&pm.znear(), &1.5));
assert!(na::approx_eq(&pm.zfar(), &10.0)); assert!(na::approx_eq(&pm.zfar(), &10.0));
p.set_fov(0.1); p.set_fovy(0.1);
pm.set_fov(0.1); pm.set_fovy(0.1);
assert!(na::approx_eq(&p.to_mat(), pm.as_mat())); assert!(na::approx_eq(&p.to_mat(), pm.as_mat()));
p.set_znear(24.0); p.set_znear(24.0);
@ -149,52 +151,60 @@ fn test_persp() {
assert!(na::approx_eq(&p.to_mat(), pm.as_mat())); assert!(na::approx_eq(&p.to_mat(), pm.as_mat()));
assert!(p.aspect() == 23.0); assert!(p.aspect() == 23.0);
assert!(p.fov() == 0.1); assert!(p.fovy() == 0.1);
assert!(p.znear() == 24.0); assert!(p.znear() == 24.0);
assert!(p.zfar() == 61.0); assert!(p.zfar() == 61.0);
assert!(na::approx_eq(&pm.aspect(), &23.0)); assert!(na::approx_eq(&pm.aspect(), &23.0));
assert!(na::approx_eq(&pm.fov(), &0.1)); assert!(na::approx_eq(&pm.fovy(), &0.1));
assert!(na::approx_eq(&pm.znear(), &24.0)); assert!(na::approx_eq(&pm.znear(), &24.0));
assert!(na::approx_eq(&pm.zfar(), &61.0)); assert!(na::approx_eq(&pm.zfar(), &61.0));
} }
#[test] #[test]
fn test_ortho() { fn test_ortho() {
let mut p = Ortho3::new(42.0f64, 0.5, 1.5, 10.0); let mut p = Ortho3::new(-0.3, 5.2, -3.9, -1.0, 1.5, 10.0);
let mut pm = OrthoMat3::new(42.0f64, 0.5, 1.5, 10.0); let mut pm = OrthoMat3::new(-0.3, 5.2, -3.9, -1.0, 1.5, 10.0);
assert!(p.to_mat() == pm.to_mat()); assert!(p.to_mat() == pm.to_mat());
assert!(p.width() == 42.0); assert!(p.left() == -0.3);
assert!(p.height() == 0.5); assert!(p.right() == 5.2);
assert!(p.bottom() == -3.9);
assert!(p.top() == -1.0);
assert!(p.znear() == 1.5); assert!(p.znear() == 1.5);
assert!(p.zfar() == 10.0); assert!(p.zfar() == 10.0);
assert!(na::approx_eq(&pm.width(), &42.0)); assert!(na::approx_eq(&pm.left(), &-0.3));
assert!(na::approx_eq(&pm.height(), &0.5)); assert!(na::approx_eq(&pm.right(), &5.2));
assert!(na::approx_eq(&pm.bottom(), &-3.9));
assert!(na::approx_eq(&pm.top(), &-1.0));
assert!(na::approx_eq(&pm.znear(), &1.5)); assert!(na::approx_eq(&pm.znear(), &1.5));
assert!(na::approx_eq(&pm.zfar(), &10.0)); assert!(na::approx_eq(&pm.zfar(), &10.0));
p.set_width(0.1); p.set_left(0.1);
pm.set_width(0.1); pm.set_left(0.1);
assert!(na::approx_eq(&p.to_mat(), pm.as_mat())); assert!(na::approx_eq(&p.to_mat(), pm.as_mat()));
p.set_znear(24.0); p.set_right(10.1);
pm.set_znear(24.0); pm.set_right(10.1);
assert!(na::approx_eq(&p.to_mat(), pm.as_mat()));
p.set_top(24.0);
pm.set_top(24.0);
assert!(na::approx_eq(&p.to_mat(), pm.as_mat()));
p.set_bottom(-23.0);
pm.set_bottom(-23.0);
assert!(na::approx_eq(&p.to_mat(), pm.as_mat())); assert!(na::approx_eq(&p.to_mat(), pm.as_mat()));
p.set_zfar(61.0); p.set_zfar(61.0);
pm.set_zfar(61.0); pm.set_zfar(61.0);
assert!(na::approx_eq(&p.to_mat(), pm.as_mat())); assert!(na::approx_eq(&p.to_mat(), pm.as_mat()));
p.set_height(23.0); p.set_znear(21.0);
pm.set_height(23.0); pm.set_znear(21.0);
assert!(na::approx_eq(&p.to_mat(), pm.as_mat())); assert!(na::approx_eq(&p.to_mat(), pm.as_mat()));
assert!(p.height() == 23.0); assert!(p.znear() == 21.0);
assert!(p.width() == 0.1);
assert!(p.znear() == 24.0);
assert!(p.zfar() == 61.0); assert!(p.zfar() == 61.0);
assert!(na::approx_eq(&pm.height(), &23.0)); assert!(na::approx_eq(&pm.znear(), &21.0));
assert!(na::approx_eq(&pm.width(), &0.1));
assert!(na::approx_eq(&pm.znear(), &24.0));
assert!(na::approx_eq(&pm.zfar(), &61.0)); assert!(na::approx_eq(&pm.zfar(), &61.0));
} }