use nalgebra::{Point3, Vector3, Matrix4, Perspective3};
use std::f32::consts::PI;
#[derive(Debug, Clone)]
pub struct Camera {
pub position: Point3<f32>,
pub target: Point3<f32>,
pub up: Vector3<f32>,
pub fov: f32,
pub fovy_degrees: f32, pub aspect_ratio: f32,
pub near: f32,
pub far: f32,
pub radius: f32,
pub theta: f32, pub phi: f32,
default_position: Point3<f32>,
default_target: Point3<f32>,
default_up: Vector3<f32>,
}
impl Camera {
pub fn new(
position: Point3<f32>,
target: Point3<f32>,
up: Vector3<f32>,
fovy_degrees: f32,
aspect_ratio: f32,
near: f32,
far: f32,
) -> Self {
let direction = position - target;
let radius = direction.magnitude();
let theta = direction.z.atan2(direction.x);
let phi = (direction.y / radius).asin();
let fov = fovy_degrees.to_radians();
Self {
position,
target,
up,
fov,
fovy_degrees,
aspect_ratio,
near,
far,
radius,
theta,
phi,
default_position: position,
default_target: target,
default_up: up,
}
}
pub fn view_matrix(&self) -> Matrix4<f32> {
Matrix4::look_at_rh(&self.position, &self.target, &self.up)
}
pub fn projection_matrix(&self) -> Matrix4<f32> {
let perspective = Perspective3::new(self.aspect_ratio, self.fov, self.near, self.far);
perspective.into_inner()
}
pub fn move_forward(&mut self, distance: f32) {
self.radius = (self.radius - distance).max(0.1);
self.update_position_from_spherical();
}
pub fn orbit(&mut self, delta_theta: f32, delta_phi: f32) {
self.theta += delta_theta;
self.phi = (self.phi + delta_phi).clamp(-PI/2.0 + 0.1, PI/2.0 - 0.1);
self.update_position_from_spherical();
}
pub fn pan(&mut self, delta_x: f32, delta_y: f32) {
let forward = (self.target - self.position).normalize();
let right = forward.cross(&self.up).normalize();
let up = right.cross(&forward).normalize();
let pan_vector = right * delta_x + up * delta_y;
self.position += pan_vector;
self.target += pan_vector;
}
pub fn zoom_fov(&mut self, delta: f32) {
self.fov = (self.fov + delta).clamp(0.1, PI - 0.1);
self.fovy_degrees = self.fov.to_degrees();
}
pub fn zoom(&mut self, delta: f32) {
self.radius = (self.radius - delta).max(0.1);
self.update_position_from_spherical();
}
pub fn reset(&mut self) {
self.position = self.default_position;
self.target = self.default_target;
self.up = self.default_up;
let direction = self.position - self.target;
self.radius = direction.magnitude();
self.theta = direction.z.atan2(direction.x);
self.phi = (direction.y / self.radius).asin();
}
fn update_position_from_spherical(&mut self) {
let x = self.radius * self.phi.cos() * self.theta.cos();
let y = self.radius * self.phi.sin();
let z = self.radius * self.phi.cos() * self.theta.sin();
self.position = self.target + Vector3::new(x, y, z);
}
pub fn reset_to_target(&mut self, target: Point3<f32>, radius: f32) {
self.target = target;
self.radius = radius;
self.theta = 0.0;
self.phi = 0.0;
self.update_position_from_spherical();
}
pub fn forward(&self) -> Vector3<f32> {
(self.target - self.position).normalize()
}
pub fn right(&self) -> Vector3<f32> {
self.forward().cross(&self.up).normalize()
}
pub fn camera_up(&self) -> Vector3<f32> {
self.right().cross(&self.forward()).normalize()
}
pub fn set_aspect_ratio(&mut self, aspect_ratio: f32) {
self.aspect_ratio = aspect_ratio;
}
pub fn distance_to_target(&self) -> f32 {
(self.position - self.target).magnitude()
}
pub fn set_fov_degrees(&mut self, fovy_degrees: f32) {
self.fovy_degrees = fovy_degrees.clamp(1.0, 179.0);
self.fov = self.fovy_degrees.to_radians();
}
pub fn fov_degrees(&self) -> f32 {
self.fovy_degrees
}
}
impl Default for Camera {
fn default() -> Self {
Self::new(
Point3::new(0.0, 0.0, 5.0),
Point3::new(0.0, 0.0, 0.0),
Vector3::new(0.0, 1.0, 0.0),
45.0, 16.0 / 9.0,
0.1,
100.0,
)
}
}