From 4df941493f165c7f71b90dc50f9201d21d4a3837 Mon Sep 17 00:00:00 2001 From: Joshua Simmons Date: Sun, 26 Oct 2025 20:29:00 +0100 Subject: [PATCH] shark: Add basic camera control --- title/shark/src/game.rs | 106 +++++++++++++++++++++++++++++++++++----- title/shark/src/main.rs | 12 +++-- 2 files changed, 101 insertions(+), 17 deletions(-) diff --git a/title/shark/src/game.rs b/title/shark/src/game.rs index ae5e732..d1a9a09 100644 --- a/title/shark/src/game.rs +++ b/title/shark/src/game.rs @@ -1,7 +1,10 @@ use std::f32::consts::SQRT_2; use narcissus_core::{BitIter, box_assume_init, default, random::Pcg64, zeroed_box}; -use narcissus_maths::{Deg, HalfTurn, Mat4, Point3, Vec3, clamp, perlin_noise3, sin_pi_f32, vec3}; +use narcissus_maths::{ + Deg, HalfTurn, Mat4, Point3, Vec2, Vec3, clamp, perlin_noise3, sin_cos_pi_f32, sin_pi_f32, + vec2, vec3, +}; use crate::spring::simple_spring_damper_exact; @@ -49,6 +52,10 @@ pub enum Action { Right, Up, Down, + CameraLeft, + CameraRight, + CameraUp, + CameraDown, Damage, } @@ -111,6 +118,10 @@ impl PlayerState { pub struct CameraState { pub eye_offset: Vec3, + pub azimuth: HalfTurn, + pub altitude: HalfTurn, + pub distance: f32, + pub shake: f32, pub shake_offset: Vec3, @@ -126,17 +137,15 @@ impl Default for CameraState { impl CameraState { pub fn new() -> Self { - let theta = HalfTurn::from(GAME_VARIABLES.camera_angle).as_f32(); - let hypotenuse = GAME_VARIABLES.camera_distance; - let height = sin_pi_f32(theta) * hypotenuse; - let base = (hypotenuse * hypotenuse - height * height).sqrt(); - - // Rotate camera - let one_on_sqrt2 = 1.0 / SQRT_2; - let eye_offset = vec3(-base * one_on_sqrt2, height, -base * one_on_sqrt2); + let altitude = HalfTurn::from(GAME_VARIABLES.camera_angle); + let distance = GAME_VARIABLES.camera_distance; + let azimuth = HalfTurn::new(0.25); Self { - eye_offset, + eye_offset: Vec3::ZERO, + altitude, + azimuth, + distance, shake: 0.0, shake_offset: Vec3::ZERO, @@ -146,7 +155,74 @@ impl CameraState { } } - pub fn tick(&mut self, target: Point3, time: f32, delta_time: f32) { + pub fn tick(&mut self, actions: &Actions, target: Point3, time: f32, delta_time: f32) { + let movement_bitmap = actions.is_active(Action::CameraUp) as usize + | (actions.is_active(Action::CameraDown) as usize) << 1 + | (actions.is_active(Action::CameraLeft) as usize) << 2 + | (actions.is_active(Action::CameraRight) as usize) << 3; + + const UP: Vec2 = vec2(0.0, 1.0); + const DOWN: Vec2 = vec2(0.0, -1.0); + const LEFT: Vec2 = vec2(-1.0, 0.0); + const RIGHT: Vec2 = vec2(1.0, 0.0); + //SQRT_2 / 2.0 + const UP_LEFT: Vec2 = vec2(1.0, 0.0); + const UP_RIGHT: Vec2 = vec2(0.0, 1.0); + const DOWN_LEFT: Vec2 = vec2(0.0, -1.0); + const DOWN_RIGHT: Vec2 = vec2(-1.0, 0.0); + + let movement = [ + // 0 0 0 0 + Vec2::ZERO, + // 0 0 0 1 + UP, + // 0 0 1 0 + DOWN, + // 0 0 1 1 + Vec2::ZERO, + // 0 1 0 0 + LEFT, + // 0 1 0 1 + UP_LEFT, + // 0 1 1 0 + DOWN_LEFT, + // 0 1 1 1 + LEFT, + // 1 0 0 0 + RIGHT, + // 1 0 0 1 + UP_RIGHT, + // 1 0 1 0 + DOWN_RIGHT, + // 1 0 1 1 + RIGHT, + // 1 1 0 0 + Vec2::ZERO, + // 1 1 0 1 + UP, + // 1 1 1 0 + DOWN, + // 1 1 1 1 + Vec2::ZERO, + ][movement_bitmap]; + + let camera_half_turn_per_second = 0.5; + + let altitude = self.altitude.as_f32(); + let altitude = altitude + movement.y * delta_time * camera_half_turn_per_second; + let altitude = clamp(altitude, -0.49, 0.49); + self.altitude = HalfTurn::new(altitude); + + let azimuth = self.azimuth.as_f32(); + let azimuth = azimuth + movement.x * delta_time * camera_half_turn_per_second; + let azimuth = (azimuth / 2.0).fract() * 2.0; + self.azimuth = HalfTurn::new(azimuth); + + let height = sin_pi_f32(altitude) * self.distance; + let base = (self.distance * self.distance - height * height).sqrt(); + let (s, c) = sin_cos_pi_f32(azimuth); + self.eye_offset = vec3(-base * s, height, -base * c); + if Point3::distance_sq(self.position, target) > (GAME_VARIABLES.camera_deadzone * GAME_VARIABLES.camera_deadzone) { @@ -181,9 +257,13 @@ impl CameraState { self.shake_offset.z = shake * perlin_noise3(1.0, t, 0.0); } + pub fn position(&self) -> Point3 { + self.position + self.eye_offset + } + pub fn camera_from_model(&self) -> Mat4 { let position = self.position + self.shake_offset; - let eye = position + self.eye_offset; + let eye = self.position(); Mat4::look_at(eye, position, Vec3::Y) } } @@ -319,7 +399,7 @@ impl GameState { self.player.position += player_velocity * delta_time; self.camera - .tick(self.player.position, self.time, delta_time); + .tick(&self.actions, self.player.position, self.time, delta_time); self.player.weapon_cooldown -= delta_time; diff --git a/title/shark/src/main.rs b/title/shark/src/main.rs index f5e4974..4d340fd 100644 --- a/title/shark/src/main.rs +++ b/title/shark/src/main.rs @@ -158,10 +158,14 @@ pub fn main() { } let action = match key { - Key::Left | Key::A => Some(Action::Left), - Key::Right | Key::D => Some(Action::Right), - Key::Up | Key::W => Some(Action::Up), - Key::Down | Key::S => Some(Action::Down), + Key::A => Some(Action::Left), + Key::D => Some(Action::Right), + Key::W => Some(Action::Up), + Key::S => Some(Action::Down), + Key::Left => Some(Action::CameraLeft), + Key::Right => Some(Action::CameraRight), + Key::Up => Some(Action::CameraUp), + Key::Down => Some(Action::CameraDown), Key::Space => Some(Action::Damage), Key::Escape => break 'main, _ => None, -- 2.51.1