tutorial 12 a better camera
This commit is contained in:
parent
7a8b9a973c
commit
10a19247f7
2 changed files with 175 additions and 124 deletions
213
src/camera.rs
213
src/camera.rs
|
@ -1,121 +1,152 @@
|
|||
use cgmath::prelude::*;
|
||||
use winit::event::{ElementState, KeyboardInput, VirtualKeyCode, WindowEvent};
|
||||
use std::f32::consts::FRAC_PI_2;
|
||||
|
||||
use cgmath::{prelude::*, Matrix4, Point3, Rad, Vector3};
|
||||
use winit::event::{ElementState, VirtualKeyCode};
|
||||
|
||||
#[rustfmt::skip]
|
||||
pub const OPENGL_TO_WGPU_MATRIX: Matrix4<f32> = Matrix4::new(
|
||||
1.0, 0.0, 0.0, 0.0,
|
||||
0.0, 1.0, 0.0, 0.0,
|
||||
0.0, 0.0, 0.5, 0.0,
|
||||
0.0, 0.0, 0.5, 1.0,
|
||||
);
|
||||
pub struct Camera {
|
||||
pub eye: cgmath::Point3<f32>,
|
||||
pub target: cgmath::Point3<f32>,
|
||||
pub up: cgmath::Vector3<f32>,
|
||||
pub aspect: f32,
|
||||
pub fovy: f32,
|
||||
pub znear: f32,
|
||||
pub zfar: f32,
|
||||
pub position: Point3<f32>,
|
||||
pub yaw: Rad<f32>,
|
||||
pub pitch: Rad<f32>,
|
||||
}
|
||||
|
||||
impl Camera {
|
||||
pub fn build_view_projection_matrix(&self) -> cgmath::Matrix4<f32> {
|
||||
let view = cgmath::Matrix4::look_at(self.eye, self.target, self.up);
|
||||
let proj = cgmath::perspective(cgmath::Deg(self.fovy), self.aspect, self.znear, self.zfar);
|
||||
pub fn new<V: Into<Point3<f32>>, Y: Into<Rad<f32>>, P: Into<Rad<f32>>>(
|
||||
position: V,
|
||||
yaw: Y,
|
||||
pitch: P,
|
||||
) -> Self {
|
||||
Self {
|
||||
position: position.into(),
|
||||
yaw: yaw.into(),
|
||||
pitch: pitch.into(),
|
||||
}
|
||||
}
|
||||
|
||||
proj * view
|
||||
pub fn calc_matrix(&self) -> Matrix4<f32> {
|
||||
Matrix4::look_at_dir(
|
||||
self.position,
|
||||
Vector3::new(self.yaw.0.cos(), self.pitch.0.sin(), self.yaw.0.sin()).normalize(),
|
||||
Vector3::unit_y(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub 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,
|
||||
sensivity: f32,
|
||||
amount_up: f32,
|
||||
amount_down: f32,
|
||||
amount_forward: f32,
|
||||
amount_backward: f32,
|
||||
amount_left: f32,
|
||||
amount_right: f32,
|
||||
rotate_horizontal: f32,
|
||||
rotate_vertical: f32,
|
||||
}
|
||||
|
||||
impl CameraController {
|
||||
pub fn new(speed: f32) -> Self {
|
||||
pub fn new(speed: f32, sensivity: 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,
|
||||
sensivity,
|
||||
amount_up: 0.0,
|
||||
amount_down: 0.0,
|
||||
amount_forward: 0.0,
|
||||
amount_backward: 0.0,
|
||||
amount_left: 0.0,
|
||||
amount_right: 0.0,
|
||||
rotate_horizontal: 0.0,
|
||||
rotate_vertical: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_events(&mut self, event: &WindowEvent) -> bool {
|
||||
match event {
|
||||
WindowEvent::KeyboardInput {
|
||||
input:
|
||||
KeyboardInput {
|
||||
state,
|
||||
virtual_keycode: Some(keycode),
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
let is_pressed = *state == ElementState::Pressed;
|
||||
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;
|
||||
}
|
||||
|
||||
match keycode {
|
||||
VirtualKeyCode::Space => {
|
||||
self.is_up_pressed = is_pressed;
|
||||
true
|
||||
}
|
||||
VirtualKeyCode::LControl => {
|
||||
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 fn process_keyboard(&mut self, key: VirtualKeyCode, state: ElementState) -> bool {
|
||||
let amount = if state == ElementState::Pressed { 1.0 } else { 0.0 };
|
||||
match key {
|
||||
VirtualKeyCode::Space => {
|
||||
self.amount_up = amount;
|
||||
true
|
||||
}
|
||||
VirtualKeyCode::LControl => {
|
||||
self.amount_down = amount;
|
||||
true
|
||||
}
|
||||
VirtualKeyCode::W | VirtualKeyCode::Up => {
|
||||
self.amount_forward = amount;
|
||||
true
|
||||
}
|
||||
VirtualKeyCode::A | VirtualKeyCode::Left => {
|
||||
self.amount_left = amount;
|
||||
true
|
||||
}
|
||||
VirtualKeyCode::S | VirtualKeyCode::Down => {
|
||||
self.amount_backward = amount;
|
||||
true
|
||||
}
|
||||
VirtualKeyCode::D | VirtualKeyCode::Right => {
|
||||
self.amount_right = amount;
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_camera(&self, camera: &mut Camera, dt: f32) {
|
||||
let mut forward = camera.target - camera.eye;
|
||||
let mut forward_norm = forward.normalize();
|
||||
let mut forward_mag = forward.magnitude();
|
||||
pub fn update_camera(&mut self, camera: &mut Camera, dt: f32) {
|
||||
let (yaw_sin, yaw_cos) = camera.yaw.sin_cos();
|
||||
let forward = Vector3::new(yaw_cos, 0.0, yaw_sin).normalize();
|
||||
let right = 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;
|
||||
|
||||
if self.is_forward_pressed != self.is_backward_pressed {
|
||||
// Prevents glitching when camera gets too close to the
|
||||
// center of the scene.
|
||||
let f_spd = (self.speed * dt).min(forward_mag - 8.0);
|
||||
if self.is_forward_pressed && forward_mag > f_spd {
|
||||
camera.eye += forward_norm * f_spd;
|
||||
} else if self.is_backward_pressed {
|
||||
camera.eye -= forward_norm * self.speed * dt;
|
||||
}
|
||||
forward = camera.target - camera.eye;
|
||||
forward_norm = forward.normalize();
|
||||
forward_mag = forward.magnitude();
|
||||
}
|
||||
camera.position.y += (self.amount_up - self.amount_down) * self.speed * dt;
|
||||
|
||||
let right = forward_norm.cross(camera.up);
|
||||
camera.yaw += Rad(self.rotate_horizontal) * self.sensivity * dt;
|
||||
camera.pitch += Rad(-self.rotate_vertical) * self.sensivity * dt;
|
||||
self.rotate_horizontal = 0.0;
|
||||
self.rotate_vertical = 0.0;
|
||||
|
||||
if self.is_right_pressed != self.is_left_pressed {
|
||||
if self.is_right_pressed {
|
||||
camera.eye =
|
||||
camera.target - (forward - right * self.speed * dt).normalize() * forward_mag;
|
||||
} else if self.is_left_pressed {
|
||||
camera.eye =
|
||||
camera.target - (forward + right * self.speed * dt).normalize() * forward_mag;
|
||||
}
|
||||
if camera.pitch < -Rad(FRAC_PI_2) {
|
||||
camera.pitch = -Rad(FRAC_PI_2);
|
||||
} else if camera.pitch > Rad(FRAC_PI_2) {
|
||||
camera.pitch = Rad(FRAC_PI_2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Projection {
|
||||
aspect: f32,
|
||||
fovy: Rad<f32>,
|
||||
znear: f32,
|
||||
zfar: f32,
|
||||
}
|
||||
|
||||
impl Projection {
|
||||
pub fn new<F: Into<Rad<f32>>>(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) -> Matrix4<f32> {
|
||||
OPENGL_TO_WGPU_MATRIX * cgmath::perspective(self.fovy, self.aspect, self.znear, self.zfar)
|
||||
}
|
||||
}
|
||||
|
|
86
src/main.rs
86
src/main.rs
|
@ -1,11 +1,7 @@
|
|||
use imgui::im_str;
|
||||
use model::{DrawLight, DrawModel, Model, Vertex};
|
||||
use wgpu::util::DeviceExt;
|
||||
use winit::{
|
||||
event::*,
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::{Window, WindowBuilder},
|
||||
};
|
||||
use winit::{dpi::PhysicalPosition, event::*, event_loop::{ControlFlow, EventLoop}, window::{Window, WindowBuilder}};
|
||||
|
||||
use cgmath::prelude::*;
|
||||
|
||||
|
@ -107,9 +103,9 @@ impl Uniforms {
|
|||
}
|
||||
}
|
||||
|
||||
fn update_view_proj(&mut self, camera: &camera::Camera) {
|
||||
self.view_position = camera.eye.to_homogeneous().into();
|
||||
self.view_proj = (OPENGL_TO_WGPU_MATRIX * camera.build_view_projection_matrix()).into();
|
||||
fn update_view_proj(&mut self, camera: &camera::Camera, projection: &camera::Projection) {
|
||||
self.view_position = camera.position.to_homogeneous().into();
|
||||
self.view_proj = (projection.calc_matrix() * camera.calc_matrix()).into();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -186,6 +182,7 @@ struct State {
|
|||
depth_texture: texture::Texture,
|
||||
|
||||
camera: camera::Camera,
|
||||
projection: camera::Projection,
|
||||
camera_controller: camera::CameraController,
|
||||
|
||||
uniforms: Uniforms,
|
||||
|
@ -210,6 +207,9 @@ struct State {
|
|||
imgui_renderer: imgui_wgpu::Renderer,
|
||||
|
||||
rotation_speed: f32,
|
||||
|
||||
last_mouse_pos: PhysicalPosition<f64>,
|
||||
mouse_pressed: bool
|
||||
}
|
||||
|
||||
impl State {
|
||||
|
@ -324,20 +324,15 @@ impl State {
|
|||
usage: wgpu::BufferUsage::VERTEX | wgpu::BufferUsage::COPY_DST,
|
||||
});
|
||||
|
||||
let camera = camera::Camera {
|
||||
eye: (0.0, 5.0, -10.0).into(),
|
||||
target: (0.0, 0.0, 0.0).into(),
|
||||
up: cgmath::Vector3::unit_y(),
|
||||
aspect: sc_desc.width as f32 / sc_desc.height as f32,
|
||||
fovy: 45.0,
|
||||
znear: 0.1,
|
||||
zfar: 100.0,
|
||||
};
|
||||
let projection = camera::Projection::new(sc_desc.width, sc_desc.height, cgmath::Deg(45.0), 0.1, 100.0);
|
||||
|
||||
let camera_controller = camera::CameraController::new(20.0);
|
||||
|
||||
let camera = camera::Camera::new((0.0, 5.0, -10.0), cgmath::Deg(90.0), cgmath::Deg(-20.0));
|
||||
|
||||
let camera_controller = camera::CameraController::new(4.0, 0.4);
|
||||
|
||||
let mut uniforms = Uniforms::new();
|
||||
uniforms.update_view_proj(&camera);
|
||||
uniforms.update_view_proj(&camera, &projection);
|
||||
|
||||
let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("uniform_buffer"),
|
||||
|
@ -369,7 +364,7 @@ impl State {
|
|||
});
|
||||
|
||||
// Lights
|
||||
let light = light::Light::new((2.0, 2.0, 2.0).into(), (1.0, 1.0, 1.0).into());
|
||||
let light = light::Light::new((8.0, 2.0, 8.0).into(), (1.0, 1.0, 1.0).into());
|
||||
|
||||
let light_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("Light VB"),
|
||||
|
@ -488,6 +483,7 @@ impl State {
|
|||
sc_desc,
|
||||
swap_chain,
|
||||
camera,
|
||||
projection,
|
||||
camera_controller,
|
||||
depth_texture,
|
||||
uniforms,
|
||||
|
@ -506,6 +502,8 @@ impl State {
|
|||
imgui_platform,
|
||||
imgui_renderer,
|
||||
rotation_speed: 1.0,
|
||||
last_mouse_pos: (0.0, 0.0).into(),
|
||||
mouse_pressed: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -515,27 +513,49 @@ impl State {
|
|||
self.sc_desc.height = new_size.height;
|
||||
self.depth_texture =
|
||||
texture::Texture::create_depth_texture(&self.device, &self.sc_desc, "depth_texture");
|
||||
self.camera.aspect = new_size.width as f32 / new_size.height as f32;
|
||||
self.projection.resize(new_size.width, new_size.height);
|
||||
self.swap_chain = self.device.create_swap_chain(&self.surface, &self.sc_desc);
|
||||
}
|
||||
|
||||
fn input(&mut self, event: &WindowEvent) -> bool {
|
||||
if let WindowEvent::KeyboardInput { .. } = event {
|
||||
if self.imgui.io().want_capture_keyboard {
|
||||
return true;
|
||||
}
|
||||
} else if let WindowEvent::MouseInput { .. } = event {
|
||||
if self.imgui.io().want_capture_mouse {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
self.camera_controller.process_events(event)
|
||||
|
||||
match event {
|
||||
WindowEvent::KeyboardInput { input: winit::event::KeyboardInput {
|
||||
virtual_keycode: Some(key),
|
||||
state,
|
||||
..
|
||||
}, .. } => {
|
||||
if self.imgui.io().want_capture_keyboard {
|
||||
true
|
||||
} else {
|
||||
self.camera_controller.process_keyboard(*key, *state)
|
||||
}
|
||||
}
|
||||
WindowEvent::CursorMoved { position, .. } => {
|
||||
if !self.imgui.io().want_capture_mouse {
|
||||
let mouse_dx = position.x - self.last_mouse_pos.x;
|
||||
let mouse_dy = position.y - self.last_mouse_pos.y;
|
||||
if self.mouse_pressed {
|
||||
self.camera_controller.process_mouse(mouse_dx, mouse_dy);
|
||||
}
|
||||
self.last_mouse_pos = *position;
|
||||
}
|
||||
true
|
||||
}
|
||||
WindowEvent::MouseInput { state, button, .. } => {
|
||||
if !self.imgui.io().want_capture_mouse && *button == MouseButton::Left {
|
||||
self.mouse_pressed = *state == ElementState::Pressed;
|
||||
}
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, dt: f32) {
|
||||
self.camera_controller.update_camera(&mut self.camera, dt);
|
||||
self.uniforms.update_view_proj(&self.camera);
|
||||
self.uniforms.update_view_proj(&self.camera, &self.projection);
|
||||
|
||||
let mut encoder = self
|
||||
.device
|
||||
|
@ -569,7 +589,7 @@ impl State {
|
|||
|
||||
let deg = cgmath::Deg(30.0 * dt * self.rotation_speed);
|
||||
for instance in &mut self.instances {
|
||||
let rot = cgmath::Quaternion::from(cgmath::Euler::new(deg, deg*1.1, deg*1.2));
|
||||
let rot = cgmath::Quaternion::from(cgmath::Euler::new(deg, deg * 1.1, deg * 1.2));
|
||||
instance.rotation = instance.rotation * rot;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue