aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortcmal <me@aria.rip>2024-08-25 17:44:20 +0100
committertcmal <me@aria.rip>2024-08-25 17:44:20 +0100
commit7f541bdb1456886c2819263f2d119bbacde7ecaf (patch)
treef587ed10d40869f2f5ff674267c08dce0c3b4621
parentfbd3e2fcac6116b53136751a8c7ce6ab23374796 (diff)
feat(camera): camera rotation and movement relative to rotation
-rw-r--r--examples/render-bsp/src/main.rs58
-rw-r--r--stockton-render/src/draw/camera.rs79
-rw-r--r--stockton-render/src/draw/context.rs12
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