From cdd715fd536a8d108b963bbee4d04e9540f38557 Mon Sep 17 00:00:00 2001 From: heinrich5991 Date: Wed, 1 Jul 2020 00:19:17 +0200 Subject: [PATCH] Add smoothing of camera zoom using a cubic polynomial --- CMakeLists.txt | 2 + src/game/bezier.cpp | 23 +++++ src/game/bezier.h | 29 ++++++ src/game/client/components/camera.cpp | 133 +++++++++++--------------- src/game/client/components/camera.h | 20 ++-- src/game/variables.h | 2 - 6 files changed, 123 insertions(+), 86 deletions(-) create mode 100644 src/game/bezier.cpp create mode 100644 src/game/bezier.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 71a5a0ce2..40686818c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1527,6 +1527,8 @@ set_src(ENGINE_SHARED GLOB src/engine/shared websockets.h ) set_src(GAME_SHARED GLOB src/game + bezier.cpp + bezier.h collision.cpp collision.h ddracecommands.h diff --git a/src/game/bezier.cpp b/src/game/bezier.cpp new file mode 100644 index 000000000..32d9a48a0 --- /dev/null +++ b/src/game/bezier.cpp @@ -0,0 +1,23 @@ +#include "bezier.h" + +CCubicBezier CCubicBezier::With(float Start, float StartDerivative, float EndDerivative, float End) +{ + return CCubicBezier(Start, Start + StartDerivative / 3, End - EndDerivative / 3, End); +} + +// f(t) = (1-t)³ a + 3(1-t)²t b + 3(1-t)t² c + t³ d +float CCubicBezier::Evaluate(float t) const +{ + return (1 - t) * (1 - t) * (1 - t) * a + + 3 * (1 - t) * (1 - t) * t * b + + 3 * (1 - t) * t * t * c + + t * t * t * d; +} + +// f(t) = 3(1-t)²(b-a) + 6(1-t)t(c-b) + 3t²(d-c) +float CCubicBezier::Derivative(float t) const +{ + return 3 * (1 - t) * (1 - t) * (b - a) + + 6 * (1 - t) * t * (c - b) + + 3 * t * t * (d - c); +} diff --git a/src/game/bezier.h b/src/game/bezier.h new file mode 100644 index 000000000..e6a9f6e7c --- /dev/null +++ b/src/game/bezier.h @@ -0,0 +1,29 @@ +#ifndef GAME_BEZIER_H +#define GAME_BEZIER_H + +// Evaluates the Bernstein polynomial of degree 3/a one-dimensional Bezier curve +// +// https://en.wikipedia.org/w/index.php?title=Bernstein_polynomial&oldid=965314973 +// +// f(t) = (1-t)³ a + 3(1-t)²t b + 3(1-t)t² c + t³ d +class CCubicBezier +{ + float a; + float b; + float c; + float d; + CCubicBezier(float a, float b, float c, float d) + { + this->a = a; + this->b = b; + this->c = c; + this->d = d; + } +public: + CCubicBezier() {} + float Evaluate(float t) const; + float Derivative(float t) const; + static CCubicBezier With(float Start, float StartDerivative, float EndDerivative, float End); +}; + +#endif // GAME_BEZIER_H diff --git a/src/game/client/components/camera.cpp b/src/game/client/components/camera.cpp index f69dd05a5..d959d5adf 100644 --- a/src/game/client/components/camera.cpp +++ b/src/game/client/components/camera.cpp @@ -6,49 +6,83 @@ #include #include -#include -#include #include +#include +#include #include "camera.h" #include "controls.h" #include +const float ZoomStep = 0.866025f; +const float ZoomTime = 0.25f; + CCamera::CCamera() { m_CamType = CAMTYPE_UNDEFINED; m_ZoomSet = false; m_Zoom = 1.0f; - m_StartZoom = m_Zoom; - m_TargetZoom = m_Zoom; - m_ZoomAnimStartTick = 0; - m_ZoomAnimEndTick = 0; + m_Zooming = false; +} + +float CCamera::ZoomProgress(float CurrentTime) const +{ + return (CurrentTime - m_ZoomSmoothingStart) / (m_ZoomSmoothingEnd - m_ZoomSmoothingStart); +} + +void CCamera::ScaleZoom(float Factor) +{ + float CurrentTarget = m_Zooming ? m_ZoomSmoothingTarget : m_Zoom; + ChangeZoom(CurrentTarget * Factor); +} + +void CCamera::ChangeZoom(float Target) +{ + if(Target >= 500.0f/ZoomStep) + { + return; + } + + float Now = Client()->LocalTime(); + float Current = m_Zoom; + float Derivative = 0.0f; + if(m_Zooming) + { + float Progress = ZoomProgress(Now); + Current = m_ZoomSmoothing.Evaluate(Progress); + Derivative = m_ZoomSmoothing.Derivative(Progress); + } + + m_ZoomSmoothingTarget = Target; + m_ZoomSmoothing = CCubicBezier::With(Current, Derivative, 0, m_ZoomSmoothingTarget); + m_ZoomSmoothingStart = Now; + m_ZoomSmoothingEnd = Now + ZoomTime; + + m_Zooming = true; } void CCamera::OnRender() { - if(IsZooming()) + if(m_Zooming) { - - // The logistic function with default values give values near maximums and minimums on [-6, 6]. - float ScaledProgress = ZoomProgress() * 12 - 6; - float Amount = 1.f / (1.f + exp(-ScaledProgress)); - m_Zoom = mix(m_StartZoom, m_TargetZoom, Amount); - - if(m_TargetZoom < m_StartZoom) - m_Zoom = clamp(m_Zoom, m_TargetZoom, m_StartZoom); + float Time = Client()->LocalTime(); + if(Time >= m_ZoomSmoothingEnd) + { + m_Zoom = m_ZoomSmoothingTarget; + m_Zooming = false; + } else - m_Zoom = clamp(m_Zoom, m_StartZoom, m_TargetZoom); + { + m_Zoom = m_ZoomSmoothing.Evaluate(ZoomProgress(Time)); + } } if(!(m_pClient->m_Snap.m_SpecInfo.m_Active || GameClient()->m_GameInfo.m_AllowZoom || Client()->State() == IClient::STATE_DEMOPLAYBACK)) { m_ZoomSet = false; m_Zoom = 1.0f; - m_StartZoom = m_Zoom; - m_TargetZoom = m_Zoom; - m_ZoomAnimEndTick = 0; + m_Zooming = false; } else if(!m_ZoomSet && g_Config.m_ClDefaultZoom != 10) { @@ -108,24 +142,10 @@ void CCamera::OnConsoleInit() Console()->Register("zoom", "", CFGFLAG_CLIENT, ConZoomReset, this, "Zoom reset"); } -const float ZoomStep = 0.866025f; - void CCamera::OnReset() { - m_Zoom = 1.0f; - - if(g_Config.m_ClDefaultZoom < 10) - { - m_Zoom = pow(1/ZoomStep, 10 - g_Config.m_ClDefaultZoom); - } - else if(g_Config.m_ClDefaultZoom > 10) - { - m_Zoom = pow(ZoomStep, g_Config.m_ClDefaultZoom - 10); - } - - m_StartZoom = m_Zoom; - m_TargetZoom = m_Zoom; - m_ZoomAnimEndTick = 0; + m_Zoom = pow(ZoomStep, g_Config.m_ClDefaultZoom - 10); + m_Zooming = false; } void CCamera::ConZoomPlus(IConsole::IResult *pResult, void *pUserData) @@ -133,10 +153,7 @@ void CCamera::ConZoomPlus(IConsole::IResult *pResult, void *pUserData) CCamera *pSelf = (CCamera *)pUserData; if(pSelf->m_pClient->m_Snap.m_SpecInfo.m_Active || pSelf->GameClient()->m_GameInfo.m_AllowZoom || pSelf->Client()->State() == IClient::STATE_DEMOPLAYBACK) { - if(g_Config.m_ClSmoothZoom) - pSelf->StartSmoothZoom(ZoomStep); - else - pSelf->m_Zoom *= ZoomStep; + pSelf->ScaleZoom(ZoomStep); } } void CCamera::ConZoomMinus(IConsole::IResult *pResult, void *pUserData) @@ -144,44 +161,10 @@ void CCamera::ConZoomMinus(IConsole::IResult *pResult, void *pUserData) CCamera *pSelf = (CCamera *)pUserData; if(pSelf->m_pClient->m_Snap.m_SpecInfo.m_Active || pSelf->GameClient()->m_GameInfo.m_AllowZoom || pSelf->Client()->State() == IClient::STATE_DEMOPLAYBACK) { - if(pSelf->m_Zoom < 500.0f/ZoomStep) - { - if(g_Config.m_ClSmoothZoom) - pSelf->StartSmoothZoom(1/ZoomStep); - else - pSelf->m_Zoom *= 1/ZoomStep; - } + pSelf->ScaleZoom(1 / ZoomStep); } } void CCamera::ConZoomReset(IConsole::IResult *pResult, void *pUserData) { - ((CCamera *)pUserData)->OnReset(); -} - -float CCamera::ZoomProgress() -{ - int SmoothTick; - Client()->GetSmoothTick(&SmoothTick, NULL, 0); - return (SmoothTick - m_ZoomAnimStartTick) / (m_ZoomAnimEndTick - m_ZoomAnimStartTick); -} - -bool CCamera::IsZooming() -{ - return Client()->GameTick(g_Config.m_ClDummy) < m_ZoomAnimEndTick && m_ZoomAnimStartTick < m_ZoomAnimEndTick; -} - -void CCamera::StartSmoothZoom(float ZoomStep) -{ - // Check if we are in the middle of a smooth zoom already. - if(IsZooming()) - { - // TODO: Implement - } - else - { - m_StartZoom = m_Zoom; - m_TargetZoom = m_StartZoom * ZoomStep; - m_ZoomAnimStartTick = Client()->GameTick(g_Config.m_ClDummy); - m_ZoomAnimEndTick = m_ZoomAnimStartTick + g_Config.m_ClSmoothZoomLength / 1000.f * Client()->GameTickSpeed(); - } + ((CCamera *)pUserData)->ChangeZoom(1.0f); } diff --git a/src/game/client/components/camera.h b/src/game/client/components/camera.h index d5693a5b0..a7c1ad13a 100644 --- a/src/game/client/components/camera.h +++ b/src/game/client/components/camera.h @@ -3,6 +3,7 @@ #ifndef GAME_CLIENT_COMPONENTS_CAMERA_H #define GAME_CLIENT_COMPONENTS_CAMERA_H #include +#include #include class CCamera : public CComponent @@ -18,14 +19,20 @@ class CCamera : public CComponent vec2 m_LastPos[2]; vec2 m_PrevCenter; + bool m_Zooming; + float m_ZoomSmoothingTarget; + CCubicBezier m_ZoomSmoothing; + float m_ZoomSmoothingStart; + float m_ZoomSmoothingEnd; + + void ScaleZoom(float Factor); + void ChangeZoom(float Target); + float ZoomProgress(float CurrentTime) const; + public: vec2 m_Center; bool m_ZoomSet; - float m_StartZoom; float m_Zoom; - float m_TargetZoom; - float m_ZoomAnimStartTick; - float m_ZoomAnimEndTick; CCamera(); virtual void OnRender(); @@ -35,11 +42,6 @@ public: virtual void OnConsoleInit(); virtual void OnReset(); - void StartSmoothZoom(float ZoomStep); - // Returns the zoom progress [0, 1] - float ZoomProgress(); - bool IsZooming(); - private: static void ConZoomPlus(IConsole::IResult *pResult, void *pUserData); static void ConZoomMinus(IConsole::IResult *pResult, void *pUserData); diff --git a/src/game/variables.h b/src/game/variables.h index 3c5f5d3fb..e19c58b5e 100644 --- a/src/game/variables.h +++ b/src/game/variables.h @@ -82,8 +82,6 @@ MACRO_CONFIG_INT(ClAutoStatboardScreenshot, cl_auto_statboard_screenshot, 0, 0, MACRO_CONFIG_INT(ClAutoStatboardScreenshotMax, cl_auto_statboard_screenshot_max, 10, 0, 1000, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Maximum number of automatically created statboard screenshots (0 = no limit)") MACRO_CONFIG_INT(ClDefaultZoom, cl_default_zoom, 10, 0, 20, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Default zoom level (10 default, min 0, max 20)") -MACRO_CONFIG_INT(ClSmoothZoom, cl_smooth_zoom, 1, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Whether to enable smooth zoom.") -MACRO_CONFIG_INT(ClSmoothZoomLength, cl_smooth_zoom_length, 800, 1, 5000, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Zoom smooth animation length in miliseconds.") MACRO_CONFIG_INT(ClPlayerUseCustomColor, player_use_custom_color, 0, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Toggles usage of custom colors") MACRO_CONFIG_COL(ClPlayerColorBody, player_color_body, 65408, CFGFLAG_CLIENT|CFGFLAG_SAVE|CFGFLAG_COLLIGHT, "Player body color")