Material editing fixes

This commit is contained in:
2026-04-12 01:36:58 +03:00
parent 03f72bdd77
commit 5377d1a75a
6 changed files with 166 additions and 6 deletions

View File

@@ -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)

View File

@@ -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;

View File

@@ -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<ProceduralTextureComponent>()) {
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<LotComponent>()) {
matEntity = parent.get<LotComponent>().proceduralMaterialEntity;
} else if (parent.has<DistrictComponent>()) {
matEntity = parent.get<DistrictComponent>().proceduralMaterialEntity;
} else if (parent.has<TownComponent>()) {
matEntity = parent.get<TownComponent>().proceduralMaterialEntity;
}
if (matEntity.is_alive() && matEntity.has<ProceduralMaterialComponent>()) {
const auto &mat = matEntity.get<ProceduralMaterialComponent>();
if (mat.diffuseTextureEntity.is_alive() &&
mat.diffuseTextureEntity.has<ProceduralTextureComponent>()) {
currentTextureEntity = mat.diffuseTextureEntity;
const auto &tex = mat.diffuseTextureEntity.get<ProceduralTextureComponent>();
currentTextureVersion = tex.version;
break;
}
}
parent = parent.parent();
}
}
bool textureChanged = false;
if (currentTextureEntity.is_alive()) {
const auto &tex = currentTextureEntity.get<ProceduralTextureComponent>();
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<RoofComponent>()) {
child.get_mut<RoofComponent>().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<ProceduralTextureComponent>()) {
const auto &tex = it->second.textureEntity.get<ProceduralTextureComponent>();
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<ProceduralTextureComponent>()) {
const auto &tex = it->second.textureEntity.get<ProceduralTextureComponent>();
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<ProceduralMaterialComponent>()) {
const auto &mat = materialEntity.get<ProceduralMaterialComponent>();
if (mat.diffuseTextureEntity.is_alive() &&
mat.diffuseTextureEntity.has<ProceduralTextureComponent>()) {
auto &meshData = m_entityMeshes[entity.id()];
meshData.textureEntity = mat.diffuseTextureEntity;
const auto &tex = mat.diffuseTextureEntity.get<ProceduralTextureComponent>();
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<ProceduralMaterialComponent>()) {
const auto &mat = district.proceduralMaterialEntity.get<ProceduralMaterialComponent>();
if (mat.diffuseTextureEntity.is_alive() &&
mat.diffuseTextureEntity.has<ProceduralTextureComponent>()) {
data.textureEntity = mat.diffuseTextureEntity;
const auto &tex = mat.diffuseTextureEntity.get<ProceduralTextureComponent>();
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<ProceduralMaterialComponent>()) {
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<ProceduralTextureComponent>()) {
data.textureEntity = textureEntity;
const auto &tex = textureEntity.get<ProceduralTextureComponent>();
data.textureVersion = tex.version;
}
m_lotBaseMeshes[entity.id()] = data;
Ogre::LogManager::getSingleton().logMessage(

View File

@@ -94,6 +94,10 @@ private:
std::string roofSideMesh;
std::vector<Ogre::Entity*> 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<uint64_t, PlazaData> 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<uint64_t, LotBaseData> m_lotBaseMeshes;
};

View File

@@ -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<ProceduralTextureComponent>()) {
const auto& tex = component.diffuseTextureEntity.get<ProceduralTextureComponent>();
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<ProceduralTextureComponent>()) {
const auto& textureComp = component.diffuseTextureEntity.get<ProceduralTextureComponent>();
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 + "'");

View File

@@ -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 +