Add round end camera system

- Spins and zooms around a center point
- Freezes the level while spinning
- Pans over to follow and object after spinning ends
- Timing and speed completely customizable
This commit is contained in:
James R 2024-01-20 21:54:11 -08:00
parent 446f5d23f3
commit 7d6239e06c
8 changed files with 310 additions and 4 deletions

View file

@ -157,6 +157,7 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32
k_dialogue.cpp
k_tally.cpp
k_bans.cpp
k_endcam.cpp
music.cpp
music_manager.cpp
)

197
src/k_endcam.cpp Normal file
View file

@ -0,0 +1,197 @@
// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2024 by James Robert Roman
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#include <algorithm>
#include "archive_wrapper.hpp"
#include "byteptr.h"
#include "doomdef.h"
#include "doomtype.h"
#include "g_game.h"
#include "k_endcam.h"
#include "m_easing.h"
#include "p_local.h"
#include "p_mobj.h"
#include "p_saveg.h"
#include "p_tick.h"
#include "r_fps.h"
#include "r_main.h"
endcam_t g_endcam;
namespace
{
fixed_t interval(tic_t t, tic_t d)
{
return (std::min(t, d) * FRACUNIT) / std::max(d, 1u);
}
fixed_t interval(tic_t t, tic_t s, tic_t d)
{
return interval(std::max(t, s) - s, d);
}
INT32 lerp(fixed_t f, INT32 a, INT32 b)
{
return a + FixedMul(f, b - a);
}
struct Camera : camera_t
{
void pos(const vector3_t& p)
{
x = p.x;
y = p.y;
z = p.z;
subsector = R_PointInSubsector(x, y);
}
};
struct EndCam : endcam_t
{
tic_t Time() const { return leveltime - begin; }
bool Freezing() const { return Time() <= swirlDuration; }
void GC()
{
if (P_MobjWasRemoved(panMobj))
{
P_SetTarget(&panMobj, nullptr);
}
}
void Start()
{
active = true;
begin = leveltime;
// Reset all viewpoints
for (int i = 0; i < MAXSPLITSCREENPLAYERS; ++i)
{
Move(static_cast<Camera&>(camera[i]));
}
R_ResetViewInterpolation(0);
}
void Move(Camera& cam)
{
tic_t t = Time();
fixed_t pan = Easing_OutQuint(interval(t, swirlDuration, panDuration), FRACUNIT, 0);
auto aim = [&](vector3_t& p, vector3_t& q)
{
cam.aiming = lerp(pan, cam.aiming, R_PointToAngle2(0, p.z, FixedHypot(q.x - p.x, q.y - p.y), q.z));
cam.pos(p);
};
if (t <= swirlDuration)
{
fixed_t swirl = interval(t, swirlDuration);
angle_t ang = FixedAngle(swirl < FRACUNIT/2 ?
Easing_InOutQuint(swirl, startAngle, endAngle) :
Easing_InOutQuad(swirl, startAngle, endAngle));
fixed_t hDist = Easing_OutQuad(swirl, startRadius.x, endRadius.x);
vector3_t p = {
origin.x - FixedMul(FCOS(ang), hDist),
origin.y - FixedMul(FSIN(ang), hDist),
origin.z + Easing_OutQuad(swirl, startRadius.y, endRadius.y)
};
aim(p, origin);
cam.angle = ang;
}
else if (!P_MobjWasRemoved(panMobj))
{
vector3_t q = {panMobj->x, panMobj->y, P_GetMobjHead(panMobj)};
vector3_t p = {cam.x, cam.y, cam.z};
Follow(FixedMul(pan, panSpeed), p, q); // modifies p
aim(p, q);
cam.angle = lerp(
Easing_Linear(pan, FRACUNIT/4, FRACUNIT),
cam.angle,
R_PointToAngle2(p.x, p.y, q.x, q.y)
);
}
}
template <typename T>
void Archive(T&& ar)
{
static_assert(srb2::is_archive_wrapper_v<T>);
#define X(T, var) SRB2_ARCHIVE_WRAPPER_CALL(ar, T, var)
X(vector3_t, origin);
X(vector2_t, startRadius);
X(vector2_t, endRadius);
X(tic_t, swirlDuration);
X(fixed_t, startAngle);
X(fixed_t, endAngle);
// panMobj is handled in p_saveg.c
X(tic_t, panDuration);
X(fixed_t, panSpeed);
X(bool, active);
X(tic_t, begin);
#undef X
}
private:
void Follow(fixed_t f, vector3_t& p, vector3_t q) const
{
FV3_Sub(&q, &p);
q.x = FixedMul(q.x, f);
q.y = FixedMul(q.y, f);
q.z = FixedMul(q.z, f);
FV3_Add(&p, &q);
}
};
EndCam& endcam_cast()
{
return static_cast<EndCam&>(g_endcam);
}
}; // namespace
void K_CommitEndCamera(void)
{
endcam_cast().Start();
}
void K_MoveEndCamera(camera_t *thiscam)
{
endcam_cast().Move(static_cast<Camera&>(*thiscam));
}
void K_EndCameraGC(void)
{
endcam_cast().GC();
}
boolean K_EndCameraIsFreezing(void)
{
return endcam_cast().Freezing();
}
void K_SaveEndCamera(savebuffer_t *save)
{
endcam_cast().Archive(srb2::ArchiveWrapper(save));
}
void K_LoadEndCamera(savebuffer_t *save)
{
endcam_cast().Archive(srb2::UnArchiveWrapper(save));
}

74
src/k_endcam.h Normal file
View file

@ -0,0 +1,74 @@
// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2024 by James Robert Roman
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#ifndef k_endcam_h
#define k_endcam_h
#include "typedef.h"
#include "doomtype.h"
#include "m_fixed.h"
#include "tables.h"
#ifdef __cplusplus
extern "C" {
#endif
struct endcam_t
{
//
// Configurable properties
//
vector3_t origin; // center point
vector2_t startRadius; // X = horizontal, Y = vertical
vector2_t endRadius;
tic_t swirlDuration;
fixed_t startAngle; // 180*FRACUNIT, NOT ANGLE_180
fixed_t endAngle;
// 1) Camera pans vertically to keep this object centered
// 2) After swirling ends, pan horizontally too
mobj_t *panMobj;
tic_t panDuration; // dropoff after swirling ends
fixed_t panSpeed; // 0-FRACUNIT
/// ...
// You should not set these yourself.
// Use K_CommitEndCamera.
boolean active;
tic_t begin; // leveltime
};
extern endcam_t g_endcam;
// Sets endcam_t.active and endcam_t.begin.
//
// VERY IMPORTANT:
//
// Set the OTHER fields in endcam_t BEFORE calling this
// function, so the camera can cut away cleanly.
void K_CommitEndCamera(void);
/// ...
// Low-level functions
void K_MoveEndCamera(camera_t *thiscam);
void K_EndCameraGC(void);
boolean K_EndCameraIsFreezing(void);
void K_SaveEndCamera(savebuffer_t *save);
void K_LoadEndCamera(savebuffer_t *save);
#ifdef __cplusplus
} // extern "C"
#endif
#endif/*k_endcam_h*/

View file

@ -47,6 +47,7 @@
#include "g_party.h"
#include "k_vote.h"
#include "k_zvote.h"
#include "k_endcam.h"
#include <tracy/tracy/TracyC.h>
@ -5745,6 +5746,12 @@ static void P_RelinkPointers(void)
P_LoadMobjPointers(RelinkMobjVoid);
if (g_endcam.panMobj)
{
if (!RelinkMobj(&g_endcam.panMobj))
CONS_Debug(DBG_GAMELOGIC, "g_endcam.panMobj not found\n");
}
// use info field (value = oldposition) to relink mobjs
for (currentthinker = thlist[THINK_MOBJ].next; currentthinker != &thlist[THINK_MOBJ];
currentthinker = currentthinker->next)
@ -6868,6 +6875,9 @@ void P_SaveNetGame(savebuffer_t *save, boolean resending)
}
}
K_SaveEndCamera(save);
WriteMobjPointer(g_endcam.panMobj);
P_NetArchivePlayers(save);
P_NetArchiveParties(save);
P_NetArchiveRoundQueue(save);
@ -6933,6 +6943,9 @@ boolean P_LoadNetGame(savebuffer_t *save, boolean reloading)
if (!P_NetUnArchiveMisc(save, reloading))
return false;
K_LoadEndCamera(save);
ReadMobjPointer(&g_endcam.panMobj);
P_NetUnArchivePlayers(save);
P_NetUnArchiveParties(save);
P_NetUnArchiveRoundQueue(save);

View file

@ -115,6 +115,7 @@
#include "music.h"
#include "k_dialogue.h"
#include "k_hud.h" // K_ClearPersistentMessages
#include "k_endcam.h"
// Replay names have time
#if !defined (UNDER_CE)
@ -7696,6 +7697,8 @@ static void P_InitLevelSettings(void)
K_ResetSpecialStage();
K_ResetBossInfo();
memset(&g_endcam, 0, sizeof g_endcam);
}
#if 0

View file

@ -48,6 +48,7 @@
#include "k_dialogue.h"
#include "m_easing.h"
#include "k_hud.h" // messagetimer
#include "k_endcam.h"
#include "lua_profile.h"
@ -65,12 +66,12 @@ static boolean g_freezeLevel;
boolean P_LevelIsFrozen(void)
{
return (g_freezeLevel || g_freezeCheat);
return (g_freezeLevel || g_freezeCheat || K_EndCameraIsFreezing());
}
boolean P_FreezeCheat(void)
{
return (g_freezeLevel || g_freezeCheat);
return (g_freezeLevel || g_freezeCheat || K_EndCameraIsFreezing());
}
void P_SetFreezeCheat(boolean value)
@ -101,7 +102,7 @@ boolean P_MobjIsFrozen(mobj_t *mobj)
}
}
if (g_freezeLevel == true)
if (g_freezeLevel == true || K_EndCameraIsFreezing())
{
// level totally frozen
return true;
@ -767,6 +768,8 @@ void P_RunChaseCameras(void)
P_MoveChaseCamera(&players[displayplayers[i]], &camera[i], false);
}
}
K_EndCameraGC();
}
static fixed_t P_GetDarkness(tic_t start, tic_t end)

View file

@ -67,6 +67,7 @@
#include "music.h"
#include "k_tally.h"
#include "k_objects.h"
#include "k_endcam.h"
#ifdef HWRENDER
#include "hardware/hw_light.h"
@ -3062,6 +3063,11 @@ void P_ToggleDemoCamera(UINT8 viewnum)
void P_ResetCamera(player_t *player, camera_t *thiscam)
{
if (g_endcam.active)
{
return;
}
tic_t tries = 0;
fixed_t x, y, z;
@ -3155,7 +3161,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
num = 0;
}
if (thiscam->freecam || player->spectator)
if ((thiscam->freecam || player->spectator) && !g_endcam.active)
{
P_DemoCameraMovement(thiscam, num);
return true;
@ -3164,6 +3170,12 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
if (paused || P_AutoPause())
return true;
if (g_endcam.active)
{
K_MoveEndCamera(thiscam);
return true;
}
playerScale = FixedDiv(player->mo->scale, mapobjectscale);
scaleDiff = playerScale - FRACUNIT;

View file

@ -190,6 +190,9 @@ TYPEDEF (botcontroller_t);
// k_brightmap.h
TYPEDEF (brightmapStorage_t);
// k_endcam.h
TYPEDEF (endcam_t);
// k_follower.h
TYPEDEF (follower_t);
TYPEDEF (followercategory_t);