From 0ebba40867663caf5d856fa09099db7d67730722 Mon Sep 17 00:00:00 2001 From: Sergey Lapin Date: Mon, 6 Apr 2026 01:45:26 +0300 Subject: [PATCH] Corners finally match --- .../editScene/components/CellGrid.hpp | 9 + .../editScene/systems/CellGridSystem.cpp | 718 ++++++++++++++---- .../editScene/systems/CellGridSystem.hpp | 5 +- .../editScene/systems/SceneSerializer.cpp | 18 + src/features/editScene/ui/CellGridEditor.cpp | 129 ++++ src/features/editScene/ui/CellGridEditor.hpp | 1 + 6 files changed, 712 insertions(+), 168 deletions(-) diff --git a/src/features/editScene/components/CellGrid.hpp b/src/features/editScene/components/CellGrid.hpp index 9090908..dbd94e0 100644 --- a/src/features/editScene/components/CellGrid.hpp +++ b/src/features/editScene/components/CellGrid.hpp @@ -119,6 +119,15 @@ struct CellGridComponent { // Generation script (Lua or custom format) std::string generationScript; + // Texture rectangle names for different parts (from ProceduralTexture) + std::string floorRectName; + std::string ceilingRectName; + std::string extWallRectName; + std::string intWallRectName; + std::string doorRectName; + std::string windowRectName; + std::string roofRectName; + // Dirty flag - triggers rebuild bool dirty = true; unsigned int version = 0; diff --git a/src/features/editScene/systems/CellGridSystem.cpp b/src/features/editScene/systems/CellGridSystem.cpp index 8dbe43f..b247b45 100644 --- a/src/features/editScene/systems/CellGridSystem.cpp +++ b/src/features/editScene/systems/CellGridSystem.cpp @@ -95,33 +95,66 @@ void CellGridSystem::update() void CellGridSystem::buildCellGrid(flecs::entity entity, CellGridComponent& grid) { Ogre::LogManager::getSingleton().logMessage("CellGrid: buildCellGrid for entity " + std::to_string(entity.id()) + - " cells=" + std::to_string(grid.cells.size())); + " cells=" + std::to_string(grid.cells.size()) + " cellSize=" + std::to_string(grid.cellSize) + + " cellHeight=" + std::to_string(grid.cellHeight)); // Destroy existing meshes destroyCellGridMeshes(entity); - // Find parent town for material + // Find material - prefer ProceduralMaterial from Town/District/Lot std::string materialName = "Ogre/StandardFloor"; // default flecs::entity townEntity = flecs::entity::null(); + flecs::entity materialEntity = flecs::entity::null(); - // Look for TownComponent in parent hierarchy + // Look for ProceduralMaterial in parent hierarchy (Lot -> District -> Town) flecs::entity parent = entity.parent(); while (parent.is_alive()) { + if (parent.has()) { + auto& lot = parent.get(); + if (lot.proceduralMaterialEntity.is_alive() && + lot.proceduralMaterialEntity.has()) { + const auto& mat = lot.proceduralMaterialEntity.get(); + if (mat.created && !mat.materialName.empty()) { + materialName = mat.materialName; + materialEntity = lot.proceduralMaterialEntity; + break; + } + } + } + if (parent.has()) { + auto& district = parent.get(); + if (district.proceduralMaterialEntity.is_alive() && + district.proceduralMaterialEntity.has()) { + const auto& mat = district.proceduralMaterialEntity.get(); + if (mat.created && !mat.materialName.empty()) { + materialName = mat.materialName; + materialEntity = district.proceduralMaterialEntity; + break; + } + } + } if (parent.has()) { townEntity = parent; auto& town = parent.get(); + // Prefer ProceduralMaterial over generated material + if (town.proceduralMaterialEntity.is_alive() && + town.proceduralMaterialEntity.has()) { + const auto& mat = town.proceduralMaterialEntity.get(); + if (mat.created && !mat.materialName.empty()) { + materialName = mat.materialName; + materialEntity = town.proceduralMaterialEntity; + break; + } + } + // Fall back to generated material materialName = town.materialName.empty() ? "TownMaterial_" + std::to_string(parent.id()) : town.materialName; break; } parent = parent.parent(); } - if (!townEntity.is_alive()) { - Ogre::LogManager::getSingleton().logMessage("CellGrid: No parent town found for entity " + std::to_string(entity.id()) + ", using default material"); - } else { - Ogre::LogManager::getSingleton().logMessage("CellGrid: Found town " + std::to_string(townEntity.id()) + - " for entity " + std::to_string(entity.id()) + " material=" + materialName); - } + Ogre::LogManager::getSingleton().logMessage("CellGrid: Using material '" + materialName + + "' for entity " + std::to_string(entity.id())); MeshData& meshData = m_entityMeshes[entity.id()]; @@ -136,6 +169,22 @@ void CellGridSystem::buildCellGrid(flecs::entity entity, CellGridComponent& grid buildWindows(grid, windowTbs); buildRoofs(entity, grid, roofTb); + // Apply UV mapping for each part (use rect if specified, otherwise default) + Ogre::LogManager::getSingleton().logMessage("CellGrid: UV mapping - floor: " + grid.floorRectName + + " ceiling: " + grid.ceilingRectName + " extWall: " + grid.extWallRectName); + applyUVMappingToBuffer(floorTb, grid.floorRectName, materialEntity); + applyUVMappingToBuffer(ceilingTb, grid.ceilingRectName, materialEntity); + applyUVMappingToBuffer(extWallTb, grid.extWallRectName, materialEntity); + applyUVMappingToBuffer(intWallTb, grid.intWallRectName, materialEntity); + applyUVMappingToBuffer(intWindowsTb, grid.intWallRectName, materialEntity); + for (auto& tb : doorTbs) { + applyUVMappingToBuffer(tb, grid.doorRectName, materialEntity); + } + for (auto& tb : windowTbs) { + applyUVMappingToBuffer(tb, grid.windowRectName, materialEntity); + } + applyUVMappingToBuffer(roofTb, grid.roofRectName, materialEntity); + // Generate unique base name std::string baseName = "CellGrid_" + std::to_string(entity.id()) + "_" + std::to_string(m_meshCount++); @@ -200,38 +249,99 @@ void CellGridSystem::buildCellGrid(flecs::entity entity, CellGridComponent& grid meshData.entities.push_back(entity3d); } - Ogre::LogManager::getSingleton().logMessage("CellGrid: Built geometry for entity " + std::to_string(entity.id())); + // Doors + int doorIdx = 0; + for (auto& tb : doorTbs) { + if (tb.getVertices().size() >= 3) { + std::string meshName = baseName + "_door" + std::to_string(doorIdx++); + meshData.doorMeshes.push_back(meshName); + auto mesh = convertToMesh(meshName, tb, materialName); + auto entity3d = m_sceneMgr->createEntity(meshName); + transform.node->attachObject(entity3d); + meshData.entities.push_back(entity3d); + } + } + + // Windows + int windowIdx = 0; + for (auto& tb : windowTbs) { + if (tb.getVertices().size() >= 3) { + std::string meshName = baseName + "_window" + std::to_string(windowIdx++); + meshData.windowMeshes.push_back(meshName); + auto mesh = convertToMesh(meshName, tb, materialName); + auto entity3d = m_sceneMgr->createEntity(meshName); + transform.node->attachObject(entity3d); + meshData.entities.push_back(entity3d); + } + } + + Ogre::LogManager::getSingleton().logMessage("CellGrid: Built geometry for entity " + std::to_string(entity.id()) + + " floor=" + std::to_string(floorTb.getVertices().size()) + + " ceiling=" + std::to_string(ceilingTb.getVertices().size()) + + " extWall=" + std::to_string(extWallTb.getVertices().size()) + + " intWall=" + std::to_string(intWallTb.getVertices().size()) + + " intWindows=" + std::to_string(intWindowsTb.getVertices().size()) + + " doors=" + std::to_string(doorTbs.size()) + + " windows=" + std::to_string(windowTbs.size()) + + " roof=" + std::to_string(roofTb.getVertices().size())); } -void CellGridSystem::buildFloorsAndCeilings(const CellGridComponent& grid, - Procedural::TriangleBuffer& floorTb, +// BitSet structure matching original town.cpp +struct BitSet { + uint64_t bit; + float sizeX; + float sizeY; + Ogre::Vector3 normal; + Ogre::Vector3 offset; + Procedural::TriangleBuffer* tb; +}; + +// Plane generator matching original town.cpp +// The sizeX/sizeY values from original BitSet are the CORRECT full sizes for PlaneGenerator +// - sizeX = wall height (vertical dimension of the plane) +// - sizeY = wall half-width (horizontal dimension of the plane, gets doubled by PlaneGenerator internally) +static void genPlane(float sizeX, float sizeY, const Ogre::Vector3& normal, + const Ogre::Vector3& offset, Procedural::TriangleBuffer& tb, + const Ogre::Vector3& origin) { + // nsegY is based on the height (sizeX in the original, which is vertical) + int nsegY = (sizeX < 1.0f) ? 1 : std::max(1, (int)(sizeX / 2.0f)); + Procedural::PlaneGenerator() + .setNormal(normal) + .setSizeX(sizeX) // This is the height of the wall + .setSizeY(sizeY) // This is already correct (half-width * 2 = full width) + .setNumSegX(1) + .setNumSegY(nsegY) + .setEnableNormals(true) + .setPosition(origin + offset) + .addToTriangleBuffer(tb); +} + +void CellGridSystem::buildFloorsAndCeilings(const CellGridComponent& grid, + Procedural::TriangleBuffer& floorTb, Procedural::TriangleBuffer& ceilingTb) { - const float cellSize = grid.cellSize; - const float floorHeight = 0.1f; - const float ceilingOffset = grid.cellHeight - 0.3f; - + // Original uses 2.0f as half-size for 4.0f cell (cellSize/2) + // Use same hScale as walls for consistency + const float hScale = grid.cellSize / 2.0f; + // Scale factor for original 4.0f cell height reference + const float vScale = grid.cellHeight / 4.0f; + const float floorY = 0.1f * vScale; + const float ceilingY = grid.cellHeight - 0.3f * vScale; + for (const auto& cell : grid.cells) { Ogre::Vector3 origin = grid.cellToWorld(cell.x, cell.y, cell.z); - - // Floor - if (cell.hasFlag(CellFlags::Floor)) { - Procedural::BoxGenerator box; - box.setSizeX(cellSize - 0.1f); - box.setSizeY(floorHeight); - box.setSizeZ(cellSize - 0.1f); - box.setPosition(Ogre::Vector3(origin.x, origin.y + floorHeight/2.0f, origin.z)); - box.addToTriangleBuffer(floorTb); + uint64_t flags = cell.flags; + + // Floor: original sizeX=2.0f, sizeY=2.0f (half-sizes), scaled by hScale + if (flags & CellFlags::Floor) { + genPlane(2.0f * hScale, 2.0f * hScale, Ogre::Vector3::UNIT_Y, + Ogre::Vector3(0, floorY, 0), floorTb, origin); } - - // Ceiling - if (cell.hasFlag(CellFlags::Ceiling)) { - Procedural::BoxGenerator box; - box.setSizeX(cellSize - 0.1f); - box.setSizeY(0.1f); - box.setSizeZ(cellSize - 0.1f); - box.setPosition(Ogre::Vector3(origin.x, origin.y + ceilingOffset, origin.z)); - box.addToTriangleBuffer(ceilingTb); + + // Ceiling: original sizeX=2.0f, sizeY=2.0f (half-sizes), scaled by hScale + if (flags & CellFlags::Ceiling) { + genPlane(2.0f * hScale, 2.0f * hScale, Ogre::Vector3::NEGATIVE_UNIT_Y, + Ogre::Vector3(0, ceilingY, 0), ceilingTb, origin); } } } @@ -241,168 +351,350 @@ void CellGridSystem::buildWalls(const CellGridComponent& grid, Procedural::TriangleBuffer& intWallTb, Procedural::TriangleBuffer& intWindowsTb) { - const float cellSize = grid.cellSize; + // Original tables designed for cellSize=2.0 (2x2 base, 4.0 height) + // In original: sizeY=2.0f represents FULL cell width (not half!) + // Scale factor: cellSize / 2.0f (not 4.0f!) + const float hScale = grid.cellSize / 2.0f; // Horizontal scale + const float halfCell = 1.0f * hScale; // half of cellSize + const float cornerWidth = 0.2f; // Fixed corner width, don't scale const float solidExtHeight = grid.cellHeight; const float solidExtOffset = 0.0f; - const float solidIntOffset = 0.1f; + const float solidIntOffset = 0.1f * hScale; const float solidIntHeight = grid.cellHeight - 0.3f - solidIntOffset; + // Tables matching original town.cpp + // Original cellSize=2.0: sizeY=2.0f (full width), offset=+/-(1.0 + 0.2)=+/-1.1? + // Wait, original offset is -2.2f which is -(2.0 + 0.2) + // So 2.0f in offset IS half-cell for original (cellSize=4.0) + // Let me re-read: original uses 4.0f cellSize, 2.0f half-cell + // But you said tables are for 2x2... so 2.0f in table = full width of 2.0 + // Then for cellSize=4.0, wall width should be 4.0 + // Scale = cellSize / 2.0f = 4.0 / 2.0 = 2.0 + // sizeY = 2.0f * 2.0 = 4.0 ✓ + std::vector bits_solid = { + // External walls: sizeX=height, sizeY=cellSize (full width = 2.0f * hScale) + { CellFlags::WallXNeg, solidExtHeight, 2.0f * hScale, + Ogre::Vector3::NEGATIVE_UNIT_X, + { -1.0f * hScale - cornerWidth, solidExtHeight / 2.0f + solidExtOffset, 0 }, &extWallTb }, + { CellFlags::WallXPos, solidExtHeight, 2.0f * hScale, + Ogre::Vector3::UNIT_X, + { 1.0f * hScale + cornerWidth, solidExtHeight / 2.0f + solidExtOffset, 0 }, &extWallTb }, + { CellFlags::WallZPos, solidExtHeight, 2.0f * hScale, + Ogre::Vector3::UNIT_Z, + { 0, solidExtHeight / 2.0f + solidExtOffset, 1.0f * hScale + cornerWidth }, &extWallTb }, + { CellFlags::WallZNeg, solidExtHeight, 2.0f * hScale, + Ogre::Vector3::NEGATIVE_UNIT_Z, + { 0, solidExtHeight / 2.0f + solidExtOffset, -1.0f * hScale - cornerWidth }, &extWallTb }, + // Internal walls: sizeY=2.0f-0.2f (in original units), offset=+/-(1.0f - 0.1f) + { CellFlags::IntWallXNeg, solidIntHeight, (2.0f - 0.2f) * hScale, + Ogre::Vector3::UNIT_X, + { -1.0f * hScale + cornerWidth/2, solidIntHeight / 2.0f + solidIntOffset, 0 }, &intWallTb }, + { CellFlags::IntWallXPos, solidIntHeight, (2.0f - 0.2f) * hScale, + Ogre::Vector3::NEGATIVE_UNIT_X, + { 1.0f * hScale - cornerWidth/2, solidIntHeight / 2.0f + solidIntOffset, 0 }, &intWallTb }, + { CellFlags::IntWallZPos, solidIntHeight, (2.0f - 0.2f) * hScale, + Ogre::Vector3::NEGATIVE_UNIT_Z, + { 0, solidIntHeight / 2.0f + solidIntOffset, 1.0f * hScale - cornerWidth/2 }, &intWallTb }, + { CellFlags::IntWallZNeg, solidIntHeight, (2.0f - 0.2f) * hScale, + Ogre::Vector3::UNIT_Z, + { 0, solidIntHeight / 2.0f + solidIntOffset, -1.0f * hScale + cornerWidth/2 }, &intWallTb }, + }; + for (const auto& cell : grid.cells) { Ogre::Vector3 origin = grid.cellToWorld(cell.x, cell.y, cell.z); + uint64_t flags = cell.flags; - // External walls - if (cell.hasFlag(CellFlags::WallXNeg)) { - Procedural::BoxGenerator box; - box.setSizeX(0.2f); - box.setSizeY(solidExtHeight); - box.setSizeZ(cellSize); - box.setPosition(Ogre::Vector3(origin.x - cellSize/2.0f - 0.1f, - origin.y + solidExtHeight/2.0f, origin.z)); - box.addToTriangleBuffer(extWallTb); - } - if (cell.hasFlag(CellFlags::WallXPos)) { - Procedural::BoxGenerator box; - box.setSizeX(0.2f); - box.setSizeY(solidExtHeight); - box.setSizeZ(cellSize); - box.setPosition(Ogre::Vector3(origin.x + cellSize/2.0f + 0.1f, - origin.y + solidExtHeight/2.0f, origin.z)); - box.addToTriangleBuffer(extWallTb); - } - if (cell.hasFlag(CellFlags::WallZPos)) { - Procedural::BoxGenerator box; - box.setSizeX(cellSize); - box.setSizeY(solidExtHeight); - box.setSizeZ(0.2f); - box.setPosition(Ogre::Vector3(origin.x, origin.y + solidExtHeight/2.0f, - origin.z + cellSize/2.0f + 0.1f)); - box.addToTriangleBuffer(extWallTb); - } - if (cell.hasFlag(CellFlags::WallZNeg)) { - Procedural::BoxGenerator box; - box.setSizeX(cellSize); - box.setSizeY(solidExtHeight); - box.setSizeZ(0.2f); - box.setPosition(Ogre::Vector3(origin.x, origin.y + solidExtHeight/2.0f, - origin.z - cellSize/2.0f - 0.1f)); - box.addToTriangleBuffer(extWallTb); - } - - // Internal walls - if (cell.hasFlag(CellFlags::IntWallXNeg)) { - Procedural::BoxGenerator box; - box.setSizeX(0.1f); - box.setSizeY(solidIntHeight); - box.setSizeZ(cellSize - 0.4f); - box.setPosition(Ogre::Vector3(origin.x - cellSize/2.0f + 0.05f, - origin.y + solidIntHeight/2.0f + solidIntOffset, origin.z)); - box.addToTriangleBuffer(intWallTb); - } - if (cell.hasFlag(CellFlags::IntWallXPos)) { - Procedural::BoxGenerator box; - box.setSizeX(0.1f); - box.setSizeY(solidIntHeight); - box.setSizeZ(cellSize - 0.4f); - box.setPosition(Ogre::Vector3(origin.x + cellSize/2.0f - 0.05f, - origin.y + solidIntHeight/2.0f + solidIntOffset, origin.z)); - box.addToTriangleBuffer(intWallTb); - } - if (cell.hasFlag(CellFlags::IntWallZPos)) { - Procedural::BoxGenerator box; - box.setSizeX(cellSize - 0.4f); - box.setSizeY(solidIntHeight); - box.setSizeZ(0.1f); - box.setPosition(Ogre::Vector3(origin.x, origin.y + solidIntHeight/2.0f + solidIntOffset, - origin.z + cellSize/2.0f - 0.05f)); - box.addToTriangleBuffer(intWallTb); - } - if (cell.hasFlag(CellFlags::IntWallZNeg)) { - Procedural::BoxGenerator box; - box.setSizeX(cellSize - 0.4f); - box.setSizeY(solidIntHeight); - box.setSizeZ(0.1f); - box.setPosition(Ogre::Vector3(origin.x, origin.y + solidIntHeight/2.0f + solidIntOffset, - origin.z - cellSize/2.0f + 0.05f)); - box.addToTriangleBuffer(intWallTb); + for (const auto& bit : bits_solid) { + if ((flags & bit.bit) == bit.bit) { + genPlane(bit.sizeX, bit.sizeY, bit.normal, bit.offset, *bit.tb, origin); + } } } } void CellGridSystem::buildCorners(const CellGridComponent& grid, Procedural::TriangleBuffer& extWallTb) { - // Build external wall corners where walls meet - const float cellSize = grid.cellSize; + // Build external wall corners where walls meet using PlaneGenerator + // Original tables for cellSize=2.0 (2x2 base), so halfCell=1.0 + const float hScale = grid.cellSize / 2.0f; + const float halfCell = 1.0f * hScale; // = cellSize / 2 + const float cornerWidth = 0.2f; // Fixed corner width const float solidExtHeight = grid.cellHeight; + const float solidExtOffset = 0.0f; + + // Tables for corners matching original town.cpp bits_ext_corners + // Format: bit, sizeX (height), sizeY (corner width), normal, offset + // Corner width is FIXED (0.2f) - don't scale to avoid thin polygons + // Each corner plane is shifted by cornerWidth/2 toward the corner to close the gap + const float cornerOffset = cornerWidth / 2.0f; + std::vector bits_ext_corners = { + // X- with Z+ corners + // First corner plane: normal along -X, at X=-(halfCell+cornerWidth), Z=+halfCell+cornerOffset + { CellFlags::WallXNeg | CellFlags::WallZPos, solidExtHeight, cornerWidth, + Ogre::Vector3::NEGATIVE_UNIT_X, + { -halfCell - cornerWidth, solidExtHeight / 2.0f + solidExtOffset, halfCell + cornerOffset }, &extWallTb }, + // Second corner plane: normal along +Z, at X=-halfCell-cornerOffset, Z=+(halfCell+cornerWidth) + { CellFlags::WallXNeg | CellFlags::WallZPos, solidExtHeight, cornerWidth, + Ogre::Vector3::UNIT_Z, + { -halfCell - cornerOffset, solidExtHeight / 2.0f + solidExtOffset, halfCell + cornerWidth }, &extWallTb }, + // X- with Z- corners + { CellFlags::WallXNeg | CellFlags::WallZNeg, solidExtHeight, cornerWidth, + Ogre::Vector3::NEGATIVE_UNIT_X, + { -halfCell - cornerWidth, solidExtHeight / 2.0f + solidExtOffset, -halfCell - cornerOffset }, &extWallTb }, + { CellFlags::WallXNeg | CellFlags::WallZNeg, solidExtHeight, cornerWidth, + Ogre::Vector3::NEGATIVE_UNIT_Z, + { -halfCell - cornerOffset, solidExtHeight / 2.0f + solidExtOffset, -halfCell - cornerWidth }, &extWallTb }, + // X+ with Z+ corners + { CellFlags::WallXPos | CellFlags::WallZPos, solidExtHeight, cornerWidth, + Ogre::Vector3::UNIT_X, + { halfCell + cornerWidth, solidExtHeight / 2.0f + solidExtOffset, halfCell + cornerOffset }, &extWallTb }, + { CellFlags::WallXPos | CellFlags::WallZPos, solidExtHeight, cornerWidth, + Ogre::Vector3::UNIT_Z, + { halfCell + cornerOffset, solidExtHeight / 2.0f + solidExtOffset, halfCell + cornerWidth }, &extWallTb }, + // X+ with Z- corners + { CellFlags::WallXPos | CellFlags::WallZNeg, solidExtHeight, cornerWidth, + Ogre::Vector3::UNIT_X, + { halfCell + cornerWidth, solidExtHeight / 2.0f + solidExtOffset, -halfCell - cornerOffset }, &extWallTb }, + { CellFlags::WallXPos | CellFlags::WallZNeg, solidExtHeight, cornerWidth, + Ogre::Vector3::NEGATIVE_UNIT_Z, + { halfCell + cornerOffset, solidExtHeight / 2.0f + solidExtOffset, -halfCell - cornerWidth }, &extWallTb }, + }; for (const auto& cell : grid.cells) { Ogre::Vector3 origin = grid.cellToWorld(cell.x, cell.y, cell.z); uint64_t wallFlags = cell.flags & CellFlags::AllExternalWalls; - // Corner cases: X- with Z+, X- with Z-, X+ with Z+, X+ with Z- - if ((cell.hasFlag(CellFlags::WallXNeg) || cell.hasFlag(CellFlags::WallZPos)) && - (wallFlags & (CellFlags::WallXNeg | CellFlags::WallZPos)) == (CellFlags::WallXNeg | CellFlags::WallZPos)) { - // Corner at X-, Z+ - Procedural::BoxGenerator box; - box.setSizeX(0.2f); - box.setSizeY(solidExtHeight); - box.setSizeZ(0.2f); - box.setPosition(Ogre::Vector3(origin.x - cellSize/2.0f - 0.1f, - origin.y + solidExtHeight/2.0f, - origin.z + cellSize/2.0f + 0.1f)); - box.addToTriangleBuffer(extWallTb); - } - - if ((cell.hasFlag(CellFlags::WallXNeg) || cell.hasFlag(CellFlags::WallZNeg)) && - (wallFlags & (CellFlags::WallXNeg | CellFlags::WallZNeg)) == (CellFlags::WallXNeg | CellFlags::WallZNeg)) { - // Corner at X-, Z- - Procedural::BoxGenerator box; - box.setSizeX(0.2f); - box.setSizeY(solidExtHeight); - box.setSizeZ(0.2f); - box.setPosition(Ogre::Vector3(origin.x - cellSize/2.0f - 0.1f, - origin.y + solidExtHeight/2.0f, - origin.z - cellSize/2.0f - 0.1f)); - box.addToTriangleBuffer(extWallTb); - } - - if ((cell.hasFlag(CellFlags::WallXPos) || cell.hasFlag(CellFlags::WallZPos)) && - (wallFlags & (CellFlags::WallXPos | CellFlags::WallZPos)) == (CellFlags::WallXPos | CellFlags::WallZPos)) { - // Corner at X+, Z+ - Procedural::BoxGenerator box; - box.setSizeX(0.2f); - box.setSizeY(solidExtHeight); - box.setSizeZ(0.2f); - box.setPosition(Ogre::Vector3(origin.x + cellSize/2.0f + 0.1f, - origin.y + solidExtHeight/2.0f, - origin.z + cellSize/2.0f + 0.1f)); - box.addToTriangleBuffer(extWallTb); - } - - if ((cell.hasFlag(CellFlags::WallXPos) || cell.hasFlag(CellFlags::WallZNeg)) && - (wallFlags & (CellFlags::WallXPos | CellFlags::WallZNeg)) == (CellFlags::WallXPos | CellFlags::WallZNeg)) { - // Corner at X+, Z- - Procedural::BoxGenerator box; - box.setSizeX(0.2f); - box.setSizeY(solidExtHeight); - box.setSizeZ(0.2f); - box.setPosition(Ogre::Vector3(origin.x + cellSize/2.0f + 0.1f, - origin.y + solidExtHeight/2.0f, - origin.z - cellSize/2.0f - 0.1f)); - box.addToTriangleBuffer(extWallTb); + for (const auto& bit : bits_ext_corners) { + // Check if both wall flags are set (corner needs both adjacent walls) + if ((wallFlags & bit.bit) == bit.bit) { + genPlane(bit.sizeX, bit.sizeY, bit.normal, bit.offset, *bit.tb, origin); + } } } } void CellGridSystem::buildDoors(const CellGridComponent& grid, std::vector& doorTbs) { - // Doors are cutouts in walls - we build the wall around them - // For now, use wall geometry with gaps - // This is simplified - full implementation needs more complex geometry + const float cellSize = grid.cellSize; + const float doorHeight = grid.cellHeight * 0.7f; + const float doorWidth = cellSize * 0.6f; + const float frameThickness = 0.1f; + + for (const auto& cell : grid.cells) { + Ogre::Vector3 origin = grid.cellToWorld(cell.x, cell.y, cell.z); + + // External doors + if (cell.hasFlag(CellFlags::DoorXNeg) || cell.hasFlag(CellFlags::DoorXPos) || + cell.hasFlag(CellFlags::DoorZNeg) || cell.hasFlag(CellFlags::DoorZPos)) { + + Procedural::TriangleBuffer tb; + + // Door frame (simplified as thin boxes around the opening) + if (cell.hasFlag(CellFlags::DoorXNeg)) { + // Top frame + Procedural::BoxGenerator box; + box.setSizeX(0.15f); + box.setSizeY(grid.cellHeight - doorHeight); + box.setSizeZ(doorWidth + 0.2f); + box.setPosition(Ogre::Vector3(origin.x - cellSize/2.0f - 0.075f, + origin.y + doorHeight + (grid.cellHeight - doorHeight)/2.0f, origin.z)); + box.addToTriangleBuffer(tb); + } + if (cell.hasFlag(CellFlags::DoorXPos)) { + Procedural::BoxGenerator box; + box.setSizeX(0.15f); + box.setSizeY(grid.cellHeight - doorHeight); + box.setSizeZ(doorWidth + 0.2f); + box.setPosition(Ogre::Vector3(origin.x + cellSize/2.0f + 0.075f, + origin.y + doorHeight + (grid.cellHeight - doorHeight)/2.0f, origin.z)); + box.addToTriangleBuffer(tb); + } + if (cell.hasFlag(CellFlags::DoorZNeg)) { + Procedural::BoxGenerator box; + box.setSizeX(doorWidth + 0.2f); + box.setSizeY(grid.cellHeight - doorHeight); + box.setSizeZ(0.15f); + box.setPosition(Ogre::Vector3(origin.x, + origin.y + doorHeight + (grid.cellHeight - doorHeight)/2.0f, + origin.z - cellSize/2.0f - 0.075f)); + box.addToTriangleBuffer(tb); + } + if (cell.hasFlag(CellFlags::DoorZPos)) { + Procedural::BoxGenerator box; + box.setSizeX(doorWidth + 0.2f); + box.setSizeY(grid.cellHeight - doorHeight); + box.setSizeZ(0.15f); + box.setPosition(Ogre::Vector3(origin.x, + origin.y + doorHeight + (grid.cellHeight - doorHeight)/2.0f, + origin.z + cellSize/2.0f + 0.075f)); + box.addToTriangleBuffer(tb); + } + + if (tb.getVertices().size() > 0) { + doorTbs.push_back(std::move(tb)); + } + } + + // Internal doors (similar but thinner) + if (cell.hasFlag(CellFlags::IntDoorXNeg) || cell.hasFlag(CellFlags::IntDoorXPos) || + cell.hasFlag(CellFlags::IntDoorZNeg) || cell.hasFlag(CellFlags::IntDoorZPos)) { + + Procedural::TriangleBuffer tb; + + if (cell.hasFlag(CellFlags::IntDoorXNeg)) { + Procedural::BoxGenerator box; + box.setSizeX(0.08f); + box.setSizeY(grid.cellHeight - doorHeight); + box.setSizeZ(doorWidth + 0.2f); + box.setPosition(Ogre::Vector3(origin.x - cellSize/2.0f + 0.04f, + origin.y + doorHeight + (grid.cellHeight - doorHeight)/2.0f, origin.z)); + box.addToTriangleBuffer(tb); + } + if (cell.hasFlag(CellFlags::IntDoorXPos)) { + Procedural::BoxGenerator box; + box.setSizeX(0.08f); + box.setSizeY(grid.cellHeight - doorHeight); + box.setSizeZ(doorWidth + 0.2f); + box.setPosition(Ogre::Vector3(origin.x + cellSize/2.0f - 0.04f, + origin.y + doorHeight + (grid.cellHeight - doorHeight)/2.0f, origin.z)); + box.addToTriangleBuffer(tb); + } + if (cell.hasFlag(CellFlags::IntDoorZNeg)) { + Procedural::BoxGenerator box; + box.setSizeX(doorWidth + 0.2f); + box.setSizeY(grid.cellHeight - doorHeight); + box.setSizeZ(0.08f); + box.setPosition(Ogre::Vector3(origin.x, + origin.y + doorHeight + (grid.cellHeight - doorHeight)/2.0f, + origin.z - cellSize/2.0f + 0.04f)); + box.addToTriangleBuffer(tb); + } + if (cell.hasFlag(CellFlags::IntDoorZPos)) { + Procedural::BoxGenerator box; + box.setSizeX(doorWidth + 0.2f); + box.setSizeY(grid.cellHeight - doorHeight); + box.setSizeZ(0.08f); + box.setPosition(Ogre::Vector3(origin.x, + origin.y + doorHeight + (grid.cellHeight - doorHeight)/2.0f, + origin.z + cellSize/2.0f - 0.04f)); + box.addToTriangleBuffer(tb); + } + + if (tb.getVertices().size() > 0) { + doorTbs.push_back(std::move(tb)); + } + } + } } void CellGridSystem::buildWindows(const CellGridComponent& grid, std::vector& windowTbs) { - // Windows are cutouts in walls - similar to doors + const float cellSize = grid.cellSize; + const float windowHeight = grid.cellHeight * 0.4f; + const float windowWidth = cellSize * 0.5f; + const float windowY = grid.cellHeight * 0.4f; + + for (const auto& cell : grid.cells) { + Ogre::Vector3 origin = grid.cellToWorld(cell.x, cell.y, cell.z); + + // External windows + if (cell.hasFlag(CellFlags::WindowXNeg) || cell.hasFlag(CellFlags::WindowXPos) || + cell.hasFlag(CellFlags::WindowZNeg) || cell.hasFlag(CellFlags::WindowZPos)) { + + Procedural::TriangleBuffer tb; + + // Window frame (surrounding the window opening) + if (cell.hasFlag(CellFlags::WindowXNeg)) { + // Frame around the window + Procedural::BoxGenerator box; + box.setSizeX(0.12f); + box.setSizeY(windowHeight + 0.2f); + box.setSizeZ(windowWidth + 0.2f); + box.setPosition(Ogre::Vector3(origin.x - cellSize/2.0f - 0.06f, + origin.y + windowY, origin.z)); + box.addToTriangleBuffer(tb); + } + if (cell.hasFlag(CellFlags::WindowXPos)) { + Procedural::BoxGenerator box; + box.setSizeX(0.12f); + box.setSizeY(windowHeight + 0.2f); + box.setSizeZ(windowWidth + 0.2f); + box.setPosition(Ogre::Vector3(origin.x + cellSize/2.0f + 0.06f, + origin.y + windowY, origin.z)); + box.addToTriangleBuffer(tb); + } + if (cell.hasFlag(CellFlags::WindowZNeg)) { + Procedural::BoxGenerator box; + box.setSizeX(windowWidth + 0.2f); + box.setSizeY(windowHeight + 0.2f); + box.setSizeZ(0.12f); + box.setPosition(Ogre::Vector3(origin.x, origin.y + windowY, + origin.z - cellSize/2.0f - 0.06f)); + box.addToTriangleBuffer(tb); + } + if (cell.hasFlag(CellFlags::WindowZPos)) { + Procedural::BoxGenerator box; + box.setSizeX(windowWidth + 0.2f); + box.setSizeY(windowHeight + 0.2f); + box.setSizeZ(0.12f); + box.setPosition(Ogre::Vector3(origin.x, origin.y + windowY, + origin.z + cellSize/2.0f + 0.06f)); + box.addToTriangleBuffer(tb); + } + + if (tb.getVertices().size() > 0) { + windowTbs.push_back(std::move(tb)); + } + } + + // Internal windows (thinner frames) + if (cell.hasFlag(CellFlags::IntWindowXNeg) || cell.hasFlag(CellFlags::IntWindowXPos) || + cell.hasFlag(CellFlags::IntWindowZNeg) || cell.hasFlag(CellFlags::IntWindowZPos)) { + + Procedural::TriangleBuffer tb; + + if (cell.hasFlag(CellFlags::IntWindowXNeg)) { + Procedural::BoxGenerator box; + box.setSizeX(0.06f); + box.setSizeY(windowHeight + 0.15f); + box.setSizeZ(windowWidth + 0.15f); + box.setPosition(Ogre::Vector3(origin.x - cellSize/2.0f + 0.03f, + origin.y + windowY, origin.z)); + box.addToTriangleBuffer(tb); + } + if (cell.hasFlag(CellFlags::IntWindowXPos)) { + Procedural::BoxGenerator box; + box.setSizeX(0.06f); + box.setSizeY(windowHeight + 0.15f); + box.setSizeZ(windowWidth + 0.15f); + box.setPosition(Ogre::Vector3(origin.x + cellSize/2.0f - 0.03f, + origin.y + windowY, origin.z)); + box.addToTriangleBuffer(tb); + } + if (cell.hasFlag(CellFlags::IntWindowZNeg)) { + Procedural::BoxGenerator box; + box.setSizeX(windowWidth + 0.15f); + box.setSizeY(windowHeight + 0.15f); + box.setSizeZ(0.06f); + box.setPosition(Ogre::Vector3(origin.x, origin.y + windowY, + origin.z - cellSize/2.0f + 0.03f)); + box.addToTriangleBuffer(tb); + } + if (cell.hasFlag(CellFlags::IntWindowZPos)) { + Procedural::BoxGenerator box; + box.setSizeX(windowWidth + 0.15f); + box.setSizeY(windowHeight + 0.15f); + box.setSizeZ(0.06f); + box.setPosition(Ogre::Vector3(origin.x, origin.y + windowY, + origin.z + cellSize/2.0f - 0.03f)); + box.addToTriangleBuffer(tb); + } + + if (tb.getVertices().size() > 0) { + windowTbs.push_back(std::move(tb)); + } + } + } } void CellGridSystem::buildRoofs(flecs::entity lotEntity, const CellGridComponent& grid, Procedural::TriangleBuffer& roofTb) @@ -471,8 +763,11 @@ Ogre::MeshPtr CellGridSystem::convertToMesh(const std::string& name, Procedural: { try { Ogre::MeshPtr mesh = tb.transformToMesh(name); + Ogre::LogManager::getSingleton().logMessage("CellGrid: convertToMesh '" + name + "' vertices=" + + std::to_string(tb.getVertices().size()) + " material=" + materialName); if (!materialName.empty() && mesh->getNumSubMeshes() > 0) { mesh->getSubMesh(0)->setMaterialName(materialName); + Ogre::LogManager::getSingleton().logMessage("CellGrid: Set material on mesh '" + name + "' to '" + materialName + "'"); } return mesh; } catch (const Ogre::Exception& e) { @@ -518,6 +813,12 @@ void CellGridSystem::destroyCellGridMeshes(flecs::entity entity) 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); + } for (const auto& name : it->second.doorMeshes) { removeMesh(name); @@ -1030,3 +1331,86 @@ void CellGridSystem::buildLotBase(flecs::entity entity, LotComponent& lot) "CellGrid: Failed to create lot base mesh: " + e.getDescription()); } } + + +void CellGridSystem::applyUVMappingToBuffer(Procedural::TriangleBuffer& tb, const std::string& rectName, flecs::entity materialEntity) +{ + if (tb.getVertices().empty()) return; + + // If no rect specified, use default UV mapping + if (rectName.empty()) { + // Default mapping - scale down UVs to a small atlas region + 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); + } + return; + } + + Ogre::LogManager::getSingleton().logMessage("CellGrid: Looking for texture rect: " + rectName); + + // Find texture entity from material + flecs::entity textureEntity = flecs::entity::null(); + if (materialEntity.is_alive() && materialEntity.has()) { + const auto& mat = materialEntity.get(); + if (mat.diffuseTextureEntity.is_alive()) { + textureEntity = mat.diffuseTextureEntity; + } + } + + if (!textureEntity.is_alive()) { + Ogre::LogManager::getSingleton().logMessage("CellGrid: WARNING - No texture entity for rect: " + rectName); + // Fall back to default UV mapping + 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); + } + return; + } + + if (!textureEntity.has()) { + Ogre::LogManager::getSingleton().logMessage("CellGrid: WARNING - Texture entity has no ProceduralTextureComponent for rect: " + rectName); + // Fall back to default UV mapping + 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); + } + return; + } + + const auto& texture = textureEntity.get(); + const TextureRectInfo* rect = texture.getNamedRect(rectName); + if (!rect) { + Ogre::LogManager::getSingleton().logMessage("CellGrid: WARNING - Rect not found: " + rectName); + // Fall back to default UV mapping + 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); + } + return; + } + + Ogre::LogManager::getSingleton().logMessage("CellGrid: Applying rect " + rectName + + " u=" + std::to_string(rect->u1) + "-" + std::to_string(rect->u2) + + " v=" + std::to_string(rect->v1) + "-" + std::to_string(rect->v2)); + + // Apply texture rectangle UV mapping + 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; + } +} diff --git a/src/features/editScene/systems/CellGridSystem.hpp b/src/features/editScene/systems/CellGridSystem.hpp index 4621d52..ba23a22 100644 --- a/src/features/editScene/systems/CellGridSystem.hpp +++ b/src/features/editScene/systems/CellGridSystem.hpp @@ -60,7 +60,10 @@ private: Ogre::MeshPtr convertToMesh(const std::string& name, Procedural::TriangleBuffer& tb, const std::string& materialName); // Apply UV mapping from color rects - void applyUVMapping(Procedural::TriangleBuffer& tb, const struct TownComponent& town, const std::string& rectName); + void applyUVMapping(Procedural::TriangleBuffer& tb, flecs::entity entity, const std::string& rectName); + + // Apply UV mapping using texture coordinates directly + void applyUVMappingToBuffer(Procedural::TriangleBuffer& tb, const std::string& rectName, flecs::entity materialEntity); // Destroy existing mesh void destroyCellGridMeshes(struct CellGridComponent& grid); diff --git a/src/features/editScene/systems/SceneSerializer.cpp b/src/features/editScene/systems/SceneSerializer.cpp index a1fb1b0..bd711f7 100644 --- a/src/features/editScene/systems/SceneSerializer.cpp +++ b/src/features/editScene/systems/SceneSerializer.cpp @@ -1269,6 +1269,15 @@ nlohmann::json SceneSerializer::serializeCellGrid(flecs::entity entity) json["cellHeight"] = grid.cellHeight; json["generationScript"] = grid.generationScript; + // Serialize texture rectangles + json["floorRectName"] = grid.floorRectName; + json["ceilingRectName"] = grid.ceilingRectName; + json["extWallRectName"] = grid.extWallRectName; + json["intWallRectName"] = grid.intWallRectName; + json["doorRectName"] = grid.doorRectName; + json["windowRectName"] = grid.windowRectName; + json["roofRectName"] = grid.roofRectName; + // Serialize cells nlohmann::json cellsJson = nlohmann::json::array(); for (const auto& cell : grid.cells) { @@ -1433,6 +1442,15 @@ void SceneSerializer::deserializeCellGrid(flecs::entity entity, const nlohmann:: grid.cellHeight = json.value("cellHeight", 4.0f); grid.generationScript = json.value("generationScript", ""); + // Deserialize texture rectangles + grid.floorRectName = json.value("floorRectName", ""); + grid.ceilingRectName = json.value("ceilingRectName", ""); + grid.extWallRectName = json.value("extWallRectName", ""); + grid.intWallRectName = json.value("intWallRectName", ""); + grid.doorRectName = json.value("doorRectName", ""); + grid.windowRectName = json.value("windowRectName", ""); + grid.roofRectName = json.value("roofRectName", ""); + // Deserialize cells if (json.contains("cells") && json["cells"].is_array()) { for (const auto& cellJson : json["cells"]) { diff --git a/src/features/editScene/ui/CellGridEditor.cpp b/src/features/editScene/ui/CellGridEditor.cpp index b02ae98..846de44 100644 --- a/src/features/editScene/ui/CellGridEditor.cpp +++ b/src/features/editScene/ui/CellGridEditor.cpp @@ -1,5 +1,7 @@ #include "CellGridEditor.hpp" #include "../components/CellGrid.hpp" +#include "../components/ProceduralMaterial.hpp" +#include "../components/ProceduralTexture.hpp" #include bool CellGridEditor::renderComponent(flecs::entity entity, CellGridComponent& grid) @@ -42,6 +44,11 @@ bool CellGridEditor::renderComponent(flecs::entity entity, CellGridComponent& gr renderFurnitureEditor(grid); } + // Texture rectangle editor + if (ImGui::CollapsingHeader("Texture Rectangles")) { + renderTextureRectEditor(entity, grid); + } + // Script editor if (ImGui::CollapsingHeader("Generation Script")) { renderScriptEditor(grid); @@ -91,6 +98,7 @@ void CellGridEditor::renderCellEditor(CellGridComponent& grid) auto flags = CellGridComponent::getAllFlags(); for (const auto& [flag, name] : flags) { + ImGui::PushID((int)flag); bool hasFlag = cell->hasFlag(flag); if (ImGui::Checkbox(name, &hasFlag)) { if (hasFlag) { @@ -100,6 +108,7 @@ void CellGridEditor::renderCellEditor(CellGridComponent& grid) } grid.markDirty(); } + ImGui::PopID(); } if (ImGui::Button("Delete Cell")) { @@ -223,3 +232,123 @@ void CellGridEditor::renderScriptEditor(CellGridComponent& grid) grid.markDirty(); } } + + +void CellGridEditor::renderTextureRectEditor(flecs::entity entity, CellGridComponent& grid) +{ + // Find available texture rectangles from parent material + flecs::entity textureEntity = flecs::entity::null(); + flecs::entity parent = entity.parent(); + while (parent.is_alive()) { + if (parent.has()) { + auto& lot = parent.get(); + if (lot.proceduralMaterialEntity.is_alive() && + lot.proceduralMaterialEntity.has()) { + const auto& mat = lot.proceduralMaterialEntity.get(); + if (mat.diffuseTextureEntity.is_alive()) { + textureEntity = mat.diffuseTextureEntity; + break; + } + } + } + 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()) { + ImGui::TextDisabled("No ProceduralMaterial with texture found in parent hierarchy"); + return; + } + + const auto& texture = textureEntity.get(); + const auto& namedRects = texture.getAllNamedRects(); + + if (namedRects.empty()) { + ImGui::TextDisabled("No named rectangles defined in texture"); + return; + } + + // Build list of rect names + std::vector rectNames; + rectNames.push_back("(default)"); + for (const auto& pair : namedRects) { + rectNames.push_back(pair.first); + } + + auto renderRectCombo = [&](const char* label, std::string& currentValue) { + int currentIndex = 0; + for (size_t i = 1; i < rectNames.size(); ++i) { + if (currentValue == rectNames[i]) { + currentIndex = (int)i; + break; + } + } + + std::string comboItems; + for (size_t i = 0; i < rectNames.size(); ++i) { + if (i > 0) comboItems += '\0'; + comboItems += rectNames[i]; + } + comboItems += '\0'; + + int newIndex = currentIndex; + if (ImGui::Combo(label, &newIndex, comboItems.c_str())) { + if (newIndex == 0) { + currentValue.clear(); + } else { + currentValue = rectNames[newIndex]; + } + return true; + } + return false; + }; + + ImGui::Text("Select texture rectangles for each part:"); + ImGui::Indent(); + + if (renderRectCombo("Floor", grid.floorRectName)) { + grid.markDirty(); + } + if (renderRectCombo("Ceiling", grid.ceilingRectName)) { + grid.markDirty(); + } + if (renderRectCombo("External Walls", grid.extWallRectName)) { + grid.markDirty(); + } + if (renderRectCombo("Internal Walls", grid.intWallRectName)) { + grid.markDirty(); + } + if (renderRectCombo("Doors", grid.doorRectName)) { + grid.markDirty(); + } + if (renderRectCombo("Windows", grid.windowRectName)) { + grid.markDirty(); + } + if (renderRectCombo("Roof", grid.roofRectName)) { + grid.markDirty(); + } + + ImGui::Unindent(); +} diff --git a/src/features/editScene/ui/CellGridEditor.hpp b/src/features/editScene/ui/CellGridEditor.hpp index 872170d..5ab0125 100644 --- a/src/features/editScene/ui/CellGridEditor.hpp +++ b/src/features/editScene/ui/CellGridEditor.hpp @@ -13,6 +13,7 @@ private: void renderCellEditor(CellGridComponent& grid); void renderFurnitureEditor(CellGridComponent& grid); void renderScriptEditor(CellGridComponent& grid); + void renderTextureRectEditor(flecs::entity entity, CellGridComponent& grid); // State for UI int selectedCellX = 0, selectedCellY = 0, selectedCellZ = 0;