diff --git a/src/input/camera_controller.rs b/src/input/camera_controller.rs new file mode 100644 index 00000000..c011dbd4 --- /dev/null +++ b/src/input/camera_controller.rs @@ -0,0 +1,144 @@ +use std::f32::consts::FRAC_PI_2; + +use cgmath::InnerSpace; + +const SAFE_FRAC_PI_2: f32 = FRAC_PI_2 - 0.0001; + +#[derive(Debug)] +pub struct CameraController { + amount_left: f32, + amount_right: f32, + amount_forward: f32, + amount_backward: f32, + amount_up: f32, + amount_down: f32, + rotate_horizontal: f32, + rotate_vertical: f32, + scroll: f32, + speed: f32, + sensitivity: f32, +} + +impl CameraController { + pub fn new(speed: f32, sensitivity: f32) -> Self { + Self { + amount_left: 0.0, + amount_right: 0.0, + amount_forward: 0.0, + amount_backward: 0.0, + amount_up: 0.0, + amount_down: 0.0, + rotate_horizontal: 0.0, + rotate_vertical: 0.0, + scroll: 0.0, + speed, + sensitivity, + } + } + + pub fn process_keyboard( + &mut self, + key: winit::event::VirtualKeyCode, + state: winit::event::ElementState, + ) -> bool { + let amount = if state == winit::event::ElementState::Pressed { + 1.0 + } else { + 0.0 + }; + match key { + winit::event::VirtualKeyCode::W | winit::event::VirtualKeyCode::Up => { + self.amount_forward = amount; + true + } + winit::event::VirtualKeyCode::S | winit::event::VirtualKeyCode::Down => { + self.amount_backward = amount; + true + } + winit::event::VirtualKeyCode::A | winit::event::VirtualKeyCode::Left => { + self.amount_left = amount; + true + } + winit::event::VirtualKeyCode::D | winit::event::VirtualKeyCode::Right => { + self.amount_right = amount; + true + } + winit::event::VirtualKeyCode::Space => { + self.amount_up = amount; + true + } + winit::event::VirtualKeyCode::LShift => { + self.amount_down = amount; + true + } + _ => false, + } + } + + pub fn process_mouse(&mut self, mouse_dx: f64, mouse_dy: f64) { + self.rotate_horizontal = mouse_dx as f32; + self.rotate_vertical = mouse_dy as f32; + } + + pub fn process_touch(&mut self, touch_dx: f64, touch_dy: f64) { + self.amount_right += touch_dx as f32; + self.amount_up += touch_dy as f32; + } + + pub fn process_scroll(&mut self, delta: &winit::event::MouseScrollDelta) { + self.scroll = -match delta { + // I'm assuming a line is about 100 pixels + winit::event::MouseScrollDelta::LineDelta(_, scroll) => scroll * 100.0, + winit::event::MouseScrollDelta::PixelDelta(winit::dpi::PhysicalPosition { + y: scroll, + .. + }) => *scroll as f32, + }; + } + + pub fn update_camera( + &mut self, + camera: &mut crate::render::camera::Camera, + dt: std::time::Duration, + ) { + let dt = dt.as_secs_f32(); + + // Move forward/backward and left/right + let (yaw_sin, yaw_cos) = camera.yaw.0.sin_cos(); + let forward = cgmath::Vector3::new(yaw_cos, 0.0, yaw_sin).normalize(); + let right = cgmath::Vector3::new(-yaw_sin, 0.0, yaw_cos).normalize(); + camera.position += forward * (self.amount_forward - self.amount_backward) * self.speed * dt; + camera.position += right * (self.amount_right - self.amount_left) * self.speed * dt; + + // Move in/out (aka. "zoom") + // Note: this isn't an actual zoom. The camera's position + // changes when zooming. I've added this to make it easier + // to get closer to an object you want to focus on. + let (pitch_sin, pitch_cos) = camera.pitch.0.sin_cos(); + let scrollward = + 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 + // modify the y coordinate directly. + camera.position.y += (self.amount_up - self.amount_down) * self.speed * dt; + + // 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); + } + } +} diff --git a/src/input/mod.rs b/src/input/mod.rs new file mode 100644 index 00000000..44e27fa7 --- /dev/null +++ b/src/input/mod.rs @@ -0,0 +1,124 @@ +use std::time::Duration; +use winit::event::{ + DeviceEvent, ElementState, KeyboardInput, MouseButton, TouchPhase, WindowEvent, +}; +use winit::window::Window; + +use crate::input::camera_controller::CameraController; +use crate::render::state::{SceneParams, State}; + +mod camera_controller; + +pub struct InputHandler { + camera_controller: CameraController, + + last_touch: Option<(f64, f64)>, + mouse_pressed: bool, + target_stroke_width: f32, +} + +impl InputHandler { + pub fn new() -> Self { + let camera_controller = CameraController::new(3000.0, 0.2); + Self { + target_stroke_width: 1.0, + last_touch: None, + mouse_pressed: false, + camera_controller, + } + } + + pub fn device_input(&mut self, event: &DeviceEvent, window: &Window) -> bool { + match event { + DeviceEvent::MouseMotion { delta } => { + if self.mouse_pressed { + self.camera_controller.process_mouse( + delta.0 / window.scale_factor(), + delta.1 / window.scale_factor(), + ); + } + true + } + _ => false, + } + } + + pub fn window_input(&mut self, event: &WindowEvent, window: &Window) -> bool { + match event { + WindowEvent::KeyboardInput { + input: + KeyboardInput { + state, + virtual_keycode: Some(key), + .. + }, + .. + } => match key { + winit::event::VirtualKeyCode::Z => { + self.target_stroke_width *= 1.2; + true + } + winit::event::VirtualKeyCode::H => { + self.target_stroke_width *= 0.8; + true + } + _ => self.camera_controller.process_keyboard(*key, *state), + }, + WindowEvent::Touch(touch) => { + match touch.phase { + TouchPhase::Started => { + self.last_touch = Some((touch.location.x, touch.location.y)) + } + TouchPhase::Moved | TouchPhase::Ended => { + if let Some(start) = self.last_touch { + let delta_x = start.0 - touch.location.x; + let delta_y = start.1 - touch.location.y; + self.camera_controller.process_touch( + delta_x / window.scale_factor(), + delta_y / window.scale_factor(), + ); + } + + self.last_touch = Some((touch.location.x, touch.location.y)) + } + TouchPhase::Cancelled => {} + } + + true + } + WindowEvent::MouseWheel { delta, .. } => { + self.camera_controller.process_scroll(delta); + true + } + WindowEvent::MouseInput { + button: MouseButton::Left, // Left Mouse Button + state, + .. + } => { + self.mouse_pressed = *state == ElementState::Pressed; + true + } + _ => false, + } + } + + pub fn update_state(&mut self, state: &mut State, dt: Duration) { + let scene = &mut state.scene; + self.camera_controller.update_camera(&mut state.camera, dt); + + // Animate the stroke_width to match target_stroke_width + scene.stroke_width = + scene.stroke_width + (self.target_stroke_width - scene.stroke_width) / 5.0; + + // Animate the strokes of primitive + scene.cpu_primitives[0 as usize].width = scene.stroke_width; + /* + scene.cpu_primitives[STROKE_PRIM_ID as usize].color = [ + (time_secs * 0.8 - 1.6).sin() * 0.1 + 0.1, + (time_secs * 0.5 - 1.6).sin() * 0.1 + 0.1, + (time_secs - 1.6).sin() * 0.1 + 0.1, + 1.0, + ]; + */ + } +} diff --git a/src/lib.rs b/src/lib.rs index cb5e3065..09808a27 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ mod fps_meter; +mod input; mod io; pub mod main_loop; mod platform; diff --git a/src/main_loop.rs b/src/main_loop.rs index 13f8e586..9efaef42 100644 --- a/src/main_loop.rs +++ b/src/main_loop.rs @@ -1,13 +1,15 @@ -use log::{info, trace}; +use log::{error, info, trace}; use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}; use winit::event_loop::{ControlFlow, EventLoop}; +use crate::input::InputHandler; use crate::platform::Instant; use crate::render::state::State; pub async fn setup(window: winit::window::Window, event_loop: EventLoop<()>) { info!("== mapr =="); + let mut input = InputHandler::new(); let mut state = State::new(&window).await; let mut last_render_time = Instant::now(); @@ -19,14 +21,14 @@ pub async fn setup(window: winit::window::Window, event_loop: EventLoop<()>) { .. // We're not using device_id currently } => { trace!("{:?}", event); - state.device_input(event, &window); + input.device_input(event,&window); } Event::WindowEvent { ref event, window_id, } if window_id == window.id() => { - if !state.window_input(event, &window) { + if !input.window_input(event, &window) { match event { WindowEvent::CloseRequested | WindowEvent::KeyboardInput { @@ -53,13 +55,19 @@ pub async fn setup(window: winit::window::Window, event_loop: EventLoop<()>) { let now = Instant::now(); let dt = now - last_render_time; last_render_time = now; - state.update(dt); + input.update_state( &mut state, dt); match state.render() { Ok(_) => {} // Reconfigure the surface if lost - Err(wgpu::SurfaceError::Lost) => state.resize(state.size), + Err(wgpu::SurfaceError::Lost) => { + error!("Surface Lost"); + *control_flow = ControlFlow::Exit; + }, // The system is out of memory, we should probably quit - Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit, + Err(wgpu::SurfaceError::OutOfMemory) => { + error!("Out of Memory"); + *control_flow = ControlFlow::Exit; + }, // All other errors (Outdated, Timeout) should be resolved by the next frame Err(e) => eprintln!("{:?}", e), } diff --git a/src/render/camera.rs b/src/render/camera.rs index af7f9305..14231ec3 100644 --- a/src/render/camera.rs +++ b/src/render/camera.rs @@ -1,5 +1,3 @@ -use std::f32::consts::FRAC_PI_2; - use cgmath::prelude::*; use crate::render::shader_ffi::CameraUniform; @@ -12,13 +10,11 @@ pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4 = cgmath::Matrix4::new( 0.0, 0.0, 0.5, 1.0, ); -const SAFE_FRAC_PI_2: f32 = FRAC_PI_2 - 0.0001; - #[derive(Debug)] pub struct Camera { pub position: cgmath::Point3, - yaw: cgmath::Rad, - pitch: cgmath::Rad, + pub yaw: cgmath::Rad, + pub pitch: cgmath::Rad, } impl Camera { @@ -46,6 +42,12 @@ impl Camera { cgmath::Vector3::unit_y(), ) } + pub fn create_camera_uniform(&self, projection: &Projection) -> CameraUniform { + CameraUniform::new( + (projection.calc_matrix() * self.calc_matrix()).into(), + self.position.to_homogeneous().into(), + ) + } } pub struct Projection { @@ -79,145 +81,3 @@ impl Projection { OPENGL_TO_WGPU_MATRIX * cgmath::perspective(self.fovy, self.aspect, self.znear, self.zfar) } } - -#[derive(Debug)] -pub struct CameraController { - amount_left: f32, - amount_right: f32, - amount_forward: f32, - amount_backward: f32, - amount_up: f32, - amount_down: f32, - rotate_horizontal: f32, - rotate_vertical: f32, - scroll: f32, - speed: f32, - sensitivity: f32, -} - -impl CameraController { - pub fn new(speed: f32, sensitivity: f32) -> Self { - Self { - amount_left: 0.0, - amount_right: 0.0, - amount_forward: 0.0, - amount_backward: 0.0, - amount_up: 0.0, - amount_down: 0.0, - rotate_horizontal: 0.0, - rotate_vertical: 0.0, - scroll: 0.0, - speed, - sensitivity, - } - } - - pub fn process_keyboard( - &mut self, - key: winit::event::VirtualKeyCode, - state: winit::event::ElementState, - ) -> bool { - let amount = if state == winit::event::ElementState::Pressed { - 1.0 - } else { - 0.0 - }; - match key { - winit::event::VirtualKeyCode::W | winit::event::VirtualKeyCode::Up => { - self.amount_forward = amount; - true - } - winit::event::VirtualKeyCode::S | winit::event::VirtualKeyCode::Down => { - self.amount_backward = amount; - true - } - winit::event::VirtualKeyCode::A | winit::event::VirtualKeyCode::Left => { - self.amount_left = amount; - true - } - winit::event::VirtualKeyCode::D | winit::event::VirtualKeyCode::Right => { - self.amount_right = amount; - true - } - winit::event::VirtualKeyCode::Space => { - self.amount_up = amount; - true - } - winit::event::VirtualKeyCode::LShift => { - self.amount_down = amount; - true - } - _ => false, - } - } - - pub fn process_mouse(&mut self, mouse_dx: f64, mouse_dy: f64) { - self.rotate_horizontal = mouse_dx as f32; - self.rotate_vertical = mouse_dy as f32; - } - - pub fn process_touch(&mut self, touch_dx: f64, touch_dy: f64) { - self.amount_right += touch_dx as f32; - self.amount_up += touch_dy as f32; - } - - pub fn process_scroll(&mut self, delta: &winit::event::MouseScrollDelta) { - self.scroll = -match delta { - // I'm assuming a line is about 100 pixels - winit::event::MouseScrollDelta::LineDelta(_, scroll) => scroll * 100.0, - winit::event::MouseScrollDelta::PixelDelta(winit::dpi::PhysicalPosition { - y: scroll, - .. - }) => *scroll as f32, - }; - } - - pub fn update_camera(&mut self, camera: &mut Camera, dt: std::time::Duration) { - let dt = dt.as_secs_f32(); - - // Move forward/backward and left/right - let (yaw_sin, yaw_cos) = camera.yaw.0.sin_cos(); - let forward = cgmath::Vector3::new(yaw_cos, 0.0, yaw_sin).normalize(); - let right = cgmath::Vector3::new(-yaw_sin, 0.0, yaw_cos).normalize(); - camera.position += forward * (self.amount_forward - self.amount_backward) * self.speed * dt; - camera.position += right * (self.amount_right - self.amount_left) * self.speed * dt; - - // Move in/out (aka. "zoom") - // Note: this isn't an actual zoom. The camera's position - // changes when zooming. I've added this to make it easier - // to get closer to an object you want to focus on. - let (pitch_sin, pitch_cos) = camera.pitch.0.sin_cos(); - let scrollward = - 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 - // modify the y coordinate directly. - camera.position.y += (self.amount_up - self.amount_down) * self.speed * dt; - - // 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); - } - } - - pub fn create_camera_uniform(camera: &Camera, projection: &Projection) -> CameraUniform { - CameraUniform::new( - (projection.calc_matrix() * camera.calc_matrix()).into(), - camera.position.to_homogeneous().into(), - ) - } -} diff --git a/src/render/mod.rs b/src/render/mod.rs index 7361483b..772d82ce 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -1,8 +1,9 @@ -mod camera; mod piplines; mod shader_ffi; mod tesselation; mod texture; mod shaders; + +pub mod camera; pub mod state; diff --git a/src/render/state.rs b/src/render/state.rs index 15dc3639..5743312f 100644 --- a/src/render/state.rs +++ b/src/render/state.rs @@ -16,7 +16,7 @@ use vector_tile::parse_tile_reader; use crate::fps_meter::FPSMeter; use crate::io::static_database; -use crate::render::camera::CameraController; +use crate::platform::{COLOR_TEXTURE_FORMAT, MIN_BUFFER_SIZE}; use crate::render::tesselation::TileMask; use crate::render::{camera, shaders}; @@ -24,23 +24,16 @@ use super::piplines::*; use super::shader_ffi::*; use super::tesselation::Tesselated; use super::texture::Texture; -use crate::platform::{COLOR_TEXTURE_FORMAT, MIN_BUFFER_SIZE}; pub struct SceneParams { - stroke_width: f32, - target_stroke_width: f32, - - last_touch: Option<(f64, f64)>, - - cpu_primitives: Vec, + pub stroke_width: f32, + pub cpu_primitives: Vec, } impl Default for SceneParams { fn default() -> Self { SceneParams { stroke_width: 1.0, - target_stroke_width: 1.0, - last_touch: None, cpu_primitives: vec![], } } @@ -69,7 +62,7 @@ pub struct State { surface_config: wgpu::SurfaceConfiguration, suspended: bool, - pub size: winit::dpi::PhysicalSize, + size: winit::dpi::PhysicalSize, render_pipeline: wgpu::RenderPipeline, mask_pipeline: wgpu::RenderPipeline, @@ -95,12 +88,10 @@ pub struct State { tile_mask_range: Range, tile_mask_instances: wgpu::Buffer, - camera: camera::Camera, + pub(crate) camera: camera::Camera, projection: camera::Projection, - camera_controller: camera::CameraController, - mouse_pressed: bool, - scene: SceneParams, + pub scene: SceneParams, } impl SceneParams { @@ -401,7 +392,6 @@ impl State { 0.1, 100000.0, ); - let camera_controller = camera::CameraController::new(3000.0, 0.2); Self { instance, @@ -431,8 +421,6 @@ impl State { tile_mask_instances, camera, projection, - camera_controller, - mouse_pressed: false, tile2_stroke_range, suspended: false, // Initially the app is not suspended } @@ -483,81 +471,6 @@ impl State { } } - pub fn device_input(&mut self, event: &DeviceEvent, window: &Window) -> bool { - match event { - DeviceEvent::MouseMotion { delta } => { - if self.mouse_pressed { - warn!("mouse {}", delta.0); - self.camera_controller.process_mouse( - delta.0 / window.scale_factor(), - delta.1 / window.scale_factor(), - ); - } - true - } - _ => false, - } - } - - pub fn window_input(&mut self, event: &WindowEvent, window: &Window) -> bool { - match event { - WindowEvent::KeyboardInput { - input: - KeyboardInput { - state, - virtual_keycode: Some(key), - .. - }, - .. - } => match key { - winit::event::VirtualKeyCode::Z => { - self.scene.target_stroke_width *= 1.2; - true - } - winit::event::VirtualKeyCode::H => { - self.scene.target_stroke_width *= 0.8; - true - } - _ => self.camera_controller.process_keyboard(*key, *state), - }, - WindowEvent::Touch(touch) => { - match touch.phase { - TouchPhase::Started => { - self.scene.last_touch = Some((touch.location.x, touch.location.y)) - } - TouchPhase::Moved | TouchPhase::Ended => { - if let Some(start) = self.scene.last_touch { - let delta_x = start.0 - touch.location.x; - let delta_y = start.1 - touch.location.y; - self.camera_controller.process_touch( - delta_x / window.scale_factor(), - delta_y / window.scale_factor(), - ); - } - - self.scene.last_touch = Some((touch.location.x, touch.location.y)) - } - TouchPhase::Cancelled => {} - } - - true - } - WindowEvent::MouseWheel { delta, .. } => { - self.camera_controller.process_scroll(delta); - true - } - WindowEvent::MouseInput { - button: MouseButton::Left, // Left Mouse Button - state, - .. - } => { - self.mouse_pressed = *state == ElementState::Pressed; - true - } - _ => false, - } - } - pub fn render(&mut self) -> Result<(), wgpu::SurfaceError> { let frame = self.surface.get_current_texture()?; let scene = &mut self.scene; @@ -570,7 +483,7 @@ impl State { &self.globals_uniform_buffer, 0, bytemuck::cast_slice(&[GlobalsUniform::new( - CameraController::create_camera_uniform(&self.camera, &self.projection), + self.camera.create_camera_uniform(&self.projection), )]), ); @@ -662,31 +575,10 @@ impl State { self.queue.submit(Some(encoder.finish())); frame.present(); + self.fps_meter.update_and_print(); Ok(()) } - pub fn update(&mut self, dt: std::time::Duration) { - let scene = &mut self.scene; - - self.camera_controller.update_camera(&mut self.camera, dt); - - // Animate the stroke_width to match target_stroke_width - scene.stroke_width = - scene.stroke_width + (scene.target_stroke_width - scene.stroke_width) / 5.0; - - // Animate the strokes of primitive - scene.cpu_primitives[STROKE_PRIM_ID as usize].width = scene.stroke_width; - /* - scene.cpu_primitives[STROKE_PRIM_ID as usize].color = [ - (time_secs * 0.8 - 1.6).sin() * 0.1 + 0.1, - (time_secs * 0.5 - 1.6).sin() * 0.1 + 0.1, - (time_secs - 1.6).sin() * 0.1 + 0.1, - 1.0, - ]; - */ - self.fps_meter.update_and_print() - } - pub fn is_suspended(&self) -> bool { self.suspended }