set_fov: Add support for time-based transitions (#9705)
authorANAND <ClobberXD@gmail.com>
Sat, 2 May 2020 10:52:11 +0000 (16:22 +0530)
committerGitHub <noreply@github.com>
Sat, 2 May 2020 10:52:11 +0000 (12:52 +0200)
doc/lua_api.txt
src/client/camera.cpp
src/client/camera.h
src/network/clientpackethandler.cpp
src/network/networkprotocol.h
src/player.h
src/script/lua_api/l_object.cpp
src/server.cpp

index f9107b623a05af3ab86fe3086757a058575e3890..44f62f7a75bfa42285be3bb8d2e8fef87f6ca245 100644 (file)
@@ -5998,15 +5998,18 @@ object you are working with still exists.
         * max: bubbles bar is not shown
         * See [Object properties] for more information
     * Is limited to range 0 ... 65535 (2^16 - 1)
-* `set_fov(fov, is_multiplier)`: Sets player's FOV
+* `set_fov(fov, is_multiplier, transition_time)`: Sets player's FOV
     * `fov`: FOV value.
     * `is_multiplier`: Set to `true` if the FOV value is a multiplier.
       Defaults to `false`.
-    * Set to 0 to clear FOV override.
-* `get_fov()`:
-    * Returns player's FOV override in degrees, and a boolean depending on whether
-      the value is a multiplier.
-    * Returns 0 as first value if player's FOV hasn't been overridden.
+    * `transition_time`: If defined, enables smooth FOV transition.
+      Interpreted as the time (in seconds) to reach target FOV.
+      If set to 0, FOV change is instantaneous. Defaults to 0.
+    * Set `fov` to 0 to clear FOV override.
+* `get_fov()`: Returns the following:
+    * Server-sent FOV value. Returns 0 if an FOV override doesn't exist.
+    * Boolean indicating whether the FOV value is a multiplier.
+    * Time (in seconds) taken for the FOV transition. Set by `set_fov`.
 * `set_attribute(attribute, value)`:  DEPRECATED, use get_meta() instead
     * Sets an extra attribute with value on player.
     * `value` must be a string, or a number which will be converted to a
index 69bd82a4797f88a68d1d0180a003dba6a772338b..1a5253db426b61e59b1249445532eb056286be07 100644 (file)
@@ -86,6 +86,51 @@ Camera::~Camera()
        m_wieldmgr->drop();
 }
 
+void Camera::notifyFovChange()
+{
+       LocalPlayer *player = m_client->getEnv().getLocalPlayer();
+       assert(player);
+
+       PlayerFovSpec spec = player->getFov();
+
+       /*
+        * Update m_old_fov_degrees first - it serves as the starting point of the
+        * upcoming transition.
+        *
+        * If an FOV transition is already active, mark current FOV as the start of
+        * the new transition. If not, set it to the previous transition's target FOV.
+        */
+       if (m_fov_transition_active)
+               m_old_fov_degrees = m_curr_fov_degrees;
+       else
+               m_old_fov_degrees = m_server_sent_fov ? m_target_fov_degrees : m_cache_fov;
+
+       /*
+        * Update m_server_sent_fov next - it corresponds to the target FOV of the
+        * upcoming transition.
+        *
+        * Set it to m_cache_fov, if server-sent FOV is 0. Otherwise check if
+        * server-sent FOV is a multiplier, and multiply it with m_cache_fov instead
+        * of overriding.
+        */
+       if (spec.fov == 0.0f) {
+               m_server_sent_fov = false;
+               m_target_fov_degrees = m_cache_fov;
+       } else {
+               m_server_sent_fov = true;
+               m_target_fov_degrees = spec.is_multiplier ? m_cache_fov * spec.fov : spec.fov;
+       }
+
+       if (spec.transition_time > 0.0f)
+               m_fov_transition_active = true;
+
+       // If FOV smooth transition is active, initialize required variables
+       if (m_fov_transition_active) {
+               m_transition_time = spec.transition_time;
+               m_fov_diff = m_target_fov_degrees - m_old_fov_degrees;
+       }
+}
+
 bool Camera::successfullyCreated(std::string &error_message)
 {
        if (!m_playernode) {
@@ -462,33 +507,38 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_r
                m_camera_position = my_cp;
 
        /*
-        * Apply server-sent FOV. If server doesn't enforce FOV,
-        * check for zoom and set to zoom FOV.
-        * Otherwise, default to m_cache_fov
+        * Apply server-sent FOV, instantaneous or smooth transition.
+        * If not, check for zoom and set to zoom FOV.
+        * Otherwise, default to m_cache_fov.
         */
-
-       f32 fov_degrees;
-       PlayerFovSpec fov_spec = player->getFov();
-       if (fov_spec.fov > 0.0f) {
-               // If server-sent FOV is a multiplier, multiply
-               // it with m_cache_fov instead of overriding
-               if (fov_spec.is_multiplier)
-                       fov_degrees = m_cache_fov * fov_spec.fov;
-               else
-                       fov_degrees = fov_spec.fov;
+       if (m_fov_transition_active) {
+               // Smooth FOV transition
+               // Dynamically calculate FOV delta based on frametimes
+               f32 delta = (frametime / m_transition_time) * m_fov_diff;
+               m_curr_fov_degrees += delta;
+
+               // Mark transition as complete if target FOV has been reached
+               if ((m_fov_diff > 0.0f && m_curr_fov_degrees >= m_target_fov_degrees) ||
+                               (m_fov_diff < 0.0f && m_curr_fov_degrees <= m_target_fov_degrees)) {
+                       m_fov_transition_active = false;
+                       m_curr_fov_degrees = m_target_fov_degrees;
+               }
+       } else if (m_server_sent_fov) {
+               // Instantaneous FOV change
+               m_curr_fov_degrees = m_target_fov_degrees;
        } else if (player->getPlayerControl().zoom && player->getZoomFOV() > 0.001f) {
                // Player requests zoom, apply zoom FOV
-               fov_degrees = player->getZoomFOV();
+               m_curr_fov_degrees = player->getZoomFOV();
        } else {
                // Set to client's selected FOV
-               fov_degrees = m_cache_fov;
+               m_curr_fov_degrees = m_cache_fov;
        }
-       fov_degrees = rangelim(fov_degrees, 1.0f, 160.0f);
+       m_curr_fov_degrees = rangelim(m_curr_fov_degrees, 1.0f, 160.0f);
 
        // FOV and aspect ratio
        const v2u32 &window_size = RenderingEngine::get_instance()->getWindowSize();
        m_aspect = (f32) window_size.X / (f32) window_size.Y;
-       m_fov_y = fov_degrees * M_PI / 180.0;
+       m_fov_y = m_curr_fov_degrees * M_PI / 180.0;
        // Increase vertical FOV on lower aspect ratios (<16:10)
        m_fov_y *= MYMAX(1.0, MYMIN(1.4, sqrt(16./10. / m_aspect)));
        m_fov_x = 2 * atan(m_aspect * tan(0.5 * m_fov_y));
index 6ec37fe103e7a5a2349d4c0cec854e15382c7499..3a59637bcd2de0101318f4263f8b3ff2cf91e1a4 100644 (file)
@@ -112,6 +112,9 @@ public:
                return MYMAX(m_fov_x, m_fov_y);
        }
 
+       // Notify about new server-sent FOV and initialize smooth FOV transition
+       void notifyFovChange();
+
        // Checks if the constructor was able to create the scene nodes
        bool successfullyCreated(std::string &error_message);
 
@@ -186,6 +189,9 @@ private:
 
        Client *m_client;
 
+       // Default Client FOV (as defined by the "fov" setting)
+       f32 m_cache_fov;
+
        // Absolute camera position
        v3f m_camera_position;
        // Absolute camera direction
@@ -193,6 +199,14 @@ private:
        // Camera offset
        v3s16 m_camera_offset;
 
+       // Server-sent FOV variables
+       bool m_server_sent_fov = false;
+       f32 m_curr_fov_degrees, m_old_fov_degrees, m_target_fov_degrees;
+
+       // FOV transition variables
+       bool m_fov_transition_active = false;
+       f32 m_fov_diff, m_transition_time;
+
        v2f m_wieldmesh_offset = v2f(55.0f, -35.0f);
        v2f m_arm_dir;
        v2f m_cam_vel;
@@ -230,7 +244,6 @@ private:
 
        f32 m_cache_fall_bobbing_amount;
        f32 m_cache_view_bobbing_amount;
-       f32 m_cache_fov;
        bool m_arm_inertia;
 
        std::list<Nametag *> m_nametags;
index d19dc38183975844144de93b4d9602b4576fb04e..6428ed752541f9a7ab07ffadf211be582b95409b 100644 (file)
@@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "client/client.h"
 
 #include "util/base64.h"
+#include "client/camera.h"
 #include "chatmessage.h"
 #include "client/clientmedia.h"
 #include "log.h"
@@ -530,11 +531,21 @@ void Client::handleCommand_Movement(NetworkPacket* pkt)
 void Client::handleCommand_Fov(NetworkPacket *pkt)
 {
        f32 fov;
-       bool is_multiplier;
+       bool is_multiplier = false;
+       f32 transition_time = 0.0f;
+
        *pkt >> fov >> is_multiplier;
 
+       // Wrap transition_time extraction within a
+       // try-catch to preserve backwards compat
+       try {
+               *pkt >> transition_time;
+       } catch (PacketError &e) {};
+
        LocalPlayer *player = m_env.getLocalPlayer();
-       player->setFov({ fov, is_multiplier });
+       assert(player);
+       player->setFov({ fov, is_multiplier, transition_time });
+       m_camera->notifyFovChange();
 }
 
 void Client::handleCommand_HP(NetworkPacket *pkt)
index 4b7345b15a1d16b29557892b3a24eb53dd861b4d..73523ea4230fe5d8dc3b8e62afd5250e23472b69 100644 (file)
@@ -384,8 +384,9 @@ enum ToClientCommand
        /*
                Sends an FOV override/multiplier to client.
 
-               float fov
+               f32 fov
                bool is_multiplier
+               f32 transition_time
        */
 
        TOCLIENT_DEATHSCREEN = 0x37,
index de7f427e91a956fb9219bb1e373ed4ce1618e86f..3bc7762fa834d5487fc30925129b6ca2725edcc8 100644 (file)
@@ -35,7 +35,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 struct PlayerFovSpec
 {
        f32 fov;
+
+       // Whether to multiply the client's FOV or to override it
        bool is_multiplier;
+
+       // The time to be take to trasition to the new FOV value.
+       // Transition is instantaneous if omitted. Omitted by default.
+       f32 transition_time;
 };
 
 struct PlayerControl
@@ -186,12 +192,12 @@ public:
 
        void setFov(const PlayerFovSpec &spec)
        {
-               m_fov_spec = spec;
+               m_fov_override_spec = spec;
        }
 
        const PlayerFovSpec &getFov() const
        {
-               return m_fov_spec;
+               return m_fov_override_spec;
        }
 
        u32 keyPressed = 0;
@@ -208,7 +214,7 @@ protected:
        char m_name[PLAYERNAME_SIZE];
        v3f m_speed;
        u16 m_wield_index = 0;
-       PlayerFovSpec m_fov_spec = { 0.0f, false };
+       PlayerFovSpec m_fov_override_spec = { 0.0f, false, 0.0f };
 
        std::vector<HudElement *> hud;
 private:
index 77e1e7dc2b44ebae5a8ae140b7922d63214945e8..dcaee10b22832f2085d1aa907b936acab03c7e05 100644 (file)
@@ -1249,7 +1249,7 @@ int ObjectRef::l_set_look_yaw(lua_State *L)
        return 1;
 }
 
-// set_fov(self, degrees[, is_multiplier])
+// set_fov(self, degrees[, is_multiplier, transition_time])
 int ObjectRef::l_set_fov(lua_State *L)
 {
        NO_MAP_LOCK_REQUIRED;
@@ -1258,7 +1258,11 @@ int ObjectRef::l_set_fov(lua_State *L)
        if (!player)
                return 0;
 
-       player->setFov({ static_cast<f32>(luaL_checknumber(L, 2)), readParam<bool>(L, 3) });
+       player->setFov({
+               static_cast<f32>(luaL_checknumber(L, 2)),
+               readParam<bool>(L, 3, false),
+               lua_isnumber(L, 4) ? static_cast<f32>(luaL_checknumber(L, 4)) : 0.0f
+       });
        getServer(L)->SendPlayerFov(player->getPeerId());
 
        return 0;
@@ -1276,8 +1280,9 @@ int ObjectRef::l_get_fov(lua_State *L)
        PlayerFovSpec fov_spec = player->getFov();
        lua_pushnumber(L, fov_spec.fov);
        lua_pushboolean(L, fov_spec.is_multiplier);
+       lua_pushnumber(L, fov_spec.transition_time);
 
-       return 2;
+       return 3;
 }
 
 // set_breath(self, breath)
index a7eb528376d62b337f87f2e2da0ef5deb74ba629..0346d197d7d3c6843b26cb66ec94d4e72f5ca83b 100644 (file)
@@ -1892,10 +1892,10 @@ void Server::SendMovePlayer(session_t peer_id)
 
 void Server::SendPlayerFov(session_t peer_id)
 {
-       NetworkPacket pkt(TOCLIENT_FOV, 4 + 1, peer_id);
+       NetworkPacket pkt(TOCLIENT_FOV, 4 + 1 + 4, peer_id);
 
        PlayerFovSpec fov_spec = m_env->getPlayer(peer_id)->getFov();
-       pkt << fov_spec.fov << fov_spec.is_multiplier;
+       pkt << fov_spec.fov << fov_spec.is_multiplier << fov_spec.transition_time;
 
        Send(&pkt);
 }