From 4e21cf5cbfda2cae75438dd74a5d91b556024deb Mon Sep 17 00:00:00 2001 From: nukeykt Date: Mon, 4 Nov 2019 02:20:05 +0900 Subject: [PATCH] Start adding view interpolation using code from duke3d --- source/exhumed/src/exhumed.cpp | 471 ++++++++++++++++++++------------- source/exhumed/src/exhumed.h | 5 + source/exhumed/src/osdcmds.cpp | 8 +- source/exhumed/src/player.cpp | 11 +- source/exhumed/src/player.h | 5 +- source/exhumed/src/view.cpp | 86 +++++- source/exhumed/src/view.h | 8 +- 7 files changed, 397 insertions(+), 197 deletions(-) diff --git a/source/exhumed/src/exhumed.cpp b/source/exhumed/src/exhumed.cpp index c111076f2..54cd0bb11 100644 --- a/source/exhumed/src/exhumed.cpp +++ b/source/exhumed/src/exhumed.cpp @@ -1817,7 +1817,7 @@ void FinishLevel() WaitVBL(); RefreshBackground(); RefreshStatus(); - DrawView(); + DrawView(65536); videoNextPage(); } @@ -1979,6 +1979,153 @@ void app_crashhandler(void) void G_Polymer_UnInit(void) { } +static inline int32_t calc_smoothratio(ClockTicks totalclk, ClockTicks ototalclk) +{ + // if (!((ud.show_help == 0 && (!g_netServer && ud.multimode < 2) && ((g_player[myconnectindex].ps->gm & MODE_MENU) == 0)) || + // (g_netServer || ud.multimode > 1) || + // ud.recstat == 2) || + // ud.pause_on) + // { + // return 65536; + // } + if (bRecord || bPlayback) + return 65536; + int32_t rfreq = (refreshfreq != -1 ? refreshfreq : 60); + uint64_t elapsedFrames = tabledivide64(((uint64_t) (totalclk - ototalclk).toScale16()) * rfreq, 65536*120); +#if 0 + //POGO: additional debug info for testing purposes + OSD_Printf("Elapsed frames: %" PRIu64 ", smoothratio: %" PRIu64 "\n", elapsedFrames, tabledivide64(65536*elapsedFrames*30, rfreq)); +#endif + return clamp(tabledivide64(65536*elapsedFrames*30, rfreq), 0, 65536); +} + +ClockTicks tclocks, tclocks2; + +static void GameDisplay(void) +{ + // End Section B + + SetView1(); + + if (levelnum == kMap20) + { + LockEnergyTiles(); + DoEnergyTile(); + DrawClock(); + } + + auto smoothRatio = calc_smoothratio(totalclock, tclocks); + + DrawView(smoothRatio); + UpdateMap(); + + if (bMapMode) + { +#if 0 + if (bHiRes && nViewBottom > nMaskY) + { + videoSetViewableArea(nViewLeft, nViewTop, nViewRight, nMaskY); + DrawMap(); + videoSetViewableArea(nViewLeft, nViewTop, nViewRight, nViewBottom); + } + else + { + DrawMap(); + } +#else + // TODO: Map should not be drawn on top of status bar. Redraw status bar? + DrawMap(); +#endif + } + + videoNextPage(); +} + +static void GameMove(void) +{ + FixPalette(); + + if (levelnum == kMap20) + { + if (lCountDown <= 0) + { + for (int i = 0; i < nTotalPlayers; i++) { + nPlayerLives[i] = 0; + } + + DoFailedFinalScene(); + levelnew = 100; + + return; + } + // Pink section + lCountDown--; + DrawClock(); + + if (nRedTicks) + { + nRedTicks--; + + if (nRedTicks <= 0) { + DoRedAlert(0); + } + + nAlarmTicks--; + nButtonColor--; + + if (nAlarmTicks <= 0) { + DoRedAlert(1); + } + } + } + + // YELLOW SECTION + MoveThings(); + + if (totalvel[nLocalPlayer] == 0) + { + bobangle = 0; + } + else + { + bobangle += 56; + bobangle &= kAngleMask; + } + + // loc_120E9: + totalmoves++; + moveframes--; +} + +int32_t r_maxfps = 60; +int32_t r_maxfpsoffset = 0; +double g_frameDelay = 0.0; + +int G_FPSLimit(void) +{ + if (!r_maxfps) + return 1; + + static double nextPageDelay; + static uint64_t lastFrameTicks; + + uint64_t const frameTicks = timerGetTicksU64(); + uint64_t const elapsedTime = frameTicks - lastFrameTicks; + double const dElapsedTime = elapsedTime; + + if (dElapsedTime >= floor(nextPageDelay)) + { + if (dElapsedTime <= nextPageDelay+g_frameDelay) + nextPageDelay += g_frameDelay-dElapsedTime; + + lastFrameTicks = frameTicks; + + return 1; + } + + return 0; +} + int app_main(int argc, char const* const* argv) { char tempbuf[256]; @@ -2031,7 +2178,6 @@ int app_main(int argc, char const* const* argv) //int edi = esi; int doTitle = kFalse; // REVERT kTrue; int stopTitle = kFalse; - int tclocks, tclocks2; levelnew = 1; // REVERT - change back to kTrue @@ -2358,6 +2504,8 @@ int app_main(int argc, char const* const* argv) if (enginePostInit()) ShutDown(); + g_frameDelay = calcFrameDelay(r_maxfps + r_maxfpsoffset); + // loc_11745: FadeOut(0); // InstallEngine(); @@ -2590,7 +2738,7 @@ LOOP3: ResetMoveFifo(); moveframes = 0; bInMove = kFalse; - tclocks = (int)totalclock; + tclocks = totalclock; nPlayerDAng = 0; lPlayerXVel = 0; lPlayerYVel = 0; @@ -2604,7 +2752,7 @@ LOOP3: mysetbrightness((uint8_t)nGamma); //int edi = totalclock; - tclocks2 = (int)totalclock; + tclocks2 = totalclock; CONTROL_BindsEnabled = 1; // Game Loop while (1) @@ -2631,209 +2779,168 @@ LOOP3: nCDTrackLength = -1; } } - // End Section B - - SetView1(); - - if (levelnum == kMap20) - { - LockEnergyTiles(); - DoEnergyTile(); - DrawClock(); - } - - DrawView(); - UpdateMap(); - - if (bMapMode) - { -#if 0 - if (bHiRes && nViewBottom > nMaskY) - { - videoSetViewableArea(nViewLeft, nViewTop, nViewRight, nMaskY); - DrawMap(); - videoSetViewableArea(nViewLeft, nViewTop, nViewRight, nViewBottom); - } - else - { - DrawMap(); - } -#else - // TODO: Map should not be drawn on top of status bar. Redraw status bar? - DrawMap(); -#endif - } - - videoNextPage(); // TODO CONTROL_GetButtonInput(); CheckKeys(); UpdateSounds(); - bInMove = kTrue; - - moveframes = ((int)totalclock - tclocks2) / 4; - - if (moveframes > 4) - moveframes = 4; - - if (moveframes != 0) - tclocks2 = (int)totalclock; - - if (bPlayback) + if (bRecord || bPlayback) { - // YELLOW - if ((bInDemo && KB_KeyWaiting() || !ReadPlaybackInputs()) && KB_GetCh()) + bInMove = kTrue; + + moveframes = ((int)totalclock - (int)tclocks2) / 4; + + if (moveframes > 4) + moveframes = 4; + + if (moveframes != 0) + tclocks2 = totalclock; + + if (bPlayback) { - KB_FlushKeyboardQueue(); - KB_ClearKeysDown(); + // YELLOW + if ((bInDemo && KB_KeyWaiting() || !ReadPlaybackInputs()) && KB_GetCh()) + { + KB_FlushKeyboardQueue(); + KB_ClearKeysDown(); - bPlayback = kFalse; - bInDemo = kFalse; + bPlayback = kFalse; + bInDemo = kFalse; - if (vcrfp) { - fclose(vcrfp); + if (vcrfp) { + fclose(vcrfp); + } + + CONTROL_BindsEnabled = 0; + goto MENU; } - - CONTROL_BindsEnabled = 0; - goto MENU; } - } - else if (bRecord || moveframes) - { - GetLocalInput(); - - sPlayerInput[nLocalPlayer].xVel = lPlayerXVel; - sPlayerInput[nLocalPlayer].yVel = lPlayerYVel; - sPlayerInput[nLocalPlayer].buttons = lLocalButtons | lLocalCodes; - sPlayerInput[nLocalPlayer].nAngle = nPlayerDAng; - sPlayerInput[nLocalPlayer].nTarget = besttarget; - - Ra[nLocalPlayer].nTarget = besttarget; - - lLocalCodes = 0; - nPlayerDAng = 0; - - sPlayerInput[nLocalPlayer].horizon = nVertPan[nLocalPlayer]; - } - - // loc_11F72: - if (bRecord && !bInDemo) { - WritePlaybackInputs(); - } - - if (nNetPlayerCount) - { - if (moveframes) + else if (bRecord || moveframes) { - UpdateInputs(); - moveframes = nNetMoveFrames; + GetLocalInput(); + + sPlayerInput[nLocalPlayer].xVel = lPlayerXVel; + sPlayerInput[nLocalPlayer].yVel = lPlayerYVel; + sPlayerInput[nLocalPlayer].buttons = lLocalButtons | lLocalCodes; + sPlayerInput[nLocalPlayer].nAngle = nPlayerDAng; + sPlayerInput[nLocalPlayer].nTarget = besttarget; + + Ra[nLocalPlayer].nTarget = besttarget; + + lLocalCodes = 0; + nPlayerDAng = 0; + + sPlayerInput[nLocalPlayer].horizon = nVertPan[nLocalPlayer]; + } + + // loc_11F72: + if (bRecord && !bInDemo) { + WritePlaybackInputs(); + } + + if (nNetPlayerCount) + { + if (moveframes) + { + UpdateInputs(); + moveframes = nNetMoveFrames; + } + } + else + { + // loc_11FBC: + while (bPause) + { + ClearAllKeys(); + if (WaitAnyKey(-1) != sc_Pause) + { + bPause = kFalse; + } + } + } + + // loc_11FEE: + tclocks += moveframes * 4; + while (moveframes && levelnew < 0) + { + GameMove(); + // if (nNetTime > 0) + // { + // nNetTime--; + // + // if (!nNetTime) { + // nFreeze = 3; + // } + // } + // else if (nNetTime == 0) + // { + // if (BUTTON(gamefunc_Open)) + // { + // CONTROL_ClearButton(gamefunc_Open); + // goto MENU2; + // } + // } + } + + bInMove = kFalse; + + // END YELLOW SECTION + + // loc_12149: + if (bInDemo) + { + while (tclocks > totalclock) { HandleAsync(); } + tclocks = totalclock; + } + + if (G_FPSLimit()) + { + GameDisplay(); } } else { - // loc_11FBC: - while (bPause) + bInMove = kTrue; + if (!bPause && totalclock >= tclocks + 4) { - ClearAllKeys(); - if (WaitAnyKey(-1) != sc_Pause) + GetLocalInput(); + + sPlayerInput[nLocalPlayer].xVel = lPlayerXVel; + sPlayerInput[nLocalPlayer].yVel = lPlayerYVel; + sPlayerInput[nLocalPlayer].buttons = lLocalButtons | lLocalCodes; + sPlayerInput[nLocalPlayer].nAngle = nPlayerDAng; + sPlayerInput[nLocalPlayer].nTarget = besttarget; + + Ra[nLocalPlayer].nTarget = besttarget; + + lLocalCodes = 0; + nPlayerDAng = 0; + + sPlayerInput[nLocalPlayer].horizon = nVertPan[nLocalPlayer]; + + do { - bPause = kFalse; - } + timerUpdate(); + tclocks += 4; + GameMove(); + timerUpdate(); + } while (levelnew < 0 && totalclock >= tclocks + 4); + } + bInMove = kFalse; + + faketimerhandler(); + + if (G_FPSLimit()) + { + GameDisplay(); } } - - // loc_11FEE: - tclocks += moveframes * 4; - while (moveframes&& levelnew < 0) - { - FixPalette(); - - if (levelnum == kMap20) - { - if (lCountDown <= 0) - { - for (int i = 0; i < nTotalPlayers; i++) { - nPlayerLives[i] = 0; - } - - DoFailedFinalScene(); - levelnew = 100; - - break; - } - // Pink section - lCountDown--; - DrawClock(); - - if (nRedTicks) - { - nRedTicks--; - - if (nRedTicks <= 0) { - DoRedAlert(0); - } - - nAlarmTicks--; - nButtonColor--; - - if (nAlarmTicks <= 0) { - DoRedAlert(1); - } - } - } - - // YELLOW SECTION - MoveThings(); - - if (totalvel[nLocalPlayer] == 0) - { - bobangle = 0; - } - else - { - bobangle += 56; - bobangle &= kAngleMask; - } - - // loc_120E9: - totalmoves++; - moveframes--; - - if (nNetTime > 0) - { - nNetTime--; - - if (!nNetTime) { - nFreeze = 3; - } - } - else if (nNetTime == 0) - { - if (BUTTON(gamefunc_Open)) - { - CONTROL_ClearButton(gamefunc_Open); - goto MENU2; - } - } - } - // END YELLOW SECTION - - // loc_12149: - if (bInDemo) - { - while (tclocks > (int)totalclock) { HandleAsync(); } - tclocks = (int)totalclock; - } - - bInMove = kFalse; - if (!bInDemo) { if (BUTTON(gamefunc_Escape)) { CONTROL_ClearButton(gamefunc_Escape); -MENU2: +// MENU2: CONTROL_BindsEnabled = 0; nMenu = menu_Menu(1); diff --git a/source/exhumed/src/exhumed.h b/source/exhumed/src/exhumed.h index 86ade5360..083fc76a2 100644 --- a/source/exhumed/src/exhumed.h +++ b/source/exhumed/src/exhumed.h @@ -188,6 +188,11 @@ extern int bVanilla; extern int mouseaiming, aimmode, mouseflip; extern int runkey_mode, auto_run; +extern int32_t r_maxfps; +extern int32_t r_maxfpsoffset; +extern double g_frameDelay; + +static inline double calcFrameDelay(int const maxFPS) { return maxFPS > 0 ? (timerGetFreqU64()/(double)maxFPS) : 0.0; } enum { kPalNormal = 0, diff --git a/source/exhumed/src/osdcmds.cpp b/source/exhumed/src/osdcmds.cpp index f075b3016..648676dc9 100644 --- a/source/exhumed/src/osdcmds.cpp +++ b/source/exhumed/src/osdcmds.cpp @@ -404,12 +404,12 @@ static int osdcmd_cvar_set_game(osdcmdptr_t parm) { ud.statusbarmode = (ud.screen_size < 8); G_UpdateScreenArea(); - } + }*/ else if (!Bstrcasecmp(parm->name, "r_maxfps") || !Bstrcasecmp(parm->name, "r_maxfpsoffset")) { if (r_maxfps != 0) r_maxfps = clamp(r_maxfps, 30, 1000); g_frameDelay = calcFrameDelay(r_maxfps + r_maxfpsoffset); - } + }/* else if (!Bstrcasecmp(parm->name, "r_ambientlight")) { if (r_ambientlight == 0) @@ -624,8 +624,8 @@ int32_t registerosdcommands(void) { "r_precache", "enable/disable the pre-level caching routine", (void *)&useprecache, CVAR_BOOL, 0, 1 }, // { "r_ambientlight", "sets the global map light level",(void *)&r_ambientlight, CVAR_FLOAT|CVAR_FUNCPTR, 0, 10 }, - //{ "r_maxfps", "limit the frame rate",(void *)&r_maxfps, CVAR_INT|CVAR_FUNCPTR, 0, 1000 }, - //{ "r_maxfpsoffset", "menu-controlled offset for r_maxfps",(void *)&r_maxfpsoffset, CVAR_INT|CVAR_FUNCPTR, -10, 10 }, + { "r_maxfps", "limit the frame rate",(void *)&r_maxfps, CVAR_INT|CVAR_FUNCPTR, 0, 1000 }, + { "r_maxfpsoffset", "menu-controlled offset for r_maxfps",(void *)&r_maxfpsoffset, CVAR_INT|CVAR_FUNCPTR, -10, 10 }, { "sensitivity","changes the mouse sensitivity", (void *)&CONTROL_MouseSensitivity, CVAR_FLOAT|CVAR_FUNCPTR, 0, 25 }, diff --git a/source/exhumed/src/player.cpp b/source/exhumed/src/player.cpp index fa601e9d7..cfb34ce2e 100644 --- a/source/exhumed/src/player.cpp +++ b/source/exhumed/src/player.cpp @@ -677,6 +677,9 @@ void RestartPlayer(short nPlayer) floorspr = -1; } + PlayerList[nPlayer].opos = sprite[nSprite].pos; + PlayerList[nPlayer].q16oangle = PlayerList[nPlayer].q16angle; + nPlayerFloorSprite[nPlayer] = floorspr; sprite[nSprite].cstat = 0x101; @@ -775,7 +778,7 @@ void RestartPlayer(short nPlayer) nYDamage[nPlayer] = 0; nXDamage[nPlayer] = 0; - PlayerList[nPlayer].q16horiz = nVertPan[nPlayer] = F16(92); + PlayerList[nPlayer].q16ohoriz = PlayerList[nPlayer].q16horiz = nVertPan[nPlayer] = F16(92); nDestVertPan[nPlayer] = F16(92); nBreathTimer[nPlayer] = 90; @@ -877,7 +880,7 @@ void StartDeathSeq(int nPlayer, int nVal) StopFiringWeapon(nPlayer); - PlayerList[nPlayer].q16horiz = nVertPan[nPlayer] = F16(92); + PlayerList[nPlayer].q16ohoriz = PlayerList[nPlayer].q16horiz = nVertPan[nPlayer] = F16(92); eyelevel[nPlayer] = -14080; nPlayerInvisible[nPlayer] = 0; dVertPan[nPlayer] = 15; @@ -1044,6 +1047,10 @@ void FuncPlayer(int pA, int nDamage, int nRun) short nSprite2; + PlayerList[nPlayer].opos = sprite[nPlayerSprite].pos; + PlayerList[nPlayer].q16oangle = PlayerList[nPlayer].q16angle; + PlayerList[nPlayer].q16ohoriz = PlayerList[nPlayer].q16horiz; + switch (nMessage) { case 0x90000: diff --git a/source/exhumed/src/player.h b/source/exhumed/src/player.h index c6a36b451..6eb0f8788 100644 --- a/source/exhumed/src/player.h +++ b/source/exhumed/src/player.h @@ -53,8 +53,9 @@ struct Player short field_3C; short nRun; - fix16_t q16angle; - fix16_t q16horiz; + fix16_t q16angle, q16oangle; + fix16_t q16horiz, q16ohoriz; + vec3_t opos; }; extern short PlayerCount; diff --git a/source/exhumed/src/view.cpp b/source/exhumed/src/view.cpp index 2bd1d031a..082ef8d1f 100644 --- a/source/exhumed/src/view.cpp +++ b/source/exhumed/src/view.cpp @@ -59,6 +59,65 @@ short enemy; short nEnemyPal = 0; +#define MAXINTERPOLATIONS MAXSPRITES +int32_t g_interpolationCnt; +int32_t oldipos[MAXINTERPOLATIONS]; +int32_t* curipos[MAXINTERPOLATIONS]; +int32_t bakipos[MAXINTERPOLATIONS]; + +int viewSetInterpolation(int32_t *const posptr) +{ + if (g_interpolationCnt >= MAXINTERPOLATIONS) + return 1; + + for (bssize_t i = 0; i < g_interpolationCnt; ++i) + if (curipos[i] == posptr) + return 0; + + curipos[g_interpolationCnt] = posptr; + oldipos[g_interpolationCnt] = *posptr; + g_interpolationCnt++; + return 0; +} + +void viewStopInterpolation(const int32_t * const posptr) +{ + for (bssize_t i = 0; i < g_interpolationCnt; ++i) + if (curipos[i] == posptr) + { + g_interpolationCnt--; + oldipos[i] = oldipos[g_interpolationCnt]; + bakipos[i] = bakipos[g_interpolationCnt]; + curipos[i] = curipos[g_interpolationCnt]; + } +} + +void viewDoInterpolations(int smoothRatio) +{ + int32_t ndelta = 0; + + for (bssize_t i = 0, j = 0; i < g_interpolationCnt; ++i) + { + int32_t const odelta = ndelta; + bakipos[i] = *curipos[i]; + ndelta = (*curipos[i]) - oldipos[i]; + if (odelta != ndelta) + j = mulscale16(ndelta, smoothRatio); + *curipos[i] = oldipos[i] + j; + } +} + +void viewUpdateInterpolations(void) //Stick at beginning of G_DoMoveThings +{ + for (bssize_t i=g_interpolationCnt-1; i>=0; i--) oldipos[i] = *curipos[i]; +} + +void viewRestoreInterpolations(void) //Stick at end of drawscreen +{ + int32_t i=g_interpolationCnt-1; + + for (; i>=0; i--) *curipos[i] = bakipos[i]; +} void InitView() { @@ -271,7 +330,17 @@ void TestLava() { } -void DrawView() +static inline int interpolate16(int a, int b, int smooth) +{ + return a + mulscale16(b - a, smooth); +} + +static inline fix16_t q16angle_interpolate16(fix16_t a, fix16_t b, int smooth) +{ + return a + mulscale16(((b+F16(1024)-a)&0x7FFFFFF)-F16(1024), smooth); +} + +void DrawView(int smoothRatio) { int playerX; int playerY; @@ -306,6 +375,7 @@ void DrawView() zbob = Sin(2 * bobangle) >> 3; int nPlayerSprite = PlayerList[nLocalPlayer].nSprite; + int nPlayerOldCstat = sprite[nPlayerSprite].cstat; if (nSnakeCam >= 0) { @@ -334,11 +404,13 @@ void DrawView() } else { - playerX = sprite[nPlayerSprite].x; - playerY = sprite[nPlayerSprite].y; - playerZ = sprite[nPlayerSprite].z + eyelevel[nLocalPlayer]; + playerX = interpolate16(PlayerList[nLocalPlayer].opos.x, sprite[nPlayerSprite].x, smoothRatio); + playerY = interpolate16(PlayerList[nLocalPlayer].opos.y, sprite[nPlayerSprite].y, smoothRatio); + playerZ = interpolate16(PlayerList[nLocalPlayer].opos.z, sprite[nPlayerSprite].z, smoothRatio) + eyelevel[nLocalPlayer]; nSector = nPlayerViewSect[nLocalPlayer]; - nAngle = PlayerList[nLocalPlayer].q16angle; + nAngle = q16angle_interpolate16(PlayerList[nLocalPlayer].q16oangle, PlayerList[nLocalPlayer].q16angle, smoothRatio); + + sprite[nPlayerSprite].cstat |= CSTAT_SPRITE_INVISIBLE; } nCameraa = nAngle; @@ -356,7 +428,7 @@ void DrawView() int floorZ = sector[sprite[nPlayerSprite].sectnum].floorz; // pan = nVertPan[nLocalPlayer]; - pan = PlayerList[nLocalPlayer].q16horiz; + pan = interpolate16(PlayerList[nLocalPlayer].q16ohoriz, PlayerList[nLocalPlayer].q16horiz, smoothRatio); if (viewz > floorZ) viewz = floorZ; @@ -523,6 +595,8 @@ void DrawView() DrawStatus(); } + sprite[nPlayerSprite].cstat = nPlayerOldCstat; + flash = 0; } diff --git a/source/exhumed/src/view.h b/source/exhumed/src/view.h index d0d8674bf..de007b34b 100644 --- a/source/exhumed/src/view.h +++ b/source/exhumed/src/view.h @@ -14,12 +14,18 @@ extern short bCamera; void InitView(); void SetView1(); void RefreshBackground(); -void DrawView(); +void DrawView(int smoothRatio); void MySetView(int x1, int y1, int x2, int y2); void ResetView(); void NoClip(); void Clip(); +int viewSetInterpolation(int32_t *const posptr); +void viewStopInterpolation(const int32_t * const posptr); +void viewDoInterpolations(int smoothRatio); +void viewUpdateInterpolations(void); +void viewRestoreInterpolations(void); + extern fix16_t nDestVertPan[]; extern short dVertPan[]; extern fix16_t nVertPan[];