This commit is contained in:
Jan Racek
2026-03-27 21:29:01 +01:00
parent 85eb7e59d9
commit bfab154547
42 changed files with 118 additions and 1013 deletions

View File

@@ -3,18 +3,11 @@
#include <psyqo/fixed-point.hh>
#include <psyqo/soft-math.hh>
#include <psyqo/trigonometry.hh>
#include "streq.hh"
#include "uisystem.hh"
namespace psxsplash {
// Bare-metal string compare (avoids linking libc)
static bool cs_streq(const char* a, const char* b) {
while (*a && *b) {
if (*a++ != *b++) return false;
}
return *a == *b;
}
void CutscenePlayer::init(Cutscene* cutscenes, int count, Camera* camera, AudioManager* audio,
UISystem* uiSystem) {
m_cutscenes = cutscenes;
@@ -31,7 +24,7 @@ bool CutscenePlayer::play(const char* name) {
if (!name || !m_cutscenes) return false;
for (int i = 0; i < m_count; i++) {
if (m_cutscenes[i].name && cs_streq(m_cutscenes[i].name, name)) {
if (m_cutscenes[i].name && streq(m_cutscenes[i].name, name)) {
m_active = &m_cutscenes[i];
m_frame = 0;
m_nextAudio = 0;
@@ -64,7 +57,7 @@ bool CutscenePlayer::play(const char* name) {
}
break;
case TrackType::ObjectRotationY:
// Can't easily recover angle from matrix — default to 0
// TODO: I added this. Then realized that it only stores rotation matrix. That sucks. To anyone who finds this Pull requests are open :P
break;
case TrackType::ObjectActive:
if (track.target) {
@@ -119,12 +112,10 @@ void CutscenePlayer::stop() {
void CutscenePlayer::tick() {
if (!m_active) return;
// Apply all tracks at the current frame
for (uint8_t i = 0; i < m_active->trackCount; i++) {
applyTrack(m_active->tracks[i]);
}
// Fire audio events whose frame has been reached
while (m_nextAudio < m_active->audioEventCount) {
CutsceneAudioEvent& evt = m_active->audioEvents[m_nextAudio];
if (evt.frame <= m_frame) {
@@ -137,39 +128,31 @@ void CutscenePlayer::tick() {
}
}
// Advance frame
m_frame++;
if (m_frame > m_active->totalFrames) {
m_active = nullptr; // Cutscene finished
}
}
// Apply easing curve to fixed-point t ∈ [0, 4096].
static int32_t applyCurve(int32_t t, InterpMode mode) {
switch (mode) {
default:
case InterpMode::Linear:
return t;
case InterpMode::Step:
return 0; // snaps to 'a' value until next keyframe
return 0;
case InterpMode::EaseIn:
// t² / 4096
return (int32_t)((int64_t)t * t >> 12);
case InterpMode::EaseOut:
// 1 (1t)² = t*(2 t) / 4096
return (int32_t)(((int64_t)t * (8192 - t)) >> 12);
case InterpMode::EaseInOut: {
// Smoothstep: 3t² 2t³ (all in 12-bit fixed point)
int64_t t2 = (int64_t)t * t; // 24-bit
int64_t t3 = t2 * t; // 36-bit
int64_t t2 = (int64_t)t * t;
int64_t t3 = t2 * t;
return (int32_t)((3 * t2 - 2 * (t3 >> 12)) >> 12);
}
}
}
// Common helper: find surrounding keyframes and compute fixed-point t (0..4096).
// Returns false if result was clamped (no interpolation needed, out[] already set).
// When true, t already has the easing curve applied based on the *destination* keyframe's InterpMode.
static bool findKfPair(CutsceneKeyframe* kf, uint8_t count, uint16_t frame,
uint8_t& a, uint8_t& b, int32_t& t, int16_t out[3]) {
if (count == 0) {
@@ -208,7 +191,6 @@ void CutscenePlayer::lerpKeyframes(CutsceneKeyframe* kf, uint8_t count, const in
uint8_t a, b;
int32_t t;
if (!findKfPair(kf, count, m_frame, a, b, t, out)) {
// If clamped to first keyframe and frame < first kf frame, blend from initial
if (count > 0 && kf[0].getFrame() > 0 && m_frame < kf[0].getFrame()) {
uint16_t span = kf[0].getFrame();
uint32_t num = (uint32_t)m_frame << 12;
@@ -228,8 +210,6 @@ void CutscenePlayer::lerpKeyframes(CutsceneKeyframe* kf, uint8_t count, const in
}
}
// Shortest-path angle interpolation.
// Angles are in psyqo units where 2048 = full circle (2π).
static constexpr int32_t ANGLE_FULL_CIRCLE = 2048;
static constexpr int32_t ANGLE_HALF_CIRCLE = 1024;
@@ -237,7 +217,6 @@ void CutscenePlayer::lerpAngles(CutsceneKeyframe* kf, uint8_t count, const int16
uint8_t a, b;
int32_t t;
if (!findKfPair(kf, count, m_frame, a, b, t, out)) {
// If clamped to first keyframe and frame < first kf frame, blend from initial
if (count > 0 && kf[0].getFrame() > 0 && m_frame < kf[0].getFrame()) {
uint16_t span = kf[0].getFrame();
uint32_t num = (uint32_t)m_frame << 12;
@@ -257,9 +236,7 @@ void CutscenePlayer::lerpAngles(CutsceneKeyframe* kf, uint8_t count, const int16
for (int i = 0; i < 3; i++) {
int32_t from = (int32_t)kf[a].values[i];
int32_t to = (int32_t)kf[b].values[i];
// Shortest-path: wrap delta into [-1024, +1024)
int32_t delta = to - from;
// Modulo into [-2048, +2048) then clamp to half-circle
delta = ((delta + ANGLE_HALF_CIRCLE) % ANGLE_FULL_CIRCLE + ANGLE_FULL_CIRCLE) % ANGLE_FULL_CIRCLE - ANGLE_HALF_CIRCLE;
out[i] = (int16_t)(from + ((delta * t) >> 12));
}
@@ -314,10 +291,8 @@ void CutscenePlayer::applyTrack(CutsceneTrack& track) {
case TrackType::ObjectActive: {
if (!track.target) return;
// Step interpolation: find the last keyframe at or before m_frame
CutsceneKeyframe* kf = track.keyframes;
uint8_t count = track.keyframeCount;
// Use initial state if we're before the first keyframe
int16_t activeVal = (count > 0 && m_frame < kf[0].getFrame())
? track.initialValues[0]
: kf[0].values[0];