From 64b03abb482a40022ef51dc2f5046b99f4835ef8 Mon Sep 17 00:00:00 2001 From: Sergey Lapin Date: Sun, 5 Apr 2026 19:51:22 +0300 Subject: [PATCH] Fixed material for Lot geometry and CellGrid is re-created now --- .../editScene/components/CellGrid.hpp | 14 + .../editScene/systems/CellGridSystem.cpp | 354 +++++++++++++++++- .../editScene/systems/CellGridSystem.hpp | 12 + .../editScene/systems/SceneSerializer.cpp | 34 ++ src/features/editScene/ui/DistrictEditor.cpp | 96 ++--- src/features/editScene/ui/LotEditor.cpp | 91 +++++ src/features/editScene/ui/TownEditor.cpp | 92 +++++ 7 files changed, 645 insertions(+), 48 deletions(-) diff --git a/src/features/editScene/components/CellGrid.hpp b/src/features/editScene/components/CellGrid.hpp index 2da6a7f..9090908 100644 --- a/src/features/editScene/components/CellGrid.hpp +++ b/src/features/editScene/components/CellGrid.hpp @@ -235,6 +235,13 @@ struct LotComponent { // Template name if this lot was created from a template std::string templateName; + // Procedural material for lot base (optional - falls back to District, then Town) + flecs::entity proceduralMaterialEntity; + std::string proceduralMaterialEntityId; // For serialization + + // Texture rectangle name from ProceduralTexture for UV mapping + std::string textureRectName; + // Dirty flag bool dirty = true; @@ -289,6 +296,13 @@ struct TownComponent { // Material name (created from color rects) std::string materialName; + // Procedural material for town (used by districts and lots) + flecs::entity proceduralMaterialEntity; + std::string proceduralMaterialEntityId; // For serialization + + // Texture rectangle name from ProceduralTexture for UV mapping + std::string textureRectName; + // Dirty flag bool dirty = true; bool materialDirty = true; diff --git a/src/features/editScene/systems/CellGridSystem.cpp b/src/features/editScene/systems/CellGridSystem.cpp index 61a1361..8dbe43f 100644 --- a/src/features/editScene/systems/CellGridSystem.cpp +++ b/src/features/editScene/systems/CellGridSystem.cpp @@ -29,6 +29,7 @@ CellGridSystem::CellGridSystem(flecs::world& world, Ogre::SceneManager* sceneMgr , m_cellGridQuery(world.query()) , m_townQuery(world.query()) , m_districtQuery(world.query()) + , m_lotQuery(world.query()) { } @@ -81,6 +82,14 @@ void CellGridSystem::update() std::to_string(districtCount) + " districts, " + std::to_string(dirtyDistrictCount) + " dirty"); } + + // Process dirty lots (for base geometry) + m_lotQuery.each([&](flecs::entity entity, LotComponent& lot) { + if (lot.dirty) { + buildLotBase(entity, lot); + lot.dirty = false; + } + }); } void CellGridSystem::buildCellGrid(flecs::entity entity, CellGridComponent& grid) @@ -89,7 +98,7 @@ void CellGridSystem::buildCellGrid(flecs::entity entity, CellGridComponent& grid " cells=" + std::to_string(grid.cells.size())); // Destroy existing meshes - destroyCellGridMeshes(grid); + destroyCellGridMeshes(entity); // Find parent town for material std::string materialName = "Ogre/StandardFloor"; // default @@ -474,7 +483,51 @@ Ogre::MeshPtr CellGridSystem::convertToMesh(const std::string& name, Procedural: void CellGridSystem::destroyCellGridMeshes(CellGridComponent& grid) { - // Cleanup handled by entity destruction, but we track mesh names + // This is called from buildCellGrid which uses the entity ID as key in m_entityMeshes + // The actual cleanup needs to happen there +} + +void CellGridSystem::destroyCellGridMeshes(flecs::entity entity) +{ + auto it = m_entityMeshes.find(entity.id()); + if (it != m_entityMeshes.end()) { + // Destroy all entities first + for (auto* ent : it->second.entities) { + if (ent) { + try { + m_sceneMgr->destroyEntity(ent); + } catch (...) {} + } + } + it->second.entities.clear(); + + // Destroy meshes + auto& meshMgr = Ogre::MeshManager::getSingleton(); + + auto removeMesh = [&](const std::string& name) { + if (!name.empty()) { + try { + meshMgr.remove(name); + } catch (...) {} + } + }; + + removeMesh(it->second.floorMesh); + removeMesh(it->second.ceilingMesh); + removeMesh(it->second.extWallMesh); + removeMesh(it->second.intWallMesh); + removeMesh(it->second.intWindowsMesh); + removeMesh(it->second.roofMesh); + + for (const auto& name : it->second.doorMeshes) { + removeMesh(name); + } + for (const auto& name : it->second.windowMeshes) { + removeMesh(name); + } + + m_entityMeshes.erase(it); + } } void CellGridSystem::updateTownMaterial(flecs::entity townEntity) @@ -680,3 +733,300 @@ void CellGridSystem::buildDistrictPlaza(flecs::entity entity, DistrictComponent& Ogre::LogManager::getSingleton().logMessage("CellGrid: Failed to create plaza mesh: " + e.getDescription()); } } + + +void CellGridSystem::buildLotBase(flecs::entity entity, LotComponent& lot) +{ + // Cleanup old mesh + auto it = m_lotBaseMeshes.find(entity.id()); + if (it != m_lotBaseMeshes.end()) { + if (it->second.entity) { + try { m_sceneMgr->destroyEntity(it->second.entity); } catch (...) {} + } + if (!it->second.meshName.empty()) { + try { Ogre::MeshManager::getSingleton().remove(it->second.meshName); } catch (...) {} + } + m_lotBaseMeshes.erase(it); + } + + // Validate transform + if (!entity.has()) { + Ogre::LogManager::getSingleton().logMessage("CellGrid: Lot has no TransformComponent"); + return; + } + auto& transform = entity.get_mut(); + if (!transform.node) { + Ogre::LogManager::getSingleton().logMessage("CellGrid: Lot has no SceneNode"); + return; + } + + // Find parent district and town for radius and material + flecs::entity districtEntity = flecs::entity::null(); + flecs::entity townEntity = flecs::entity::null(); + float districtRadius = 50.0f; + std::string materialName = "Ogre/StandardFloor"; + + // Material hierarchy: Lot -> District -> Town + // 1. Check Lot's own material first + if (lot.proceduralMaterialEntity.is_alive() && + lot.proceduralMaterialEntity.has()) { + const auto& mat = lot.proceduralMaterialEntity.get(); + if (mat.created && !mat.materialName.empty()) { + materialName = mat.materialName; + Ogre::LogManager::getSingleton().logMessage("CellGrid: Using Lot's own material: " + materialName); + } + } + + // 2. Walk up hierarchy for District and Town, and fallback materials + flecs::entity parent = entity.parent(); + while (parent.is_alive()) { + if (parent.has()) { + districtEntity = parent; + auto& district = parent.get(); + districtRadius = district.radius; + // Use District material if Lot doesn't have one + if (materialName == "Ogre/StandardFloor" && + district.proceduralMaterialEntity.is_alive() && + district.proceduralMaterialEntity.has()) { + const auto& mat = district.proceduralMaterialEntity.get(); + if (mat.created && !mat.materialName.empty()) { + materialName = mat.materialName; + Ogre::LogManager::getSingleton().logMessage("CellGrid: Using District material: " + materialName); + } + } + } + if (parent.has()) { + townEntity = parent; + // Check Town's ProceduralMaterial first, then generated material + if (materialName == "Ogre/StandardFloor") { + auto& town = parent.get(); + if (town.proceduralMaterialEntity.is_alive() && + town.proceduralMaterialEntity.has()) { + const auto& mat = town.proceduralMaterialEntity.get(); + if (mat.created && !mat.materialName.empty()) { + materialName = mat.materialName; + Ogre::LogManager::getSingleton().logMessage("CellGrid: Using Town ProceduralMaterial: " + materialName); + } + } + // Fallback to Town's generated material + if (materialName == "Ogre/StandardFloor" && !town.materialName.empty()) { + materialName = town.materialName; + Ogre::LogManager::getSingleton().logMessage("CellGrid: Using Town generated material: " + materialName); + } + } + break; + } + parent = parent.parent(); + } + + if (!districtEntity.is_alive()) { + Ogre::LogManager::getSingleton().logMessage("CellGrid: Lot has no parent District"); + return; + } + + Ogre::LogManager::getSingleton().logMessage("CellGrid: Material selection for lot " + std::to_string(entity.id()) + + " final materialName='" + materialName + "'"); + + // Position the Lot's SceneNode at the correct location around the district + Ogre::Quaternion lotRotation(Ogre::Degree(lot.angle), Ogre::Vector3::UNIT_Y); + Ogre::Vector3 lotOffset = lotRotation * (Ogre::Vector3::UNIT_Z * districtRadius); + lotOffset += Ogre::Vector3(lot.offsetX, 0, lot.offsetZ); + + // Set the Lot's position (CellGrid children will inherit this) + transform.node->setPosition(lotOffset); + transform.node->setOrientation(lotRotation); + + // Update the TransformComponent to match + transform.position = lotOffset; + transform.rotation = lotRotation; + + Ogre::LogManager::getSingleton().logMessage( + "CellGrid: Building lot base for entity " + std::to_string(entity.id()) + + " at position (" + std::to_string(lotOffset.x) + ", " + + std::to_string(lotOffset.y) + ", " + std::to_string(lotOffset.z) + + ") with material: " + materialName); + + // Build lot base geometry (box foundation) + Procedural::TriangleBuffer tb; + + const float cellSize = 4.0f; + const float baseHeight = 4.0f; + float width = cellSize * lot.width; + float depth = cellSize * lot.depth; + + // Create box for lot base + Procedural::BoxGenerator() + .setSizeX(width) + .setSizeY(baseHeight) + .setSizeZ(depth) + .setNumSegY(8) + .setNumSegX(8) + .setNumSegZ(8) + .setEnableNormals(true) + .setPosition(Ogre::Vector3(0.0f, -baseHeight / 2.0f + lot.elevation - 0.01f, 0.0f)) + .addToTriangleBuffer(tb); + + // Determine which texture rect to use (Lot -> District -> Town) + std::string textureRectToUse; + flecs::entity materialEntityToUse; + + // If Lot has its own material, use its texture rect + if (lot.proceduralMaterialEntity.is_alive()) { + textureRectToUse = lot.textureRectName; + materialEntityToUse = lot.proceduralMaterialEntity; + if (!textureRectToUse.empty()) { + Ogre::LogManager::getSingleton().logMessage("CellGrid: Using Lot's own texture rect: " + textureRectToUse); + } + } else { + // Lot uses District/Town material - look up hierarchy for texture rect + flecs::entity parent = entity.parent(); + while (parent.is_alive()) { + if (parent.has()) { + auto& district = parent.get(); + if (!district.textureRectName.empty()) { + textureRectToUse = district.textureRectName; + materialEntityToUse = district.proceduralMaterialEntity; + Ogre::LogManager::getSingleton().logMessage("CellGrid: Using District texture rect: " + textureRectToUse); + break; + } + } + if (parent.has()) { + auto& town = parent.get(); + if (!town.textureRectName.empty()) { + textureRectToUse = town.textureRectName; + materialEntityToUse = town.proceduralMaterialEntity; + Ogre::LogManager::getSingleton().logMessage("CellGrid: Using Town texture rect: " + textureRectToUse); + } + break; + } + parent = parent.parent(); + } + } + + // 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 = materialEntityToUse.get(); + if (mat.diffuseTextureEntity.is_alive()) { + textureEntity = mat.diffuseTextureEntity; + } + } + // Fallback to District/Town texture + if (!textureEntity.is_alive()) { + flecs::entity parent = entity.parent(); + while (parent.is_alive()) { + if (parent.has()) { + auto& district = parent.get(); + if (district.proceduralMaterialEntity.is_alive() && + district.proceduralMaterialEntity.has()) { + const auto& mat = district.proceduralMaterialEntity.get(); + if (mat.diffuseTextureEntity.is_alive()) { + textureEntity = mat.diffuseTextureEntity; + break; + } + } + } + if (parent.has()) { + auto& town = parent.get(); + if (town.proceduralMaterialEntity.is_alive() && + town.proceduralMaterialEntity.has()) { + const auto& mat = town.proceduralMaterialEntity.get(); + if (mat.diffuseTextureEntity.is_alive()) { + textureEntity = mat.diffuseTextureEntity; + break; + } + } + break; + } + parent = parent.parent(); + } + } + + if (textureEntity.is_alive() && textureEntity.has()) { + const auto& texture = textureEntity.get(); + const TextureRectInfo* rect = texture.getNamedRect(textureRectToUse); + if (rect) { + const float margin = 0.01f; + float uRange = (rect->u2 - rect->u1) * (1.0f - 2.0f * margin); + float vRange = (rect->v2 - rect->v1) * (1.0f - 2.0f * margin); + float uOffset = rect->u1 + (rect->u2 - rect->u1) * margin; + float vOffset = rect->v1 + (rect->v2 - rect->v1) * margin; + + for (auto& v : tb.getVertices()) { + v.mUV.x = uOffset + v.mUV.x * uRange; + v.mUV.y = vOffset + v.mUV.y * vRange; + } + Ogre::LogManager::getSingleton().logMessage("CellGrid: Applied UV mapping for rect '" + + textureRectToUse + "'"); + } else { + Ogre::LogManager::getSingleton().logMessage("CellGrid: WARNING - Rect not found: " + textureRectToUse); + } + } else { + Ogre::LogManager::getSingleton().logMessage("CellGrid: WARNING - No texture entity for rect: " + textureRectToUse); + } + } else { + // Default UV mapping for texture atlas (same as original town.cpp) + Ogre::LogManager::getSingleton().logMessage("CellGrid: Using default UV mapping (no texture rect set)"); + for (auto& v : tb.getVertices()) { + v.mUV *= 0.08f; + v.mUV.x += 0.41f; + v.mUV.x = Ogre::Math::Clamp(v.mUV.x, 0.4f, 0.5f); + v.mUV.y = Ogre::Math::Clamp(v.mUV.y, 0.0f, 0.1f); + } + } + + // Create mesh and entity + std::string meshName = "LotBase_" + std::to_string(entity.id()) + "_" + std::to_string(m_meshCount++); + try { + if (tb.getVertices().size() < 3) { + Ogre::LogManager::getSingleton().logMessage("CellGrid: ERROR - Not enough vertices for lot base mesh"); + return; + } + + Ogre::MeshPtr mesh = tb.transformToMesh(meshName); + if (mesh.isNull()) { + Ogre::LogManager::getSingleton().logMessage("CellGrid: ERROR - transformToMesh returned null for lot base"); + return; + } + + if (!materialName.empty() && mesh->getNumSubMeshes() > 0) { + mesh->getSubMesh(0)->setMaterialName(materialName); + Ogre::LogManager::getSingleton().logMessage("CellGrid: Set lot base material to: " + materialName); + } else { + Ogre::LogManager::getSingleton().logMessage("CellGrid: WARNING - Could not set material. materialName='" + + materialName + "' subMeshes=" + std::to_string(mesh->getNumSubMeshes())); + } + + Ogre::Entity* lotBaseEntity = m_sceneMgr->createEntity(meshName); + if (!lotBaseEntity) { + Ogre::LogManager::getSingleton().logMessage("CellGrid: ERROR - createEntity returned null for lot base"); + return; + } + + // Attach to the Lot's SceneNode directly (position already set above) + transform.node->attachObject(lotBaseEntity); + + // Debug: Check actual material + if (lotBaseEntity->getNumSubEntities() > 0) { + std::string actualMat = lotBaseEntity->getSubEntity(0)->getMaterialName(); + Ogre::LogManager::getSingleton().logMessage("CellGrid: Lot base actual material: " + actualMat); + } + + LotBaseData data; + data.meshName = meshName; + data.entity = lotBaseEntity; + m_lotBaseMeshes[entity.id()] = data; + + Ogre::LogManager::getSingleton().logMessage( + "CellGrid: Successfully created lot base mesh for entity " + + std::to_string(entity.id()) + " with " + + std::to_string(tb.getVertices().size()) + " vertices"); + + } catch (const Ogre::Exception& e) { + Ogre::LogManager::getSingleton().logMessage( + "CellGrid: Failed to create lot base mesh: " + e.getDescription()); + } +} diff --git a/src/features/editScene/systems/CellGridSystem.hpp b/src/features/editScene/systems/CellGridSystem.hpp index 524f66e..4621d52 100644 --- a/src/features/editScene/systems/CellGridSystem.hpp +++ b/src/features/editScene/systems/CellGridSystem.hpp @@ -29,6 +29,7 @@ private: flecs::query m_cellGridQuery; flecs::query m_townQuery; flecs::query m_districtQuery; + flecs::query m_lotQuery; bool m_initialized = false; int m_meshCount = 0; @@ -52,6 +53,9 @@ private: // Build plaza for district void buildDistrictPlaza(flecs::entity entity, struct DistrictComponent& district); + // Build lot base geometry + void buildLotBase(flecs::entity entity, struct LotComponent& lot); + // Convert triangle buffer to mesh Ogre::MeshPtr convertToMesh(const std::string& name, Procedural::TriangleBuffer& tb, const std::string& materialName); @@ -60,6 +64,7 @@ private: // Destroy existing mesh void destroyCellGridMeshes(struct CellGridComponent& grid); + void destroyCellGridMeshes(flecs::entity entity); // Mesh storage per entity struct MeshData { @@ -81,4 +86,11 @@ private: Ogre::Entity* entity = nullptr; }; std::unordered_map m_plazaMeshes; + + // Lot base mesh storage per lot entity + struct LotBaseData { + std::string meshName; + Ogre::Entity* entity = nullptr; + }; + std::unordered_map m_lotBaseMeshes; }; diff --git a/src/features/editScene/systems/SceneSerializer.cpp b/src/features/editScene/systems/SceneSerializer.cpp index fdfb512..a1fb1b0 100644 --- a/src/features/editScene/systems/SceneSerializer.cpp +++ b/src/features/editScene/systems/SceneSerializer.cpp @@ -1305,6 +1305,8 @@ nlohmann::json SceneSerializer::serializeTown(flecs::entity entity) json["townName"] = town.townName; json["materialName"] = town.materialName; + json["proceduralMaterialEntityId"] = town.proceduralMaterialEntityId; + json["textureRectName"] = town.textureRectName; // Serialize color rects nlohmann::json colorRectsJson = nlohmann::json::object(); @@ -1355,6 +1357,8 @@ nlohmann::json SceneSerializer::serializeLot(flecs::entity entity) json["offsetX"] = lot.offsetX; json["offsetZ"] = lot.offsetZ; json["templateName"] = lot.templateName; + json["proceduralMaterialEntityId"] = lot.proceduralMaterialEntityId; + json["textureRectName"] = lot.textureRectName; return json; } @@ -1471,6 +1475,21 @@ void SceneSerializer::deserializeTown(flecs::entity entity, const nlohmann::json town.townName = json.value("townName", "New Town"); town.materialName = json.value("materialName", ""); + town.proceduralMaterialEntityId = json.value("proceduralMaterialEntityId", ""); + town.textureRectName = json.value("textureRectName", ""); + + // Resolve material entity reference + if (!town.proceduralMaterialEntityId.empty()) { + try { + uint64_t matId = std::stoull(town.proceduralMaterialEntityId); + flecs::entity matEntity(m_world, matId); + if (matEntity.is_alive() && matEntity.has()) { + town.proceduralMaterialEntity = matEntity; + } + } catch (...) { + // Invalid ID format, ignore + } + } // Deserialize color rects if (json.contains("colorRects") && json["colorRects"].is_object()) { @@ -1543,6 +1562,21 @@ void SceneSerializer::deserializeLot(flecs::entity entity, const nlohmann::json& lot.offsetX = json.value("offsetX", 0.0f); lot.offsetZ = json.value("offsetZ", 0.0f); lot.templateName = json.value("templateName", ""); + lot.proceduralMaterialEntityId = json.value("proceduralMaterialEntityId", ""); + lot.textureRectName = json.value("textureRectName", ""); + + // Resolve material entity reference + if (!lot.proceduralMaterialEntityId.empty()) { + try { + uint64_t matId = std::stoull(lot.proceduralMaterialEntityId); + flecs::entity matEntity(m_world, matId); + if (matEntity.is_alive() && matEntity.has()) { + lot.proceduralMaterialEntity = matEntity; + } + } catch (...) { + // Invalid ID format, ignore + } + } lot.dirty = true; entity.set(lot); diff --git a/src/features/editScene/ui/DistrictEditor.cpp b/src/features/editScene/ui/DistrictEditor.cpp index 70694b6..87104df 100644 --- a/src/features/editScene/ui/DistrictEditor.cpp +++ b/src/features/editScene/ui/DistrictEditor.cpp @@ -27,55 +27,54 @@ bool DistrictEditor::renderComponent(flecs::entity entity, DistrictComponent& di " for entity " + std::to_string(entity.id())); } - // Plaza material and texture settings - if (district.isPlaza) { - ImGui::Separator(); - ImGui::Text("Plaza Material:"); + // District material and texture settings + ImGui::Separator(); + ImGui::Text("District Material:"); + + // Material selector + flecs::world world = entity.world(); + std::vector materialEntities; + std::vector materialNames; + + int currentMatIndex = -1; + int noneMatIndex = 0; + + materialEntities.push_back(flecs::entity::null()); + materialNames.push_back("Use Town Material"); + + world.query().each([&](flecs::entity e, ProceduralMaterialComponent& mat) { + materialEntities.push_back(e); + std::string name = mat.materialName.empty() ? "Material " + std::to_string(e.id()) : mat.materialName; + materialNames.push_back(name); - // Material selector - flecs::world world = entity.world(); - std::vector materialEntities; - std::vector materialNames; - - int currentMatIndex = -1; - int noneMatIndex = 0; - - materialEntities.push_back(flecs::entity::null()); - materialNames.push_back("None"); - - world.query().each([&](flecs::entity e, ProceduralMaterialComponent& mat) { - materialEntities.push_back(e); - std::string name = mat.materialName.empty() ? "Material " + std::to_string(e.id()) : mat.materialName; - materialNames.push_back(name); - - if (district.proceduralMaterialEntity == e) { - currentMatIndex = (int)materialEntities.size() - 1; - } - }); - - if (currentMatIndex == -1) currentMatIndex = noneMatIndex; - - std::string matComboItems; - for (size_t i = 0; i < materialNames.size(); ++i) { - if (i > 0) matComboItems += '\0'; - matComboItems += materialNames[i]; + if (district.proceduralMaterialEntity == e) { + currentMatIndex = (int)materialEntities.size() - 1; } - matComboItems += '\0'; - - int newMatIndex = currentMatIndex; - if (ImGui::Combo("Material", &newMatIndex, matComboItems.c_str())) { - if (newMatIndex == noneMatIndex) { - district.proceduralMaterialEntity = flecs::entity::null(); - district.proceduralMaterialEntityId.clear(); - } else { - district.proceduralMaterialEntity = materialEntities[newMatIndex]; - district.proceduralMaterialEntityId = std::to_string(materialEntities[newMatIndex].id()); - } - modified = true; + }); + + if (currentMatIndex == -1) currentMatIndex = noneMatIndex; + + std::string matComboItems; + for (size_t i = 0; i < materialNames.size(); ++i) { + if (i > 0) matComboItems += '\0'; + matComboItems += materialNames[i]; + } + matComboItems += '\0'; + + int newMatIndex = currentMatIndex; + if (ImGui::Combo("Material", &newMatIndex, matComboItems.c_str())) { + if (newMatIndex == noneMatIndex) { + district.proceduralMaterialEntity = flecs::entity::null(); + district.proceduralMaterialEntityId.clear(); + } else { + district.proceduralMaterialEntity = materialEntities[newMatIndex]; + district.proceduralMaterialEntityId = std::to_string(materialEntities[newMatIndex].id()); } - - // Texture rectangle selector - if (district.proceduralMaterialEntity.is_alive()) { + modified = true; + } + + // Texture rectangle selector + if (district.proceduralMaterialEntity.is_alive()) { // Find the texture entity from the material flecs::entity textureEntity = flecs::entity::null(); if (district.proceduralMaterialEntity.has()) { @@ -129,6 +128,11 @@ bool DistrictEditor::renderComponent(flecs::entity entity, DistrictComponent& di ImGui::TextDisabled("Material has no associated ProceduralTexture"); } } + + // Plaza settings + if (district.isPlaza) { + ImGui::Separator(); + ImGui::Text("Plaza uses District Material"); } // Count lots diff --git a/src/features/editScene/ui/LotEditor.cpp b/src/features/editScene/ui/LotEditor.cpp index dea04b8..d6ce63c 100644 --- a/src/features/editScene/ui/LotEditor.cpp +++ b/src/features/editScene/ui/LotEditor.cpp @@ -1,5 +1,7 @@ #include "LotEditor.hpp" #include "../components/CellGrid.hpp" +#include "../components/ProceduralTexture.hpp" +#include "../components/ProceduralMaterial.hpp" #include bool LotEditor::renderComponent(flecs::entity entity, LotComponent& lot) @@ -39,6 +41,95 @@ bool LotEditor::renderComponent(flecs::entity entity, LotComponent& lot) modified = true; } + ImGui::Separator(); + ImGui::Text("Lot Material:"); + + // Material selector + flecs::world world = entity.world(); + std::vector materialEntities; + std::vector materialNames; + materialEntities.push_back(flecs::entity::null()); + materialNames.push_back("Use District/Town"); + + int currentMatIndex = -1; + world.query().each([&](flecs::entity e, ProceduralMaterialComponent& mat) { + materialEntities.push_back(e); + std::string name = mat.materialName.empty() ? "Material " + std::to_string(e.id()) : mat.materialName; + materialNames.push_back(name); + if (lot.proceduralMaterialEntity == e) { + currentMatIndex = (int)materialEntities.size() - 1; + } + }); + + if (currentMatIndex == -1) currentMatIndex = 0; + + std::string comboItems; + for (size_t i = 0; i < materialNames.size(); ++i) { + if (i > 0) comboItems += '\0'; + comboItems += materialNames[i]; + } + comboItems += '\0'; + + int newIndex = currentMatIndex; + if (ImGui::Combo("Material", &newIndex, comboItems.c_str())) { + if (newIndex == 0) { + lot.proceduralMaterialEntity = flecs::entity::null(); + lot.proceduralMaterialEntityId.clear(); + } else { + lot.proceduralMaterialEntity = materialEntities[newIndex]; + lot.proceduralMaterialEntityId = std::to_string(materialEntities[newIndex].id()); + } + modified = true; + } + + // Texture rectangle selector + if (lot.proceduralMaterialEntity.is_alive()) { + flecs::entity textureEntity = flecs::entity::null(); + if (lot.proceduralMaterialEntity.has()) { + const auto& mat = lot.proceduralMaterialEntity.get(); + if (mat.diffuseTextureEntity.is_alive()) { + textureEntity = mat.diffuseTextureEntity; + } + } + + if (textureEntity.is_alive() && textureEntity.has()) { + const auto& texture = textureEntity.get(); + const auto& namedRects = texture.getAllNamedRects(); + + if (!namedRects.empty()) { + std::vector rectNames; + rectNames.push_back("None (use full texture)"); + + int currentRectIndex = -1; + for (const auto& pair : namedRects) { + rectNames.push_back(pair.first); + if (lot.textureRectName == pair.first) { + currentRectIndex = (int)rectNames.size() - 1; + } + } + + if (currentRectIndex == -1) currentRectIndex = 0; + + std::string rectComboItems; + for (size_t i = 0; i < rectNames.size(); ++i) { + if (i > 0) rectComboItems += '\0'; + rectComboItems += rectNames[i]; + } + rectComboItems += '\0'; + + int newRectIndex = currentRectIndex; + if (ImGui::Combo("Texture Rectangle", &newRectIndex, rectComboItems.c_str())) { + if (newRectIndex == 0) { + lot.textureRectName.clear(); + } else { + lot.textureRectName = rectNames[newRectIndex]; + } + modified = true; + } + } + } + } + // Statistics int cellCount = 0; int roomCount = 0; diff --git a/src/features/editScene/ui/TownEditor.cpp b/src/features/editScene/ui/TownEditor.cpp index d051078..af49153 100644 --- a/src/features/editScene/ui/TownEditor.cpp +++ b/src/features/editScene/ui/TownEditor.cpp @@ -1,5 +1,7 @@ #include "TownEditor.hpp" #include "../components/CellGrid.hpp" +#include "../components/ProceduralMaterial.hpp" +#include "../components/ProceduralTexture.hpp" #include bool TownEditor::renderComponent(flecs::entity entity, TownComponent& town) @@ -25,6 +27,96 @@ bool TownEditor::renderComponent(flecs::entity entity, TownComponent& town) }); ImGui::Text("Districts: %d", districtCount); + ImGui::Separator(); + ImGui::Text("Town Material:"); + + // Material selector + flecs::world world = entity.world(); + std::vector materialEntities; + std::vector materialNames; + materialEntities.push_back(flecs::entity::null()); + materialNames.push_back("Use Generated (Color Rects)"); + + int currentMatIndex = -1; + world.query().each([&](flecs::entity e, ProceduralMaterialComponent& mat) { + materialEntities.push_back(e); + std::string name = mat.materialName.empty() ? "Material " + std::to_string(e.id()) : mat.materialName; + materialNames.push_back(name); + if (town.proceduralMaterialEntity == e) { + currentMatIndex = (int)materialEntities.size() - 1; + } + }); + + if (currentMatIndex == -1) currentMatIndex = 0; + + std::string comboItems; + for (size_t i = 0; i < materialNames.size(); ++i) { + if (i > 0) comboItems += '\0'; + comboItems += materialNames[i]; + } + comboItems += '\0'; + + int newIndex = currentMatIndex; + if (ImGui::Combo("Material", &newIndex, comboItems.c_str())) { + if (newIndex == 0) { + town.proceduralMaterialEntity = flecs::entity::null(); + town.proceduralMaterialEntityId.clear(); + } else { + town.proceduralMaterialEntity = materialEntities[newIndex]; + town.proceduralMaterialEntityId = std::to_string(materialEntities[newIndex].id()); + } + modified = true; + town.markMaterialDirty(); + } + + // Texture rectangle selector + if (town.proceduralMaterialEntity.is_alive()) { + flecs::entity textureEntity = flecs::entity::null(); + if (town.proceduralMaterialEntity.has()) { + const auto& mat = town.proceduralMaterialEntity.get(); + if (mat.diffuseTextureEntity.is_alive()) { + textureEntity = mat.diffuseTextureEntity; + } + } + + if (textureEntity.is_alive() && textureEntity.has()) { + const auto& texture = textureEntity.get(); + const auto& namedRects = texture.getAllNamedRects(); + + if (!namedRects.empty()) { + std::vector rectNames; + rectNames.push_back("None (use full texture)"); + + int currentRectIndex = -1; + for (const auto& pair : namedRects) { + rectNames.push_back(pair.first); + if (town.textureRectName == pair.first) { + currentRectIndex = (int)rectNames.size() - 1; + } + } + + if (currentRectIndex == -1) currentRectIndex = 0; + + std::string rectComboItems; + for (size_t i = 0; i < rectNames.size(); ++i) { + if (i > 0) rectComboItems += '\0'; + rectComboItems += rectNames[i]; + } + rectComboItems += '\0'; + + int newRectIndex = currentRectIndex; + if (ImGui::Combo("Texture Rectangle", &newRectIndex, rectComboItems.c_str())) { + if (newRectIndex == 0) { + town.textureRectName.clear(); + } else { + town.textureRectName = rectNames[newRectIndex]; + } + modified = true; + } + } + } + } + // Color rects if (ImGui::CollapsingHeader("Color Rects")) { for (auto& [name, rect] : town.colorRects) {