//! This module contains everything to deal with cameras. use nalgebra::Matrix4; use nalgebra::Vector4; use math::vector::{Vector2, Vector3}; use math::frustum::Frustum; /// Converts a Matrix4 into a Matrix4 pub fn mat_to_f32(mat: Matrix4) -> Matrix4 { mat.map(|x| x as f32) } /// The trait that a render camera should implement. /// /// It allows the renderer to use it. pub trait RenderCamera { /// Returns the view matrix of the camera. fn view(&self) -> Matrix4; /// Returns the perspective matrix of the camera. fn perspective(&self) -> Matrix4; /// Returns the product of the perspective matrix and the view matrix. fn model_view(&self) -> Matrix4 { self.perspective() * self.view() } /// Returns the frustum of the camera. fn frustum(&self) -> Frustum { panic!(); } } /// Creates the pose matrix, inverse of the look at matrix. pub fn pose(position: Vector3, target: Vector3, up: Vector3) -> Matrix4 { // This is the right way to do things let e3 = (position - target).normalized(); // Well, ok, maybe this is not the right way, but it works let e1 = up.cross_product(e3).normalized(); let e2 = e3.cross_product(e1); [ [e1[0], e1[1], e1[2], 0.0], [e2[0], e2[1], e2[2], 0.0], [e3[0], e3[1], e3[2], 0.0], [position[0], position[1], position[2], 1.0], ].into() } /// Creates a look at matrix from the center, the target pointed by the camera and the up vector. pub fn look_at(position: Vector3, target: Vector3, up: Vector3) -> Matrix4 { pose(position, target, up).try_inverse().unwrap() } /// Creates a perspective matrix of a camera. pub fn perspective(fov: f64, aspect_ratio: f64, z_near: f64, z_far: f64) -> Matrix4 { let top = z_near * (fov / 2.0).tan(); let height = 2.0 * top; let width = aspect_ratio * height; let x = 2.0 * z_near / width; let y = 2.0 * z_near / height; let c = - (z_far + z_near) / (z_far - z_near); let d = - 2.0 * z_far * z_near / (z_far - z_near); Matrix4::new( x, 0.0, 0.0, 0.0, 0.0, y, 0.0, 0.0, 0.0, 0.0, c, d, 0.0, 0.0, -1.0, 0.0, ) } /// Returns the inverse of the perspective matrix. pub fn perspective_inverse(fov: f64, aspect_ratio: f64, z_near: f64, z_far: f64) -> Matrix4 { let top = z_near * (fov / 2.0).tan(); let height = 2.0 * top; let width = aspect_ratio * height; let x = 2.0 * z_near / width; let y = 2.0 * z_near / height; let c = - (z_far + z_near) / (z_far - z_near); let d = - 2.0 * z_far * z_near / (z_far - z_near); Matrix4::new( 1.0 / x, 0.0, 0.0, 0.0, 0.0, 1.0 / y, 0.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 1.0 / d, c / d, ) } /// A simple camera with its position, target and up vector. #[derive(Clone, Debug)] pub struct Camera { /// The position of the center of the camera. pub position: Vector3, /// The 3D point the camera is targetting. pub target: Vector3, /// The up vector of the camera. pub up: Vector3, /// The field of view of the camera. pub fov: f64, /// The minimum depth for visible things. pub z_near: f64, /// The maximum depth for visible things. pub z_far: f64, /// The aspect ratio of the camera. pub aspect_ratio: f64, } impl Camera { /// Creates a new camera from its attributes. pub fn new(position: Vector3, target: Vector3, up: Vector3) -> Camera { use std::f64::consts::PI; Camera { position: position, target: target, up: up, z_near: 0.0001, z_far: 1000.0, aspect_ratio: 16.0 / 9.0, fov: PI / 3.0, } } /// Returns the pose matrix of the camera. pub fn pose(&self) -> Matrix4 { pose(self.position, self.target, self.up) } /// Returns the view matrix of the camera, inverse of the pose. pub fn view(&self) -> Matrix4 { look_at(self.position, self.target, self.up) } /// Returns the perspective matrix of the camera. pub fn perspective(&self) -> Matrix4 { perspective(self.fov, self.aspect_ratio, self.z_near, self.z_far) } /// Returns the inverse of the perspective matrix of the camera. pub fn perspective_inverse(&self) -> Matrix4 { perspective_inverse(self.fov, self.aspect_ratio, self.z_near, self.z_far) } /// Returns the frustum of the camera. pub fn frustum(&self) -> Frustum { Frustum::from_matrix(&(self.perspective() * self.view())) } /// Unprojects a 2D point (x, y) in the 3D world. /// /// The coordinates must be in [-1.0, 1.0] pub fn unproject(&self, point: Vector2) -> Vector3 { let point = Vector4::new(point[0], point[1], 0.5, 1.0); let v = self.pose() * self.perspective_inverse() * point; Vector3::new(v[0] / v[3], v[1] / v[3], v[2] / v[3]) } } impl RenderCamera for Camera { fn view(&self) -> Matrix4 { self.view() } fn perspective(&self) -> Matrix4 { self.perspective() } }