use bytemuck::{Pod, Zeroable}; use winit::event::*; #[rustfmt::skip] pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4 = cgmath::Matrix4::new( 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.5, 1.0, ); pub struct Camera { pub eye: cgmath::Point3, pub target: cgmath::Point3, pub up: cgmath::Vector3, pub aspect: f32, pub fovy: f32, pub znear: f32, pub zfar: f32, } impl Camera { pub fn build_view_projection_matrix(&self) -> cgmath::Matrix4 { let view = cgmath::Matrix4::look_at_rh(self.eye, self.target, self.up); let proj = cgmath::perspective(cgmath::Deg(self.fovy), self.aspect, self.znear, self.zfar); OPENGL_TO_WGPU_MATRIX * proj * view } } // This is so we can store this in a buffer #[repr(C)] #[derive(Debug, Copy, Clone, Pod, Zeroable)] pub struct CameraUniform { // We can't use cgmath with bytemuck directly so we'll have // to convert the Matrix4 into a 4x4 f32 array pub view_proj: [[f32; 4]; 4], } impl CameraUniform { pub fn new() -> Self { use cgmath::SquareMatrix; Self { view_proj: cgmath::Matrix4::identity().into(), } } pub fn update_view_proj(&mut self, camera: &Camera) { self.view_proj = camera.build_view_projection_matrix().into(); } } pub struct CameraController { pub speed: f32, pub is_up_pressed: bool, pub is_down_pressed: bool, pub is_forward_pressed: bool, pub is_backward_pressed: bool, pub is_left_pressed: bool, pub is_right_pressed: bool, } impl CameraController { pub fn new(speed: f32) -> Self { Self { speed, is_up_pressed: false, is_down_pressed: false, is_forward_pressed: false, is_backward_pressed: false, is_left_pressed: false, is_right_pressed: false, } } pub fn process_events(&mut self, event: &WindowEvent) -> bool { match event { WindowEvent::KeyboardInput { input: KeyboardInput { state, virtual_keycode: Some(keycode), .. }, .. } => { let is_pressed = *state == ElementState::Pressed; match keycode { VirtualKeyCode::Space => { self.is_up_pressed = is_pressed; true } VirtualKeyCode::LShift => { self.is_down_pressed = is_pressed; true } VirtualKeyCode::W | VirtualKeyCode::Up => { self.is_forward_pressed = is_pressed; true } VirtualKeyCode::A | VirtualKeyCode::Left => { self.is_left_pressed = is_pressed; true } VirtualKeyCode::S | VirtualKeyCode::Down => { self.is_backward_pressed = is_pressed; true } VirtualKeyCode::D | VirtualKeyCode::Right => { self.is_right_pressed = is_pressed; true } _ => false, } } _ => false, } } pub fn update_camera(&self, camera: &mut Camera) { use cgmath::InnerSpace; let forward = camera.target - camera.eye; let forward_norm = forward.normalize(); let forward_mag = forward.magnitude(); // Prevents glitching when camera gets too close to the // center of the scene. if self.is_forward_pressed && forward_mag > self.speed { camera.eye += forward_norm * self.speed; } if self.is_backward_pressed { camera.eye -= forward_norm * self.speed; } let right = forward_norm.cross(camera.up); // Redo radius calc in case the up/ down is pressed. let forward = camera.target - camera.eye; let forward_mag = forward.magnitude(); if self.is_right_pressed { // Rescale the distance between the target and eye so // that it doesn't change. The eye therefore still // lies on the circle made by the target and eye. camera.eye = camera.target - (forward + right * self.speed).normalize() * forward_mag; } if self.is_left_pressed { camera.eye = camera.target - (forward - right * self.speed).normalize() * forward_mag; } } }