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