diff --git a/src/camera.rs b/src/camera.rs index 95794ef..98a9bf7 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -9,8 +9,10 @@ pub trait Camera { pub struct Flycam { // input - is_up_pressed: bool, - is_down_pressed: bool, + is_world_up_pressed: bool, + is_world_down_pressed: bool, + is_cam_up_pressed: bool, + is_cam_down_pressed: bool, is_forward_pressed: bool, is_backward_pressed: bool, is_left_pressed: bool, @@ -19,12 +21,15 @@ pub struct Flycam { mouse_dy: f32, // state last_update: Instant, - pan: f32, - tilt: f32, + euler_y: f32, + euler_x: f32, + velocity: Vec3, position: Vec3, // constants - speed: f32, - turn_speed: f32, + turn_sensitivity: f32, + thrust_mag: f32, + friction_coeff: f32, + drag_coeff: f32, aspect: f32, fovy: f32, znear: f32, @@ -32,10 +37,12 @@ pub struct Flycam { } impl Flycam { - pub fn new(speed: f32, turn_speed: f32) -> Self { + pub fn new(turn_sensitivity: f32, thrust_mag: f32, friction_coeff: f32, drag_coeff: f32) -> Self { Self { - is_up_pressed: false, - is_down_pressed: false, + is_world_up_pressed: false, + is_world_down_pressed: false, + is_cam_up_pressed: false, + is_cam_down_pressed: false, is_forward_pressed: false, is_backward_pressed: false, is_left_pressed: false, @@ -43,11 +50,14 @@ impl Flycam { mouse_dx: 0.0, mouse_dy: 0.0, last_update: Instant::now(), - pan: 0.0, - tilt: 0.0, + euler_y: 0.0, + euler_x: 0.0, + velocity: Vec3::new(0.0, 0.0, 0.0), position: Vec3::new(0.0, 0.5, 1.0), - speed, - turn_speed, + turn_sensitivity, + thrust_mag, + friction_coeff, + drag_coeff, aspect: 1.0, // TODO compute from size fovy: std::f32::consts::FRAC_PI_2, znear: 0.01, @@ -61,10 +71,16 @@ impl Flycam { let is_pressed = state == ElementState::Pressed; match key { VirtualKeyCode::Space => { - self.is_up_pressed = is_pressed; + self.is_world_up_pressed = is_pressed; } VirtualKeyCode::LShift => { - self.is_down_pressed = is_pressed; + self.is_world_down_pressed = is_pressed; + } + VirtualKeyCode::Q => { + self.is_cam_down_pressed = is_pressed; + } + VirtualKeyCode::E => { + self.is_cam_up_pressed = is_pressed; } VirtualKeyCode::W | VirtualKeyCode::Up => { self.is_forward_pressed = is_pressed; @@ -96,55 +112,82 @@ impl Flycam { 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.update_orientation(dt); + self.update_kinematic(dt); + } + + fn update_orientation(&mut self, dt: f32) { + let t = self.turn_sensitivity; + + self.euler_x -= t * self.mouse_dy; // mouse +y = 2D plane down = look down = 3d space -x + self.euler_y -= t * self.mouse_dx; // mouse +x = 2D plane right = look to the right = 3d space -y + 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; + // Clamp euler_x to [-pi/2, pi/2] + let euler_x_limit = std::f32::consts::FRAC_PI_2; + if self.euler_x < -euler_x_limit { + self.euler_x = -euler_x_limit; + } else if self.euler_x > euler_x_limit { + self.euler_x = euler_x_limit; } + } - let s = dt * self.speed; + fn update_kinematic(&mut self, dt: f32) { + let net_acc = self.get_thrust_acc() + self.get_friction_acc() + self.get_drag_acc(); + + let delta_vel = net_acc * dt; + self.velocity += delta_vel; + + let delta_pos = self.velocity * dt; + self.position += delta_pos; + } + + fn get_thrust_acc(&self) -> glam::Vec3 { 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); + + let thruster_cam_x = axis(self.is_left_pressed, self.is_right_pressed); + let thruster_cam_y = axis(self.is_cam_down_pressed, self.is_cam_up_pressed); + let thruster_cam_z = -axis(self.is_backward_pressed, self.is_forward_pressed); // forward is -z + let thruster_world_y = axis(self.is_world_down_pressed, self.is_world_up_pressed); + + let thrusters_cam = Vec3::new(thruster_cam_x, thruster_cam_y, thruster_cam_z); + let thrusters_world = Vec3::new(0.0, thruster_world_y, 0.0); + + let cam_to_world = self.get_orientation(); + let thrusters_total = thrusters_world + cam_to_world * thrusters_cam; + + self.thrust_mag * thrusters_total + } + + fn get_friction_acc(&self) -> glam::Vec3 { + self.friction_coeff * -self.velocity + } + + fn get_drag_acc(&self) -> glam::Vec3 { + 0.5 * self.drag_coeff * -self.velocity * self.velocity.length() } fn key_axis(negative: bool, positive: bool) -> f32 { if negative { if positive { - 0.0 + 0.0 // positive + negative cancel out } else { - -1.0 + -1.0 // negative only } } else { if positive { - 1.0 + 1.0 // positive only } else { - 0.0 + 0.0 // neutral } } } - 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; + fn get_orientation(&self) -> glam::Quat { + // returns rotation from camera axes to world axes + Quat::from_euler(glam::EulerRot::YXZ, self.euler_y, self.euler_x, 0.0) } } @@ -154,10 +197,14 @@ impl Camera for Flycam { } 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); + // view matrix is inverted camera pose (world space to camera space) + let rotation = Mat4::from_quat(self.get_orientation().inverse()); + let translation = Mat4::from_translation(-self.position); + let view = rotation * translation; + + // perspective projection let proj = Mat4::perspective_rh_gl(self.fovy, self.aspect, self.znear, self.zfar); + let vp = proj * view; vp.to_cols_array_2d() } diff --git a/src/main.rs b/src/main.rs index fc04388..fe4b85a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -149,7 +149,7 @@ async fn make_window_renderer(window: &winit::window::Window) -> Renderer { fn main() { let event_loop = EventLoop::new(); let window = WindowBuilder::new().build(&event_loop).unwrap(); - let mut camera = Flycam::new(10.0, 0.002); + let mut camera = Flycam::new(0.002, 50.0, 5.0, 5.0); let mut is_grabbed = false; let mut ren = pollster::block_on(make_window_renderer(&window)); // let mut state: Box = Box::new(Planets::new(&mut ren));