Add better camera control. Needs some more polishing

This commit is contained in:
Maximilian Ammann 2022-01-11 22:22:07 +01:00
parent d4cdebf79d
commit 31c3bfc0e8
6 changed files with 211 additions and 112 deletions

View File

@ -5,8 +5,8 @@ pub const MUNICH_Y: u32 = 11360;
pub const MUNICH_Z: u8 = 15; pub const MUNICH_Z: u8 = 15;
pub fn fetch_munich_tiles(cache: &Cache) { pub fn fetch_munich_tiles(cache: &Cache) {
for x in 0..5 { for x in 0..15 {
for y in 0..5 { for y in 0..15 {
cache.fetch((MUNICH_X + x, MUNICH_Y + y, MUNICH_Z).into()) cache.fetch((MUNICH_X + x, MUNICH_Y + y, MUNICH_Z).into())
} }
} }

View File

@ -1,36 +1,32 @@
use std::f32::consts::FRAC_PI_2; use std::f32::consts::FRAC_PI_2;
use cgmath::InnerSpace; use crate::render::camera;
use cgmath::{EuclideanSpace, InnerSpace, Matrix4, SquareMatrix, Vector2, Vector3, Vector4};
use log::info;
const SAFE_FRAC_PI_2: f32 = FRAC_PI_2 - 0.0001; const SAFE_FRAC_PI_2: f32 = FRAC_PI_2 - 0.0001;
#[derive(Debug)] #[derive(Debug)]
pub struct CameraController { pub struct CameraController {
amount_left: f32, translate_x: f64,
amount_right: f32, translate_y: f64,
amount_forward: f32, direct_translate_x: f64,
amount_backward: f32, direct_translate_y: f64,
amount_up: f32,
amount_down: f32, zoom: f64,
rotate_horizontal: f32,
rotate_vertical: f32, speed: f64,
scroll: f32, sensitivity: f64,
speed: f32,
sensitivity: f32,
} }
impl CameraController { impl CameraController {
pub fn new(speed: f32, sensitivity: f32) -> Self { pub fn new(speed: f64, sensitivity: f64) -> Self {
Self { Self {
amount_left: 0.0, translate_x: 0.0,
amount_right: 0.0, translate_y: 0.0,
amount_forward: 0.0, direct_translate_x: 0.0,
amount_backward: 0.0, direct_translate_y: 0.0,
amount_up: 0.0, zoom: 0.0,
amount_down: 0.0,
rotate_horizontal: 0.0,
rotate_vertical: 0.0,
scroll: 0.0,
speed, speed,
sensitivity, sensitivity,
} }
@ -42,58 +38,107 @@ impl CameraController {
state: winit::event::ElementState, state: winit::event::ElementState,
) -> bool { ) -> bool {
let amount = if state == winit::event::ElementState::Pressed { let amount = if state == winit::event::ElementState::Pressed {
1.0 10.0 * self.sensitivity // left, right is the same as panning 10px
} else { } else {
0.0 0.0
}; };
match key { match key {
winit::event::VirtualKeyCode::W | winit::event::VirtualKeyCode::Up => { winit::event::VirtualKeyCode::W | winit::event::VirtualKeyCode::Up => {
self.amount_forward = amount; self.translate_y += amount;
true true
} }
winit::event::VirtualKeyCode::S | winit::event::VirtualKeyCode::Down => { winit::event::VirtualKeyCode::S | winit::event::VirtualKeyCode::Down => {
self.amount_backward = amount; self.translate_y -= amount;
true true
} }
winit::event::VirtualKeyCode::A | winit::event::VirtualKeyCode::Left => { winit::event::VirtualKeyCode::A | winit::event::VirtualKeyCode::Left => {
self.amount_left = amount; self.translate_x -= amount;
true true
} }
winit::event::VirtualKeyCode::D | winit::event::VirtualKeyCode::Right => { winit::event::VirtualKeyCode::D | winit::event::VirtualKeyCode::Right => {
self.amount_right = amount; self.translate_x += amount;
true true
} }
winit::event::VirtualKeyCode::Space => { winit::event::VirtualKeyCode::Space => {
self.amount_up = amount; self.translate_y = amount;
true
}
winit::event::VirtualKeyCode::LShift => {
self.amount_down = amount;
true true
} }
_ => false, _ => false,
} }
} }
pub fn process_mouse(&mut self, mouse_dx: f64, mouse_dy: f64) { pub fn process_mouse(
self.rotate_horizontal = mouse_dx as f32; &mut self,
self.rotate_vertical = mouse_dy as f32; start_cam_x: f64,
start_cam_y: f64,
mouse_dx: f64,
mouse_dy: f64,
width: f64,
height: f64,
camera: &mut camera::Camera,
view_proj: &Matrix4<f64>,
) {
info!("mouse_dx {} mouse_dy {}", mouse_dx, mouse_dy);
let origin = Vector2::new(0.0, 0.0);
let screen = Vector2::new(mouse_dx, mouse_dy);
let camera_pos = &camera.position.to_vec();
let world = Self::screen_to_world(&origin, width, height, camera_pos, &view_proj)
- Self::screen_to_world(&screen, width, height, camera_pos, &view_proj);
info!("world {:?}", world);
//self.direct_translate_x -= world.x;
//self.direct_translate_y += world.y;
camera.position.x = start_cam_x - world.x;
camera.position.y = start_cam_y + world.y;
}
fn screen_to_world(
screen: &Vector2<f64>,
width: f64,
height: f64,
camera_pos: &Vector3<f64>,
view_proj: &Matrix4<f64>,
) -> Vector4<f64> {
let min_depth = 0.0;
let max_depth = 1.0;
let x = 0.0;
let y = 0.0;
let ox = x + width / 2.0;
let oy = y + height / 2.0;
let oz = min_depth;
let pz = max_depth - min_depth;
// Adapted from: https://docs.microsoft.com/en-us/windows/win32/direct3d9/viewports-and-clipping#viewport-rectangle
let direct_x = Matrix4::from_cols(
Vector4::new(width as f64 / 2.0, 0.0, 0.0, 0.0),
Vector4::new(0.0, height as f64 / 2.0, 0.0, 0.0),
Vector4::new(0.0, 0.0, pz, 0.0),
Vector4::new(ox, oy, oz, 1.0),
);
let screen_hom = Vector4::new(screen.x, screen.y, 1.0, 1.0) * camera_pos.z;
let result = direct_x.invert().unwrap() * screen_hom;
let world_pos = view_proj.invert().unwrap() * result;
world_pos
} }
pub fn process_touch(&mut self, touch_dx: f64, touch_dy: f64) { pub fn process_touch(&mut self, touch_dx: f64, touch_dy: f64) {
self.amount_right += touch_dx as f32; self.translate_x += touch_dx as f64 * self.sensitivity;
self.amount_up += touch_dy as f32; self.translate_y += touch_dy as f64 * self.sensitivity;
} }
pub fn process_scroll(&mut self, delta: &winit::event::MouseScrollDelta) { pub fn process_scroll(&mut self, delta: &winit::event::MouseScrollDelta) {
self.scroll = -match delta { self.zoom = -match delta {
// I'm assuming a line is about 100 pixels // I'm assuming a line is about 100 pixels
winit::event::MouseScrollDelta::LineDelta(_, scroll) => scroll * 100.0, winit::event::MouseScrollDelta::LineDelta(_, scroll) => *scroll as f64 * 100.0,
winit::event::MouseScrollDelta::PixelDelta(winit::dpi::PhysicalPosition { winit::event::MouseScrollDelta::PixelDelta(winit::dpi::PhysicalPosition {
y: scroll, y: scroll,
.. ..
}) => *scroll as f32, }) => *scroll,
}; } * self.sensitivity;
} }
pub fn update_camera( pub fn update_camera(
@ -101,44 +146,27 @@ impl CameraController {
camera: &mut crate::render::camera::Camera, camera: &mut crate::render::camera::Camera,
dt: std::time::Duration, dt: std::time::Duration,
) { ) {
let dt = dt.as_secs_f32(); camera.position.x += self.direct_translate_x;
camera.position.y += self.direct_translate_y;
self.direct_translate_x = 0.0;
self.direct_translate_y = 0.0;
// Move forward/backward and left/right let dt = dt.as_secs_f64() * self.speed;
let (yaw_sin, yaw_cos) = camera.yaw.0.sin_cos();
let forward = cgmath::Vector3::new(yaw_cos, 0.0, yaw_sin).normalize(); let dy = self.translate_y * dt;
let right = cgmath::Vector3::new(-yaw_sin, 0.0, yaw_cos).normalize(); camera.position.y += dy;
camera.position += forward * (self.amount_forward - self.amount_backward) * self.speed * dt; let dx = self.translate_x * dt;
camera.position += right * (self.amount_right - self.amount_left) * self.speed * dt; camera.position.x += dx;
// Move in/out (aka. "zoom") // Move in/out (aka. "zoom")
// Note: this isn't an actual zoom. The camera's position // Note: this isn't an actual zoom. The camera's position
// changes when zooming. I've added this to make it easier // changes when zooming. I've added this to make it easier
// to get closer to an object you want to focus on. // to get closer to an object you want to focus on.
let (pitch_sin, pitch_cos) = camera.pitch.0.sin_cos(); let dz = self.zoom * dt;
let scrollward = camera.position.z += dz;
cgmath::Vector3::new(pitch_cos * yaw_cos, pitch_sin, pitch_cos * yaw_sin).normalize();
camera.position += scrollward * self.scroll * self.speed * self.sensitivity * dt;
self.scroll = 0.0;
// Move up/down. Since we don't use roll, we can just self.zoom -= dz;
// modify the y coordinate directly. self.translate_x -= dx;
camera.position.y += (self.amount_up - self.amount_down) * self.speed * dt; self.translate_y -= dy;
// Rotate
camera.yaw += cgmath::Rad(self.rotate_horizontal) * self.sensitivity * dt;
camera.pitch += cgmath::Rad(-self.rotate_vertical) * self.sensitivity * dt;
// If process_mouse isn't called every frame, these values
// will not get set to zero, and the camera will rotate
// when moving in a non cardinal direction.
self.rotate_horizontal = 0.0;
self.rotate_vertical = 0.0;
// Keep the camera's angle from going too high/low.
if camera.pitch < -cgmath::Rad(SAFE_FRAC_PI_2) {
camera.pitch = -cgmath::Rad(SAFE_FRAC_PI_2);
} else if camera.pitch > cgmath::Rad(SAFE_FRAC_PI_2) {
camera.pitch = cgmath::Rad(SAFE_FRAC_PI_2);
}
} }
} }

View File

@ -1,3 +1,4 @@
use cgmath::Vector3;
use std::time::Duration; use std::time::Duration;
use winit::event::{ use winit::event::{
DeviceEvent, ElementState, KeyboardInput, MouseButton, TouchPhase, WindowEvent, DeviceEvent, ElementState, KeyboardInput, MouseButton, TouchPhase, WindowEvent,
@ -5,6 +6,7 @@ use winit::event::{
use winit::window::Window; use winit::window::Window;
use crate::input::camera_controller::CameraController; use crate::input::camera_controller::CameraController;
use crate::render::camera::Camera;
use crate::render::state::State; use crate::render::state::State;
mod camera_controller; mod camera_controller;
@ -13,38 +15,78 @@ pub struct InputHandler {
camera_controller: CameraController, camera_controller: CameraController,
last_touch: Option<(f64, f64)>, last_touch: Option<(f64, f64)>,
start_camera_pos: Option<cgmath::Point3<f64>>,
mouse_pressed: bool, mouse_pressed: bool,
target_stroke_width: f32, target_stroke_width: f32,
} }
impl InputHandler { impl InputHandler {
pub fn new() -> Self { pub fn new() -> Self {
let camera_controller = CameraController::new(5000.0, 0.2); let camera_controller = CameraController::new(5.0, 100.0);
Self { Self {
target_stroke_width: 1.0, target_stroke_width: 1.0,
start_camera_pos: None,
last_touch: None, last_touch: None,
mouse_pressed: false, mouse_pressed: false,
camera_controller, camera_controller,
} }
} }
pub fn device_input(&mut self, event: &DeviceEvent, window: &Window) -> bool { pub fn device_input(
&mut self,
event: &DeviceEvent,
state: &mut State,
window: &Window,
) -> bool {
match event { match event {
DeviceEvent::MouseMotion { delta } => { DeviceEvent::MouseMotion { delta } => {
if self.mouse_pressed { /* if self.mouse_pressed {
let view_proj = state.camera.calc_view_proj(&state.perspective);
self.camera_controller.process_mouse( self.camera_controller.process_mouse(
delta.0 / window.scale_factor(), delta.0 / window.scale_factor(),
delta.1 / window.scale_factor(), delta.1 / window.scale_factor(),
state.size.width as f64,
state.size.height as f64,
&mut state.camera,
&view_proj,
); );
} }*/
true true
} }
_ => false, _ => false,
} }
} }
pub fn window_input(&mut self, event: &WindowEvent, window: &Window) -> bool { pub fn window_input(
&mut self,
event: &WindowEvent,
state: &mut State,
window: &Window,
) -> bool {
match event { match event {
WindowEvent::CursorMoved { position, .. } => {
if self.mouse_pressed {
if let Some(start) = self.last_touch {
let delta_x = start.0 - position.x;
let delta_y = start.1 - position.y;
let view_proj = state.camera.calc_view_proj(&state.perspective);
self.camera_controller.process_mouse(
self.start_camera_pos.unwrap().x,
self.start_camera_pos.unwrap().y,
delta_x,
delta_y,
state.size.width as f64,
state.size.height as f64,
&mut state.camera,
&view_proj,
);
} else {
self.last_touch = Some((position.x, position.y));
self.start_camera_pos = Some(state.camera.position);
}
}
true
}
WindowEvent::KeyboardInput { WindowEvent::KeyboardInput {
input: input:
KeyboardInput { KeyboardInput {
@ -67,19 +109,29 @@ impl InputHandler {
WindowEvent::Touch(touch) => { WindowEvent::Touch(touch) => {
match touch.phase { match touch.phase {
TouchPhase::Started => { TouchPhase::Started => {
self.last_touch = Some((touch.location.x, touch.location.y)) self.last_touch = Some((touch.location.x, touch.location.y));
self.start_camera_pos = Some(state.camera.position);
} }
TouchPhase::Moved | TouchPhase::Ended => { TouchPhase::Ended => {
self.last_touch = None;
self.start_camera_pos = None;
}
TouchPhase::Moved => {
if let Some(start) = self.last_touch { if let Some(start) = self.last_touch {
let delta_x = start.0 - touch.location.x; let delta_x = start.0 - touch.location.x;
let delta_y = start.1 - touch.location.y; let delta_y = start.1 - touch.location.y;
self.camera_controller.process_touch( let view_proj = state.camera.calc_view_proj(&state.perspective);
delta_x / window.scale_factor(), self.camera_controller.process_mouse(
delta_y / window.scale_factor(), self.start_camera_pos.unwrap().x,
self.start_camera_pos.unwrap().y,
delta_x,
delta_y,
state.size.width as f64,
state.size.height as f64,
&mut state.camera,
&view_proj,
); );
} }
self.last_touch = Some((touch.location.x, touch.location.y))
} }
TouchPhase::Cancelled => {} TouchPhase::Cancelled => {}
} }
@ -96,6 +148,11 @@ impl InputHandler {
.. ..
} => { } => {
self.mouse_pressed = *state == ElementState::Pressed; self.mouse_pressed = *state == ElementState::Pressed;
if !self.mouse_pressed {
self.last_touch = None;
self.start_camera_pos = None;
}
true true
} }
_ => false, _ => false,

View File

@ -45,14 +45,14 @@ pub async fn setup(window: winit::window::Window, event_loop: EventLoop<()>, cac
.. // We're not using device_id currently .. // We're not using device_id currently
} => { } => {
trace!("{:?}", event); trace!("{:?}", event);
input.device_input(event,&window); input.device_input(event, state, &window);
} }
Event::WindowEvent { Event::WindowEvent {
ref event, ref event,
window_id, window_id,
} if window_id == window.id() => { } if window_id == window.id() => {
if !input.window_input(event, &window) { if !input.window_input(event, state, &window) {
match event { match event {
WindowEvent::CloseRequested WindowEvent::CloseRequested
| WindowEvent::KeyboardInput { | WindowEvent::KeyboardInput {

View File

@ -1,9 +1,10 @@
use cgmath::prelude::*; use cgmath::prelude::*;
use cgmath::Matrix4;
use crate::render::shader_ffi::CameraUniform; use crate::render::shader_ffi::CameraUniform;
#[rustfmt::skip] #[rustfmt::skip]
pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4<f32> = cgmath::Matrix4::new( pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4<f64> = cgmath::Matrix4::new(
1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0,
0.0, 1.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, 0.0,
@ -12,16 +13,16 @@ pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4<f32> = cgmath::Matrix4::new(
#[derive(Debug)] #[derive(Debug)]
pub struct Camera { pub struct Camera {
pub position: cgmath::Point3<f32>, pub position: cgmath::Point3<f64>,
pub yaw: cgmath::Rad<f32>, pub yaw: cgmath::Rad<f64>,
pub pitch: cgmath::Rad<f32>, pub pitch: cgmath::Rad<f64>,
} }
impl Camera { impl Camera {
pub fn new< pub fn new<
V: Into<cgmath::Point3<f32>>, V: Into<cgmath::Point3<f64>>,
Y: Into<cgmath::Rad<f32>>, Y: Into<cgmath::Rad<f64>>,
P: Into<cgmath::Rad<f32>>, P: Into<cgmath::Rad<f64>>,
>( >(
position: V, position: V,
yaw: Y, yaw: Y,
@ -34,7 +35,7 @@ impl Camera {
} }
} }
pub fn calc_matrix(&self) -> cgmath::Matrix4<f32> { pub fn calc_matrix(&self) -> cgmath::Matrix4<f64> {
cgmath::Matrix4::look_to_rh( cgmath::Matrix4::look_to_rh(
self.position, self.position,
cgmath::Vector3::new(self.yaw.0.cos(), self.pitch.0.sin(), self.yaw.0.sin()) cgmath::Vector3::new(self.yaw.0.cos(), self.pitch.0.sin(), self.yaw.0.sin())
@ -42,29 +43,37 @@ impl Camera {
cgmath::Vector3::unit_y(), cgmath::Vector3::unit_y(),
) )
} }
pub fn calc_view_proj(&self, perspective: &Perspective) -> Matrix4<f64> {
perspective.calc_matrix() * self.calc_matrix()
}
pub fn create_camera_uniform(&self, perspective: &Perspective) -> CameraUniform { pub fn create_camera_uniform(&self, perspective: &Perspective) -> CameraUniform {
let view_proj = (perspective.calc_matrix() * self.calc_matrix()); let view_proj = self.calc_view_proj(perspective);
CameraUniform::new(view_proj.into(), self.position.to_homogeneous().into()) CameraUniform::new(
view_proj.cast::<f32>().unwrap().into(),
self.position.to_homogeneous().cast::<f32>().unwrap().into(),
)
} }
} }
pub struct Perspective { pub struct Perspective {
aspect: f32, aspect: f64,
fovy: cgmath::Rad<f32>, fovy: cgmath::Rad<f64>,
znear: f32, znear: f64,
zfar: f32, zfar: f64,
} }
impl Perspective { impl Perspective {
pub fn new<F: Into<cgmath::Rad<f32>>>( pub fn new<F: Into<cgmath::Rad<f64>>>(
width: u32, width: u32,
height: u32, height: u32,
fovy: F, fovy: F,
znear: f32, znear: f64,
zfar: f32, zfar: f64,
) -> Self { ) -> Self {
Self { Self {
aspect: width as f32 / height as f32, aspect: width as f64 / height as f64,
fovy: fovy.into(), fovy: fovy.into(),
znear, znear,
zfar, zfar,
@ -72,10 +81,10 @@ impl Perspective {
} }
pub fn resize(&mut self, width: u32, height: u32) { pub fn resize(&mut self, width: u32, height: u32) {
self.aspect = width as f32 / height as f32; self.aspect = width as f64 / height as f64;
} }
pub fn calc_matrix(&self) -> cgmath::Matrix4<f32> { pub fn calc_matrix(&self) -> cgmath::Matrix4<f64> {
OPENGL_TO_WGPU_MATRIX * cgmath::perspective(self.fovy, self.aspect, self.znear, self.zfar) OPENGL_TO_WGPU_MATRIX * cgmath::perspective(self.fovy, self.aspect, self.znear, self.zfar)
} }
} }
@ -137,6 +146,7 @@ mod tests {
Vector4::new(ox, oy, oz, 1.0), Vector4::new(ox, oy, oz, 1.0),
); );
let screen_hom = direct_x * result; let screen_hom = direct_x * result;
println!("screen_hom: {:?}", screen_hom);
let screen = Vector3::new( let screen = Vector3::new(
screen_hom.x / screen_hom.w, screen_hom.x / screen_hom.w,
screen_hom.y / screen_hom.w, screen_hom.y / screen_hom.w,
@ -144,8 +154,12 @@ mod tests {
); );
println!("screen: {:?}", screen); println!("screen: {:?}", screen);
let screen_hom2 = Vector4::new(screen.x, screen.y, 1.0, 1.0) * 5000.0;
println!("screen_hom2: {:?}", screen_hom2);
let result = direct_x.invert().unwrap() * screen_hom; let result = direct_x.invert().unwrap() * screen_hom;
println!("result: {:?}", result); println!("result screen_hom: {:?}", result);
let result = direct_x.invert().unwrap() * screen_hom2;
println!("result screen_hom2: {:?}", result);
let world_pos = view_proj.invert().unwrap() * result; let world_pos = view_proj.invert().unwrap() * result;
println!("world_pos: {:?}", world_pos); println!("world_pos: {:?}", world_pos);
} }

View File

@ -47,7 +47,7 @@ pub struct State {
surface_config: wgpu::SurfaceConfiguration, surface_config: wgpu::SurfaceConfiguration,
suspended: bool, suspended: bool,
size: winit::dpi::PhysicalSize<u32>, pub size: winit::dpi::PhysicalSize<u32>,
render_pipeline: wgpu::RenderPipeline, render_pipeline: wgpu::RenderPipeline,
mask_pipeline: wgpu::RenderPipeline, mask_pipeline: wgpu::RenderPipeline,
@ -67,7 +67,7 @@ pub struct State {
tile_mask_instances_buffer: wgpu::Buffer, tile_mask_instances_buffer: wgpu::Buffer,
pub camera: camera::Camera, pub camera: camera::Camera,
perspective: camera::Perspective, pub perspective: camera::Perspective,
pub scene: SceneParams, pub scene: SceneParams,
} }