diff --git a/src/render/camera.rs b/src/render/camera.rs index 4bc5d110..220d3d9a 100644 --- a/src/render/camera.rs +++ b/src/render/camera.rs @@ -1,7 +1,6 @@ use std::f32::consts::FRAC_PI_2; use cgmath::prelude::*; -use winit::event::{ElementState, VirtualKeyCode}; #[rustfmt::skip] pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4 = cgmath::Matrix4::new( @@ -11,133 +10,225 @@ pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4 = cgmath::Matrix4::new( 0.0, 0.0, 0.5, 1.0, ); -pub(crate) struct Camera { - pub eye: cgmath::Point3, - pub target: cgmath::Point3, - pub up: cgmath::Vector3, - pub aspect: f32, - pub fovy: f32, - pub znear: f32, - pub zfar: f32, +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, } impl Camera { - fn build_view_projection_matrix(&self) -> cgmath::Matrix4 { - let view = cgmath::Matrix4::look_at_rh(self.eye, self.target, self.up); - let proj = cgmath::perspective(cgmath::Deg(self.fovy), self.aspect, self.znear, self.zfar); - proj * view + pub fn new< + V: Into>, + Y: Into>, + P: Into>, + >( + position: V, + yaw: Y, + pitch: P, + ) -> Self { + Self { + position: position.into(), + yaw: yaw.into(), + pitch: pitch.into(), + } + } + + pub fn calc_matrix(&self) -> cgmath::Matrix4 { + cgmath::Matrix4::look_to_rh( + self.position, + cgmath::Vector3::new(self.yaw.0.cos(), self.pitch.0.sin(), self.yaw.0.sin()) + .normalize(), + cgmath::Vector3::unit_y(), + ) + } +} + +pub struct Projection { + aspect: f32, + fovy: cgmath::Rad, + znear: f32, + zfar: f32, +} + +impl Projection { + pub fn new>>( + width: u32, + height: u32, + fovy: F, + znear: f32, + zfar: f32, + ) -> Self { + Self { + aspect: width as f32 / height as f32, + fovy: fovy.into(), + znear, + zfar, + } + } + + pub fn resize(&mut self, width: u32, height: u32) { + self.aspect = width as f32 / height as f32; + } + + pub fn calc_matrix(&self) -> cgmath::Matrix4 { + 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_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); + } } } #[repr(C)] -#[derive(Debug, Copy, Clone)] -pub(crate) struct CameraUniform { - pub(crate) view_proj: [[f32; 4]; 4], +#[derive(Copy, Clone)] +pub struct CameraUniform { + pub view_position: [f32; 4], + pub view_proj: [[f32; 4]; 4], } unsafe impl bytemuck::Pod for CameraUniform {} unsafe impl bytemuck::Zeroable for CameraUniform {} - impl CameraUniform { - pub(crate) fn new() -> Self { - use cgmath::SquareMatrix; + pub fn new() -> Self { Self { + view_position: [0.0; 4], view_proj: cgmath::Matrix4::identity().into(), } } - pub(crate) fn update_view_proj(&mut self, camera: &Camera) { - self.view_proj = (OPENGL_TO_WGPU_MATRIX * camera.build_view_projection_matrix()).into(); + // UPDATED! + pub(crate) fn update_view_proj(&mut self, camera: &Camera, projection: &Projection) { + self.view_position = camera.position.to_homogeneous().into(); + self.view_proj = (projection.calc_matrix() * camera.calc_matrix()).into() } } - -pub(crate) struct CameraController { - speed: f32, - is_up_pressed: bool, - is_down_pressed: bool, - is_forward_pressed: bool, - is_backward_pressed: bool, - is_left_pressed: bool, - is_right_pressed: bool, -} - -impl CameraController { - pub(crate) fn new(speed: f32) -> Self { - Self { - speed, - is_up_pressed: false, - is_down_pressed: false, - is_forward_pressed: false, - is_backward_pressed: false, - is_left_pressed: false, - is_right_pressed: false, - } - } - - pub(crate) fn process_events(&mut self, key: winit::event::VirtualKeyCode, - state: winit::event::ElementState,) -> bool { - println!("process_events"); - let is_pressed = state == ElementState::Pressed; - match key { - VirtualKeyCode::Space => { - self.is_up_pressed = is_pressed; - true - } - VirtualKeyCode::LShift => { - self.is_down_pressed = is_pressed; - true - } - VirtualKeyCode::W | VirtualKeyCode::Up => { - self.is_forward_pressed = is_pressed; - true - } - VirtualKeyCode::A | VirtualKeyCode::Left => { - self.is_left_pressed = is_pressed; - true - } - VirtualKeyCode::S | VirtualKeyCode::Down => { - self.is_backward_pressed = is_pressed; - true - } - VirtualKeyCode::D | VirtualKeyCode::Right => { - self.is_right_pressed = is_pressed; - true - } - _ => false, - } - - } - - pub(crate) fn update_camera(&self, camera: &mut Camera) { - use cgmath::InnerSpace; - let forward = camera.target - camera.eye; - let forward_norm = forward.normalize(); - let forward_mag = forward.magnitude(); - - // Prevents glitching when camera gets too close to the - // center of the scene. - if self.is_forward_pressed && forward_mag > self.speed { - camera.eye += forward_norm * self.speed; - } - if self.is_backward_pressed { - camera.eye -= forward_norm * self.speed; - } - - let right = forward_norm.cross(camera.up); - - // Redo radius calc in case the up/ down is pressed. - let forward = camera.target - camera.eye; - let forward_mag = forward.magnitude(); - - if self.is_right_pressed { - // Rescale the distance between the target and eye so - // that it doesn't change. The eye therefore still - // lies on the circle made by the target and eye. - camera.eye = camera.target - (forward + right * self.speed).normalize() * forward_mag; - } - if self.is_left_pressed { - camera.eye = camera.target - (forward - right * self.speed).normalize() * forward_mag; - } - } -} \ No newline at end of file diff --git a/src/render/piplines.rs b/src/render/piplines.rs index 3eba17af..5cecdd05 100644 --- a/src/render/piplines.rs +++ b/src/render/piplines.rs @@ -37,7 +37,7 @@ pub fn create_map_render_pipeline_description<'a>( } } else { wgpu::StencilFaceState { - compare: wgpu::CompareFunction::Always, + compare: wgpu::CompareFunction::Equal, fail_op: wgpu::StencilOperation::Keep, depth_fail_op: wgpu::StencilOperation::Keep, pass_op: wgpu::StencilOperation::Keep, @@ -53,7 +53,7 @@ pub fn create_map_render_pipeline_description<'a>( polygon_mode: wgpu::PolygonMode::Fill, front_face: wgpu::FrontFace::Ccw, strip_index_format: None, - cull_mode: None, // Maps look the same from he bottom and above + cull_mode: None, // TODO Maps look the same from he bottom and above clamp_depth: false, conservative: false, }, diff --git a/src/render/shaders/vertex.wgsl b/src/render/shaders/vertex.wgsl index f8233312..7e413818 100644 --- a/src/render/shaders/vertex.wgsl +++ b/src/render/shaders/vertex.wgsl @@ -43,7 +43,7 @@ fn main( ) -> VertexOutput { var prim: Primitive = u_primitives.primitives[a_prim_id + instance_idx]; var z = 0.0; - var world_pos = a_position; + var world_pos = a_position + a_normal * prim.width; var position = globals.view_proj * vec4(world_pos, z, 1.0); diff --git a/src/render/state.rs b/src/render/state.rs index 27188791..42edf2ad 100644 --- a/src/render/state.rs +++ b/src/render/state.rs @@ -11,7 +11,7 @@ use winit::window::Window; use crate::fps_meter::FPSMeter; use crate::render::camera; -use crate::render::camera::Camera; +use crate::render::camera::{Camera, CameraUniform}; use crate::render::tesselation::TileMask; use super::piplines::*; @@ -81,9 +81,10 @@ pub struct State { tile_mask_indices_uniform_buffer: wgpu::Buffer, tile_mask_range: Range, - camera: camera::Camera, // UPDATED! - camera_controller: camera::CameraController, // UPDATED! - camera_uniform: camera::CameraUniform, // UPDATED! + camera: camera::Camera, // UPDATED! + projection: camera::Projection, // NEW! + camera_controller: camera::CameraController, // UPDAT + camera_uniform: camera::CameraUniform, // UPDAT mouse_pressed: bool, scene: SceneParams, @@ -340,19 +341,12 @@ impl State { None }; - let camera = Camera { - eye: (0.0, 1.0, 2.0).into(), - target: (0.0, 0.0, 0.0).into(), - up: cgmath::Vector3::unit_y(), - aspect: surface_config.width as f32 / surface_config.height as f32, - fovy: 45.0, - znear: 0.1, - zfar: 100.0, - }; - let camera_controller = camera::CameraController::new(4.0); - let mut camera_uniform = camera::CameraUniform::new(); + let camera = camera::Camera::new((0.0, 5.0, 5000.0), cgmath::Deg(-90.0), cgmath::Deg(-0.0)); + let projection = camera::Projection::new(surface_config.width, surface_config.height, cgmath::Deg(45.0), 0.1, 10000.0); + let camera_controller = camera::CameraController::new(4000.0, 0.4); - camera_uniform.update_view_proj(&camera); + let mut camera_uniform = CameraUniform::new(); + camera_uniform.update_view_proj(&camera, &projection); // UPDATED! Self { surface, @@ -378,6 +372,7 @@ impl State { tile_mask_indices_uniform_buffer, tile_mask_range, camera, + projection, camera_controller, camera_uniform, mouse_pressed: false @@ -391,7 +386,7 @@ impl State { self.surface_config.height = new_size.height; self.surface.configure(&self.device, &self.surface_config); - self.camera.aspect = self.surface_config.width as f32 / self.surface_config.height as f32; + self.projection.resize(new_size.width, new_size.height); // Re-configure depth buffer self.depth_texture = Texture::create_depth_texture( @@ -421,9 +416,9 @@ impl State { virtual_keycode: Some(key), state, .. - }) => self.camera_controller.process_events(*key, *state), + }) => self.camera_controller.process_keyboard(*key, *state), DeviceEvent::MouseWheel { delta, .. } => { - + self.camera_controller.process_scroll(delta); true } DeviceEvent::Button { @@ -435,7 +430,7 @@ impl State { } DeviceEvent::MouseMotion { delta } => { if self.mouse_pressed { - //self.camera_controller.process_mouse(delta.0, delta.1); + self.camera_controller.process_mouse(delta.0, delta.1); } true } @@ -463,7 +458,6 @@ impl State { scene.zoom, )]), ); - println!("{:?}", &self.camera_uniform.view_proj); self.queue.write_buffer( &self.prims_uniform_buffer, @@ -517,7 +511,7 @@ impl State { pass.set_bind_group(0, &self.bind_group, &[]); -/* { + { // Increment stencil pass.set_pipeline(&self.mask_pipeline); //pass.set_stencil_reference(0); @@ -527,23 +521,17 @@ impl State { ); pass.set_vertex_buffer(0, self.tile_mask_vertex_uniform_buffer.slice(..)); pass.draw_indexed(self.tile_mask_range.clone(), 0, 0..1); - }*/ + } { pass.set_pipeline(&self.render_pipeline); - //pass.set_stencil_reference(1); - /*pass.set_index_buffer( + pass.set_stencil_reference(1); + pass.set_index_buffer( self.indices_uniform_buffer.slice(..), wgpu::IndexFormat::Uint16, ); pass.set_vertex_buffer(0, self.vertex_uniform_buffer.slice(..)); //pass.draw_indexed(self.fill_range.clone(), 0, 0..(self.num_instances as u32)); - pass.draw_indexed(self.tile_stroke_range.clone(), 0, 0..1);*/ - pass.set_index_buffer( - self.tile_mask_indices_uniform_buffer.slice(..), - wgpu::IndexFormat::Uint16, - ); - pass.set_vertex_buffer(0, self.tile_mask_vertex_uniform_buffer.slice(..)); - pass.draw_indexed(self.tile_mask_range.clone(), 0, 0..1); + pass.draw_indexed(self.tile_stroke_range.clone(), 0, 0..1); } } @@ -557,11 +545,8 @@ impl State { let scene = &mut self.scene; let time_secs = self.fps_meter.time_secs as f32; - //self.camera_controller.update_camera(&mut self.camera, dt); - //self.camera_uniform.update_view_proj(&self.camera, &self.projection); - - self.camera_controller.update_camera(&mut self.camera); - self.camera_uniform.update_view_proj(&self.camera); + self.camera_controller.update_camera(&mut self.camera, dt); + self.camera_uniform.update_view_proj(&self.camera, &self.projection); // Animate the zoom to match target_zoom diff --git a/src/render/tesselation.rs b/src/render/tesselation.rs index 05fcdda5..ad1f1a6c 100644 --- a/src/render/tesselation.rs +++ b/src/render/tesselation.rs @@ -194,7 +194,7 @@ impl Tesselated for TileMask { GpuVertex::new([EXTENT, EXTENT], [0.0, 0.0], prim_id), ]; - buffer.indices = vec![0, 2, 1, 1, 2, 3]; + buffer.indices = vec![0, 2, 1, 3,2,1]; initial_indices_count as u32.. buffer.indices.len() as u32 }