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(feature = "dsp")]
pub fn smooth_track_dsp(&mut self, target: Point3<f32>, alpha: f32) {
let alpha_clamped = alpha.clamp(0.01, 1.0);
let cur = self.target;
let smoothed_x = cur.x + (target.x - cur.x) * alpha_clamped;
let smoothed_y = cur.y + (target.y - cur.y) * alpha_clamped;
let smoothed_z = cur.z + (target.z - cur.z) * alpha_clamped;
self.set_target(Point3::new(smoothed_x, smoothed_y, smoothed_z));
}
}
#[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);
}
#[test]
fn test_camera_projection_target_center() {
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 p_target = nalgebra::Vector4::new(0.0, 0.0, 0.0, 1.0);
let clip = camera.vp_matrix * p_target;
assert!(clip.x.abs() < 1e-4);
assert!(clip.y.abs() < 1e-4);
assert!(clip.w > 0.0); }
#[test]
fn test_camera_view_matrix_orthogonality() {
let mut camera = Camera::new(16.0 / 9.0);
camera.set_position(Point3::new(3.0, 4.0, 5.0));
camera.set_target(Point3::new(0.0, 1.0, 0.0));
let R = camera.view_matrix.fixed_view::<3, 3>(0, 0);
let I = R * R.transpose();
let identity = nalgebra::Matrix3::identity();
let diff = (I - identity).norm();
assert!(
diff < 1e-4,
"View matrix rotation is not orthogonal: diff = {}",
diff
);
}
}