Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

spring dampened camera #1589

Closed
Closed
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
188 changes: 177 additions & 11 deletions rts/Game/CameraHandler.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */

#include <cmath>
#include <cstring> // memset
#include <cstdlib>
#include <cstdarg> // va_start
Expand All @@ -17,13 +18,15 @@
#include "Camera/OverviewController.h"
#include "Camera/SpringController.h"
#include "Players/Player.h"
#include "System/MathConstants.h"
#include "UI/UnitTracker.h"
#include "Rendering/GlobalRendering.h"
#include "System/SpringMath.h"
#include "System/SafeUtil.h"
#include "System/StringHash.h"
#include "System/Config/ConfigHandler.h"
#include "System/Log/ILog.h"
#include "System/SpringDampers.h"

#include "System/Misc/TracyDefs.h"

Expand Down Expand Up @@ -63,6 +66,20 @@ CONFIG(float, CamTimeExponent)
.minimumValue(0.0f)
.description("Camera transitions happen at lerp(old, new, timeNorm ^ CamTimeExponent).");

CONFIG(int, CamTransitionMode)
.defaultValue(CCameraHandler::CAMERA_TRANSITION_MODE_EXP_DECAY)
.description(strformat("Defines the function used for camera transitions. Options are:\n%i = Exponential Decay\n%i = Spring Dampened\n%i = Spring Dampened with timed transitions",
(int)CCameraHandler::CAMERA_TRANSITION_MODE_EXP_DECAY,
(int)CCameraHandler::CAMERA_TRANSITION_MODE_SPRING_DAMPENED,
(int)CCameraHandler::CAMERA_TRANSITION_MODE_TIMED_SPRING_DAMPENED))
.minimumValue(0)
.maximumValue((int)CCameraHandler::CAMERA_TRANSITION_MODE_TIMED_SPRING_DAMPENED);

CONFIG(int, CamSpringHalflife)
.defaultValue(100)
.description("For Spring Dampened camera. It is the time at which the camera should be approximately halfway towards the goal.")
loveridge marked this conversation as resolved.
Show resolved Hide resolved
.minimumValue(0);

CCameraHandler* camHandler = nullptr;


Expand Down Expand Up @@ -152,16 +169,20 @@ void CCameraHandler::Init()
SortRegisteredActions();
}

configHandler->NotifyOnChange(this, {"CamModeName", "CamTimeFactor", "CamTimeExponent", "CamTransitionMode", "CamSpringHalflife"});

{
camTransState.startFOV = 90.0f;
camTransState.timeStart = 0.0f;
camTransState.timeEnd = 0.0f;

camTransState.timeFactor = configHandler->GetFloat("CamTimeFactor");
camTransState.timeExponent = configHandler->GetFloat("CamTimeExponent");
camTransState.halflife = configHandler->GetFloat("CamSpringHalflife");
}

SetCameraMode(configHandler->GetString("CamModeName"));
currCamTransitionNum = configHandler->GetInt("CamTransitionMode");

for (CCameraController* cc: camControllers) {
cc->Update();
Expand Down Expand Up @@ -210,6 +231,21 @@ void CCameraHandler::KillControllers()
assert(camControllers[0] == nullptr);
}

void CCameraHandler::ConfigNotify(const std::string& key, const std::string& value)
{
RECOIL_DETAILED_TRACY_ZONE;
if (key == "CamModeName") {
SetCameraMode(configHandler->GetString("CamModeName"));
} else {
currCamTransitionNum = configHandler->GetInt("CamTransitionMode");

camTransState.timeFactor = configHandler->GetFloat("CamTimeFactor");
camTransState.timeExponent = configHandler->GetFloat("CamTimeExponent");
camTransState.halflife = configHandler->GetFloat("CamSpringHalflife");

camTransState.lastTime = spring_gettime().toMilliSecsf();
}
}

void CCameraHandler::UpdateController(CPlayer* player, bool fpsMode, bool fsEdgeMove, bool wnEdgeMove)
{
Expand All @@ -223,7 +259,9 @@ void CCameraHandler::UpdateController(CPlayer* player, bool fpsMode, bool fsEdge
// We have to update transition both before and after updating the controller:
// before: if the controller makes a new begincam, it needs to take into account previous transitions
// after: apply changes made by the controller with 0 time transition.
UpdateTransition();
if (currCamTransitionNum == CAMERA_TRANSITION_MODE_EXP_DECAY) {
UpdateTransition();
}

if (fpsCon.oldDCpos != ZeroVector) {
camCon.SetPos(fpsCon.oldDCpos);
Expand All @@ -243,7 +281,7 @@ void CCameraHandler::UpdateController(CCameraController& camCon, bool keyMove, b
RECOIL_DETAILED_TRACY_ZONE;
if (keyMove) {
// NOTE: z-component contains speed scaling factor, xy is movement
const float3 camMoveVector = camera->GetMoveVectorFromState(true);
const float3 camMoveVector = camera->GetMoveVectorFromState(true);

// key scrolling
if ((camMoveVector * XYVector).SqLength() > 0.0f) {
Expand Down Expand Up @@ -278,10 +316,8 @@ void CCameraHandler::UpdateController(CCameraController& camCon, bool keyMove, b
}
}


void CCameraHandler::CameraTransition(float nsecs)
void CameraTransitionExpDecay(const CCameraController* currCam, CCameraHandler::CamTransitionState& camTransState, float nsecs)
{
RECOIL_DETAILED_TRACY_ZONE;
nsecs = std::max(nsecs, 0.0f) * camTransState.timeFactor;

// calculate when transition should end based on duration in seconds
Expand All @@ -298,13 +334,46 @@ void CCameraHandler::CameraTransition(float nsecs)
camTransState.startFOV = camera->GetVFOV();
}

void CCameraHandler::UpdateTransition()
void CameraTransitionTimedSpringDampened(const CCameraController* currCam, CCameraHandler::CamTransitionState& camTransState, float nsecs)
{
if (nsecs <= 0.0f) {
camera->SetPos(currCam->GetPos());
camera->SetRot(currCam->GetRot());
camera->SetVFOV(currCam->GetFOV());
} else {
camTransState.timeEnd = nsecs * 1000.0f;
camTransState.timeStart = nsecs * 1000.0f;
}
}

void CameraTransitionSpringDampened(const CCameraController* currCam, CCameraHandler::CamTransitionState& camTransState, float nsecs)
{
if (nsecs <= 0.0f) {
camera->SetPos(currCam->GetPos());
camera->SetRot(currCam->GetRot());
camera->SetVFOV(currCam->GetFOV());
}
}


static constexpr decltype(&CameraTransitionExpDecay) cameraTransitionFunction[] = {
CameraTransitionExpDecay,
CameraTransitionSpringDampened,
CameraTransitionTimedSpringDampened,
};

void CCameraHandler::CameraTransition(float nsecs)
{
RECOIL_DETAILED_TRACY_ZONE;
camTransState.tweenPos = camControllers[currCamCtrlNum]->GetPos();
camTransState.tweenRot = camControllers[currCamCtrlNum]->GetRot();
camTransState.tweenFOV = camControllers[currCamCtrlNum]->GetFOV();
cameraTransitionFunction[currCamTransitionNum](camControllers[currCamCtrlNum], camTransState, nsecs);
}

void UpdateTransitionExpDecay(const CCameraController* currCam, CCameraHandler::CamTransitionState& camTransState)
{
RECOIL_DETAILED_TRACY_ZONE;
camTransState.tweenPos = currCam->GetPos();
camTransState.tweenRot = currCam->GetRot();
camTransState.tweenFOV = currCam->GetFOV();

int vsync = configHandler->GetInt("VSync");
float transTime = globalRendering->lastFrameStart.toMilliSecsf();
Expand All @@ -317,12 +386,12 @@ void CCameraHandler::UpdateTransition()
transTime = globalRendering->lastSwapBuffersEnd.toMilliSecsf() + 1000.0f / drawFPS;
}

const float timeRatio = (camTransState.timeEnd - camTransState.timeStart != 0.0f) ?
float timeRatio = (camTransState.timeEnd - camTransState.timeStart != 0.0f) ?
std::fmax(0.0f, (camTransState.timeEnd - transTime) / (camTransState.timeEnd - camTransState.timeStart)) :
0.0f;

float tweenFact = 1.0f - math::pow(timeRatio, camTransState.timeExponent);


if (vsync == 1 && camera->useInterpolate == 1) {
tweenFact = 1.0f - timeRatio;
Expand Down Expand Up @@ -357,6 +426,103 @@ void CCameraHandler::UpdateTransition()
camera->Update();
}

float3 vectorRemainder(float3 vec)
{
vec.x = std::remainder(vec.x, math::TWOPI);
vec.y = std::remainder(vec.y, math::TWOPI);
vec.z = std::remainder(vec.z, math::TWOPI);
return vec;
}

void UpdateTransitionTimedSpringDampened(const CCameraController* currCam, CCameraHandler::CamTransitionState& camTransState)
{
float3 targetPos = currCam->GetPos();
float3 targetRot = vectorRemainder(currCam->GetRot());
float targetFov = currCam->GetFOV();

float3 currentPos = camera->GetPos();
float3 currentRot = vectorRemainder(camera->GetRot());
float currentFov = camera->GetVFOV();
float3 goalRot{};

float currTime = spring_gettime().toMilliSecsf();
float dt = currTime - camTransState.lastTime;
camTransState.lastTime = currTime;
camTransState.timeEnd -= dt;
camTransState.timeEnd = std::max(camTransState.timeEnd, 0.0f);
camTransState.timeStart -= dt;
camTransState.timeStart = std::max(camTransState.timeStart, 0.0f);

if(currentPos.equals(targetPos) && currentRot.equals(targetRot) && currentFov == targetFov) {
return;
}

float damping = spring_damper_damping(camTransState.halflife);
float eydt = spring_damper_eydt(damping, dt);

timed_spring_damper_exact_vector(currentPos, camTransState.posVelocity, camTransState.startPos,
targetPos, camTransState.timeEnd, camTransState.halflife, damping, eydt, dt, 2.0f);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
timed_spring_damper_exact_vector(currentPos, camTransState.posVelocity, camTransState.startPos,
targetPos, camTransState.timeEnd, camTransState.halflife, damping, eydt, dt, 2.0f);
static constexpr float APPREHENSION = 2.0f; // value chosen on the basis of XYZ
timed_spring_damper_exact_vector(currentPos, camTransState.posVelocity, camTransState.startPos,
targetPos, camTransState.timeEnd, camTransState.halflife, damping, eydt, dt, APPREHENSION);

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is what the guy writes about apprehension:

apprehension parameter controls how far into the future we try to track the linear interpolation. A value of 2 means two-times the half life, or that we expect the blend-out to be 75% done by the goal time.


if (abs(targetRot.y - currentRot.y) > 4.5f) {
loveridge marked this conversation as resolved.
Show resolved Hide resolved
if (targetRot.y > 0) {
targetRot.y -= math::TWOPI;
}else {
targetRot.y += math::TWOPI;
}
}
timed_spring_damper_exact_vector(currentRot, camTransState.rotVelocity, camTransState.startRot,
targetRot, camTransState.timeStart, camTransState.halflife, damping, eydt, dt, 2.0f);

simple_spring_damper_exact(currentFov, camTransState.fovVelocity, targetFov, damping, eydt, dt);
// LOG_L(L_INFO, "tweenfact %0.3f, %0.3f, %0.3f", 0.0f, camTransState.timeEnd, dt);
camera->SetPos(currentPos);
camera->SetRot(vectorRemainder(currentRot));
camera->SetVFOV(currentFov);
camera->Update();
}

void UpdateTransitionSpringDampened(const CCameraController* currCam, CCameraHandler::CamTransitionState& camTransState){
float3 targetPos = currCam->GetPos();
float3 targetRot = ClampRad(currCam->GetRot());
float targetFov = currCam->GetFOV();

float3 currentPos = camera->GetPos();
float3 currentRot = ClampRad(camera->GetRot());
float currentFov = camera->GetVFOV();
float3 goalRot{};

float currTime = spring_gettime().toMilliSecsf();
float dt = currTime - camTransState.lastTime;
camTransState.lastTime = currTime;

if(currentPos.equals(targetPos) && currentRot.equals(targetRot) && currentFov == targetFov) {
return;
}

float damping = spring_damper_damping(camTransState.halflife);
float eydt = spring_damper_eydt(damping, dt);

simple_spring_damper_exact_vector(currentPos, camTransState.posVelocity, targetPos, damping, eydt, dt);
simple_spring_damper_exact_vector(goalRot, camTransState.rotVelocity, GetRadAngleToward(currentRot, targetRot), damping, eydt, dt);
simple_spring_damper_exact(currentFov, camTransState.fovVelocity, targetFov, damping, eydt, dt);
// LOG_L(L_INFO, "tweenfact %0.3f, %0.3f, %0.3f", GetRadAngleToward(currentRot, targetRot).x, GetRadAngleToward(currentRot, targetRot).y, GetRadAngleToward(currentRot, targetRot).z);
camera->SetPos(currentPos);
camera->SetRot(ClampRad(goalRot + currentRot));
camera->SetVFOV(currentFov);
camera->Update();
}

static constexpr decltype(&UpdateTransitionExpDecay) cameraUpdateTransitionFunction[] = {
UpdateTransitionExpDecay,
UpdateTransitionSpringDampened,
UpdateTransitionTimedSpringDampened,
};

void CCameraHandler::UpdateTransition()
{
RECOIL_DETAILED_TRACY_ZONE;
cameraUpdateTransitionFunction[currCamTransitionNum](camControllers[currCamCtrlNum], camTransState);
}

void CCameraHandler::SetCameraMode(unsigned int newMode)
{
Expand Down
49 changes: 33 additions & 16 deletions rts/Game/CameraHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,36 @@ class CCameraHandler : public CommandReceiver
CAMERA_MODE_DUMMY = 6,
CAMERA_MODE_LAST = 7,
};
enum {
CAMERA_TRANSITION_MODE_EXP_DECAY = 0,
CAMERA_TRANSITION_MODE_SPRING_DAMPENED = 1,
CAMERA_TRANSITION_MODE_TIMED_SPRING_DAMPENED = 2,
};

struct CamTransitionState {
float3 startPos;
float3 tweenPos;
float3 startRot;
float3 tweenRot;

float startFOV = 0.0f;
float tweenFOV = 0.0f;

float timeStart = 0.0f;
float timeEnd = 0.0f;

// spring dampened transitions
float3 posVelocity;
float3 rotVelocity;
float fovVelocity;
float lastTime;

// configurable parameters
float timeFactor = 0.0f;
float timeExponent = 0.0f;
float halflife = 0.0f;
};


public:
CCameraHandler();
Expand Down Expand Up @@ -59,6 +89,8 @@ class CCameraHandler : public CommandReceiver
void PushMode();
void PopMode();

void ConfigNotify(const std::string& key, const std::string& value);

void SetTransitionParams(float factor, float expon) {
camTransState.timeFactor = factor;
camTransState.timeExponent = expon;
Expand Down Expand Up @@ -106,22 +138,7 @@ class CCameraHandler : public CommandReceiver

private:
unsigned int currCamCtrlNum = CAMERA_MODE_DUMMY;

struct CamTransitionState {
float3 startPos;
float3 tweenPos;
float3 startRot;
float3 tweenRot;

float startFOV = 0.0f;
float tweenFOV = 0.0f;

float timeStart = 0.0f;
float timeEnd = 0.0f;
// configurable parameters
float timeFactor = 0.0f;
float timeExponent = 0.0f;
};
unsigned int currCamTransitionNum = CAMERA_TRANSITION_MODE_EXP_DECAY;

CamTransitionState camTransState;

Expand Down
3 changes: 3 additions & 0 deletions rts/Game/UI/UnitTracker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,9 @@ void CUnitTracker::SetCam()
if (lastFollowUnit != 0 && unitHandler.GetUnitUnsafe(lastFollowUnit) == nullptr) {
timeOut = 1;
lastFollowUnit = 0;
} else {
oldCamDir = camera->GetDir();
oldCamPos = camera->GetPos();
}

if (timeOut > 0) {
Expand Down
6 changes: 3 additions & 3 deletions rts/Lua/LuaUnsyncedCtrl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1226,9 +1226,9 @@ int LuaUnsyncedCtrl::SetCameraState(lua_State* L)
luaL_error(L, "[%s([ stateTable[, camTransTime[, transTimeFactor[, transTimeExpon] ] ] ])] incorrect arguments", __func__);

camHandler->SetTransitionParams(luaL_optfloat(L, 3, camHandler->GetTransitionTimeFactor()), luaL_optfloat(L, 4, camHandler->GetTransitionTimeExponent()));
camHandler->CameraTransition(luaL_optfloat(L, 2, 0.0f));

const bool retval = camHandler->SetState(hasState ? ParseCamStateMap(L, 1) : camHandler->GetState());
camHandler->CameraTransition(luaL_optfloat(L, 2, 0.0f));
const bool synced = CLuaHandle::GetHandleSynced(L);

// always push false in synced
Expand Down Expand Up @@ -2657,7 +2657,7 @@ int LuaUnsyncedCtrl::AssignMouseCursor(lua_State* L)
*
* @function Spring.ReplaceMouseCursor
* @string oldFileName
* @string newFileName
* @string newFileName
* @bool[opt=false] hotSpotTopLeft
* @treturn ?nil|bool assigned
*/
Expand Down Expand Up @@ -3552,7 +3552,7 @@ int LuaUnsyncedCtrl::ShareResources(lua_State* L)
* @number x
* @number y
* @number z
* @treturn nil
* @treturn nil
*/
int LuaUnsyncedCtrl::SetLastMessagePosition(lua_State* L)
{
Expand Down
Loading