From 5a4dd23df21c2ca5cbfb89002efa2f269ede2bd4 Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Sat, 3 Jan 2026 23:44:01 +0000 Subject: [PATCH] Implement PhysicsBridge and ScriptEngine for enhanced physics and scripting capabilities - Added PhysicsBridge class to manage physics interactions using Bullet Physics. - Introduced ScriptEngine class to handle Lua scripting, including loading scene objects and managing audio commands. - Updated test_cube_script to utilize ScriptEngine instead of CubeScript, ensuring compatibility with new architecture. - Implemented methods for loading meshes, creating physics objects, and handling audio playback within the ScriptEngine. - Enhanced error handling and input management for Lua scripts. --- CMakeLists.txt | 14 +- src/app/sdl3_app.hpp | 8 +- src/app/sdl3_app_buffers.cpp | 4 +- src/app/sdl3_app_core.cpp | 8 +- src/app/sdl3_app_render.cpp | 8 +- src/gui/gui_renderer.cpp | 22 +- src/gui/gui_renderer.hpp | 4 +- src/script/gui_types.hpp | 60 ++++++ src/script/physics_bridge.cpp | 91 ++++++++ src/script/physics_bridge.hpp | 56 +++++ .../{cube_script.cpp => script_engine.cpp} | 194 ++++-------------- .../{cube_script.hpp => script_engine.hpp} | 70 +------ tests/test_cube_script.cpp | 18 +- 13 files changed, 299 insertions(+), 258 deletions(-) create mode 100644 src/script/gui_types.hpp create mode 100644 src/script/physics_bridge.cpp create mode 100644 src/script/physics_bridge.hpp rename src/script/{cube_script.cpp => script_engine.cpp} (81%) rename src/script/{cube_script.hpp => script_engine.hpp} (65%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6e08d0f..62147fc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -112,7 +112,8 @@ if(BUILD_SDL3_APP) src/gui/gui_renderer.cpp src/app/sdl3_app_render.cpp src/app/vulkan_api.cpp - src/script/cube_script.cpp + src/script/script_engine.cpp + src/script/physics_bridge.cpp ) target_include_directories(sdl3_app PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src") target_link_libraries(sdl3_app PRIVATE ${SDL_TARGET} Vulkan::Vulkan lua::lua CLI11::CLI11 rapidjson assimp::assimp Bullet::Bullet glm::glm Vorbis::vorbisfile Vorbis::vorbis) @@ -133,11 +134,12 @@ endif() enable_testing() -add_executable(cube_script_tests +add_executable(script_engine_tests tests/test_cube_script.cpp - src/script/cube_script.cpp + src/script/script_engine.cpp + src/script/physics_bridge.cpp src/app/audio_player.cpp ) -target_include_directories(cube_script_tests PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src") -target_link_libraries(cube_script_tests PRIVATE ${SDL_TARGET} lua::lua assimp::assimp Bullet::Bullet glm::glm Vorbis::vorbisfile Vorbis::vorbis) -add_test(NAME cube_script_tests COMMAND cube_script_tests) +target_include_directories(script_engine_tests PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src") +target_link_libraries(script_engine_tests PRIVATE ${SDL_TARGET} lua::lua assimp::assimp Bullet::Bullet glm::glm Vorbis::vorbisfile Vorbis::vorbis) +add_test(NAME script_engine_tests COMMAND script_engine_tests) diff --git a/src/app/sdl3_app.hpp b/src/app/sdl3_app.hpp index 0c8074f..cecbe52 100644 --- a/src/app/sdl3_app.hpp +++ b/src/app/sdl3_app.hpp @@ -20,7 +20,7 @@ #include "app/audio_player.hpp" #include "core/vertex.hpp" -#include "script/cube_script.hpp" +#include "script/script_engine.hpp" #include "gui/gui_renderer.hpp" namespace sdl3cpp::app { @@ -129,10 +129,10 @@ private: VkDeviceMemory indexBufferMemory_ = VK_NULL_HANDLE; VkSemaphore imageAvailableSemaphore_ = VK_NULL_HANDLE; VkSemaphore renderFinishedSemaphore_ = VK_NULL_HANDLE; - script::CubeScript cubeScript_; + 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; @@ -140,7 +140,7 @@ private: int consecutiveSwapchainRecreations_ = 0; bool firstFrameCompleted_ = false; script::GuiInputSnapshot guiInputSnapshot_; - std::vector guiCommands_; + std::vector guiCommands_; std::unique_ptr guiRenderer_; bool guiHasCommands_ = false; std::vector renderObjects_; diff --git a/src/app/sdl3_app_buffers.cpp b/src/app/sdl3_app_buffers.cpp index 1c45dbf..277d3b4 100644 --- a/src/app/sdl3_app_buffers.cpp +++ b/src/app/sdl3_app_buffers.cpp @@ -10,13 +10,13 @@ namespace sdl3cpp::app { void Sdl3App::LoadSceneData() { TRACE_FUNCTION(); - shaderPathMap_ = cubeScript_.LoadShaderPathsMap(); + shaderPathMap_ = scriptEngine_.LoadShaderPathsMap(); if (shaderPathMap_.empty()) { throw std::runtime_error("Lua script did not provide shader paths"); } defaultShaderKey_ = shaderPathMap_.count("default") ? "default" : shaderPathMap_.begin()->first; - auto sceneObjects = cubeScript_.LoadSceneObjects(); + auto sceneObjects = scriptEngine_.LoadSceneObjects(); if (sceneObjects.empty()) { throw std::runtime_error("Lua script did not provide any scene objects"); } diff --git a/src/app/sdl3_app_core.cpp b/src/app/sdl3_app_core.cpp index 39c78f1..92cb810 100644 --- a/src/app/sdl3_app_core.cpp +++ b/src/app/sdl3_app_core.cpp @@ -114,7 +114,7 @@ void ShowErrorDialog(const char* title, const std::string& message) { } // namespace Sdl3App::Sdl3App(const std::filesystem::path& scriptPath, bool luaDebug) - : cubeScript_(scriptPath, luaDebug), + : scriptEngine_(scriptPath, luaDebug), scriptDirectory_(scriptPath.parent_path()) { TRACE_FUNCTION(); TRACE_VAR(scriptPath); @@ -164,7 +164,7 @@ void Sdl3App::InitSDL() { SDL_StartTextInput(window_); try { audioPlayer_ = std::make_unique(); - cubeScript_.SetAudioPlayer(audioPlayer_.get()); + scriptEngine_.SetAudioPlayer(audioPlayer_.get()); } catch (const std::exception& exc) { std::cerr << "AudioPlayer: " << exc.what() << '\n'; } @@ -282,9 +282,9 @@ void Sdl3App::MainLoop() { SDL_GetMouseState(&mouseX, &mouseY); guiInputSnapshot_.mouseX = mouseX; guiInputSnapshot_.mouseY = mouseY; - cubeScript_.UpdateGuiInput(guiInputSnapshot_); + scriptEngine_.UpdateGuiInput(guiInputSnapshot_); if (guiHasCommands_ && guiRenderer_) { - guiCommands_ = cubeScript_.LoadGuiCommands(); + guiCommands_ = scriptEngine_.LoadGuiCommands(); guiRenderer_->Prepare(guiCommands_, swapChainExtent_.width, swapChainExtent_.height); } guiInputSnapshot_.wheel = 0.0f; diff --git a/src/app/sdl3_app_render.cpp b/src/app/sdl3_app_render.cpp index 6226df0..e8ddd40 100644 --- a/src/app/sdl3_app_render.cpp +++ b/src/app/sdl3_app_render.cpp @@ -178,7 +178,7 @@ void Sdl3App::RecordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageI } } vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineIt->second); - pushConstants.model = cubeScript_.ComputeModelMatrix(object.computeModelMatrixRef, time); + pushConstants.model = scriptEngine_.ComputeModelMatrix(object.computeModelMatrixRef, time); vkCmdPushConstants(commandBuffer, pipelineLayout_, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(core::PushConstants), &pushConstants); vkCmdDrawIndexed(commandBuffer, object.indexCount, 1, object.indexOffset, object.vertexOffset, 0); @@ -225,7 +225,7 @@ void Sdl3App::ProcessGuiEvent(const SDL_Event& event) { void Sdl3App::SetupGuiRenderer() { TRACE_FUNCTION(); - guiHasCommands_ = cubeScript_.HasGuiCommands(); + guiHasCommands_ = scriptEngine_.HasGuiCommands(); if (!guiHasCommands_) { guiRenderer_.reset(); return; @@ -233,7 +233,7 @@ void Sdl3App::SetupGuiRenderer() { if (!guiRenderer_) { guiRenderer_ = std::make_unique(device_, physicalDevice_, swapChainImageFormat_, - cubeScript_.GetScriptDirectory()); + scriptEngine_.GetScriptDirectory()); } guiRenderer_->Resize(swapChainExtent_.width, swapChainExtent_.height, swapChainImageFormat_); } @@ -293,7 +293,7 @@ void Sdl3App::DrawFrame(float time) { TRACE_VAR(imageIndex); float aspect = static_cast(swapChainExtent_.width) / static_cast(swapChainExtent_.height); - auto viewProj = cubeScript_.GetViewProjectionMatrix(aspect); + auto viewProj = scriptEngine_.GetViewProjectionMatrix(aspect); vkResetCommandBuffer(commandBuffers_[imageIndex], 0); RecordCommandBuffer(commandBuffers_[imageIndex], imageIndex, time, viewProj); diff --git a/src/gui/gui_renderer.cpp b/src/gui/gui_renderer.cpp index 38c319b..92ae4df 100644 --- a/src/gui/gui_renderer.cpp +++ b/src/gui/gui_renderer.cpp @@ -145,9 +145,9 @@ ParsedSvg ParseSvgFile(const std::filesystem::path& path) { return result; } -script::CubeScript::GuiCommand::RectData IntersectRect(const script::CubeScript::GuiCommand::RectData& a, - const script::CubeScript::GuiCommand::RectData& b) { - script::CubeScript::GuiCommand::RectData result; +script::GuiCommand::RectData IntersectRect(const script::GuiCommand::RectData& a, + const script::GuiCommand::RectData& b) { + script::GuiCommand::RectData result; result.x = std::max(a.x, b.x); result.y = std::max(a.y, b.y); float right = std::min(a.x + a.width, b.x + b.width); @@ -165,7 +165,7 @@ int ClampToRange(int value, int minimum, int maximum) { class GuiRenderer::Canvas { public: - using RectData = script::CubeScript::GuiCommand::RectData; + using RectData = script::GuiCommand::RectData; void Resize(uint32_t width, uint32_t height) { width_ = width; @@ -366,7 +366,7 @@ GuiRenderer::GuiRenderer(VkDevice device, VkPhysicalDevice physicalDevice, VkFor return canvasWidth_ > 0 && canvasHeight_ > 0 && stagingBuffer_ != VK_NULL_HANDLE; } - void GuiRenderer::Prepare(const std::vector& commands, uint32_t width, + void GuiRenderer::Prepare(const std::vector& commands, uint32_t width, uint32_t height) { if (width == 0 || height == 0 || !canvas_) { return; @@ -375,10 +375,10 @@ GuiRenderer::GuiRenderer(VkDevice device, VkPhysicalDevice physicalDevice, VkFor canvas_->Clear(); for (const auto& command : commands) { switch (command.type) { - case script::CubeScript::GuiCommand::Type::Rect: + case script::GuiCommand::Type::Rect: canvas_->FillRect(command.rect, command.color, command.borderColor, command.borderWidth); break; - case script::CubeScript::GuiCommand::Type::Text: { + case script::GuiCommand::Type::Text: { if (command.hasClipRect) { canvas_->PushClip(command.clipRect); } @@ -386,7 +386,7 @@ GuiRenderer::GuiRenderer(VkDevice device, VkPhysicalDevice physicalDevice, VkFor canvas_->DrawText(command.text, command.color, command.bounds, command.alignX, command.alignY, command.fontSize); } else { - script::CubeScript::GuiCommand::RectData fallback{ + script::GuiCommand::RectData fallback{ command.rect.x, command.rect.y, command.fontSize * static_cast(std::max(1, command.text.size())), command.fontSize}; canvas_->DrawText(command.text, command.color, fallback, command.alignX, @@ -397,13 +397,13 @@ GuiRenderer::GuiRenderer(VkDevice device, VkPhysicalDevice physicalDevice, VkFor } break; } - case script::CubeScript::GuiCommand::Type::ClipPush: + case script::GuiCommand::Type::ClipPush: canvas_->PushClip(command.rect); break; - case script::CubeScript::GuiCommand::Type::ClipPop: + case script::GuiCommand::Type::ClipPop: canvas_->PopClip(); break; - case script::CubeScript::GuiCommand::Type::Svg: + case script::GuiCommand::Type::Svg: if (command.svgPath.empty()) { break; } diff --git a/src/gui/gui_renderer.hpp b/src/gui/gui_renderer.hpp index c5c9d4d..34d84f1 100644 --- a/src/gui/gui_renderer.hpp +++ b/src/gui/gui_renderer.hpp @@ -8,7 +8,7 @@ #include -#include "script/cube_script.hpp" +#include "script/gui_types.hpp" namespace sdl3cpp::gui { @@ -34,7 +34,7 @@ public: GuiRenderer(const GuiRenderer&) = delete; GuiRenderer& operator=(const GuiRenderer&) = delete; - void Prepare(const std::vector& commands, uint32_t width, + void Prepare(const std::vector& commands, uint32_t width, uint32_t height); void BlitToSwapchain(VkCommandBuffer commandBuffer, VkImage image); void Resize(uint32_t width, uint32_t height, VkFormat format); diff --git a/src/script/gui_types.hpp b/src/script/gui_types.hpp new file mode 100644 index 0000000..b2da88e --- /dev/null +++ b/src/script/gui_types.hpp @@ -0,0 +1,60 @@ +#ifndef SDL3CPP_SCRIPT_GUI_TYPES_HPP +#define SDL3CPP_SCRIPT_GUI_TYPES_HPP + +#include +#include + +namespace sdl3cpp::script { + +struct GuiInputSnapshot { + float mouseX = 0.0f; + float mouseY = 0.0f; + bool mouseDown = false; + float wheel = 0.0f; + std::string textInput; + std::unordered_map keyStates; +}; + +struct GuiColor { + float r = 0; + float g = 0; + float b = 0; + float a = 1.0f; +}; + +struct GuiCommand { + enum class Type { + Rect, + Text, + ClipPush, + ClipPop, + Svg, + }; + + struct RectData { + float x = 0; + float y = 0; + float width = 0; + float height = 0; + }; + + Type type = Type::Rect; + RectData rect; + GuiColor color; + GuiColor borderColor; + float borderWidth = 0.0f; + bool hasClipRect = false; + RectData clipRect{}; + std::string text; + float fontSize = 16.0f; + std::string alignX = "left"; + std::string alignY = "center"; + std::string svgPath; + GuiColor svgTint; + RectData bounds{}; + bool hasBounds = false; +}; + +} // namespace sdl3cpp::script + +#endif // SDL3CPP_SCRIPT_GUI_TYPES_HPP diff --git a/src/script/physics_bridge.cpp b/src/script/physics_bridge.cpp new file mode 100644 index 0000000..5fc300b --- /dev/null +++ b/src/script/physics_bridge.cpp @@ -0,0 +1,91 @@ +#include "script/physics_bridge.hpp" + +#include + +namespace sdl3cpp::script { + +PhysicsBridge::PhysicsBridge() + : collisionConfig_(std::make_unique()), + dispatcher_(std::make_unique(collisionConfig_.get())), + broadphase_(std::make_unique()), + solver_(std::make_unique()), + world_(std::make_unique( + dispatcher_.get(), + broadphase_.get(), + solver_.get(), + collisionConfig_.get())) { + world_->setGravity(btVector3(0.0f, -9.81f, 0.0f)); +} + +PhysicsBridge::~PhysicsBridge() { + if (world_) { + for (auto& [name, entry] : bodies_) { + if (entry.body) { + world_->removeRigidBody(entry.body.get()); + } + } + } +} + +bool PhysicsBridge::addBoxRigidBody(const std::string& name, + const btVector3& halfExtents, + float mass, + const btTransform& transform, + std::string& error) { + if (name.empty()) { + error = "Rigid body name must not be empty"; + return false; + } + if (!world_) { + error = "Physics world is not initialized"; + return false; + } + if (bodies_.count(name)) { + error = "Rigid body already exists: " + name; + return false; + } + auto shape = std::make_unique(halfExtents); + btVector3 inertia(0.0f, 0.0f, 0.0f); + if (mass > 0.0f) { + shape->calculateLocalInertia(mass, inertia); + } + auto motionState = std::make_unique(transform); + btRigidBody::btRigidBodyConstructionInfo constructionInfo( + mass, + motionState.get(), + shape.get(), + inertia); + auto body = std::make_unique(constructionInfo); + world_->addRigidBody(body.get()); + bodies_.emplace(name, BodyRecord{ + std::move(shape), + std::move(motionState), + std::move(body), + }); + return true; +} + +int PhysicsBridge::stepSimulation(float deltaTime) { + if (!world_) { + return 0; + } + return static_cast(world_->stepSimulation(deltaTime, 10, 1.0f / 60.0f)); +} + +bool PhysicsBridge::getRigidBodyTransform(const std::string& name, + btTransform& outTransform, + std::string& error) const { + auto it = bodies_.find(name); + if (it == bodies_.end()) { + error = "Rigid body not found: " + name; + return false; + } + if (!it->second.motionState) { + error = "Rigid body motion state is missing"; + return false; + } + it->second.motionState->getWorldTransform(outTransform); + return true; +} + +} // namespace sdl3cpp::script diff --git a/src/script/physics_bridge.hpp b/src/script/physics_bridge.hpp new file mode 100644 index 0000000..f4671d0 --- /dev/null +++ b/src/script/physics_bridge.hpp @@ -0,0 +1,56 @@ +#ifndef SDL3CPP_SCRIPT_PHYSICS_BRIDGE_HPP +#define SDL3CPP_SCRIPT_PHYSICS_BRIDGE_HPP + +#include +#include +#include + +class btVector3; +class btTransform; +class btCollisionShape; +class btMotionState; +class btRigidBody; +class btDefaultCollisionConfiguration; +class btCollisionDispatcher; +class btBroadphaseInterface; +class btSequentialImpulseConstraintSolver; +class btDiscreteDynamicsWorld; + +namespace sdl3cpp::script { + +class PhysicsBridge { +public: + PhysicsBridge(); + ~PhysicsBridge(); + + PhysicsBridge(const PhysicsBridge&) = delete; + PhysicsBridge& operator=(const PhysicsBridge&) = delete; + + bool addBoxRigidBody(const std::string& name, + const btVector3& halfExtents, + float mass, + const btTransform& transform, + std::string& error); + int stepSimulation(float deltaTime); + bool getRigidBodyTransform(const std::string& name, + btTransform& outTransform, + std::string& error) const; + +private: + struct BodyRecord { + std::unique_ptr shape; + std::unique_ptr motionState; + std::unique_ptr body; + }; + + std::unique_ptr collisionConfig_; + std::unique_ptr dispatcher_; + std::unique_ptr broadphase_; + std::unique_ptr solver_; + std::unique_ptr world_; + std::unordered_map bodies_; +}; + +} // namespace sdl3cpp::script + +#endif // SDL3CPP_SCRIPT_PHYSICS_BRIDGE_HPP diff --git a/src/script/cube_script.cpp b/src/script/script_engine.cpp similarity index 81% rename from src/script/cube_script.cpp rename to src/script/script_engine.cpp index 459ee1c..4af61d2 100644 --- a/src/script/cube_script.cpp +++ b/src/script/script_engine.cpp @@ -1,4 +1,4 @@ -#include "script/cube_script.hpp" +#include "script/script_engine.hpp" #include "app/audio_player.hpp" #include @@ -14,129 +14,12 @@ #include #include #include -#include #include -#include #include -#include #include -#include namespace sdl3cpp::script { -struct PhysicsBridge { - struct BodyRecord { - std::unique_ptr shape; - std::unique_ptr motionState; - std::unique_ptr body; - }; - - PhysicsBridge(); - ~PhysicsBridge(); - - bool addBoxRigidBody(const std::string& name, - const btVector3& halfExtents, - float mass, - const btTransform& transform, - std::string& error); - int stepSimulation(float deltaTime); - bool getRigidBodyTransform(const std::string& name, - btTransform& outTransform, - std::string& error) const; - -private: - std::unique_ptr collisionConfig_; - std::unique_ptr dispatcher_; - std::unique_ptr broadphase_; - std::unique_ptr solver_; - std::unique_ptr world_; - std::unordered_map bodies_; -}; - -PhysicsBridge::PhysicsBridge() - : collisionConfig_(std::make_unique()), - dispatcher_(std::make_unique(collisionConfig_.get())), - broadphase_(std::make_unique()), - solver_(std::make_unique()), - world_(std::make_unique( - dispatcher_.get(), - broadphase_.get(), - solver_.get(), - collisionConfig_.get())) { - world_->setGravity(btVector3(0.0f, -9.81f, 0.0f)); -} - -PhysicsBridge::~PhysicsBridge() { - if (world_) { - for (auto& [name, entry] : bodies_) { - if (entry.body) { - world_->removeRigidBody(entry.body.get()); - } - } - } -} - -bool PhysicsBridge::addBoxRigidBody(const std::string& name, - const btVector3& halfExtents, - float mass, - const btTransform& transform, - std::string& error) { - if (name.empty()) { - error = "Rigid body name must not be empty"; - return false; - } - if (!world_) { - error = "Physics world is not initialized"; - return false; - } - if (bodies_.count(name)) { - error = "Rigid body already exists: " + name; - return false; - } - auto shape = std::make_unique(halfExtents); - btVector3 inertia(0.0f, 0.0f, 0.0f); - if (mass > 0.0f) { - shape->calculateLocalInertia(mass, inertia); - } - auto motionState = std::make_unique(transform); - btRigidBody::btRigidBodyConstructionInfo constructionInfo( - mass, - motionState.get(), - shape.get(), - inertia); - auto body = std::make_unique(constructionInfo); - world_->addRigidBody(body.get()); - bodies_.emplace(name, BodyRecord{ - std::move(shape), - std::move(motionState), - std::move(body), - }); - return true; -} - -int PhysicsBridge::stepSimulation(float deltaTime) { - if (!world_) { - return 0; - } - return static_cast(world_->stepSimulation(deltaTime, 10, 1.0f / 60.0f)); -} - -bool PhysicsBridge::getRigidBodyTransform(const std::string& name, - btTransform& outTransform, - std::string& error) const { - auto it = bodies_.find(name); - if (it == bodies_.end()) { - error = "Rigid body not found: " + name; - return false; - } - if (!it->second.motionState) { - error = "Rigid body motion state is missing"; - return false; - } - it->second.motionState->getWorldTransform(outTransform); - return true; -} - namespace detail { std::array ReadVector3(lua_State* L, int index) { @@ -206,7 +89,7 @@ struct MeshPayload { std::vector indices; }; -bool TryLoadMeshPayload(const CubeScript* script, +bool TryLoadMeshPayload(const ScriptEngine* script, const std::string& requestedPath, MeshPayload& payload, std::string& error) { @@ -293,7 +176,6 @@ glm::vec3 ToVec3(const std::array& value) { } glm::quat ToQuat(const std::array& value) { - // Lua exposes {x, y, z, w} return glm::quat(value[3], value[0], value[1], value[2]); } @@ -307,9 +189,9 @@ void PushMatrix(lua_State* L, const glm::mat4& matrix) { } int PushMeshToLua(lua_State* L, const MeshPayload& payload) { - lua_newtable(L); // mesh + lua_newtable(L); - lua_newtable(L); // vertices table + lua_newtable(L); for (size_t vertexIndex = 0; vertexIndex < payload.positions.size(); ++vertexIndex) { lua_newtable(L); @@ -331,7 +213,7 @@ int PushMeshToLua(lua_State* L, const MeshPayload& payload) { } lua_setfield(L, -2, "vertices"); - lua_newtable(L); // indices table + 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)); @@ -342,7 +224,7 @@ int PushMeshToLua(lua_State* L, const MeshPayload& payload) { } int LuaLoadMeshFromFile(lua_State* L) { - auto* script = static_cast(lua_touserdata(L, lua_upvalueindex(1))); + auto* script = static_cast(lua_touserdata(L, lua_upvalueindex(1))); const char* path = luaL_checkstring(L, 1); MeshPayload payload; std::string error; @@ -357,7 +239,7 @@ int LuaLoadMeshFromFile(lua_State* L) { } int LuaPhysicsCreateBox(lua_State* L) { - auto* script = static_cast(lua_touserdata(L, lua_upvalueindex(1))); + 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"); @@ -389,7 +271,7 @@ int LuaPhysicsCreateBox(lua_State* L) { } int LuaPhysicsStepSimulation(lua_State* L) { - auto* script = static_cast(lua_touserdata(L, lua_upvalueindex(1))); + 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); @@ -397,7 +279,7 @@ int LuaPhysicsStepSimulation(lua_State* L) { } int LuaPhysicsGetTransform(lua_State* L) { - auto* script = static_cast(lua_touserdata(L, lua_upvalueindex(1))); + auto* script = static_cast(lua_touserdata(L, lua_upvalueindex(1))); const char* name = luaL_checkstring(L, 1); btTransform transform; std::string error; @@ -428,20 +310,20 @@ int LuaPhysicsGetTransform(lua_State* L) { lua_rawseti(L, -2, 3); lua_pushnumber(L, orientation.w()); lua_rawseti(L, -2, 4); - lua_setfield(L, -2, "rotation"); + lua_setfield(L, -2, "rotation"); return 1; } int LuaAudioPlayBackground(lua_State* L) { - auto* script = static_cast(lua_touserdata(L, lua_upvalueindex(1))); + 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(CubeScript::AudioCommandType::Background, path, loop, error)) { + if (!script->QueueAudioCommand(ScriptEngine::AudioCommandType::Background, path, loop, error)) { lua_pushnil(L); lua_pushstring(L, error.c_str()); return 2; @@ -451,14 +333,14 @@ int LuaAudioPlayBackground(lua_State* L) { } int LuaAudioPlaySound(lua_State* L) { - auto* script = static_cast(lua_touserdata(L, lua_upvalueindex(1))); + 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(CubeScript::AudioCommandType::Effect, path, loop, error)) { + if (!script->QueueAudioCommand(ScriptEngine::AudioCommandType::Effect, path, loop, error)) { lua_pushnil(L); lua_pushstring(L, error.c_str()); return 2; @@ -486,7 +368,7 @@ std::array IdentityMatrix() { } // namespace -CubeScript::CubeScript(const std::filesystem::path& scriptPath, bool debugEnabled) +ScriptEngine::ScriptEngine(const std::filesystem::path& scriptPath, bool debugEnabled) : L_(luaL_newstate()), scriptDirectory_(scriptPath.parent_path()), debugEnabled_(debugEnabled), @@ -557,7 +439,7 @@ CubeScript::CubeScript(const std::filesystem::path& scriptPath, bool debugEnable } } -CubeScript::~CubeScript() { +ScriptEngine::~ScriptEngine() { if (L_) { if (guiInputRef_ != LUA_REFNIL) { luaL_unref(L_, LUA_REGISTRYINDEX, guiInputRef_); @@ -569,7 +451,7 @@ CubeScript::~CubeScript() { } } -std::vector CubeScript::LoadSceneObjects() { +std::vector ScriptEngine::LoadSceneObjects() { lua_getglobal(L_, "get_scene_objects"); if (!lua_isfunction(L_, -1)) { lua_pop(L_, 1); @@ -635,7 +517,7 @@ std::vector CubeScript::LoadSceneObjects() { return objects; } -std::array CubeScript::ComputeModelMatrix(int functionRef, float time) { +std::array ScriptEngine::ComputeModelMatrix(int functionRef, float time) { if (functionRef == LUA_REFNIL) { lua_getglobal(L_, "compute_model_matrix"); if (!lua_isfunction(L_, -1)) { @@ -662,7 +544,7 @@ std::array CubeScript::ComputeModelMatrix(int functionRef, float time return matrix; } -std::array CubeScript::GetViewProjectionMatrix(float aspect) { +std::array ScriptEngine::GetViewProjectionMatrix(float aspect) { lua_getglobal(L_, "get_view_projection"); if (!lua_isfunction(L_, -1)) { lua_pop(L_, 1); @@ -683,7 +565,7 @@ std::array CubeScript::GetViewProjectionMatrix(float aspect) { return matrix; } -std::vector CubeScript::ReadVertexArray(lua_State* L, int index) { +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"); @@ -718,7 +600,7 @@ std::vector CubeScript::ReadVertexArray(lua_State* L, int index) { return vertices; } -std::vector CubeScript::ReadIndexArray(lua_State* L, int index) { +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"); @@ -745,7 +627,7 @@ std::vector CubeScript::ReadIndexArray(lua_State* L, int index) { return indices; } -std::unordered_map CubeScript::LoadShaderPathsMap() { +std::unordered_map ScriptEngine::LoadShaderPathsMap() { lua_getglobal(L_, "get_shader_paths"); if (!lua_isfunction(L_, -1)) { lua_pop(L_, 1); @@ -778,7 +660,7 @@ std::unordered_map CubeScript::LoadShaderP return shaderMap; } -CubeScript::ShaderPaths CubeScript::ReadShaderPathsTable(lua_State* L, int index) { +ScriptEngine::ShaderPaths ScriptEngine::ReadShaderPathsTable(lua_State* L, int index) { ShaderPaths paths; int absIndex = lua_absindex(L, index); @@ -801,16 +683,16 @@ CubeScript::ShaderPaths CubeScript::ReadShaderPathsTable(lua_State* L, int index return paths; } -std::string CubeScript::LuaErrorMessage(lua_State* L) { +std::string ScriptEngine::LuaErrorMessage(lua_State* L) { const char* message = lua_tostring(L, -1); return message ? message : "unknown lua error"; } -PhysicsBridge& CubeScript::GetPhysicsBridge() { +PhysicsBridge& ScriptEngine::GetPhysicsBridge() { return *physicsBridge_; } -std::vector CubeScript::LoadGuiCommands() { +std::vector ScriptEngine::LoadGuiCommands() { std::vector commands; if (guiCommandsFnRef_ == LUA_REFNIL) { return commands; @@ -897,8 +779,8 @@ std::vector CubeScript::LoadGuiCommands() { } lua_pop(L_, 1); } - lua_pop(L_, 1); // pop type - lua_pop(L_, 1); // pop command table + lua_pop(L_, 1); + lua_pop(L_, 1); commands.push_back(std::move(command)); } @@ -906,7 +788,7 @@ std::vector CubeScript::LoadGuiCommands() { return commands; } -void CubeScript::UpdateGuiInput(const GuiInputSnapshot& input) { +void ScriptEngine::UpdateGuiInput(const GuiInputSnapshot& input) { if (guiInputRef_ == LUA_REFNIL) { return; } @@ -947,15 +829,15 @@ void CubeScript::UpdateGuiInput(const GuiInputSnapshot& input) { lua_pop(L_, 1); } -bool CubeScript::HasGuiCommands() const { +bool ScriptEngine::HasGuiCommands() const { return guiCommandsFnRef_ != LUA_REFNIL; } -std::filesystem::path CubeScript::GetScriptDirectory() const { +std::filesystem::path ScriptEngine::GetScriptDirectory() const { return scriptDirectory_; } -CubeScript::GuiCommand::RectData CubeScript::ReadRect(lua_State* L, int index) { +GuiCommand::RectData ScriptEngine::ReadRect(lua_State* L, int index) { GuiCommand::RectData rect{}; if (!lua_istable(L, index)) { return rect; @@ -977,7 +859,7 @@ CubeScript::GuiCommand::RectData CubeScript::ReadRect(lua_State* L, int index) { return rect; } -GuiColor CubeScript::ReadColor(lua_State* L, int index, const GuiColor& defaultColor) { +GuiColor ScriptEngine::ReadColor(lua_State* L, int index, const GuiColor& defaultColor) { GuiColor color = defaultColor; if (!lua_istable(L, index)) { return color; @@ -999,7 +881,7 @@ GuiColor CubeScript::ReadColor(lua_State* L, int index, const GuiColor& defaultC return color; } -bool CubeScript::ReadStringField(lua_State* L, int index, const char* name, std::string& outString) { +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)) { @@ -1011,7 +893,7 @@ bool CubeScript::ReadStringField(lua_State* L, int index, const char* name, std: return false; } -void CubeScript::SetAudioPlayer(app::AudioPlayer* audioPlayer) { +void ScriptEngine::SetAudioPlayer(app::AudioPlayer* audioPlayer) { audioPlayer_ = audioPlayer; if (!audioPlayer_) { return; @@ -1026,7 +908,7 @@ void CubeScript::SetAudioPlayer(app::AudioPlayer* audioPlayer) { pendingAudioCommands_.clear(); } -bool CubeScript::QueueAudioCommand(AudioCommandType type, std::string path, bool loop, std::string& error) { +bool ScriptEngine::QueueAudioCommand(AudioCommandType type, std::string path, bool loop, std::string& error) { if (audioPlayer_) { try { AudioCommand command{type, std::move(path), loop}; @@ -1041,7 +923,7 @@ bool CubeScript::QueueAudioCommand(AudioCommandType type, std::string path, bool return true; } -void CubeScript::ExecuteAudioCommand(app::AudioPlayer* player, const AudioCommand& command) { +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()); @@ -1056,7 +938,7 @@ void CubeScript::ExecuteAudioCommand(app::AudioPlayer* player, const AudioComman } } -std::filesystem::path CubeScript::ResolveScriptPath(const std::string& requested) const { +std::filesystem::path ScriptEngine::ResolveScriptPath(const std::string& requested) const { std::filesystem::path resolved(requested); if (!resolved.is_absolute()) { resolved = scriptDirectory_ / resolved; diff --git a/src/script/cube_script.hpp b/src/script/script_engine.hpp similarity index 65% rename from src/script/cube_script.hpp rename to src/script/script_engine.hpp index 57932f9..0bc5c91 100644 --- a/src/script/cube_script.hpp +++ b/src/script/script_engine.hpp @@ -1,5 +1,5 @@ -#ifndef SDL3CPP_SCRIPT_CUBE_SCRIPT_HPP -#define SDL3CPP_SCRIPT_CUBE_SCRIPT_HPP +#ifndef SDL3CPP_SCRIPT_SCRIPT_ENGINE_HPP +#define SDL3CPP_SCRIPT_SCRIPT_ENGINE_HPP #include #include @@ -11,6 +11,8 @@ #include #include "core/vertex.hpp" +#include "script/gui_types.hpp" +#include "script/physics_bridge.hpp" namespace sdl3cpp::app { class AudioPlayer; @@ -18,65 +20,13 @@ class AudioPlayer; namespace sdl3cpp::script { -struct PhysicsBridge; - -struct GuiInputSnapshot { - float mouseX = 0.0f; - float mouseY = 0.0f; - bool mouseDown = false; - float wheel = 0.0f; - std::string textInput; - std::unordered_map keyStates; -}; - -struct GuiColor { - float r = 0; - float g = 0; - float b = 0; - float a = 1.0f; -}; - -struct GuiCommand { - enum class Type { - Rect, - Text, - ClipPush, - ClipPop, - Svg, - }; - - struct RectData { - float x = 0; - float y = 0; - float width = 0; - float height = 0; - }; - - Type type = Type::Rect; - RectData rect; - GuiColor color; - GuiColor borderColor; - float borderWidth = 0.0f; - bool hasClipRect = false; - RectData clipRect{}; - std::string text; - float fontSize = 16.0f; - std::string alignX = "left"; - std::string alignY = "center"; - std::string svgPath; - GuiColor svgTint; - RectData bounds{}; - bool hasBounds = false; -}; - -class CubeScript { +class ScriptEngine { public: - using GuiCommand = ::sdl3cpp::script::GuiCommand; - using GuiColor = ::sdl3cpp::script::GuiColor; + explicit ScriptEngine(const std::filesystem::path& scriptPath, bool debugEnabled = false); + ~ScriptEngine(); -public: - explicit CubeScript(const std::filesystem::path& scriptPath, bool debugEnabled = false); - ~CubeScript(); + ScriptEngine(const ScriptEngine&) = delete; + ScriptEngine& operator=(const ScriptEngine&) = delete; struct ShaderPaths { std::string vertex; @@ -137,4 +87,4 @@ private: } // namespace sdl3cpp::script -#endif // SDL3CPP_SCRIPT_CUBE_SCRIPT_HPP +#endif // SDL3CPP_SCRIPT_SCRIPT_ENGINE_HPP diff --git a/tests/test_cube_script.cpp b/tests/test_cube_script.cpp index d8b4c4f..66ed854 100644 --- a/tests/test_cube_script.cpp +++ b/tests/test_cube_script.cpp @@ -1,4 +1,4 @@ -#include "script/cube_script.hpp" +#include "script/script_engine.hpp" #include #include @@ -50,8 +50,8 @@ int main() { std::cout << "Loading Lua fixture: " << scriptPath << '\n'; try { - sdl3cpp::script::CubeScript cubeScript(scriptPath); - auto objects = cubeScript.LoadSceneObjects(); + sdl3cpp::script::ScriptEngine scriptEngine(scriptPath); + auto objects = scriptEngine.LoadSceneObjects(); Assert(objects.size() == 1, "expected exactly one scene object", failures); if (!objects.empty()) { const auto& object = objects.front(); @@ -63,17 +63,17 @@ int main() { Assert(object.computeModelMatrixRef != LUA_REFNIL, "vertex object must keep a Lua reference", failures); - auto objectMatrix = cubeScript.ComputeModelMatrix(object.computeModelMatrixRef, 0.5f); + auto objectMatrix = scriptEngine.ComputeModelMatrix(object.computeModelMatrixRef, 0.5f); ExpectIdentity(objectMatrix, "object compute_model_matrix", failures); } - auto fallbackMatrix = cubeScript.ComputeModelMatrix(LUA_REFNIL, 1.0f); + auto fallbackMatrix = scriptEngine.ComputeModelMatrix(LUA_REFNIL, 1.0f); ExpectIdentity(fallbackMatrix, "global compute_model_matrix", failures); - auto viewProjection = cubeScript.GetViewProjectionMatrix(1.33f); + auto viewProjection = scriptEngine.GetViewProjectionMatrix(1.33f); ExpectIdentity(viewProjection, "view_projection matrix", failures); - auto shaderMap = cubeScript.LoadShaderPathsMap(); + auto shaderMap = scriptEngine.LoadShaderPathsMap(); Assert(shaderMap.size() == 1, "expected a single shader variant", failures); auto testEntry = shaderMap.find("test"); Assert(testEntry != shaderMap.end(), "shader map missing " @@ -88,9 +88,9 @@ int main() { } if (failures == 0) { - std::cout << "cube_script_tests: PASSED\n"; + std::cout << "script_engine_tests: PASSED\n"; } else { - std::cerr << "cube_script_tests: FAILED (" << failures << " errors)\n"; + std::cerr << "script_engine_tests: FAILED (" << failures << " errors)\n"; } return failures;