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

@@ -6,6 +6,7 @@
#include "profiler.hh"
#include "renderer.hh"
#include "splashpack.hh"
#include "streq.hh"
#include "luaapi.hh"
#include "loadingscreen.hh"
@@ -163,24 +164,21 @@ void psxsplash::SceneManager::InitializeScene(uint8_t* splashpackData, LoadingSc
m_playerHeight = sceneSetup.playerHeight;
// Load movement parameters from splashpack (v8+)
m_controls.setMoveSpeed(sceneSetup.moveSpeed);
m_controls.setSprintSpeed(sceneSetup.sprintSpeed);
m_playerRadius = (int32_t)sceneSetup.playerRadius.value;
if (m_playerRadius == 0) m_playerRadius = PLAYER_RADIUS; // fallback to default
if (m_playerRadius == 0) m_playerRadius = PLAYER_RADIUS;
m_jumpVelocityRaw = (int32_t)sceneSetup.jumpVelocity.value;
int32_t gravityRaw = (int32_t)sceneSetup.gravity.value;
m_gravityPerFrame = gravityRaw / 30; // Convert per-second² to per-frame velocity change
if (m_gravityPerFrame == 0 && gravityRaw > 0) m_gravityPerFrame = 1; // Ensure nonzero
m_gravityPerFrame = gravityRaw / 30;
if (m_gravityPerFrame == 0 && gravityRaw > 0) m_gravityPerFrame = 1;
m_velocityY = 0;
m_isGrounded = true;
m_lastFrameTime = 0;
m_deltaFrames = 1;
// Initialize collision system
m_collisionSystem.init();
// Register colliders from splashpack data
for (size_t i = 0; i < sceneSetup.colliders.size(); i++) {
SPLASHPACKCollider* collider = sceneSetup.colliders[i];
if (collider == nullptr) continue;
@@ -203,7 +201,6 @@ void psxsplash::SceneManager::InitializeScene(uint8_t* splashpackData, LoadingSc
);
}
// Register trigger boxes from splashpack data
for (size_t i = 0; i < sceneSetup.triggerBoxes.size(); i++) {
SPLASHPACKTriggerBox* tb = sceneSetup.triggerBoxes[i];
if (tb == nullptr) continue;
@@ -219,9 +216,7 @@ void psxsplash::SceneManager::InitializeScene(uint8_t* splashpackData, LoadingSc
m_collisionSystem.registerTriggerBox(bounds, tb->luaFileIndex);
}
// Load Lua files - order is important here. We need
// to load the Lua files before we register the game objects,
// as the game objects may reference Lua files by index.
for (int i = 0; i < m_luaFiles.size(); i++) {
auto luaFile = m_luaFiles[i];
L.LoadLuaFile(luaFile->luaCode, luaFile->length, i);
@@ -233,7 +228,6 @@ void psxsplash::SceneManager::InitializeScene(uint8_t* splashpackData, LoadingSc
L.OnSceneCreationStart();
// Register game objects
for (auto object : m_gameObjects) {
L.RegisterGameObject(object);
}
@@ -255,34 +249,25 @@ void psxsplash::SceneManager::InitializeScene(uint8_t* splashpackData, LoadingSc
void psxsplash::SceneManager::GameTick(psyqo::GPU &gpu) {
LuaAPI::IncrementFrameCount();
// Tick cutscene player (advance frame and apply tracks before rendering)
m_cutscenePlayer.tick();
// Delta-time measurement: count elapsed frames based on gpu timer
// PS1 NTSC frame = ~33333 microseconds (30fps vsync)
{
uint32_t now = gpu.now();
if (m_lastFrameTime != 0) {
uint32_t elapsed = now - m_lastFrameTime;
// 33333us per frame at 30fps. If >50000us, we dropped a frame.
m_deltaFrames = (elapsed > 50000) ? 2 : 1;
if (elapsed > 83000) m_deltaFrames = 3; // Two frames dropped
if (elapsed > 83000) m_deltaFrames = 3;
}
m_lastFrameTime = now;
}
uint32_t renderingStart = gpu.now();
auto& renderer = psxsplash::Renderer::GetInstance();
// Dispatch render path based on scene type.
// Interior scenes (type 1) use room/portal occlusion; exterior scenes use BVH culling.
if (m_sceneType == 1 && m_roomCount > 0 && m_rooms != nullptr) {
// Determine which room the camera is in for portal culling.
// During cutscene playback the camera may be elsewhere from the player,
// so use the actual camera position to find the room.
int camRoom = -1;
if (m_navRegions.isLoaded()) {
if (m_cutscenePlayer.isPlaying()) {
// Camera-driven: look up nav region from camera world position
auto& camPos = m_currentCamera.GetPosition();
uint16_t camRegion = m_navRegions.findRegion(camPos.x.value, camPos.z.value);
if (camRegion != NAV_NO_REGION) {
@@ -290,7 +275,6 @@ void psxsplash::SceneManager::GameTick(psyqo::GPU &gpu) {
if (ri != 0xFF) camRoom = (int)ri;
}
} else if (m_playerNavRegion != NAV_NO_REGION) {
// Normal gameplay: use cached player nav region
uint8_t ri = m_navRegions.getRoomIndex(m_playerNavRegion);
if (ri != 0xFF) camRoom = (int)ri;
}
@@ -308,10 +292,8 @@ void psxsplash::SceneManager::GameTick(psyqo::GPU &gpu) {
psxsplash::debug::Profiler::getInstance().setSectionTime(psxsplash::debug::PROFILER_RENDERING, renderingTime);
#endif
// Collision detection
uint32_t collisionStart = gpu.now();
// Build player AABB from position + radius/height
AABB playerAABB;
{
psyqo::FixedPoint<12> r;
@@ -327,7 +309,6 @@ void psxsplash::SceneManager::GameTick(psyqo::GPU &gpu) {
psyqo::Vec3 pushBack;
int collisionCount = m_collisionSystem.detectCollisions(playerAABB, pushBack);
// Apply push-back to player position
{
psyqo::FixedPoint<12> zero;
if (pushBack.x != zero || pushBack.z != zero) {
@@ -426,9 +407,7 @@ void psxsplash::SceneManager::GameTick(psyqo::GPU &gpu) {
uint32_t navmeshStart = gpu.now();
if (!freecam) {
// Priority: WorldCollision + NavRegions (v7) > NavGrid (v5) > Legacy Navmesh
if (m_worldCollision.isLoaded()) {
// Move-and-slide against world geometry (XZ walls only)
psyqo::Vec3 slid = m_worldCollision.moveAndSlide(
oldPlayerPosition, m_playerPosition, m_playerRadius, 0xFF);
@@ -664,9 +643,6 @@ void psxsplash::SceneManager::loadScene(psyqo::GPU& gpu, int sceneIndex, bool is
char filename[32];
FileLoader::BuildSceneFilename(sceneIndex, filename, sizeof(filename));
// Blank BOTH framebuffers so the user doesn't see the BIOS screen
// or a frozen frame. FastFill ignores the scissor/DrawingArea so we
// can target any VRAM region directly.
psyqo::Prim::FastFill ff(psyqo::Color{.r = 0, .g = 0, .b = 0});
ff.rect = psyqo::Rect{0, 0, 320, 240};
gpu.sendPrimitive(ff);
@@ -674,7 +650,6 @@ void psxsplash::SceneManager::loadScene(psyqo::GPU& gpu, int sceneIndex, bool is
gpu.sendPrimitive(ff);
gpu.pumpCallbacks();
// Try to load a loading screen for the target scene
LoadingScreen loading;
if (s_font) {
if (loading.load(gpu, *s_font, sceneIndex)) {
@@ -742,9 +717,7 @@ void psxsplash::SceneManager::clearScene() {
// (bytecode, strings, tables, registry) in one shot via lua_close.
L.Shutdown();
// 2. Free vector BACKING STORAGE (not just contents).
// clear() only sets size=0 but keeps the allocated capacity.
// swap-with-empty releases the heap blocks so they can be coalesced.
// 2. Clear all vectors to free their heap storage (game objects, Lua files, names, etc)
{ eastl::vector<GameObject*> tmp; tmp.swap(m_gameObjects); }
{ eastl::vector<LuaFile*> tmp; tmp.swap(m_luaFiles); }
{ eastl::vector<const char*> tmp; tmp.swap(m_objectNames); }
@@ -775,12 +748,10 @@ void psxsplash::SceneManager::shrinkBuffer() {
if (m_liveDataSize == 0 || m_currentSceneData == nullptr) return;
uint8_t* oldBase = m_currentSceneData;
// Allocate the shrunk buffer. The volatile cast prevents the compiler
// from assuming operator new never returns NULL (it does with
// -fno-exceptions), which would let it optimize away the null check.
uint8_t* volatile newBaseV = new uint8_t[m_liveDataSize];
uint8_t* newBase = newBaseV;
if (!newBase) return; // Heap exhausted — keep the full buffer
if (!newBase) return;
__builtin_memcpy(newBase, oldBase, m_liveDataSize);
intptr_t delta = reinterpret_cast<intptr_t>(newBase) - reinterpret_cast<intptr_t>(oldBase);
@@ -823,11 +794,6 @@ void psxsplash::SceneManager::shrinkBuffer() {
m_uiSystem.relocate(delta);
// Re-key Lua registry entries for game objects. RegisterGameObject stored
// lightuserdata keys using the OLD buffer addresses. After relocation, the
// game object pointers changed but the registry keys are stale. Without
// this, Entity.Find/FindByIndex return nil (wrong key), and in release
// builds (-Os) the optimizer can turn this into a hard crash.
if (!m_gameObjects.empty()) {
L.RelocateGameObjects(
reinterpret_cast<GameObject**>(m_gameObjects.data()),
@@ -842,16 +808,11 @@ void psxsplash::SceneManager::shrinkBuffer() {
// OBJECT NAME LOOKUP
// ============================================================================
// Inline streq (no libc on bare-metal PS1)
static bool name_eq(const char* a, const char* b) {
while (*a && *b) { if (*a++ != *b++) return false; }
return *a == *b;
}
psxsplash::GameObject* psxsplash::SceneManager::findObjectByName(const char* name) const {
if (!name || m_objectNames.empty()) return nullptr;
for (size_t i = 0; i < m_objectNames.size() && i < m_gameObjects.size(); i++) {
if (m_objectNames[i] && name_eq(m_objectNames[i], name)) {
if (m_objectNames[i] && streq(m_objectNames[i], name)) {
return m_gameObjects[i];
}
}
@@ -861,7 +822,7 @@ psxsplash::GameObject* psxsplash::SceneManager::findObjectByName(const char* nam
int psxsplash::SceneManager::findAudioClipByName(const char* name) const {
if (!name || m_audioClipNames.empty()) return -1;
for (size_t i = 0; i < m_audioClipNames.size(); i++) {
if (m_audioClipNames[i] && name_eq(m_audioClipNames[i], name)) {
if (m_audioClipNames[i] && streq(m_audioClipNames[i], name)) {
return static_cast<int>(i);
}
}