From 5377d1a75a3291da16f15c044e240d789fcc81b5 Mon Sep 17 00:00:00 2001 From: Sergey Lapin Date: Sun, 12 Apr 2026 01:36:58 +0300 Subject: [PATCH] Material editing fixes --- .../components/ProceduralMaterial.hpp | 3 + .../components/ProceduralTexture.hpp | 6 + .../editScene/systems/CellGridSystem.cpp | 135 +++++++++++++++++- .../editScene/systems/CellGridSystem.hpp | 10 ++ .../systems/ProceduralMaterialSystem.cpp | 17 ++- .../systems/ProceduralTextureSystem.cpp | 1 + 6 files changed, 166 insertions(+), 6 deletions(-) diff --git a/src/features/editScene/components/ProceduralMaterial.hpp b/src/features/editScene/components/ProceduralMaterial.hpp index 075a5d1..490cae5 100644 --- a/src/features/editScene/components/ProceduralMaterial.hpp +++ b/src/features/editScene/components/ProceduralMaterial.hpp @@ -26,6 +26,9 @@ struct ProceduralMaterialComponent { // Pointer to the created Ogre material Ogre::MaterialPtr ogreMaterial; + // Track texture version for automatic rebuild when texture changes + unsigned int textureVersion = 0; + // Material properties float ambient[3] = {0.2f, 0.2f, 0.2f}; // Ambient color (RGB 0-1) float diffuse[3] = {1.0f, 1.0f, 1.0f}; // Diffuse color multiplier (RGB 0-1) diff --git a/src/features/editScene/components/ProceduralTexture.hpp b/src/features/editScene/components/ProceduralTexture.hpp index 095e36b..669f50c 100644 --- a/src/features/editScene/components/ProceduralTexture.hpp +++ b/src/features/editScene/components/ProceduralTexture.hpp @@ -49,6 +49,10 @@ struct ProceduralTextureComponent { // Whether the texture has been generated bool generated = false; + // Version counter - increments each time texture is regenerated + // Used by dependent systems to detect texture changes + unsigned int version = 0; + // Pointer to the generated Ogre texture Ogre::TexturePtr ogreTexture; @@ -107,6 +111,7 @@ struct ProceduralTextureComponent { getRectUVs(rectIndex, u1, v1, u2, v2); namedRects[name] = TextureRectInfo(name, u1, v1, u2, v2); + dirty = true; // Mark texture as dirty since rect atlas changed return true; } @@ -115,6 +120,7 @@ struct ProceduralTextureComponent { auto it = namedRects.find(name); if (it != namedRects.end()) { namedRects.erase(it); + dirty = true; // Mark texture as dirty since rect atlas changed return true; } return false; diff --git a/src/features/editScene/systems/CellGridSystem.cpp b/src/features/editScene/systems/CellGridSystem.cpp index 0de4c07..174e9f9 100644 --- a/src/features/editScene/systems/CellGridSystem.cpp +++ b/src/features/editScene/systems/CellGridSystem.cpp @@ -71,6 +71,69 @@ void CellGridSystem::update() }); } + // Check if texture has changed (new color rects, etc.) + // We check this even if needsRebuild is already true, to ensure we capture + // the latest texture version for tracking + flecs::entity currentTextureEntity = flecs::entity::null(); + unsigned int currentTextureVersion = 0; + + // First check stored texture entity + auto it = m_entityMeshes.find(entity.id()); + if (it != m_entityMeshes.end() && + it->second.textureEntity.is_alive() && + it->second.textureEntity.has()) { + currentTextureEntity = it->second.textureEntity; + currentTextureVersion = it->second.textureVersion; + } + + // If no stored texture, try to find from parent material + if (!currentTextureEntity.is_alive()) { + flecs::entity parent = entity.parent(); + while (parent.is_alive()) { + flecs::entity matEntity = flecs::entity::null(); + if (parent.has()) { + matEntity = parent.get().proceduralMaterialEntity; + } else if (parent.has()) { + matEntity = parent.get().proceduralMaterialEntity; + } else if (parent.has()) { + matEntity = parent.get().proceduralMaterialEntity; + } + + if (matEntity.is_alive() && matEntity.has()) { + const auto &mat = matEntity.get(); + if (mat.diffuseTextureEntity.is_alive() && + mat.diffuseTextureEntity.has()) { + currentTextureEntity = mat.diffuseTextureEntity; + const auto &tex = mat.diffuseTextureEntity.get(); + currentTextureVersion = tex.version; + break; + } + } + parent = parent.parent(); + } + } + + bool textureChanged = false; + if (currentTextureEntity.is_alive()) { + const auto &tex = currentTextureEntity.get(); + if (tex.version != currentTextureVersion) { + textureChanged = true; + needsRebuild = true; + } + } + + // If we need to rebuild, mark all roof children as dirty + // This is needed because buildRoofs() only builds dirty roofs, + // and the rebuild will destroy existing roof meshes + if (needsRebuild) { + entity.children([&](flecs::entity child) { + if (child.has()) { + child.get_mut().markDirty(); + } + return true; + }); + } + if (needsRebuild) { buildCellGrid(entity, grid); grid.dirty = false; @@ -99,7 +162,22 @@ void CellGridSystem::update() m_districtQuery.each([&](flecs::entity entity, DistrictComponent &district) { districtCount++; - if (district.dirty) { + bool needsRebuild = district.dirty; + + // Check if texture has changed + if (!needsRebuild) { + auto it = m_plazaMeshes.find(entity.id()); + if (it != m_plazaMeshes.end() && + it->second.textureEntity.is_alive() && + it->second.textureEntity.has()) { + const auto &tex = it->second.textureEntity.get(); + if (tex.version != it->second.textureVersion) { + needsRebuild = true; + } + } + } + + if (needsRebuild) { dirtyDistrictCount++; Ogre::LogManager::getSingleton().logMessage( "CellGrid: Processing dirty district " + @@ -120,7 +198,22 @@ void CellGridSystem::update() // Process dirty lots (for base geometry) m_lotQuery.each([&](flecs::entity entity, LotComponent &lot) { - if (lot.dirty) { + bool needsRebuild = lot.dirty; + + // Check if texture has changed + if (!needsRebuild) { + auto it = m_lotBaseMeshes.find(entity.id()); + if (it != m_lotBaseMeshes.end() && + it->second.textureEntity.is_alive() && + it->second.textureEntity.has()) { + const auto &tex = it->second.textureEntity.get(); + if (tex.version != it->second.textureVersion) { + needsRebuild = true; + } + } + } + + if (needsRebuild) { buildLotBase(entity, lot); lot.dirty = false; } @@ -403,6 +496,20 @@ void CellGridSystem::buildCellGrid(flecs::entity entity, Ogre::LogManager::getSingleton().logMessage( "CellGrid: Unknown error building frames"); } + + // Store texture dependency for automatic rebuild when texture changes + // Get texture entity from material entity + if (materialEntity.is_alive() && + materialEntity.has()) { + const auto &mat = materialEntity.get(); + if (mat.diffuseTextureEntity.is_alive() && + mat.diffuseTextureEntity.has()) { + auto &meshData = m_entityMeshes[entity.id()]; + meshData.textureEntity = mat.diffuseTextureEntity; + const auto &tex = mat.diffuseTextureEntity.get(); + meshData.textureVersion = tex.version; + } + } } // BitSet structure matching original town.cpp @@ -2440,6 +2547,17 @@ void CellGridSystem::buildDistrictPlaza(flecs::entity entity, PlazaData data; data.meshName = meshName; data.entity = plazzaEntity; + // Store texture dependency for automatic rebuild when texture changes + if (district.proceduralMaterialEntity.is_alive() && + district.proceduralMaterialEntity.has()) { + const auto &mat = district.proceduralMaterialEntity.get(); + if (mat.diffuseTextureEntity.is_alive() && + mat.diffuseTextureEntity.has()) { + data.textureEntity = mat.diffuseTextureEntity; + const auto &tex = mat.diffuseTextureEntity.get(); + data.textureVersion = tex.version; + } + } m_plazaMeshes[entity.id()] = data; Ogre::LogManager::getSingleton().logMessage( @@ -2667,10 +2785,11 @@ void CellGridSystem::buildLotBase(flecs::entity entity, LotComponent &lot) } } + // Find texture entity from the material that was selected + flecs::entity textureEntity = flecs::entity::null(); + // Apply UV mapping if (!textureRectToUse.empty()) { - // Find texture entity from the material that was selected - flecs::entity textureEntity = flecs::entity::null(); if (materialEntityToUse.is_alive() && materialEntityToUse.has()) { const auto &mat = @@ -2823,6 +2942,14 @@ void CellGridSystem::buildLotBase(flecs::entity entity, LotComponent &lot) LotBaseData data; data.meshName = meshName; data.entity = lotBaseEntity; + // Store texture dependency for automatic rebuild when texture changes + // textureEntity was determined earlier in the function + if (textureEntity.is_alive() && + textureEntity.has()) { + data.textureEntity = textureEntity; + const auto &tex = textureEntity.get(); + data.textureVersion = tex.version; + } m_lotBaseMeshes[entity.id()] = data; Ogre::LogManager::getSingleton().logMessage( diff --git a/src/features/editScene/systems/CellGridSystem.hpp b/src/features/editScene/systems/CellGridSystem.hpp index 11f95aa..ef9d73e 100644 --- a/src/features/editScene/systems/CellGridSystem.hpp +++ b/src/features/editScene/systems/CellGridSystem.hpp @@ -94,6 +94,10 @@ private: std::string roofSideMesh; std::vector entities; + // Track texture dependency for automatic rebuild when texture changes + flecs::entity textureEntity = flecs::entity::null(); + unsigned int textureVersion = 0; + // Frame meshes (unique per CellGrid for cellSize/cellHeight adaptation) std::string externalWindowFrameMesh; std::string internalWindowFrameMesh; @@ -109,6 +113,9 @@ private: struct PlazaData { std::string meshName; Ogre::Entity* entity = nullptr; + // Track texture dependency for automatic rebuild when texture changes + flecs::entity textureEntity = flecs::entity::null(); + unsigned int textureVersion = 0; }; std::unordered_map m_plazaMeshes; @@ -116,6 +123,9 @@ private: struct LotBaseData { std::string meshName; Ogre::Entity* entity = nullptr; + // Track texture dependency for automatic rebuild when texture changes + flecs::entity textureEntity = flecs::entity::null(); + unsigned int textureVersion = 0; }; std::unordered_map m_lotBaseMeshes; }; diff --git a/src/features/editScene/systems/ProceduralMaterialSystem.cpp b/src/features/editScene/systems/ProceduralMaterialSystem.cpp index d8a3563..337eaa4 100644 --- a/src/features/editScene/systems/ProceduralMaterialSystem.cpp +++ b/src/features/editScene/systems/ProceduralMaterialSystem.cpp @@ -29,8 +29,18 @@ void ProceduralMaterialSystem::update() if (!m_initialized) return; m_query.each([&](flecs::entity entity, ProceduralMaterialComponent& component) { - // Check if we need to recreate (dirty or not created yet) - if (component.dirty || !component.created) { + bool needsRecreate = component.dirty || !component.created; + + // Check if texture has changed (new version) + if (!needsRecreate && component.diffuseTextureEntity.is_alive() && + component.diffuseTextureEntity.has()) { + const auto& tex = component.diffuseTextureEntity.get(); + if (tex.version != component.textureVersion) { + needsRecreate = true; + } + } + + if (needsRecreate) { createMaterial(entity, component); } }); @@ -65,10 +75,12 @@ void ProceduralMaterialSystem::createMaterial(flecs::entity entity, ProceduralMa pass->setShininess(component.shininess); // Add diffuse texture if we have a reference to a ProceduralTexture entity + unsigned int currentTexVersion = 0; if (component.diffuseTextureEntity.is_alive() && component.diffuseTextureEntity.has()) { const auto& textureComp = component.diffuseTextureEntity.get(); + currentTexVersion = textureComp.version; if (textureComp.generated && !textureComp.textureName.empty()) { Ogre::TextureUnitState* texUnit = pass->createTextureUnitState(); texUnit->setTextureName(textureComp.textureName); @@ -78,6 +90,7 @@ void ProceduralMaterialSystem::createMaterial(flecs::entity entity, ProceduralMa component.created = true; component.dirty = false; + component.textureVersion = currentTexVersion; Ogre::LogManager::getSingleton().logMessage( "ProceduralMaterial: Created '" + component.materialName + "'"); diff --git a/src/features/editScene/systems/ProceduralTextureSystem.cpp b/src/features/editScene/systems/ProceduralTextureSystem.cpp index 56d0ee2..bc429af 100644 --- a/src/features/editScene/systems/ProceduralTextureSystem.cpp +++ b/src/features/editScene/systems/ProceduralTextureSystem.cpp @@ -105,6 +105,7 @@ void ProceduralTextureSystem::generateTexture(flecs::entity entity, ProceduralTe component.generated = true; component.dirty = false; + component.version++; Ogre::LogManager::getSingleton().logMessage( "ProceduralTexture: Generated '" + component.textureName +