diff options
author | tcmal <me@aria.rip> | 2024-08-25 17:44:20 +0100 |
---|---|---|
committer | tcmal <me@aria.rip> | 2024-08-25 17:44:20 +0100 |
commit | 7f541bdb1456886c2819263f2d119bbacde7ecaf (patch) | |
tree | f587ed10d40869f2f5ff674267c08dce0c3b4621 | |
parent | fbd3e2fcac6116b53136751a8c7ce6ab23374796 (diff) |
feat(camera): camera rotation and movement relative to rotation
-rw-r--r-- | examples/render-bsp/src/main.rs | 58 | ||||
-rw-r--r-- | stockton-render/src/draw/camera.rs | 79 | ||||
-rw-r--r-- | stockton-render/src/draw/context.rs | 12 |
3 files changed, 121 insertions, 28 deletions
diff --git a/examples/render-bsp/src/main.rs b/examples/render-bsp/src/main.rs index ac1415e..51fc80f 100644 --- a/examples/render-bsp/src/main.rs +++ b/examples/render-bsp/src/main.rs @@ -22,12 +22,13 @@ extern crate winit; extern crate simple_logger; extern crate image; -use image::load_from_memory; use std::time::SystemTime; +use std::f32::consts::PI; +use image::load_from_memory; use stockton_levels::prelude::*; use stockton_levels::q3::Q3BSPFile; -use stockton_types::{World, Vector3}; +use stockton_types::{World, Vector3, Vector2}; use stockton_render::Renderer; use winit::{ @@ -36,8 +37,14 @@ use winit::{ window::WindowBuilder }; +/// Movement speed, world units per second const SPEED: f32 = 100.0; +/// Pixels required to rotate 90 degrees +const PIXELS_PER_90D: f32 = 100.0; + +const SENSITIVITY: f32 = PI / (2.0 * PIXELS_PER_90D); + #[derive(Debug)] struct KeyState { pub up: bool, @@ -91,6 +98,12 @@ fn main() { // Load the world and renderer let event_loop = EventLoop::new(); let window = WindowBuilder::new().build(&event_loop).unwrap(); + + if let Err(_) = window.set_cursor_grab(true) { + println!("warning: cursor not grabbed"); + } + window.set_cursor_visible(false); + let data = include_bytes!("../data/test.bsp").to_vec().into_boxed_slice(); let bsp: Result<Q3BSPFile<Q3System>, stockton_levels::types::ParseError> = Q3BSPFile::parse_file(&data); let bsp: Q3BSPFile<Q3System> = bsp.unwrap(); @@ -113,6 +126,7 @@ fn main() { let mut last_update = SystemTime::now(); let mut key_state = KeyState::new(); + let mut last_cursor_pos = Vector2::new(0.0, 0.0); // Keep rendering the world event_loop.run(move |event, _, flow| { @@ -126,19 +140,38 @@ fn main() { *flow = ControlFlow::Exit }, WindowEvent::KeyboardInput {input, ..} => match input.scancode { - // Left - 105 => key_state.left = input.state == ElementState::Pressed, - // Right - 106 => key_state.right = input.state == ElementState::Pressed, - // Up (in) - 103 => key_state.inwards = input.state == ElementState::Pressed, - // Down (out) - 108 => key_state.out = input.state == ElementState::Pressed, + // A + 30 => key_state.left = input.state == ElementState::Pressed, + // D + 32 => key_state.right = input.state == ElementState::Pressed, + // W (in) + 17 => key_state.inwards = input.state == ElementState::Pressed, + // S (out) + 31 => key_state.out = input.state == ElementState::Pressed, // Space (up) 57 => key_state.up = input.state == ElementState::Pressed, // Ctrl (down) - 29 => key_state.down = input.state == ElementState::Pressed, + 42 => key_state.down = input.state == ElementState::Pressed, _ => () + }, + WindowEvent::CursorMoved { + position, + .. + } => { + // Special case: First frame + if last_cursor_pos.x != 0.0 || last_cursor_pos.y == 0.0 { + let x_offset = (position.x as f32 - last_cursor_pos.x) * SENSITIVITY; + let y_offset = (position.y as f32 - last_cursor_pos.y) * SENSITIVITY; + + renderer.context.rotate(Vector3::new( + -y_offset, + x_offset, + 0.0 + )); + } + + last_cursor_pos.x = position.x as f32; + last_cursor_pos.y = position.y as f32; } _ => () }, @@ -153,8 +186,7 @@ fn main() { let delta_pos = key_state.as_vector() * delta * SPEED; if delta_pos.x != 0.0 || delta_pos.y != 0.0 || delta_pos.z != 0.0 { - renderer.context.move_camera(delta_pos); - println!("camera is at {:?}", renderer.context.camera_pos()); + renderer.context.move_camera_relative(delta_pos); } renderer.render_frame().unwrap() diff --git a/stockton-render/src/draw/camera.rs b/stockton-render/src/draw/camera.rs index 2abf814..cbad9b1 100644 --- a/stockton-render/src/draw/camera.rs +++ b/stockton-render/src/draw/camera.rs @@ -16,9 +16,10 @@ //! Things related to converting 3D world space to 2D screen space use std::iter::once; +use std::f32::consts::PI; use hal::prelude::*; use hal::buffer::Usage; -use na::{look_at_lh, perspective_lh_zo}; +use na::{look_at_lh, perspective_lh_zo, radians}; use core::mem::ManuallyDrop; @@ -26,18 +27,37 @@ use crate::error; use crate::types::*; use super::buffer::{StagedBuffer, ModifiableBuffer}; use stockton_types::{Vector3, Matrix4}; +use na::{Mat4, Vec4}; + +/// 90 degrees in radians +const R90: f32 = PI / 2.0; + +/// 180 degrees in radians +const R180: f32 = PI; + +fn euler_to_direction(euler: &Vector3) -> Vector3 { + let pitch = euler.x; + let yaw = euler.y; + let _roll = euler.z; // TODO: Support camera roll + + Vector3::new( + yaw.sin() * pitch.cos(), + pitch.sin(), + yaw.cos() * pitch.cos() + ) +} pub struct CameraSettings { - /// Position of the camera + /// Position of the camera (world units) pub position: Vector3, - /// A point the camera is looking directly at - pub looking_at: Vector3, + /// Rotation of the camera (euler angles in radians) + pub rotation: Vector3, - /// The up direction + /// The up direction (normalized) pub up: Vector3, - /// FOV in radians + /// FOV (radians) pub fov: f32, /// Near clipping plane (world units) @@ -76,8 +96,8 @@ impl<'a> WorkingCamera<'a> { command_queue: &mut CommandQueue, command_pool: &mut CommandPool) -> Result<WorkingCamera<'a>, error::CreationError> { WorkingCamera::with_settings(CameraSettings { - position: Vector3::new(-320.0, 0.0, 0.0), - looking_at: Vector3::new(0.0, 0.0, 0.0), + position: Vector3::new(0.0, 0.0, 0.0), + rotation: Vector3::new(0.0, R90, 0.0), up: Vector3::new(0.0, 1.0, 0.0), fov: f32::to_radians(90.0), near: 0.1, @@ -176,10 +196,13 @@ impl<'a> WorkingCamera<'a> { /// Returns a matrix that transforms from world space to screen space pub fn vp_matrix(&self) -> Matrix4 { + // Get look direction from euler angles + let direction = euler_to_direction(&self.settings.rotation); + // Converts world space to camera space let view_matrix = look_at_lh( &self.settings.position, - &self.settings.looking_at, + &(direction + &self.settings.position), &self.settings.up ); @@ -208,9 +231,41 @@ impl<'a> WorkingCamera<'a> { self.is_dirty = true; } - /// Move the camera by `delta` - pub fn move_camera(&mut self, delta: Vector3) { - self.settings.position += delta; + /// Apply rotation of the camera + /// `euler` should be euler angles in degrees + pub fn rotate(&mut self, euler: Vector3) { + // TODO + self.settings.rotation += euler; + + // Clamp -pi/2 < pitch < pi/2 + if self.settings.rotation.x > R90 { + self.settings.rotation.x = R90; + } else if self.settings.rotation.x < -R90 { + self.settings.rotation.x = -R90; + } + + // -pi < yaw <= pi + if self.settings.rotation.y <= -R180 { + self.settings.rotation.y = R180 - self.settings.rotation.y % -R180; + } else if self.settings.rotation.y > 180.0 { + self.settings.rotation.y = -R180 + self.settings.rotation.y % R180; + } + + self.is_dirty = true; + } + + /// Move the camera by `delta`, relative to the camera's rotation + pub fn move_camera_relative(&mut self, delta: Vector3) { + let rot_matrix = Mat4::from_euler_angles( + -self.settings.rotation.x, + self.settings.rotation.y, + self.settings.rotation.z + ); + + let new = rot_matrix * Vec4::new(delta.x, delta.y, delta.z, 1.0); + self.settings.position.x += new.x; + self.settings.position.y += new.y; + self.settings.position.z += new.z; self.is_dirty = true; } diff --git a/stockton-render/src/draw/context.rs b/stockton-render/src/draw/context.rs index e8517f2..16fbf9f 100644 --- a/stockton-render/src/draw/context.rs +++ b/stockton-render/src/draw/context.rs @@ -844,9 +844,15 @@ impl<'a> RenderingContext<'a> { self.camera.camera_pos() } - /// Move the camera by `delta` - pub fn move_camera(&mut self, delta: Vector3) { - self.camera.move_camera(delta) + /// Move the camera by `delta` relative to its rotation + pub fn move_camera_relative(&mut self, delta: Vector3) { + self.camera.move_camera_relative(delta) + } + + /// Rotate the camera + /// `euler` should be euler angles in radians + pub fn rotate(&mut self, euler: Vector3) { + self.camera.rotate(euler) } /// Load all active faces into the vertex buffers for drawing |