From 24bcc55e74857b3c72a6f68c26747ee66eeda98f Mon Sep 17 00:00:00 2001 From: Turtle1331 Date: Fri, 4 Mar 2022 05:58:12 -0800 Subject: [PATCH 1/4] Overhaul flycam impl --- src/camera.rs | 141 +++++++++++++++++++++++++++++++++----------------- src/main.rs | 2 +- 2 files changed, 95 insertions(+), 48 deletions(-) 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)); From a056c6f934f4d9cd220c287acd1413bf6e9184e6 Mon Sep 17 00:00:00 2001 From: Turtle1331 Date: Mon, 7 Mar 2022 06:20:40 -0800 Subject: [PATCH 2/4] Flycam physics: make it linear (remove drag), use meaningful parameters --- src/camera.rs | 25 ++++++++++++------------- src/main.rs | 2 +- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/camera.rs b/src/camera.rs index 98a9bf7..db03887 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -2,6 +2,8 @@ use glam::{Mat4, Quat, Vec2, Vec3}; use std::time::Instant; use winit::event::{ElementState, VirtualKeyCode}; +const LN2: f32 = 0.6931472; // TODO figure out how to use lazy_static! or something + pub trait Camera { fn get_eye(&self) -> [f32; 4]; fn get_vp(&self) -> [[f32; 4]; 4]; @@ -28,8 +30,7 @@ pub struct Flycam { // constants turn_sensitivity: f32, thrust_mag: f32, - friction_coeff: f32, - drag_coeff: f32, + damping_coeff: f32, aspect: f32, fovy: f32, znear: f32, @@ -37,7 +38,9 @@ pub struct Flycam { } impl Flycam { - pub fn new(turn_sensitivity: f32, thrust_mag: f32, friction_coeff: f32, drag_coeff: f32) -> Self { + /// thrust_speed: top speed when using a single thruster, in units/second + /// duration to halve difference between current and target velocity, in seconds + pub fn new(turn_sensitivity: f32, thrust_speed: f32, damper_half_life: f32) -> Self { Self { is_world_up_pressed: false, is_world_down_pressed: false, @@ -55,9 +58,8 @@ impl Flycam { velocity: Vec3::new(0.0, 0.0, 0.0), position: Vec3::new(0.0, 0.5, 1.0), turn_sensitivity, - thrust_mag, - friction_coeff, - drag_coeff, + thrust_mag: thrust_speed / damper_half_life * LN2, + damping_coeff: LN2 / damper_half_life, aspect: 1.0, // TODO compute from size fovy: std::f32::consts::FRAC_PI_2, znear: 0.01, @@ -135,7 +137,8 @@ impl Flycam { } fn update_kinematic(&mut self, dt: f32) { - let net_acc = self.get_thrust_acc() + self.get_friction_acc() + self.get_drag_acc(); + /// update velocity and position from acceleration using forward differences + let net_acc = self.get_thrust_acc() + self.get_damping_acc(); let delta_vel = net_acc * dt; self.velocity += delta_vel; @@ -161,12 +164,8 @@ impl Flycam { 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 get_damping_acc(&self) -> glam::Vec3 { + self.damping_coeff * -self.velocity } fn key_axis(negative: bool, positive: bool) -> f32 { diff --git a/src/main.rs b/src/main.rs index fe4b85a..a16af12 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(0.002, 50.0, 5.0, 5.0); + let mut camera = Flycam::new(0.002, 10.0, 0.25); 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)); From 655400cf37838aa18f3bf98308f65a552a57b618 Mon Sep 17 00:00:00 2001 From: Turtle1331 Date: Wed, 16 Mar 2022 21:49:09 -0700 Subject: [PATCH 3/4] Use the standard library's LN_2 constant --- src/camera.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/camera.rs b/src/camera.rs index db03887..39189a0 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -1,8 +1,7 @@ use glam::{Mat4, Quat, Vec2, Vec3}; use std::time::Instant; use winit::event::{ElementState, VirtualKeyCode}; - -const LN2: f32 = 0.6931472; // TODO figure out how to use lazy_static! or something +use std::f32::consts::LN_2; pub trait Camera { fn get_eye(&self) -> [f32; 4]; @@ -58,8 +57,8 @@ impl Flycam { velocity: Vec3::new(0.0, 0.0, 0.0), position: Vec3::new(0.0, 0.5, 1.0), turn_sensitivity, - thrust_mag: thrust_speed / damper_half_life * LN2, - damping_coeff: LN2 / damper_half_life, + thrust_mag: thrust_speed / damper_half_life * LN_2, + damping_coeff: LN_2 / damper_half_life, aspect: 1.0, // TODO compute from size fovy: std::f32::consts::FRAC_PI_2, znear: 0.01, From 46e380617c6eb115da2092cc71930e6d59dde4c4 Mon Sep 17 00:00:00 2001 From: Turtle1331 Date: Thu, 17 Mar 2022 22:15:36 -0700 Subject: [PATCH 4/4] Add flycam docstrings --- src/camera.rs | 41 +++++++++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/src/camera.rs b/src/camera.rs index 39189a0..bfda144 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -10,6 +10,7 @@ pub trait Camera { pub struct Flycam { // input + // currently held keys is_world_up_pressed: bool, is_world_down_pressed: bool, is_cam_up_pressed: bool, @@ -18,18 +19,26 @@ pub struct Flycam { is_backward_pressed: bool, is_left_pressed: bool, is_right_pressed: bool, + // accumulated mouse movement yet to be processed mouse_dx: f32, mouse_dy: f32, + // state + // timestamp last_update: Instant, - euler_y: f32, + // camera orientation euler_x: f32, + euler_y: f32, + // camera movement state velocity: Vec3, position: Vec3, + // constants - turn_sensitivity: f32, - thrust_mag: f32, - damping_coeff: f32, + // camera movement + turn_sensitivity: f32, // coefficient for mouse_dx/dy -> euler_x/y + thrust_mag: f32, // coefficient for thrust acceleration vector + damping_coeff: f32, // coefficient for damping acceleration vector + // camera frustum aspect: f32, fovy: f32, znear: f32, @@ -52,8 +61,8 @@ impl Flycam { mouse_dx: 0.0, mouse_dy: 0.0, last_update: Instant::now(), - euler_y: 0.0, euler_x: 0.0, + euler_y: 0.0, velocity: Vec3::new(0.0, 0.0, 0.0), position: Vec3::new(0.0, 0.5, 1.0), turn_sensitivity, @@ -68,6 +77,7 @@ impl Flycam { } impl Flycam { + /// update stored keyboard state for use in update() pub fn process_keyboard(&mut self, key: VirtualKeyCode, state: ElementState) { let is_pressed = state == ElementState::Pressed; match key { @@ -99,6 +109,7 @@ impl Flycam { } } + /// update accumulated mouse movement for use in update() 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; @@ -108,6 +119,7 @@ impl Flycam { self.aspect = (width as f32) / (height as f32); } + /// apply input and update camera movement pub fn update(&mut self) { let dt = self.last_update.elapsed(); self.last_update = Instant::now(); @@ -135,8 +147,8 @@ impl Flycam { } } + /// update velocity and position from acceleration using forward differences fn update_kinematic(&mut self, dt: f32) { - /// update velocity and position from acceleration using forward differences let net_acc = self.get_thrust_acc() + self.get_damping_acc(); let delta_vel = net_acc * dt; @@ -146,12 +158,15 @@ impl Flycam { self.position += delta_pos; } + /// use keyboard key pairs to trigger directional thrusters in camera and world coordinates + /// thrust_speed is the max speed (under drag) with a single thruster, but combinations can + /// produce higher speeds (e.g. forward and right, camera down and world down) fn get_thrust_acc(&self) -> glam::Vec3 { let axis = Self::key_axis; - 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_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); @@ -163,10 +178,14 @@ impl Flycam { self.thrust_mag * thrusters_total } + /// calculate a damping force (proportional to velocity) + /// the damping coefficient is calculated in the constructor, which is parameterized in terms + /// of more physically meaningful values fn get_damping_acc(&self) -> glam::Vec3 { self.damping_coeff * -self.velocity } + /// a helper function to turn a pair of key states into a sign for thruster direction fn key_axis(negative: bool, positive: bool) -> f32 { if negative { if positive { @@ -183,8 +202,10 @@ impl Flycam { } } + /// the current camera orientation, which can be seen as a rotation (in quaternion form) from + /// camera axes to world axes + /// glam's YXZ ordering matches the standard roll-pitch-yaw Euler angles 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) } }