From 829c5fb42f4c984b848c36432f9dd7ae41c3a70a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Kapu=C5=9Bci=C5=84ski?= Date: Sat, 12 Mar 2022 23:31:32 +0100 Subject: [PATCH] Added support for glTF 2.0 model format --- src/CMakeLists.txt | 2 + src/common/resources/resourcemanager.cpp | 6 +- src/common/resources/resourcemanager.h | 5 +- src/graphics/core/material.h | 2 +- src/graphics/engine/oldmodelmanager.cpp | 52 +- src/graphics/model/model.cpp | 12 + src/graphics/model/model.h | 2 + src/graphics/model/model_gltf.cpp | 653 +++++++++++++++++++++++ src/graphics/model/model_gltf.h | 36 ++ src/graphics/model/model_input.cpp | 5 + src/graphics/model/model_mesh.cpp | 15 + src/graphics/model/model_mesh.h | 2 + 12 files changed, 778 insertions(+), 14 deletions(-) create mode 100644 src/graphics/model/model_gltf.cpp create mode 100644 src/graphics/model/model_gltf.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b3fd2f09..df3ab715 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -133,6 +133,8 @@ add_library(colobotbase STATIC graphics/model/model_input.h graphics/model/model_io_exception.h graphics/model/model_io_structs.h + graphics/model/model_gltf.cpp + graphics/model/model_gltf.h graphics/model/model_manager.cpp graphics/model/model_manager.h graphics/model/model_mesh.cpp diff --git a/src/common/resources/resourcemanager.cpp b/src/common/resources/resourcemanager.cpp index 9cfe5770..89497977 100644 --- a/src/common/resources/resourcemanager.cpp +++ b/src/common/resources/resourcemanager.cpp @@ -55,9 +55,9 @@ CResourceManager::~CResourceManager() } } -std::string CResourceManager::CleanPath(const std::string& path) +std::string CResourceManager::CleanPath(const std::filesystem::path& path) { - return boost::regex_replace(path, boost::regex("(.*)/\\.\\./"), ""); + return boost::regex_replace(path.generic_u8string(), boost::regex("(.*)/\\.\\./"), ""); } @@ -137,7 +137,7 @@ std::unique_ptr CResourceManager::GetSNDFileHandler(const std:: } -bool CResourceManager::Exists(const std::string &filename) +bool CResourceManager::Exists(const std::filesystem::path& filename) { if (PHYSFS_isInit()) { diff --git a/src/common/resources/resourcemanager.h b/src/common/resources/resourcemanager.h index 01b63432..c88c839c 100644 --- a/src/common/resources/resourcemanager.h +++ b/src/common/resources/resourcemanager.h @@ -23,6 +23,7 @@ #include "common/resources/sdl_memory_wrapper.h" #include "common/resources/sndfile_wrapper.h" +#include #include #include #include @@ -33,7 +34,7 @@ public: CResourceManager(const char *argv0); ~CResourceManager(); - static std::string CleanPath(const std::string &path); + static std::string CleanPath(const std::filesystem::path& path); //! Add a location to the search path static bool AddLocation(const std::string &location, bool prepend = true, const std::string &mountPoint = ""); @@ -52,7 +53,7 @@ public: static std::unique_ptr GetSNDFileHandler(const std::string &filename); //! Check if file exists - static bool Exists(const std::string &filename); + static bool Exists(const std::filesystem::path& filename); //! Check if file exists and is a directory static bool DirectoryExists(const std::string& directory); diff --git a/src/graphics/core/material.h b/src/graphics/core/material.h index bda24413..8b570bd3 100644 --- a/src/graphics/core/material.h +++ b/src/graphics/core/material.h @@ -67,7 +67,7 @@ struct Material //! Alpha mode AlphaMode alphaMode = AlphaMode::NONE; //! Alpha threshold - float alphaThreshold = 0.0; + float alphaThreshold = 0.5; // Cull face CullFace cullFace = CullFace::BACK; // Special tag diff --git a/src/graphics/engine/oldmodelmanager.cpp b/src/graphics/engine/oldmodelmanager.cpp index c0891d49..dfa0bb11 100644 --- a/src/graphics/engine/oldmodelmanager.cpp +++ b/src/graphics/engine/oldmodelmanager.cpp @@ -25,6 +25,7 @@ #include "common/stringutils.h" #include "common/resources/inputstream.h" +#include "common/resources/resourcemanager.h" #include "graphics/engine/engine.h" @@ -45,24 +46,59 @@ COldModelManager::~COldModelManager() { } -bool COldModelManager::LoadModel(const std::string& fileName, bool mirrored, int variant) +bool COldModelManager::LoadModel(const std::string& name, bool mirrored, int variant) { - GetLogger()->Debug("Loading model '%s'\n", fileName.c_str()); - CModel model; try { - std::filesystem::path path = "models/" + fileName; + auto extension = std::filesystem::path(name).extension().string(); - ModelInput::Read(model, path); + if (!extension.empty()) + { + GetLogger()->Debug("Loading model '%s'\n", name.c_str()); + + ModelInput::Read(model, "models/" + name); + + if (model.GetMeshCount() == 0) + return false; + + goto skip; + } + + auto gltf_path = "models/" + name + ".gltf"; + + if (CResourceManager::Exists(gltf_path)) + { + GetLogger()->Debug("Loading model '%s'\n", (name + ".gltf").c_str()); + + ModelInput::Read(model, gltf_path); + + if (model.GetMeshCount() > 0) + goto skip; + } + + auto mod_path = "models/" + name + ".mod"; + + if (CResourceManager::Exists(mod_path)) + { + GetLogger()->Debug("Loading model '%s'\n", (name + ".mod").c_str()); + + ModelInput::Read(model, mod_path); + + if (model.GetMeshCount() > 0) + goto skip; + } + + return false; } catch (const CModelIOException& e) { - GetLogger()->Error("Loading model '%s' failed: %s\n", fileName.c_str(), e.what()); + GetLogger()->Error("Loading model '%s' failed: %s\n", name.c_str(), e.what()); return false; } - CModelMesh* mesh = model.GetMesh("main"); +skip: + CModelMesh* mesh = model.GetMesh(); assert(mesh != nullptr); ModelInfo modelInfo; @@ -72,7 +108,7 @@ bool COldModelManager::LoadModel(const std::string& fileName, bool mirrored, int if (mirrored) Mirror(modelInfo.triangles); - FileInfo fileInfo(fileName, mirrored, variant); + FileInfo fileInfo(name, mirrored, variant); m_models[fileInfo] = modelInfo; m_engine->AddBaseObjTriangles(modelInfo.baseObjRank, modelInfo.triangles); diff --git a/src/graphics/model/model.cpp b/src/graphics/model/model.cpp index 89a4813f..8c205471 100644 --- a/src/graphics/model/model.cpp +++ b/src/graphics/model/model.cpp @@ -30,6 +30,18 @@ int CModel::GetMeshCount() const return m_meshes.size(); } +CModelMesh* CModel::GetMesh() +{ + if (m_meshes.size() == 1) + { + return &m_meshes.begin()->second; + } + else + { + return GetMesh("main"); + } +} + CModelMesh* CModel::GetMesh(const std::string& name) { auto it = m_meshes.find(name); diff --git a/src/graphics/model/model.h b/src/graphics/model/model.h index 990f16ed..e2d5d15f 100644 --- a/src/graphics/model/model.h +++ b/src/graphics/model/model.h @@ -42,6 +42,8 @@ class CModel public: //! Returns mesh count int GetMeshCount() const; + //! Returns the only mesh or mesh with name "main" + CModelMesh* GetMesh(); //! Return a mesh with given \a name CModelMesh* GetMesh(const std::string& name); //! Return a mesh with given \a name diff --git a/src/graphics/model/model_gltf.cpp b/src/graphics/model/model_gltf.cpp new file mode 100644 index 00000000..b60e6797 --- /dev/null +++ b/src/graphics/model/model_gltf.cpp @@ -0,0 +1,653 @@ +/* + * This file is part of the Colobot: Gold Edition source code + * Copyright (C) 2001-2022, Daniel Roux, EPSITEC SA & TerranovaTeam + * http://epsitec.ch; http://colobot.info; http://github.com/colobot + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://gnu.org/licenses + */ + +#include "graphics/model/model_mod.h" + +#include "common/ioutils.h" +#include "common/resources/inputstream.h" + +#include "graphics/model/model_io_exception.h" +#include "graphics/model/model_io_structs.h" + +#include +#include +#include + +using namespace IOUtils; + +using json = nlohmann::json; + +namespace Gfx::ModelIO +{ + +struct BufferView +{ + int buffer = 0; + size_t offset = 0; + size_t length = 0; +}; + +struct Accessor +{ + int bufferView = 0; + size_t byteOffset = 0; + int componentType = 0; + int count = 0; + std::string type = ""; +}; + +struct Texture +{ + int sampler = -1; + int source = 0; +}; + +struct Image +{ + std::string uri; +}; + +struct Sampler +{ + +}; + +class GLTFLoader +{ +public: + void Load(const std::filesystem::path& path); + + CModel GetModel() + { + return m_model; + } + +private: + void ReadBuffers(); + void ReadBufferViews(); + void ReadMaterials(); + void ReadAccessors(); + void ReadSamplers(); + void ReadImages(); + void ReadTextures(); + void ReadMeshes(); + + std::vector ReadPositions(int index); + std::vector ReadNormals(int index); + std::vector ReadUVs(int index); + std::vector ReadColors(int index); + std::vector ReadIndices(int index); + + CModel m_model; + + std::filesystem::path m_directory; + json m_root; + + std::vector> m_buffers; + std::vector m_bufferViews; + std::vector m_materials; + std::vector m_accessors; + std::vector m_textures; + std::vector m_images; + std::vector m_samplers; +}; + +void ReadGLTFModel(CModel& model, const std::filesystem::path& path) +{ + GLTFLoader loader; + + loader.Load(path); + + model = loader.GetModel(); +} + +void GLTFLoader::Load(const std::filesystem::path& path) +{ + m_directory = path.parent_path(); + + CInputStream stream(path); + + stream >> m_root; + + ReadBuffers(); + ReadBufferViews(); + ReadAccessors(); + ReadSamplers(); + ReadImages(); + ReadTextures(); + ReadMaterials(); + ReadMeshes(); +} + +void GLTFLoader::ReadBuffers() +{ + m_buffers.clear(); + + for (const auto& node : m_root["buffers"]) + { + size_t length = node["byteLength"].get(); + + std::vector buffer(length); + + if (node.contains("uri")) + { + auto uri = m_directory / node["uri"].get(); + + CInputStream stream(uri); + + stream.read(buffer.data(), buffer.size()); + } + else + { + std::cerr << "Base64 not yet supported"; + } + + m_buffers.emplace_back(std::move(buffer)); + } +} + +void GLTFLoader::ReadBufferViews() +{ + m_bufferViews.clear(); + + for (const auto& node : m_root["bufferViews"]) + { + BufferView bufferView{}; + + bufferView.buffer = node["buffer"].get(); + bufferView.offset = node["byteOffset"].get(); + bufferView.length = node["byteLength"].get(); + + m_bufferViews.push_back(bufferView); + } +} + +void GLTFLoader::ReadMaterials() +{ + m_materials.clear(); + + for (const auto& material : m_root["materials"]) + { + Material mat; + + if (material.contains("doubleSided")) + { + mat.cullFace = material["doubleSided"].get() ? CullFace::NONE : CullFace::BACK; + } + + if (material.contains("extras")) + { + const auto& extras = material["extras"]; + + if (extras.contains("dirt")) + { + int dirt = extras["dirt"].get(); + + std::string texName = std::string("dirty0") + char('0' + dirt) + ".png"; + + mat.detailTexture = texName; + } + + if (extras.contains("tag")) + { + mat.tag = extras["tag"].get(); + } + + if (extras.contains("energy")) + { + if (extras["energy"].get() != 0) + mat.tag = "energy"; + } + + if (extras.contains("tracker_1")) + { + if (extras["tracker_1"].get() != 0) + mat.tag = "tracker_right"; + } + + if (extras.contains("tracker_2")) + { + if (extras["tracker_2"].get() != 0) + mat.tag = "tracker_left"; + } + + if (extras.contains("transparency")) + { + if (extras["transparency"].get() != 0) + { + mat.alphaMode = AlphaMode::MASK; + mat.alphaThreshold = 0.5f; + } + } + } + + if (material.contains("pbrMetallicRoughness")) + { + const auto& pbr = material["pbrMetallicRoughness"]; + + if (pbr.contains("baseColorFactor")) + { + const auto& color = pbr["baseColorFactor"]; + + mat.albedoColor = { + color[0].get(), + color[1].get(), + color[2].get(), + color[3].get() + }; + } + + if (pbr.contains("baseColorTexture")) + { + const auto& tex = pbr["baseColorTexture"]; + + if (tex.contains("index")) + { + int index = tex["index"].get(); + + const auto& texture = m_textures[index]; + + const auto& image = m_images[texture.source]; + + mat.albedoTexture = image.uri; + } + } + + if (pbr.contains("metallicFactor")) + { + mat.metalness = pbr["metallicFactor"].get(); + } + + if (pbr.contains("roughnessFactor")) + { + mat.roughness = pbr["roughnessFactor"].get(); + } + + if (pbr.contains("emissiveFactor")) + { + const auto& color = pbr["emissiveFactor"]; + + mat.emissiveColor = { + color[0].get(), + color[1].get(), + color[2].get(), + 0.0 + }; + } + + if (pbr.contains("emissiveTextue")) + { + const auto& tex = pbr["emissiveTextue"]; + + if (tex.contains("index")) + { + int index = tex["index"].get(); + + const auto& texture = m_textures[index]; + + const auto& image = m_images[texture.source]; + + mat.emissiveTexture = image.uri; + } + } + } + + if (material.contains("alphaMode")) + { + auto mode = material["alphaMode"].get(); + + if (mode == "OPAQUE") + mat.alphaMode = AlphaMode::NONE; + else if (mode == "MASK") + mat.alphaMode = AlphaMode::MASK; + else if (mode == "BLEND") + mat.alphaMode = AlphaMode::BLEND; + } + + if (material.contains("alphaCutoff")) + { + mat.alphaThreshold = material["alphaCutoff"].get(); + } + + m_materials.push_back(mat); + } +} + +void GLTFLoader::ReadAccessors() +{ + m_accessors.clear(); + + for (const auto& node : m_root["accessors"]) + { + Accessor accessor{}; + + accessor.bufferView = node["bufferView"].get(); + + if (node.contains("byteOffset")) + accessor.byteOffset = node["byteOffset"].get(); + else + accessor.byteOffset = 0; + accessor.count = node["count"].get(); + accessor.componentType = node["componentType"].get(); + accessor.type = node["type"].get(); + + m_accessors.push_back(accessor); + } +} + +void GLTFLoader::ReadSamplers() +{ + m_samplers.clear(); + + for (const auto& node : m_root["samplers"]) + { + Sampler sampler{}; + + m_samplers.push_back(sampler); + } +} + +void GLTFLoader::ReadImages() +{ + m_images.clear(); + + for (const auto& node : m_root["images"]) + { + Image image{}; + + if (node.contains("uri")) + { + image.uri = node["uri"].get(); + } + + m_images.push_back(image); + } +} + +void GLTFLoader::ReadTextures() +{ + m_textures.clear(); + + for (const auto& node : m_root["textures"]) + { + Texture texture{}; + + if (node.contains("sampler")) + texture.sampler = node["sampler"].get(); + + texture.source = node["source"].get(); + + m_textures.push_back(texture); + } +} + +void GLTFLoader::ReadMeshes() +{ + m_model = {}; + + for (const auto& node : m_root["meshes"]) + { + auto name = node["name"].get(); + + CModelMesh mesh; + + for (const auto& primitive : node["primitives"]) + { + const auto& material = m_materials[primitive["material"].get()]; + + auto& part = mesh.AddPart(material); + + std::vector positions; + std::vector normals; + std::vector uvs; + std::vector uvs2; + std::vector colors; + + std::vector indices; + + if (primitive.contains("attributes")) + { + const auto& attributes = primitive["attributes"]; + + positions = ReadPositions(attributes["POSITION"].get()); + + if (positions.empty()) continue; + + if (attributes.contains("NORMAL")) + { + normals = ReadNormals(attributes["NORMAL"].get()); + } + else + { + normals.resize(positions.size()); + std::fill(normals.begin(), normals.end(), glm::vec3(0.0, 1.0, 0.0)); + } + + if (attributes.contains("TEXCOORD_0")) + { + uvs = ReadUVs(attributes["TEXCOORD_0"].get()); + } + else + { + uvs.resize(positions.size()); + std::fill(uvs.begin(), uvs.end(), glm::vec2(0.0, 0.0)); + } + + if (attributes.contains("TEXCOORD_1")) + { + uvs2 = ReadUVs(attributes["TEXCOORD_1"].get()); + } + else + { + uvs2.resize(positions.size()); + std::fill(uvs2.begin(), uvs2.end(), glm::vec2(0.0, 0.0)); + } + + if (attributes.contains("COLOR_0")) + { + colors = ReadColors(attributes["COLOR_0"].get()); + } + else + { + colors.resize(positions.size()); + std::fill(colors.begin(), colors.end(), glm::u8vec4(255, 255, 255, 255)); + } + + if (primitive.contains("indices")) + { + indices = ReadIndices(primitive["indices"].get()); + } + + // Combine vertex attributes into vertices + std::vector vertices(positions.size()); + + for (size_t i = 0; i < positions.size(); i++) + { + vertices[i].position = positions[i] * glm::vec3(1.0, 1.0, -1.0); + vertices[i].normal = normals[i] * glm::vec3(1.0, 1.0, -1.0); + vertices[i].uv = uvs[i]; + vertices[i].uv2 = uvs2[i]; + vertices[i].color = colors[i]; + } + + // No indices, reverse triangle vertices + if (indices.empty()) + { + for (size_t i = 0; i < vertices.size() - 2; i += 3) + std::swap(vertices[i], vertices[i + 1]); + } + // Indices present, reverse triangle indices order + else + { + for (size_t i = 0; i < indices.size() - 2; i += 3) + std::swap(indices[i], indices[i + 1]); + } + + for (const auto& vertex : vertices) + part.AddVertex(vertex); + + for (const auto& index : indices) + part.AddIndex(index); + } + } + + if (mesh.GetPartCount() > 0) + m_model.AddMesh(name, std::move(mesh)); + } +} + +std::vector GLTFLoader::ReadPositions(int index) +{ + const auto& accessor = m_accessors[index]; + + std::vector positions(accessor.count); + + const auto& bufferView = m_bufferViews[accessor.bufferView]; + + const auto& buffer = m_buffers[bufferView.buffer]; + + auto data = reinterpret_cast(buffer.data() + bufferView.offset); + + for (size_t i = 0; i < accessor.count; i++) + positions[i] = data[i]; + + return positions; +} + +std::vector GLTFLoader::ReadNormals(int index) +{ + const auto& accessor = m_accessors[index]; + + std::vector normals(accessor.count); + + const auto& bufferView = m_bufferViews[accessor.bufferView]; + + const auto& buffer = m_buffers[bufferView.buffer]; + + auto data = reinterpret_cast(buffer.data() + bufferView.offset); + + for (size_t i = 0; i < accessor.count; i++) + normals[i] = data[i]; + + return normals; +} + +std::vector GLTFLoader::ReadUVs(int index) +{ + const auto& accessor = m_accessors[index]; + + std::vector uvs(accessor.count); + + const auto& bufferView = m_bufferViews[accessor.bufferView]; + + const auto& buffer = m_buffers[bufferView.buffer]; + + if (accessor.componentType == 5126) + { + auto data = reinterpret_cast(buffer.data() + bufferView.offset); + + for (size_t i = 0; i < accessor.count; i++) + uvs[i] = data[i]; + } + else + { + std::cerr << "Invalid UV type: " << accessor.componentType << '\n'; + } + + return uvs; +} + +std::vector GLTFLoader::ReadColors(int index) +{ + const auto& accessor = m_accessors[index]; + + std::vector colors(accessor.count); + + const auto& bufferView = m_bufferViews[accessor.bufferView]; + + const auto& buffer = m_buffers[bufferView.buffer]; + + if (accessor.componentType == 5121) + { + auto data = reinterpret_cast(buffer.data() + bufferView.offset); + + for (size_t i = 0; i < accessor.count; i++) + colors[i] = data[i]; + } + else if (accessor.componentType == 5123) + { + auto data = reinterpret_cast(buffer.data() + bufferView.offset); + + for (size_t i = 0; i < accessor.count; i++) + colors[i] = glm::u8vec4(data[i] / glm::u16vec4(256)); + } + else if (accessor.componentType == 5126) + { + auto data = reinterpret_cast(buffer.data() + bufferView.offset); + + for (size_t i = 0; i < accessor.count; i++) + colors[i] = glm::u8vec4(data[i] * 255.0f); + } + else + { + std::cerr << "Invalid color type: " << accessor.componentType << '\n'; + } + + return colors; +} + +std::vector GLTFLoader::ReadIndices(int index) +{ + const auto& accessor = m_accessors[index]; + + std::vector indices(accessor.count); + + const auto& bufferView = m_bufferViews[accessor.bufferView]; + + const auto& buffer = m_buffers[bufferView.buffer]; + + // Unsigned byte components + if (accessor.componentType == 5121) + { + auto data = reinterpret_cast(buffer.data() + bufferView.offset); + + for (size_t i = 0; i < accessor.count; i++) + indices[i] = data[i]; + } + // Unsigned short components + else if (accessor.componentType == 5123) + { + auto data = reinterpret_cast(buffer.data() + bufferView.offset); + + for (size_t i = 0; i < accessor.count; i++) + indices[i] = data[i]; + } + // Unsigned int components + else if (accessor.componentType == 5125) + { + auto data = reinterpret_cast(buffer.data() + bufferView.offset); + + for (size_t i = 0; i < accessor.count; i++) + indices[i] = data[i]; + } + + return indices; +} + +} diff --git a/src/graphics/model/model_gltf.h b/src/graphics/model/model_gltf.h new file mode 100644 index 00000000..db130fa7 --- /dev/null +++ b/src/graphics/model/model_gltf.h @@ -0,0 +1,36 @@ +/* + * This file is part of the Colobot: Gold Edition source code + * Copyright (C) 2001-2022, Daniel Roux, EPSITEC SA & TerranovaTeam + * http://epsitec.ch; http://colobot.info; http://github.com/colobot + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://gnu.org/licenses + */ + +#pragma once + +#include "graphics/model/model.h" + +#include +#include + + /** + * \namespace ModelInput + * \brief Namespace with functions to read model files + */ +namespace Gfx::ModelIO +{ + +void ReadGLTFModel(CModel& model, const std::filesystem::path& path); + +} diff --git a/src/graphics/model/model_input.cpp b/src/graphics/model/model_input.cpp index dca3c8b0..872e56db 100644 --- a/src/graphics/model/model_input.cpp +++ b/src/graphics/model/model_input.cpp @@ -19,6 +19,7 @@ #include "graphics/model/model_input.h" +#include "graphics/model/model_gltf.h" #include "graphics/model/model_mod.h" #include "graphics/model/model_txt.h" @@ -39,6 +40,10 @@ void ModelInput::Read(CModel& model, const std::filesystem::path& path) { ModelIO::ReadTextModel(model, path); } + else if (extension == ".gltf") + { + ModelIO::ReadGLTFModel(model, path); + } else { throw CModelIOException(std::string("Unknown model format: ") + extension.string()); diff --git a/src/graphics/model/model_mesh.cpp b/src/graphics/model/model_mesh.cpp index cb80021c..33c84686 100644 --- a/src/graphics/model/model_mesh.cpp +++ b/src/graphics/model/model_mesh.cpp @@ -119,6 +119,21 @@ const CModelPart& CModelMesh::GetPart(size_t index) const return m_parts[index]; } +CModelPart& CModelMesh::AddPart(const Material& material) +{ + for (auto& part : m_parts) + { + if (part.GetMaterial() == material) + { + return part; + } + } + + m_parts.push_back(CModelPart(material)); + + return m_parts.back(); +} + const glm::vec3& CModelMesh::GetPosition() const { return m_position; diff --git a/src/graphics/model/model_mesh.h b/src/graphics/model/model_mesh.h index 3216d982..ab4b4200 100644 --- a/src/graphics/model/model_mesh.h +++ b/src/graphics/model/model_mesh.h @@ -80,6 +80,8 @@ public: size_t GetPartCount() const; //! Returns a part with given index const CModelPart& GetPart(size_t index) const; + //! Adds a new part with given material or returns an existing one + CModelPart& AddPart(const Material& material); //! Returns the mesh position const glm::vec3& GetPosition() const;