This commit is contained in:
Jan Racek
2026-03-24 13:01:47 +01:00
parent 55c1d2c39b
commit e51c06b012
51 changed files with 8111 additions and 491 deletions

View File

@@ -2,6 +2,8 @@
#include <psyqo-lua/lua.hh>
#include <psyqo/soft-math.hh>
#include <psyqo/trigonometry.hh>
#include <psyqo/xprintf.h>
#include "gameobject.hh"
@@ -10,15 +12,27 @@ constexpr const char GAMEOBJECT_SCRIPT[] = R"(
return function(metatable)
local get_position = metatable.get_position
local set_position = metatable.set_position
local get_active = metatable.get_active
local set_active = metatable.set_active
local get_rotationY = metatable.get_rotationY
local set_rotationY = metatable.set_rotationY
metatable.get_position = nil
metatable.set_position = nil
metatable.get_active = nil
metatable.set_active = nil
metatable.get_rotationY = nil
metatable.set_rotationY = nil
function metatable.__index(self, key)
local raw = rawget(self, key)
if raw ~= nil then return raw end
if key == "position" then
return get_position(self.__cpp_ptr)
elseif key == "active" then
return get_active(self.__cpp_ptr)
elseif key == "rotationY" then
return get_rotationY(self.__cpp_ptr)
end
return nil
end
@@ -29,6 +43,10 @@ return function(metatable)
return
elseif key == "active" then
set_active(self.__cpp_ptr, value)
return
elseif key == "rotationY" then
set_rotationY(self.__cpp_ptr, value)
return
end
rawset(self, key, value)
end
@@ -37,17 +55,18 @@ end
// Lua helpers
static constexpr lua_Number kFixedScale = 4096;
static int gameobjectGetPosition(psyqo::Lua L) {
auto go = L.toUserdata<psxsplash::GameObject>(1);
L.newTable();
L.pushNumber(go->position.x.raw());
L.pushNumber(static_cast<lua_Number>(go->position.x.raw()) / kFixedScale);
L.setField(2, "x");
L.pushNumber(go->position.y.raw());
L.pushNumber(static_cast<lua_Number>(go->position.y.raw()) / kFixedScale);
L.setField(2, "y");
L.pushNumber(go->position.z.raw());
L.pushNumber(static_cast<lua_Number>(go->position.z.raw()) / kFixedScale);
L.setField(2, "z");
return 1;
@@ -59,18 +78,15 @@ static int gameobjectSetPosition(psyqo::Lua L) {
auto go = L.toUserdata<psxsplash::GameObject>(1);
L.getField(2, "x");
psyqo::FixedPoint<> x(L.toNumber(3), psyqo::FixedPoint<>::RAW);
go->position.x = x;
go->position.x = psyqo::FixedPoint<>(static_cast<int32_t>(L.toNumber(3) * kFixedScale), psyqo::FixedPoint<>::RAW);
L.pop();
L.getField(2, "y");
psyqo::FixedPoint<> y(L.toNumber(3), psyqo::FixedPoint<>::RAW);
go->position.y = y;
go->position.y = psyqo::FixedPoint<>(static_cast<int32_t>(L.toNumber(3) * kFixedScale), psyqo::FixedPoint<>::RAW);
L.pop();
L.getField(2, "z");
psyqo::FixedPoint<> z(L.toNumber(3), psyqo::FixedPoint<>::RAW);
go->position.z = z;
go->position.z = psyqo::FixedPoint<>(static_cast<int32_t>(L.toNumber(3) * kFixedScale), psyqo::FixedPoint<>::RAW);
L.pop();
return 0;
@@ -89,6 +105,59 @@ static int gamobjectSetActive(psyqo::Lua L) {
return 0;
}
// Angle constants: psyqo::Angle is FixedPoint<10>, so 1.0_pi = raw 1024
static constexpr lua_Number kAngleScale = 1024;
static psyqo::Trig<> s_trig;
// Fast integer atan2 approximation → psyqo::Angle (pi-fraction units)
// Uses linear approximation in first octant then folds to full circle.
// Max error ~4° (acceptable for PS1 game objects).
static psyqo::Angle fastAtan2(int32_t sinVal, int32_t cosVal) {
psyqo::Angle result;
if (cosVal == 0 && sinVal == 0) { result.value = 0; return result; }
int32_t abs_s = sinVal < 0 ? -sinVal : sinVal;
int32_t abs_c = cosVal < 0 ? -cosVal : cosVal;
int32_t minV = abs_s < abs_c ? abs_s : abs_c;
int32_t maxV = abs_s > abs_c ? abs_s : abs_c;
// Compute angle in first octant [0, π/4 = 256 Angle units]
// angle = (minV/maxV) * 256, using only 32-bit math.
// Max minV for normalized sin/cos ≈ 4096, so minV * 256 ≈ 1M — fits int32.
int32_t angle = (minV * 256) / maxV;
// Past 45°: use complement
if (abs_s > abs_c) angle = 512 - angle; // π/2 - angle
// Quadrant 2/3: cos < 0
if (cosVal < 0) angle = 1024 - angle; // π - angle
// Quadrant 3/4: sin < 0
if (sinVal < 0) angle = -angle;
result.value = angle;
return result;
}
static int gameobjectGetRotationY(psyqo::Lua L) {
auto go = L.toUserdata<psxsplash::GameObject>(1);
// Y rotation matrix: vs[0].x = cos(θ), vs[0].z = sin(θ)
int32_t sinRaw = go->rotation.vs[0].z.raw();
int32_t cosRaw = go->rotation.vs[0].x.raw();
psyqo::Angle angle = fastAtan2(sinRaw, cosRaw);
// Return in pi-units: 0.5 = π/2 = 90°
L.pushNumber(static_cast<lua_Number>(angle.value) / kAngleScale);
return 1;
}
static int gameobjectSetRotationY(psyqo::Lua L) {
auto go = L.toUserdata<psxsplash::GameObject>(1);
lua_Number piUnits = L.toNumber(2);
psyqo::Angle angle;
angle.value = static_cast<int32_t>(piUnits * kAngleScale);
go->rotation = psyqo::SoftMath::generateRotationMatrix33(angle, psyqo::SoftMath::Axis::Y, s_trig);
return 0;
}
void psxsplash::Lua::Init() {
auto L = m_state;
// Load and run the game objects script
@@ -109,11 +178,17 @@ void psxsplash::Lua::Init() {
L.push(gamobjectSetActive);
L.setField(-2, "set_active");
L.push(gameobjectGetRotationY);
L.setField(-2, "get_rotationY");
L.push(gameobjectSetRotationY);
L.setField(-2, "set_rotationY");
L.copy(-1);
m_metatableReference = L.ref();
if (L.pcall(1, 0) == 0) {
printf("Lua script 'gameObjects' executed successfully");
// success
} else {
printf("Error registering Lua script: %s\n", L.optString(-1, "Unknown error"));
L.clearStack();
@@ -136,6 +211,24 @@ void psxsplash::Lua::Init() {
m_luascriptsReference = L.ref();
}
void psxsplash::Lua::Shutdown() {
// Close the Lua VM if it's still open.
// Safe to call multiple times or on an already-closed VM.
if (m_state.getState()) {
m_state.close();
}
m_metatableReference = LUA_NOREF;
m_luascriptsReference = LUA_NOREF;
m_luaSceneScriptsReference = LUA_NOREF;
}
void psxsplash::Lua::Reset() {
// Nuclear reset: destroy the entire Lua VM and create a fresh one.
Shutdown();
m_state = psyqo::Lua(); // fresh state (luaL_newstate + openlibs)
Init();
}
void psxsplash::Lua::LoadLuaFile(const char* code, size_t len, int index) {
auto L = m_state;
char filename[32];
@@ -143,18 +236,31 @@ void psxsplash::Lua::LoadLuaFile(const char* code, size_t len, int index) {
if (L.loadBuffer(code, len, filename) != LUA_OK) {
printf("Lua error: %s\n", L.toString(-1));
L.pop();
return;
}
// (1) script func
L.rawGetI(LUA_REGISTRYINDEX, m_luascriptsReference);
// (1) script func (2) scripts table
L.newTable();
// (1) script func (2) scripts table (3) {}
// (1) script func (2) scripts table (3) env {}
// Give the environment a metatable that falls back to _G
// so scripts can see Entity, Debug, Input, etc.
L.newTable();
// (1) script func (2) scripts table (3) env {} (4) mt {}
L.pushGlobalTable();
// (1) script func (2) scripts table (3) env {} (4) mt {} (5) _G
L.setField(-2, "__index");
// (1) script func (2) scripts table (3) env {} (4) mt { __index = _G }
L.setMetatable(-2);
// (1) script func (2) scripts table (3) env { mt }
L.pushNumber(index);
// (1) script func (2) scripts table (3) {} (4) index
// (1) script func (2) scripts table (3) env (4) index
L.copy(-2);
// (1) script func (2) scripts table (3) {} (4) index (5) {}
// (1) script func (2) scripts table (3) env (4) index (5) env
L.setTable(-4);
// (1) script func (2) scripts table (3) {}
// (1) script func (2) scripts table (3) env
lua_setupvalue(L.getState(), -3, 1);
// (1) script func (2) scripts table
L.pop();
@@ -177,9 +283,15 @@ void psxsplash::Lua::RegisterSceneScripts(int index) {
L.rawGetI(LUA_REGISTRYINDEX, m_luascriptsReference);
// (1) {} (2) scripts table
L.pushNumber(index);
// (1) {} (2) script environments table (2) index
// (1) {} (2) script environments table (3) index
L.getTable(-2);
// (1) {} (2) script environments table (3) script environment table for the scene
if (!L.isTable(-1)) {
// Scene Lua file index is invalid or script not loaded
printf("Warning: scene Lua file index %d not found\n", index);
L.pop(3);
return;
}
onSceneCreationStartFunctionWrapper.resolveGlobal(L);
onSceneCreationEndFunctionWrapper.resolveGlobal(L);
L.pop(3);
@@ -218,29 +330,112 @@ void psxsplash::Lua::RegisterGameObject(GameObject* go) {
// (1) {} (2) go + 1 (3) {}
L.rawSet(LUA_REGISTRYINDEX);
// (1) {}
// Initialize event mask for this object
uint32_t eventMask = EVENT_NONE;
if (go->luaFileIndex != -1) {
L.rawGetI(LUA_REGISTRYINDEX, m_luascriptsReference);
// (1) {} (2) script environments table
L.rawGetI(-1, go->luaFileIndex);
// (1) {} (2) script environments table (3) script environment table for this object
onCollisionMethodWrapper.resolveGlobal(L);
onInteractMethodWrapper.resolveGlobal(L);
// Guard: if the script file failed to load (e.g. compilation error),
// the environment will be nil — skip event resolution.
if (!L.isTable(-1)) {
L.pop(2);
} else {
// Resolve each event and build the bitmask
// Only events that exist in the script get their bit set
if (onCreateMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_CREATE;
if (onCollisionMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_COLLISION;
if (onInteractMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_INTERACT;
if (onTriggerEnterMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_TRIGGER_ENTER;
if (onTriggerStayMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_TRIGGER_STAY;
if (onTriggerExitMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_TRIGGER_EXIT;
if (onUpdateMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_UPDATE;
if (onDestroyMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_DESTROY;
if (onEnableMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_ENABLE;
if (onDisableMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_DISABLE;
if (onButtonPressMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_BUTTON_PRESS;
if (onButtonReleaseMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_BUTTON_RELEASE;
L.pop(2);
// (1) {}
}
}
// Store the event mask directly in the GameObject
go->eventMask = eventMask;
L.pop();
// empty stack
printf("GameObject registered in Lua registry: %p\n", ptr);
// Fire onCreate event if this object handles it
if (eventMask & EVENT_ON_CREATE) {
onCreateMethodWrapper.callMethod(*this, go);
}
}
void psxsplash::Lua::OnCollision(GameObject* self, GameObject* other) {
if (!hasEvent(self, EVENT_ON_COLLISION)) return;
onCollisionMethodWrapper.callMethod(*this, self, other);
}
void psxsplash::Lua::OnInteract(GameObject* self) {
if (!hasEvent(self, EVENT_ON_INTERACT)) return;
onInteractMethodWrapper.callMethod(*this, self);
}
void psxsplash::Lua::OnTriggerEnter(GameObject* trigger, GameObject* other) {
if (!hasEvent(trigger, EVENT_ON_TRIGGER_ENTER)) return;
onTriggerEnterMethodWrapper.callMethod(*this, trigger, other);
}
void psxsplash::Lua::OnTriggerStay(GameObject* trigger, GameObject* other) {
if (!hasEvent(trigger, EVENT_ON_TRIGGER_STAY)) return;
onTriggerStayMethodWrapper.callMethod(*this, trigger, other);
}
void psxsplash::Lua::OnTriggerExit(GameObject* trigger, GameObject* other) {
if (!hasEvent(trigger, EVENT_ON_TRIGGER_EXIT)) return;
onTriggerExitMethodWrapper.callMethod(*this, trigger, other);
}
void psxsplash::Lua::OnDestroy(GameObject* go) {
if (!hasEvent(go, EVENT_ON_DESTROY)) return;
onDestroyMethodWrapper.callMethod(*this, go);
// Clear the event mask when object is destroyed
go->eventMask = EVENT_NONE;
}
void psxsplash::Lua::OnEnable(GameObject* go) {
if (!hasEvent(go, EVENT_ON_ENABLE)) return;
onEnableMethodWrapper.callMethod(*this, go);
}
void psxsplash::Lua::OnDisable(GameObject* go) {
if (!hasEvent(go, EVENT_ON_DISABLE)) return;
onDisableMethodWrapper.callMethod(*this, go);
}
void psxsplash::Lua::OnButtonPress(GameObject* go, int button) {
if (!hasEvent(go, EVENT_ON_BUTTON_PRESS)) return;
onButtonPressMethodWrapper.callMethod(*this, go, button);
}
void psxsplash::Lua::OnButtonRelease(GameObject* go, int button) {
if (!hasEvent(go, EVENT_ON_BUTTON_RELEASE)) return;
onButtonReleaseMethodWrapper.callMethod(*this, go, button);
}
void psxsplash::Lua::OnUpdate(GameObject* go, int deltaFrames) {
if (!hasEvent(go, EVENT_ON_UPDATE)) return;
onUpdateMethodWrapper.callMethod(*this, go, deltaFrames);
}
void psxsplash::Lua::PushGameObject(GameObject* go) {
auto L = m_state;
L.push(go);