mirror of
https://github.com/johndoe6345789/SDL3CPlusPlus.git
synced 2026-04-28 23:54:59 +00:00
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.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
|
||||
@@ -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<core::Vertex> vertices_;
|
||||
std::vector<uint16_t> indices_;
|
||||
std::unordered_map<std::string, script::ScriptEngine::ShaderPaths> shaderPathMap_;
|
||||
std::unordered_map<std::string, script::ShaderManager::ShaderPaths> shaderPathMap_;
|
||||
std::unordered_map<std::string, VkPipeline> graphicsPipelines_;
|
||||
std::string defaultShaderKey_;
|
||||
VkFence inFlightFence_ = VK_NULL_HANDLE;
|
||||
|
||||
72
src/script/audio_manager.cpp
Normal file
72
src/script/audio_manager.cpp
Normal file
@@ -0,0 +1,72 @@
|
||||
#include "script/audio_manager.hpp"
|
||||
#include "app/audio_player.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <system_error>
|
||||
#include <utility>
|
||||
|
||||
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
|
||||
42
src/script/audio_manager.hpp
Normal file
42
src/script/audio_manager.hpp
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <lua.hpp>
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
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<AudioCommand> pendingAudioCommands_;
|
||||
|
||||
void ExecuteAudioCommand(app::AudioPlayer* player, const AudioCommand& command);
|
||||
std::filesystem::path ResolveScriptPath(const std::string& requested) const;
|
||||
};
|
||||
|
||||
} // namespace sdl3cpp::script
|
||||
230
src/script/gui_manager.cpp
Normal file
230
src/script/gui_manager.cpp
Normal file
@@ -0,0 +1,230 @@
|
||||
#include "script/gui_manager.hpp"
|
||||
|
||||
#include <lua.hpp>
|
||||
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
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<GuiCommand> GuiManager::LoadGuiCommands() {
|
||||
std::vector<GuiCommand> 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<int>(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<float>(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<float>(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<float>(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<float>(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
|
||||
31
src/script/gui_manager.hpp
Normal file
31
src/script/gui_manager.hpp
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "script/gui_types.hpp"
|
||||
#include "script/lua_helpers.hpp"
|
||||
|
||||
#include <lua.hpp>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace sdl3cpp::script {
|
||||
|
||||
class GuiManager {
|
||||
public:
|
||||
explicit GuiManager(lua_State* L);
|
||||
|
||||
std::vector<GuiCommand> 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
|
||||
@@ -4,35 +4,10 @@
|
||||
#include "script/mesh_loader.hpp"
|
||||
|
||||
#include <btBulletDynamicsCommon.h>
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
#include <lua.hpp>
|
||||
|
||||
namespace sdl3cpp::script {
|
||||
|
||||
namespace {
|
||||
|
||||
glm::vec3 ToVec3(const std::array<float, 3>& value) {
|
||||
return glm::vec3(value[0], value[1], value[2]);
|
||||
}
|
||||
|
||||
glm::quat ToQuat(const std::array<float, 4>& 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<float, 3> translation = ReadVector3(L, 1);
|
||||
std::array<float, 4> 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
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
#include "script/lua_helpers.hpp"
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
#include <lua.hpp>
|
||||
#include <stdexcept>
|
||||
|
||||
@@ -74,4 +78,31 @@ std::array<float, 16> IdentityMatrix() {
|
||||
0.0f, 0.0f, 0.0f, 1.0f};
|
||||
}
|
||||
|
||||
glm::vec3 ToVec3(const std::array<float, 3>& value) {
|
||||
return glm::vec3(value[0], value[1], value[2]);
|
||||
}
|
||||
|
||||
glm::quat ToQuat(const std::array<float, 4>& 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<float, 3> translation = ReadVector3(L, 1);
|
||||
std::array<float, 4> 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
|
||||
|
||||
@@ -13,6 +13,7 @@ std::array<float, 4> ReadQuaternion(lua_State* L, int index);
|
||||
std::array<float, 16> ReadMatrix(lua_State* L, int index);
|
||||
std::string GetLuaError(lua_State* L);
|
||||
std::array<float, 16> IdentityMatrix();
|
||||
int LuaGlmMatrixFromTransform(lua_State* L);
|
||||
|
||||
} // namespace sdl3cpp::script
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "script/mesh_loader.hpp"
|
||||
#include "script/script_engine.hpp"
|
||||
|
||||
#include <assimp/Importer.hpp>
|
||||
#include <assimp/material.h>
|
||||
@@ -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<sdl3cpp::script::ScriptEngine*>(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
|
||||
|
||||
@@ -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
|
||||
|
||||
194
src/script/scene_manager.cpp
Normal file
194
src/script/scene_manager.cpp
Normal file
@@ -0,0 +1,194 @@
|
||||
#include "script/scene_manager.hpp"
|
||||
|
||||
#include <lua.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace sdl3cpp::script {
|
||||
|
||||
SceneManager::SceneManager(lua_State* L) : L_(L) {}
|
||||
|
||||
std::vector<SceneManager::SceneObject> 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<SceneObject> objects;
|
||||
objects.reserve(count);
|
||||
|
||||
for (size_t i = 1; i <= count; ++i) {
|
||||
lua_rawgeti(L_, -1, static_cast<int>(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<float, 16> 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<float, 16> matrix = ReadMatrix(L_, -1);
|
||||
lua_pop(L_, 1);
|
||||
return matrix;
|
||||
}
|
||||
|
||||
std::array<float, 16> 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<float, 16> matrix = ReadMatrix(L_, -1);
|
||||
lua_pop(L_, 1);
|
||||
return matrix;
|
||||
}
|
||||
|
||||
std::vector<core::Vertex> 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<core::Vertex> vertices;
|
||||
vertices.reserve(count);
|
||||
|
||||
for (size_t i = 1; i <= count; ++i) {
|
||||
lua_rawgeti(L_, absIndex, static_cast<int>(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<uint16_t> 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<uint16_t> indices;
|
||||
indices.reserve(count);
|
||||
|
||||
for (size_t i = 1; i <= count; ++i) {
|
||||
lua_rawgeti(L_, absIndex, static_cast<int>(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<uint16_t>(value - 1));
|
||||
}
|
||||
|
||||
return indices;
|
||||
}
|
||||
|
||||
std::string SceneManager::GetLuaError() {
|
||||
const char* message = lua_tostring(L_, -1);
|
||||
return message ? message : "unknown lua error";
|
||||
}
|
||||
|
||||
} // namespace sdl3cpp::script
|
||||
38
src/script/scene_manager.hpp
Normal file
38
src/script/scene_manager.hpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <lua.hpp>
|
||||
#include <lauxlib.h>
|
||||
#include "script/lua_helpers.hpp"
|
||||
#include "core/vertex.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace sdl3cpp::script {
|
||||
|
||||
class SceneManager {
|
||||
public:
|
||||
struct SceneObject {
|
||||
std::vector<core::Vertex> vertices;
|
||||
std::vector<uint16_t> indices;
|
||||
int computeModelMatrixRef = LUA_REFNIL;
|
||||
std::string shaderKey = "default";
|
||||
};
|
||||
|
||||
explicit SceneManager(lua_State* L);
|
||||
|
||||
std::vector<SceneObject> LoadSceneObjects();
|
||||
std::array<float, 16> ComputeModelMatrix(int functionRef, float time);
|
||||
std::array<float, 16> GetViewProjectionMatrix(float aspect);
|
||||
|
||||
private:
|
||||
lua_State* L_;
|
||||
|
||||
std::vector<core::Vertex> ReadVertexArray(int index);
|
||||
std::vector<uint16_t> ReadIndexArray(int index);
|
||||
std::string GetLuaError();
|
||||
};
|
||||
|
||||
} // namespace sdl3cpp::script
|
||||
@@ -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 <assimp/Importer.hpp>
|
||||
#include <assimp/material.h>
|
||||
#include <assimp/postprocess.h>
|
||||
#include <assimp/scene.h>
|
||||
#include <btBulletDynamicsCommon.h>
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
#include <lua.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <system_error>
|
||||
#include <utility>
|
||||
|
||||
namespace sdl3cpp::script {
|
||||
|
||||
namespace detail {
|
||||
|
||||
std::array<float, 3> ReadVector3(lua_State* L, int index) {
|
||||
std::array<float, 3> 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<int>(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<float>(lua_tonumber(L, -1));
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::array<float, 16> ReadMatrix(lua_State* L, int index) {
|
||||
std::array<float, 16> 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<int>(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<float>(lua_tonumber(L, -1));
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::array<float, 4> ReadQuaternion(lua_State* L, int index) {
|
||||
std::array<float, 4> 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<int>(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<float>(lua_tonumber(L, -1));
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
namespace {
|
||||
|
||||
struct MeshPayload {
|
||||
std::vector<std::array<float, 3>> positions;
|
||||
std::vector<std::array<float, 3>> colors;
|
||||
std::vector<uint32_t> 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<float, 3>& value) {
|
||||
return glm::vec3(value[0], value[1], value[2]);
|
||||
}
|
||||
|
||||
glm::quat ToQuat(const std::array<float, 4>& 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<int>(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<lua_Integer>(payload.indices[index]) + 1);
|
||||
lua_rawseti(L, -2, static_cast<int>(index + 1));
|
||||
}
|
||||
lua_setfield(L, -2, "indices");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int LuaLoadMeshFromFile(lua_State* L) {
|
||||
auto* script = static_cast<ScriptEngine*>(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<ScriptEngine*>(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<float, 3> halfExtents = detail::ReadVector3(L, 2);
|
||||
float mass = static_cast<float>(luaL_checknumber(L, 3));
|
||||
std::array<float, 3> origin = detail::ReadVector3(L, 4);
|
||||
std::array<float, 4> 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<ScriptEngine*>(lua_touserdata(L, lua_upvalueindex(1)));
|
||||
float deltaTime = static_cast<float>(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<ScriptEngine*>(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<ScriptEngine*>(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<ScriptEngine*>(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<float, 3> translation = detail::ReadVector3(L, 1);
|
||||
std::array<float, 4> 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<float, 16> 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>()) {
|
||||
physicsBridge_(std::make_unique<PhysicsBridge>()),
|
||||
sceneManager_(std::make_unique<SceneManager>(L_)),
|
||||
shaderManager_(std::make_unique<ShaderManager>(L_)),
|
||||
guiManager_(std::make_unique<GuiManager>(L_)),
|
||||
audioManager_(std::make_unique<AudioManager>(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::SceneObject> 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<SceneObject> objects;
|
||||
objects.reserve(count);
|
||||
|
||||
for (size_t i = 1; i <= count; ++i) {
|
||||
lua_rawgeti(L_, -1, static_cast<int>(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<SceneManager::SceneObject> ScriptEngine::LoadSceneObjects() {
|
||||
return sceneManager_->LoadSceneObjects();
|
||||
}
|
||||
|
||||
std::array<float, 16> 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<float, 16> matrix = detail::ReadMatrix(L_, -1);
|
||||
lua_pop(L_, 1);
|
||||
return matrix;
|
||||
return sceneManager_->ComputeModelMatrix(functionRef, time);
|
||||
}
|
||||
|
||||
std::array<float, 16> 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<float, 16> matrix = detail::ReadMatrix(L_, -1);
|
||||
lua_pop(L_, 1);
|
||||
return matrix;
|
||||
return sceneManager_->GetViewProjectionMatrix(aspect);
|
||||
}
|
||||
|
||||
std::vector<core::Vertex> 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<core::Vertex> vertices;
|
||||
vertices.reserve(count);
|
||||
|
||||
for (size_t i = 1; i <= count; ++i) {
|
||||
lua_rawgeti(L, absIndex, static_cast<int>(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<uint16_t> 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<uint16_t> indices;
|
||||
indices.reserve(count);
|
||||
|
||||
for (size_t i = 1; i <= count; ++i) {
|
||||
lua_rawgeti(L, absIndex, static_cast<int>(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<uint16_t>(value - 1));
|
||||
}
|
||||
|
||||
return indices;
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, ScriptEngine::ShaderPaths> 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<std::string, ShaderPaths> 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<std::string, ShaderManager::ShaderPaths> ScriptEngine::LoadShaderPathsMap() {
|
||||
return shaderManager_->LoadShaderPathsMap();
|
||||
}
|
||||
|
||||
PhysicsBridge& ScriptEngine::GetPhysicsBridge() {
|
||||
@@ -693,262 +88,32 @@ PhysicsBridge& ScriptEngine::GetPhysicsBridge() {
|
||||
}
|
||||
|
||||
std::vector<GuiCommand> ScriptEngine::LoadGuiCommands() {
|
||||
std::vector<GuiCommand> 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<int>(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<float>(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<float>(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<float>(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<float>(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
|
||||
954
src/script/script_engine.cpp.backup
Normal file
954
src/script/script_engine.cpp.backup
Normal file
@@ -0,0 +1,954 @@
|
||||
#include "script/script_engine.hpp"
|
||||
#include "app/audio_player.hpp"
|
||||
|
||||
#include <assimp/Importer.hpp>
|
||||
#include <assimp/material.h>
|
||||
#include <assimp/postprocess.h>
|
||||
#include <assimp/scene.h>
|
||||
#include <btBulletDynamicsCommon.h>
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <system_error>
|
||||
#include <utility>
|
||||
|
||||
namespace sdl3cpp::script {
|
||||
|
||||
namespace detail {
|
||||
|
||||
std::array<float, 3> ReadVector3(lua_State* L, int index) {
|
||||
std::array<float, 3> 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<int>(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<float>(lua_tonumber(L, -1));
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::array<float, 16> ReadMatrix(lua_State* L, int index) {
|
||||
std::array<float, 16> 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<int>(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<float>(lua_tonumber(L, -1));
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::array<float, 4> ReadQuaternion(lua_State* L, int index) {
|
||||
std::array<float, 4> 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<int>(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<float>(lua_tonumber(L, -1));
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
namespace {
|
||||
|
||||
struct MeshPayload {
|
||||
std::vector<std::array<float, 3>> positions;
|
||||
std::vector<std::array<float, 3>> colors;
|
||||
std::vector<uint32_t> 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<float, 3>& value) {
|
||||
return glm::vec3(value[0], value[1], value[2]);
|
||||
}
|
||||
|
||||
glm::quat ToQuat(const std::array<float, 4>& 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<int>(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<lua_Integer>(payload.indices[index]) + 1);
|
||||
lua_rawseti(L, -2, static_cast<int>(index + 1));
|
||||
}
|
||||
lua_setfield(L, -2, "indices");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int LuaLoadMeshFromFile(lua_State* L) {
|
||||
auto* script = static_cast<ScriptEngine*>(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<ScriptEngine*>(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<float, 3> halfExtents = detail::ReadVector3(L, 2);
|
||||
float mass = static_cast<float>(luaL_checknumber(L, 3));
|
||||
std::array<float, 3> origin = detail::ReadVector3(L, 4);
|
||||
std::array<float, 4> 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<ScriptEngine*>(lua_touserdata(L, lua_upvalueindex(1)));
|
||||
float deltaTime = static_cast<float>(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<ScriptEngine*>(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<ScriptEngine*>(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<ScriptEngine*>(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<float, 3> translation = detail::ReadVector3(L, 1);
|
||||
std::array<float, 4> 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<float, 16> 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>()) {
|
||||
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::SceneObject> 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<SceneObject> objects;
|
||||
objects.reserve(count);
|
||||
|
||||
for (size_t i = 1; i <= count; ++i) {
|
||||
lua_rawgeti(L_, -1, static_cast<int>(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<float, 16> 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<float, 16> matrix = detail::ReadMatrix(L_, -1);
|
||||
lua_pop(L_, 1);
|
||||
return matrix;
|
||||
}
|
||||
|
||||
std::array<float, 16> 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<float, 16> matrix = detail::ReadMatrix(L_, -1);
|
||||
lua_pop(L_, 1);
|
||||
return matrix;
|
||||
}
|
||||
|
||||
std::vector<core::Vertex> 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<core::Vertex> vertices;
|
||||
vertices.reserve(count);
|
||||
|
||||
for (size_t i = 1; i <= count; ++i) {
|
||||
lua_rawgeti(L, absIndex, static_cast<int>(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<uint16_t> 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<uint16_t> indices;
|
||||
indices.reserve(count);
|
||||
|
||||
for (size_t i = 1; i <= count; ++i) {
|
||||
lua_rawgeti(L, absIndex, static_cast<int>(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<uint16_t>(value - 1));
|
||||
}
|
||||
|
||||
return indices;
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, ScriptEngine::ShaderPaths> 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<std::string, ShaderPaths> 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<GuiCommand> ScriptEngine::LoadGuiCommands() {
|
||||
std::vector<GuiCommand> 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<int>(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<float>(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<float>(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<float>(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<float>(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
|
||||
@@ -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<core::Vertex> vertices;
|
||||
std::vector<uint16_t> indices;
|
||||
int computeModelMatrixRef = LUA_REFNIL;
|
||||
std::string shaderKey = "default";
|
||||
};
|
||||
|
||||
enum class AudioCommandType {
|
||||
Background,
|
||||
Effect,
|
||||
};
|
||||
|
||||
std::vector<SceneObject> LoadSceneObjects();
|
||||
std::vector<SceneManager::SceneObject> LoadSceneObjects();
|
||||
std::array<float, 16> ComputeModelMatrix(int functionRef, float time);
|
||||
std::array<float, 16> GetViewProjectionMatrix(float aspect);
|
||||
std::unordered_map<std::string, ShaderPaths> LoadShaderPathsMap();
|
||||
std::unordered_map<std::string, ShaderManager::ShaderPaths> LoadShaderPathsMap();
|
||||
std::vector<GuiCommand> 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<core::Vertex> ReadVertexArray(lua_State* L, int index);
|
||||
static std::vector<uint16_t> 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> physicsBridge_;
|
||||
app::AudioPlayer* audioPlayer_ = nullptr;
|
||||
std::vector<AudioCommand> pendingAudioCommands_;
|
||||
std::vector<AudioManager::AudioCommand> pendingAudioCommands_;
|
||||
std::unique_ptr<SceneManager> sceneManager_;
|
||||
std::unique_ptr<ShaderManager> shaderManager_;
|
||||
std::unique_ptr<GuiManager> guiManager_;
|
||||
std::unique_ptr<AudioManager> audioManager_;
|
||||
};
|
||||
|
||||
} // namespace sdl3cpp::script
|
||||
|
||||
74
src/script/shader_manager.cpp
Normal file
74
src/script/shader_manager.cpp
Normal file
@@ -0,0 +1,74 @@
|
||||
#include "script/shader_manager.hpp"
|
||||
|
||||
#include <lua.hpp>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace sdl3cpp::script {
|
||||
|
||||
ShaderManager::ShaderManager(lua_State* L) : L_(L) {}
|
||||
|
||||
std::unordered_map<std::string, ShaderManager::ShaderPaths> 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<std::string, ShaderPaths> 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
|
||||
28
src/script/shader_manager.hpp
Normal file
28
src/script/shader_manager.hpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <lua.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace sdl3cpp::script {
|
||||
|
||||
class ShaderManager {
|
||||
public:
|
||||
struct ShaderPaths {
|
||||
std::string vertex;
|
||||
std::string fragment;
|
||||
};
|
||||
|
||||
explicit ShaderManager(lua_State* L);
|
||||
|
||||
std::unordered_map<std::string, ShaderPaths> LoadShaderPathsMap();
|
||||
|
||||
private:
|
||||
lua_State* L_;
|
||||
|
||||
ShaderPaths ReadShaderPathsTable(int index);
|
||||
std::string GetLuaError();
|
||||
};
|
||||
|
||||
} // namespace sdl3cpp::script
|
||||
Reference in New Issue
Block a user