use core::f32::consts;
use nalgebra::{Isometry3, Perspective3, Point3, Vector3};
pub struct Camera {
pub position: Point3<f32>,
fov: f32,
pub near: f32,
pub far: f32,
view_matrix: nalgebra::Matrix4<f32>,
projection_matrix: nalgebra::Matrix4<f32>,
pub vp_matrix: nalgebra::Matrix4<f32>,
target: Point3<f32>,
aspect_ratio: f32,
}
impl Camera {
pub fn new(aspect_ratio: f32) -> Camera {
let mut ret = Camera {
position: Point3::new(0.0, 0.0, 0.0),
fov: consts::PI / 2.0,
view_matrix: nalgebra::Matrix4::identity(),
projection_matrix: nalgebra::Matrix4::identity(),
vp_matrix: nalgebra::Matrix4::identity(),
target: Point3::new(0.0, 0.0, 0.0),
aspect_ratio,
near: 0.4,
far: 20.0,
};
ret.update_projection();
ret
}
pub fn set_position(&mut self, pos: Point3<f32>) {
self.position = pos;
self.update_view();
}
pub fn set_fovy(&mut self, fovy: f32) {
self.fov = fovy;
self.update_projection();
}
pub fn set_near(&mut self, near: f32) {
self.near = near;
self.update_projection();
}
pub fn set_far(&mut self, far: f32) {
self.far = far;
self.update_projection();
}
pub fn set_near_far(&mut self, near: f32, far: f32) {
self.near = near;
self.far = far;
self.update_projection();
}
pub fn get_near_far_ratio(&self) -> f32 {
self.far / self.near
}
pub fn set_target(&mut self, target: Point3<f32>) {
self.target = target;
self.update_view();
}
pub fn get_direction(&self) -> Vector3<f32> {
let transpose = self.view_matrix;
Vector3::new(transpose[(2, 0)], transpose[(2, 1)], transpose[(2, 2)])
}
pub fn get_aspect_ratio(&self) -> f32 {
self.aspect_ratio
}
fn update_view(&mut self) {
let view = Isometry3::look_at_rh(&self.position, &self.target, &Vector3::y());
self.view_matrix = view.to_homogeneous();
self.vp_matrix = self.projection_matrix * self.view_matrix;
}
fn update_projection(&mut self) {
let projection = Perspective3::new(self.aspect_ratio, self.fov, self.near, self.far);
self.projection_matrix = projection.to_homogeneous();
self.vp_matrix = self.projection_matrix * self.view_matrix;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_camera_creation() {
let camera = Camera::new(16.0 / 9.0);
assert!((camera.get_aspect_ratio() - 16.0 / 9.0).abs() < 0.001);
assert_eq!(camera.near, 0.4);
assert_eq!(camera.far, 20.0);
assert_eq!(camera.position, Point3::new(0.0, 0.0, 0.0));
}
#[test]
fn test_camera_set_position() {
let mut camera = Camera::new(1.0);
let new_pos = Point3::new(5.0, 10.0, 15.0);
camera.set_position(new_pos);
assert_eq!(camera.position, new_pos);
}
#[test]
fn test_camera_set_target() {
let mut camera = Camera::new(1.0);
let target = Point3::new(1.0, 2.0, 3.0);
camera.set_target(target);
assert_eq!(camera.target, target);
}
#[test]
fn test_camera_set_fovy() {
let mut camera = Camera::new(1.0);
let new_fov = core::f32::consts::PI / 4.0; camera.set_fovy(new_fov);
assert!((camera.fov - new_fov).abs() < 0.001);
}
#[test]
fn test_camera_get_direction() {
let mut camera = Camera::new(1.0);
camera.set_position(Point3::new(0.0, 0.0, 5.0));
camera.set_target(Point3::new(0.0, 0.0, 0.0));
let direction = camera.get_direction();
assert!(direction.magnitude() > 0.0);
}
#[test]
fn test_camera_vp_matrix_updates() {
let mut camera = Camera::new(1.0);
let initial_vp = camera.vp_matrix;
camera.set_position(Point3::new(5.0, 5.0, 5.0));
assert_ne!(camera.vp_matrix, initial_vp);
let after_pos = camera.vp_matrix;
camera.set_fovy(core::f32::consts::PI / 4.0);
assert_ne!(camera.vp_matrix, after_pos);
}
}