From d1372f1a9887b7e0b0dfb9250298c0084f9aebb3 Mon Sep 17 00:00:00 2001 From: Engin Manap Date: Fri, 27 Dec 2024 12:10:04 +0100 Subject: [PATCH] Add saving and loading material overrides of single models --- src/Assets/ModelAsset.cpp | 2 +- src/Assets/ModelAsset.h | 1 + src/GameObjects/Model.cpp | 51 +++++++++++++++++++++++++++++++++++++++ src/GameObjects/Model.h | 19 +++++++++++++-- src/WorldLoader.cpp | 25 +++++++++++++++++++ 5 files changed, 95 insertions(+), 3 deletions(-) diff --git a/src/Assets/ModelAsset.cpp b/src/Assets/ModelAsset.cpp index 4d08589e..5fdc2a0c 100755 --- a/src/Assets/ModelAsset.cpp +++ b/src/Assets/ModelAsset.cpp @@ -700,7 +700,7 @@ void ModelAsset::serializeCustomizations() { tinyxml2::XMLElement * animationsSectionsNode = customizationDocument.NewElement("Animation"); rootNode->InsertEndChild(animationsSectionsNode); - for(auto it=this->animationSections.begin(); it != this->animationSections.end(); it++) { + for(auto it=this->animationSections.begin(); it != this->animationSections.end(); ++it) { tinyxml2::XMLElement *sectionElement = customizationDocument.NewElement("Section"); animationsSectionsNode->InsertEndChild(sectionElement); diff --git a/src/Assets/ModelAsset.h b/src/Assets/ModelAsset.h index 808e8026..963f00e8 100644 --- a/src/Assets/ModelAsset.h +++ b/src/Assets/ModelAsset.h @@ -178,6 +178,7 @@ class ModelAsset : public Asset { } return meshMaterialMap.at(mesh); } + ~ModelAsset() override; std::vector> getMeshes() const { diff --git a/src/GameObjects/Model.cpp b/src/GameObjects/Model.cpp index c77faee2..02a93c37 100644 --- a/src/GameObjects/Model.cpp +++ b/src/GameObjects/Model.cpp @@ -300,7 +300,22 @@ bool Model::fillObjects(tinyxml2::XMLDocument &document, tinyxml2::XMLElement *o if(child->fillObjects(document, childNode)) { childrenNode->InsertEndChild(childNode); } + } + } + //Material customizations + const std::vector>> meshMaterialMap = getNewMeshMaterials(); + if (!meshMaterialMap.empty()) { + tinyxml2::XMLElement *childrenNode = document.NewElement("MeshMaterialList"); + tinyxml2::XMLElement *childrenCountNode = document.NewElement("Count"); + childrenCountNode->SetText(std::to_string(meshMaterialMap.size()).c_str()); + childrenNode->InsertEndChild(childrenCountNode); + objectElement->InsertEndChild(childrenNode); + for (auto& meshMaterial:meshMaterialMap) { + tinyxml2::XMLElement *childNode = document.NewElement("MeshMaterial"); + childNode->SetAttribute("MeshName", meshMaterial.first.c_str()); + meshMaterial.second->serialize(document, childNode); + childrenNode->InsertEndChild(childNode); } } @@ -586,6 +601,42 @@ ImGuiResult Model::putAIonGUI(ActorInterface *actorInterface, return result; } +std::vector>> Model::getNewMeshMaterials() const{ + std::vector>> meshMaterialList; + + for (const auto& thisMeshMaterial:meshMetaData) { + std::shared_ptr material = this->modelAsset->getMeshMaterial(thisMeshMaterial->mesh); + if (material == nullptr) { + std::cerr << "Model has a mesh that is not part of asset. This should not happen. " << std::endl; + continue; + } + if (material != thisMeshMaterial->material) {//difference in materials, we should save this. + meshMaterialList.emplace_back(thisMeshMaterial->mesh->getName(), thisMeshMaterial->material); + } + } + return meshMaterialList; +} + +void Model::loadOverriddenMeshMaterial(std::vector>> &customisedMeshMaterialList) { + for (const auto& thisMeshMaterial:customisedMeshMaterialList) { + //find the mesh + bool found = false; + for (auto& meshMetaData: this->meshMetaData) { + if (meshMetaData->mesh->getName() == thisMeshMaterial.first) { + std::shared_ptr newMaterial = thisMeshMaterial.second; + newMaterial->loadGPUSide(assetManager.get()); + newMaterial = assetManager->registerMaterial(newMaterial); + graphicsWrapper->setMaterial(*newMaterial); + meshMetaData->material = newMaterial; + found = true; + } + } + if (!found) { + std::cerr << "Model " << this->name << " doesn't have a mesh with name " << thisMeshMaterial.first << " This should never happen" << std::endl; + } + } +} + void Model::attachAI(ActorInterface *AIActor) { //after this, clearing the AI is job of the model. this->AIActor = AIActor; diff --git a/src/GameObjects/Model.h b/src/GameObjects/Model.h index 8dcb0baf..28ad3837 100644 --- a/src/GameObjects/Model.h +++ b/src/GameObjects/Model.h @@ -70,7 +70,22 @@ class Model : public PhysicalRenderable, public GameObject { static ImGuiResult putAIonGUI(ActorInterface *actorInterface, std::vector ¶meters, const ImGuiRequest &request, std::string &lastSelectedAIName); + /** + * This method returns a list of MeshAsset->Material pairs, that has been customized specifically for this model, that is not shared with the + * ModelAsset. Model asset has a default material, and world has overrides for that asset. This method returns overrides for not the asset level, + * but the model level. + * + * This method will check one by one all meshes and compare with the asset. That is because we don't want to make the Model object use more memory + * for something thats expected to apply only a very small portion of the models. + * Consider this a slow method. + * + * @return list of meshes that had different material than asset + */ + std::vector>> getNewMeshMaterials() const; + public: + void loadOverriddenMeshMaterial(std::vector>> & customisedMeshMaterialList); + Model(uint32_t objectID, std::shared_ptr assetManager, const std::string &modelFile) : Model(objectID, assetManager, 0, modelFile, false) {}; @@ -149,7 +164,7 @@ class Model : public PhysicalRenderable, public GameObject { return modelAsset->isTransparent(); } - ~Model(); + ~Model() override; bool fillObjects(tinyxml2::XMLDocument &document, tinyxml2::XMLElement *objectsNode) const override; @@ -180,7 +195,7 @@ class Model : public PhysicalRenderable, public GameObject { std::string getName() const override { return name + "_" + std::to_string(objectID); - }; + } ImGuiResult addImGuiEditorElements(const ImGuiRequest &request) override; /************Game Object methods **************/ diff --git a/src/WorldLoader.cpp b/src/WorldLoader.cpp index 3cb30f2f..75c6f052 100644 --- a/src/WorldLoader.cpp +++ b/src/WorldLoader.cpp @@ -589,6 +589,31 @@ WorldLoader::loadObject( std::shared_ptr assetManager, tinyxml2::X } } } + //Now Load material changes + std::vector>> custumizedMeshMaterialList; + tinyxml2::XMLElement* meshMaterialListNode = objectNode->FirstChildElement("MeshMaterialList"); + if (meshMaterialListNode != nullptr) { + tinyxml2::XMLElement *meshMaterialNode = meshMaterialListNode->FirstChildElement("MeshMaterial"); + while (meshMaterialNode != nullptr) { + if (meshMaterialNode->Attribute("MeshName") == nullptr) { + std::cerr << "A mesh material definition with no MeshName found. Can't be processed. Skipping." << std::endl; + } else { + std::string meshName = meshMaterialNode->Attribute("MeshName"); + tinyxml2::XMLElement *materialNode = meshMaterialNode->FirstChildElement("Material"); + if (materialNode == nullptr) { + std::cerr << "A mesh material definition with no Material found. Can't be processed. Skipping." << std::endl; + } else { + std::shared_ptr newMaterial = Material::deserialize(assetManager.get(), materialNode); + custumizedMeshMaterialList.emplace_back(meshName, newMaterial); + } + } + meshMaterialNode = meshMaterialNode->NextSiblingElement("MeshMaterial"); + } + } + if (!custumizedMeshMaterialList.empty()) { + loadedObjectInformation->model->loadOverriddenMeshMaterial(custumizedMeshMaterialList); + } + loadedObjects.push_back(std::move(loadedObjectInformation));