diff --git a/config/seed_runtime.json b/config/seed_runtime.json index 04251ef..df2ba84 100644 --- a/config/seed_runtime.json +++ b/config/seed_runtime.json @@ -61,7 +61,7 @@ "renderer": "vulkan" }, "materialx": { - "enabled": false, + "enabled": true, "parameters_enabled": true, "document": "MaterialX/resources/Materials/Examples/StandardSurface/standard_surface_carpaint.mtlx", "shader_key": "materialx", diff --git a/scripts/cube_logic.lua b/scripts/cube_logic.lua index 18fb140..be26a03 100644 --- a/scripts/cube_logic.lua +++ b/scripts/cube_logic.lua @@ -510,6 +510,21 @@ local function update_audio_controls() end local rotation_speed = 0.9 +local default_material_shader = "pbr" + +local function resolve_material_shader() + if type(config) ~= "table" then + return default_material_shader + end + local materialx = config.materialx + if type(materialx) ~= "table" or not materialx.enabled then + return default_material_shader + end + if type(materialx.shader_key) == "string" and materialx.shader_key ~= "" then + return materialx.shader_key + end + return "materialx" +end local function build_static_model_matrix(position, scale) local translation = math3d.translation(position[1], position[2], position[3]) @@ -523,6 +538,7 @@ local function apply_color_to_vertices(color) local v = cube_vertices[i] colored_vertices[i] = { position = v.position, + normal = v.normal, color = color, } end @@ -564,7 +580,8 @@ local function create_skybox() end local function create_spinning_cube() - log_debug("Spinning cube shader=pbr (MaterialX-driven)") + local shader_key = resolve_material_shader() + log_debug("Spinning cube shader=%s", shader_key) local function compute_model_matrix(time) local rotation = math3d.rotation_y(time * rotation_speed) local scale = scale_matrix(1.5, 1.5, 1.5) -- Make cube 3x3x3 units @@ -576,7 +593,7 @@ local function create_spinning_cube() vertices = cube_vertices, indices = (#cube_indices_double_sided > 0) and cube_indices_double_sided or cube_indices, compute_model_matrix = compute_model_matrix, - shader_key = "pbr", + shader_key = shader_key, } end @@ -764,7 +781,7 @@ function get_shader_paths() end -function get_view_projection(aspect) +local function build_view_state(aspect) local now = os.clock() local dt = 0.0 if last_frame_time then @@ -789,7 +806,21 @@ function get_view_projection(aspect) local view = math3d.look_at(camera.position, center, world_up) local projection = math3d.perspective(camera.fov, aspect, camera.near, camera.far) - return math3d.multiply(projection, view) + return { + view = view, + proj = projection, + view_proj = math3d.multiply(projection, view), + camera_pos = {camera.position[1], camera.position[2], camera.position[3]}, + } +end + +function get_view_state(aspect) + return build_view_state(aspect) +end + +function get_view_projection(aspect) + local state = build_view_state(aspect) + return state.view_proj end function get_gui_commands() diff --git a/scripts/shader_toolkit.lua b/scripts/shader_toolkit.lua index 2c67bb9..dd55a4b 100644 --- a/scripts/shader_toolkit.lua +++ b/scripts/shader_toolkit.lua @@ -315,7 +315,8 @@ local vertex_color_source = [[ #version 450 layout(location = 0) in vec3 inPos; -layout(location = 1) in vec3 inColor; +layout(location = 1) in vec3 inNormal; +layout(location = 2) in vec3 inColor; layout(location = 0) out vec3 fragColor; @@ -389,8 +390,8 @@ local shadow_vertex_source = [[ #version 450 layout(location = 0) in vec3 inPosition; -layout(location = 1) in vec3 inColor; -// layout(location = 2) in vec2 inTexCoord; // Not used +layout(location = 1) in vec3 inNormal; +layout(location = 2) in vec3 inColor; layout(push_constant) uniform PushConstants { mat4 model; @@ -594,7 +595,8 @@ local vertex_world_color_source = [[ #version 450 layout(location = 0) in vec3 inPos; -layout(location = 1) in vec3 inColor; +layout(location = 1) in vec3 inNormal; +layout(location = 2) in vec3 inColor; layout(location = 0) out vec3 fragColor; layout(location = 1) out vec3 fragWorldPos; @@ -1027,8 +1029,8 @@ local pbr_vertex_source = [[ #version 450 layout(location = 0) in vec3 inPosition; -layout(location = 1) in vec3 inColor; // Color instead of normal -layout(location = 2) in vec2 inTexCoord; // Not used for now +layout(location = 1) in vec3 inNormal; +layout(location = 2) in vec3 inColor; layout(location = 0) out vec3 fragColor; layout(location = 1) out vec3 fragWorldPos; @@ -1061,8 +1063,8 @@ void main() { gl_Position = pc.proj * pc.view * worldPos; fragWorldPos = worldPos.xyz; - fragNormal = normalize(mat3(pc.model) * vec3(0.0, 0.0, 1.0)); // Simple normal for flat shading - fragTexCoord = vec2(0.0, 0.0); // Not used + fragNormal = normalize(mat3(pc.model) * inNormal); + fragTexCoord = vec2(0.0, 0.0); fragColor = inColor; // Use vertex color } ]] diff --git a/scripts/shader_variants.lua b/scripts/shader_variants.lua index 3a8e0ab..6a670f7 100644 --- a/scripts/shader_variants.lua +++ b/scripts/shader_variants.lua @@ -202,7 +202,8 @@ local function build_static_cube_variants() #version 450 layout(location = 0) in vec3 inPos; -layout(location = 1) in vec3 inColor; +layout(location = 1) in vec3 inNormal; +layout(location = 2) in vec3 inColor; layout(location = 0) out vec3 fragColor; diff --git a/src/core/vertex.hpp b/src/core/vertex.hpp index df35253..c69c2e0 100644 --- a/src/core/vertex.hpp +++ b/src/core/vertex.hpp @@ -7,6 +7,7 @@ namespace sdl3cpp::core { struct Vertex { std::array position; + std::array normal; std::array color; }; diff --git a/src/services/impl/bgfx_graphics_backend.cpp b/src/services/impl/bgfx_graphics_backend.cpp index 60c899d..1137344 100644 --- a/src/services/impl/bgfx_graphics_backend.cpp +++ b/src/services/impl/bgfx_graphics_backend.cpp @@ -12,6 +12,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -25,6 +28,31 @@ std::string ToLower(std::string value) { return value; } +glm::mat4 ToMat4(const std::array& value) { + return glm::make_mat4(value.data()); +} + +bool IsIdentityMatrix(const std::array& value) { + const float identity[16] = { + 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 + }; + for (size_t i = 0; i < 16; ++i) { + if (value[i] != identity[i]) { + return false; + } + } + return true; +} + +void SetUniformIfValid(bgfx::UniformHandle handle, const void* data, uint16_t count = 1) { + if (bgfx::isValid(handle)) { + bgfx::setUniform(handle, data, count); + } +} + bgfx::RendererType::Enum RendererFromString(const std::string& value) { const std::string lower = ToLower(value); if (lower == "vulkan") { @@ -48,8 +76,20 @@ BgfxGraphicsBackend::BgfxGraphicsBackend(std::shared_ptr configS } vertexLayout_.begin() .add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float) + .add(bgfx::Attrib::Normal, 3, bgfx::AttribType::Float) .add(bgfx::Attrib::Color0, 3, bgfx::AttribType::Float) .end(); + + const std::array identity = { + 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 + }; + viewState_.view = identity; + viewState_.proj = identity; + viewState_.viewProj = identity; + viewState_.cameraPosition = {0.0f, 0.0f, 0.0f}; } BgfxGraphicsBackend::~BgfxGraphicsBackend() { @@ -134,6 +174,7 @@ void BgfxGraphicsBackend::Initialize(void* window, const GraphicsConfig& config) bgfx::setViewClear(viewId_, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH, 0x1f1f1fff, 1.0f, 0); bgfx::setDebug(BGFX_DEBUG_TEXT); + InitializeUniforms(); initialized_ = true; } @@ -148,6 +189,7 @@ void BgfxGraphicsBackend::Shutdown() { DestroyPipelines(); DestroyBuffers(); + DestroyUniforms(); bgfx::shutdown(); initialized_ = false; } @@ -235,6 +277,64 @@ bgfx::ShaderHandle BgfxGraphicsBackend::CreateShader(const std::string& label, return bgfx::createShader(mem); } +void BgfxGraphicsBackend::InitializeUniforms() { + materialXUniforms_.worldMatrix = bgfx::createUniform("u_worldMatrix", bgfx::UniformType::Mat4); + materialXUniforms_.viewMatrix = bgfx::createUniform("u_viewMatrix", bgfx::UniformType::Mat4); + materialXUniforms_.projectionMatrix = bgfx::createUniform("u_projectionMatrix", bgfx::UniformType::Mat4); + materialXUniforms_.viewProjectionMatrix = bgfx::createUniform("u_viewProjectionMatrix", bgfx::UniformType::Mat4); + materialXUniforms_.worldViewMatrix = bgfx::createUniform("u_worldViewMatrix", bgfx::UniformType::Mat4); + materialXUniforms_.worldViewProjectionMatrix = bgfx::createUniform("u_worldViewProjectionMatrix", bgfx::UniformType::Mat4); + materialXUniforms_.worldInverseTransposeMatrix = bgfx::createUniform("u_worldInverseTransposeMatrix", bgfx::UniformType::Mat4); + materialXUniforms_.viewPosition = bgfx::createUniform("u_viewPosition", bgfx::UniformType::Vec4); +} + +void BgfxGraphicsBackend::DestroyUniforms() { + bgfx::UniformHandle handles[] = { + materialXUniforms_.worldMatrix, + materialXUniforms_.viewMatrix, + materialXUniforms_.projectionMatrix, + materialXUniforms_.viewProjectionMatrix, + materialXUniforms_.worldViewMatrix, + materialXUniforms_.worldViewProjectionMatrix, + materialXUniforms_.worldInverseTransposeMatrix, + materialXUniforms_.viewPosition + }; + for (bgfx::UniformHandle handle : handles) { + if (bgfx::isValid(handle)) { + bgfx::destroy(handle); + } + } + materialXUniforms_ = MaterialXUniforms{}; +} + +void BgfxGraphicsBackend::ApplyMaterialXUniforms(const std::array& modelMatrix) { + glm::mat4 model = ToMat4(modelMatrix); + glm::mat4 view = ToMat4(viewState_.view); + glm::mat4 proj = ToMat4(viewState_.proj); + glm::mat4 viewProj = (IsIdentityMatrix(viewState_.view) && IsIdentityMatrix(viewState_.proj)) + ? ToMat4(viewState_.viewProj) + : proj * view; + glm::mat4 worldView = view * model; + glm::mat4 worldViewProj = viewProj * model; + glm::mat4 worldInverseTranspose = glm::transpose(glm::inverse(model)); + + SetUniformIfValid(materialXUniforms_.worldMatrix, glm::value_ptr(model)); + SetUniformIfValid(materialXUniforms_.viewMatrix, glm::value_ptr(view)); + SetUniformIfValid(materialXUniforms_.projectionMatrix, glm::value_ptr(proj)); + SetUniformIfValid(materialXUniforms_.viewProjectionMatrix, glm::value_ptr(viewProj)); + SetUniformIfValid(materialXUniforms_.worldViewMatrix, glm::value_ptr(worldView)); + SetUniformIfValid(materialXUniforms_.worldViewProjectionMatrix, glm::value_ptr(worldViewProj)); + SetUniformIfValid(materialXUniforms_.worldInverseTransposeMatrix, glm::value_ptr(worldInverseTranspose)); + + float viewPosition[4] = { + viewState_.cameraPosition[0], + viewState_.cameraPosition[1], + viewState_.cameraPosition[2], + 1.0f + }; + SetUniformIfValid(materialXUniforms_.viewPosition, viewPosition); +} + GraphicsPipelineHandle BgfxGraphicsBackend::CreatePipeline(GraphicsDeviceHandle device, const std::string& shaderKey, const ShaderPaths& shaderPaths) { @@ -339,14 +439,7 @@ bool BgfxGraphicsBackend::BeginFrame(GraphicsDeviceHandle device) { if (!initialized_) { return false; } - const float identity[16] = { - 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 - }; bgfx::setViewRect(viewId_, 0, 0, viewportWidth_, viewportHeight_); - bgfx::setViewTransform(viewId_, viewProj_.data(), identity); bgfx::touch(viewId_); return true; } @@ -359,8 +452,9 @@ bool BgfxGraphicsBackend::EndFrame(GraphicsDeviceHandle device) { return true; } -void BgfxGraphicsBackend::SetViewProjection(const std::array& viewProj) { - viewProj_ = viewProj; +void BgfxGraphicsBackend::SetViewState(const ViewState& viewState) { + viewState_ = viewState; + bgfx::setViewTransform(viewId_, viewState_.view.data(), viewState_.proj.data()); } void BgfxGraphicsBackend::Draw(GraphicsDeviceHandle device, GraphicsPipelineHandle pipeline, @@ -394,6 +488,7 @@ void BgfxGraphicsBackend::Draw(GraphicsDeviceHandle device, GraphicsPipelineHand } bgfx::setTransform(modelMatrix.data()); + ApplyMaterialXUniforms(modelMatrix); bgfx::setVertexBuffer(0, vb->handle, startVertex, availableVertices); bgfx::setIndexBuffer(ib->handle, indexOffset, indexCount); bgfx::setState(BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A | BGFX_STATE_WRITE_Z | diff --git a/src/services/impl/bgfx_graphics_backend.hpp b/src/services/impl/bgfx_graphics_backend.hpp index 04fd294..888db09 100644 --- a/src/services/impl/bgfx_graphics_backend.hpp +++ b/src/services/impl/bgfx_graphics_backend.hpp @@ -35,7 +35,7 @@ public: void DestroyBuffer(GraphicsDeviceHandle device, GraphicsBufferHandle buffer) override; bool BeginFrame(GraphicsDeviceHandle device) override; bool EndFrame(GraphicsDeviceHandle device) override; - void SetViewProjection(const std::array& viewProj) override; + void SetViewState(const ViewState& viewState) override; void Draw(GraphicsDeviceHandle device, GraphicsPipelineHandle pipeline, GraphicsBufferHandle vertexBuffer, GraphicsBufferHandle indexBuffer, uint32_t indexOffset, uint32_t indexCount, int32_t vertexOffset, @@ -61,6 +61,17 @@ private: uint32_t indexCount = 0; }; + struct MaterialXUniforms { + bgfx::UniformHandle worldMatrix = BGFX_INVALID_HANDLE; + bgfx::UniformHandle viewMatrix = BGFX_INVALID_HANDLE; + bgfx::UniformHandle projectionMatrix = BGFX_INVALID_HANDLE; + bgfx::UniformHandle viewProjectionMatrix = BGFX_INVALID_HANDLE; + bgfx::UniformHandle worldViewMatrix = BGFX_INVALID_HANDLE; + bgfx::UniformHandle worldViewProjectionMatrix = BGFX_INVALID_HANDLE; + bgfx::UniformHandle worldInverseTransposeMatrix = BGFX_INVALID_HANDLE; + bgfx::UniformHandle viewPosition = BGFX_INVALID_HANDLE; + }; + void SetupPlatformData(void* window); bgfx::RendererType::Enum ResolveRendererType() const; std::vector ReadShaderSource(const std::string& path, @@ -68,6 +79,9 @@ private: bgfx::ShaderHandle CreateShader(const std::string& label, const std::string& source, bool isVertex) const; + void InitializeUniforms(); + void DestroyUniforms(); + void ApplyMaterialXUniforms(const std::array& modelMatrix); void DestroyPipelines(); void DestroyBuffers(); @@ -77,7 +91,8 @@ private: std::unordered_map> pipelines_; std::unordered_map> vertexBuffers_; std::unordered_map> indexBuffers_; - std::array viewProj_{}; + ViewState viewState_{}; + MaterialXUniforms materialXUniforms_{}; uint32_t viewportWidth_ = 0; uint32_t viewportHeight_ = 0; bool initialized_ = false; diff --git a/src/services/impl/graphics_service.cpp b/src/services/impl/graphics_service.cpp index 7c474d6..8c31661 100644 --- a/src/services/impl/graphics_service.cpp +++ b/src/services/impl/graphics_service.cpp @@ -144,17 +144,17 @@ bool GraphicsService::BeginFrame() { } void GraphicsService::RenderScene(const std::vector& commands, - const std::array& viewProj) { + const ViewState& viewState) { logger_->Trace("GraphicsService", "RenderScene", "commands.size=" + std::to_string(commands.size()) + - ", viewProj.size=" + std::to_string(viewProj.size())); + ", viewProj.size=" + std::to_string(viewState.viewProj.size())); if (!initialized_) { return; } // Set the view-projection matrix for the frame - backend_->SetViewProjection(viewProj); + backend_->SetViewState(viewState); // Execute draw calls for (const auto& command : commands) { diff --git a/src/services/impl/graphics_service.hpp b/src/services/impl/graphics_service.hpp index 35e67fd..83d473e 100644 --- a/src/services/impl/graphics_service.hpp +++ b/src/services/impl/graphics_service.hpp @@ -38,7 +38,7 @@ public: void UploadIndexData(const std::vector& indices) override; bool BeginFrame() override; void RenderScene(const std::vector& commands, - const std::array& viewProj) override; + const ViewState& viewState) override; bool EndFrame() override; void WaitIdle() override; GraphicsDeviceHandle GetDevice() const override; diff --git a/src/services/impl/gxm_graphics_backend.cpp b/src/services/impl/gxm_graphics_backend.cpp index 28d7225..7126586 100644 --- a/src/services/impl/gxm_graphics_backend.cpp +++ b/src/services/impl/gxm_graphics_backend.cpp @@ -461,8 +461,9 @@ bool GxmGraphicsBackend::EndFrame(GraphicsDeviceHandle device) { return true; } -void GxmGraphicsBackend::SetViewProjection(const std::array& viewProj) { - std::cout << "GXM: Setting view-projection matrix" << std::endl; +void GxmGraphicsBackend::SetViewState(const ViewState& viewState) { + std::cout << "GXM: Setting view state" << std::endl; + (void)viewState; // Matrix will be set when drawing with specific pipeline } diff --git a/src/services/impl/gxm_graphics_backend.hpp b/src/services/impl/gxm_graphics_backend.hpp index 9c95f35..cfae653 100644 --- a/src/services/impl/gxm_graphics_backend.hpp +++ b/src/services/impl/gxm_graphics_backend.hpp @@ -36,7 +36,7 @@ public: bool BeginFrame(GraphicsDeviceHandle device) override; bool EndFrame(GraphicsDeviceHandle device) override; - void SetViewProjection(const std::array& viewProj) override; + void SetViewState(const ViewState& viewState) override; void Draw(GraphicsDeviceHandle device, GraphicsPipelineHandle pipeline, GraphicsBufferHandle vertexBuffer, GraphicsBufferHandle indexBuffer, diff --git a/src/services/impl/mesh_service.cpp b/src/services/impl/mesh_service.cpp index 31ea023..5e0bb8b 100644 --- a/src/services/impl/mesh_service.cpp +++ b/src/services/impl/mesh_service.cpp @@ -53,7 +53,8 @@ bool MeshService::LoadFromFile(const std::string& requestedPath, Assimp::Importer importer; const aiScene* scene = importer.ReadFile( resolved.string(), - aiProcess_Triangulate | aiProcess_JoinIdenticalVertices | aiProcess_PreTransformVertices); + aiProcess_Triangulate | aiProcess_JoinIdenticalVertices | + aiProcess_PreTransformVertices | aiProcess_GenNormals); if (!scene) { outError = importer.GetErrorString() ? importer.GetErrorString() : "Assimp failed to load mesh"; @@ -72,9 +73,11 @@ bool MeshService::LoadFromFile(const std::string& requestedPath, } outPayload.positions.clear(); + outPayload.normals.clear(); outPayload.colors.clear(); outPayload.indices.clear(); outPayload.positions.reserve(mesh->mNumVertices); + outPayload.normals.reserve(mesh->mNumVertices); outPayload.colors.reserve(mesh->mNumVertices); outPayload.indices.reserve(mesh->mNumFaces * 3); @@ -93,6 +96,12 @@ bool MeshService::LoadFromFile(const std::string& requestedPath, const aiVector3D& vertex = mesh->mVertices[i]; outPayload.positions.push_back({vertex.x, vertex.y, vertex.z}); + aiVector3D normal(0.0f, 0.0f, 1.0f); + if (mesh->HasNormals()) { + normal = mesh->mNormals[i]; + } + outPayload.normals.push_back({normal.x, normal.y, normal.z}); + aiColor3D color = materialColor; if (mesh->HasVertexColors(0) && mesh->mColors[0]) { const aiColor4D& vertexColor = mesh->mColors[0][i]; @@ -123,6 +132,7 @@ void MeshService::PushMeshToLua(lua_State* L, const MeshPayload& payload) { if (logger_) { logger_->Trace("MeshService", "PushMeshToLua", "positions.size=" + std::to_string(payload.positions.size()) + + ", normals.size=" + std::to_string(payload.normals.size()) + ", colors.size=" + std::to_string(payload.colors.size()) + ", indices.size=" + std::to_string(payload.indices.size()) + ", luaStateIsNull=" + std::string(L ? "false" : "true")); @@ -140,6 +150,17 @@ void MeshService::PushMeshToLua(lua_State* L, const MeshPayload& payload) { } lua_setfield(L, -2, "position"); + lua_newtable(L); + std::array normal = {0.0f, 0.0f, 1.0f}; + if (vertexIndex < payload.normals.size()) { + normal = payload.normals[vertexIndex]; + } + for (int component = 0; component < 3; ++component) { + lua_pushnumber(L, normal[component]); + lua_rawseti(L, -2, component + 1); + } + lua_setfield(L, -2, "normal"); + lua_newtable(L); for (int component = 0; component < 3; ++component) { lua_pushnumber(L, payload.colors[vertexIndex][component]); diff --git a/src/services/impl/render_coordinator_service.cpp b/src/services/impl/render_coordinator_service.cpp index a70babc..3642831 100644 --- a/src/services/impl/render_coordinator_service.cpp +++ b/src/services/impl/render_coordinator_service.cpp @@ -101,9 +101,9 @@ void RenderCoordinatorService::RenderFrame(float time) { auto extent = graphicsService_->GetSwapchainExtent(); float aspect = extent.second == 0 ? 1.0f : static_cast(extent.first) / static_cast(extent.second); - auto viewProj = sceneScriptService_->GetViewProjectionMatrix(aspect); + auto viewState = sceneScriptService_->GetViewState(aspect); - graphicsService_->RenderScene(renderCommands, viewProj); + graphicsService_->RenderScene(renderCommands, viewState); } if (!graphicsService_->EndFrame()) { diff --git a/src/services/impl/scene_script_service.cpp b/src/services/impl/scene_script_service.cpp index 3a9e94f..5314e77 100644 --- a/src/services/impl/scene_script_service.cpp +++ b/src/services/impl/scene_script_service.cpp @@ -6,6 +6,9 @@ #include #include +#include +#include +#include #include #include #include @@ -14,6 +17,46 @@ namespace sdl3cpp::services::impl { namespace { +std::array MultiplyMatrices(const std::array& left, + const std::array& right) { + glm::mat4 leftMat = glm::make_mat4(left.data()); + glm::mat4 rightMat = glm::make_mat4(right.data()); + glm::mat4 combined = leftMat * rightMat; + std::array result{}; + std::memcpy(result.data(), glm::value_ptr(combined), sizeof(float) * result.size()); + return result; +} + +bool ReadMatrixField(lua_State* L, int tableIndex, const char* field, std::array& target) { + lua_getfield(L, tableIndex, field); + if (lua_isnil(L, -1)) { + lua_pop(L, 1); + return false; + } + if (!lua_istable(L, -1)) { + lua_pop(L, 1); + throw std::runtime_error(std::string("Field '") + field + "' must be a 4x4 matrix"); + } + target = lua::ReadMatrix(L, -1); + lua_pop(L, 1); + return true; +} + +bool ReadVector3Field(lua_State* L, int tableIndex, const char* field, std::array& target) { + lua_getfield(L, tableIndex, field); + if (lua_isnil(L, -1)) { + lua_pop(L, 1); + return false; + } + if (!lua_istable(L, -1)) { + lua_pop(L, 1); + throw std::runtime_error(std::string("Field '") + field + "' must be a vec3"); + } + target = lua::ReadVector3(L, -1); + lua_pop(L, 1); + return true; +} + std::vector ReadVertexArray(lua_State* L, int index, const std::shared_ptr& logger) { int absIndex = lua_absindex(L, index); if (!lua_istable(L, absIndex)) { @@ -44,6 +87,14 @@ std::vector ReadVertexArray(lua_State* L, int index, const std::sh vertex.position = lua::ReadVector3(L, -1); lua_pop(L, 1); + lua_getfield(L, vertexIndex, "normal"); + if (lua_istable(L, -1)) { + vertex.normal = lua::ReadVector3(L, -1); + } else { + vertex.normal = {0.0f, 0.0f, 1.0f}; + } + lua_pop(L, 1); + lua_getfield(L, vertexIndex, "color"); vertex.color = lua::ReadVector3(L, -1); lua_pop(L, 1); @@ -232,12 +283,66 @@ std::array SceneScriptService::ComputeModelMatrix(int functionRef, fl return matrix; } -std::array SceneScriptService::GetViewProjectionMatrix(float aspect) { +ViewState SceneScriptService::GetViewState(float aspect) { if (logger_) { - logger_->Trace("SceneScriptService", "GetViewProjectionMatrix", "aspect=" + std::to_string(aspect)); + logger_->Trace("SceneScriptService", "GetViewState", "aspect=" + std::to_string(aspect)); } lua_State* L = GetLuaState(); + ViewState state; + state.view = lua::IdentityMatrix(); + state.proj = lua::IdentityMatrix(); + state.viewProj = lua::IdentityMatrix(); + state.cameraPosition = {0.0f, 0.0f, 0.0f}; + + lua_getglobal(L, "get_view_state"); + if (lua_isfunction(L, -1)) { + lua_pushnumber(L, aspect); + if (lua_pcall(L, 1, 1, 0) != LUA_OK) { + std::string message = lua::GetLuaError(L); + lua_pop(L, 1); + if (logger_) { + logger_->Error("Lua get_view_state failed: " + message); + } + throw std::runtime_error("Lua get_view_state failed: " + message); + } + if (!lua_istable(L, -1)) { + lua_pop(L, 1); + if (logger_) { + logger_->Error("'get_view_state' did not return a table"); + } + throw std::runtime_error("'get_view_state' did not return a table"); + } + bool hasView = false; + bool hasProj = false; + bool hasViewProj = false; + + try { + hasView = ReadMatrixField(L, -1, "view", state.view); + hasProj = ReadMatrixField(L, -1, "proj", state.proj); + hasViewProj = ReadMatrixField(L, -1, "view_proj", state.viewProj); + if (!hasViewProj) { + hasViewProj = ReadMatrixField(L, -1, "viewProj", state.viewProj); + } + ReadVector3Field(L, -1, "camera_pos", state.cameraPosition); + ReadVector3Field(L, -1, "camera_position", state.cameraPosition); + } catch (const std::exception& ex) { + lua_pop(L, 1); + if (logger_) { + logger_->Error("Lua get_view_state returned invalid data: " + std::string(ex.what())); + } + throw; + } + + lua_pop(L, 1); + + if (!hasViewProj && hasView && hasProj) { + state.viewProj = MultiplyMatrices(state.proj, state.view); + } + return state; + } + + lua_pop(L, 1); lua_getglobal(L, "get_view_projection"); if (!lua_isfunction(L, -1)) { lua_pop(L, 1); @@ -262,9 +367,9 @@ std::array SceneScriptService::GetViewProjectionMatrix(float aspect) } throw std::runtime_error("'get_view_projection' did not return a table"); } - std::array matrix = lua::ReadMatrix(L, -1); + state.viewProj = lua::ReadMatrix(L, -1); lua_pop(L, 1); - return matrix; + return state; } lua_State* SceneScriptService::GetLuaState() const { diff --git a/src/services/impl/scene_script_service.hpp b/src/services/impl/scene_script_service.hpp index 963350f..b964f44 100644 --- a/src/services/impl/scene_script_service.hpp +++ b/src/services/impl/scene_script_service.hpp @@ -19,7 +19,7 @@ public: std::vector LoadSceneObjects() override; std::array ComputeModelMatrix(int functionRef, float time) override; - std::array GetViewProjectionMatrix(float aspect) override; + ViewState GetViewState(float aspect) override; private: lua_State* GetLuaState() const; diff --git a/src/services/interfaces/graphics_types.hpp b/src/services/interfaces/graphics_types.hpp index 3461dc4..78f4107 100644 --- a/src/services/interfaces/graphics_types.hpp +++ b/src/services/interfaces/graphics_types.hpp @@ -34,6 +34,16 @@ struct ShaderPaths { bool disableDepthTest = false; }; +/** + * @brief View state used for per-frame uniforms. + */ +struct ViewState { + std::array view{}; + std::array proj{}; + std::array viewProj{}; + std::array cameraPosition{}; +}; + /** * @brief Render command for a single draw call. */ diff --git a/src/services/interfaces/i_graphics_backend.hpp b/src/services/interfaces/i_graphics_backend.hpp index 0b287ca..7f685b2 100644 --- a/src/services/interfaces/i_graphics_backend.hpp +++ b/src/services/interfaces/i_graphics_backend.hpp @@ -140,11 +140,11 @@ public: virtual bool EndFrame(GraphicsDeviceHandle device) = 0; /** - * @brief Set the view-projection matrix for the current frame. + * @brief Set the view state for the current frame. * - * @param viewProj View-projection matrix + * @param viewState Per-frame view state */ - virtual void SetViewProjection(const std::array& viewProj) = 0; + virtual void SetViewState(const ViewState& viewState) = 0; /** * @brief Draw with a pipeline. diff --git a/src/services/interfaces/i_graphics_service.hpp b/src/services/interfaces/i_graphics_service.hpp index 0a28e84..4a4ed19 100644 --- a/src/services/interfaces/i_graphics_service.hpp +++ b/src/services/interfaces/i_graphics_service.hpp @@ -91,7 +91,7 @@ public: * @param viewProj View-projection matrix */ virtual void RenderScene(const std::vector& commands, - const std::array& viewProj) = 0; + const ViewState& viewState) = 0; /** * @brief End the frame and present the rendered image. diff --git a/src/services/interfaces/i_scene_script_service.hpp b/src/services/interfaces/i_scene_script_service.hpp index 8b50957..264deee 100644 --- a/src/services/interfaces/i_scene_script_service.hpp +++ b/src/services/interfaces/i_scene_script_service.hpp @@ -1,7 +1,7 @@ #pragma once #include "scene_types.hpp" -#include +#include "graphics_types.hpp" #include namespace sdl3cpp::services { @@ -15,7 +15,7 @@ public: virtual std::vector LoadSceneObjects() = 0; virtual std::array ComputeModelMatrix(int functionRef, float time) = 0; - virtual std::array GetViewProjectionMatrix(float aspect) = 0; + virtual ViewState GetViewState(float aspect) = 0; }; } // namespace sdl3cpp::services diff --git a/src/services/interfaces/mesh_types.hpp b/src/services/interfaces/mesh_types.hpp index 7bdffa8..d025ee6 100644 --- a/src/services/interfaces/mesh_types.hpp +++ b/src/services/interfaces/mesh_types.hpp @@ -8,6 +8,7 @@ namespace sdl3cpp::services { struct MeshPayload { std::vector> positions; + std::vector> normals; std::vector> colors; std::vector indices; };