RingRacers/src/k_endcam.cpp

236 lines
4.9 KiB
C++

// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2025 by James Robert Roman
// Copyright (C) 2025 by Kart Krew
//
// 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 "doomstat.h"
#include "doomtype.h"
#include "g_game.h"
#include "k_battle.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<tic_t>(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 active && 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)
);
}
}
void Stop()
{
active = false;
}
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)
{
// Level will be frozen, so make sure the lasers are
// spawned before that happens.
K_SpawnOvertimeBarrier();
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));
}
void K_StartRoundWinCamera(mobj_t *origin, angle_t focusAngle, fixed_t finalRadius, tic_t panDuration, fixed_t panSpeed, tic_t swirlDuration)
{
const fixed_t angF = AngleFixed(focusAngle);
g_endcam.origin = {origin->x, origin->y, P_GetMobjHead(origin)};
g_endcam.startRadius = {2400*mapobjectscale, 800*mapobjectscale};
g_endcam.endRadius = {finalRadius, finalRadius / 2};
g_endcam.swirlDuration = swirlDuration;
g_endcam.startAngle = angF + (90*FRACUNIT);
g_endcam.endAngle = angF + (720*FRACUNIT);
P_SetTarget(&g_endcam.panMobj, origin);
g_endcam.panDuration = panDuration;
g_endcam.panSpeed = panSpeed;
K_CommitEndCamera();
g_darkness.start = leveltime;
g_darkness.end = leveltime + g_endcam.swirlDuration + DARKNESS_FADE_TIME;
}
void K_StopRoundWinCamera(void)
{
endcam_cast().Stop();
}