From eeb7c9c00f363c9be340dd036eb739cf34f2d9b7 Mon Sep 17 00:00:00 2001 From: sebastian Date: Thu, 30 Jul 2026 14:23:19 +0200 Subject: [PATCH] ADD: Asset Loading --- CMakeLists.txt | 35 ++++- engine/src/loader/AssetLoader.cpp | 89 +++++++++++ engine/src/loader/AssetLoader.h | 62 ++++++++ engine/src/loader/assets/AssetFormatters.h | 59 +++++++ engine/src/loader/assets/AssetRequests.cpp | 43 +++++ engine/src/loader/assets/AssetRequests.h | 39 +++++ engine/src/loader/assets/AssetTypes.h | 75 +++++++++ engine/src/loader/assets/LoadedAssets.h | 30 ++++ engine/src/loader/assets/RawAssetData.h | 81 ++++++++++ engine/src/loader/models/Material.cpp | 5 + engine/src/loader/models/Material.h | 41 +++++ engine/src/loader/models/MeshSection.cpp | 5 + engine/src/loader/models/MeshSection.h | 28 ++++ engine/src/loader/models/Model.cpp | 52 ++++++ engine/src/loader/models/Model.h | 37 +++++ engine/src/loader/models/ModelImporter.cpp | 104 ++++++++++++ engine/src/loader/models/ModelImporter.h | 24 +++ engine/src/loader/models/ModelUploader.cpp | 148 ++++++++++++++++++ engine/src/loader/models/ModelUploader.h | 33 ++++ engine/src/loader/textures/Texture2D.cpp | 46 ++++++ engine/src/loader/textures/Texture2D.h | 38 +++++ .../src/loader/textures/TextureImporter.cpp | 45 ++++++ engine/src/loader/textures/TextureImporter.h | 20 +++ .../src/loader/textures/TextureUploader.cpp | 23 +++ engine/src/loader/textures/TextureUploader.h | 19 +++ 25 files changed, 1178 insertions(+), 3 deletions(-) create mode 100644 engine/src/loader/AssetLoader.cpp create mode 100644 engine/src/loader/AssetLoader.h create mode 100644 engine/src/loader/assets/AssetFormatters.h create mode 100644 engine/src/loader/assets/AssetRequests.cpp create mode 100644 engine/src/loader/assets/AssetRequests.h create mode 100644 engine/src/loader/assets/AssetTypes.h create mode 100644 engine/src/loader/assets/LoadedAssets.h create mode 100644 engine/src/loader/assets/RawAssetData.h create mode 100644 engine/src/loader/models/Material.cpp create mode 100644 engine/src/loader/models/Material.h create mode 100644 engine/src/loader/models/MeshSection.cpp create mode 100644 engine/src/loader/models/MeshSection.h create mode 100644 engine/src/loader/models/Model.cpp create mode 100644 engine/src/loader/models/Model.h create mode 100644 engine/src/loader/models/ModelImporter.cpp create mode 100644 engine/src/loader/models/ModelImporter.h create mode 100644 engine/src/loader/models/ModelUploader.cpp create mode 100644 engine/src/loader/models/ModelUploader.h create mode 100644 engine/src/loader/textures/Texture2D.cpp create mode 100644 engine/src/loader/textures/Texture2D.h create mode 100644 engine/src/loader/textures/TextureImporter.cpp create mode 100644 engine/src/loader/textures/TextureImporter.h create mode 100644 engine/src/loader/textures/TextureUploader.cpp create mode 100644 engine/src/loader/textures/TextureUploader.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 19ce6b0..9e0b006 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,6 +60,13 @@ CPMAddPackage( GIT_TAG v1.91.5 DOWNLOAD_ONLY YES ) + +CPMAddPackage( + NAME spdlog + GITHUB_REPOSITORY gabime/spdlog + GIT_TAG v1.15.3 +) + add_library(imgui STATIC ${imgui_SOURCE_DIR}/imgui.cpp ${imgui_SOURCE_DIR}/imgui_draw.cpp @@ -103,9 +110,33 @@ add_library(Engine STATIC engine/src/utils/openglWrapper/shader/UniformValue.h engine/src/layer/Scene.cpp engine/src/layer/Scene.h + engine/src/loader/AssetLoader.cpp + engine/src/loader/AssetLoader.h + engine/src/loader/assets/AssetTypes.h + engine/src/loader/assets/AssetRequests.h + engine/src/loader/assets/AssetRequests.cpp + engine/src/loader/assets/RawAssetData.h + engine/src/loader/assets/AssetFormatters.h + engine/src/loader/textures/TextureUploader.cpp + engine/src/loader/textures/TextureUploader.h + engine/src/loader/textures/TextureImporter.cpp + engine/src/loader/textures/TextureImporter.h + engine/src/loader/textures/Texture2D.cpp + engine/src/loader/textures/Texture2D.h + engine/src/loader/models/ModelImporter.cpp + engine/src/loader/models/ModelImporter.h + engine/src/loader/models/ModelUploader.cpp + engine/src/loader/models/ModelUploader.h + engine/src/loader/models/Material.cpp + engine/src/loader/models/Material.h + engine/src/loader/assets/LoadedAssets.h + engine/src/loader/models/Model.cpp + engine/src/loader/models/Model.h + engine/src/loader/models/MeshSection.cpp + engine/src/loader/models/MeshSection.h ) target_include_directories(Engine PUBLIC engine/src) -target_link_libraries(Engine PUBLIC OpenGL::GL glfw glad glm::glm imgui) +target_link_libraries(Engine PUBLIC OpenGL::GL glfw glad glm::glm imgui spdlog::spdlog tinyobjloader stb_image) # --- Executable --- add_executable(ColorRace @@ -151,6 +182,4 @@ add_executable(ColorRace target_link_libraries(ColorRace PRIVATE Engine - tinyobjloader - stb_image ) \ No newline at end of file diff --git a/engine/src/loader/AssetLoader.cpp b/engine/src/loader/AssetLoader.cpp new file mode 100644 index 0000000..efbde9e --- /dev/null +++ b/engine/src/loader/AssetLoader.cpp @@ -0,0 +1,89 @@ +// +// Created by sebastian on 30.07.26. +// + +#include "AssetLoader.h" + +#include "spdlog/spdlog.h" +#include "textures/TextureImporter.h" + +void engine::AssetLoader::scheduleAsset(const AssetRequest &request) { + { + std::lock_guard lock(pendingMutex); + pendingQueue.push(request); + ++total; + } + pendingCondition.notify_one(); + std::visit([](const T& req) -> void { + using Type = std::decay_t; + if constexpr (std::is_same_v) { + spdlog::debug("Scheduled Texture Request: {} -> {}", req.name, req.path); + } else if constexpr (std::is_same_v) { + spdlog::debug("Scheduled Model Request: {} -> {}", req.name, req.path); + } + }, request); +} + +void engine::AssetLoader::start() { + if (!running) { + running = true; + loadingThread = std::thread(&AssetLoader::loadingThreadFunc, this); + } +} + +void engine::AssetLoader::stop() { + running = false; + pendingCondition.notify_all(); + if (loadingThread.joinable()) { + loadingThread.join(); + } +} + +engine::LoadingProgress engine::AssetLoader::getProgress() const { + return {total.load(), loaded.load()}; +} + +void engine::AssetLoader::loadingThreadFunc() { + while (running) { + AssetRequest request; + { + std::unique_lock lock(pendingMutex); + pendingCondition.wait(lock, [this] { + return !pendingQueue.empty() || !running; + }); + + if (!running && pendingQueue.empty()) { + return; + } + + request = pendingQueue.front(); + pendingQueue.pop(); + } + + IntermediateAsset result = std::visit([](T0& req) -> IntermediateAsset { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return processTextureRequest(req); + } else if constexpr (std::is_same_v) { + return processModelRequest(req); + } + }, request); + + { + std::lock_guard lock(readyMutex); + readyQueue.push(std::move(result)); + } + } +} + +engine::RawTextureData engine::AssetLoader::processTextureRequest(const TextureRequest &request) { + spdlog::debug("Processing Texture Request: {} -> {}", request.name, request.path); + RawTextureData textureData = TextureImporter::import(request.path, request.flipY); + textureData.name = request.name; + return textureData; +} + +engine::RawModelData engine::AssetLoader::processModelRequest(const ModelRequest &request) { + spdlog::debug("Processing Model Request: {} -> {}", request.name, request.path); + +} diff --git a/engine/src/loader/AssetLoader.h b/engine/src/loader/AssetLoader.h new file mode 100644 index 0000000..bb04377 --- /dev/null +++ b/engine/src/loader/AssetLoader.h @@ -0,0 +1,62 @@ +// +// Created by sebastian on 30.07.26. +// + +#ifndef COLORRACE_ASSETLOADER_H +#define COLORRACE_ASSETLOADER_H +#include +#include +#include +#include +#include +#include +#include +#include + +#include "assets/AssetRequests.h" +#include "assets/RawAssetData.h" + +namespace engine { + struct LoadingProgress { + int total; + int loaded; + float fraction() const { + return total > 0 ? static_cast(loaded) / total : 1.f; + } + bool isDone() const { + return loaded >= total; + } + }; + + class AssetLoader { + public: + void scheduleAsset(const AssetRequest& request); + void start(); + void stop(); + + void processUploadQueue(int maxPerFrame); + LoadingProgress getProgress() const; + + private: + void loadingThreadFunc(); + + static RawTextureData processTextureRequest(const TextureRequest& request); + static RawModelData processModelRequest(const ModelRequest& request); + + std::queue pendingQueue; + std::mutex pendingMutex; + std::condition_variable pendingCondition; + + std::queue readyQueue; + std::mutex readyMutex; + + std::atomic total{0}; + std::atomic loaded{0}; + + std::thread loadingThread; + std::atomic running{false}; + }; +} + + +#endif //COLORRACE_ASSETLOADER_H diff --git a/engine/src/loader/assets/AssetFormatters.h b/engine/src/loader/assets/AssetFormatters.h new file mode 100644 index 0000000..9cc6304 --- /dev/null +++ b/engine/src/loader/assets/AssetFormatters.h @@ -0,0 +1,59 @@ +// +// Created by sebastian on 30.07.26. +// + +#ifndef COLORRACE_ASSETFORMATTERS_H +#define COLORRACE_ASSETFORMATTERS_H +#pragma once + +#include "AssetRequests.h" +#include "AssetTypes.h" + +#include + + +template<> +struct std::formatter +{ + constexpr auto parse( + std::format_parse_context& ctx) + { + return ctx.begin(); + } + + + auto format( + const engine::AssetRequest& request, + std::format_context& ctx) const + { + return std::format_to( + ctx.out(), + "{}", + engine::assetRequestToString(request) + ); + } +}; + + +template<> +struct std::formatter +{ + constexpr auto parse( + std::format_parse_context& ctx) + { + return ctx.begin(); + } + + + auto format( + engine::TextureType type, + std::format_context& ctx) const + { + return std::format_to( + ctx.out(), + "{}", + engine::toString(type) + ); + } +}; +#endif //COLORRACE_ASSETFORMATTERS_H diff --git a/engine/src/loader/assets/AssetRequests.cpp b/engine/src/loader/assets/AssetRequests.cpp new file mode 100644 index 0000000..9a69581 --- /dev/null +++ b/engine/src/loader/assets/AssetRequests.cpp @@ -0,0 +1,43 @@ +// +// Created by sebastian on 30.07.26. +// +// AssetRequests.cpp + +#include "AssetRequests.h" + +#include +#include + + +namespace engine { + + std::string assetRequestToString( + const AssetRequest& request) + { + return std::visit( + [](auto const& req) + { + using Type = std::decay_t; + + + if constexpr(std::is_same_v) + { + return std::format( + "TextureRequest{{name='{}', path='{}'}}", + req.name, + req.path + ); + } + else + { + return std::format( + "ModelRequest{{name='{}', path='{}'}}", + req.name, + req.path + ); + } + }, + request); + } + +} \ No newline at end of file diff --git a/engine/src/loader/assets/AssetRequests.h b/engine/src/loader/assets/AssetRequests.h new file mode 100644 index 0000000..212706e --- /dev/null +++ b/engine/src/loader/assets/AssetRequests.h @@ -0,0 +1,39 @@ +// +// Created by sebastian on 30.07.26. +// + +#ifndef COLORRACE_ASSEZREQUESTS_H +#define COLORRACE_ASSEZREQUESTS_H +#pragma once + +#include +#include + +namespace engine { + + struct TextureRequest + { + std::string name; + std::string path; + bool flipY = true; + }; + + + struct ModelRequest + { + std::string name; + std::string path; + }; + + + using AssetRequest = + std::variant< + TextureRequest, + ModelRequest + >; + + + std::string assetRequestToString(const AssetRequest& request); + +} +#endif //COLORRACE_ASSEZREQUESTS_H diff --git a/engine/src/loader/assets/AssetTypes.h b/engine/src/loader/assets/AssetTypes.h new file mode 100644 index 0000000..78ad04f --- /dev/null +++ b/engine/src/loader/assets/AssetTypes.h @@ -0,0 +1,75 @@ +// +// Created by sebastian on 30.07.26. +// + +#ifndef COLORRACE_ASSETTYPES_H +#define COLORRACE_ASSETTYPES_H +#pragma once + +#include +#include + +namespace engine { + + enum class AssetType : uint8_t + { + Texture, + Model + }; + + + enum class TextureType : uint8_t + { + Diffuse, + Specular, + Normal, + Emissive, + Opacity, + + Count + }; + + + enum class ModelSplitPolicy : uint8_t + { + Combined, + ByMaterial, + ByObject + }; + + + constexpr std::string_view toString(TextureType type) + { + switch(type) + { + case TextureType::Diffuse: return "Diffuse"; + case TextureType::Specular: return "Specular"; + case TextureType::Normal: return "Normal"; + case TextureType::Emissive: return "Emissive"; + case TextureType::Opacity: return "Opacity"; + case TextureType::Count: break; + } + + return "Unknown"; + } + + + constexpr std::string_view toString(ModelSplitPolicy policy) + { + switch(policy) + { + case ModelSplitPolicy::Combined: + return "Combined"; + + case ModelSplitPolicy::ByMaterial: + return "ByMaterial"; + + case ModelSplitPolicy::ByObject: + return "ByObject"; + } + + return "Unknown"; + } + +} +#endif //COLORRACE_ASSETTYPES_H diff --git a/engine/src/loader/assets/LoadedAssets.h b/engine/src/loader/assets/LoadedAssets.h new file mode 100644 index 0000000..cd91f27 --- /dev/null +++ b/engine/src/loader/assets/LoadedAssets.h @@ -0,0 +1,30 @@ +// +// Created by sebastian on 30.07.26. +// + +#ifndef COLORRACE_LOADEDASSETS_H +#define COLORRACE_LOADEDASSETS_H +// engine/src/loader/assets/LoadedAssets.h +#pragma once +#include +#include +#include +#include "loader/textures/Texture2D.h" +#include "loader/models/Model.h" + +namespace engine { + + struct LoadedTexture { + std::string name; + std::shared_ptr texture; + }; + + struct LoadedModel { + std::string name; + std::shared_ptr model; + }; + + using LoadedAsset = std::variant; + +} +#endif //COLORRACE_LOADEDASSETS_H diff --git a/engine/src/loader/assets/RawAssetData.h b/engine/src/loader/assets/RawAssetData.h new file mode 100644 index 0000000..0178127 --- /dev/null +++ b/engine/src/loader/assets/RawAssetData.h @@ -0,0 +1,81 @@ +// +// Created by sebastian on 30.07.26. +// + +#ifndef COLORRACE_RAWASSETDATA_H +#define COLORRACE_RAWASSETDATA_H +#pragma once + +#include "AssetTypes.h" + +#include +#include +#include +#include + +namespace engine { + + + struct RawTextureData + { + std::string name; + + std::vector pixels; + + int width; + int height; + int channels; + }; + + + struct RawMaterialData + { + std::string name; + + std::unordered_map texturePaths; + + float shininess; + float opacity; + }; + + + struct RawMeshSection + { + std::vector vertices; + std::vector normals; + std::vector textureCoords; + + std::vector indices; + + std::string materialName; + std::string name; + }; + + + struct RawObjectData + { + std::string name; + + std::vector subModels; + }; + + + struct RawModelData + { + std::string name; + + std::vector objects; + + ModelSplitPolicy splitPolicy; + }; + + + using IntermediateAsset = + std::variant< + RawTextureData, + RawModelData + >; + + +} +#endif //COLORRACE_RAWASSETDATA_H diff --git a/engine/src/loader/models/Material.cpp b/engine/src/loader/models/Material.cpp new file mode 100644 index 0000000..c3b0896 --- /dev/null +++ b/engine/src/loader/models/Material.cpp @@ -0,0 +1,5 @@ +// +// Created by sebastian on 30.07.26. +// + +#include "Material.h" diff --git a/engine/src/loader/models/Material.h b/engine/src/loader/models/Material.h new file mode 100644 index 0000000..651953d --- /dev/null +++ b/engine/src/loader/models/Material.h @@ -0,0 +1,41 @@ +// +// Created by sebastian on 30.07.26. +// + +#ifndef COLORRACE_MATERIAL_H +#define COLORRACE_MATERIAL_H +#include +#include + +#include "loader/assets/AssetTypes.h" +#include "loader/textures/Texture2D.h" + + +namespace engine { + + class Material { + public: + Material() = default; + Material(std::array, static_cast(TextureType::Count)> textures, + float shininess, float opacity) + : m_textures(std::move(textures)), m_shininess(shininess), m_opacity(opacity) {} + + void bind() const { + for (size_t i = 0; i < m_textures.size(); ++i) { + if (m_textures[i]) m_textures[i]->bind(static_cast(i)); + } + } + + [[nodiscard]] float getShininess() const { return m_shininess; } + [[nodiscard]] float getOpacity() const { return m_opacity; } + + private: + std::array, static_cast(TextureType::Count)> m_textures; + float m_shininess = 1.0f; + float m_opacity = 1.0f; + }; + +} + + +#endif //COLORRACE_MATERIAL_H diff --git a/engine/src/loader/models/MeshSection.cpp b/engine/src/loader/models/MeshSection.cpp new file mode 100644 index 0000000..581640d --- /dev/null +++ b/engine/src/loader/models/MeshSection.cpp @@ -0,0 +1,5 @@ +// +// Created by sebastian on 30.07.26. +// + +#include "MeshSection.h" diff --git a/engine/src/loader/models/MeshSection.h b/engine/src/loader/models/MeshSection.h new file mode 100644 index 0000000..c3b0625 --- /dev/null +++ b/engine/src/loader/models/MeshSection.h @@ -0,0 +1,28 @@ +// +// Created by sebastian on 30.07.26. +// + +#ifndef COLORRACE_MESHSECTION_H +#define COLORRACE_MESHSECTION_H + + +// engine/src/renderer/model/MeshSection.h +#pragma once +#include +#include + +#include "Material.h" + +namespace engine { + + struct MeshSection { + std::string name; + unsigned int indexOffset; // in Indizes, nicht Bytes + unsigned int indexCount; + std::shared_ptr material; + }; + +} + + +#endif //COLORRACE_MESHSECTION_H diff --git a/engine/src/loader/models/Model.cpp b/engine/src/loader/models/Model.cpp new file mode 100644 index 0000000..a0de1c0 --- /dev/null +++ b/engine/src/loader/models/Model.cpp @@ -0,0 +1,52 @@ +// +// Created by sebastian on 30.07.26. +// + +#include "Model.h" + +#include "spdlog/spdlog.h" +using namespace engine; +Model::Model(std::unique_ptr vao, int vertexCount, bool isIndexed) : m_vao(std::move(vao)), m_vertexCount(vertexCount), m_isIndexed(isIndexed) {} + +Model::Model(std::unique_ptr vao, int vertexCount) : Model(std::move(vao), vertexCount, false) {} + +void Model::bind() const { + m_vao->bind(); +} + +void Model::unbind() const { + m_vao->unbind(); +} + +Model::Model(std::unique_ptr vao, int vertexCount, std::vector sections) + : m_vao(std::move(vao)), m_vertexCount(vertexCount), m_isIndexed(true), m_sections(std::move(sections)) {} + +void Model::draw() const { + bind(); + if (m_sections.empty()) { + // Legacy-Pfad: unverändert wie bisher, z.B. für CubeFactory-Primitive + if (m_isIndexed) glDrawElements(GL_TRIANGLES, m_vertexCount, GL_UNSIGNED_INT, nullptr); + else glDrawArrays(GL_TRIANGLES, 0, m_vertexCount); + } else { + for (const auto& section : m_sections) { + section.material->bind(); + glDrawElements(GL_TRIANGLES, static_cast(section.indexCount), GL_UNSIGNED_INT, + reinterpret_cast(section.indexOffset * sizeof(unsigned int))); + } + } + unbind(); +} + +void Model::drawSection(const std::string& name) const { + auto it = std::ranges::find_if(m_sections, + [&](const MeshSection& s) { return s.name == name; }); + if (it == m_sections.end()) { + spdlog::warn("MeshSection '{}' nicht gefunden", name); + return; + } + it->material->bind(); + bind(); + glDrawElements(GL_TRIANGLES, static_cast(it->indexCount), GL_UNSIGNED_INT, + reinterpret_cast(it->indexOffset * sizeof(unsigned int))); + unbind(); +} \ No newline at end of file diff --git a/engine/src/loader/models/Model.h b/engine/src/loader/models/Model.h new file mode 100644 index 0000000..9299324 --- /dev/null +++ b/engine/src/loader/models/Model.h @@ -0,0 +1,37 @@ +// +// Created by sebastian on 30.07.26. +// + +#ifndef COLORRACE_MODEL_H +#define COLORRACE_MODEL_H +#include + +#include "MeshSection.h" +#include "utils/openglWrapper/openglObjects/Vao.h" + + +namespace engine { + class Model { + public: + Model(std::unique_ptr vao, int vertexCount, bool isIndexed); + Model(std::unique_ptr vao, int vertexCount); + Model(std::unique_ptr vao, int vertexCount, std::vector sections); // neu + + void bind() const; + void unbind() const; + void draw() const; + void drawSection(const std::string& name) const; // neu + + [[nodiscard]] int getVertexCount() const { return m_vertexCount; } + [[nodiscard]] bool isIndexed() const { return m_isIndexed; } + [[nodiscard]] bool hasSections() const { return !m_sections.empty(); } + + private: + std::unique_ptr m_vao; + int m_vertexCount; + bool m_isIndexed; + std::vector m_sections; // leer bei den alten Konstruktoren + }; +} + +#endif //COLORRACE_MODEL_H diff --git a/engine/src/loader/models/ModelImporter.cpp b/engine/src/loader/models/ModelImporter.cpp new file mode 100644 index 0000000..472ae1c --- /dev/null +++ b/engine/src/loader/models/ModelImporter.cpp @@ -0,0 +1,104 @@ +// +// Created by sebastian on 30.07.26. +// + +#include "ModelImporter.h" +#define TINYOBJLOADER_IMPLEMENTATION +#include +#include + +#include "tiny_obj_loader.h" +#include "loader/textures/TextureImporter.h" + + +engine::RawModelBundle engine::ModelImporter::importFromFile(const std::string& objPath) { + tinyobj::ObjReaderConfig config; + config.mtl_search_path = std::filesystem::path(objPath).parent_path().string(); + config.triangulate = true; // GL braucht Dreiecke, keine n-Ecke + + tinyobj::ObjReader reader; + if (!reader.ParseFromFile(objPath, config)) { + throw std::runtime_error("OBJ-Import fehlgeschlagen '" + objPath + "': " + reader.Error()); + } + + const auto& attrib = reader.GetAttrib(); + const auto& shapes = reader.GetShapes(); + const auto& tinyMaterials = reader.GetMaterials(); + + RawModelBundle bundle; + bundle.model.name = std::filesystem::path(objPath).stem().string(); + + // 1. Materialien übersetzen + for (const auto& mat : tinyMaterials) { + RawMaterialData material; + material.name = mat.name; + if (!mat.diffuse_texname.empty()) material.texturePaths[TextureType::Diffuse] = mat.diffuse_texname; + if (!mat.specular_texname.empty()) material.texturePaths[TextureType::Specular] = mat.specular_texname; + if (!mat.bump_texname.empty()) material.texturePaths[TextureType::Normal] = mat.bump_texname; + if (!mat.emissive_texname.empty()) material.texturePaths[TextureType::Emissive] = mat.emissive_texname; + if (!mat.alpha_texname.empty()) material.texturePaths[TextureType::Opacity] = mat.alpha_texname; + material.shininess = mat.shininess; + material.opacity = mat.dissolve; // 'd' in der .mtl + bundle.materials.push_back(std::move(material)); + } + + // 2. Referenzierte Texturen dekodieren (dedupliziert innerhalb dieses Imports) + std::unordered_set seen; + auto mtlDir = std::filesystem::path(objPath).parent_path(); + for (const auto& material : bundle.materials) { + for (const auto& [type, relPath] : material.texturePaths) { + if (seen.insert(relPath).second) { + bundle.textures.push_back(TextureImporter::import((mtlDir / relPath).string())); + } + } + } + + // 3. Shapes -> Objekte, Faces nach Material gruppieren + for (const auto& shape : shapes) { + RawObjectData object; + object.name = shape.name.empty() ? "default" : shape.name; + + std::unordered_map sectionsByMaterial; + + size_t indexOffset = 0; + for (size_t f = 0; f < shape.mesh.num_face_vertices.size(); ++f) { + int faceVertexCount = shape.mesh.num_face_vertices[f]; + int materialId = shape.mesh.material_ids[f]; // -1 = kein Material zugewiesen + + auto& section = sectionsByMaterial[materialId]; + if (materialId >= 0) section.materialName = tinyMaterials[materialId].name; + + for (int v = 0; v < faceVertexCount; ++v) { + tinyobj::index_t idx = shape.mesh.indices[indexOffset + v]; + + section.vertices.insert(section.vertices.end(), { + attrib.vertices[3*idx.vertex_index + 0], + attrib.vertices[3*idx.vertex_index + 1], + attrib.vertices[3*idx.vertex_index + 2] + }); + if (idx.normal_index >= 0) { + section.normals.insert(section.normals.end(), { + attrib.normals[3*idx.normal_index + 0], + attrib.normals[3*idx.normal_index + 1], + attrib.normals[3*idx.normal_index + 2] + }); + } + if (idx.texcoord_index >= 0) { + section.textureCoords.insert(section.textureCoords.end(), { + attrib.texcoords[2*idx.texcoord_index + 0], + attrib.texcoords[2*idx.texcoord_index + 1] + }); + } + section.indices.push_back(static_cast(section.indices.size())); + } + indexOffset += faceVertexCount; + } + + for (auto& [materialId, section] : sectionsByMaterial) { + object.subModels.push_back(std::move(section)); + } + bundle.model.objects.push_back(std::move(object)); + } + + return bundle; +} diff --git a/engine/src/loader/models/ModelImporter.h b/engine/src/loader/models/ModelImporter.h new file mode 100644 index 0000000..1700f69 --- /dev/null +++ b/engine/src/loader/models/ModelImporter.h @@ -0,0 +1,24 @@ +// +// Created by sebastian on 30.07.26. +// + +#ifndef COLORRACE_MODELIMPORTER_H +#define COLORRACE_MODELIMPORTER_H +#include "loader/assets/RawAssetData.h" + + +namespace engine { + struct RawModelBundle { + RawModelData model; + std::vector materials; + std::vector textures; + }; + + class ModelImporter { + public: + static RawModelBundle importFromFile(const std::string& objPath); + }; +} + + +#endif //COLORRACE_MODELIMPORTER_H diff --git a/engine/src/loader/models/ModelUploader.cpp b/engine/src/loader/models/ModelUploader.cpp new file mode 100644 index 0000000..f382b3d --- /dev/null +++ b/engine/src/loader/models/ModelUploader.cpp @@ -0,0 +1,148 @@ +// +// Created by sebastian on 30.07.26. +// + +#include "ModelUploader.h" + +#include + +#include "Material.h" +#include "loader/textures/TextureUploader.h" +#include "spdlog/spdlog.h" + +std::vector engine::ModelUploader::upload(const engine::RawModelBundle &bundle, engine::ModelSplitPolicy policy) { + std::vector results; + + auto uploadedTextures = uploadTextures(bundle.textures, results); + auto materials = buildMaterials(bundle.materials, uploadedTextures); + + switch (policy) { + case ModelSplitPolicy::ByObject: + for (const auto& object : bundle.model.objects) { + auto model = std::make_shared(uploadSections(object.subModels, materials)); + results.push_back(LoadedModel{bundle.model.name + "." + object.name, model}); + } + break; + case ModelSplitPolicy::Combined: + results.push_back(LoadedModel{ + bundle.model.name, + std::make_shared(buildCombinedByObject(bundle.model.objects, materials)) + }); + break; + case ModelSplitPolicy::ByMaterial: + results.push_back(LoadedModel{ + bundle.model.name, + std::make_shared(buildCombinedByMaterial(bundle.model.objects, materials)) + }); + break; + } + return results; +} + +std::unordered_map> engine::ModelUploader::uploadTextures( + const std::vector &textures, std::vector &results) { + + std::unordered_map> uploadedTextures; + for (const auto& rawTexture : textures) { + auto texture = std::make_shared(TextureUploader::upload(rawTexture)); + uploadedTextures[rawTexture.name] = texture; + results.emplace_back(LoadedTexture{rawTexture.name, texture}); + } + return uploadedTextures; +} + +engine::ModelUploader::MaterialMap engine::ModelUploader::buildMaterials(const std::vector &materials, const std::unordered_map > &textures) { + MaterialMap result; + + for (const auto& raw : materials) { + std::array, static_cast(TextureType::Count)> resolved; + for (const auto& [type, texName] : raw.texturePaths) { + if (auto it = textures.find(texName); it != textures.end()) { + resolved[static_cast(type)] = it->second; + } else { + spdlog::warn("Textur '{}' für Material '{}' nicht gefunden", texName, raw.name); + } + } + result[raw.name] = std::make_shared(resolved, raw.shininess, raw.opacity); + } + + result[""] = std::make_shared(); // Fallback für Faces ohne usemtl + + return result; +} + +engine::Model engine::ModelUploader::uploadSections(const std::vector §ions, const MaterialMap &materials) { + std::vector vertices, normals, texCoords; + std::vector indices; + std::vector meshSections; + + unsigned int vertexOffset = 0; + for (const auto& section : sections) { + MeshSection meshSection; + meshSection.name = section.materialName.empty() ? section.name : section.materialName; + meshSection.indexOffset = static_cast(indices.size()); + meshSection.indexCount = static_cast(section.indices.size()); + + auto it = materials.find(section.materialName); + meshSection.material = (it != materials.end()) ? it->second : materials.at(""); + + vertices.insert(vertices.end(), section.vertices.begin(), section.vertices.end()); + normals.insert(normals.end(), section.normals.begin(), section.normals.end()); + texCoords.insert(texCoords.end(), section.textureCoords.begin(), section.textureCoords.end()); + for (int localIndex : section.indices) indices.push_back(vertexOffset + static_cast(localIndex)); + + vertexOffset += static_cast(section.vertices.size() / 3); + meshSections.push_back(std::move(meshSection)); + } + + auto vao = Vao::create(); + vao->bind(); + + + vao->initDataFeed(vertices.data(), vertices.size() * sizeof(float), GL_STATIC_DRAW, /* Attribute für Location 0, 3 Komponenten */ makeAttributeList(std::make_unique(0))); + vao->initDataFeed(normals.data(), normals.size() * sizeof(float), GL_STATIC_DRAW, /* Attribute für Location 1, 3 Komponenten */ makeAttributeList(std::make_unique(1))); + vao->initDataFeed(texCoords.data(), texCoords.size() * sizeof(float), GL_STATIC_DRAW, /* Attribute für Location 2, 2 Komponenten */ makeAttributeList(std::make_unique(2))); + vao->createIndexBuffer(indices.data(), indices.size()); + + vao->unbind(); + + return Model(std::move(vao), static_cast(indices.size()), std::move(meshSections)); +} + +engine::Model engine::ModelUploader::buildCombinedByObject(const std::vector &objects, const MaterialMap &materials) { + std::vector all; + for (const auto& object : objects) { + for (auto section : object.subModels) { + section.materialName = object.name; + all.push_back(std::move(section)); + } + } + return uploadSections(all, materials); +} + +engine::Model engine::ModelUploader::buildCombinedByMaterial(const std::vector &objects, const MaterialMap &materials) { + std::unordered_map merged; + + for (const auto& object : objects) { + for (auto section : object.subModels) { + auto& target = merged[section.materialName]; + + target.materialName = section.materialName; + unsigned int offset = static_cast(target.vertices.size() / 3); + target.vertices.insert(target.vertices.end(), section.vertices.begin(), section.vertices.end()); + target.normals.insert(target.normals.end(), section.normals.begin(), section.normals.end()); + target.textureCoords.insert(target.textureCoords.end(), section.textureCoords.begin(), section.textureCoords.end()); + for (int idx : section.indices) target.indices.push_back(static_cast(offset) + idx); + } + } + + std::vector all; + for (auto& [name, section] : merged) all.push_back(std::move(section)); + return uploadSections(all, materials); +} + +std::vector> engine::ModelUploader::makeAttributeList(std::unique_ptr attr) { + std::vector> result; + result.push_back(std::move(attr)); + return result; +}; diff --git a/engine/src/loader/models/ModelUploader.h b/engine/src/loader/models/ModelUploader.h new file mode 100644 index 0000000..a17b5a0 --- /dev/null +++ b/engine/src/loader/models/ModelUploader.h @@ -0,0 +1,33 @@ +// +// Created by sebastian on 30.07.26. +// + +#ifndef COLORRACE_MODELUPLOADER_H +#define COLORRACE_MODELUPLOADER_H +#include + +#include "ModelImporter.h" +#include "loader/assets/LoadedAssets.h" + + +namespace engine { + class Material; + + class ModelUploader { + public: + static std::vector upload(const engine::RawModelBundle& bundle, engine::ModelSplitPolicy policy); + private: + using MaterialMap = std::unordered_map>; + + static std::unordered_map> uploadTextures(const std::vector& textures, std::vector& results); + static MaterialMap buildMaterials(const std::vector& materials, const std::unordered_map>& textures); + static Model uploadSections(const std::vector& sections, const MaterialMap& materials); + static Model buildCombinedByObject(const std::vector &objects, const MaterialMap& materials); + static Model buildCombinedByMaterial(const std::vector &objects, const MaterialMap& materials); + + static std::vector> makeAttributeList(std::unique_ptr attr); + }; +} + + +#endif //COLORRACE_MODELUPLOADER_H diff --git a/engine/src/loader/textures/Texture2D.cpp b/engine/src/loader/textures/Texture2D.cpp new file mode 100644 index 0000000..5948121 --- /dev/null +++ b/engine/src/loader/textures/Texture2D.cpp @@ -0,0 +1,46 @@ +// +// Created by sebastian on 30.07.26. +// + +#include "Texture2D.h" + +#include "glad/glad.h" + +namespace engine { + Texture2D::~Texture2D() {release();} + + Texture2D::Texture2D(Texture2D&& other) noexcept + : m_id(other.m_id), m_width(other.m_width), m_height(other.m_height), m_channels(other.m_channels) { + other.m_id = 0; // Ownership übertragen: das Original darf beim Zerstören nichts mehr löschen + } + + + Texture2D& Texture2D::operator=(Texture2D&& other) noexcept { + if (this != &other) { + release(); + m_id = other.m_id; + m_width = other.m_width; + m_height = other.m_height; + m_channels = other.m_channels; + other.m_id = 0; + } + return *this; + } + + void Texture2D::release() { + if (m_id != 0) { + glDeleteTextures(1, &m_id); + m_id = 0; + } + } + + void Texture2D::bind(unsigned int unit) const { + glActiveTexture(GL_TEXTURE0 + unit); + glBindTexture(GL_TEXTURE_2D, m_id); + } + + void Texture2D::unbind(unsigned int unit) const { + glActiveTexture(GL_TEXTURE0 + unit); + glBindTexture(GL_TEXTURE_2D, 0); + } +} diff --git a/engine/src/loader/textures/Texture2D.h b/engine/src/loader/textures/Texture2D.h new file mode 100644 index 0000000..a26748d --- /dev/null +++ b/engine/src/loader/textures/Texture2D.h @@ -0,0 +1,38 @@ +// +// Created by sebastian on 30.07.26. +// + +#ifndef COLORRACE_TEXTURE2D_H +#define COLORRACE_TEXTURE2D_H +#include "../../../../cmake-build-debug/_deps/glfw-src/src/internal.h" + + +namespace engine { + class Texture2D { + public: + Texture2D() = default; + ~Texture2D(); + + Texture2D(const Texture2D&) = delete; + Texture2D& operator=(const Texture2D&) = delete; + + Texture2D(Texture2D&& other) noexcept; + Texture2D& operator=(Texture2D&& other) noexcept; + + void bind(unsigned int unit = 0) const; + void unbind(unsigned int unit = 0) const; + + private: + GLuint m_id = 0; + int m_width = 0; + int m_height = 0; + int m_channels = 0; + + void release(); + + friend class TextureUploader; + Texture2D(GLuint id, int width, int height, int channels) : m_id(id), m_width(width), m_height(height), m_channels(channels) {}; + }; +} + +#endif //COLORRACE_TEXTURE2D_H diff --git a/engine/src/loader/textures/TextureImporter.cpp b/engine/src/loader/textures/TextureImporter.cpp new file mode 100644 index 0000000..44511bb --- /dev/null +++ b/engine/src/loader/textures/TextureImporter.cpp @@ -0,0 +1,45 @@ +// +// Created by sebastian on 30.07.26. +// + +#include "TextureImporter.h" + +#include + +engine::RawTextureData engine::TextureImporter::import(const std::filesystem::path &path, bool flipY) { + stbi_set_flip_vertically_on_load(flipY); + + int width = 0; + int height = 0; + int channels = 0; + + stbi_uc* pixels = stbi_load(path.string().c_str(), &width, &height, &channels, 0); + + if (!pixels) { + throw std::runtime_error( + std::string("Failed to load texture: ") + + path.string() + + " (" + + stbi_failure_reason() + + ")"); + } + + RawTextureData data; + data.name = path.stem().string(); + data.width = width; + data.height = height; + data.channels = channels; + + const auto size = + static_cast(width) + * height + * channels; + + data.pixels.assign( + pixels, + pixels + size); + + stbi_image_free(pixels); + + return data; +} diff --git a/engine/src/loader/textures/TextureImporter.h b/engine/src/loader/textures/TextureImporter.h new file mode 100644 index 0000000..b679029 --- /dev/null +++ b/engine/src/loader/textures/TextureImporter.h @@ -0,0 +1,20 @@ +// +// Created by sebastian on 30.07.26. +// + +#ifndef COLORRACE_TEXTUREIMPORTER_H +#define COLORRACE_TEXTUREIMPORTER_H +#include + +#include "loader/assets/RawAssetData.h" + + +namespace engine { + class TextureImporter { + public: + static RawTextureData import(const std::filesystem::path& path, bool flipY = true); + }; + +} + +#endif //COLORRACE_TEXTUREIMPORTER_H diff --git a/engine/src/loader/textures/TextureUploader.cpp b/engine/src/loader/textures/TextureUploader.cpp new file mode 100644 index 0000000..47a883f --- /dev/null +++ b/engine/src/loader/textures/TextureUploader.cpp @@ -0,0 +1,23 @@ +// +// Created by sebastian on 30.07.26. +// + +#include "TextureUploader.h" + +#include "glad/glad.h" + +engine::Texture2D engine::TextureUploader::upload(const RawTextureData &data) { + GLuint id; + glGenTextures(1, &id); + glBindTexture(GL_TEXTURE_2D, id); + + GLenum format = data.channels == 4 ? GL_RGBA : data.channels == 3 ? GL_RGB : GL_RED; + glTexImage2D(GL_TEXTURE_2D, 0, format, data.width, data.height, 0, format, GL_UNSIGNED_BYTE, data.pixels.data()); + glGenerateMipmap(GL_TEXTURE_2D); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + return {id, data.width, data.height, data.channels}; // privater Ctor, TextureUploader ist friend +} diff --git a/engine/src/loader/textures/TextureUploader.h b/engine/src/loader/textures/TextureUploader.h new file mode 100644 index 0000000..98ad009 --- /dev/null +++ b/engine/src/loader/textures/TextureUploader.h @@ -0,0 +1,19 @@ +// +// Created by sebastian on 30.07.26. +// + +#ifndef COLORRACE_TEXTUREUPLOADER_H +#define COLORRACE_TEXTUREUPLOADER_H +#include "Texture2D.h" +#include "loader/assets/RawAssetData.h" + + +namespace engine { + class TextureUploader { + public: + static Texture2D upload(const RawTextureData& data); + }; +} + + +#endif //COLORRACE_TEXTUREUPLOADER_H