From d2579699c7aa511cedc6477eab27fb0c1fe5be0f Mon Sep 17 00:00:00 2001 From: Ryo Kawaguchi Date: Thu, 20 Jul 2023 20:09:28 +0900 Subject: [PATCH 01/17] Support pan, rotate and zoom camera operations --- crates/viewer/src/camera.rs | 58 ++++++++++++++++++++++------ crates/viewer/src/main.rs | 76 +++++++++++++++++++++++++++++++------ 2 files changed, 111 insertions(+), 23 deletions(-) diff --git a/crates/viewer/src/camera.rs b/crates/viewer/src/camera.rs index bdaafc7d..30631e59 100644 --- a/crates/viewer/src/camera.rs +++ b/crates/viewer/src/camera.rs @@ -1,4 +1,6 @@ -use glam::{vec3, Mat4}; +use glam::{vec3, Mat3, Mat4, Vec3}; + +const MIN_ZOOM_FACTOR: f32 = 0.05; #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum Projection { @@ -11,11 +13,22 @@ enum Projection { pub struct Camera { projection: Projection, aspect_ratio: f32, + zoom_factor: f32, + position: Vec3, + upward: Vec3, + target: Vec3, } impl Camera { pub fn new(width: u32, height: u32) -> Self { - Self { projection: Projection::Orthographic, aspect_ratio: width as f32 / height as f32 } + Self { + projection: Projection::Orthographic, + aspect_ratio: width as f32 / height as f32, + zoom_factor: 1.0, + position: vec3(20.0, -30.0, 20.0), + upward: vec3(0.0, 0.0, 1.0), + target: vec3(0.0, 0.0, 0.0), + } } pub fn resize(&mut self, width: u32, height: u32) { @@ -30,16 +43,43 @@ impl Camera { self.projection = Projection::Orthographic; } + /// Pan the camera view horizontally and vertically. Look-at target will move along with the + /// camera. + pub fn pan(&mut self, x: f32, y: f32) { + let forward = self.target - self.position; + let rightward = self.upward.cross(forward).normalize(); + let translation = rightward * x + self.upward * y; + self.position += translation; + self.target += translation; + } + + /// Zoom in or out, while looking at the same target. + pub fn zoom(&mut self, zoom_delta: f32) { + // Change the camera position for perspective projection. + let forward = self.target - self.position; + self.position += forward * zoom_delta; + // Update the zoom factor for orthographic projection. + self.zoom_factor = (self.zoom_factor - zoom_delta).max(MIN_ZOOM_FACTOR); + } + + /// Orbit around the target while keeping the distance. + pub fn rotate(&mut self, yaw: f32, pitch: f32) { + let backward = self.position - self.target; + let yaw_rotation = Mat3::from_rotation_z(yaw); + let pitch_rotation = Mat3::from_rotation_x(pitch); + self.position = self.target + yaw_rotation * pitch_rotation * backward + } + pub fn matrix(&self) -> Mat4 { // These magic numbers are configured so that the particular model we are loading is // visible in its entirety. They will be dynamically computed eventually when we have "fit // to view" function or alike. let proj = match self.projection { Projection::Orthographic => Mat4::orthographic_rh( - -100.0 * self.aspect_ratio, - 100.0 * self.aspect_ratio, - -100.0, - 100.0, + -50.0 * self.zoom_factor * self.aspect_ratio, + 50.0 * self.zoom_factor * self.aspect_ratio, + -50.0 * self.zoom_factor, + 50.0 * self.zoom_factor, -1000.0, 1000.0, ), @@ -48,11 +88,7 @@ impl Camera { }, }; - let view = Mat4::look_at_rh( - vec3(20.0, -30.0, 20.0), // Eye position - vec3(0.0, 0.0, 0.0), // Look-at target - vec3(0.0, 0.0, 1.0), // Up vector of the camera - ); + let view = Mat4::look_at_rh(self.position, self.target, self.upward); proj * view } diff --git a/crates/viewer/src/main.rs b/crates/viewer/src/main.rs index 7a0408c5..a063ee2e 100644 --- a/crates/viewer/src/main.rs +++ b/crates/viewer/src/main.rs @@ -4,7 +4,7 @@ use crate::{ }; use anyhow::Error; use clap::{Parser, ValueEnum}; -use glam::{vec3, DVec3, Mat4}; +use glam::{vec2, vec3, DVec3, Mat4, Vec2}; use opencascade::primitives::Shape; use simple_game::{ graphics::{ @@ -17,7 +17,8 @@ use simple_game::{ use smaa::{SmaaMode, SmaaTarget}; use std::path::PathBuf; use winit::{ - event::{KeyEvent, WindowEvent}, + dpi::PhysicalPosition, + event::{ElementState, KeyEvent, MouseButton, MouseScrollDelta::PixelDelta, WindowEvent}, event_loop::EventLoopWindowTarget, keyboard::{KeyCode, PhysicalKey}, window::Window, @@ -27,9 +28,40 @@ mod camera; mod edge_drawer; mod surface_drawer; -const MIN_SCALE: f32 = 0.01; +// Multipliers to convert mouse position deltas to a more sane camera perspective change. +const ZOOM_MULTIPLIER: f32 = 5.0; +const TOUCHPAD_ZOOM_MULTIPLIER: f32 = 2.0; +const ROTATE_MULTIPLIER: f32 = 5.0; +const TOUCHPAD_ROTATE_MULTIPLIER: f32 = 0.05; +const PAN_MULTIPLIER: f32 = 100.0; + +#[derive(Default)] +struct MouseState { + left_button_down: bool, + middle_button_down: bool, + right_button_down: bool, + last_position: PhysicalPosition, +} + +impl MouseState { + fn delta(&mut self, position: PhysicalPosition) -> (f64, f64) { + let delta = (position.x - self.last_position.x, position.y - self.last_position.y); + self.last_position = position; + delta + } + + fn input(&mut self, button: MouseButton, state: ElementState) { + match button { + MouseButton::Left => self.left_button_down = state == ElementState::Pressed, + MouseButton::Middle => self.middle_button_down = state == ElementState::Pressed, + MouseButton::Right => self.right_button_down = state == ElementState::Pressed, + _ => {}, + } + } +} struct ViewerApp { + client_rect: Vec2, camera: camera::Camera, depth_texture: DepthTexture, text_system: TextSystem, @@ -39,8 +71,7 @@ struct ViewerApp { smaa_target: SmaaTarget, rendered_edges: RenderedLine, cad_mesh: CadMesh, - angle: f32, - scale: f32, + mouse_state: MouseState, } #[derive(Parser, Debug, Clone)] @@ -149,6 +180,7 @@ impl GameApp for ViewerApp { let depth_texture_format = depth_texture.format(); Self { + client_rect: vec2(width as f32, height as f32), camera: camera::Camera::new(width, height), depth_texture, text_system: TextSystem::new(device, surface_texture_format, width, height), @@ -168,12 +200,12 @@ impl GameApp for ViewerApp { smaa_target, cad_mesh, rendered_edges, - angle: 0.0, - scale: 1.0, + mouse_state: Default::default(), } } fn resize(&mut self, graphics_device: &mut GraphicsDevice, width: u32, height: u32) { + self.client_rect = vec2(width as f32, height as f32); self.camera.resize(width, height); self.depth_texture = DepthTexture::new(graphics_device.device(), width, height); self.text_system.resize(width, height); @@ -188,11 +220,32 @@ impl GameApp for ViewerApp { ) { match event { WindowEvent::TouchpadRotate { delta, .. } => { - self.angle += 2.0 * delta * std::f32::consts::PI / 180.0; + self.camera.rotate(delta * TOUCHPAD_ROTATE_MULTIPLIER, 0.0); + }, + WindowEvent::CursorMoved { position, .. } => { + let delta = self.mouse_state.delta(*position); + let delta_x = delta.0 as f32 / self.client_rect.x; + let delta_y = delta.1 as f32 / self.client_rect.y; + if self.mouse_state.left_button_down { + self.camera.rotate(delta_x * ROTATE_MULTIPLIER, delta_y * ROTATE_MULTIPLIER); + } + if self.mouse_state.middle_button_down { + self.camera.pan(delta_x * PAN_MULTIPLIER, delta_y * PAN_MULTIPLIER); + } + if self.mouse_state.right_button_down { + self.camera.zoom(delta_y * ZOOM_MULTIPLIER); + } + }, + WindowEvent::MouseInput { state, button, .. } => { + self.mouse_state.input(*button, *state) + }, + WindowEvent::MouseWheel { delta: PixelDelta(delta), .. } => { + let delta_x = delta.x as f32 / self.client_rect.x; + let delta_y = delta.y as f32 / self.client_rect.y; + self.camera.pan(delta_x * PAN_MULTIPLIER, delta_y * PAN_MULTIPLIER); }, WindowEvent::TouchpadMagnify { delta, .. } => { - self.scale += *delta as f32; - self.scale = self.scale.max(MIN_SCALE); + self.camera.zoom(*delta as f32 * TOUCHPAD_ZOOM_MULTIPLIER); }, WindowEvent::KeyboardInput { event: KeyEvent { physical_key: PhysicalKey::Code(key_code), .. }, @@ -219,8 +272,7 @@ impl GameApp for ViewerApp { ); let camera_matrix = self.camera.matrix(); - let transform = Mat4::from_rotation_z(self.angle) - * Mat4::from_scale(vec3(self.scale, self.scale, self.scale)); + let transform = Mat4::IDENTITY; self.surface_drawer.render( &mut frame_encoder.encoder, From e9c046800a72002271c9c40c96be6fd7d3969384 Mon Sep 17 00:00:00 2001 From: Ryo Kawaguchi Date: Tue, 25 Jul 2023 02:41:30 +0900 Subject: [PATCH 02/17] Add comments to Camera struct fields. --- crates/viewer/src/camera.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/viewer/src/camera.rs b/crates/viewer/src/camera.rs index 30631e59..fcaa3d55 100644 --- a/crates/viewer/src/camera.rs +++ b/crates/viewer/src/camera.rs @@ -13,9 +13,13 @@ enum Projection { pub struct Camera { projection: Projection, aspect_ratio: f32, + // Zoom factor used for orthographic projection. zoom_factor: f32, + // Position of the camera. position: Vec3, + // The upward vector of the camera, determining its orientation. upward: Vec3, + // The look-at target, in the center of the view. target: Vec3, } From 8e8685e1bc52cbcee9618a7f8dc91c5db0f39866 Mon Sep 17 00:00:00 2001 From: Ryo Kawaguchi Date: Sat, 9 Sep 2023 17:46:05 +0900 Subject: [PATCH 03/17] Use quartenion rotation --- crates/viewer/src/camera.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/crates/viewer/src/camera.rs b/crates/viewer/src/camera.rs index fcaa3d55..a03f5bc2 100644 --- a/crates/viewer/src/camera.rs +++ b/crates/viewer/src/camera.rs @@ -1,4 +1,4 @@ -use glam::{vec3, Mat3, Mat4, Vec3}; +use glam::{vec3, Mat3, Mat4, Quat, Vec3}; const MIN_ZOOM_FACTOR: f32 = 0.05; @@ -68,10 +68,12 @@ impl Camera { /// Orbit around the target while keeping the distance. pub fn rotate(&mut self, yaw: f32, pitch: f32) { - let backward = self.position - self.target; - let yaw_rotation = Mat3::from_rotation_z(yaw); - let pitch_rotation = Mat3::from_rotation_x(pitch); - self.position = self.target + yaw_rotation * pitch_rotation * backward + let backward = (self.position - self.target).normalize(); + let leftward = self.upward.cross(backward); + let rotation = + Quat::from_axis_angle(self.upward, yaw) * Quat::from_axis_angle(leftward, pitch); + self.position = (self.target + rotation * backward) * backward.length(); + self.upward = rotation * self.upward; } pub fn matrix(&self) -> Mat4 { From 2189ea54fb04a0366b44634ce50e4656905e9559 Mon Sep 17 00:00:00 2001 From: Ryo Kawaguchi Date: Sat, 9 Sep 2023 18:01:35 +0900 Subject: [PATCH 04/17] Adjust the conversion from mouse events to viewport change --- crates/viewer/src/main.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/crates/viewer/src/main.rs b/crates/viewer/src/main.rs index a063ee2e..77aded6d 100644 --- a/crates/viewer/src/main.rs +++ b/crates/viewer/src/main.rs @@ -28,12 +28,13 @@ mod camera; mod edge_drawer; mod surface_drawer; -// Multipliers to convert mouse position deltas to a more sane camera perspective change. +// Multipliers to convert mouse position deltas to a more intuitve camera perspective change. const ZOOM_MULTIPLIER: f32 = 5.0; -const TOUCHPAD_ZOOM_MULTIPLIER: f32 = 2.0; -const ROTATE_MULTIPLIER: f32 = 5.0; -const TOUCHPAD_ROTATE_MULTIPLIER: f32 = 0.05; -const PAN_MULTIPLIER: f32 = 100.0; +const TOUCHPAD_ZOOM_MULTIPLIER: f32 = 0.5; +const ROTATE_MULTIPLIER: f32 = -5.0; +const TOUCHPAD_ROTATE_MULTIPLIER: f32 = -0.05; +const PAN_MULTIPLIER: f32 = 150.0; +const TOUCHPAD_PAN_MULTIPLIER: f32 = 100.0; #[derive(Default)] struct MouseState { @@ -240,9 +241,13 @@ impl GameApp for ViewerApp { self.mouse_state.input(*button, *state) }, WindowEvent::MouseWheel { delta: PixelDelta(delta), .. } => { + // winit can not distinguish mouse wheel and touchpad pan events unfortunately. + // Because of that, we assign pan operation to MouseWheel events. For mice, you + // need to instead use mouse move while holding down the right button. let delta_x = delta.x as f32 / self.client_rect.x; let delta_y = delta.y as f32 / self.client_rect.y; - self.camera.pan(delta_x * PAN_MULTIPLIER, delta_y * PAN_MULTIPLIER); + self.camera + .pan(delta_x * TOUCHPAD_PAN_MULTIPLIER, delta_y * TOUCHPAD_PAN_MULTIPLIER); }, WindowEvent::TouchpadMagnify { delta, .. } => { self.camera.zoom(*delta as f32 * TOUCHPAD_ZOOM_MULTIPLIER); From 6e4a78e599d3160f87e4b861b51be6fca796b9f7 Mon Sep 17 00:00:00 2001 From: Ryo Kawaguchi Date: Sat, 9 Sep 2023 18:08:56 +0900 Subject: [PATCH 05/17] Fix CI --- crates/viewer/src/camera.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/viewer/src/camera.rs b/crates/viewer/src/camera.rs index a03f5bc2..6003cc5b 100644 --- a/crates/viewer/src/camera.rs +++ b/crates/viewer/src/camera.rs @@ -1,4 +1,4 @@ -use glam::{vec3, Mat3, Mat4, Quat, Vec3}; +use glam::{vec3, Mat4, Quat, Vec3}; const MIN_ZOOM_FACTOR: f32 = 0.05; From b2c47f46061bf2d7fec13e769e1821f6321dbc33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kov=C3=A1cs?= <481354+mkovaxx@users.noreply.github.com> Date: Sun, 26 Nov 2023 20:56:21 +0900 Subject: [PATCH 06/17] Apply suggestions from code review --- crates/viewer/src/camera.rs | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/crates/viewer/src/camera.rs b/crates/viewer/src/camera.rs index 6003cc5b..c6f8d36d 100644 --- a/crates/viewer/src/camera.rs +++ b/crates/viewer/src/camera.rs @@ -10,17 +10,17 @@ enum Projection { Perspective, } -pub struct Camera { +pub struct OrbitCamera { projection: Projection, aspect_ratio: f32, // Zoom factor used for orthographic projection. zoom_factor: f32, - // Position of the camera. - position: Vec3, - // The upward vector of the camera, determining its orientation. - upward: Vec3, // The look-at target, in the center of the view. target: Vec3, + // The radius of the orbit + radius: f32, + // The orientation of the camera around the target point + orientation: Quat, } impl Camera { @@ -67,13 +67,8 @@ impl Camera { } /// Orbit around the target while keeping the distance. - pub fn rotate(&mut self, yaw: f32, pitch: f32) { - let backward = (self.position - self.target).normalize(); - let leftward = self.upward.cross(backward); - let rotation = - Quat::from_axis_angle(self.upward, yaw) * Quat::from_axis_angle(leftward, pitch); - self.position = (self.target + rotation * backward) * backward.length(); - self.upward = rotation * self.upward; + pub fn rotate(&mut self, rotator: Quat) { + self.orientation *= rotator; } pub fn matrix(&self) -> Mat4 { From 28e86dba54ea34ba9b0fa1268fbdb55e5f42a15f Mon Sep 17 00:00:00 2001 From: Mate Kovacs Date: Sun, 26 Nov 2023 21:24:54 +0900 Subject: [PATCH 07/17] get rotation working --- crates/viewer/src/camera.rs | 33 ++++++++++++++------------------- crates/viewer/src/main.rs | 26 ++++++++++++++++++++------ 2 files changed, 34 insertions(+), 25 deletions(-) diff --git a/crates/viewer/src/camera.rs b/crates/viewer/src/camera.rs index c6f8d36d..40e0e5c8 100644 --- a/crates/viewer/src/camera.rs +++ b/crates/viewer/src/camera.rs @@ -1,4 +1,4 @@ -use glam::{vec3, Mat4, Quat, Vec3}; +use glam::{vec3, Mat3, Mat4, Quat, Vec3}; const MIN_ZOOM_FACTOR: f32 = 0.05; @@ -23,15 +23,15 @@ pub struct OrbitCamera { orientation: Quat, } -impl Camera { +impl OrbitCamera { pub fn new(width: u32, height: u32) -> Self { Self { projection: Projection::Orthographic, aspect_ratio: width as f32 / height as f32, zoom_factor: 1.0, - position: vec3(20.0, -30.0, 20.0), - upward: vec3(0.0, 0.0, 1.0), target: vec3(0.0, 0.0, 0.0), + radius: 100.0, + orientation: Quat::IDENTITY, } } @@ -47,24 +47,16 @@ impl Camera { self.projection = Projection::Orthographic; } + fn get_local_frame(&self) -> Mat3 { + Mat3::from_quat(self.orientation) + } + /// Pan the camera view horizontally and vertically. Look-at target will move along with the /// camera. - pub fn pan(&mut self, x: f32, y: f32) { - let forward = self.target - self.position; - let rightward = self.upward.cross(forward).normalize(); - let translation = rightward * x + self.upward * y; - self.position += translation; - self.target += translation; - } + pub fn pan(&mut self, x: f32, y: f32) {} /// Zoom in or out, while looking at the same target. - pub fn zoom(&mut self, zoom_delta: f32) { - // Change the camera position for perspective projection. - let forward = self.target - self.position; - self.position += forward * zoom_delta; - // Update the zoom factor for orthographic projection. - self.zoom_factor = (self.zoom_factor - zoom_delta).max(MIN_ZOOM_FACTOR); - } + pub fn zoom(&mut self, zoom_delta: f32) {} /// Orbit around the target while keeping the distance. pub fn rotate(&mut self, rotator: Quat) { @@ -89,7 +81,10 @@ impl Camera { }, }; - let view = Mat4::look_at_rh(self.position, self.target, self.upward); + let local_frame = self.get_local_frame(); + let position = self.target + self.radius * local_frame.z_axis; + let upward = local_frame.y_axis; + let view = Mat4::look_at_rh(position, self.target, upward); proj * view } diff --git a/crates/viewer/src/main.rs b/crates/viewer/src/main.rs index 77aded6d..c53f2f0b 100644 --- a/crates/viewer/src/main.rs +++ b/crates/viewer/src/main.rs @@ -3,8 +3,9 @@ use crate::{ surface_drawer::{CadMesh, SurfaceDrawer}, }; use anyhow::Error; +use camera::OrbitCamera; use clap::{Parser, ValueEnum}; -use glam::{vec2, vec3, DVec3, Mat4, Vec2}; +use glam::{vec2, vec3, DVec3, Mat4, Quat, Vec2, Vec3}; use opencascade::primitives::Shape; use simple_game::{ graphics::{ @@ -31,7 +32,7 @@ mod surface_drawer; // Multipliers to convert mouse position deltas to a more intuitve camera perspective change. const ZOOM_MULTIPLIER: f32 = 5.0; const TOUCHPAD_ZOOM_MULTIPLIER: f32 = 0.5; -const ROTATE_MULTIPLIER: f32 = -5.0; +const POINTER_DRAG_ROTATE_MULTIPLIER: f32 = 8.0; const TOUCHPAD_ROTATE_MULTIPLIER: f32 = -0.05; const PAN_MULTIPLIER: f32 = 150.0; const TOUCHPAD_PAN_MULTIPLIER: f32 = 100.0; @@ -63,7 +64,7 @@ impl MouseState { struct ViewerApp { client_rect: Vec2, - camera: camera::Camera, + camera: OrbitCamera, depth_texture: DepthTexture, text_system: TextSystem, fps_counter: FPSCounter, @@ -182,7 +183,7 @@ impl GameApp for ViewerApp { Self { client_rect: vec2(width as f32, height as f32), - camera: camera::Camera::new(width, height), + camera: OrbitCamera::new(width, height), depth_texture, text_system: TextSystem::new(device, surface_texture_format, width, height), fps_counter: FPSCounter::new(), @@ -221,14 +222,27 @@ impl GameApp for ViewerApp { ) { match event { WindowEvent::TouchpadRotate { delta, .. } => { - self.camera.rotate(delta * TOUCHPAD_ROTATE_MULTIPLIER, 0.0); + let axis = Vec3::new(0.0, 0.0, 1.0); + let rotator = Quat::from_axis_angle(axis, TOUCHPAD_ROTATE_MULTIPLIER * delta); + self.camera.rotate(rotator); }, WindowEvent::CursorMoved { position, .. } => { let delta = self.mouse_state.delta(*position); let delta_x = delta.0 as f32 / self.client_rect.x; let delta_y = delta.1 as f32 / self.client_rect.y; if self.mouse_state.left_button_down { - self.camera.rotate(delta_x * ROTATE_MULTIPLIER, delta_y * ROTATE_MULTIPLIER); + // On the screen, Y is DOWN, but in camera space, it's UP + let camera_space_delta = Vec2::new(delta_x, -delta_y); + // Construct the camera space rotation axis perpendicular to delta + let axis = Vec3::new(camera_space_delta.y, -camera_space_delta.x, 0.0); + let magnitude = axis.length(); + if magnitude > 0.0 { + let rotator = Quat::from_axis_angle( + axis.normalize(), + POINTER_DRAG_ROTATE_MULTIPLIER * magnitude, + ); + self.camera.rotate(rotator); + } } if self.mouse_state.middle_button_down { self.camera.pan(delta_x * PAN_MULTIPLIER, delta_y * PAN_MULTIPLIER); From c7858bea04cc763fcea389a4f6e35234923318f7 Mon Sep 17 00:00:00 2001 From: Mate Kovacs Date: Sun, 26 Nov 2023 21:40:10 +0900 Subject: [PATCH 08/17] get pan working, fix making deltas relative to window size --- crates/viewer/src/camera.rs | 6 ++++-- crates/viewer/src/main.rs | 31 ++++++++++++++++--------------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/crates/viewer/src/camera.rs b/crates/viewer/src/camera.rs index 40e0e5c8..6397ebf1 100644 --- a/crates/viewer/src/camera.rs +++ b/crates/viewer/src/camera.rs @@ -1,4 +1,4 @@ -use glam::{vec3, Mat3, Mat4, Quat, Vec3}; +use glam::{vec3, Mat3, Mat4, Quat, Vec2, Vec3}; const MIN_ZOOM_FACTOR: f32 = 0.05; @@ -53,7 +53,9 @@ impl OrbitCamera { /// Pan the camera view horizontally and vertically. Look-at target will move along with the /// camera. - pub fn pan(&mut self, x: f32, y: f32) {} + pub fn pan(&mut self, delta: Vec2) { + self.target -= self.get_local_frame() * delta.extend(0.0); + } /// Zoom in or out, while looking at the same target. pub fn zoom(&mut self, zoom_delta: f32) {} diff --git a/crates/viewer/src/main.rs b/crates/viewer/src/main.rs index c53f2f0b..a531527b 100644 --- a/crates/viewer/src/main.rs +++ b/crates/viewer/src/main.rs @@ -32,7 +32,7 @@ mod surface_drawer; // Multipliers to convert mouse position deltas to a more intuitve camera perspective change. const ZOOM_MULTIPLIER: f32 = 5.0; const TOUCHPAD_ZOOM_MULTIPLIER: f32 = 0.5; -const POINTER_DRAG_ROTATE_MULTIPLIER: f32 = 8.0; +const ROTATE_MULTIPLIER: f32 = 8.0; const TOUCHPAD_ROTATE_MULTIPLIER: f32 = -0.05; const PAN_MULTIPLIER: f32 = 150.0; const TOUCHPAD_PAN_MULTIPLIER: f32 = 100.0; @@ -220,6 +220,8 @@ impl GameApp for ViewerApp { event: &WindowEvent, window_target: &EventLoopWindowTarget<()>, ) { + let screen_diagonal = self.client_rect.length(); + match event { WindowEvent::TouchpadRotate { delta, .. } => { let axis = Vec3::new(0.0, 0.0, 1.0); @@ -228,27 +230,24 @@ impl GameApp for ViewerApp { }, WindowEvent::CursorMoved { position, .. } => { let delta = self.mouse_state.delta(*position); - let delta_x = delta.0 as f32 / self.client_rect.x; - let delta_y = delta.1 as f32 / self.client_rect.y; + // On the screen, Y is DOWN, but in camera space, it's UP + let camera_space_delta = + Vec2::new(delta.0 as f32, -delta.1 as f32) / screen_diagonal; if self.mouse_state.left_button_down { - // On the screen, Y is DOWN, but in camera space, it's UP - let camera_space_delta = Vec2::new(delta_x, -delta_y); // Construct the camera space rotation axis perpendicular to delta let axis = Vec3::new(camera_space_delta.y, -camera_space_delta.x, 0.0); let magnitude = axis.length(); if magnitude > 0.0 { - let rotator = Quat::from_axis_angle( - axis.normalize(), - POINTER_DRAG_ROTATE_MULTIPLIER * magnitude, - ); + let rotator = + Quat::from_axis_angle(axis.normalize(), ROTATE_MULTIPLIER * magnitude); self.camera.rotate(rotator); } } if self.mouse_state.middle_button_down { - self.camera.pan(delta_x * PAN_MULTIPLIER, delta_y * PAN_MULTIPLIER); + self.camera.pan(PAN_MULTIPLIER * camera_space_delta); } if self.mouse_state.right_button_down { - self.camera.zoom(delta_y * ZOOM_MULTIPLIER); + self.camera.zoom(camera_space_delta.y * ZOOM_MULTIPLIER); } }, WindowEvent::MouseInput { state, button, .. } => { @@ -258,10 +257,12 @@ impl GameApp for ViewerApp { // winit can not distinguish mouse wheel and touchpad pan events unfortunately. // Because of that, we assign pan operation to MouseWheel events. For mice, you // need to instead use mouse move while holding down the right button. - let delta_x = delta.x as f32 / self.client_rect.x; - let delta_y = delta.y as f32 / self.client_rect.y; - self.camera - .pan(delta_x * TOUCHPAD_PAN_MULTIPLIER, delta_y * TOUCHPAD_PAN_MULTIPLIER); + + // On the screen, Y is DOWN, but in camera space, it's UP + let camera_space_delta = + Vec2::new(delta.x as f32, -delta.y as f32) / screen_diagonal; + + self.camera.pan(TOUCHPAD_PAN_MULTIPLIER * camera_space_delta); }, WindowEvent::TouchpadMagnify { delta, .. } => { self.camera.zoom(*delta as f32 * TOUCHPAD_ZOOM_MULTIPLIER); From c46b93c06931041779ebe7be2dbdfd75f4d257b7 Mon Sep 17 00:00:00 2001 From: Mate Kovacs Date: Sun, 26 Nov 2023 21:47:26 +0900 Subject: [PATCH 09/17] get zoom working --- crates/viewer/src/camera.rs | 4 +++- crates/viewer/src/main.rs | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/viewer/src/camera.rs b/crates/viewer/src/camera.rs index 6397ebf1..cf1af5e0 100644 --- a/crates/viewer/src/camera.rs +++ b/crates/viewer/src/camera.rs @@ -58,7 +58,9 @@ impl OrbitCamera { } /// Zoom in or out, while looking at the same target. - pub fn zoom(&mut self, zoom_delta: f32) {} + pub fn zoom(&mut self, zoom_delta: f32) { + self.radius = f32::max(self.radius * f32::exp(zoom_delta), f32::EPSILON); + } /// Orbit around the target while keeping the distance. pub fn rotate(&mut self, rotator: Quat) { diff --git a/crates/viewer/src/main.rs b/crates/viewer/src/main.rs index a531527b..f8a831d1 100644 --- a/crates/viewer/src/main.rs +++ b/crates/viewer/src/main.rs @@ -265,7 +265,8 @@ impl GameApp for ViewerApp { self.camera.pan(TOUCHPAD_PAN_MULTIPLIER * camera_space_delta); }, WindowEvent::TouchpadMagnify { delta, .. } => { - self.camera.zoom(*delta as f32 * TOUCHPAD_ZOOM_MULTIPLIER); + let zoom_delta = *delta as f32 * TOUCHPAD_ZOOM_MULTIPLIER; + self.camera.zoom(-zoom_delta); }, WindowEvent::KeyboardInput { event: KeyEvent { physical_key: PhysicalKey::Code(key_code), .. }, From 9a3763bef4ac51be37e775a14fcd339b75a46f89 Mon Sep 17 00:00:00 2001 From: Mate Kovacs Date: Sun, 26 Nov 2023 21:58:38 +0900 Subject: [PATCH 10/17] make zooming work in ortho mode --- crates/viewer/src/camera.rs | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/crates/viewer/src/camera.rs b/crates/viewer/src/camera.rs index cf1af5e0..e3f2bf3d 100644 --- a/crates/viewer/src/camera.rs +++ b/crates/viewer/src/camera.rs @@ -59,7 +59,7 @@ impl OrbitCamera { /// Zoom in or out, while looking at the same target. pub fn zoom(&mut self, zoom_delta: f32) { - self.radius = f32::max(self.radius * f32::exp(zoom_delta), f32::EPSILON); + self.zoom_factor = f32::max(self.zoom_factor * f32::exp(zoom_delta), f32::EPSILON); } /// Orbit around the target while keeping the distance. @@ -71,23 +71,33 @@ impl OrbitCamera { // These magic numbers are configured so that the particular model we are loading is // visible in its entirety. They will be dynamically computed eventually when we have "fit // to view" function or alike. - let proj = match self.projection { - Projection::Orthographic => Mat4::orthographic_rh( - -50.0 * self.zoom_factor * self.aspect_ratio, - 50.0 * self.zoom_factor * self.aspect_ratio, - -50.0 * self.zoom_factor, - 50.0 * self.zoom_factor, - -1000.0, - 1000.0, - ), + + let (proj, effective_radius) = match self.projection { + Projection::Orthographic => { + let proj = Mat4::orthographic_rh( + -50.0 * self.zoom_factor * self.aspect_ratio, + 50.0 * self.zoom_factor * self.aspect_ratio, + -50.0 * self.zoom_factor, + 50.0 * self.zoom_factor, + -1000.0, + 1000.0, + ); + (proj, self.radius) + }, Projection::Perspective => { - Mat4::perspective_rh(std::f32::consts::PI / 2.0, self.aspect_ratio, 0.01, 1000.0) + let proj = Mat4::perspective_rh( + std::f32::consts::PI / 2.0, + self.aspect_ratio, + 0.01, + 1000.0, + ); + (proj, self.zoom_factor * self.radius) }, }; let local_frame = self.get_local_frame(); - let position = self.target + self.radius * local_frame.z_axis; let upward = local_frame.y_axis; + let position = self.target + effective_radius * local_frame.z_axis; let view = Mat4::look_at_rh(position, self.target, upward); proj * view From 60d4344bc46438169accd1ed31937f3aaf0d3ade Mon Sep 17 00:00:00 2001 From: Mate Kovacs Date: Sun, 26 Nov 2023 22:44:19 +0900 Subject: [PATCH 11/17] use MIN_ZOOM_FACTOR --- crates/viewer/src/camera.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/viewer/src/camera.rs b/crates/viewer/src/camera.rs index e3f2bf3d..994ae0e1 100644 --- a/crates/viewer/src/camera.rs +++ b/crates/viewer/src/camera.rs @@ -59,7 +59,7 @@ impl OrbitCamera { /// Zoom in or out, while looking at the same target. pub fn zoom(&mut self, zoom_delta: f32) { - self.zoom_factor = f32::max(self.zoom_factor * f32::exp(zoom_delta), f32::EPSILON); + self.zoom_factor = f32::max(self.zoom_factor * f32::exp(zoom_delta), MIN_ZOOM_FACTOR); } /// Orbit around the target while keeping the distance. From b5e53d2dbe7a5184c2c6d91d363075fd22694a73 Mon Sep 17 00:00:00 2001 From: Mate Kovacs Date: Mon, 27 Nov 2023 23:22:14 +0900 Subject: [PATCH 12/17] extend OrbitCamera::new with init_pos --- crates/viewer/src/camera.rs | 14 +++++++++----- crates/viewer/src/main.rs | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/crates/viewer/src/camera.rs b/crates/viewer/src/camera.rs index 994ae0e1..4d13b91d 100644 --- a/crates/viewer/src/camera.rs +++ b/crates/viewer/src/camera.rs @@ -1,4 +1,4 @@ -use glam::{vec3, Mat3, Mat4, Quat, Vec2, Vec3}; +use glam::{Mat3, Mat4, Quat, Vec2, Vec3, Vec3Swizzles}; const MIN_ZOOM_FACTOR: f32 = 0.05; @@ -24,14 +24,18 @@ pub struct OrbitCamera { } impl OrbitCamera { - pub fn new(width: u32, height: u32) -> Self { + pub fn new(width: u32, height: u32, init_pos: Vec3) -> Self { + let target = Vec3::ZERO; + let radius = init_pos.length(); + let look_at_matrix = Mat4::look_at_rh(init_pos, target, Vec3::Z); + let orientation = Quat::from_mat4(&look_at_matrix).inverse(); Self { projection: Projection::Orthographic, aspect_ratio: width as f32 / height as f32, zoom_factor: 1.0, - target: vec3(0.0, 0.0, 0.0), - radius: 100.0, - orientation: Quat::IDENTITY, + target, + radius, + orientation, } } diff --git a/crates/viewer/src/main.rs b/crates/viewer/src/main.rs index f8a831d1..0ee7182d 100644 --- a/crates/viewer/src/main.rs +++ b/crates/viewer/src/main.rs @@ -183,7 +183,7 @@ impl GameApp for ViewerApp { Self { client_rect: vec2(width as f32, height as f32), - camera: OrbitCamera::new(width, height), + camera: OrbitCamera::new(width, height, Vec3::new(40.0, -40.0, 20.0)), depth_texture, text_system: TextSystem::new(device, surface_texture_format, width, height), fps_counter: FPSCounter::new(), From d611667dfeb07ef2837bbb375165f33bb8104eaf Mon Sep 17 00:00:00 2001 From: Mate Kovacs Date: Mon, 27 Nov 2023 23:24:00 +0900 Subject: [PATCH 13/17] make clippy happy --- crates/viewer/src/camera.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/viewer/src/camera.rs b/crates/viewer/src/camera.rs index 4d13b91d..a43b1ea8 100644 --- a/crates/viewer/src/camera.rs +++ b/crates/viewer/src/camera.rs @@ -1,4 +1,4 @@ -use glam::{Mat3, Mat4, Quat, Vec2, Vec3, Vec3Swizzles}; +use glam::{Mat3, Mat4, Quat, Vec2, Vec3}; const MIN_ZOOM_FACTOR: f32 = 0.05; From 28bf38081e458fef9d9792570f7fa9575eff2cdc Mon Sep 17 00:00:00 2001 From: Mate Kovacs Date: Tue, 28 Nov 2023 20:23:38 +0900 Subject: [PATCH 14/17] renormalize Quat after multiplication inside OrbitCamera --- crates/viewer/src/camera.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/viewer/src/camera.rs b/crates/viewer/src/camera.rs index a43b1ea8..6139b58c 100644 --- a/crates/viewer/src/camera.rs +++ b/crates/viewer/src/camera.rs @@ -68,7 +68,7 @@ impl OrbitCamera { /// Orbit around the target while keeping the distance. pub fn rotate(&mut self, rotator: Quat) { - self.orientation *= rotator; + self.orientation = (self.orientation * rotator).normalize(); } pub fn matrix(&self) -> Mat4 { From b0d2ad015d643bada0d1143a8a1a394608dc8625 Mon Sep 17 00:00:00 2001 From: Mate Kovacs Date: Tue, 28 Nov 2023 20:25:14 +0900 Subject: [PATCH 15/17] revisit near and far clip values --- crates/viewer/src/camera.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/viewer/src/camera.rs b/crates/viewer/src/camera.rs index 6139b58c..c068510b 100644 --- a/crates/viewer/src/camera.rs +++ b/crates/viewer/src/camera.rs @@ -92,8 +92,8 @@ impl OrbitCamera { let proj = Mat4::perspective_rh( std::f32::consts::PI / 2.0, self.aspect_ratio, - 0.01, - 1000.0, + 10.0, + 10_000.0, ); (proj, self.zoom_factor * self.radius) }, From c2236dc8a1040db80cbfee196b70c63126406a1e Mon Sep 17 00:00:00 2001 From: Mate Kovacs Date: Tue, 28 Nov 2023 20:31:05 +0900 Subject: [PATCH 16/17] optimize a bit --- crates/viewer/src/camera.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/viewer/src/camera.rs b/crates/viewer/src/camera.rs index c068510b..fff7c036 100644 --- a/crates/viewer/src/camera.rs +++ b/crates/viewer/src/camera.rs @@ -100,9 +100,11 @@ impl OrbitCamera { }; let local_frame = self.get_local_frame(); - let upward = local_frame.y_axis; let position = self.target + effective_radius * local_frame.z_axis; - let view = Mat4::look_at_rh(position, self.target, upward); + + // NOTE(mkovaxx): This is computing inverse(translation * orientation), but more efficiently + let view = + Mat4::from_quat(self.orientation.conjugate()) * Mat4::from_translation(-position); proj * view } From b91468c722b63a701ede20b447edfc2b051a333d Mon Sep 17 00:00:00 2001 From: Brian Schwind Date: Thu, 30 Nov 2023 23:39:02 +0900 Subject: [PATCH 17/17] Update crates/viewer/src/camera.rs --- crates/viewer/src/camera.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/viewer/src/camera.rs b/crates/viewer/src/camera.rs index fff7c036..b93989cd 100644 --- a/crates/viewer/src/camera.rs +++ b/crates/viewer/src/camera.rs @@ -92,7 +92,7 @@ impl OrbitCamera { let proj = Mat4::perspective_rh( std::f32::consts::PI / 2.0, self.aspect_ratio, - 10.0, + 1.0, 10_000.0, ); (proj, self.zoom_factor * self.radius)