From d75c274a09ffa1ef8db3f97e9c58b67cf074fb78 Mon Sep 17 00:00:00 2001 From: Mark Poliakov Date: Mon, 28 Apr 2025 09:21:27 +0300 Subject: [PATCH] Add camera movement --- src/input.rs | 54 ++++++++++++++++++++++++++++++ src/main.rs | 80 +++++++++++++++++++++++++++++++++++---------- src/render/mod.rs | 22 +++++++------ src/world/camera.rs | 62 +++++++++++++++++++++++++++++++++++ src/world/mod.rs | 1 + 5 files changed, 192 insertions(+), 27 deletions(-) create mode 100644 src/input.rs create mode 100644 src/world/camera.rs diff --git a/src/input.rs b/src/input.rs new file mode 100644 index 0000000..a10edae --- /dev/null +++ b/src/input.rs @@ -0,0 +1,54 @@ +use std::mem; + +use winit::{ + event::{ElementState, KeyEvent}, + keyboard::{KeyCode, PhysicalKey}, +}; + +#[derive(Default)] +pub struct InputState { + mouse_delta_x: f64, + mouse_delta_y: f64, + + pub forward: bool, + pub backwards: bool, + pub left: bool, + pub right: bool, + pub up: bool, + pub down: bool, +} + +impl InputState { + pub fn new() -> Self { + Self::default() + } + + pub fn add_mouse_motion(&mut self, dx: f64, dy: f64) { + self.mouse_delta_x += dx; + self.mouse_delta_y += dy; + } + + pub fn mouse_delta(&mut self) -> (f64, f64) { + let dx = mem::replace(&mut self.mouse_delta_x, 0.0); + let dy = mem::replace(&mut self.mouse_delta_y, 0.0); + (dx, dy) + } + + pub fn key_event(&mut self, event: KeyEvent) { + let PhysicalKey::Code(key) = event.physical_key else { + return; + }; + let pressed = event.state == ElementState::Pressed; + + match key { + KeyCode::KeyW => self.forward = pressed, + KeyCode::KeyS => self.backwards = pressed, + KeyCode::KeyA => self.left = pressed, + KeyCode::KeyD => self.right = pressed, + KeyCode::Space => self.up = pressed, + KeyCode::ShiftLeft => self.down = pressed, + _ => (), + } + } +} + diff --git a/src/main.rs b/src/main.rs index 549def0..914dad7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ use std::{process::ExitCode, sync::Arc, time::Instant}; use error::Error; use glam::Vec3; +use input::InputState; use render::{ asset::{AssetLoader, TextureLoadStage}, Renderer, @@ -14,11 +15,12 @@ use winit::{ dpi::PhysicalSize, event::{DeviceEvent, DeviceId, StartCause, WindowEvent}, event_loop::{ActiveEventLoop, EventLoop}, - window::{Window, WindowId}, + window::{CursorGrabMode, Window, WindowId}, }; -use world::{Chunk, ChunkGenerator, NoiseChunkGenerator, World}; +use world::{camera::Camera, ChunkGenerator, NoiseChunkGenerator, World}; pub mod error; +pub mod input; pub mod render; pub mod world; @@ -27,9 +29,10 @@ pub struct Engine { renderer: Option, assets: Option>, world: Option, - camera_position: Vec3, + input: InputState, + camera: Camera, generator: Box, - start: Instant, + last_frame: Instant, } impl Engine { @@ -41,6 +44,10 @@ impl Engine { .with_inner_size(PhysicalSize::new(800, 600)); let window = Arc::new(event_loop.create_window(window_attributes)?); + + window.set_cursor_grab(CursorGrabMode::Locked).ok(); + window.set_cursor_visible(false); + let render = Renderer::from_window(window.clone(), assets)?; self.window = Some(window); @@ -49,6 +56,42 @@ impl Engine { Ok(()) } + + pub fn update_camera(&mut self, dt: f64) { + const SENSITIVITY: f64 = 0.15; + const HORIZONTAL_SPEED: f32 = 17.5; + const VERTICAL_SPEED: f32 = 15.0; + + let (mouse_dx, mouse_dy) = self.input.mouse_delta(); + let mouse_dx = mouse_dx * dt * SENSITIVITY; + let mouse_dy = mouse_dy * dt * SENSITIVITY; + + self.camera.turn(mouse_dy as f32, mouse_dx as f32); + + let want_dz = self.input.forward as i32 - self.input.backwards as i32; + let want_dx = self.input.right as i32 - self.input.left as i32; + let want_dy = self.input.up as i32 - self.input.down as i32; + + if want_dx == 0 && want_dy == 0 && want_dz == 0 { + return; + } + + let want_horizontal_vector = if want_dx != 0 || want_dz != 0 { + let look_vector = self.camera.horizontal_look_vector(); + let right_vector = look_vector.cross(Vec3::Y); + ((want_dx as f32) * right_vector + (want_dz as f32) * look_vector).normalize() + } else { + Vec3::ZERO + }; + + let want_vertical_vector = (want_dy as f32) * Vec3::Y; + + let horizontal_vector = want_horizontal_vector * dt as f32 * HORIZONTAL_SPEED; + let vertical_vector = want_vertical_vector * dt as f32 * VERTICAL_SPEED; + + self.camera + .move_towards(horizontal_vector + vertical_vector); + } } impl ApplicationHandler for Engine { @@ -99,24 +142,23 @@ impl ApplicationHandler for Engine { } } WindowEvent::RedrawRequested => { + let frame_delta = self.last_frame.elapsed(); + self.last_frame = Instant::now(); + let frame_dt = frame_delta.as_secs_f64(); + self.update_camera(frame_dt); + if let (Some(render), Some(world)) = (self.renderer.as_mut(), self.world.as_mut()) { - let delta = self.start.elapsed(); - let dt = delta.as_secs_f64(); + let camera_position = self.camera.position(); - self.camera_position = Vec3::new( - (dt.cos() * 32.0) as f32 + (Chunk::SIZE / 2) as f32, - 48.0, - (dt.sin() * 32.0) as f32 + (Chunk::SIZE / 2) as f32, - ); + world.update_with_camera(camera_position, &mut *self.generator); - world.update_with_camera(self.camera_position, &mut *self.generator); - - if let Err(error) = render.render(self.camera_position, world) { + if let Err(error) = render.render(&mut self.camera, world) { log::error!("Render error: {error}"); event_loop.exit(); } } } + WindowEvent::KeyboardInput { event, .. } => self.input.key_event(event), _ => (), } } @@ -129,7 +171,10 @@ impl ApplicationHandler for Engine { ) { let _ = event_loop; let _ = device_id; - let _ = event; + + if let DeviceEvent::MouseMotion { delta } = event { + self.input.add_mouse_motion(delta.0, delta.1); + } } fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { @@ -156,9 +201,10 @@ fn run() -> Result<(), Error> { renderer: None, assets: Some(loader), world: Some(world), - camera_position: Vec3::ZERO, + camera: Camera::new(), + input: InputState::new(), generator: Box::new(NoiseChunkGenerator::from_seed(1234)), - start: Instant::now(), + last_frame: Instant::now(), }; event_loop.run_app(&mut engine)?; diff --git a/src/render/mod.rs b/src/render/mod.rs index 4c1c802..712eaa1 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -4,7 +4,7 @@ use std::{ }; use asset::{AssetLoader, TextureLoadStage}; -use glam::{Mat4, Vec3}; +use glam::Mat4; use mesh::WorldMeshState; use util::{Allocators, ViewportExt}; use vertex::InputVertex; @@ -38,7 +38,7 @@ use winit::window::Window; use crate::{ error::Error, - world::{Chunk, ChunkCoords, World}, + world::{camera::Camera, Chunk, ChunkCoords, World}, }; pub mod asset; @@ -241,7 +241,7 @@ impl Renderer { }) } - pub fn render(&mut self, camera_position: Vec3, world: &mut World) -> Result<(), Error> { + pub fn render(&mut self, camera: &mut Camera, world: &mut World) -> Result<(), Error> { self.fps.new_frame(); self.fps.update(); @@ -270,15 +270,16 @@ impl Renderer { let model = Mat4::IDENTITY; let projection = Mat4::perspective_rh_gl(45.0f32.to_radians(), aspect, 0.01, 1000.0); + let view = camera.transform(); - fn make_view(camera: Vec3, target: Vec3) -> Mat4 { - Mat4::look_at_rh(camera, target, Vec3::Y) - } + // fn make_view(camera: Vec3, target: Vec3) -> Mat4 { + // Mat4::look_at_rh(camera, target, Vec3::Y) + // } - let view = make_view( - camera_position, - Vec3::new((Chunk::SIZE / 2) as f32, 32.0, (Chunk::SIZE / 2) as f32), - ); + // let view = make_view( + // camera_position, + // Vec3::new((Chunk::SIZE / 2) as f32, 32.0, (Chunk::SIZE / 2) as f32), + // ); let set0 = { let uniform_buffer = self @@ -352,6 +353,7 @@ impl Renderer { (x as i32 + Chunk::SIZE as i32 / 2) / Chunk::SIZE as i32 } + let camera_position = camera.position(); let camera_cx = cpos(camera_position.x); let camera_cz = cpos(camera_position.z); diff --git a/src/world/camera.rs b/src/world/camera.rs new file mode 100644 index 0000000..cca1fd5 --- /dev/null +++ b/src/world/camera.rs @@ -0,0 +1,62 @@ +use glam::{EulerRot, Mat4, Vec3, Vec4, Vec4Swizzles}; + +pub struct Camera { + position: Vec3, + yaw: f32, + pitch: f32, + + transform: Mat4, + dirty: bool, +} + +impl Camera { + pub fn new() -> Self { + Self { + position: Vec3::ZERO, + yaw: 0.0, + pitch: 0.0, + transform: Mat4::IDENTITY, + dirty: true, + } + } + + pub fn position(&self) -> Vec3 { + self.position + } + + pub fn move_to(&mut self, position: Vec3) { + self.position = position; + self.dirty = true; + } + + pub fn move_towards(&mut self, delta: Vec3) { + self.position += delta; + self.dirty = true; + } + + pub fn turn(&mut self, pitch: f32, yaw: f32) { + self.pitch += pitch; + self.yaw += yaw; + self.dirty = true; + } + + pub fn horizontal_look_vector(&self) -> Vec3 { + Vec3::new(self.yaw.cos(), 0.0, self.yaw.sin()) + } + + pub fn look_vector(&self) -> Vec3 { + (Mat4::from_euler(EulerRot::XYZ, 0.0, -self.yaw, -self.pitch) + * Vec4::new(1.0, 0.0, 0.0, 0.0)) + .xyz() + } + + pub fn transform(&mut self) -> &Mat4 { + if self.dirty { + let look_vector = self.look_vector(); + self.transform = Mat4::look_to_rh(self.position, look_vector, Vec3::Y); + } + + &self.transform + } +} + diff --git a/src/world/mod.rs b/src/world/mod.rs index ecbec0b..337ef31 100644 --- a/src/world/mod.rs +++ b/src/world/mod.rs @@ -1,5 +1,6 @@ use bitflags::bitflags; +pub mod camera; pub mod chunk; pub mod feature; pub mod generator;