diff --git a/gameengine/CMakeLists.txt b/gameengine/CMakeLists.txt index 3ba2ec308..1fafa3b38 100644 --- a/gameengine/CMakeLists.txt +++ b/gameengine/CMakeLists.txt @@ -281,6 +281,7 @@ if(BUILD_SDL3_APP) src/services/impl/workflow/geometry/workflow_geometry_create_cube_step.cpp src/services/impl/workflow/geometry/workflow_geometry_create_plane_step.cpp src/services/impl/workflow/geometry/workflow_geometry_cube_generate_step.cpp + src/services/impl/workflow/rendering/workflow_draw_map_step.cpp src/services/impl/workflow/rendering/workflow_draw_textured_box_step.cpp src/services/impl/workflow/rendering/workflow_draw_textured_step.cpp src/services/impl/workflow/rendering/workflow_draw_viewmodel_step.cpp @@ -291,6 +292,7 @@ if(BUILD_SDL3_APP) src/services/impl/workflow/rendering/workflow_frame_end_scene_step.cpp src/services/impl/workflow/rendering/workflow_geometry_create_flashlight_step.cpp src/services/impl/workflow/rendering/workflow_lighting_setup_step.cpp + src/services/impl/workflow/rendering/workflow_map_load_step.cpp src/services/impl/workflow/rendering/workflow_model_load_step.cpp src/services/impl/workflow/rendering/workflow_postfx_bloom_blur_step.cpp src/services/impl/workflow/rendering/workflow_postfx_bloom_extract_step.cpp diff --git a/gameengine/packages/seed/map.glb b/gameengine/packages/seed/map.glb new file mode 100644 index 000000000..2b40ed3e8 Binary files /dev/null and b/gameengine/packages/seed/map.glb differ diff --git a/gameengine/packages/seed/workflows/frame_tick.json b/gameengine/packages/seed/workflows/frame_tick.json index 3707facaf..5ac228c36 100644 --- a/gameengine/packages/seed/workflows/frame_tick.json +++ b/gameengine/packages/seed/workflows/frame_tick.json @@ -108,6 +108,21 @@ "typeVersion": 1, "position": [1000, 0] }, + { + "id": "draw_map", + "name": "Draw Map (glTF)", + "type": "draw.map", + "typeVersion": 1, + "position": [1050, 0], + "parameters": { + "default_texture": "walls_texture", + "floor": "floor_texture", + "platform": "floor_texture", + "ceiling": "ceiling_texture", + "roughness": 0.8, + "metallic": 0.0 + } + }, { "id": "draw_floor", "name": "Draw Floor", @@ -502,7 +517,12 @@ }, "draw_bodies": { "main": { - "0": [{ "node": "draw_floor", "type": "main", "index": 0 }] + "0": [{ "node": "draw_map", "type": "main", "index": 0 }] + } + }, + "draw_map": { + "main": { + "0": [{ "node": "draw_torch", "type": "main", "index": 0 }] } }, "draw_floor": { diff --git a/gameengine/packages/seed/workflows/seed_game.json b/gameengine/packages/seed/workflows/seed_game.json index d65d2d6c6..f9ddbf324 100644 --- a/gameengine/packages/seed/workflows/seed_game.json +++ b/gameengine/packages/seed/workflows/seed_game.json @@ -330,6 +330,18 @@ "texture": "ceiling_texture" } }, + { + "id": "load_map", + "name": "Load Map (glTF)", + "type": "map.load", + "typeVersion": 1, + "position": [750, 100], + "parameters": { + "file_path": "packages/seed/map.glb", + "scale": 1.0, + "create_physics": 1 + } + }, { "id": "plane_floor", "name": "Create Floor Plane", @@ -1226,7 +1238,12 @@ }, "tex_ceiling": { "main": { - "0": [{ "node": "plane_floor", "type": "main", "index": 0 }] + "0": [{ "node": "load_map", "type": "main", "index": 0 }] + } + }, + "load_map": { + "main": { + "0": [{ "node": "player", "type": "main", "index": 0 }] } }, "plane_floor": { diff --git a/gameengine/python/export_room_gltf.py b/gameengine/python/export_room_gltf.py new file mode 100644 index 000000000..1b2969390 --- /dev/null +++ b/gameengine/python/export_room_gltf.py @@ -0,0 +1,238 @@ +#!/usr/bin/env python3 +"""Export the seed demo room as a glTF/GLB file. + +Reads the seed_game.json workflow and converts all physics bodies +(boxes) into a glTF scene with meshes. The output can be opened +in Blender, edited, and re-exported for the engine to load. + +Usage: + python export_room_gltf.py [--output packages/seed/map.glb] +""" + +import argparse +import json +import struct +import math +from pathlib import Path + + +def create_box_mesh(sx, sy, sz): + """Create a unit box mesh scaled by sx, sy, sz. Returns (vertices, indices). + Vertex format: position(3) + normal(3) + uv(2) = 8 floats per vertex. + """ + hx, hy, hz = sx / 2, sy / 2, sz / 2 + + # 6 faces, 4 vertices each = 24 vertices + # Each face has its own normal for flat shading + faces = [ + # front (+Z) + (( hx, hy, hz), (-hx, hy, hz), (-hx, -hy, hz), ( hx, -hy, hz), ( 0, 0, 1)), + # back (-Z) + ((-hx, hy, -hz), ( hx, hy, -hz), ( hx, -hy, -hz), (-hx, -hy, -hz), ( 0, 0, -1)), + # right (+X) + (( hx, hy, -hz), ( hx, hy, hz), ( hx, -hy, hz), ( hx, -hy, -hz), ( 1, 0, 0)), + # left (-X) + ((-hx, hy, hz), (-hx, hy, -hz), (-hx, -hy, -hz), (-hx, -hy, hz), (-1, 0, 0)), + # top (+Y) + (( hx, hy, -hz), (-hx, hy, -hz), (-hx, hy, hz), ( hx, hy, hz), ( 0, 1, 0)), + # bottom (-Y) + (( hx, -hy, hz), (-hx, -hy, hz), (-hx, -hy, -hz), ( hx, -hy, -hz), ( 0, -1, 0)), + ] + + vertices = [] + indices = [] + uvs_face = [(1, 0), (0, 0), (0, 1), (1, 1)] + + for face_idx, (v0, v1, v2, v3, n) in enumerate(faces): + base = len(vertices) + for vi, (vx, vy, vz) in enumerate([v0, v1, v2, v3]): + u, v = uvs_face[vi] + # Scale UVs by face dimensions for tiling + if abs(n[0]) > 0.5: # X-facing + u *= sz + v *= sy + elif abs(n[1]) > 0.5: # Y-facing + u *= sx + v *= sz + else: # Z-facing + u *= sx + v *= sy + vertices.append((vx, vy, vz, n[0], n[1], n[2], u, v)) + + indices.extend([base, base + 1, base + 2, base, base + 2, base + 3]) + + return vertices, indices + + +def build_gltf(bodies, output_path): + """Build a GLB file from a list of physics bodies.""" + import io + + nodes = [] + meshes = [] + accessors = [] + buffer_views = [] + bin_data = io.BytesIO() + + for body in bodies: + name = body["name"] + px = body.get("pos_x", 0) + py = body.get("pos_y", 0) + pz = body.get("pos_z", 0) + sx = body.get("size_x", 1) + sy = body.get("size_y", 1) + sz = body.get("size_z", 1) + shape = body.get("shape", "box") + + if shape == "capsule": + continue # Skip player + + verts, idxs = create_box_mesh(sx, sy, sz) + + # Write index data (uint16) + idx_offset = bin_data.tell() + for i in idxs: + bin_data.write(struct.pack(" +#include +#include +#include +#include +#include + +namespace sdl3cpp::services::impl { + +WorkflowDrawMapStep::WorkflowDrawMapStep(std::shared_ptr logger) + : logger_(std::move(logger)) {} + +std::string WorkflowDrawMapStep::GetPluginId() const { + return "draw.map"; +} + +void WorkflowDrawMapStep::Execute(const WorkflowStepDefinition& step, WorkflowContext& context) { + if (context.GetBool("frame_skip", false)) return; + + auto* pass = context.Get("gpu_render_pass", nullptr); + auto* cmd = context.Get("gpu_command_buffer", nullptr); + auto* pipeline = context.Get("gpu_pipeline_textured", nullptr); + if (!pass || !cmd || !pipeline) return; + + const auto* mapNodes = context.TryGet("map.nodes"); + if (!mapNodes || !mapNodes->is_array()) return; + + // Read texture mapping from parameters: "textures" object maps name patterns to texture keys + // e.g. { "floor": "floor_texture", "ceiling": "ceiling_texture", "*": "walls_texture" } + std::vector> textureMappings; + std::string defaultTexture; + + for (const auto& [key, param] : step.parameters) { + if (key == "roughness" || key == "metallic") continue; + if (param.type == WorkflowParameterValue::Type::String) { + if (key == "default_texture") { + defaultTexture = param.stringValue; + } else { + // Key is the name pattern, value is the texture context key + textureMappings.emplace_back(key, param.stringValue); + } + } + } + if (defaultTexture.empty() && !textureMappings.empty()) { + defaultTexture = textureMappings.back().second; + } + + WorkflowStepParameterResolver params; + auto getNum = [&](const char* name, float def) -> float { + const auto* p = params.FindParameter(step, name); + return (p && p->type == WorkflowParameterValue::Type::Number) ? static_cast(p->numberValue) : def; + }; + const float roughness = getNum("roughness", 0.8f); + const float metallic = getNum("metallic", 0.0f); + + auto view = context.Get("render.view_matrix", glm::mat4(1.0f)); + auto proj = context.Get("render.proj_matrix", glm::mat4(1.0f)); + auto camPos = context.Get("render.camera_pos", glm::vec3(0.0f)); + auto shadowVP = context.Get("render.shadow_vp", glm::mat4(1.0f)); + + auto fu = context.Get("render.frag_uniforms", rendering::FragmentUniformData{}); + fu.material[0] = roughness; + fu.material[1] = metallic; + + SDL_BindGPUGraphicsPipeline(pass, pipeline); + + glm::mat4 model = glm::mat4(1.0f); + glm::mat4 mvp = proj * view * model; + + rendering::VertexUniformData vu = {}; + std::memcpy(vu.mvp, glm::value_ptr(mvp), sizeof(float) * 16); + std::memcpy(vu.model_mat, glm::value_ptr(model), sizeof(float) * 16); + vu.normal[0] = 0; vu.normal[1] = 1; vu.normal[2] = 0; + vu.uv_scale[0] = 1.0f; vu.uv_scale[1] = 1.0f; + vu.camera_pos[0] = camPos.x; vu.camera_pos[1] = camPos.y; vu.camera_pos[2] = camPos.z; + std::memcpy(vu.shadow_vp, glm::value_ptr(shadowVP), sizeof(float) * 16); + + auto* shadow_tex = context.Get("shadow_depth_texture", nullptr); + auto* shadow_samp = context.Get("shadow_depth_sampler", nullptr); + + for (const auto& node : *mapNodes) { + std::string meshName = node["name"]; + auto* vb = context.Get("plane_" + meshName + "_vb", nullptr); + auto* ib = context.Get("plane_" + meshName + "_ib", nullptr); + if (!vb || !ib) continue; + + uint32_t indexCount = node["index_count"]; + + // Find matching texture from JSON mappings + std::string texKey = defaultTexture; + for (const auto& [pattern, texName] : textureMappings) { + if (meshName.find(pattern) != std::string::npos) { + texKey = texName; + break; + } + } + + auto* meshTex = context.Get(texKey + "_gpu", nullptr); + auto* meshSamp = context.Get(texKey + "_sampler", nullptr); + if (!meshTex || !meshSamp) continue; + + if (shadow_tex && shadow_samp) { + SDL_GPUTextureSamplerBinding bindings[2] = {}; + bindings[0].texture = meshTex; + bindings[0].sampler = meshSamp; + bindings[1].texture = shadow_tex; + bindings[1].sampler = shadow_samp; + SDL_BindGPUFragmentSamplers(pass, 0, bindings, 2); + } + + SDL_GPUBufferBinding vbBind = {}; vbBind.buffer = vb; + SDL_BindGPUVertexBuffers(pass, 0, &vbBind, 1); + SDL_GPUBufferBinding ibBind = {}; ibBind.buffer = ib; + SDL_BindGPUIndexBuffer(pass, &ibBind, SDL_GPU_INDEXELEMENTSIZE_16BIT); + + // Normal from bounding box thinnest axis + if (node.contains("bb_min") && node.contains("bb_max")) { + auto bbMin = node["bb_min"]; + auto bbMax = node["bb_max"]; + float dx = bbMax[0].get() - bbMin[0].get(); + float dy = bbMax[1].get() - bbMin[1].get(); + float dz = bbMax[2].get() - bbMin[2].get(); + if (dy < dx && dy < dz) { + vu.normal[0] = 0; vu.normal[1] = 1; vu.normal[2] = 0; + } else if (dx < dz) { + vu.normal[0] = 1; vu.normal[1] = 0; vu.normal[2] = 0; + } else { + vu.normal[0] = 0; vu.normal[1] = 0; vu.normal[2] = 1; + } + } + + SDL_PushGPUVertexUniformData(cmd, 0, &vu, sizeof(vu)); + SDL_PushGPUFragmentUniformData(cmd, 0, &fu, sizeof(fu)); + SDL_DrawGPUIndexedPrimitives(pass, indexCount, 1, 0, 0, 0); + } +} + +} // namespace sdl3cpp::services::impl diff --git a/gameengine/src/services/impl/workflow/rendering/workflow_map_load_step.cpp b/gameengine/src/services/impl/workflow/rendering/workflow_map_load_step.cpp new file mode 100644 index 000000000..0711fbf90 --- /dev/null +++ b/gameengine/src/services/impl/workflow/rendering/workflow_map_load_step.cpp @@ -0,0 +1,223 @@ +#include "services/interfaces/workflow/rendering/workflow_map_load_step.hpp" +#include "services/interfaces/workflow/workflow_step_parameter_resolver.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace sdl3cpp::services::impl { + +WorkflowMapLoadStep::WorkflowMapLoadStep(std::shared_ptr logger) + : logger_(std::move(logger)) {} + +std::string WorkflowMapLoadStep::GetPluginId() const { + return "map.load"; +} + +void WorkflowMapLoadStep::Execute(const WorkflowStepDefinition& step, WorkflowContext& context) { + WorkflowStepParameterResolver params; + + auto getStr = [&](const char* name, const std::string& def) -> std::string { + const auto* p = params.FindParameter(step, name); + if (p && p->type == WorkflowParameterValue::Type::String) return p->stringValue; + auto it = step.inputs.find(name); + if (it != step.inputs.end()) { + const auto* ctx = context.TryGet(it->second); + if (ctx) return *ctx; + } + return def; + }; + auto getNum = [&](const char* name, float def) -> float { + const auto* p = params.FindParameter(step, name); + return (p && p->type == WorkflowParameterValue::Type::Number) ? static_cast(p->numberValue) : def; + }; + + const std::string file_path = getStr("file_path", ""); + const float scale = getNum("scale", 1.0f); + const bool create_physics = static_cast(getNum("create_physics", 1)) != 0; + + if (file_path.empty()) { + throw std::runtime_error("map.load: 'file_path' parameter is required"); + } + + SDL_GPUDevice* device = context.Get("gpu_device", nullptr); + if (!device) throw std::runtime_error("map.load: GPU device not found"); + + // Load scene with Assimp + Assimp::Importer importer; + const aiScene* scene = importer.ReadFile(file_path, + aiProcess_Triangulate | aiProcess_GenNormals | aiProcess_FlipUVs | + aiProcess_JoinIdenticalVertices); + + if (!scene || !scene->mRootNode) { + throw std::runtime_error("map.load: Failed to load '" + file_path + "': " + + importer.GetErrorString()); + } + + // Vertex format: float3 pos + float2 uv = 20 bytes (matches textured pipeline) + struct PosUvVertex { float x, y, z, u, v; }; + + auto* world = context.Get("physics_world", nullptr); + + nlohmann::json mapNodes = nlohmann::json::array(); + int meshCount = 0; + + // Process each node in the scene + std::function processNode = + [&](const aiNode* node, aiMatrix4x4 parentTransform) { + + aiMatrix4x4 transform = parentTransform * node->mTransformation; + + for (unsigned int m = 0; m < node->mNumMeshes; ++m) { + const aiMesh* mesh = scene->mMeshes[node->mMeshes[m]]; + std::string meshName = node->mName.C_Str(); + if (meshName.empty()) meshName = "map_mesh_" + std::to_string(meshCount); + + // Extract vertices with transform applied + std::vector vertices; + aiVector3D bbMin(1e9f, 1e9f, 1e9f), bbMax(-1e9f, -1e9f, -1e9f); + + for (unsigned int i = 0; i < mesh->mNumVertices; ++i) { + aiVector3D pos = transform * mesh->mVertices[i]; + pos *= scale; + + PosUvVertex vert; + vert.x = pos.x; + vert.y = pos.y; + vert.z = pos.z; + if (mesh->mTextureCoords[0]) { + vert.u = mesh->mTextureCoords[0][i].x; + vert.v = mesh->mTextureCoords[0][i].y; + } else { + vert.u = 0.0f; + vert.v = 0.0f; + } + vertices.push_back(vert); + + bbMin.x = std::min(bbMin.x, pos.x); + bbMin.y = std::min(bbMin.y, pos.y); + bbMin.z = std::min(bbMin.z, pos.z); + bbMax.x = std::max(bbMax.x, pos.x); + bbMax.y = std::max(bbMax.y, pos.y); + bbMax.z = std::max(bbMax.z, pos.z); + } + + std::vector indices; + for (unsigned int f = 0; f < mesh->mNumFaces; ++f) { + const aiFace& face = mesh->mFaces[f]; + for (unsigned int j = 0; j < face.mNumIndices; ++j) { + indices.push_back(static_cast(face.mIndices[j])); + } + } + + if (vertices.empty()) continue; + + // Upload to GPU + uint32_t vtxSize = static_cast(vertices.size() * sizeof(PosUvVertex)); + uint32_t idxSize = static_cast(indices.size() * sizeof(uint16_t)); + + SDL_GPUBufferCreateInfo vbInfo = {}; + vbInfo.usage = SDL_GPU_BUFFERUSAGE_VERTEX; + vbInfo.size = vtxSize; + SDL_GPUBuffer* vb = SDL_CreateGPUBuffer(device, &vbInfo); + + SDL_GPUBufferCreateInfo ibInfo = {}; + ibInfo.usage = SDL_GPU_BUFFERUSAGE_INDEX; + ibInfo.size = idxSize; + SDL_GPUBuffer* ib = SDL_CreateGPUBuffer(device, &ibInfo); + + SDL_GPUTransferBufferCreateInfo tbInfo = {}; + tbInfo.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD; + tbInfo.size = vtxSize + idxSize; + SDL_GPUTransferBuffer* tb = SDL_CreateGPUTransferBuffer(device, &tbInfo); + + auto* mapped = static_cast(SDL_MapGPUTransferBuffer(device, tb, false)); + std::memcpy(mapped, vertices.data(), vtxSize); + std::memcpy(mapped + vtxSize, indices.data(), idxSize); + SDL_UnmapGPUTransferBuffer(device, tb); + + SDL_GPUCommandBuffer* cmd = SDL_AcquireGPUCommandBuffer(device); + SDL_GPUCopyPass* cp = SDL_BeginGPUCopyPass(cmd); + + SDL_GPUTransferBufferLocation srcV = {}; srcV.transfer_buffer = tb; + SDL_GPUBufferRegion dstV = {}; dstV.buffer = vb; dstV.size = vtxSize; + SDL_UploadToGPUBuffer(cp, &srcV, &dstV, false); + + SDL_GPUTransferBufferLocation srcI = {}; srcI.transfer_buffer = tb; srcI.offset = vtxSize; + SDL_GPUBufferRegion dstI = {}; dstI.buffer = ib; dstI.size = idxSize; + SDL_UploadToGPUBuffer(cp, &srcI, &dstI, false); + + SDL_EndGPUCopyPass(cp); + SDL_SubmitGPUCommandBuffer(cmd); + SDL_ReleaseGPUTransferBuffer(device, tb); + + // Store with plane_ prefix (compatible with draw.textured) + context.Set("plane_" + meshName + "_vb", vb); + context.Set("plane_" + meshName + "_ib", ib); + context.Set("plane_" + meshName, nlohmann::json{ + {"vertex_count", vertices.size()}, + {"index_count", indices.size()}, + {"stride", 20} + }); + + // Create physics body from bounding box + if (create_physics && world) { + float cx = (bbMin.x + bbMax.x) * 0.5f; + float cy = (bbMin.y + bbMax.y) * 0.5f; + float cz = (bbMin.z + bbMax.z) * 0.5f; + float hx = (bbMax.x - bbMin.x) * 0.5f; + float hy = (bbMax.y - bbMin.y) * 0.5f; + float hz = (bbMax.z - bbMin.z) * 0.5f; + + if (hx > 0.01f && hy > 0.01f && hz > 0.01f) { + auto* shape = new btBoxShape(btVector3(hx, hy, hz)); + btTransform startTransform; + startTransform.setIdentity(); + startTransform.setOrigin(btVector3(cx, cy, cz)); + + auto* motionState = new btDefaultMotionState(startTransform); + btRigidBody::btRigidBodyConstructionInfo rbInfo(0.0f, motionState, shape); + auto* body = new btRigidBody(rbInfo); + world->addRigidBody(body); + context.Set("physics_body_" + meshName, body); + } + } + + // Track for the frame loop draw step + nlohmann::json nodeInfo = { + {"name", meshName}, + {"index_count", indices.size()}, + {"bb_min", {bbMin.x, bbMin.y, bbMin.z}}, + {"bb_max", {bbMax.x, bbMax.y, bbMax.z}} + }; + mapNodes.push_back(nodeInfo); + meshCount++; + } + + // Process children + for (unsigned int c = 0; c < node->mNumChildren; ++c) { + processNode(node->mChildren[c], transform); + } + }; + + aiMatrix4x4 identity; + processNode(scene->mRootNode, identity); + + context.Set("map.nodes", mapNodes); + + if (logger_) { + logger_->Info("map.load: Loaded '" + file_path + "' (" + + std::to_string(meshCount) + " meshes, " + + std::to_string(scene->mNumMeshes) + " total in file)"); + } +} + +} // namespace sdl3cpp::services::impl diff --git a/gameengine/src/services/impl/workflow/workflow_registrar.cpp b/gameengine/src/services/impl/workflow/workflow_registrar.cpp index 864eddc3e..5066cc021 100644 --- a/gameengine/src/services/impl/workflow/workflow_registrar.cpp +++ b/gameengine/src/services/impl/workflow/workflow_registrar.cpp @@ -41,6 +41,8 @@ #include "services/interfaces/workflow/rendering/workflow_model_load_step.hpp" #include "services/interfaces/workflow/rendering/workflow_draw_viewmodel_step.hpp" #include "services/interfaces/workflow/rendering/workflow_geometry_create_flashlight_step.hpp" +#include "services/interfaces/workflow/rendering/workflow_map_load_step.hpp" +#include "services/interfaces/workflow/rendering/workflow_draw_map_step.hpp" #include "services/interfaces/workflow/rendering/workflow_draw_textured_box_step.hpp" #include "services/interfaces/workflow/rendering/workflow_shadow_setup_step.hpp" #include "services/interfaces/workflow/rendering/workflow_shadow_pass_step.hpp" @@ -276,6 +278,8 @@ void WorkflowRegistrar::RegisterSteps(std::shared_ptr reg registry->RegisterStep(std::make_shared(logger_)); registry->RegisterStep(std::make_shared(logger_)); registry->RegisterStep(std::make_shared(logger_)); + registry->RegisterStep(std::make_shared(logger_)); + registry->RegisterStep(std::make_shared(logger_)); registry->RegisterStep(std::make_shared(logger_)); registry->RegisterStep(std::make_shared(logger_)); registry->RegisterStep(std::make_shared(logger_)); diff --git a/gameengine/src/services/interfaces/workflow/rendering/workflow_draw_map_step.hpp b/gameengine/src/services/interfaces/workflow/rendering/workflow_draw_map_step.hpp new file mode 100644 index 000000000..09ebaf98a --- /dev/null +++ b/gameengine/src/services/interfaces/workflow/rendering/workflow_draw_map_step.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include "services/interfaces/i_workflow_step.hpp" +#include "services/interfaces/i_logger.hpp" + +#include + +namespace sdl3cpp::services::impl { + +class WorkflowDrawMapStep : public IWorkflowStep { +public: + explicit WorkflowDrawMapStep(std::shared_ptr logger); + std::string GetPluginId() const override; + void Execute(const WorkflowStepDefinition& step, WorkflowContext& context) override; +private: + std::shared_ptr logger_; +}; + +} // namespace sdl3cpp::services::impl diff --git a/gameengine/src/services/interfaces/workflow/rendering/workflow_map_load_step.hpp b/gameengine/src/services/interfaces/workflow/rendering/workflow_map_load_step.hpp new file mode 100644 index 000000000..eb2d9636a --- /dev/null +++ b/gameengine/src/services/interfaces/workflow/rendering/workflow_map_load_step.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include "services/interfaces/i_workflow_step.hpp" +#include "services/interfaces/i_logger.hpp" + +#include + +namespace sdl3cpp::services::impl { + +class WorkflowMapLoadStep : public IWorkflowStep { +public: + explicit WorkflowMapLoadStep(std::shared_ptr logger); + std::string GetPluginId() const override; + void Execute(const WorkflowStepDefinition& step, WorkflowContext& context) override; +private: + std::shared_ptr logger_; +}; + +} // namespace sdl3cpp::services::impl