use glam::{Mat4, Quat, Vec2, Vec3}; use std::time::Instant; use winit::event::{ElementState, VirtualKeyCode}; pub trait Camera { fn get_eye(&self) -> [f32; 4]; fn get_vp(&self) -> [[f32; 4]; 4]; } pub struct Flycam { // input is_up_pressed: bool, is_down_pressed: bool, is_forward_pressed: bool, is_backward_pressed: bool, is_left_pressed: bool, is_right_pressed: bool, mouse_dx: f32, mouse_dy: f32, // state last_update: Instant, pan: f32, tilt: f32, position: Vec3, // constants speed: f32, turn_speed: f32, aspect: f32, fovy: f32, znear: f32, zfar: f32, } impl Flycam { pub fn new(speed: f32, turn_speed: f32) -> Self { Self { is_up_pressed: false, is_down_pressed: false, is_forward_pressed: false, is_backward_pressed: false, is_left_pressed: false, is_right_pressed: false, mouse_dx: 0.0, mouse_dy: 0.0, last_update: Instant::now(), pan: 0.0, tilt: 0.0, position: Vec3::new(0.0, 0.5, 1.0), speed, turn_speed, aspect: 1.0, // TODO compute from size fovy: std::f32::consts::FRAC_PI_2, znear: 0.01, zfar: 100.0, } } } impl Flycam { pub fn process_keyboard(&mut self, key: VirtualKeyCode, state: ElementState) { let is_pressed = state == ElementState::Pressed; match key { VirtualKeyCode::Space => { self.is_up_pressed = is_pressed; } VirtualKeyCode::LShift => { self.is_down_pressed = is_pressed; } VirtualKeyCode::W | VirtualKeyCode::Up => { self.is_forward_pressed = is_pressed; } VirtualKeyCode::A | VirtualKeyCode::Left => { self.is_left_pressed = is_pressed; } VirtualKeyCode::S | VirtualKeyCode::Down => { self.is_backward_pressed = is_pressed; } VirtualKeyCode::D | VirtualKeyCode::Right => { self.is_right_pressed = is_pressed; } _ => {} } } pub fn process_mouse(&mut self, mouse_dx: f64, mouse_dy: f64) { self.mouse_dx += mouse_dx as f32; self.mouse_dy += mouse_dy as f32; } pub fn resize(&mut self, width: u32, height: u32) { self.aspect = (width as f32) / (height as f32); } pub fn update(&mut self) { let dt = self.last_update.elapsed(); self.last_update = Instant::now(); let dt = dt.as_micros() as f32 / 1_000_000.0; let t = self.turn_speed; self.pan += t * self.mouse_dx; self.tilt += t * self.mouse_dy; self.mouse_dx = 0.0; self.mouse_dy = 0.0; let tilt_limit = std::f32::consts::FRAC_PI_2; if self.tilt < -tilt_limit { self.tilt = -tilt_limit; } else if self.tilt > tilt_limit { self.tilt = tilt_limit; } let s = dt * self.speed; let axis = Self::key_axis; let truck = s * axis(self.is_backward_pressed, self.is_forward_pressed); let dolly = s * axis(self.is_right_pressed, self.is_left_pressed); let boom = s * axis(self.is_down_pressed, self.is_up_pressed); self.move_position(truck, dolly, boom); } fn key_axis(negative: bool, positive: bool) -> f32 { if negative { if positive { 0.0 } else { -1.0 } } else { if positive { 1.0 } else { 0.0 } } } fn move_position(&mut self, truck: f32, dolly: f32, boom: f32) { // truck direction from straight down let h = Vec2::new(self.pan.sin(), -self.pan.cos()); // truck direction from the side let v = Vec2::new(self.tilt.cos(), -self.tilt.sin()); // composite to get forward direction let truck_to = Vec3::new(h.x * v.x, v.y, h.y * v.x); let dolly_to = Vec3::new(-self.pan.cos(), 0.0, -self.pan.sin()); self.position += (truck_to * truck) + (dolly_to * dolly); self.position.y += boom; } } impl Camera for Flycam { fn get_eye(&self) -> [f32; 4] { self.position.extend(0.0).to_array() } fn get_vp(&self) -> [[f32; 4]; 4] { let orientation = Quat::from_euler(glam::EulerRot::XYZ, self.tilt, self.pan, 0.0); let rotation = Mat4::from_quat(orientation); let view = rotation * Mat4::from_translation(-self.position); let proj = Mat4::perspective_rh_gl(self.fovy, self.aspect, self.znear, self.zfar); let vp = proj * view; vp.to_cols_array_2d() } }