diff --git a/src/input/camera_controller.rs b/src/input/camera_controller.rs index 5a3e8ff9..179df087 100644 --- a/src/input/camera_controller.rs +++ b/src/input/camera_controller.rs @@ -1,4 +1,5 @@ -use cgmath::{EuclideanSpace, Point3, Vector2, Vector3, Zero}; +use cgmath::{EuclideanSpace, Matrix4, Point3, Vector2, Vector3, Vector4, Zero}; +use log::info; use crate::render::camera; @@ -33,11 +34,11 @@ impl CameraController { }; match key { winit::event::VirtualKeyCode::W | winit::event::VirtualKeyCode::Up => { - self.camera_translate.y += amount; + self.camera_translate.y -= amount; true } winit::event::VirtualKeyCode::S | winit::event::VirtualKeyCode::Down => { - self.camera_translate.y -= amount; + self.camera_translate.y += amount; true } winit::event::VirtualKeyCode::A | winit::event::VirtualKeyCode::Left => { @@ -52,19 +53,33 @@ impl CameraController { } } - pub fn process_mouse( + pub fn pan_camera( &mut self, initial_camera_position: cgmath::Point3, - delta: Vector2, + initial_screen_position: Vector2, + current_screen_position: Vector2, + intial_camera: &camera::Camera, camera: &camera::Camera, perspective: &camera::Perspective, ) { let view_proj = camera.calc_view_proj(perspective); - let world = camera.project_screen_to_world(&Vector2::new(0.0, 0.0), &view_proj) - - camera.project_screen_to_world(&delta, &view_proj); + let initial = camera.project_screen_to_world( + &initial_screen_position, + &intial_camera.calc_view_proj(perspective), + ); + let current = camera.project_screen_to_world( + ¤t_screen_position, + &intial_camera.calc_view_proj(perspective), + ); + let delta = initial - current; + + info!("initial: {:?}", initial); + info!("current: {:?}", current); + info!("delta: {:?}", delta); self.camera_position = - Some(initial_camera_position.to_vec() + Vector3::new(-world.x, world.y, 0.0)) + Some(initial_camera_position.to_vec() + Vector3::new(delta.x, delta.y, 0.0)); + //self.camera_position = Some(Vector3::new(-delta.x, delta.y, 0.0)) } pub fn process_scroll(&mut self, delta: &winit::event::MouseScrollDelta) { @@ -85,7 +100,9 @@ impl CameraController { let dt = dt.as_secs_f64() * self.speed; if let Some(position) = self.camera_position { + info!("position: {:?}", position); camera.position = Point3::from_vec(position); + //camera.translation = Matrix4::from_translation(position); self.camera_position = None; } @@ -95,7 +112,6 @@ impl CameraController { // to get closer to an object you want to focus on. let delta = self.camera_translate * dt; camera.position += delta; - self.camera_translate -= delta; } } diff --git a/src/input/mod.rs b/src/input/mod.rs index ae22473c..b46e56e5 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -8,6 +8,7 @@ use winit::event::{ }; use crate::input::camera_controller::CameraController; +use crate::render::camera::Camera; use crate::render::render_state::RenderState; mod camera_controller; @@ -15,19 +16,21 @@ mod camera_controller; pub struct InputHandler { camera_controller: CameraController, + mouse_position: Option>, last_mouse_position: Option>, + initial_camera: Option, initial_camera_position: Option>, mouse_pressed: bool, - target_stroke_width: f32, } impl InputHandler { pub fn new() -> Self { let camera_controller = CameraController::new(5.0, 100.0); Self { - target_stroke_width: 1.0, initial_camera_position: None, + mouse_position: None, last_mouse_position: None, + initial_camera: None, mouse_pressed: false, camera_controller, } @@ -39,29 +42,34 @@ impl InputHandler { } } - fn process_mouse_delta(&mut self, position: Vector2, state: &mut RenderState) { - if let (Some(last_mouse_position), Some(initial_camera_position)) = - (self.last_mouse_position, self.initial_camera_position) - { - let delta = last_mouse_position - position; - self.camera_controller.process_mouse( + fn pan_camera(&mut self, position: Vector2, render_state: &mut RenderState) { + if let (Some(last_mouse_position), Some(initial_camera_position), Some(initial_camera)) = ( + self.last_mouse_position, + self.initial_camera_position, + self.initial_camera.as_ref(), + ) { + self.camera_controller.pan_camera( initial_camera_position, - delta, - &state.camera, - &state.perspective, + last_mouse_position, + position, + initial_camera, + &render_state.camera, + &render_state.perspective, ); } else { self.last_mouse_position = Some(position); - self.initial_camera_position = Some(state.camera.position); + self.initial_camera_position = Some(render_state.camera.position); + self.initial_camera = Some(render_state.camera.clone()); } } - pub fn window_input(&mut self, event: &WindowEvent, state: &mut RenderState) -> bool { + pub fn window_input(&mut self, event: &WindowEvent, render_state: &mut RenderState) -> bool { match event { WindowEvent::CursorMoved { position, .. } => { if self.mouse_pressed { let mouse_position: (f64, f64) = position.to_owned().into(); - self.process_mouse_delta(Vector2::from(mouse_position), state); + self.pan_camera(Vector2::from(mouse_position), render_state); + self.mouse_position = Some(Vector2::from(mouse_position)); } true } @@ -74,14 +82,6 @@ impl InputHandler { }, .. } => 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) => { @@ -89,10 +89,10 @@ impl InputHandler { match touch.phase { TouchPhase::Started => { self.last_mouse_position = Some(Vector2::from(touch_position)); - self.initial_camera_position = Some(state.camera.position); + self.initial_camera_position = Some(render_state.camera.position); } TouchPhase::Moved | TouchPhase::Ended => { - self.process_mouse_delta(Vector2::from(touch_position), state); + self.pan_camera(Vector2::from(touch_position), render_state); } TouchPhase::Cancelled => {} } @@ -111,8 +111,30 @@ impl InputHandler { self.mouse_pressed = *state == ElementState::Pressed; if !self.mouse_pressed { + /*if let ( + Some(last_mouse_position), + Some(initial_camera_position), + Some(initial_camera), + Some(mouse_position), + ) = ( + self.last_mouse_position, + self.initial_camera_position, + self.initial_camera.as_ref(), + self.mouse_position, + ) { + self.camera_controller.pan_camera( + initial_camera_position, + last_mouse_position, + mouse_position, + initial_camera, + &render_state.camera, + &render_state.perspective, + ); + }*/ + self.last_mouse_position = None; self.initial_camera_position = None; + self.initial_camera = None; } true } diff --git a/src/render/camera.rs b/src/render/camera.rs index 6c2a3eec..51c64398 100644 --- a/src/render/camera.rs +++ b/src/render/camera.rs @@ -1,5 +1,5 @@ use cgmath::prelude::*; -use cgmath::{Matrix4, Vector2, Vector4}; +use cgmath::{Matrix4, Vector2, Vector3, Vector4}; use crate::render::shaders::ShaderCamera; @@ -19,8 +19,9 @@ pub const FLIP_Y: cgmath::Matrix4 = cgmath::Matrix4::new( 0.0, 0.0, 0.0, 1.0, ); -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Camera { + pub translation: cgmath::Matrix4, pub position: cgmath::Point3, pub yaw: cgmath::Rad, pub pitch: cgmath::Rad, @@ -43,6 +44,7 @@ impl Camera { ) -> Self { Self { position: position.into(), + translation: Matrix4::identity(), yaw: yaw.into(), pitch: pitch.into(), width: width as f64, @@ -61,7 +63,7 @@ impl Camera { cgmath::Vector3::new(self.yaw.0.cos(), self.pitch.0.sin(), self.yaw.0.sin()) .normalize(), cgmath::Vector3::unit_y(), - ) + ) * self.translation } pub fn calc_view_proj(&self, perspective: &Perspective) -> Matrix4 { @@ -76,32 +78,198 @@ impl Camera { ) } - pub fn project_screen_to_world( - &self, - screen: &Vector2, - view_proj: &Matrix4, - ) -> Vector4 { + fn dx_matrix(width: f64, height: f64) -> Matrix4 { + // Adapted from: https://docs.microsoft.com/en-us/windows/win32/direct3d9/viewports-and-clipping#viewport-rectangle + 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 px = width as f64; + let py = height as f64; + let pz = max_depth - min_depth; + 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), + ) + } + + fn clip_to_window(clip: &Vector4, width: f64, height: f64) -> Vector3 { + // Adopted from: https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkViewport.html + // and https://matthewwellings.com/blog/the-new-vulkan-coordinate-system/ + #[rustfmt::skip] + let ndc = Vector4::new( + clip.x / clip.w, + clip.y / clip.w, + clip.z / clip.w, + clip.w / clip.w + ); + let min_depth = 0.0; let max_depth = 1.0; let x = 0.0; let y = 0.0; - let ox = x + self.width / 2.0; - let oy = y + self.height / 2.0; + let ox = x + width as f64 / 2.0; + let oy = y + height as f64 / 2.0; let oz = min_depth; + let px = width as f64; + let py = height as f64; let pz = max_depth - min_depth; + let xd = ndc.x; + let yd = ndc.y; + let zd = ndc.z; + Vector3::new(px / 2.0 * xd + ox, py / 2.0 * yd + oy, pz * zd + oz) + } - // Adapted from: https://docs.microsoft.com/en-us/windows/win32/direct3d9/viewports-and-clipping#viewport-rectangle - let direct_x = Matrix4::from_cols( - Vector4::new(self.width as f64 / 2.0, 0.0, 0.0, 0.0), - Vector4::new(0.0, self.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), + // https://docs.microsoft.com/en-us/windows/win32/dxtecharts/the-direct3d-transformation-pipeline + fn clip_to_window_matrix(clip: &Vector4, width: f64, height: f64) -> Vector4 { + let w = clip.w; + let z = clip.z; + println!("z in clip space: {z}"); + println!("w in clip space: {w}"); + + #[rustfmt::skip] + let ndc = Vector4::new( + clip.x / clip.w, + clip.y / clip.w, + clip.z / clip.w, + clip.w / clip.w ); - let homogenous = Vector4::new(screen.x, screen.y, 1.0, 1.0) * self.position.z; + let window = Self::dx_matrix(width, height) * ndc; + window + } - view_proj.invert().unwrap() * direct_x.invert().unwrap() * homogenous + // https://docs.microsoft.com/en-us/windows/win32/dxtecharts/the-direct3d-transformation-pipeline + fn window_to_clip( + window: &Vector3, + origin_clip_space: &Vector4, + width: f64, + height: f64, + ) -> Vector4 { + let z = window.z; + println!("z in window space: {z}"); + #[rustfmt::skip] + let fixed_window = Vector4::new( + window.x, + window.y, + window.z, + 1.0 + ); + + let ndc = Self::dx_matrix(width, height).invert().unwrap() * fixed_window; + + let w = origin_clip_space.w; + + #[rustfmt::skip] + let clip = Vector4::new( + ndc.x * w, + ndc.y * w, + ndc.z * w, + w, + ); + + clip + } + + fn window_to_world( + window: &Vector3, + view_proj: &Matrix4, + width: f64, + height: f64, + ) -> Vector3 { + #[rustfmt::skip] + let fixed_window = Vector4::new( + window.x, + window.y, + window.z, + 1.0 + ); + + let ndc = Self::dx_matrix(width, height).invert().unwrap() * fixed_window; + let unprojected = view_proj.invert().unwrap() * ndc; + let world = Vector3::new( + unprojected.x / unprojected.w, + unprojected.y / unprojected.w, + unprojected.z / unprojected.w, + ); + world + } + + fn window_to_world_nalgebra( + window: &Vector3, + view_proj: &Matrix4, + width: f64, + height: f64, + ) -> Vector3 { + let pt = Vector4::new( + 2.0 * (window.x - 0.0) / width - 1.0, + 2.0 * (window.y - 0.0) / height - 1.0, + window.z, + 1.0, + ); + + /* // opengl + let pt = Vector4::new( + 2.0 * (window.x - 0.0) / width - 1.0, + 2.0 * (window.y - 0.0) / height - 1.0, + 2.0 * window.z - 1.0, + 1.0, + ); + */ + let unprojected_nalgebra = view_proj.invert().unwrap() * pt; + let world = Vector3::new( + unprojected_nalgebra.x / unprojected_nalgebra.w, + unprojected_nalgebra.y / unprojected_nalgebra.w, + unprojected_nalgebra.z / unprojected_nalgebra.w, + ); + world + } + + pub fn project_screen_to_world( + &self, + window: &Vector2, + view_proj: &Matrix4, + ) -> Vector3 { + /* let origin_clip_space = (view_proj * Vector4::new(0.0, 0.0, 0.0, 1.0)); + + let origin_window_space = + Self::clip_to_window_matrix(&origin_clip_space, self.width, self.height); + let reverse_clip = Self::window_to_clip( + &Vector3::new(window.x, window.y, origin_window_space.z), + &origin_clip_space, + self.width, + self.height, + );*/ + + let near_world = Camera::window_to_world( + &Vector3::new(window.x, window.y, 0.0), + &view_proj, + self.width, + self.height, + ); + + let far_world = Camera::window_to_world( + &Vector3::new(window.x, window.y, 1.0), + &view_proj, + self.width, + self.height, + ); + + // for z = 0 in world coordinates + let u = -near_world.z / (far_world.z - near_world.z); + println!("u: {:?}", u); + + /*let vec = (near_world - far_world).normalize(); + let znear = 0.1; + near_world + znear * vec*/ + + near_world + u * (far_world - near_world) } } @@ -132,83 +300,129 @@ impl Perspective { self.aspect = width as f64 / height as f64; } - fn calc_matrix(&self) -> cgmath::Matrix4 { + pub fn calc_matrix(&self) -> cgmath::Matrix4 { OPENGL_TO_WGPU_MATRIX * cgmath::perspective(self.fovy, self.aspect, self.znear, self.zfar) } } #[cfg(test)] mod tests { - use cgmath::{ElementWise, Matrix4, SquareMatrix, Vector3, Vector4}; + use cgmath::{AbsDiffEq, ElementWise, Matrix4, SquareMatrix, Vector2, Vector3, Vector4}; use super::{Camera, Perspective}; #[test] fn test() { - let camera = Camera::new((0.0, 5.0, 5000.0), cgmath::Deg(-90.0), cgmath::Deg(-0.0)); - let width = 1920; - let height = 1080; - let perspective = Perspective::new(width, height, cgmath::Deg(45.0), 0.1, 100000.0); - let projection = perspective; + let width = 1920.0; + let height = 1080.0; + let camera = Camera::new( + (0.0, 5.0, 5000.0), + cgmath::Deg(-90.0), + cgmath::Deg(45.0), + width as u32, + height as u32, + ); + // 4732.561319582916 + let perspective = Perspective::new( + width as u32, + height as u32, + cgmath::Deg(45.0), + 0.1, + 100000.0, + ); + let view_proj: Matrix4 = camera.calc_view_proj(&perspective); + + let world_pos: Vector4 = Vector4::new(0.0, 0.0, 0.0, 1.0); + let clip = view_proj * world_pos; + + let origin_clip_space = view_proj * Vector4::new(0.0, 0.0, 0.0, 1.0); + println!("origin w in clip space: {:?}", origin_clip_space.w); - let world_pos: Vector4 = Vector4::new(2000.0, 2000.0, 0.0, 1.0); println!("world_pos: {:?}", world_pos); - let view_proj: Matrix4 = (projection.calc_matrix() * camera.calc_matrix()) - .cast() - .unwrap(); + println!("clip: {:?}", clip); + println!("world_pos: {:?}", view_proj.invert().unwrap() * clip); - let result = view_proj * world_pos; - println!("result: {:?}", result); + println!("window: {:?}", Camera::clip_to_window(&clip, width, height)); + let window = Camera::clip_to_window_matrix(&clip, width, height); + println!("window (matrix): {:?}", window); - // Adopted from: https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkViewport.html - // and https://matthewwellings.com/blog/the-new-vulkan-coordinate-system/ - let result_ndc = Vector4::new( - result.x / result.w, - result.y / result.w, - result.z / result.w, - result.w, + let origin_window_space = Camera::clip_to_window_matrix(&origin_clip_space, width, height); + let reverse_clip = Camera::window_to_clip( + &Vector3::new(window.x, window.y, origin_window_space.z), + &origin_clip_space, + width, + height, + ); + let reverse_world = view_proj.invert().unwrap() * reverse_clip; + + println!("r clip: {:?}", reverse_clip); + println!("r world: {:?}", reverse_world); + + // --------- nalgebra + + let scale = 1.0 / origin_clip_space.w; + + let origin_window_space_nalgebra = Vector3::new( + 0.0 + (width * (origin_clip_space.x * scale + 1.0) * 0.5), + 0.0 + (height * (origin_clip_space.y * scale + 1.0) * 0.5), + origin_clip_space.z * scale, + ); + println!("r origin (nalgebra): {:?}", origin_window_space_nalgebra); + println!( + "r world (nalgebra): {:?}", + Camera::window_to_world_nalgebra(&window.truncate(), &view_proj, width, height) + ); + // -------- + + // pdf trick + let near_world = Camera::window_to_world_nalgebra( + &Vector3::new(window.x, window.y, 0.0), + &view_proj, + width, + height, ); - let min_depth = 0.0; - let max_depth = 1.0; - - let x = 0.0; - let y = 0.0; - let ox = x + width as f64 / 2.0; - let oy = y + height as f64 / 2.0; - let oz = min_depth; - let px = width as f64; - let py = height as f64; - let pz = max_depth - min_depth; - let xd = result_ndc.x; - let yd = result_ndc.y; - let zd = result_ndc.z; - let screen = Vector3::new(px / 2.0 * xd + ox, py / 2.0 * yd + oy, pz * zd + oz); - println!("screen: {:?}", screen); - - // 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 far_world = Camera::window_to_world_nalgebra( + &Vector3::new(window.x, window.y, 1.0), + &view_proj, + width, + height, ); - let screen_hom = direct_x * result; - println!("screen_hom: {:?}", screen_hom); - let screen = Vector3::new( - screen_hom.x / screen_hom.w, - screen_hom.y / screen_hom.w, - screen_hom.z / screen_hom.w, - ); - 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; - 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; - println!("world_pos: {:?}", world_pos); + // for z = 0 in world coordinates + let u = -near_world.z / (far_world.z - near_world.z); + println!("u: {:?}", u); + let unprojected = near_world + u * (far_world - near_world); + println!("unprojected: {:?}", unprojected); + //assert!(Vector3::new(world_pos.x, world_pos.y, world_pos.z).abs_diff_eq(&unprojected, 0.05)); + //.------ + + // ---- test for unproject + + let window = Vector2::new(960.0, 631.0); // 0, 4096: passt nicht + //let window = Vector2::new(962.0, 1.0); // 0, 300: passt nicht + //let window = Vector2::new(960.0, 540.0); // 0, 0 passt + let near_world = Camera::window_to_world( + &Vector3::new(window.x, window.y, 0.0), + &view_proj, + width, + height, + ); + + let far_world = Camera::window_to_world( + &Vector3::new(window.x, window.y, 1.0), + &view_proj, + width, + height, + ); + + // for z = 0 in world coordinates + let u = -near_world.z / (far_world.z - near_world.z); + println!("u: {:?}", u); + let unprojected = near_world + u * (far_world - near_world); + println!("unprojected: {:?}", unprojected); + // ---- + + //assert!(reverse_world.abs_diff_eq(&world_pos, 0.05)) } } diff --git a/src/render/mod.rs b/src/render/mod.rs index e90716a6..2a832672 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -2,7 +2,6 @@ //! the GPU. mod buffer_pool; - mod options; mod piplines; mod shaders; diff --git a/src/render/render_state.rs b/src/render/render_state.rs index 9b6584d5..47f7a9b5 100644 --- a/src/render/render_state.rs +++ b/src/render/render_state.rs @@ -260,7 +260,7 @@ impl RenderState { let camera = camera::Camera::new( (0.0, 5.0, 5000.0), cgmath::Deg(-90.0), - cgmath::Deg(-0.0), + cgmath::Deg(-45.0), size.width, size.height, ); @@ -268,7 +268,7 @@ impl RenderState { surface_config.width, surface_config.height, cgmath::Deg(45.0), - 0.1, + 10.0, 100000.0, ); @@ -378,7 +378,7 @@ impl RenderState { "transportation" => [1.0, 0.0, 0.0, 1.0], "building" => [0.0, 1.0, 1.0, 1.0], "boundary" => [0.0, 0.0, 0.0, 1.0], - "water" => [0.0, 0.0, 1.0, 1.0], + "water" => [0.0, 0.0, 0.7, 1.0], "waterway" => [0.0, 0.0, 1.0, 1.0], _ => [0.0, 0.0, 0.0, 0.0], },