From 58adfdb900e09c9a515a5724d4eb30b37e11c4fc Mon Sep 17 00:00:00 2001
From: Kai <6393786-Fexii@users.noreply.gitlab.com>
Date: Thu, 9 Jul 2020 10:12:11 -0700
Subject: [PATCH] Add freefly camera mode and cycle camera mode keybind

---
 voxygen/src/scene/camera.rs       | 62 ++++++++++++++++++++-------
 voxygen/src/scene/figure/cache.rs | 26 +++++-------
 voxygen/src/scene/mod.rs          | 11 ++++-
 voxygen/src/session.rs            | 69 ++++++++++++++++++++++++++-----
 voxygen/src/settings.rs           |  2 +
 voxygen/src/window.rs             |  2 +
 6 files changed, 129 insertions(+), 43 deletions(-)

diff --git a/voxygen/src/scene/camera.rs b/voxygen/src/scene/camera.rs
index 54a96974c2..871abfd9c8 100644
--- a/voxygen/src/scene/camera.rs
+++ b/voxygen/src/scene/camera.rs
@@ -8,6 +8,7 @@ const FAR_PLANE: f32 = 100000.0;
 
 const FIRST_PERSON_INTERP_TIME: f32 = 0.1;
 const THIRD_PERSON_INTERP_TIME: f32 = 0.1;
+const FREEFLY_INTERP_TIME: f32 = 0.0;
 const LERP_ORI_RATE: f32 = 15.0;
 pub const MIN_ZOOM: f32 = 0.1;
 
@@ -16,6 +17,7 @@ pub const MIN_ZOOM: f32 = 0.1;
 pub enum CameraMode {
     FirstPerson = 0,
     ThirdPerson = 1,
+    Freefly = 2,
 }
 
 impl Default for CameraMode {
@@ -73,15 +75,7 @@ impl Camera {
     /// and position of the camera.
     pub fn compute_dependents(&mut self, terrain: &impl ReadVol) {
         let dist = {
-            let (start, end) = (
-                self.focus
-                    + (Vec3::new(
-                        -f32::sin(self.ori.x) * f32::cos(self.ori.y),
-                        -f32::cos(self.ori.x) * f32::cos(self.ori.y),
-                        f32::sin(self.ori.y),
-                    ) * self.dist),
-                self.focus,
-            );
+            let (start, end) = (self.focus - self.forward() * self.dist, self.focus);
 
             match terrain
                 .ray(start, end)
@@ -155,13 +149,10 @@ impl Camera {
 
     /// Zoom the camera by the given delta, limiting the input accordingly.
     pub fn zoom_by(&mut self, delta: f32) {
-        match self.mode {
-            CameraMode::ThirdPerson => {
-                // Clamp camera dist to the 2 <= x <= infinity range
-                self.tgt_dist = (self.tgt_dist + delta).max(2.0);
-            },
-            CameraMode::FirstPerson => {},
-        };
+        if self.mode == CameraMode::ThirdPerson {
+            // Clamp camera dist to the 2 <= x <= infinity range
+            self.tgt_dist = (self.tgt_dist + delta).max(2.0);
+        }
     }
 
     /// Zoom with the ability to switch between first and third-person mode.
@@ -181,6 +172,7 @@ impl Camera {
                     self.set_mode(CameraMode::ThirdPerson);
                     self.tgt_dist = MIN_THIRD_PERSON;
                 },
+                _ => {},
             }
         }
     }
@@ -244,6 +236,7 @@ impl Camera {
         match self.mode {
             CameraMode::FirstPerson => FIRST_PERSON_INTERP_TIME,
             CameraMode::ThirdPerson => THIRD_PERSON_INTERP_TIME,
+            CameraMode::Freefly => FREEFLY_INTERP_TIME,
         }
     }
 
@@ -287,6 +280,9 @@ impl Camera {
                 CameraMode::FirstPerson => {
                     self.set_distance(MIN_ZOOM);
                 },
+                CameraMode::Freefly => {
+                    self.zoom_by(0.0);
+                },
             }
         }
     }
@@ -301,4 +297,38 @@ impl Camera {
             mode => mode,
         }
     }
+
+    /// Cycle the camera to its next valid mode
+    pub fn next_mode(&mut self) {
+        self.set_mode(match self.mode {
+            CameraMode::ThirdPerson => CameraMode::FirstPerson,
+            CameraMode::FirstPerson => CameraMode::Freefly,
+            CameraMode::Freefly => CameraMode::ThirdPerson,
+        });
+    }
+
+    /// Return a unit vector in the forward direction for the current camera
+    /// orientation
+    pub fn forward(&self) -> Vec3<f32> {
+        Vec3::new(
+            f32::sin(self.ori.x) * f32::cos(self.ori.y),
+            f32::cos(self.ori.x) * f32::cos(self.ori.y),
+            -f32::sin(self.ori.y),
+        )
+    }
+
+    /// Return a unit vector in the right direction for the current camera
+    /// orientation
+    pub fn right(&self) -> Vec3<f32> {
+        const UP: Vec3<f32> = Vec3::new(0.0, 0.0, 1.0);
+        self.forward().cross(UP).normalized()
+    }
+
+    /// Return a unit vector in the forward direction on the XY plane for
+    /// the current camera orientation
+    pub fn forward_xy(&self) -> Vec2<f32> { Vec2::new(f32::sin(self.ori.x), f32::cos(self.ori.x)) }
+
+    /// Return a unit vector in the right direction on the XY plane for
+    /// the current camera orientation
+    pub fn right_xy(&self) -> Vec2<f32> { Vec2::new(f32::cos(self.ori.x), -f32::sin(self.ori.x)) }
 }
diff --git a/voxygen/src/scene/figure/cache.rs b/voxygen/src/scene/figure/cache.rs
index a65d6fd31c..09faea2a4f 100644
--- a/voxygen/src/scene/figure/cache.rs
+++ b/voxygen/src/scene/figure/cache.rs
@@ -120,37 +120,33 @@ impl<Skel: Skeleton> FigureModelCache<Skel> {
 
                 [
                     match camera_mode {
-                        CameraMode::ThirdPerson => {
+                        CameraMode::ThirdPerson | CameraMode::Freefly => {
                             Some(humanoid_head_spec.mesh_head(&body, generate_mesh))
                         },
                         CameraMode::FirstPerson => None,
                     },
                     match camera_mode {
-                        CameraMode::ThirdPerson => Some(humanoid_armor_chest_spec.mesh_chest(
-                            &body,
-                            loadout,
-                            generate_mesh,
-                        )),
+                        CameraMode::ThirdPerson | CameraMode::Freefly => Some(
+                            humanoid_armor_chest_spec.mesh_chest(&body, loadout, generate_mesh),
+                        ),
                         CameraMode::FirstPerson => None,
                     },
                     match camera_mode {
-                        CameraMode::ThirdPerson => {
+                        CameraMode::ThirdPerson | CameraMode::Freefly => {
                             Some(humanoid_armor_belt_spec.mesh_belt(&body, loadout, generate_mesh))
                         },
                         CameraMode::FirstPerson => None,
                     },
                     match camera_mode {
-                        CameraMode::ThirdPerson => {
+                        CameraMode::ThirdPerson | CameraMode::Freefly => {
                             Some(humanoid_armor_back_spec.mesh_back(&body, loadout, generate_mesh))
                         },
                         CameraMode::FirstPerson => None,
                     },
                     match camera_mode {
-                        CameraMode::ThirdPerson => Some(humanoid_armor_pants_spec.mesh_pants(
-                            &body,
-                            loadout,
-                            generate_mesh,
-                        )),
+                        CameraMode::ThirdPerson | CameraMode::Freefly => Some(
+                            humanoid_armor_pants_spec.mesh_pants(&body, loadout, generate_mesh),
+                        ),
                         CameraMode::FirstPerson => None,
                     },
                     Some(humanoid_armor_hand_spec.mesh_left_hand(&body, loadout, generate_mesh)),
@@ -158,7 +154,7 @@ impl<Skel: Skeleton> FigureModelCache<Skel> {
                     Some(humanoid_armor_foot_spec.mesh_left_foot(&body, loadout, generate_mesh)),
                     Some(humanoid_armor_foot_spec.mesh_right_foot(&body, loadout, generate_mesh)),
                     match camera_mode {
-                        CameraMode::ThirdPerson => {
+                        CameraMode::ThirdPerson | CameraMode::Freefly => {
                             Some(humanoid_armor_shoulder_spec.mesh_left_shoulder(
                                 &body,
                                 loadout,
@@ -168,7 +164,7 @@ impl<Skel: Skeleton> FigureModelCache<Skel> {
                         CameraMode::FirstPerson => None,
                     },
                     match camera_mode {
-                        CameraMode::ThirdPerson => {
+                        CameraMode::ThirdPerson | CameraMode::Freefly => {
                             Some(humanoid_armor_shoulder_spec.mesh_right_shoulder(
                                 &body,
                                 loadout,
diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs
index 31923d547a..5f449fe15c 100644
--- a/voxygen/src/scene/mod.rs
+++ b/voxygen/src/scene/mod.rs
@@ -239,10 +239,17 @@ impl Scene {
             },
             CameraMode::ThirdPerson if scene_data.is_aiming => player_scale * 2.1,
             CameraMode::ThirdPerson => player_scale * 1.65,
+            CameraMode::Freefly => 0.0,
         };
 
-        self.camera
-            .set_focus_pos(player_pos + Vec3::unit_z() * (up - tilt.min(0.0).sin() * dist * 0.6));
+        match self.camera.get_mode() {
+            CameraMode::FirstPerson | CameraMode::ThirdPerson => {
+                self.camera.set_focus_pos(
+                    player_pos + Vec3::unit_z() * (up - tilt.min(0.0).sin() * dist * 0.6),
+                );
+            },
+            CameraMode::Freefly => {},
+        };
 
         // Tick camera for interpolation.
         self.camera.update(
diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs
index 454b255dcb..da11fb7672 100644
--- a/voxygen/src/session.rs
+++ b/voxygen/src/session.rs
@@ -173,7 +173,9 @@ impl PlayState for SessionState {
         )
         .unwrap();
 
-        let mut ori = self.scene.camera().get_orientation();
+        let mut walk_forward_dir = self.scene.camera().forward_xy();
+        let mut walk_right_dir = self.scene.camera().right_xy();
+        let mut freefly_vel = Vec3::zero();
         let mut free_look = false;
         let mut auto_walk = false;
 
@@ -517,6 +519,10 @@ impl PlayState for SessionState {
                             _ => {},
                         }
                     },
+                    Event::InputUpdate(GameInput::CycleCamera, true) => {
+                        let camera = self.scene.camera_mut();
+                        camera.next_mode();
+                    },
                     Event::AnalogGameInput(input) => match input {
                         AnalogGameInput::MovementX(v) => {
                             self.key_state.analog_matrix.x = v;
@@ -543,17 +549,60 @@ impl PlayState for SessionState {
             }
 
             if !free_look {
-                ori = self.scene.camera().get_orientation();
+                walk_forward_dir = self.scene.camera().forward_xy();
+                walk_right_dir = self.scene.camera().right_xy();
                 self.inputs.look_dir = Dir::from_unnormalized(cam_dir + aim_dir_offset).unwrap();
             }
-            // Calculate the movement input vector of the player from the current key
-            // presses and the camera direction.
-            let unit_vecs = (
-                Vec2::new(ori[0].cos(), -ori[0].sin()),
-                Vec2::new(ori[0].sin(), ori[0].cos()),
-            );
-            let dir_vec = self.key_state.dir_vec();
-            self.inputs.move_dir = unit_vecs.0 * dir_vec[0] + unit_vecs.1 * dir_vec[1];
+
+            // Get the current state of movement related inputs
+            let input_vec = self.key_state.dir_vec();
+            let (axis_right, axis_up) = (input_vec[0], input_vec[1]);
+
+            match self.scene.camera().get_mode() {
+                camera::CameraMode::FirstPerson | camera::CameraMode::ThirdPerson => {
+                    // Move the player character based on their walking direction.
+                    // This could be different from the camera direction if free look is enabled.
+                    self.inputs.move_dir = walk_right_dir * axis_right + walk_forward_dir * axis_up;
+                    freefly_vel = Vec3::zero();
+                },
+
+                camera::CameraMode::Freefly => {
+                    // Move the camera freely in 3d space. Apply acceleration so that
+                    // the movement feels more natural and controlled.
+                    const FREEFLY_ACCEL: f32 = 120.0;
+                    const FREEFLY_DAMPING: f32 = 80.0;
+                    const FREEFLY_MAX_SPEED: f32 = 50.0;
+
+                    let forward = self.scene.camera().forward();
+                    let right = self.scene.camera().right();
+                    let dir = right * axis_right + forward * axis_up;
+
+                    let dt = clock.get_last_delta().as_secs_f32();
+                    if freefly_vel.magnitude_squared() > 0.01 {
+                        let new_vel =
+                            freefly_vel - freefly_vel.normalized() * (FREEFLY_DAMPING * dt);
+                        if freefly_vel.dot(new_vel) > 0.0 {
+                            freefly_vel = new_vel;
+                        } else {
+                            freefly_vel = Vec3::zero();
+                        }
+                    }
+                    if dir.magnitude_squared() > 0.01 {
+                        freefly_vel += dir * (FREEFLY_ACCEL * dt);
+                        if freefly_vel.magnitude() > FREEFLY_MAX_SPEED {
+                            freefly_vel = freefly_vel.normalized() * FREEFLY_MAX_SPEED;
+                        }
+                    }
+
+                    let pos = self.scene.camera().get_focus_pos();
+                    self.scene
+                        .camera_mut()
+                        .set_focus_pos(pos + freefly_vel * dt);
+
+                    // Do not apply any movement to the player character
+                    self.inputs.move_dir = Vec2::zero();
+                },
+            };
 
             self.inputs.climb = self.key_state.climb();
 
diff --git a/voxygen/src/settings.rs b/voxygen/src/settings.rs
index 589f3a109c..013953c65c 100644
--- a/voxygen/src/settings.rs
+++ b/voxygen/src/settings.rs
@@ -142,6 +142,7 @@ impl ControlSettings {
             //GameInput::Charge => KeyMouse::Key(VirtualKeyCode::Key1),
             GameInput::FreeLook => KeyMouse::Key(VirtualKeyCode::L),
             GameInput::AutoWalk => KeyMouse::Key(VirtualKeyCode::Period),
+            GameInput::CycleCamera => KeyMouse::Key(VirtualKeyCode::Key0),
             GameInput::Slot1 => KeyMouse::Key(VirtualKeyCode::Key1),
             GameInput::Slot2 => KeyMouse::Key(VirtualKeyCode::Key2),
             GameInput::Slot3 => KeyMouse::Key(VirtualKeyCode::Key3),
@@ -204,6 +205,7 @@ impl Default for ControlSettings {
             //GameInput::Charge,
             GameInput::FreeLook,
             GameInput::AutoWalk,
+            GameInput::CycleCamera,
             GameInput::Slot1,
             GameInput::Slot2,
             GameInput::Slot3,
diff --git a/voxygen/src/window.rs b/voxygen/src/window.rs
index 7402e81e4b..d7641cbc89 100644
--- a/voxygen/src/window.rs
+++ b/voxygen/src/window.rs
@@ -65,6 +65,7 @@ pub enum GameInput {
     SwapLoadout,
     FreeLook,
     AutoWalk,
+    CycleCamera,
 }
 
 impl GameInput {
@@ -89,6 +90,7 @@ impl GameInput {
             GameInput::Mount => "gameinput.mount",
             GameInput::Enter => "gameinput.enter",
             GameInput::Command => "gameinput.command",
+            GameInput::CycleCamera => "gameinput.cyclecamera",
             GameInput::Escape => "gameinput.escape",
             GameInput::Map => "gameinput.map",
             GameInput::Bag => "gameinput.bag",