From e9b944680cf22605d1576009e5673271f037b7fb Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Sun, 4 Jan 2026 00:05:05 +0000 Subject: [PATCH] feat: Enhance ScriptEngine with Shader and Scene Management - Added ShaderManager to handle shader paths loading from Lua. - Integrated SceneManager for managing scene objects within ScriptEngine. - Updated ScriptEngine to utilize ShaderManager and SceneManager. - Refactored audio command handling to use AudioManager. - Improved error handling and Lua integration for shader and scene loading. - Cleaned up code structure and dependencies in script_engine.hpp. --- CMakeLists.txt | 8 + config/gui_runtime.json | 2 +- scripts/gui_demo.lua | 30 +- src/app/sdl3_app.hpp | 3 +- src/script/audio_manager.cpp | 72 +++ src/script/audio_manager.hpp | 42 ++ src/script/gui_manager.cpp | 230 +++++++ src/script/gui_manager.hpp | 31 + src/script/lua_bindings.cpp | 37 +- src/script/lua_helpers.cpp | 31 + src/script/lua_helpers.hpp | 1 + src/script/mesh_loader.cpp | 16 + src/script/mesh_loader.hpp | 1 + src/script/scene_manager.cpp | 194 ++++++ src/script/scene_manager.hpp | 38 ++ src/script/script_engine.cpp | 903 +------------------------- src/script/script_engine.cpp.backup | 954 ++++++++++++++++++++++++++++ src/script/script_engine.hpp | 44 +- src/script/shader_manager.cpp | 74 +++ src/script/shader_manager.hpp | 28 + 20 files changed, 1790 insertions(+), 949 deletions(-) create mode 100644 src/script/audio_manager.cpp create mode 100644 src/script/audio_manager.hpp create mode 100644 src/script/gui_manager.cpp create mode 100644 src/script/gui_manager.hpp create mode 100644 src/script/scene_manager.cpp create mode 100644 src/script/scene_manager.hpp create mode 100644 src/script/script_engine.cpp.backup create mode 100644 src/script/shader_manager.cpp create mode 100644 src/script/shader_manager.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e7fceb..2ce4085 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -114,6 +114,10 @@ if(BUILD_SDL3_APP) src/app/vulkan_api.cpp src/script/script_engine.cpp src/script/physics_bridge.cpp + src/script/scene_manager.cpp + src/script/shader_manager.cpp + src/script/gui_manager.cpp + src/script/audio_manager.cpp src/script/lua_helpers.cpp src/script/lua_bindings.cpp src/script/mesh_loader.cpp @@ -141,6 +145,10 @@ add_executable(script_engine_tests tests/test_cube_script.cpp src/script/script_engine.cpp src/script/physics_bridge.cpp + src/script/scene_manager.cpp + src/script/shader_manager.cpp + src/script/gui_manager.cpp + src/script/audio_manager.cpp src/script/lua_helpers.cpp src/script/lua_bindings.cpp src/script/mesh_loader.cpp diff --git a/config/gui_runtime.json b/config/gui_runtime.json index 4f1cc61..1f595a9 100644 --- a/config/gui_runtime.json +++ b/config/gui_runtime.json @@ -3,7 +3,7 @@ "window_height": 768, "lua_script": "scripts/gui_demo.lua", "scripts_directory": "scripts", - "project_root": ".", + "project_root": "../", "shaders_directory": "shaders", "device_extensions": [ "VK_KHR_swapchain" diff --git a/scripts/gui_demo.lua b/scripts/gui_demo.lua index d29c32f..c91941b 100644 --- a/scripts/gui_demo.lua +++ b/scripts/gui_demo.lua @@ -1,19 +1,19 @@ -local Gui = require(\ gui\) -local math3d = require(\math3d\) +local Gui = require('gui') +local math3d = require('math3d') local ctx = Gui.newContext() local input = Gui.newInputState() -local textState = Gui.newTextState(\\) +local textState = Gui.newTextState('') local listState = Gui.newListState() local items = { - \Dashboard Setup\, - \Input Streams\, - \Telemetry\, - \Power Profile\, - \Diagnostics\, - \Release Notes\, + 'Dashboard Setup', + 'Input Streams', + 'Telemetry', + 'Power Profile', + 'Diagnostics', + 'Release Notes', } -local statusMessage = \Idle\ +local statusMessage = 'Idle' local selectedItem = items[1] local rotationSpeeds = {x = 0.45, y = 0.65} @@ -39,8 +39,8 @@ local cubeIndices = { local shaderVariants = { default = { - vertex = \shaders/cube.vert.spv\, - fragment = \shaders/cube.frag.spv\, + vertex = 'shaders/cube.vert.spv', + fragment = 'shaders/cube.frag.spv', }, } @@ -69,7 +69,7 @@ local function createCube(position) vertices = cubeVertices, indices = cubeIndices, compute_model_matrix = computeModel, - shader_key = \default\, + shader_key = 'default', } end @@ -102,9 +102,9 @@ local function drawPanel() }) Gui.svg(ctx, {x = 320, y = 30, width = 120, height = 120}, \assets/logo.svg\) textState = Gui.textbox(ctx, \search_field\, {x = 30, y = 80, width = 420, height = 40}, textState, { - placeholder = \Filter modules...\, + placeholder = 'Filter modules...', onSubmit = function(text) - statusMessage = \Searching for: \ .. (text ~= \\ and text or \anything\) + statusMessage = 'Searching for: ' .. (text ~= '' and text or 'anything') end, }) diff --git a/src/app/sdl3_app.hpp b/src/app/sdl3_app.hpp index cecbe52..8ad1021 100644 --- a/src/app/sdl3_app.hpp +++ b/src/app/sdl3_app.hpp @@ -21,6 +21,7 @@ #include "app/audio_player.hpp" #include "core/vertex.hpp" #include "script/script_engine.hpp" +#include "script/shader_manager.hpp" #include "gui/gui_renderer.hpp" namespace sdl3cpp::app { @@ -132,7 +133,7 @@ private: script::ScriptEngine scriptEngine_; std::vector vertices_; std::vector indices_; - std::unordered_map shaderPathMap_; + std::unordered_map shaderPathMap_; std::unordered_map graphicsPipelines_; std::string defaultShaderKey_; VkFence inFlightFence_ = VK_NULL_HANDLE; diff --git a/src/script/audio_manager.cpp b/src/script/audio_manager.cpp new file mode 100644 index 0000000..cf75af8 --- /dev/null +++ b/src/script/audio_manager.cpp @@ -0,0 +1,72 @@ +#include "script/audio_manager.hpp" +#include "app/audio_player.hpp" + +#include +#include +#include +#include + +namespace sdl3cpp::script { + +AudioManager::AudioManager(const std::filesystem::path& scriptDirectory) + : scriptDirectory_(scriptDirectory) {} + +void AudioManager::SetAudioPlayer(app::AudioPlayer* audioPlayer) { + audioPlayer_ = audioPlayer; + if (!audioPlayer_) { + return; + } + for (const auto& command : pendingAudioCommands_) { + try { + ExecuteAudioCommand(audioPlayer_, command); + } catch (const std::exception& exc) { + std::cerr << "AudioPlayer: " << exc.what() << '\n'; + } + } + pendingAudioCommands_.clear(); +} + +bool AudioManager::QueueAudioCommand(AudioCommandType type, std::string path, bool loop, std::string& error) { + if (audioPlayer_) { + try { + AudioCommand command{type, std::move(path), loop}; + ExecuteAudioCommand(audioPlayer_, command); + return true; + } catch (const std::exception& exc) { + error = exc.what(); + return false; + } + } + pendingAudioCommands_.push_back(AudioCommand{type, std::move(path), loop}); + return true; +} + +void AudioManager::ExecuteAudioCommand(app::AudioPlayer* player, const AudioCommand& command) { + auto resolved = ResolveScriptPath(command.path); + if (!std::filesystem::exists(resolved)) { + throw std::runtime_error("Audio file not found: " + resolved.string()); + } + switch (command.type) { + case AudioCommandType::Background: + player->PlayBackground(resolved, command.loop); + break; + case AudioCommandType::Effect: + player->PlayEffect(resolved, command.loop); + break; + } +} + +std::filesystem::path AudioManager::ResolveScriptPath(const std::string& requested) const { + std::filesystem::path resolved(requested); + if (!resolved.is_absolute()) { + resolved = scriptDirectory_ / resolved; + } + std::error_code ec; + auto canonical = std::filesystem::weakly_canonical(resolved, ec); + if (!ec) { + resolved = canonical; + } + return resolved; +} + +} // namespace sdl3cpp::script \ No newline at end of file diff --git a/src/script/audio_manager.hpp b/src/script/audio_manager.hpp new file mode 100644 index 0000000..b95bb5c --- /dev/null +++ b/src/script/audio_manager.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include + +#include +#include +#include + +namespace sdl3cpp::app { +class AudioPlayer; +} + +namespace sdl3cpp::script { + +class AudioManager { +public: + enum class AudioCommandType { + Background, + Effect + }; + + struct AudioCommand { + AudioCommandType type; + std::string path; + bool loop; + }; + + explicit AudioManager(const std::filesystem::path& scriptDirectory); + + void SetAudioPlayer(app::AudioPlayer* audioPlayer); + bool QueueAudioCommand(AudioCommandType type, std::string path, bool loop, std::string& error); + +private: + std::filesystem::path scriptDirectory_; + app::AudioPlayer* audioPlayer_ = nullptr; + std::vector pendingAudioCommands_; + + void ExecuteAudioCommand(app::AudioPlayer* player, const AudioCommand& command); + std::filesystem::path ResolveScriptPath(const std::string& requested) const; +}; + +} // namespace sdl3cpp::script \ No newline at end of file diff --git a/src/script/gui_manager.cpp b/src/script/gui_manager.cpp new file mode 100644 index 0000000..133edfd --- /dev/null +++ b/src/script/gui_manager.cpp @@ -0,0 +1,230 @@ +#include "script/gui_manager.hpp" + +#include + +#include +#include +#include +#include + +namespace sdl3cpp::script { + +GuiManager::GuiManager(lua_State* L) : L_(L) { + lua_getglobal(L_, "gui_input"); + if (!lua_isnil(L_, -1)) { + guiInputRef_ = luaL_ref(L_, LUA_REGISTRYINDEX); + } else { + lua_pop(L_, 1); + } + + lua_getglobal(L_, "get_gui_commands"); + if (lua_isfunction(L_, -1)) { + guiCommandsFnRef_ = luaL_ref(L_, LUA_REGISTRYINDEX); + } else { + lua_pop(L_, 1); + } +} + +std::vector GuiManager::LoadGuiCommands() { + std::vector commands; + if (guiCommandsFnRef_ == LUA_REFNIL) { + return commands; + } + lua_rawgeti(L_, LUA_REGISTRYINDEX, guiCommandsFnRef_); + if (lua_pcall(L_, 0, 1, 0) != LUA_OK) { + std::string message = GetLuaError(); + lua_pop(L_, 1); + throw std::runtime_error("Lua get_gui_commands failed: " + message); + } + if (!lua_istable(L_, -1)) { + lua_pop(L_, 1); + throw std::runtime_error("'get_gui_commands' did not return a table"); + } + + size_t count = lua_rawlen(L_, -1); + commands.reserve(count); + + for (size_t i = 1; i <= count; ++i) { + lua_rawgeti(L_, -1, static_cast(i)); + if (!lua_istable(L_, -1)) { + lua_pop(L_, 1); + throw std::runtime_error("GUI command at index " + std::to_string(i) + " is not a table"); + } + int commandIndex = lua_gettop(L_); + lua_getfield(L_, commandIndex, "type"); + const char* typeName = lua_tostring(L_, -1); + if (!typeName) { + lua_pop(L_, 2); + throw std::runtime_error("GUI command at index " + std::to_string(i) + " is missing a type"); + } + GuiCommand command{}; + if (std::strcmp(typeName, "rect") == 0) { + command.type = GuiCommand::Type::Rect; + command.rect = ReadRect(commandIndex); + command.color = ReadColor(commandIndex, GuiColor{0.0f, 0.0f, 0.0f, 1.0f}); + command.borderColor = ReadColor(commandIndex, GuiColor{0.0f, 0.0f, 0.0f, 0.0f}); + lua_getfield(L_, commandIndex, "borderWidth"); + if (lua_isnumber(L_, -1)) { + command.borderWidth = static_cast(lua_tonumber(L_, -1)); + } + lua_pop(L_, 1); + } else if (std::strcmp(typeName, "text") == 0) { + command.type = GuiCommand::Type::Text; + ReadStringField(commandIndex, "text", command.text); + lua_getfield(L_, commandIndex, "fontSize"); + if (lua_isnumber(L_, -1)) { + command.fontSize = static_cast(lua_tonumber(L_, -1)); + } + lua_pop(L_, 1); + std::string align; + if (ReadStringField(commandIndex, "alignX", align)) { + command.alignX = align; + } + if (ReadStringField(commandIndex, "alignY", align)) { + command.alignY = align; + } + lua_getfield(L_, commandIndex, "clipRect"); + if (lua_istable(L_, -1)) { + command.clipRect = ReadRect(-1); + command.hasClipRect = true; + } + lua_pop(L_, 1); + lua_getfield(L_, commandIndex, "bounds"); + if (lua_istable(L_, -1)) { + command.bounds = ReadRect(-1); + command.hasBounds = true; + } + lua_pop(L_, 1); + command.color = ReadColor(commandIndex, GuiColor{1.0f, 1.0f, 1.0f, 1.0f}); + } else if (std::strcmp(typeName, "clip_push") == 0) { + command.type = GuiCommand::Type::ClipPush; + command.rect = ReadRect(commandIndex); + } else if (std::strcmp(typeName, "clip_pop") == 0) { + command.type = GuiCommand::Type::ClipPop; + } else if (std::strcmp(typeName, "svg") == 0) { + command.type = GuiCommand::Type::Svg; + ReadStringField(commandIndex, "path", command.svgPath); + command.rect = ReadRect(commandIndex); + command.svgTint = ReadColor(commandIndex, GuiColor{1.0f, 1.0f, 1.0f, 0.0f}); + lua_getfield(L_, commandIndex, "tint"); + if (lua_istable(L_, -1)) { + command.svgTint = ReadColor(-1, command.svgTint); + } + lua_pop(L_, 1); + } + lua_pop(L_, 1); + lua_pop(L_, 1); + commands.push_back(std::move(command)); + } + + lua_pop(L_, 1); + return commands; +} + +void GuiManager::UpdateGuiInput(const GuiInputSnapshot& input) { + if (guiInputRef_ == LUA_REFNIL) { + return; + } + lua_rawgeti(L_, LUA_REGISTRYINDEX, guiInputRef_); + int stateIndex = lua_gettop(L_); + + lua_getfield(L_, stateIndex, "resetTransient"); + lua_pushvalue(L_, stateIndex); + lua_call(L_, 1, 0); + + lua_getfield(L_, stateIndex, "setMouse"); + lua_pushvalue(L_, stateIndex); + lua_pushnumber(L_, input.mouseX); + lua_pushnumber(L_, input.mouseY); + lua_pushboolean(L_, input.mouseDown); + lua_call(L_, 4, 0); + + lua_getfield(L_, stateIndex, "setWheel"); + lua_pushvalue(L_, stateIndex); + lua_pushnumber(L_, input.wheel); + lua_call(L_, 2, 0); + + if (!input.textInput.empty()) { + lua_getfield(L_, stateIndex, "addTextInput"); + lua_pushvalue(L_, stateIndex); + lua_pushstring(L_, input.textInput.c_str()); + lua_call(L_, 2, 0); + } + + for (const auto& [key, pressed] : input.keyStates) { + lua_getfield(L_, stateIndex, "setKey"); + lua_pushvalue(L_, stateIndex); + lua_pushstring(L_, key.c_str()); + lua_pushboolean(L_, pressed); + lua_call(L_, 3, 0); + } + + lua_pop(L_, 1); +} + +bool GuiManager::HasGuiCommands() const { + return guiCommandsFnRef_ != LUA_REFNIL; +} + +GuiCommand::RectData GuiManager::ReadRect(int index) { + GuiCommand::RectData rect{}; + if (!lua_istable(L_, index)) { + return rect; + } + int absIndex = lua_absindex(L_, index); + auto readField = [&](const char* name, float defaultValue) -> float { + lua_getfield(L_, absIndex, name); + float value = defaultValue; + if (lua_isnumber(L_, -1)) { + value = static_cast(lua_tonumber(L_, -1)); + } + lua_pop(L_, 1); + return value; + }; + rect.x = readField("x", rect.x); + rect.y = readField("y", rect.y); + rect.width = readField("width", rect.width); + rect.height = readField("height", rect.height); + return rect; +} + +GuiColor GuiManager::ReadColor(int index, const GuiColor& defaultColor) { + GuiColor color = defaultColor; + if (!lua_istable(L_, index)) { + return color; + } + int absIndex = lua_absindex(L_, index); + for (int component = 0; component < 4; ++component) { + lua_rawgeti(L_, absIndex, component + 1); + if (lua_isnumber(L_, -1)) { + float value = static_cast(lua_tonumber(L_, -1)); + switch (component) { + case 0: color.r = value; break; + case 1: color.g = value; break; + case 2: color.b = value; break; + case 3: color.a = value; break; + } + } + lua_pop(L_, 1); + } + return color; +} + +bool GuiManager::ReadStringField(int index, const char* name, std::string& outString) { + int absIndex = lua_absindex(L_, index); + lua_getfield(L_, absIndex, name); + if (lua_isstring(L_, -1)) { + outString = lua_tostring(L_, -1); + lua_pop(L_, 1); + return true; + } + lua_pop(L_, 1); + return false; +} + +std::string GuiManager::GetLuaError() { + const char* message = lua_tostring(L_, -1); + return message ? message : "unknown lua error"; +} + +} // namespace sdl3cpp::script \ No newline at end of file diff --git a/src/script/gui_manager.hpp b/src/script/gui_manager.hpp new file mode 100644 index 0000000..c661318 --- /dev/null +++ b/src/script/gui_manager.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include "script/gui_types.hpp" +#include "script/lua_helpers.hpp" + +#include + +#include + +namespace sdl3cpp::script { + +class GuiManager { +public: + explicit GuiManager(lua_State* L); + + std::vector LoadGuiCommands(); + void UpdateGuiInput(const GuiInputSnapshot& input); + bool HasGuiCommands() const; + +private: + lua_State* L_; + int guiInputRef_ = LUA_REFNIL; + int guiCommandsFnRef_ = LUA_REFNIL; + + GuiCommand::RectData ReadRect(int index); + GuiColor ReadColor(int index, const GuiColor& defaultColor); + bool ReadStringField(int index, const char* name, std::string& outString); + std::string GetLuaError(); +}; + +} // namespace sdl3cpp::script \ No newline at end of file diff --git a/src/script/lua_bindings.cpp b/src/script/lua_bindings.cpp index eed0f56..1cbcefb 100644 --- a/src/script/lua_bindings.cpp +++ b/src/script/lua_bindings.cpp @@ -4,35 +4,10 @@ #include "script/mesh_loader.hpp" #include -#include -#include -#include -#include #include namespace sdl3cpp::script { -namespace { - -glm::vec3 ToVec3(const std::array& value) { - return glm::vec3(value[0], value[1], value[2]); -} - -glm::quat ToQuat(const std::array& value) { - return glm::quat(value[3], value[0], value[1], value[2]); -} - -void PushMatrix(lua_State* L, const glm::mat4& matrix) { - lua_newtable(L); - const float* ptr = glm::value_ptr(matrix); - for (int i = 0; i < 16; ++i) { - lua_pushnumber(L, ptr[i]); - lua_rawseti(L, -2, i + 1); - } -} - -} // namespace - void LuaBindings::RegisterBindings(lua_State* L, ScriptEngine* engine) { lua_pushlightuserdata(L, engine); lua_pushcclosure(L, &LoadMeshFromFile, 1); @@ -169,7 +144,7 @@ int LuaBindings::AudioPlayBackground(lua_State* L) { } std::string error; - if (!engine->QueueAudioCommand(ScriptEngine::AudioCommandType::Background, path, loop, error)) { + if (!engine->QueueAudioCommand(AudioManager::AudioCommandType::Background, path, loop, error)) { lua_pushnil(L); lua_pushstring(L, error.c_str()); return 2; @@ -188,7 +163,7 @@ int LuaBindings::AudioPlaySound(lua_State* L) { } std::string error; - if (!engine->QueueAudioCommand(ScriptEngine::AudioCommandType::Effect, path, loop, error)) { + if (!engine->QueueAudioCommand(AudioManager::AudioCommandType::Effect, path, loop, error)) { lua_pushnil(L); lua_pushstring(L, error.c_str()); return 2; @@ -199,13 +174,7 @@ int LuaBindings::AudioPlaySound(lua_State* L) { } int LuaBindings::GlmMatrixFromTransform(lua_State* L) { - std::array translation = ReadVector3(L, 1); - std::array rotation = ReadQuaternion(L, 2); - glm::vec3 pos = ToVec3(translation); - glm::quat quat = ToQuat(rotation); - glm::mat4 matrix = glm::translate(glm::mat4(1.0f), pos) * glm::mat4_cast(quat); - PushMatrix(L, matrix); - return 1; + return LuaGlmMatrixFromTransform(L); } } // namespace sdl3cpp::script diff --git a/src/script/lua_helpers.cpp b/src/script/lua_helpers.cpp index cd1a5a4..8f25574 100644 --- a/src/script/lua_helpers.cpp +++ b/src/script/lua_helpers.cpp @@ -1,5 +1,9 @@ #include "script/lua_helpers.hpp" +#include +#include +#include +#include #include #include @@ -74,4 +78,31 @@ std::array IdentityMatrix() { 0.0f, 0.0f, 0.0f, 1.0f}; } +glm::vec3 ToVec3(const std::array& value) { + return glm::vec3(value[0], value[1], value[2]); +} + +glm::quat ToQuat(const std::array& value) { + return glm::quat(value[3], value[0], value[1], value[2]); +} + +void PushMatrix(lua_State* L, const glm::mat4& matrix) { + lua_newtable(L); + const float* ptr = glm::value_ptr(matrix); + for (int i = 0; i < 16; ++i) { + lua_pushnumber(L, ptr[i]); + lua_rawseti(L, -2, i + 1); + } +} + +int LuaGlmMatrixFromTransform(lua_State* L) { + std::array translation = ReadVector3(L, 1); + std::array rotation = ReadQuaternion(L, 2); + glm::vec3 pos = ToVec3(translation); + glm::quat quat = ToQuat(rotation); + glm::mat4 matrix = glm::translate(glm::mat4(1.0f), pos) * glm::mat4_cast(quat); + PushMatrix(L, matrix); + return 1; +} + } // namespace sdl3cpp::script diff --git a/src/script/lua_helpers.hpp b/src/script/lua_helpers.hpp index 645ff0a..cba590b 100644 --- a/src/script/lua_helpers.hpp +++ b/src/script/lua_helpers.hpp @@ -13,6 +13,7 @@ std::array ReadQuaternion(lua_State* L, int index); std::array ReadMatrix(lua_State* L, int index); std::string GetLuaError(lua_State* L); std::array IdentityMatrix(); +int LuaGlmMatrixFromTransform(lua_State* L); } // namespace sdl3cpp::script diff --git a/src/script/mesh_loader.cpp b/src/script/mesh_loader.cpp index 5b0e98b..d25325c 100644 --- a/src/script/mesh_loader.cpp +++ b/src/script/mesh_loader.cpp @@ -1,4 +1,5 @@ #include "script/mesh_loader.hpp" +#include "script/script_engine.hpp" #include #include @@ -132,4 +133,19 @@ int MeshLoader::PushMeshToLua(lua_State* L, const MeshPayload& payload) { return 1; } +int MeshLoader::LuaLoadMeshFromFile(lua_State* L) { + auto* script = static_cast(lua_touserdata(L, lua_upvalueindex(1))); + const char* path = luaL_checkstring(L, 1); + MeshPayload payload; + std::string error; + if (!LoadFromFile(script->GetScriptDirectory(), path, payload, error)) { + lua_pushnil(L); + lua_pushstring(L, error.c_str()); + return 2; + } + PushMeshToLua(L, payload); + lua_pushnil(L); + return 2; +} + } // namespace sdl3cpp::script diff --git a/src/script/mesh_loader.hpp b/src/script/mesh_loader.hpp index b91f153..48afaf5 100644 --- a/src/script/mesh_loader.hpp +++ b/src/script/mesh_loader.hpp @@ -24,6 +24,7 @@ public: std::string& outError); static int PushMeshToLua(lua_State* L, const MeshPayload& payload); + static int LuaLoadMeshFromFile(lua_State* L); }; } // namespace sdl3cpp::script diff --git a/src/script/scene_manager.cpp b/src/script/scene_manager.cpp new file mode 100644 index 0000000..4a5ed24 --- /dev/null +++ b/src/script/scene_manager.cpp @@ -0,0 +1,194 @@ +#include "script/scene_manager.hpp" + +#include + +#include +#include +#include + +namespace sdl3cpp::script { + +SceneManager::SceneManager(lua_State* L) : L_(L) {} + +std::vector SceneManager::LoadSceneObjects() { + lua_getglobal(L_, "get_scene_objects"); + if (!lua_isfunction(L_, -1)) { + lua_pop(L_, 1); + throw std::runtime_error("Lua function 'get_scene_objects' is missing"); + } + if (lua_pcall(L_, 0, 1, 0) != LUA_OK) { + std::string message = GetLuaError(); + lua_pop(L_, 1); + throw std::runtime_error("Lua get_scene_objects failed: " + message); + } + if (!lua_istable(L_, -1)) { + lua_pop(L_, 1); + throw std::runtime_error("'get_scene_objects' did not return a table"); + } + + size_t count = lua_rawlen(L_, -1); + std::vector objects; + objects.reserve(count); + + for (size_t i = 1; i <= count; ++i) { + lua_rawgeti(L_, -1, static_cast(i)); + if (!lua_istable(L_, -1)) { + lua_pop(L_, 1); + throw std::runtime_error("Scene object at index " + std::to_string(i) + " is not a table"); + } + + SceneObject object; + lua_getfield(L_, -1, "vertices"); + object.vertices = ReadVertexArray(-1); + lua_pop(L_, 1); + if (object.vertices.empty()) { + lua_pop(L_, 1); + throw std::runtime_error("Scene object " + std::to_string(i) + " must supply at least one vertex"); + } + + lua_getfield(L_, -1, "indices"); + object.indices = ReadIndexArray(-1); + lua_pop(L_, 1); + if (object.indices.empty()) { + lua_pop(L_, 1); + throw std::runtime_error("Scene object " + std::to_string(i) + " must supply indices"); + } + + lua_getfield(L_, -1, "compute_model_matrix"); + if (lua_isfunction(L_, -1)) { + object.computeModelMatrixRef = luaL_ref(L_, LUA_REGISTRYINDEX); + } else { + lua_pop(L_, 1); + object.computeModelMatrixRef = LUA_REFNIL; + } + + lua_getfield(L_, -1, "shader_key"); + if (lua_isstring(L_, -1)) { + object.shaderKey = lua_tostring(L_, -1); + } + lua_pop(L_, 1); + + objects.push_back(std::move(object)); + lua_pop(L_, 1); + } + + lua_pop(L_, 1); + return objects; +} + +std::array SceneManager::ComputeModelMatrix(int functionRef, float time) { + if (functionRef == LUA_REFNIL) { + lua_getglobal(L_, "compute_model_matrix"); + if (!lua_isfunction(L_, -1)) { + lua_pop(L_, 1); + return IdentityMatrix(); + } + } else { + lua_rawgeti(L_, LUA_REGISTRYINDEX, functionRef); + } + + lua_pushnumber(L_, time); + if (lua_pcall(L_, 1, 1, 0) != LUA_OK) { + std::string message = GetLuaError(); + lua_pop(L_, 1); + throw std::runtime_error("Lua compute_model_matrix failed: " + message); + } + if (!lua_istable(L_, -1)) { + lua_pop(L_, 1); + throw std::runtime_error("'compute_model_matrix' did not return a table"); + } + + std::array matrix = ReadMatrix(L_, -1); + lua_pop(L_, 1); + return matrix; +} + +std::array SceneManager::GetViewProjectionMatrix(float aspect) { + lua_getglobal(L_, "get_view_projection"); + if (!lua_isfunction(L_, -1)) { + lua_pop(L_, 1); + throw std::runtime_error("Lua function 'get_view_projection' is missing"); + } + lua_pushnumber(L_, aspect); + if (lua_pcall(L_, 1, 1, 0) != LUA_OK) { + std::string message = GetLuaError(); + lua_pop(L_, 1); + throw std::runtime_error("Lua get_view_projection failed: " + message); + } + if (!lua_istable(L_, -1)) { + lua_pop(L_, 1); + throw std::runtime_error("'get_view_projection' did not return a table"); + } + std::array matrix = ReadMatrix(L_, -1); + lua_pop(L_, 1); + return matrix; +} + +std::vector SceneManager::ReadVertexArray(int index) { + int absIndex = lua_absindex(L_, index); + if (!lua_istable(L_, absIndex)) { + throw std::runtime_error("Expected table for vertex data"); + } + + size_t count = lua_rawlen(L_, absIndex); + std::vector vertices; + vertices.reserve(count); + + for (size_t i = 1; i <= count; ++i) { + lua_rawgeti(L_, absIndex, static_cast(i)); + if (!lua_istable(L_, -1)) { + lua_pop(L_, 1); + throw std::runtime_error("Vertex entry at index " + std::to_string(i) + " is not a table"); + } + + int vertexIndex = lua_gettop(L_); + core::Vertex vertex{}; + + lua_getfield(L_, vertexIndex, "position"); + vertex.position = ReadVector3(L_, -1); + lua_pop(L_, 1); + + lua_getfield(L_, vertexIndex, "color"); + vertex.color = ReadVector3(L_, -1); + lua_pop(L_, 1); + + lua_pop(L_, 1); + vertices.push_back(vertex); + } + + return vertices; +} + +std::vector SceneManager::ReadIndexArray(int index) { + int absIndex = lua_absindex(L_, index); + if (!lua_istable(L_, absIndex)) { + throw std::runtime_error("Expected table for index data"); + } + + size_t count = lua_rawlen(L_, absIndex); + std::vector indices; + indices.reserve(count); + + for (size_t i = 1; i <= count; ++i) { + lua_rawgeti(L_, absIndex, static_cast(i)); + if (!lua_isinteger(L_, -1)) { + lua_pop(L_, 1); + throw std::runtime_error("Index entry at position " + std::to_string(i) + " is not an integer"); + } + lua_Integer value = lua_tointeger(L_, -1); + lua_pop(L_, 1); + if (value < 1) { + throw std::runtime_error("Index values must be 1 or greater"); + } + indices.push_back(static_cast(value - 1)); + } + + return indices; +} + +std::string SceneManager::GetLuaError() { + const char* message = lua_tostring(L_, -1); + return message ? message : "unknown lua error"; +} + +} // namespace sdl3cpp::script \ No newline at end of file diff --git a/src/script/scene_manager.hpp b/src/script/scene_manager.hpp new file mode 100644 index 0000000..a4be83c --- /dev/null +++ b/src/script/scene_manager.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include "script/lua_helpers.hpp" +#include "core/vertex.hpp" + +#include +#include +#include +#include + +namespace sdl3cpp::script { + +class SceneManager { +public: + struct SceneObject { + std::vector vertices; + std::vector indices; + int computeModelMatrixRef = LUA_REFNIL; + std::string shaderKey = "default"; + }; + + explicit SceneManager(lua_State* L); + + std::vector LoadSceneObjects(); + std::array ComputeModelMatrix(int functionRef, float time); + std::array GetViewProjectionMatrix(float aspect); + +private: + lua_State* L_; + + std::vector ReadVertexArray(int index); + std::vector ReadIndexArray(int index); + std::string GetLuaError(); +}; + +} // namespace sdl3cpp::script \ No newline at end of file diff --git a/src/script/script_engine.cpp b/src/script/script_engine.cpp index 4af61d2..78c961e 100644 --- a/src/script/script_engine.cpp +++ b/src/script/script_engine.cpp @@ -1,405 +1,40 @@ #include "script/script_engine.hpp" +#include "script/scene_manager.hpp" +#include "script/shader_manager.hpp" +#include "script/gui_manager.hpp" +#include "script/audio_manager.hpp" +#include "script/lua_bindings.hpp" #include "app/audio_player.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include -#include #include #include #include -#include #include namespace sdl3cpp::script { -namespace detail { - -std::array ReadVector3(lua_State* L, int index) { - std::array result{}; - int absIndex = lua_absindex(L, index); - size_t len = lua_rawlen(L, absIndex); - if (len != 3) { - throw std::runtime_error("Expected vector with 3 components"); - } - for (size_t i = 1; i <= 3; ++i) { - lua_rawgeti(L, absIndex, static_cast(i)); - if (!lua_isnumber(L, -1)) { - lua_pop(L, 1); - throw std::runtime_error("Vector component is not a number"); - } - result[i - 1] = static_cast(lua_tonumber(L, -1)); - lua_pop(L, 1); - } - return result; -} - -std::array ReadMatrix(lua_State* L, int index) { - std::array result{}; - int absIndex = lua_absindex(L, index); - size_t len = lua_rawlen(L, absIndex); - if (len != 16) { - throw std::runtime_error("Expected 4x4 matrix with 16 components"); - } - for (size_t i = 1; i <= 16; ++i) { - lua_rawgeti(L, absIndex, static_cast(i)); - if (!lua_isnumber(L, -1)) { - lua_pop(L, 1); - throw std::runtime_error("Matrix component is not a number"); - } - result[i - 1] = static_cast(lua_tonumber(L, -1)); - lua_pop(L, 1); - } - return result; -} - -std::array ReadQuaternion(lua_State* L, int index) { - std::array result{}; - int absIndex = lua_absindex(L, index); - size_t len = lua_rawlen(L, absIndex); - if (len != 4) { - throw std::runtime_error("Expected quaternion with 4 components"); - } - for (size_t i = 1; i <= 4; ++i) { - lua_rawgeti(L, absIndex, static_cast(i)); - if (!lua_isnumber(L, -1)) { - lua_pop(L, 1); - throw std::runtime_error("Quaternion component is not a number"); - } - result[i - 1] = static_cast(lua_tonumber(L, -1)); - lua_pop(L, 1); - } - return result; -} - -} // namespace detail - -namespace { - -struct MeshPayload { - std::vector> positions; - std::vector> colors; - std::vector indices; -}; - -bool TryLoadMeshPayload(const ScriptEngine* script, - const std::string& requestedPath, - MeshPayload& payload, - std::string& error) { - std::filesystem::path resolved(requestedPath); - if (!resolved.is_absolute()) { - resolved = script->GetScriptDirectory() / resolved; - } - std::error_code ec; - resolved = std::filesystem::weakly_canonical(resolved, ec); - if (ec) { - error = "Failed to resolve mesh path: " + ec.message(); - return false; - } - if (!std::filesystem::exists(resolved)) { - error = "Mesh file not found: " + resolved.string(); - return false; - } - - Assimp::Importer importer; - const aiScene* scene = importer.ReadFile( - resolved.string(), - aiProcess_Triangulate | aiProcess_JoinIdenticalVertices | aiProcess_PreTransformVertices); - if (!scene) { - error = importer.GetErrorString() ? importer.GetErrorString() : "Assimp failed to load mesh"; - return false; - } - if (scene->mNumMeshes == 0) { - error = "Scene contains no meshes"; - return false; - } - - const aiMesh* mesh = scene->mMeshes[0]; - if (!mesh->mNumVertices) { - error = "Mesh contains no vertices"; - return false; - } - - payload.positions.reserve(mesh->mNumVertices); - payload.colors.reserve(mesh->mNumVertices); - payload.indices.reserve(mesh->mNumFaces * 3); - - aiColor3D defaultColor(0.6f, 0.8f, 1.0f); - - aiColor3D materialColor = defaultColor; - if (mesh->mMaterialIndex < scene->mNumMaterials) { - const aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex]; - aiColor4D diffuse; - if (material && material->Get(AI_MATKEY_COLOR_DIFFUSE, diffuse) == AI_SUCCESS) { - materialColor = aiColor3D(diffuse.r, diffuse.g, diffuse.b); - } - } - - for (unsigned i = 0; i < mesh->mNumVertices; ++i) { - const aiVector3D& vertex = mesh->mVertices[i]; - payload.positions.push_back({vertex.x, vertex.y, vertex.z}); - - aiColor3D color = materialColor; - if (mesh->HasVertexColors(0) && mesh->mColors[0]) { - const aiColor4D& vertexColor = mesh->mColors[0][i]; - color = aiColor3D(vertexColor.r, vertexColor.g, vertexColor.b); - } - payload.colors.push_back({color.r, color.g, color.b}); - } - - for (unsigned faceIndex = 0; faceIndex < mesh->mNumFaces; ++faceIndex) { - const aiFace& face = mesh->mFaces[faceIndex]; - if (face.mNumIndices != 3) { - continue; - } - payload.indices.push_back(face.mIndices[0]); - payload.indices.push_back(face.mIndices[1]); - payload.indices.push_back(face.mIndices[2]); - } - - if (payload.indices.empty()) { - error = "Mesh contains no triangle faces"; - return false; - } - return true; -} - -glm::vec3 ToVec3(const std::array& value) { - return glm::vec3(value[0], value[1], value[2]); -} - -glm::quat ToQuat(const std::array& value) { - return glm::quat(value[3], value[0], value[1], value[2]); -} - -void PushMatrix(lua_State* L, const glm::mat4& matrix) { - lua_newtable(L); - const float* ptr = glm::value_ptr(matrix); - for (int i = 0; i < 16; ++i) { - lua_pushnumber(L, ptr[i]); - lua_rawseti(L, -2, i + 1); - } -} - -int PushMeshToLua(lua_State* L, const MeshPayload& payload) { - lua_newtable(L); - - lua_newtable(L); - for (size_t vertexIndex = 0; vertexIndex < payload.positions.size(); ++vertexIndex) { - lua_newtable(L); - - lua_newtable(L); - for (int component = 0; component < 3; ++component) { - lua_pushnumber(L, payload.positions[vertexIndex][component]); - lua_rawseti(L, -2, component + 1); - } - lua_setfield(L, -2, "position"); - - lua_newtable(L); - for (int component = 0; component < 3; ++component) { - lua_pushnumber(L, payload.colors[vertexIndex][component]); - lua_rawseti(L, -2, component + 1); - } - lua_setfield(L, -2, "color"); - - lua_rawseti(L, -2, static_cast(vertexIndex + 1)); - } - lua_setfield(L, -2, "vertices"); - - lua_newtable(L); - for (size_t index = 0; index < payload.indices.size(); ++index) { - lua_pushinteger(L, static_cast(payload.indices[index]) + 1); - lua_rawseti(L, -2, static_cast(index + 1)); - } - lua_setfield(L, -2, "indices"); - - return 1; -} - -int LuaLoadMeshFromFile(lua_State* L) { - auto* script = static_cast(lua_touserdata(L, lua_upvalueindex(1))); - const char* path = luaL_checkstring(L, 1); - MeshPayload payload; - std::string error; - if (!TryLoadMeshPayload(script, path, payload, error)) { - lua_pushnil(L); - lua_pushstring(L, error.c_str()); - return 2; - } - PushMeshToLua(L, payload); - lua_pushnil(L); - return 2; -} - -int LuaPhysicsCreateBox(lua_State* L) { - auto* script = static_cast(lua_touserdata(L, lua_upvalueindex(1))); - const char* name = luaL_checkstring(L, 1); - if (!lua_istable(L, 2) || !lua_istable(L, 4) || !lua_istable(L, 5)) { - luaL_error(L, "physics_create_box expects vector tables for half extents, origin, and rotation"); - } - std::array halfExtents = detail::ReadVector3(L, 2); - float mass = static_cast(luaL_checknumber(L, 3)); - std::array origin = detail::ReadVector3(L, 4); - std::array rotation = detail::ReadQuaternion(L, 5); - - btTransform transform; - transform.setIdentity(); - transform.setOrigin(btVector3(origin[0], origin[1], origin[2])); - transform.setRotation(btQuaternion(rotation[0], rotation[1], rotation[2], rotation[3])); - - std::string error; - if (!script->GetPhysicsBridge().addBoxRigidBody( - name, - btVector3(halfExtents[0], halfExtents[1], halfExtents[2]), - mass, - transform, - error)) { - lua_pushnil(L); - lua_pushstring(L, error.c_str()); - return 2; - } - - lua_pushboolean(L, 1); - return 1; -} - -int LuaPhysicsStepSimulation(lua_State* L) { - auto* script = static_cast(lua_touserdata(L, lua_upvalueindex(1))); - float deltaTime = static_cast(luaL_checknumber(L, 1)); - int steps = script->GetPhysicsBridge().stepSimulation(deltaTime); - lua_pushinteger(L, steps); - return 1; -} - -int LuaPhysicsGetTransform(lua_State* L) { - auto* script = static_cast(lua_touserdata(L, lua_upvalueindex(1))); - const char* name = luaL_checkstring(L, 1); - btTransform transform; - std::string error; - if (!script->GetPhysicsBridge().getRigidBodyTransform(name, transform, error)) { - lua_pushnil(L); - lua_pushstring(L, error.c_str()); - return 2; - } - - lua_newtable(L); - lua_newtable(L); - const btVector3& origin = transform.getOrigin(); - lua_pushnumber(L, origin.x()); - lua_rawseti(L, -2, 1); - lua_pushnumber(L, origin.y()); - lua_rawseti(L, -2, 2); - lua_pushnumber(L, origin.z()); - lua_rawseti(L, -2, 3); - lua_setfield(L, -2, "position"); - - lua_newtable(L); - const btQuaternion& orientation = transform.getRotation(); - lua_pushnumber(L, orientation.x()); - lua_rawseti(L, -2, 1); - lua_pushnumber(L, orientation.y()); - lua_rawseti(L, -2, 2); - lua_pushnumber(L, orientation.z()); - lua_rawseti(L, -2, 3); - lua_pushnumber(L, orientation.w()); - lua_rawseti(L, -2, 4); - lua_setfield(L, -2, "rotation"); - - return 1; -} - -int LuaAudioPlayBackground(lua_State* L) { - auto* script = static_cast(lua_touserdata(L, lua_upvalueindex(1))); - const char* path = luaL_checkstring(L, 1); - bool loop = true; - if (lua_gettop(L) >= 2 && lua_isboolean(L, 2)) { - loop = lua_toboolean(L, 2); - } - std::string error; - if (!script->QueueAudioCommand(ScriptEngine::AudioCommandType::Background, path, loop, error)) { - lua_pushnil(L); - lua_pushstring(L, error.c_str()); - return 2; - } - lua_pushboolean(L, 1); - return 1; -} - -int LuaAudioPlaySound(lua_State* L) { - auto* script = static_cast(lua_touserdata(L, lua_upvalueindex(1))); - const char* path = luaL_checkstring(L, 1); - bool loop = false; - if (lua_gettop(L) >= 2 && lua_isboolean(L, 2)) { - loop = lua_toboolean(L, 2); - } - std::string error; - if (!script->QueueAudioCommand(ScriptEngine::AudioCommandType::Effect, path, loop, error)) { - lua_pushnil(L); - lua_pushstring(L, error.c_str()); - return 2; - } - lua_pushboolean(L, 1); - return 1; -} - -int LuaGlmMatrixFromTransform(lua_State* L) { - std::array translation = detail::ReadVector3(L, 1); - std::array rotation = detail::ReadQuaternion(L, 2); - glm::vec3 pos = ToVec3(translation); - glm::quat quat = ToQuat(rotation); - glm::mat4 matrix = glm::translate(glm::mat4(1.0f), pos) * glm::mat4_cast(quat); - PushMatrix(L, matrix); - return 1; -} - -std::array IdentityMatrix() { - return {1.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 1.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f}; -} - -} // namespace - ScriptEngine::ScriptEngine(const std::filesystem::path& scriptPath, bool debugEnabled) : L_(luaL_newstate()), scriptDirectory_(scriptPath.parent_path()), debugEnabled_(debugEnabled), - physicsBridge_(std::make_unique()) { + physicsBridge_(std::make_unique()), + sceneManager_(std::make_unique(L_)), + shaderManager_(std::make_unique(L_)), + guiManager_(std::make_unique(L_)), + audioManager_(std::make_unique(scriptDirectory_)) { if (!L_) { throw std::runtime_error("Failed to create Lua state"); } + luaL_openlibs(L_); - lua_pushlightuserdata(L_, this); - lua_pushcclosure(L_, &LuaLoadMeshFromFile, 1); - lua_setglobal(L_, "load_mesh_from_file"); - lua_pushlightuserdata(L_, this); - lua_pushcclosure(L_, &LuaPhysicsCreateBox, 1); - lua_setglobal(L_, "physics_create_box"); - lua_pushlightuserdata(L_, this); - lua_pushcclosure(L_, &LuaPhysicsStepSimulation, 1); - lua_setglobal(L_, "physics_step_simulation"); - lua_pushlightuserdata(L_, this); - lua_pushcclosure(L_, &LuaPhysicsGetTransform, 1); - lua_setglobal(L_, "physics_get_transform"); - lua_pushlightuserdata(L_, this); - lua_pushcclosure(L_, &LuaGlmMatrixFromTransform, 1); - lua_setglobal(L_, "glm_matrix_from_transform"); - lua_pushlightuserdata(L_, this); - lua_pushcclosure(L_, &LuaAudioPlayBackground, 1); - lua_setglobal(L_, "audio_play_background"); - lua_pushlightuserdata(L_, this); - lua_pushcclosure(L_, &LuaAudioPlaySound, 1); - lua_setglobal(L_, "audio_play_sound"); + + LuaBindings::RegisterBindings(L_, this); + lua_pushboolean(L_, debugEnabled_); lua_setglobal(L_, "lua_debug"); + auto scriptDir = scriptPath.parent_path(); if (!scriptDir.empty()) { lua_getglobal(L_, "package"); @@ -416,276 +51,36 @@ ScriptEngine::ScriptEngine(const std::filesystem::path& scriptPath, bool debugEn } lua_pop(L_, 1); } + if (luaL_dofile(L_, scriptPath.string().c_str()) != LUA_OK) { - std::string message = LuaErrorMessage(L_); + std::string message = sdl3cpp::script::GetLuaError(L_); lua_pop(L_, 1); lua_close(L_); L_ = nullptr; throw std::runtime_error("Failed to load Lua script: " + message); } - - lua_getglobal(L_, "gui_input"); - if (!lua_isnil(L_, -1)) { - guiInputRef_ = luaL_ref(L_, LUA_REGISTRYINDEX); - } else { - lua_pop(L_, 1); - } - - lua_getglobal(L_, "get_gui_commands"); - if (lua_isfunction(L_, -1)) { - guiCommandsFnRef_ = luaL_ref(L_, LUA_REGISTRYINDEX); - } else { - lua_pop(L_, 1); - } } ScriptEngine::~ScriptEngine() { if (L_) { - if (guiInputRef_ != LUA_REFNIL) { - luaL_unref(L_, LUA_REGISTRYINDEX, guiInputRef_); - } - if (guiCommandsFnRef_ != LUA_REFNIL) { - luaL_unref(L_, LUA_REGISTRYINDEX, guiCommandsFnRef_); - } lua_close(L_); } } -std::vector ScriptEngine::LoadSceneObjects() { - lua_getglobal(L_, "get_scene_objects"); - if (!lua_isfunction(L_, -1)) { - lua_pop(L_, 1); - throw std::runtime_error("Lua function 'get_scene_objects' is missing"); - } - if (lua_pcall(L_, 0, 1, 0) != LUA_OK) { - std::string message = LuaErrorMessage(L_); - lua_pop(L_, 1); - throw std::runtime_error("Lua get_scene_objects failed: " + message); - } - if (!lua_istable(L_, -1)) { - lua_pop(L_, 1); - throw std::runtime_error("'get_scene_objects' did not return a table"); - } - - size_t count = lua_rawlen(L_, -1); - std::vector objects; - objects.reserve(count); - - for (size_t i = 1; i <= count; ++i) { - lua_rawgeti(L_, -1, static_cast(i)); - if (!lua_istable(L_, -1)) { - lua_pop(L_, 1); - throw std::runtime_error("Scene object at index " + std::to_string(i) + " is not a table"); - } - - SceneObject object; - lua_getfield(L_, -1, "vertices"); - object.vertices = ReadVertexArray(L_, -1); - lua_pop(L_, 1); - if (object.vertices.empty()) { - lua_pop(L_, 1); - throw std::runtime_error("Scene object " + std::to_string(i) + " must supply at least one vertex"); - } - - lua_getfield(L_, -1, "indices"); - object.indices = ReadIndexArray(L_, -1); - lua_pop(L_, 1); - if (object.indices.empty()) { - lua_pop(L_, 1); - throw std::runtime_error("Scene object " + std::to_string(i) + " must supply indices"); - } - - lua_getfield(L_, -1, "compute_model_matrix"); - if (lua_isfunction(L_, -1)) { - object.computeModelMatrixRef = luaL_ref(L_, LUA_REGISTRYINDEX); - } else { - lua_pop(L_, 1); - object.computeModelMatrixRef = LUA_REFNIL; - } - - lua_getfield(L_, -1, "shader_key"); - if (lua_isstring(L_, -1)) { - object.shaderKey = lua_tostring(L_, -1); - } - lua_pop(L_, 1); - - objects.push_back(std::move(object)); - lua_pop(L_, 1); - } - - lua_pop(L_, 1); - return objects; +std::vector ScriptEngine::LoadSceneObjects() { + return sceneManager_->LoadSceneObjects(); } std::array ScriptEngine::ComputeModelMatrix(int functionRef, float time) { - if (functionRef == LUA_REFNIL) { - lua_getglobal(L_, "compute_model_matrix"); - if (!lua_isfunction(L_, -1)) { - lua_pop(L_, 1); - return IdentityMatrix(); - } - } else { - lua_rawgeti(L_, LUA_REGISTRYINDEX, functionRef); - } - - lua_pushnumber(L_, time); - if (lua_pcall(L_, 1, 1, 0) != LUA_OK) { - std::string message = LuaErrorMessage(L_); - lua_pop(L_, 1); - throw std::runtime_error("Lua compute_model_matrix failed: " + message); - } - if (!lua_istable(L_, -1)) { - lua_pop(L_, 1); - throw std::runtime_error("'compute_model_matrix' did not return a table"); - } - - std::array matrix = detail::ReadMatrix(L_, -1); - lua_pop(L_, 1); - return matrix; + return sceneManager_->ComputeModelMatrix(functionRef, time); } std::array ScriptEngine::GetViewProjectionMatrix(float aspect) { - lua_getglobal(L_, "get_view_projection"); - if (!lua_isfunction(L_, -1)) { - lua_pop(L_, 1); - throw std::runtime_error("Lua function 'get_view_projection' is missing"); - } - lua_pushnumber(L_, aspect); - if (lua_pcall(L_, 1, 1, 0) != LUA_OK) { - std::string message = LuaErrorMessage(L_); - lua_pop(L_, 1); - throw std::runtime_error("Lua get_view_projection failed: " + message); - } - if (!lua_istable(L_, -1)) { - lua_pop(L_, 1); - throw std::runtime_error("'get_view_projection' did not return a table"); - } - std::array matrix = detail::ReadMatrix(L_, -1); - lua_pop(L_, 1); - return matrix; + return sceneManager_->GetViewProjectionMatrix(aspect); } -std::vector ScriptEngine::ReadVertexArray(lua_State* L, int index) { - int absIndex = lua_absindex(L, index); - if (!lua_istable(L, absIndex)) { - throw std::runtime_error("Expected table for vertex data"); - } - - size_t count = lua_rawlen(L, absIndex); - std::vector vertices; - vertices.reserve(count); - - for (size_t i = 1; i <= count; ++i) { - lua_rawgeti(L, absIndex, static_cast(i)); - if (!lua_istable(L, -1)) { - lua_pop(L, 1); - throw std::runtime_error("Vertex entry at index " + std::to_string(i) + " is not a table"); - } - - int vertexIndex = lua_gettop(L); - core::Vertex vertex{}; - - lua_getfield(L, vertexIndex, "position"); - vertex.position = detail::ReadVector3(L, -1); - lua_pop(L, 1); - - lua_getfield(L, vertexIndex, "color"); - vertex.color = detail::ReadVector3(L, -1); - lua_pop(L, 1); - - lua_pop(L, 1); - vertices.push_back(vertex); - } - - return vertices; -} - -std::vector ScriptEngine::ReadIndexArray(lua_State* L, int index) { - int absIndex = lua_absindex(L, index); - if (!lua_istable(L, absIndex)) { - throw std::runtime_error("Expected table for index data"); - } - - size_t count = lua_rawlen(L, absIndex); - std::vector indices; - indices.reserve(count); - - for (size_t i = 1; i <= count; ++i) { - lua_rawgeti(L, absIndex, static_cast(i)); - if (!lua_isinteger(L, -1)) { - lua_pop(L, 1); - throw std::runtime_error("Index entry at position " + std::to_string(i) + " is not an integer"); - } - lua_Integer value = lua_tointeger(L, -1); - lua_pop(L, 1); - if (value < 1) { - throw std::runtime_error("Index values must be 1 or greater"); - } - indices.push_back(static_cast(value - 1)); - } - - return indices; -} - -std::unordered_map ScriptEngine::LoadShaderPathsMap() { - lua_getglobal(L_, "get_shader_paths"); - if (!lua_isfunction(L_, -1)) { - lua_pop(L_, 1); - throw std::runtime_error("Lua function 'get_shader_paths' is missing"); - } - if (lua_pcall(L_, 0, 1, 0) != LUA_OK) { - std::string message = LuaErrorMessage(L_); - lua_pop(L_, 1); - throw std::runtime_error("Lua get_shader_paths failed: " + message); - } - if (!lua_istable(L_, -1)) { - lua_pop(L_, 1); - throw std::runtime_error("'get_shader_paths' did not return a table"); - } - - std::unordered_map shaderMap; - lua_pushnil(L_); - while (lua_next(L_, -2) != 0) { - if (lua_isstring(L_, -2) && lua_istable(L_, -1)) { - std::string key = lua_tostring(L_, -2); - shaderMap.emplace(key, ReadShaderPathsTable(L_, -1)); - } - lua_pop(L_, 1); - } - - lua_pop(L_, 1); - if (shaderMap.empty()) { - throw std::runtime_error("'get_shader_paths' did not return any shader variants"); - } - return shaderMap; -} - -ScriptEngine::ShaderPaths ScriptEngine::ReadShaderPathsTable(lua_State* L, int index) { - ShaderPaths paths; - int absIndex = lua_absindex(L, index); - - lua_getfield(L, absIndex, "vertex"); - if (!lua_isstring(L, -1)) { - lua_pop(L, 1); - throw std::runtime_error("Shader path 'vertex' must be a string"); - } - paths.vertex = lua_tostring(L, -1); - lua_pop(L, 1); - - lua_getfield(L, absIndex, "fragment"); - if (!lua_isstring(L, -1)) { - lua_pop(L, 1); - throw std::runtime_error("Shader path 'fragment' must be a string"); - } - paths.fragment = lua_tostring(L, -1); - lua_pop(L, 1); - - return paths; -} - -std::string ScriptEngine::LuaErrorMessage(lua_State* L) { - const char* message = lua_tostring(L, -1); - return message ? message : "unknown lua error"; +std::unordered_map ScriptEngine::LoadShaderPathsMap() { + return shaderManager_->LoadShaderPathsMap(); } PhysicsBridge& ScriptEngine::GetPhysicsBridge() { @@ -693,262 +88,32 @@ PhysicsBridge& ScriptEngine::GetPhysicsBridge() { } std::vector ScriptEngine::LoadGuiCommands() { - std::vector commands; - if (guiCommandsFnRef_ == LUA_REFNIL) { - return commands; - } - lua_rawgeti(L_, LUA_REGISTRYINDEX, guiCommandsFnRef_); - if (lua_pcall(L_, 0, 1, 0) != LUA_OK) { - std::string message = LuaErrorMessage(L_); - lua_pop(L_, 1); - throw std::runtime_error("Lua get_gui_commands failed: " + message); - } - if (!lua_istable(L_, -1)) { - lua_pop(L_, 1); - throw std::runtime_error("'get_gui_commands' did not return a table"); - } - - size_t count = lua_rawlen(L_, -1); - commands.reserve(count); - - for (size_t i = 1; i <= count; ++i) { - lua_rawgeti(L_, -1, static_cast(i)); - if (!lua_istable(L_, -1)) { - lua_pop(L_, 1); - throw std::runtime_error("GUI command at index " + std::to_string(i) + " is not a table"); - } - int commandIndex = lua_gettop(L_); - lua_getfield(L_, commandIndex, "type"); - const char* typeName = lua_tostring(L_, -1); - if (!typeName) { - lua_pop(L_, 2); - throw std::runtime_error("GUI command at index " + std::to_string(i) + " is missing a type"); - } - GuiCommand command{}; - if (std::strcmp(typeName, "rect") == 0) { - command.type = GuiCommand::Type::Rect; - command.rect = ReadRect(L_, commandIndex); - command.color = ReadColor(L_, commandIndex, GuiColor{0.0f, 0.0f, 0.0f, 1.0f}); - command.borderColor = ReadColor(L_, commandIndex, GuiColor{0.0f, 0.0f, 0.0f, 0.0f}); - lua_getfield(L_, commandIndex, "borderWidth"); - if (lua_isnumber(L_, -1)) { - command.borderWidth = static_cast(lua_tonumber(L_, -1)); - } - lua_pop(L_, 1); - } else if (std::strcmp(typeName, "text") == 0) { - command.type = GuiCommand::Type::Text; - ReadStringField(L_, commandIndex, "text", command.text); - lua_getfield(L_, commandIndex, "fontSize"); - if (lua_isnumber(L_, -1)) { - command.fontSize = static_cast(lua_tonumber(L_, -1)); - } - lua_pop(L_, 1); - std::string align; - if (ReadStringField(L_, commandIndex, "alignX", align)) { - command.alignX = align; - } - if (ReadStringField(L_, commandIndex, "alignY", align)) { - command.alignY = align; - } - lua_getfield(L_, commandIndex, "clipRect"); - if (lua_istable(L_, -1)) { - command.clipRect = ReadRect(L_, -1); - command.hasClipRect = true; - } - lua_pop(L_, 1); - lua_getfield(L_, commandIndex, "bounds"); - if (lua_istable(L_, -1)) { - command.bounds = ReadRect(L_, -1); - command.hasBounds = true; - } - lua_pop(L_, 1); - command.color = ReadColor(L_, commandIndex, GuiColor{1.0f, 1.0f, 1.0f, 1.0f}); - } else if (std::strcmp(typeName, "clip_push") == 0) { - command.type = GuiCommand::Type::ClipPush; - command.rect = ReadRect(L_, commandIndex); - } else if (std::strcmp(typeName, "clip_pop") == 0) { - command.type = GuiCommand::Type::ClipPop; - } else if (std::strcmp(typeName, "svg") == 0) { - command.type = GuiCommand::Type::Svg; - ReadStringField(L_, commandIndex, "path", command.svgPath); - command.rect = ReadRect(L_, commandIndex); - command.svgTint = ReadColor(L_, commandIndex, GuiColor{1.0f, 1.0f, 1.0f, 0.0f}); - lua_getfield(L_, commandIndex, "tint"); - if (lua_istable(L_, -1)) { - command.svgTint = ReadColor(L_, -1, command.svgTint); - } - lua_pop(L_, 1); - } - lua_pop(L_, 1); - lua_pop(L_, 1); - commands.push_back(std::move(command)); - } - - lua_pop(L_, 1); - return commands; + return guiManager_->LoadGuiCommands(); } void ScriptEngine::UpdateGuiInput(const GuiInputSnapshot& input) { - if (guiInputRef_ == LUA_REFNIL) { - return; - } - lua_rawgeti(L_, LUA_REGISTRYINDEX, guiInputRef_); - int stateIndex = lua_gettop(L_); - - lua_getfield(L_, stateIndex, "resetTransient"); - lua_pushvalue(L_, stateIndex); - lua_call(L_, 1, 0); - - lua_getfield(L_, stateIndex, "setMouse"); - lua_pushvalue(L_, stateIndex); - lua_pushnumber(L_, input.mouseX); - lua_pushnumber(L_, input.mouseY); - lua_pushboolean(L_, input.mouseDown); - lua_call(L_, 4, 0); - - lua_getfield(L_, stateIndex, "setWheel"); - lua_pushvalue(L_, stateIndex); - lua_pushnumber(L_, input.wheel); - lua_call(L_, 2, 0); - - if (!input.textInput.empty()) { - lua_getfield(L_, stateIndex, "addTextInput"); - lua_pushvalue(L_, stateIndex); - lua_pushstring(L_, input.textInput.c_str()); - lua_call(L_, 2, 0); - } - - for (const auto& [key, pressed] : input.keyStates) { - lua_getfield(L_, stateIndex, "setKey"); - lua_pushvalue(L_, stateIndex); - lua_pushstring(L_, key.c_str()); - lua_pushboolean(L_, pressed); - lua_call(L_, 3, 0); - } - - lua_pop(L_, 1); + guiManager_->UpdateGuiInput(input); } bool ScriptEngine::HasGuiCommands() const { - return guiCommandsFnRef_ != LUA_REFNIL; + return guiManager_->HasGuiCommands(); } std::filesystem::path ScriptEngine::GetScriptDirectory() const { return scriptDirectory_; } -GuiCommand::RectData ScriptEngine::ReadRect(lua_State* L, int index) { - GuiCommand::RectData rect{}; - if (!lua_istable(L, index)) { - return rect; - } - int absIndex = lua_absindex(L, index); - auto readField = [&](const char* name, float defaultValue) -> float { - lua_getfield(L, absIndex, name); - float value = defaultValue; - if (lua_isnumber(L, -1)) { - value = static_cast(lua_tonumber(L, -1)); - } - lua_pop(L, 1); - return value; - }; - rect.x = readField("x", rect.x); - rect.y = readField("y", rect.y); - rect.width = readField("width", rect.width); - rect.height = readField("height", rect.height); - return rect; -} - -GuiColor ScriptEngine::ReadColor(lua_State* L, int index, const GuiColor& defaultColor) { - GuiColor color = defaultColor; - if (!lua_istable(L, index)) { - return color; - } - int absIndex = lua_absindex(L, index); - for (int component = 0; component < 4; ++component) { - lua_rawgeti(L, absIndex, component + 1); - if (lua_isnumber(L, -1)) { - float value = static_cast(lua_tonumber(L, -1)); - switch (component) { - case 0: color.r = value; break; - case 1: color.g = value; break; - case 2: color.b = value; break; - case 3: color.a = value; break; - } - } - lua_pop(L, 1); - } - return color; -} - -bool ScriptEngine::ReadStringField(lua_State* L, int index, const char* name, std::string& outString) { - int absIndex = lua_absindex(L, index); - lua_getfield(L, absIndex, name); - if (lua_isstring(L, -1)) { - outString = lua_tostring(L, -1); - lua_pop(L, 1); - return true; - } - lua_pop(L, 1); - return false; -} - void ScriptEngine::SetAudioPlayer(app::AudioPlayer* audioPlayer) { - audioPlayer_ = audioPlayer; - if (!audioPlayer_) { - return; - } - for (const auto& command : pendingAudioCommands_) { - try { - ExecuteAudioCommand(audioPlayer_, command); - } catch (const std::exception& exc) { - std::cerr << "AudioPlayer: " << exc.what() << '\n'; - } - } - pendingAudioCommands_.clear(); + audioManager_->SetAudioPlayer(audioPlayer); } -bool ScriptEngine::QueueAudioCommand(AudioCommandType type, std::string path, bool loop, std::string& error) { - if (audioPlayer_) { - try { - AudioCommand command{type, std::move(path), loop}; - ExecuteAudioCommand(audioPlayer_, command); - return true; - } catch (const std::exception& exc) { - error = exc.what(); - return false; - } - } - pendingAudioCommands_.push_back(AudioCommand{type, std::move(path), loop}); - return true; +bool ScriptEngine::QueueAudioCommand(AudioManager::AudioCommandType type, std::string path, bool loop, std::string& error) { + return audioManager_->QueueAudioCommand(type, path, loop, error); } -void ScriptEngine::ExecuteAudioCommand(app::AudioPlayer* player, const AudioCommand& command) { - auto resolved = ResolveScriptPath(command.path); - if (!std::filesystem::exists(resolved)) { - throw std::runtime_error("Audio file not found: " + resolved.string()); - } - switch (command.type) { - case AudioCommandType::Background: - player->PlayBackground(resolved, command.loop); - break; - case AudioCommandType::Effect: - player->PlayEffect(resolved, command.loop); - break; - } +std::string ScriptEngine::GetLuaError() { + const char* message = lua_tostring(L_, -1); + return message ? message : "unknown lua error"; } -std::filesystem::path ScriptEngine::ResolveScriptPath(const std::string& requested) const { - std::filesystem::path resolved(requested); - if (!resolved.is_absolute()) { - resolved = scriptDirectory_ / resolved; - } - std::error_code ec; - auto canonical = std::filesystem::weakly_canonical(resolved, ec); - if (!ec) { - resolved = canonical; - } - return resolved; -} - -} // namespace sdl3cpp::script +} // namespace sdl3cpp::script \ No newline at end of file diff --git a/src/script/script_engine.cpp.backup b/src/script/script_engine.cpp.backup new file mode 100644 index 0000000..4af61d2 --- /dev/null +++ b/src/script/script_engine.cpp.backup @@ -0,0 +1,954 @@ +#include "script/script_engine.hpp" +#include "app/audio_player.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace sdl3cpp::script { + +namespace detail { + +std::array ReadVector3(lua_State* L, int index) { + std::array result{}; + int absIndex = lua_absindex(L, index); + size_t len = lua_rawlen(L, absIndex); + if (len != 3) { + throw std::runtime_error("Expected vector with 3 components"); + } + for (size_t i = 1; i <= 3; ++i) { + lua_rawgeti(L, absIndex, static_cast(i)); + if (!lua_isnumber(L, -1)) { + lua_pop(L, 1); + throw std::runtime_error("Vector component is not a number"); + } + result[i - 1] = static_cast(lua_tonumber(L, -1)); + lua_pop(L, 1); + } + return result; +} + +std::array ReadMatrix(lua_State* L, int index) { + std::array result{}; + int absIndex = lua_absindex(L, index); + size_t len = lua_rawlen(L, absIndex); + if (len != 16) { + throw std::runtime_error("Expected 4x4 matrix with 16 components"); + } + for (size_t i = 1; i <= 16; ++i) { + lua_rawgeti(L, absIndex, static_cast(i)); + if (!lua_isnumber(L, -1)) { + lua_pop(L, 1); + throw std::runtime_error("Matrix component is not a number"); + } + result[i - 1] = static_cast(lua_tonumber(L, -1)); + lua_pop(L, 1); + } + return result; +} + +std::array ReadQuaternion(lua_State* L, int index) { + std::array result{}; + int absIndex = lua_absindex(L, index); + size_t len = lua_rawlen(L, absIndex); + if (len != 4) { + throw std::runtime_error("Expected quaternion with 4 components"); + } + for (size_t i = 1; i <= 4; ++i) { + lua_rawgeti(L, absIndex, static_cast(i)); + if (!lua_isnumber(L, -1)) { + lua_pop(L, 1); + throw std::runtime_error("Quaternion component is not a number"); + } + result[i - 1] = static_cast(lua_tonumber(L, -1)); + lua_pop(L, 1); + } + return result; +} + +} // namespace detail + +namespace { + +struct MeshPayload { + std::vector> positions; + std::vector> colors; + std::vector indices; +}; + +bool TryLoadMeshPayload(const ScriptEngine* script, + const std::string& requestedPath, + MeshPayload& payload, + std::string& error) { + std::filesystem::path resolved(requestedPath); + if (!resolved.is_absolute()) { + resolved = script->GetScriptDirectory() / resolved; + } + std::error_code ec; + resolved = std::filesystem::weakly_canonical(resolved, ec); + if (ec) { + error = "Failed to resolve mesh path: " + ec.message(); + return false; + } + if (!std::filesystem::exists(resolved)) { + error = "Mesh file not found: " + resolved.string(); + return false; + } + + Assimp::Importer importer; + const aiScene* scene = importer.ReadFile( + resolved.string(), + aiProcess_Triangulate | aiProcess_JoinIdenticalVertices | aiProcess_PreTransformVertices); + if (!scene) { + error = importer.GetErrorString() ? importer.GetErrorString() : "Assimp failed to load mesh"; + return false; + } + if (scene->mNumMeshes == 0) { + error = "Scene contains no meshes"; + return false; + } + + const aiMesh* mesh = scene->mMeshes[0]; + if (!mesh->mNumVertices) { + error = "Mesh contains no vertices"; + return false; + } + + payload.positions.reserve(mesh->mNumVertices); + payload.colors.reserve(mesh->mNumVertices); + payload.indices.reserve(mesh->mNumFaces * 3); + + aiColor3D defaultColor(0.6f, 0.8f, 1.0f); + + aiColor3D materialColor = defaultColor; + if (mesh->mMaterialIndex < scene->mNumMaterials) { + const aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex]; + aiColor4D diffuse; + if (material && material->Get(AI_MATKEY_COLOR_DIFFUSE, diffuse) == AI_SUCCESS) { + materialColor = aiColor3D(diffuse.r, diffuse.g, diffuse.b); + } + } + + for (unsigned i = 0; i < mesh->mNumVertices; ++i) { + const aiVector3D& vertex = mesh->mVertices[i]; + payload.positions.push_back({vertex.x, vertex.y, vertex.z}); + + aiColor3D color = materialColor; + if (mesh->HasVertexColors(0) && mesh->mColors[0]) { + const aiColor4D& vertexColor = mesh->mColors[0][i]; + color = aiColor3D(vertexColor.r, vertexColor.g, vertexColor.b); + } + payload.colors.push_back({color.r, color.g, color.b}); + } + + for (unsigned faceIndex = 0; faceIndex < mesh->mNumFaces; ++faceIndex) { + const aiFace& face = mesh->mFaces[faceIndex]; + if (face.mNumIndices != 3) { + continue; + } + payload.indices.push_back(face.mIndices[0]); + payload.indices.push_back(face.mIndices[1]); + payload.indices.push_back(face.mIndices[2]); + } + + if (payload.indices.empty()) { + error = "Mesh contains no triangle faces"; + return false; + } + return true; +} + +glm::vec3 ToVec3(const std::array& value) { + return glm::vec3(value[0], value[1], value[2]); +} + +glm::quat ToQuat(const std::array& value) { + return glm::quat(value[3], value[0], value[1], value[2]); +} + +void PushMatrix(lua_State* L, const glm::mat4& matrix) { + lua_newtable(L); + const float* ptr = glm::value_ptr(matrix); + for (int i = 0; i < 16; ++i) { + lua_pushnumber(L, ptr[i]); + lua_rawseti(L, -2, i + 1); + } +} + +int PushMeshToLua(lua_State* L, const MeshPayload& payload) { + lua_newtable(L); + + lua_newtable(L); + for (size_t vertexIndex = 0; vertexIndex < payload.positions.size(); ++vertexIndex) { + lua_newtable(L); + + lua_newtable(L); + for (int component = 0; component < 3; ++component) { + lua_pushnumber(L, payload.positions[vertexIndex][component]); + lua_rawseti(L, -2, component + 1); + } + lua_setfield(L, -2, "position"); + + lua_newtable(L); + for (int component = 0; component < 3; ++component) { + lua_pushnumber(L, payload.colors[vertexIndex][component]); + lua_rawseti(L, -2, component + 1); + } + lua_setfield(L, -2, "color"); + + lua_rawseti(L, -2, static_cast(vertexIndex + 1)); + } + lua_setfield(L, -2, "vertices"); + + lua_newtable(L); + for (size_t index = 0; index < payload.indices.size(); ++index) { + lua_pushinteger(L, static_cast(payload.indices[index]) + 1); + lua_rawseti(L, -2, static_cast(index + 1)); + } + lua_setfield(L, -2, "indices"); + + return 1; +} + +int LuaLoadMeshFromFile(lua_State* L) { + auto* script = static_cast(lua_touserdata(L, lua_upvalueindex(1))); + const char* path = luaL_checkstring(L, 1); + MeshPayload payload; + std::string error; + if (!TryLoadMeshPayload(script, path, payload, error)) { + lua_pushnil(L); + lua_pushstring(L, error.c_str()); + return 2; + } + PushMeshToLua(L, payload); + lua_pushnil(L); + return 2; +} + +int LuaPhysicsCreateBox(lua_State* L) { + auto* script = static_cast(lua_touserdata(L, lua_upvalueindex(1))); + const char* name = luaL_checkstring(L, 1); + if (!lua_istable(L, 2) || !lua_istable(L, 4) || !lua_istable(L, 5)) { + luaL_error(L, "physics_create_box expects vector tables for half extents, origin, and rotation"); + } + std::array halfExtents = detail::ReadVector3(L, 2); + float mass = static_cast(luaL_checknumber(L, 3)); + std::array origin = detail::ReadVector3(L, 4); + std::array rotation = detail::ReadQuaternion(L, 5); + + btTransform transform; + transform.setIdentity(); + transform.setOrigin(btVector3(origin[0], origin[1], origin[2])); + transform.setRotation(btQuaternion(rotation[0], rotation[1], rotation[2], rotation[3])); + + std::string error; + if (!script->GetPhysicsBridge().addBoxRigidBody( + name, + btVector3(halfExtents[0], halfExtents[1], halfExtents[2]), + mass, + transform, + error)) { + lua_pushnil(L); + lua_pushstring(L, error.c_str()); + return 2; + } + + lua_pushboolean(L, 1); + return 1; +} + +int LuaPhysicsStepSimulation(lua_State* L) { + auto* script = static_cast(lua_touserdata(L, lua_upvalueindex(1))); + float deltaTime = static_cast(luaL_checknumber(L, 1)); + int steps = script->GetPhysicsBridge().stepSimulation(deltaTime); + lua_pushinteger(L, steps); + return 1; +} + +int LuaPhysicsGetTransform(lua_State* L) { + auto* script = static_cast(lua_touserdata(L, lua_upvalueindex(1))); + const char* name = luaL_checkstring(L, 1); + btTransform transform; + std::string error; + if (!script->GetPhysicsBridge().getRigidBodyTransform(name, transform, error)) { + lua_pushnil(L); + lua_pushstring(L, error.c_str()); + return 2; + } + + lua_newtable(L); + lua_newtable(L); + const btVector3& origin = transform.getOrigin(); + lua_pushnumber(L, origin.x()); + lua_rawseti(L, -2, 1); + lua_pushnumber(L, origin.y()); + lua_rawseti(L, -2, 2); + lua_pushnumber(L, origin.z()); + lua_rawseti(L, -2, 3); + lua_setfield(L, -2, "position"); + + lua_newtable(L); + const btQuaternion& orientation = transform.getRotation(); + lua_pushnumber(L, orientation.x()); + lua_rawseti(L, -2, 1); + lua_pushnumber(L, orientation.y()); + lua_rawseti(L, -2, 2); + lua_pushnumber(L, orientation.z()); + lua_rawseti(L, -2, 3); + lua_pushnumber(L, orientation.w()); + lua_rawseti(L, -2, 4); + lua_setfield(L, -2, "rotation"); + + return 1; +} + +int LuaAudioPlayBackground(lua_State* L) { + auto* script = static_cast(lua_touserdata(L, lua_upvalueindex(1))); + const char* path = luaL_checkstring(L, 1); + bool loop = true; + if (lua_gettop(L) >= 2 && lua_isboolean(L, 2)) { + loop = lua_toboolean(L, 2); + } + std::string error; + if (!script->QueueAudioCommand(ScriptEngine::AudioCommandType::Background, path, loop, error)) { + lua_pushnil(L); + lua_pushstring(L, error.c_str()); + return 2; + } + lua_pushboolean(L, 1); + return 1; +} + +int LuaAudioPlaySound(lua_State* L) { + auto* script = static_cast(lua_touserdata(L, lua_upvalueindex(1))); + const char* path = luaL_checkstring(L, 1); + bool loop = false; + if (lua_gettop(L) >= 2 && lua_isboolean(L, 2)) { + loop = lua_toboolean(L, 2); + } + std::string error; + if (!script->QueueAudioCommand(ScriptEngine::AudioCommandType::Effect, path, loop, error)) { + lua_pushnil(L); + lua_pushstring(L, error.c_str()); + return 2; + } + lua_pushboolean(L, 1); + return 1; +} + +int LuaGlmMatrixFromTransform(lua_State* L) { + std::array translation = detail::ReadVector3(L, 1); + std::array rotation = detail::ReadQuaternion(L, 2); + glm::vec3 pos = ToVec3(translation); + glm::quat quat = ToQuat(rotation); + glm::mat4 matrix = glm::translate(glm::mat4(1.0f), pos) * glm::mat4_cast(quat); + PushMatrix(L, matrix); + return 1; +} + +std::array IdentityMatrix() { + return {1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f}; +} + +} // namespace + +ScriptEngine::ScriptEngine(const std::filesystem::path& scriptPath, bool debugEnabled) + : L_(luaL_newstate()), + scriptDirectory_(scriptPath.parent_path()), + debugEnabled_(debugEnabled), + physicsBridge_(std::make_unique()) { + if (!L_) { + throw std::runtime_error("Failed to create Lua state"); + } + luaL_openlibs(L_); + lua_pushlightuserdata(L_, this); + lua_pushcclosure(L_, &LuaLoadMeshFromFile, 1); + lua_setglobal(L_, "load_mesh_from_file"); + lua_pushlightuserdata(L_, this); + lua_pushcclosure(L_, &LuaPhysicsCreateBox, 1); + lua_setglobal(L_, "physics_create_box"); + lua_pushlightuserdata(L_, this); + lua_pushcclosure(L_, &LuaPhysicsStepSimulation, 1); + lua_setglobal(L_, "physics_step_simulation"); + lua_pushlightuserdata(L_, this); + lua_pushcclosure(L_, &LuaPhysicsGetTransform, 1); + lua_setglobal(L_, "physics_get_transform"); + lua_pushlightuserdata(L_, this); + lua_pushcclosure(L_, &LuaGlmMatrixFromTransform, 1); + lua_setglobal(L_, "glm_matrix_from_transform"); + lua_pushlightuserdata(L_, this); + lua_pushcclosure(L_, &LuaAudioPlayBackground, 1); + lua_setglobal(L_, "audio_play_background"); + lua_pushlightuserdata(L_, this); + lua_pushcclosure(L_, &LuaAudioPlaySound, 1); + lua_setglobal(L_, "audio_play_sound"); + lua_pushboolean(L_, debugEnabled_); + lua_setglobal(L_, "lua_debug"); + auto scriptDir = scriptPath.parent_path(); + if (!scriptDir.empty()) { + lua_getglobal(L_, "package"); + if (lua_istable(L_, -1)) { + lua_getfield(L_, -1, "path"); + const char* currentPath = lua_tostring(L_, -1); + std::string newPath = scriptDir.string() + "/?.lua;"; + if (currentPath) { + newPath += currentPath; + } + lua_pop(L_, 1); + lua_pushstring(L_, newPath.c_str()); + lua_setfield(L_, -2, "path"); + } + lua_pop(L_, 1); + } + if (luaL_dofile(L_, scriptPath.string().c_str()) != LUA_OK) { + std::string message = LuaErrorMessage(L_); + lua_pop(L_, 1); + lua_close(L_); + L_ = nullptr; + throw std::runtime_error("Failed to load Lua script: " + message); + } + + lua_getglobal(L_, "gui_input"); + if (!lua_isnil(L_, -1)) { + guiInputRef_ = luaL_ref(L_, LUA_REGISTRYINDEX); + } else { + lua_pop(L_, 1); + } + + lua_getglobal(L_, "get_gui_commands"); + if (lua_isfunction(L_, -1)) { + guiCommandsFnRef_ = luaL_ref(L_, LUA_REGISTRYINDEX); + } else { + lua_pop(L_, 1); + } +} + +ScriptEngine::~ScriptEngine() { + if (L_) { + if (guiInputRef_ != LUA_REFNIL) { + luaL_unref(L_, LUA_REGISTRYINDEX, guiInputRef_); + } + if (guiCommandsFnRef_ != LUA_REFNIL) { + luaL_unref(L_, LUA_REGISTRYINDEX, guiCommandsFnRef_); + } + lua_close(L_); + } +} + +std::vector ScriptEngine::LoadSceneObjects() { + lua_getglobal(L_, "get_scene_objects"); + if (!lua_isfunction(L_, -1)) { + lua_pop(L_, 1); + throw std::runtime_error("Lua function 'get_scene_objects' is missing"); + } + if (lua_pcall(L_, 0, 1, 0) != LUA_OK) { + std::string message = LuaErrorMessage(L_); + lua_pop(L_, 1); + throw std::runtime_error("Lua get_scene_objects failed: " + message); + } + if (!lua_istable(L_, -1)) { + lua_pop(L_, 1); + throw std::runtime_error("'get_scene_objects' did not return a table"); + } + + size_t count = lua_rawlen(L_, -1); + std::vector objects; + objects.reserve(count); + + for (size_t i = 1; i <= count; ++i) { + lua_rawgeti(L_, -1, static_cast(i)); + if (!lua_istable(L_, -1)) { + lua_pop(L_, 1); + throw std::runtime_error("Scene object at index " + std::to_string(i) + " is not a table"); + } + + SceneObject object; + lua_getfield(L_, -1, "vertices"); + object.vertices = ReadVertexArray(L_, -1); + lua_pop(L_, 1); + if (object.vertices.empty()) { + lua_pop(L_, 1); + throw std::runtime_error("Scene object " + std::to_string(i) + " must supply at least one vertex"); + } + + lua_getfield(L_, -1, "indices"); + object.indices = ReadIndexArray(L_, -1); + lua_pop(L_, 1); + if (object.indices.empty()) { + lua_pop(L_, 1); + throw std::runtime_error("Scene object " + std::to_string(i) + " must supply indices"); + } + + lua_getfield(L_, -1, "compute_model_matrix"); + if (lua_isfunction(L_, -1)) { + object.computeModelMatrixRef = luaL_ref(L_, LUA_REGISTRYINDEX); + } else { + lua_pop(L_, 1); + object.computeModelMatrixRef = LUA_REFNIL; + } + + lua_getfield(L_, -1, "shader_key"); + if (lua_isstring(L_, -1)) { + object.shaderKey = lua_tostring(L_, -1); + } + lua_pop(L_, 1); + + objects.push_back(std::move(object)); + lua_pop(L_, 1); + } + + lua_pop(L_, 1); + return objects; +} + +std::array ScriptEngine::ComputeModelMatrix(int functionRef, float time) { + if (functionRef == LUA_REFNIL) { + lua_getglobal(L_, "compute_model_matrix"); + if (!lua_isfunction(L_, -1)) { + lua_pop(L_, 1); + return IdentityMatrix(); + } + } else { + lua_rawgeti(L_, LUA_REGISTRYINDEX, functionRef); + } + + lua_pushnumber(L_, time); + if (lua_pcall(L_, 1, 1, 0) != LUA_OK) { + std::string message = LuaErrorMessage(L_); + lua_pop(L_, 1); + throw std::runtime_error("Lua compute_model_matrix failed: " + message); + } + if (!lua_istable(L_, -1)) { + lua_pop(L_, 1); + throw std::runtime_error("'compute_model_matrix' did not return a table"); + } + + std::array matrix = detail::ReadMatrix(L_, -1); + lua_pop(L_, 1); + return matrix; +} + +std::array ScriptEngine::GetViewProjectionMatrix(float aspect) { + lua_getglobal(L_, "get_view_projection"); + if (!lua_isfunction(L_, -1)) { + lua_pop(L_, 1); + throw std::runtime_error("Lua function 'get_view_projection' is missing"); + } + lua_pushnumber(L_, aspect); + if (lua_pcall(L_, 1, 1, 0) != LUA_OK) { + std::string message = LuaErrorMessage(L_); + lua_pop(L_, 1); + throw std::runtime_error("Lua get_view_projection failed: " + message); + } + if (!lua_istable(L_, -1)) { + lua_pop(L_, 1); + throw std::runtime_error("'get_view_projection' did not return a table"); + } + std::array matrix = detail::ReadMatrix(L_, -1); + lua_pop(L_, 1); + return matrix; +} + +std::vector ScriptEngine::ReadVertexArray(lua_State* L, int index) { + int absIndex = lua_absindex(L, index); + if (!lua_istable(L, absIndex)) { + throw std::runtime_error("Expected table for vertex data"); + } + + size_t count = lua_rawlen(L, absIndex); + std::vector vertices; + vertices.reserve(count); + + for (size_t i = 1; i <= count; ++i) { + lua_rawgeti(L, absIndex, static_cast(i)); + if (!lua_istable(L, -1)) { + lua_pop(L, 1); + throw std::runtime_error("Vertex entry at index " + std::to_string(i) + " is not a table"); + } + + int vertexIndex = lua_gettop(L); + core::Vertex vertex{}; + + lua_getfield(L, vertexIndex, "position"); + vertex.position = detail::ReadVector3(L, -1); + lua_pop(L, 1); + + lua_getfield(L, vertexIndex, "color"); + vertex.color = detail::ReadVector3(L, -1); + lua_pop(L, 1); + + lua_pop(L, 1); + vertices.push_back(vertex); + } + + return vertices; +} + +std::vector ScriptEngine::ReadIndexArray(lua_State* L, int index) { + int absIndex = lua_absindex(L, index); + if (!lua_istable(L, absIndex)) { + throw std::runtime_error("Expected table for index data"); + } + + size_t count = lua_rawlen(L, absIndex); + std::vector indices; + indices.reserve(count); + + for (size_t i = 1; i <= count; ++i) { + lua_rawgeti(L, absIndex, static_cast(i)); + if (!lua_isinteger(L, -1)) { + lua_pop(L, 1); + throw std::runtime_error("Index entry at position " + std::to_string(i) + " is not an integer"); + } + lua_Integer value = lua_tointeger(L, -1); + lua_pop(L, 1); + if (value < 1) { + throw std::runtime_error("Index values must be 1 or greater"); + } + indices.push_back(static_cast(value - 1)); + } + + return indices; +} + +std::unordered_map ScriptEngine::LoadShaderPathsMap() { + lua_getglobal(L_, "get_shader_paths"); + if (!lua_isfunction(L_, -1)) { + lua_pop(L_, 1); + throw std::runtime_error("Lua function 'get_shader_paths' is missing"); + } + if (lua_pcall(L_, 0, 1, 0) != LUA_OK) { + std::string message = LuaErrorMessage(L_); + lua_pop(L_, 1); + throw std::runtime_error("Lua get_shader_paths failed: " + message); + } + if (!lua_istable(L_, -1)) { + lua_pop(L_, 1); + throw std::runtime_error("'get_shader_paths' did not return a table"); + } + + std::unordered_map shaderMap; + lua_pushnil(L_); + while (lua_next(L_, -2) != 0) { + if (lua_isstring(L_, -2) && lua_istable(L_, -1)) { + std::string key = lua_tostring(L_, -2); + shaderMap.emplace(key, ReadShaderPathsTable(L_, -1)); + } + lua_pop(L_, 1); + } + + lua_pop(L_, 1); + if (shaderMap.empty()) { + throw std::runtime_error("'get_shader_paths' did not return any shader variants"); + } + return shaderMap; +} + +ScriptEngine::ShaderPaths ScriptEngine::ReadShaderPathsTable(lua_State* L, int index) { + ShaderPaths paths; + int absIndex = lua_absindex(L, index); + + lua_getfield(L, absIndex, "vertex"); + if (!lua_isstring(L, -1)) { + lua_pop(L, 1); + throw std::runtime_error("Shader path 'vertex' must be a string"); + } + paths.vertex = lua_tostring(L, -1); + lua_pop(L, 1); + + lua_getfield(L, absIndex, "fragment"); + if (!lua_isstring(L, -1)) { + lua_pop(L, 1); + throw std::runtime_error("Shader path 'fragment' must be a string"); + } + paths.fragment = lua_tostring(L, -1); + lua_pop(L, 1); + + return paths; +} + +std::string ScriptEngine::LuaErrorMessage(lua_State* L) { + const char* message = lua_tostring(L, -1); + return message ? message : "unknown lua error"; +} + +PhysicsBridge& ScriptEngine::GetPhysicsBridge() { + return *physicsBridge_; +} + +std::vector ScriptEngine::LoadGuiCommands() { + std::vector commands; + if (guiCommandsFnRef_ == LUA_REFNIL) { + return commands; + } + lua_rawgeti(L_, LUA_REGISTRYINDEX, guiCommandsFnRef_); + if (lua_pcall(L_, 0, 1, 0) != LUA_OK) { + std::string message = LuaErrorMessage(L_); + lua_pop(L_, 1); + throw std::runtime_error("Lua get_gui_commands failed: " + message); + } + if (!lua_istable(L_, -1)) { + lua_pop(L_, 1); + throw std::runtime_error("'get_gui_commands' did not return a table"); + } + + size_t count = lua_rawlen(L_, -1); + commands.reserve(count); + + for (size_t i = 1; i <= count; ++i) { + lua_rawgeti(L_, -1, static_cast(i)); + if (!lua_istable(L_, -1)) { + lua_pop(L_, 1); + throw std::runtime_error("GUI command at index " + std::to_string(i) + " is not a table"); + } + int commandIndex = lua_gettop(L_); + lua_getfield(L_, commandIndex, "type"); + const char* typeName = lua_tostring(L_, -1); + if (!typeName) { + lua_pop(L_, 2); + throw std::runtime_error("GUI command at index " + std::to_string(i) + " is missing a type"); + } + GuiCommand command{}; + if (std::strcmp(typeName, "rect") == 0) { + command.type = GuiCommand::Type::Rect; + command.rect = ReadRect(L_, commandIndex); + command.color = ReadColor(L_, commandIndex, GuiColor{0.0f, 0.0f, 0.0f, 1.0f}); + command.borderColor = ReadColor(L_, commandIndex, GuiColor{0.0f, 0.0f, 0.0f, 0.0f}); + lua_getfield(L_, commandIndex, "borderWidth"); + if (lua_isnumber(L_, -1)) { + command.borderWidth = static_cast(lua_tonumber(L_, -1)); + } + lua_pop(L_, 1); + } else if (std::strcmp(typeName, "text") == 0) { + command.type = GuiCommand::Type::Text; + ReadStringField(L_, commandIndex, "text", command.text); + lua_getfield(L_, commandIndex, "fontSize"); + if (lua_isnumber(L_, -1)) { + command.fontSize = static_cast(lua_tonumber(L_, -1)); + } + lua_pop(L_, 1); + std::string align; + if (ReadStringField(L_, commandIndex, "alignX", align)) { + command.alignX = align; + } + if (ReadStringField(L_, commandIndex, "alignY", align)) { + command.alignY = align; + } + lua_getfield(L_, commandIndex, "clipRect"); + if (lua_istable(L_, -1)) { + command.clipRect = ReadRect(L_, -1); + command.hasClipRect = true; + } + lua_pop(L_, 1); + lua_getfield(L_, commandIndex, "bounds"); + if (lua_istable(L_, -1)) { + command.bounds = ReadRect(L_, -1); + command.hasBounds = true; + } + lua_pop(L_, 1); + command.color = ReadColor(L_, commandIndex, GuiColor{1.0f, 1.0f, 1.0f, 1.0f}); + } else if (std::strcmp(typeName, "clip_push") == 0) { + command.type = GuiCommand::Type::ClipPush; + command.rect = ReadRect(L_, commandIndex); + } else if (std::strcmp(typeName, "clip_pop") == 0) { + command.type = GuiCommand::Type::ClipPop; + } else if (std::strcmp(typeName, "svg") == 0) { + command.type = GuiCommand::Type::Svg; + ReadStringField(L_, commandIndex, "path", command.svgPath); + command.rect = ReadRect(L_, commandIndex); + command.svgTint = ReadColor(L_, commandIndex, GuiColor{1.0f, 1.0f, 1.0f, 0.0f}); + lua_getfield(L_, commandIndex, "tint"); + if (lua_istable(L_, -1)) { + command.svgTint = ReadColor(L_, -1, command.svgTint); + } + lua_pop(L_, 1); + } + lua_pop(L_, 1); + lua_pop(L_, 1); + commands.push_back(std::move(command)); + } + + lua_pop(L_, 1); + return commands; +} + +void ScriptEngine::UpdateGuiInput(const GuiInputSnapshot& input) { + if (guiInputRef_ == LUA_REFNIL) { + return; + } + lua_rawgeti(L_, LUA_REGISTRYINDEX, guiInputRef_); + int stateIndex = lua_gettop(L_); + + lua_getfield(L_, stateIndex, "resetTransient"); + lua_pushvalue(L_, stateIndex); + lua_call(L_, 1, 0); + + lua_getfield(L_, stateIndex, "setMouse"); + lua_pushvalue(L_, stateIndex); + lua_pushnumber(L_, input.mouseX); + lua_pushnumber(L_, input.mouseY); + lua_pushboolean(L_, input.mouseDown); + lua_call(L_, 4, 0); + + lua_getfield(L_, stateIndex, "setWheel"); + lua_pushvalue(L_, stateIndex); + lua_pushnumber(L_, input.wheel); + lua_call(L_, 2, 0); + + if (!input.textInput.empty()) { + lua_getfield(L_, stateIndex, "addTextInput"); + lua_pushvalue(L_, stateIndex); + lua_pushstring(L_, input.textInput.c_str()); + lua_call(L_, 2, 0); + } + + for (const auto& [key, pressed] : input.keyStates) { + lua_getfield(L_, stateIndex, "setKey"); + lua_pushvalue(L_, stateIndex); + lua_pushstring(L_, key.c_str()); + lua_pushboolean(L_, pressed); + lua_call(L_, 3, 0); + } + + lua_pop(L_, 1); +} + +bool ScriptEngine::HasGuiCommands() const { + return guiCommandsFnRef_ != LUA_REFNIL; +} + +std::filesystem::path ScriptEngine::GetScriptDirectory() const { + return scriptDirectory_; +} + +GuiCommand::RectData ScriptEngine::ReadRect(lua_State* L, int index) { + GuiCommand::RectData rect{}; + if (!lua_istable(L, index)) { + return rect; + } + int absIndex = lua_absindex(L, index); + auto readField = [&](const char* name, float defaultValue) -> float { + lua_getfield(L, absIndex, name); + float value = defaultValue; + if (lua_isnumber(L, -1)) { + value = static_cast(lua_tonumber(L, -1)); + } + lua_pop(L, 1); + return value; + }; + rect.x = readField("x", rect.x); + rect.y = readField("y", rect.y); + rect.width = readField("width", rect.width); + rect.height = readField("height", rect.height); + return rect; +} + +GuiColor ScriptEngine::ReadColor(lua_State* L, int index, const GuiColor& defaultColor) { + GuiColor color = defaultColor; + if (!lua_istable(L, index)) { + return color; + } + int absIndex = lua_absindex(L, index); + for (int component = 0; component < 4; ++component) { + lua_rawgeti(L, absIndex, component + 1); + if (lua_isnumber(L, -1)) { + float value = static_cast(lua_tonumber(L, -1)); + switch (component) { + case 0: color.r = value; break; + case 1: color.g = value; break; + case 2: color.b = value; break; + case 3: color.a = value; break; + } + } + lua_pop(L, 1); + } + return color; +} + +bool ScriptEngine::ReadStringField(lua_State* L, int index, const char* name, std::string& outString) { + int absIndex = lua_absindex(L, index); + lua_getfield(L, absIndex, name); + if (lua_isstring(L, -1)) { + outString = lua_tostring(L, -1); + lua_pop(L, 1); + return true; + } + lua_pop(L, 1); + return false; +} + +void ScriptEngine::SetAudioPlayer(app::AudioPlayer* audioPlayer) { + audioPlayer_ = audioPlayer; + if (!audioPlayer_) { + return; + } + for (const auto& command : pendingAudioCommands_) { + try { + ExecuteAudioCommand(audioPlayer_, command); + } catch (const std::exception& exc) { + std::cerr << "AudioPlayer: " << exc.what() << '\n'; + } + } + pendingAudioCommands_.clear(); +} + +bool ScriptEngine::QueueAudioCommand(AudioCommandType type, std::string path, bool loop, std::string& error) { + if (audioPlayer_) { + try { + AudioCommand command{type, std::move(path), loop}; + ExecuteAudioCommand(audioPlayer_, command); + return true; + } catch (const std::exception& exc) { + error = exc.what(); + return false; + } + } + pendingAudioCommands_.push_back(AudioCommand{type, std::move(path), loop}); + return true; +} + +void ScriptEngine::ExecuteAudioCommand(app::AudioPlayer* player, const AudioCommand& command) { + auto resolved = ResolveScriptPath(command.path); + if (!std::filesystem::exists(resolved)) { + throw std::runtime_error("Audio file not found: " + resolved.string()); + } + switch (command.type) { + case AudioCommandType::Background: + player->PlayBackground(resolved, command.loop); + break; + case AudioCommandType::Effect: + player->PlayEffect(resolved, command.loop); + break; + } +} + +std::filesystem::path ScriptEngine::ResolveScriptPath(const std::string& requested) const { + std::filesystem::path resolved(requested); + if (!resolved.is_absolute()) { + resolved = scriptDirectory_ / resolved; + } + std::error_code ec; + auto canonical = std::filesystem::weakly_canonical(resolved, ec); + if (!ec) { + resolved = canonical; + } + return resolved; +} + +} // namespace sdl3cpp::script diff --git a/src/script/script_engine.hpp b/src/script/script_engine.hpp index 0bc5c91..c557c09 100644 --- a/src/script/script_engine.hpp +++ b/src/script/script_engine.hpp @@ -13,6 +13,10 @@ #include "core/vertex.hpp" #include "script/gui_types.hpp" #include "script/physics_bridge.hpp" +#include "script/scene_manager.hpp" +#include "script/shader_manager.hpp" +#include "script/gui_manager.hpp" +#include "script/audio_manager.hpp" namespace sdl3cpp::app { class AudioPlayer; @@ -28,49 +32,27 @@ public: ScriptEngine(const ScriptEngine&) = delete; ScriptEngine& operator=(const ScriptEngine&) = delete; - struct ShaderPaths { - std::string vertex; - std::string fragment; - }; - - struct SceneObject { - std::vector vertices; - std::vector indices; - int computeModelMatrixRef = LUA_REFNIL; - std::string shaderKey = "default"; - }; - - enum class AudioCommandType { - Background, - Effect, - }; - - std::vector LoadSceneObjects(); + std::vector LoadSceneObjects(); std::array ComputeModelMatrix(int functionRef, float time); std::array GetViewProjectionMatrix(float aspect); - std::unordered_map LoadShaderPathsMap(); + std::unordered_map LoadShaderPathsMap(); std::vector LoadGuiCommands(); void UpdateGuiInput(const GuiInputSnapshot& input); bool HasGuiCommands() const; std::filesystem::path GetScriptDirectory() const; PhysicsBridge& GetPhysicsBridge(); void SetAudioPlayer(app::AudioPlayer* audioPlayer); - bool QueueAudioCommand(AudioCommandType type, std::string path, bool loop, std::string& error); + bool QueueAudioCommand(AudioManager::AudioCommandType type, std::string path, bool loop, std::string& error); + std::string GetLuaError(); private: - struct AudioCommand { - AudioCommandType type = AudioCommandType::Background; - std::string path; - bool loop = false; - }; - - void ExecuteAudioCommand(app::AudioPlayer* player, const AudioCommand& command); +private: + void ExecuteAudioCommand(app::AudioPlayer* player, const AudioManager::AudioCommand& command); std::filesystem::path ResolveScriptPath(const std::string& requested) const; static std::vector ReadVertexArray(lua_State* L, int index); static std::vector ReadIndexArray(lua_State* L, int index); static std::string LuaErrorMessage(lua_State* L); - static ShaderPaths ReadShaderPathsTable(lua_State* L, int index); static GuiCommand::RectData ReadRect(lua_State* L, int index); static GuiColor ReadColor(lua_State* L, int index, const GuiColor& defaultColor); static bool ReadStringField(lua_State* L, int index, const char* name, std::string& outString); @@ -82,7 +64,11 @@ private: bool debugEnabled_ = false; std::unique_ptr physicsBridge_; app::AudioPlayer* audioPlayer_ = nullptr; - std::vector pendingAudioCommands_; + std::vector pendingAudioCommands_; + std::unique_ptr sceneManager_; + std::unique_ptr shaderManager_; + std::unique_ptr guiManager_; + std::unique_ptr audioManager_; }; } // namespace sdl3cpp::script diff --git a/src/script/shader_manager.cpp b/src/script/shader_manager.cpp new file mode 100644 index 0000000..9a8ed68 --- /dev/null +++ b/src/script/shader_manager.cpp @@ -0,0 +1,74 @@ +#include "script/shader_manager.hpp" + +#include + +#include +#include +#include + +namespace sdl3cpp::script { + +ShaderManager::ShaderManager(lua_State* L) : L_(L) {} + +std::unordered_map ShaderManager::LoadShaderPathsMap() { + lua_getglobal(L_, "get_shader_paths"); + if (!lua_isfunction(L_, -1)) { + lua_pop(L_, 1); + throw std::runtime_error("Lua function 'get_shader_paths' is missing"); + } + if (lua_pcall(L_, 0, 1, 0) != LUA_OK) { + std::string message = GetLuaError(); + lua_pop(L_, 1); + throw std::runtime_error("Lua get_shader_paths failed: " + message); + } + if (!lua_istable(L_, -1)) { + lua_pop(L_, 1); + throw std::runtime_error("'get_shader_paths' did not return a table"); + } + + std::unordered_map shaderMap; + lua_pushnil(L_); + while (lua_next(L_, -2) != 0) { + if (lua_isstring(L_, -2) && lua_istable(L_, -1)) { + std::string key = lua_tostring(L_, -2); + shaderMap.emplace(key, ReadShaderPathsTable(-1)); + } + lua_pop(L_, 1); + } + + lua_pop(L_, 1); + if (shaderMap.empty()) { + throw std::runtime_error("'get_shader_paths' did not return any shader variants"); + } + return shaderMap; +} + +ShaderManager::ShaderPaths ShaderManager::ReadShaderPathsTable(int index) { + ShaderPaths paths; + int absIndex = lua_absindex(L_, index); + + lua_getfield(L_, absIndex, "vertex"); + if (!lua_isstring(L_, -1)) { + lua_pop(L_, 1); + throw std::runtime_error("Shader path 'vertex' must be a string"); + } + paths.vertex = lua_tostring(L_, -1); + lua_pop(L_, 1); + + lua_getfield(L_, absIndex, "fragment"); + if (!lua_isstring(L_, -1)) { + lua_pop(L_, 1); + throw std::runtime_error("Shader path 'fragment' must be a string"); + } + paths.fragment = lua_tostring(L_, -1); + lua_pop(L_, 1); + + return paths; +} + +std::string ShaderManager::GetLuaError() { + const char* message = lua_tostring(L_, -1); + return message ? message : "unknown lua error"; +} + +} // namespace sdl3cpp::script \ No newline at end of file diff --git a/src/script/shader_manager.hpp b/src/script/shader_manager.hpp new file mode 100644 index 0000000..76e8c05 --- /dev/null +++ b/src/script/shader_manager.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include + +#include +#include + +namespace sdl3cpp::script { + +class ShaderManager { +public: + struct ShaderPaths { + std::string vertex; + std::string fragment; + }; + + explicit ShaderManager(lua_State* L); + + std::unordered_map LoadShaderPathsMap(); + +private: + lua_State* L_; + + ShaderPaths ReadShaderPathsTable(int index); + std::string GetLuaError(); +}; + +} // namespace sdl3cpp::script \ No newline at end of file