Skip to content

Commit

Permalink
Support pan, rotate and zoom camera operations
Browse files Browse the repository at this point in the history
  • Loading branch information
skywhale committed Jul 24, 2023
1 parent c8076e1 commit 393b13a
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 23 deletions.
58 changes: 47 additions & 11 deletions crates/viewer/src/camera.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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) {
Expand All @@ -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,
),
Expand All @@ -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
}
Expand Down
79 changes: 67 additions & 12 deletions crates/viewer/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::{
edge_drawer::{EdgeDrawer, LineBuilder, LineVertex3, RenderedLine},
surface_drawer::{CadMesh, SurfaceDrawer},
};
use glam::{dvec3, vec3, DVec3, Mat4};
use glam::{dvec3, vec2, vec3, DVec3, Mat4, Vec2};
use opencascade::{
angle::{RVec, ToAngle},
primitives::{Face, Shape, Solid, Wire},
Expand All @@ -18,7 +18,11 @@ use simple_game::{
};
use smaa::{SmaaMode, SmaaTarget};
use winit::{
event::{KeyboardInput, VirtualKeyCode, WindowEvent},
dpi::PhysicalPosition,
event::{
ElementState, KeyboardInput, MouseButton, MouseScrollDelta::PixelDelta, VirtualKeyCode,
WindowEvent,
},
event_loop::ControlFlow,
window::Window,
};
Expand All @@ -27,9 +31,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<f64>,
}

impl MouseState {
fn delta(&mut self, position: PhysicalPosition<f64>) -> (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,
Expand All @@ -39,8 +74,7 @@ struct ViewerApp {
smaa_target: SmaaTarget,
rendered_edges: RenderedLine,
cad_mesh: CadMesh,
angle: f32,
scale: f32,
mouse_state: MouseState,
}

impl GameApp for ViewerApp {
Expand Down Expand Up @@ -100,6 +134,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),
Expand All @@ -119,12 +154,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);
Expand All @@ -135,11 +170,32 @@ impl GameApp for ViewerApp {
fn handle_window_event(&mut self, event: &WindowEvent, control_flow: &mut ControlFlow) {
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 {
input: KeyboardInput { virtual_keycode: Some(keycode), .. },
Expand All @@ -166,8 +222,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,
Expand Down

0 comments on commit 393b13a

Please sign in to comment.