]> git.nega.tv - josh/narcissus/commitdiff
shark: Add basic camera control
authorJoshua Simmons <josh@nega.tv>
Sun, 26 Oct 2025 19:29:00 +0000 (20:29 +0100)
committerJoshua Simmons <josh@nega.tv>
Sun, 26 Oct 2025 19:29:56 +0000 (20:29 +0100)
title/shark/src/game.rs
title/shark/src/main.rs

index ae5e7326f1e18220e91991402155a611b97b5148..d1a9a09ede066b2db8a12f5545c77a9b0ffa09ef 100644 (file)
@@ -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;
 
index f5e497445af0acf90bdb7203818113a8e54e683e..4d340fdb635dfbab86721650487be06e49f15055 100644 (file)
@@ -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,