From 68ba3c0f45faa71b989b0a05fd974405a21cfd7b Mon Sep 17 00:00:00 2001
From: 3gg <3gg@shellblade.net>
Date: Mon, 16 Sep 2024 19:56:58 -0700
Subject: Add camera control.

---
 game/src/plugins/viewer.c | 113 ++++++++++++++++++++++++++++++++++++++++------
 1 file changed, 100 insertions(+), 13 deletions(-)

diff --git a/game/src/plugins/viewer.c b/game/src/plugins/viewer.c
index f621b00..5fc4be7 100644
--- a/game/src/plugins/viewer.c
+++ b/game/src/plugins/viewer.c
@@ -1,5 +1,6 @@
 #include "plugin.h"
 
+#include <gfx/app.h>
 #include <gfx/asset.h>
 #include <gfx/renderer.h>
 #include <gfx/scene.h>
@@ -27,11 +28,33 @@ static const char* BOXES =
 
 #define DEFAULT_SCENE_FILE GIRL
 
-struct State {
-  Scene*       scene;
-  Model*       model;
-  SceneCamera* camera;
-};
+static const bool RenderBoundingBoxes     = false;
+static const R    DefaultCameraSpeed      = (R)6.0;
+static const R    DefaultMouseSensitivity = (R)(10 * TO_RAD);
+static const vec3 DefaultCameraPosition   = (vec3){0, 2, 5};
+
+typedef struct CameraCommand {
+  bool CameraMoveLeft     : 1;
+  bool CameraMoveRight    : 1;
+  bool CameraMoveForward  : 1;
+  bool CameraMoveBackward : 1;
+} CameraCommand;
+
+typedef struct CameraController {
+  R camera_speed;           // Camera movement speed.
+  R mouse_sensitivity;      // Controls the degree with which mouse movements
+                            // rotate the camera.
+  vec2 prev_mouse_position; // Mouse position in the previous frame.
+  bool rotating;            // When true, subsequent mouse movements cause the
+                            // camera to rotate.
+} CameraController;
+
+typedef struct State {
+  Scene*           scene;
+  Model*           model;
+  SceneCamera*     camera;
+  CameraController camera_controller;
+} State;
 
 /// Load the skyquad texture.
 static const Texture* load_environment_map(Gfx* gfx) {
@@ -131,8 +154,21 @@ bool init(Game* game, State** pp_state) {
   if (anima) {
     gfx_play_animation(
         anima, &(AnimationPlaySettings){.name = "Walk", .loop = true});
+    // TODO: Interpolate animations.
+    /*gfx_play_animation(
+        anima,
+        &(AnimationPlaySettings){.name = "Jumping-jack-lower", .loop = true});
+    gfx_play_animation(
+        anima, &(AnimationPlaySettings){
+                   .name = "Jumping-jack-arms-mid", .loop = true});*/
   }
 
+  spatial3_set_position(
+      &gfx_get_camera_camera(state->camera)->spatial, DefaultCameraPosition);
+
+  state->camera_controller.camera_speed      = DefaultCameraSpeed;
+  state->camera_controller.mouse_sensitivity = DefaultMouseSensitivity;
+
   *pp_state = state;
   return true;
 
@@ -153,19 +189,68 @@ void shutdown(Game* game, State* state) {
   }
 }
 
+static void update_camera(
+    CameraController* controller, R dt, vec2 mouse_position,
+    CameraCommand command, Spatial3* camera) {
+  assert(controller);
+  assert(camera);
+
+  // Translation.
+  const R move_x = (R)(command.CameraMoveLeft ? -1 : 0) +
+                   (R)(command.CameraMoveRight ? 1 : 0);
+  const R move_y = (R)(command.CameraMoveForward ? 1 : 0) +
+                   (R)(command.CameraMoveBackward ? -1 : 0);
+  const vec2 translation =
+      vec2_scale(vec2_make(move_x, move_y), controller->camera_speed * dt);
+  spatial3_move_right(camera, translation.x);
+  spatial3_move_forwards(camera, translation.y);
+
+  // Rotation.
+  if (controller->rotating) {
+    const vec2 mouse_delta =
+        vec2_sub(mouse_position, controller->prev_mouse_position);
+
+    const vec2 rotation =
+        vec2_scale(mouse_delta, controller->mouse_sensitivity * dt);
+
+    spatial3_global_yaw(camera, -rotation.x);
+    spatial3_pitch(camera, -rotation.y);
+  }
+
+  // Update controller state.
+  controller->prev_mouse_position = mouse_position;
+}
+
 void update(Game* game, State* state, double t, double dt) {
   assert(game);
   assert(state);
   assert(state->scene);
   assert(state->camera);
 
-  const vec3 orbit_point = vec3_make(0, 2, 0);
-  Camera*    camera      = gfx_get_camera_camera(state->camera);
-  spatial3_orbit(
-      &camera->spatial, orbit_point,
-      /*radius=*/5,
-      /*azimuth=*/(R)(t * 0.5), /*zenith=*/0);
-  spatial3_lookat(&camera->spatial, orbit_point);
+  double mouse_x, mouse_y;
+  gfx_app_get_mouse_position(&mouse_x, &mouse_y);
+  const vec2 mouse_position = {(R)mouse_x, (R)mouse_y};
+
+  const CameraCommand camera_command = (CameraCommand){
+      .CameraMoveLeft     = gfx_app_is_key_pressed(KeyA),
+      .CameraMoveRight    = gfx_app_is_key_pressed(KeyD),
+      .CameraMoveForward  = gfx_app_is_key_pressed(KeyW),
+      .CameraMoveBackward = gfx_app_is_key_pressed(KeyS),
+  };
+
+  state->camera_controller.rotating = gfx_app_is_mouse_button_pressed(LMB);
+
+  update_camera(
+      &state->camera_controller, (R)dt, mouse_position, camera_command,
+      &gfx_get_camera_camera(state->camera)->spatial);
+
+  //  const vec3 orbit_point = vec3_make(0, 2, 0);
+  //  Camera*    camera      = gfx_get_camera_camera(state->camera);
+  //  spatial3_orbit(
+  //      &camera->spatial, orbit_point,
+  //      /*radius=*/5,
+  //      /*azimuth=*/(R)(t * 0.5), /*zenith=*/0);
+  //  spatial3_lookat(&camera->spatial, orbit_point);
 
   gfx_update(state->scene, state->camera, (R)t);
 }
@@ -261,7 +346,9 @@ void render(const Game* game, const State* state) {
                     .scene  = state->scene,
                     .camera = state->camera});
 
-  render_bounding_boxes(game, state);
+  if (RenderBoundingBoxes) {
+    render_bounding_boxes(game, state);
+  }
 }
 
 void resize(Game* game, State* state, int width, int height) {
-- 
cgit v1.2.3