Fix "feel-good" panning

This commit is contained in:
Maximilian Ammann 2022-01-17 10:34:55 +01:00
parent 1cadd50d96
commit 0cd5f8fc41
5 changed files with 365 additions and 114 deletions

View File

@ -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<f64>,
delta: Vector2<f64>,
initial_screen_position: Vector2<f64>,
current_screen_position: Vector2<f64>,
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(
&current_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;
}
}

View File

@ -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<Vector2<f64>>,
last_mouse_position: Option<Vector2<f64>>,
initial_camera: Option<Camera>,
initial_camera_position: Option<cgmath::Point3<f64>>,
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<f64>, 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<f64>, 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
}

View File

@ -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<f64> = cgmath::Matrix4::new(
0.0, 0.0, 0.0, 1.0,
);
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct Camera {
pub translation: cgmath::Matrix4<f64>,
pub position: cgmath::Point3<f64>,
pub yaw: cgmath::Rad<f64>,
pub pitch: cgmath::Rad<f64>,
@ -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<f64> {
@ -76,32 +78,198 @@ impl Camera {
)
}
pub fn project_screen_to_world(
&self,
screen: &Vector2<f64>,
view_proj: &Matrix4<f64>,
) -> Vector4<f64> {
fn dx_matrix(width: f64, height: f64) -> Matrix4<f64> {
// 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<f64>, width: f64, height: f64) -> Vector3<f64> {
// 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<f64>, width: f64, height: f64) -> Vector4<f64> {
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<f64>,
origin_clip_space: &Vector4<f64>,
width: f64,
height: f64,
) -> Vector4<f64> {
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<f64>,
view_proj: &Matrix4<f64>,
width: f64,
height: f64,
) -> Vector3<f64> {
#[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<f64>,
view_proj: &Matrix4<f64>,
width: f64,
height: f64,
) -> Vector3<f64> {
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<f64>,
view_proj: &Matrix4<f64>,
) -> Vector3<f64> {
/* 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<f64> {
pub fn calc_matrix(&self) -> cgmath::Matrix4<f64> {
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<f64> = camera.calc_view_proj(&perspective);
let world_pos: Vector4<f64> = 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<f64> = Vector4::new(2000.0, 2000.0, 0.0, 1.0);
println!("world_pos: {:?}", world_pos);
let view_proj: Matrix4<f64> = (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))
}
}

View File

@ -2,7 +2,6 @@
//! the GPU.
mod buffer_pool;
mod options;
mod piplines;
mod shaders;

View File

@ -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],
},