2022-05-08 21:42:30 +00:00
|
|
|
use glam::{Mat4, Quat, Vec3};
|
2022-04-25 01:37:34 +00:00
|
|
|
use std::f32::consts::LN_2;
|
2022-04-23 03:42:33 +00:00
|
|
|
use std::time::Instant;
|
|
|
|
use winit::event::{ElementState, VirtualKeyCode};
|
|
|
|
|
2022-05-15 21:30:05 +00:00
|
|
|
#[derive(Clone, Debug)]
|
2022-05-11 15:30:01 +00:00
|
|
|
pub struct Camera {
|
|
|
|
pub eye: [f32; 4],
|
|
|
|
pub vp: [[f32; 4]; 4],
|
2022-04-23 03:42:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct Flycam {
|
|
|
|
// input
|
|
|
|
// currently held keys
|
|
|
|
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,
|
|
|
|
is_right_pressed: bool,
|
|
|
|
// accumulated mouse movement yet to be processed
|
|
|
|
mouse_dx: f32,
|
|
|
|
mouse_dy: f32,
|
|
|
|
|
|
|
|
// state
|
|
|
|
// timestamp
|
|
|
|
last_update: Instant,
|
|
|
|
// camera orientation
|
|
|
|
euler_x: f32,
|
|
|
|
euler_y: f32,
|
|
|
|
// camera movement state
|
|
|
|
velocity: Vec3,
|
|
|
|
position: Vec3,
|
|
|
|
|
|
|
|
// constants
|
|
|
|
// camera movement
|
2022-04-25 01:37:34 +00:00
|
|
|
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
|
2022-04-23 03:42:33 +00:00
|
|
|
// camera frustum
|
|
|
|
aspect: f32,
|
|
|
|
fovy: f32,
|
|
|
|
znear: f32,
|
|
|
|
zfar: f32,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Flycam {
|
|
|
|
/// 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,
|
|
|
|
is_cam_up_pressed: false,
|
|
|
|
is_cam_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(),
|
|
|
|
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,
|
|
|
|
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,
|
|
|
|
zfar: 100.0,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
|
|
|
VirtualKeyCode::Space => {
|
|
|
|
self.is_world_up_pressed = is_pressed;
|
|
|
|
}
|
|
|
|
VirtualKeyCode::LShift => {
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn resize(&mut self, width: u32, height: u32) {
|
|
|
|
self.aspect = (width as f32) / (height as f32);
|
|
|
|
}
|
|
|
|
|
2022-05-15 21:55:00 +00:00
|
|
|
/// disable all key presses
|
|
|
|
pub fn defocus(&mut self) {
|
|
|
|
self.is_world_down_pressed = false;
|
|
|
|
self.is_world_up_pressed = false;
|
|
|
|
self.is_cam_down_pressed = false;
|
|
|
|
self.is_cam_up_pressed = false;
|
|
|
|
self.is_forward_pressed = false;
|
|
|
|
self.is_left_pressed = false;
|
|
|
|
self.is_backward_pressed = false;
|
|
|
|
self.is_right_pressed = false;
|
|
|
|
}
|
|
|
|
|
2022-04-23 03:42:33 +00:00
|
|
|
/// apply input and update camera movement
|
|
|
|
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;
|
|
|
|
|
|
|
|
self.update_orientation(dt);
|
|
|
|
self.update_kinematic(dt);
|
|
|
|
}
|
|
|
|
|
2022-05-08 21:42:30 +00:00
|
|
|
fn update_orientation(&mut self, _dt: f32) {
|
2022-04-23 03:42:33 +00:00
|
|
|
let t = self.turn_sensitivity;
|
|
|
|
|
2022-04-25 01:37:34 +00:00
|
|
|
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
|
2022-04-23 03:42:33 +00:00
|
|
|
|
|
|
|
self.mouse_dx = 0.0;
|
|
|
|
self.mouse_dy = 0.0;
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// update velocity and position from acceleration using forward differences
|
|
|
|
fn update_kinematic(&mut self, dt: f32) {
|
|
|
|
let net_acc = self.get_thrust_acc() + self.get_damping_acc();
|
|
|
|
|
|
|
|
let delta_vel = net_acc * dt;
|
|
|
|
self.velocity += delta_vel;
|
|
|
|
|
|
|
|
let delta_pos = self.velocity * dt;
|
|
|
|
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;
|
|
|
|
|
2022-04-25 01:37:34 +00:00
|
|
|
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);
|
2022-04-23 03:42:33 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
/// 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 {
|
2022-04-25 01:37:34 +00:00
|
|
|
0.0 // positive + negative cancel out
|
2022-04-23 03:42:33 +00:00
|
|
|
} else {
|
2022-04-25 01:37:34 +00:00
|
|
|
-1.0 // negative only
|
2022-04-23 03:42:33 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if positive {
|
2022-04-25 01:37:34 +00:00
|
|
|
1.0 // positive only
|
2022-04-23 03:42:33 +00:00
|
|
|
} else {
|
2022-04-25 01:37:34 +00:00
|
|
|
0.0 // neutral
|
2022-04-23 03:42:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// 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 {
|
|
|
|
Quat::from_euler(glam::EulerRot::YXZ, self.euler_y, self.euler_x, 0.0)
|
|
|
|
}
|
|
|
|
|
2022-05-11 15:30:01 +00:00
|
|
|
pub fn get_camera(&self) -> Camera {
|
|
|
|
Camera {
|
|
|
|
eye: self.get_eye(),
|
2022-05-17 00:57:54 +00:00
|
|
|
vp: self.get_vp(),
|
2022-05-11 15:30:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_eye(&self) -> [f32; 4] {
|
2022-04-23 03:42:33 +00:00
|
|
|
self.position.extend(0.0).to_array()
|
|
|
|
}
|
|
|
|
|
2022-05-11 15:30:01 +00:00
|
|
|
pub fn get_vp(&self) -> [[f32; 4]; 4] {
|
2022-04-23 03:42:33 +00:00
|
|
|
// 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()
|
|
|
|
}
|
|
|
|
}
|