From 738fa34534b61a4e900cd895f8acce79ec8048bc 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 1/2] 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 { + 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 { + const UP: Vec3 = 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 { 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 { 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 FigureModelCache { [ 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 FigureModelCache { 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 FigureModelCache { 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", From 6b771b8dca79716bc8bf095879e17e7cf1d2fe23 Mon Sep 17 00:00:00 2001 From: Kai <6393786-Fexii@users.noreply.gitlab.com> Date: Thu, 9 Jul 2020 14:01:48 -0700 Subject: [PATCH 2/2] Add a clientside check to only allow freefly camera for admins --- client/src/lib.rs | 12 ++++++++++++ voxygen/src/scene/camera.rs | 13 ++++++++++--- voxygen/src/session.rs | 6 +++++- 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/client/src/lib.rs b/client/src/lib.rs index 49b30e06b9..91db147335 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -1083,6 +1083,18 @@ impl Client { .collect() } + /// Return true if this client is an admin on the server + pub fn is_admin(&self) -> bool { + let client_uid = self + .state + .read_component_cloned::(self.entity) + .expect("Client doesn't have a Uid!!!"); + + self.player_list + .get(&client_uid) + .map_or(false, |info| info.is_admin) + } + /// Clean client ECS state fn clean_state(&mut self) { let client_uid = self diff --git a/voxygen/src/scene/camera.rs b/voxygen/src/scene/camera.rs index 871abfd9c8..265688dc82 100644 --- a/voxygen/src/scene/camera.rs +++ b/voxygen/src/scene/camera.rs @@ -298,11 +298,18 @@ impl Camera { } } - /// Cycle the camera to its next valid mode - pub fn next_mode(&mut self) { + /// Cycle the camera to its next valid mode. If is_admin is false then only + /// modes which are accessible without admin access will be cycled to. + pub fn next_mode(&mut self, is_admin: bool) { self.set_mode(match self.mode { CameraMode::ThirdPerson => CameraMode::FirstPerson, - CameraMode::FirstPerson => CameraMode::Freefly, + CameraMode::FirstPerson => { + if is_admin { + CameraMode::Freefly + } else { + CameraMode::ThirdPerson + } + }, CameraMode::Freefly => CameraMode::ThirdPerson, }); } diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index da11fb7672..b256174024 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -520,8 +520,12 @@ impl PlayState for SessionState { } }, Event::InputUpdate(GameInput::CycleCamera, true) => { + // Prevent accessing camera modes which aren't available in multiplayer + // unless you are an admin. This is an easily bypassed clientside check. + // The server should do its own filtering of which entities are sent to + // clients to prevent abuse. let camera = self.scene.camera_mut(); - camera.next_mode(); + camera.next_mode(self.client.borrow().is_admin()); }, Event::AnalogGameInput(input) => match input { AnalogGameInput::MovementX(v) => {