diff --git a/src/features/editScene/systems/CellGridSystem.cpp b/src/features/editScene/systems/CellGridSystem.cpp index b247b45..7121d87 100644 --- a/src/features/editScene/systems/CellGridSystem.cpp +++ b/src/features/editScene/systems/CellGridSystem.cpp @@ -23,13 +23,14 @@ #include #include -CellGridSystem::CellGridSystem(flecs::world& world, Ogre::SceneManager* sceneMgr) - : m_world(world) - , m_sceneMgr(sceneMgr) - , m_cellGridQuery(world.query()) - , m_townQuery(world.query()) - , m_districtQuery(world.query()) - , m_lotQuery(world.query()) +CellGridSystem::CellGridSystem(flecs::world &world, + Ogre::SceneManager *sceneMgr) + : m_world(world) + , m_sceneMgr(sceneMgr) + , m_cellGridQuery(world.query()) + , m_townQuery(world.query()) + , m_districtQuery(world.query()) + , m_lotQuery(world.query()) { } @@ -37,1380 +38,1879 @@ CellGridSystem::~CellGridSystem() = default; void CellGridSystem::initialize() { - if (m_initialized) return; - m_initialized = true; - Ogre::LogManager::getSingleton().logMessage("CellGridSystem initialized"); + if (m_initialized) + return; + m_initialized = true; + Ogre::LogManager::getSingleton().logMessage( + "CellGridSystem initialized"); } void CellGridSystem::update() { - if (!m_initialized) return; - - // Process dirty cell grids - m_cellGridQuery.each([&](flecs::entity entity, CellGridComponent& grid) { - if (grid.dirty) { - buildCellGrid(entity, grid); - grid.dirty = false; - } - }); - - // Update town materials - m_townQuery.each([&](flecs::entity entity, TownComponent& town) { - if (town.materialDirty) { - updateTownMaterial(entity); - town.materialDirty = false; - } - }); - - // Process dirty districts (for plaza generation) - int districtCount = 0; - int dirtyDistrictCount = 0; - m_districtQuery.each([&](flecs::entity entity, DistrictComponent& district) { - districtCount++; - if (district.dirty) { - dirtyDistrictCount++; - Ogre::LogManager::getSingleton().logMessage("CellGrid: Processing dirty district " + - std::to_string(entity.id()) + " isPlaza=" + (district.isPlaza ? "true" : "false")); - buildDistrictPlaza(entity, district); - district.dirty = false; - } - }); - - static int frameCount = 0; - if (++frameCount % 60 == 0) { - Ogre::LogManager::getSingleton().logMessage("CellGrid: 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; - } - }); + if (!m_initialized) + return; + + // Process dirty cell grids + m_cellGridQuery.each( + [&](flecs::entity entity, CellGridComponent &grid) { + if (grid.dirty) { + buildCellGrid(entity, grid); + grid.dirty = false; + } + }); + + // Update town materials + m_townQuery.each([&](flecs::entity entity, TownComponent &town) { + if (town.materialDirty) { + updateTownMaterial(entity); + town.materialDirty = false; + } + }); + + // Process dirty districts (for plaza generation) + int districtCount = 0; + int dirtyDistrictCount = 0; + m_districtQuery.each([&](flecs::entity entity, + DistrictComponent &district) { + districtCount++; + if (district.dirty) { + dirtyDistrictCount++; + Ogre::LogManager::getSingleton().logMessage( + "CellGrid: Processing dirty district " + + std::to_string(entity.id()) + " isPlaza=" + + (district.isPlaza ? "true" : "false")); + buildDistrictPlaza(entity, district); + district.dirty = false; + } + }); + + static int frameCount = 0; + if (++frameCount % 60 == 0) { + Ogre::LogManager::getSingleton().logMessage( + "CellGrid: 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) +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()) + " cellSize=" + std::to_string(grid.cellSize) + - " cellHeight=" + std::to_string(grid.cellHeight)); - - // Destroy existing meshes - destroyCellGridMeshes(entity); - - // 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 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(); - } - - Ogre::LogManager::getSingleton().logMessage("CellGrid: Using material '" + materialName + - "' for entity " + std::to_string(entity.id())); - - MeshData& meshData = m_entityMeshes[entity.id()]; - - // Build geometry parts - Procedural::TriangleBuffer floorTb, ceilingTb, extWallTb, intWallTb, intWindowsTb, roofTb; - std::vector doorTbs, windowTbs; - - buildFloorsAndCeilings(grid, floorTb, ceilingTb); - buildWalls(grid, extWallTb, intWallTb, intWindowsTb); - buildCorners(grid, extWallTb); - buildDoors(grid, doorTbs); - 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++); - - // Convert to meshes and create entities - if (!entity.has()) { - Ogre::LogManager::getSingleton().logMessage("CellGrid: No TransformComponent for entity " + std::to_string(entity.id())); - return; - } - auto& transform = entity.get_mut(); - if (!transform.node) { - Ogre::LogManager::getSingleton().logMessage("CellGrid: No SceneNode for entity " + std::to_string(entity.id())); - return; - } - - // Create meshes for each part - if (floorTb.getVertices().size() >= 3) { - meshData.floorMesh = baseName + "_floor"; - auto mesh = convertToMesh(meshData.floorMesh, floorTb, materialName); - auto entity3d = m_sceneMgr->createEntity(meshData.floorMesh); - transform.node->attachObject(entity3d); - meshData.entities.push_back(entity3d); - } - - if (ceilingTb.getVertices().size() >= 3) { - meshData.ceilingMesh = baseName + "_ceiling"; - auto mesh = convertToMesh(meshData.ceilingMesh, ceilingTb, materialName); - auto entity3d = m_sceneMgr->createEntity(meshData.ceilingMesh); - transform.node->attachObject(entity3d); - meshData.entities.push_back(entity3d); - } - - if (extWallTb.getVertices().size() >= 3) { - meshData.extWallMesh = baseName + "_extWall"; - auto mesh = convertToMesh(meshData.extWallMesh, extWallTb, materialName); - auto entity3d = m_sceneMgr->createEntity(meshData.extWallMesh); - transform.node->attachObject(entity3d); - meshData.entities.push_back(entity3d); - } - - if (intWallTb.getVertices().size() >= 3) { - meshData.intWallMesh = baseName + "_intWall"; - auto mesh = convertToMesh(meshData.intWallMesh, intWallTb, materialName); - auto entity3d = m_sceneMgr->createEntity(meshData.intWallMesh); - transform.node->attachObject(entity3d); - meshData.entities.push_back(entity3d); - } - - if (intWindowsTb.getVertices().size() >= 3) { - meshData.intWindowsMesh = baseName + "_intWindows"; - auto mesh = convertToMesh(meshData.intWindowsMesh, intWindowsTb, materialName); - auto entity3d = m_sceneMgr->createEntity(meshData.intWindowsMesh); - transform.node->attachObject(entity3d); - meshData.entities.push_back(entity3d); - } - - // Roof - if (roofTb.getVertices().size() >= 3) { - meshData.roofMesh = baseName + "_roof"; - auto mesh = convertToMesh(meshData.roofMesh, roofTb, materialName); - auto entity3d = m_sceneMgr->createEntity(meshData.roofMesh); - transform.node->attachObject(entity3d); - meshData.entities.push_back(entity3d); - } - - // 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())); + Ogre::LogManager::getSingleton().logMessage( + "CellGrid: buildCellGrid for entity " + + std::to_string(entity.id()) + + " 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 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 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< + ProceduralMaterialComponent>(); + 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< + ProceduralMaterialComponent>(); + 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< + ProceduralMaterialComponent>(); + 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(); + } + + Ogre::LogManager::getSingleton().logMessage( + "CellGrid: Using material '" + materialName + "' for entity " + + std::to_string(entity.id())); + + MeshData &meshData = m_entityMeshes[entity.id()]; + + // Build geometry parts + Procedural::TriangleBuffer floorTb, ceilingTb, extWallTb, intWallTb, + intWindowsTb, roofTb; + std::vector doorTbs, windowTbs; + + buildFloorsAndCeilings(grid, floorTb, ceilingTb); + buildWalls(grid, extWallTb, intWallTb, intWindowsTb); + buildCorners(grid, extWallTb); + buildInternalCorners(grid, intWallTb); + buildDoors(grid, doorTbs); + 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++); + + // Convert to meshes and create entities + if (!entity.has()) { + Ogre::LogManager::getSingleton().logMessage( + "CellGrid: No TransformComponent for entity " + + std::to_string(entity.id())); + return; + } + auto &transform = entity.get_mut(); + if (!transform.node) { + Ogre::LogManager::getSingleton().logMessage( + "CellGrid: No SceneNode for entity " + + std::to_string(entity.id())); + return; + } + + // Create meshes for each part + if (floorTb.getVertices().size() >= 3) { + meshData.floorMesh = baseName + "_floor"; + auto mesh = convertToMesh(meshData.floorMesh, floorTb, + materialName); + auto entity3d = m_sceneMgr->createEntity(meshData.floorMesh); + transform.node->attachObject(entity3d); + meshData.entities.push_back(entity3d); + } + + if (ceilingTb.getVertices().size() >= 3) { + meshData.ceilingMesh = baseName + "_ceiling"; + auto mesh = convertToMesh(meshData.ceilingMesh, ceilingTb, + materialName); + auto entity3d = m_sceneMgr->createEntity(meshData.ceilingMesh); + transform.node->attachObject(entity3d); + meshData.entities.push_back(entity3d); + } + + if (extWallTb.getVertices().size() >= 3) { + meshData.extWallMesh = baseName + "_extWall"; + auto mesh = convertToMesh(meshData.extWallMesh, extWallTb, + materialName); + auto entity3d = m_sceneMgr->createEntity(meshData.extWallMesh); + transform.node->attachObject(entity3d); + meshData.entities.push_back(entity3d); + } + + if (intWallTb.getVertices().size() >= 3) { + meshData.intWallMesh = baseName + "_intWall"; + auto mesh = convertToMesh(meshData.intWallMesh, intWallTb, + materialName); + auto entity3d = m_sceneMgr->createEntity(meshData.intWallMesh); + transform.node->attachObject(entity3d); + meshData.entities.push_back(entity3d); + } + + if (intWindowsTb.getVertices().size() >= 3) { + meshData.intWindowsMesh = baseName + "_intWindows"; + auto mesh = convertToMesh(meshData.intWindowsMesh, intWindowsTb, + materialName); + auto entity3d = + m_sceneMgr->createEntity(meshData.intWindowsMesh); + transform.node->attachObject(entity3d); + meshData.entities.push_back(entity3d); + } + + // Roof + if (roofTb.getVertices().size() >= 3) { + meshData.roofMesh = baseName + "_roof"; + auto mesh = + convertToMesh(meshData.roofMesh, roofTb, materialName); + auto entity3d = m_sceneMgr->createEntity(meshData.roofMesh); + transform.node->attachObject(entity3d); + meshData.entities.push_back(entity3d); + } + + // 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())); } // BitSet structure matching original town.cpp struct BitSet { - uint64_t bit; - float sizeX; - float sizeY; - Ogre::Vector3 normal; - Ogre::Vector3 offset; - Procedural::TriangleBuffer* tb; + 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); +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) +void CellGridSystem::buildFloorsAndCeilings( + const CellGridComponent &grid, Procedural::TriangleBuffer &floorTb, + Procedural::TriangleBuffer &ceilingTb) { - // 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; + // 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; + // Floor Y slightly below internal wall offset to avoid gap + // Internal wall offset is 0.1f, floor sits slightly below + const float floorY = 0.2f; + const float ceilingY = grid.cellHeight - 0.3f; - for (const auto& cell : grid.cells) { - Ogre::Vector3 origin = grid.cellToWorld(cell.x, cell.y, cell.z); - uint64_t flags = cell.flags; + for (const auto &cell : grid.cells) { + Ogre::Vector3 origin = grid.cellToWorld(cell.x, cell.y, cell.z); + 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); - } + // 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: 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); - } - } + // 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); + } + } } -void CellGridSystem::buildWalls(const CellGridComponent& grid, - Procedural::TriangleBuffer& extWallTb, - Procedural::TriangleBuffer& intWallTb, - Procedural::TriangleBuffer& intWindowsTb) +void CellGridSystem::buildWalls(const CellGridComponent &grid, + Procedural::TriangleBuffer &extWallTb, + Procedural::TriangleBuffer &intWallTb, + Procedural::TriangleBuffer &intWindowsTb) { - // 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 * 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; - - for (const auto& bit : bits_solid) { - if ((flags & bit.bit) == bit.bit) { - genPlane(bit.sizeX, bit.sizeY, bit.normal, bit.offset, *bit.tb, origin); - } - } - } + // 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 * hScale; + const float solidIntHeight = grid.cellHeight - 0.3f - solidIntOffset; + + // Tables matching original town.cpp + // hScale = cellSize / 2.0f (for horizontal dimensions) + // cornerWidth = 0.2f (fixed, not scaled) + // Internal wall offset = 0.1f (fixed, matching original) + const float intWallOffset = 0.1f; + 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 * hScale - 0.2f), offset=+/-(hScale - 0.1f) + { CellFlags::IntWallXNeg, + solidIntHeight, + 2.0f * hScale - cornerWidth, + Ogre::Vector3::UNIT_X, + { -1.0f * hScale + intWallOffset, + solidIntHeight / 2.0f + solidIntOffset, 0 }, + &intWallTb }, + { CellFlags::IntWallXPos, + solidIntHeight, + 2.0f * hScale - cornerWidth, + Ogre::Vector3::NEGATIVE_UNIT_X, + { 1.0f * hScale - intWallOffset, + solidIntHeight / 2.0f + solidIntOffset, 0 }, + &intWallTb }, + { CellFlags::IntWallZPos, + solidIntHeight, + 2.0f * hScale - cornerWidth, + Ogre::Vector3::NEGATIVE_UNIT_Z, + { 0, solidIntHeight / 2.0f + solidIntOffset, + 1.0f * hScale - intWallOffset }, + &intWallTb }, + { CellFlags::IntWallZNeg, + solidIntHeight, + 2.0f * hScale - cornerWidth, + Ogre::Vector3::UNIT_Z, + { 0, solidIntHeight / 2.0f + solidIntOffset, + -1.0f * hScale + intWallOffset }, + &intWallTb }, + }; + + for (const auto &cell : grid.cells) { + Ogre::Vector3 origin = grid.cellToWorld(cell.x, cell.y, cell.z); + uint64_t flags = cell.flags; + + 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) +void CellGridSystem::buildCorners(const CellGridComponent &grid, + Procedural::TriangleBuffer &extWallTb) { - // 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; - - 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); - } - } - } + // 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; + + 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) +void CellGridSystem::buildInternalCorners(const CellGridComponent &grid, + Procedural::TriangleBuffer &intWallTb) { - 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)); - } - } - } + // Build internal wall corners where internal walls meet + // 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; + const float cornerWidth = 0.2f; // Fixed corner width + const float solidIntOffset = 0.1f; // Fixed offset matching floor + const float solidIntHeight = grid.cellHeight - 0.3f - solidIntOffset; + + // Internal wall width = 2.0f * hScale - 0.2f = full width - corner offset on both sides + const float intWallWidth = 2.0f * hScale - cornerWidth; + + // bits_int_corners1 - corners extending in positive direction from each wall + // Format: bit, sizeX (height), sizeY (corner width), normal, offset + // Normals flipped to match internal wall normals + std::vector bits_int_corners1 = { + // IntWallXNeg (flag 14): corner at Z+ side + { CellFlags::IntWallXNeg, + solidIntHeight, + cornerWidth, + Ogre::Vector3::UNIT_X, + { -halfCell + cornerWidth / 2, + solidIntHeight / 2.0f + solidIntOffset, + (intWallWidth + cornerWidth) / 2.0f }, + &intWallTb }, + // IntWallXPos (flag 15): corner at Z+ side + { CellFlags::IntWallXPos, + solidIntHeight, + cornerWidth, + Ogre::Vector3::NEGATIVE_UNIT_X, + { halfCell - cornerWidth / 2, + solidIntHeight / 2.0f + solidIntOffset, + (intWallWidth + cornerWidth) / 2.0f }, + &intWallTb }, + // IntWallZPos (flag 16): corner at X+ side + { CellFlags::IntWallZPos, + solidIntHeight, + cornerWidth, + Ogre::Vector3::NEGATIVE_UNIT_Z, + { (intWallWidth + cornerWidth) / 2.0f, + solidIntHeight / 2.0f + solidIntOffset, + halfCell - cornerWidth / 2 }, + &intWallTb }, + // IntWallZNeg (flag 17): corner at X+ side + { CellFlags::IntWallZNeg, + solidIntHeight, + cornerWidth, + Ogre::Vector3::UNIT_Z, + { (intWallWidth + cornerWidth) / 2.0f, + solidIntHeight / 2.0f + solidIntOffset, + -halfCell + cornerWidth / 2 }, + &intWallTb }, + }; + + // bits_int_corners2 - corners extending in negative direction from each wall + std::vector bits_int_corners2 = { + // IntWallXNeg (flag 14): corner at Z- side + { CellFlags::IntWallXNeg, + solidIntHeight, + cornerWidth, + Ogre::Vector3::UNIT_X, + { -halfCell + cornerWidth / 2, + solidIntHeight / 2.0f + solidIntOffset, + -(intWallWidth + cornerWidth) / 2.0f }, + &intWallTb }, + // IntWallXPos (flag 15): corner at Z- side + { CellFlags::IntWallXPos, + solidIntHeight, + cornerWidth, + Ogre::Vector3::NEGATIVE_UNIT_X, + { halfCell - cornerWidth / 2, + solidIntHeight / 2.0f + solidIntOffset, + -(intWallWidth + cornerWidth) / 2.0f }, + &intWallTb }, + // IntWallZPos (flag 16): corner at X- side + { CellFlags::IntWallZPos, + solidIntHeight, + cornerWidth, + Ogre::Vector3::NEGATIVE_UNIT_Z, + { -(intWallWidth + cornerWidth) / 2.0f, + solidIntHeight / 2.0f + solidIntOffset, + halfCell - cornerWidth / 2 }, + &intWallTb }, + // IntWallZNeg (flag 17): corner at X- side + { CellFlags::IntWallZNeg, + solidIntHeight, + cornerWidth, + Ogre::Vector3::UNIT_Z, + { -(intWallWidth + cornerWidth) / 2.0f, + solidIntHeight / 2.0f + solidIntOffset, + -halfCell + cornerWidth / 2 }, + &intWallTb }, + }; + + for (const auto &cell : grid.cells) { + Ogre::Vector3 origin = grid.cellToWorld(cell.x, cell.y, cell.z); + uint64_t intWallFlags = cell.flags & + CellFlags::AllInternalWalls; + + // Process bits_int_corners1 with conflict resolution + // From original: corners_flags2 logic skips certain combinations + for (const auto &bit : bits_int_corners1) { + if ((intWallFlags & bit.bit) == bit.bit) { + // Conflict resolution from original town.cpp lines 3111-3122 + // Skip if there's an adjacent wall that would cause overlap + bool skip = false; + if (bit.bit == CellFlags::IntWallXNeg && + (intWallFlags & CellFlags::IntWallZPos)) + skip = true; + if (bit.bit == CellFlags::IntWallXPos && + (intWallFlags & CellFlags::IntWallZPos)) + skip = true; + if (bit.bit == CellFlags::IntWallZPos && + (intWallFlags & CellFlags::IntWallXPos)) + skip = true; + if (bit.bit == CellFlags::IntWallZNeg && + (intWallFlags & CellFlags::IntWallXPos)) + skip = true; + + if (!skip) { + genPlane(bit.sizeX, bit.sizeY, + bit.normal, bit.offset, + *bit.tb, origin); + } + } + } + + // Process bits_int_corners2 with conflict resolution + // From original town.cpp lines 3129-3140 + for (const auto &bit : bits_int_corners2) { + if ((intWallFlags & bit.bit) == bit.bit) { + // Conflict resolution + bool skip = false; + if (bit.bit == CellFlags::IntWallXNeg && + (intWallFlags & CellFlags::IntWallZNeg)) + skip = true; + if (bit.bit == CellFlags::IntWallXPos && + (intWallFlags & CellFlags::IntWallZNeg)) + skip = true; + if (bit.bit == CellFlags::IntWallZPos && + (intWallFlags & CellFlags::IntWallXNeg)) + skip = true; + if (bit.bit == CellFlags::IntWallZNeg && + (intWallFlags & CellFlags::IntWallXNeg)) + skip = true; + + if (!skip) { + genPlane(bit.sizeX, bit.sizeY, + bit.normal, bit.offset, + *bit.tb, origin); + } + } + } + } } -void CellGridSystem::buildWindows(const CellGridComponent& grid, std::vector& windowTbs) +void CellGridSystem::buildDoors(const CellGridComponent &grid, + std::vector &doorTbs) { - 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)); - } - } - } + 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::buildRoofs(flecs::entity lotEntity, const CellGridComponent& grid, Procedural::TriangleBuffer& roofTb) +void CellGridSystem::buildWindows( + const CellGridComponent &grid, + std::vector &windowTbs) { - if (!lotEntity.has()) return; - - // Get roof components from lot's children - lotEntity.children([&](flecs::entity child) { - if (child.has()) { - auto& roof = child.get(); - Ogre::Vector3 origin = grid.cellToWorld(roof.posX, roof.posY, roof.posZ); - origin += Ogre::Vector3(roof.offsetX, roof.offsetY, roof.offsetZ); - - float width = roof.sizeX * grid.cellSize; - float depth = roof.sizeZ * grid.cellSize; - float height = roof.maxHeight - roof.baseHeight; - - switch (roof.type) { - case RoofComponent::Flat: { - Procedural::BoxGenerator box; - box.setSizeX(width); - box.setSizeY(roof.baseHeight); - box.setSizeZ(depth); - box.setPosition(Ogre::Vector3(origin.x + width/2.0f - grid.cellSize/2.0f, - origin.y + roof.baseHeight/2.0f, - origin.z + depth/2.0f - grid.cellSize/2.0f)); - box.addToTriangleBuffer(roofTb); - break; - } - case RoofComponent::Normal: - case RoofComponent::Normal2: { - // Gable roof using two rotated boxes or a prism - // Simplified as a box for now - Procedural::BoxGenerator box; - box.setSizeX(width); - box.setSizeY(roof.baseHeight + height/2.0f); - box.setSizeZ(depth); - box.setPosition(Ogre::Vector3(origin.x + width/2.0f - grid.cellSize/2.0f, - origin.y + (roof.baseHeight + height/2.0f)/2.0f, - origin.z + depth/2.0f - grid.cellSize/2.0f)); - box.addToTriangleBuffer(roofTb); - break; - } - case RoofComponent::Cone: { - Procedural::ConeGenerator cone; - cone.setRadius(width/2.0f); - cone.setHeight(roof.baseHeight + height); - cone.setPosition(Ogre::Vector3(origin.x, origin.y, origin.z)); - cone.addToTriangleBuffer(roofTb); - break; - } - case RoofComponent::Cylinder: { - Procedural::CylinderGenerator cyl; - cyl.setRadius(width/2.0f); - cyl.setHeight(roof.baseHeight); - cyl.setPosition(Ogre::Vector3(origin.x, origin.y + roof.baseHeight/2.0f, origin.z)); - cyl.addToTriangleBuffer(roofTb); - break; - } - } - } - }); + 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)); + } + } + } } -Ogre::MeshPtr CellGridSystem::convertToMesh(const std::string& name, Procedural::TriangleBuffer& tb, const std::string& materialName) +void CellGridSystem::buildRoofs(flecs::entity lotEntity, + const CellGridComponent &grid, + Procedural::TriangleBuffer &roofTb) { - 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) { - Ogre::LogManager::getSingleton().logMessage("CellGrid: Failed to create mesh '" + name + "': " + e.getDescription()); - return Ogre::MeshPtr(); - } + if (!lotEntity.has()) + return; + + // Get roof components from lot's children + lotEntity.children([&](flecs::entity child) { + if (child.has()) { + auto &roof = child.get(); + Ogre::Vector3 origin = grid.cellToWorld( + roof.posX, roof.posY, roof.posZ); + origin += Ogre::Vector3(roof.offsetX, roof.offsetY, + roof.offsetZ); + + float width = roof.sizeX * grid.cellSize; + float depth = roof.sizeZ * grid.cellSize; + float height = roof.maxHeight - roof.baseHeight; + + switch (roof.type) { + case RoofComponent::Flat: { + Procedural::BoxGenerator box; + box.setSizeX(width); + box.setSizeY(roof.baseHeight); + box.setSizeZ(depth); + box.setPosition(Ogre::Vector3( + origin.x + width / 2.0f - + grid.cellSize / 2.0f, + origin.y + roof.baseHeight / 2.0f, + origin.z + depth / 2.0f - + grid.cellSize / 2.0f)); + box.addToTriangleBuffer(roofTb); + break; + } + case RoofComponent::Normal: + case RoofComponent::Normal2: { + // Gable roof using two rotated boxes or a prism + // Simplified as a box for now + Procedural::BoxGenerator box; + box.setSizeX(width); + box.setSizeY(roof.baseHeight + height / 2.0f); + box.setSizeZ(depth); + box.setPosition(Ogre::Vector3( + origin.x + width / 2.0f - + grid.cellSize / 2.0f, + origin.y + (roof.baseHeight + + height / 2.0f) / + 2.0f, + origin.z + depth / 2.0f - + grid.cellSize / 2.0f)); + box.addToTriangleBuffer(roofTb); + break; + } + case RoofComponent::Cone: { + Procedural::ConeGenerator cone; + cone.setRadius(width / 2.0f); + cone.setHeight(roof.baseHeight + height); + cone.setPosition(Ogre::Vector3( + origin.x, origin.y, origin.z)); + cone.addToTriangleBuffer(roofTb); + break; + } + case RoofComponent::Cylinder: { + Procedural::CylinderGenerator cyl; + cyl.setRadius(width / 2.0f); + cyl.setHeight(roof.baseHeight); + cyl.setPosition(Ogre::Vector3( + origin.x, + origin.y + roof.baseHeight / 2.0f, + origin.z)); + cyl.addToTriangleBuffer(roofTb); + break; + } + } + } + }); } -void CellGridSystem::destroyCellGridMeshes(CellGridComponent& grid) +Ogre::MeshPtr CellGridSystem::convertToMesh(const std::string &name, + Procedural::TriangleBuffer &tb, + const std::string &materialName) { - // This is called from buildCellGrid which uses the entity ID as key in m_entityMeshes - // The actual cleanup needs to happen there + 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) { + Ogre::LogManager::getSingleton().logMessage( + "CellGrid: Failed to create mesh '" + name + + "': " + e.getDescription()); + return Ogre::MeshPtr(); + } +} + +void CellGridSystem::destroyCellGridMeshes(CellGridComponent &grid) +{ + // 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); - } - - for (const auto& name : it->second.doorMeshes) { - removeMesh(name); - } - for (const auto& name : it->second.windowMeshes) { - removeMesh(name); - } - - m_entityMeshes.erase(it); - } + 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); + } + + 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) { - if (!townEntity.has()) return; - - auto& town = townEntity.get_mut(); - if (town.materialName.empty()) { - town.materialName = "TownMaterial_" + std::to_string(townEntity.id()); - } - - // Create or update material - auto& matMgr = Ogre::MaterialManager::getSingleton(); - Ogre::MaterialPtr mat; - - if (matMgr.resourceExists(town.materialName, Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME)) { - mat = matMgr.getByName(town.materialName); - mat->unload(); - } else { - mat = matMgr.create(town.materialName, Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); - } - - // For now, create a simple colored material - // Full implementation would create texture atlas from color rects - auto pass = mat->getTechnique(0)->getPass(0); - pass->setDiffuse(Ogre::ColourValue(0.8f, 0.8f, 0.8f)); - pass->setAmbient(Ogre::ColourValue(0.4f, 0.4f, 0.4f)); - pass->setSpecular(Ogre::ColourValue(0.1f, 0.1f, 0.1f)); - - Ogre::LogManager::getSingleton().logMessage("CellGrid: Updated town material '" + town.materialName + "'"); + if (!townEntity.has()) + return; + + auto &town = townEntity.get_mut(); + if (town.materialName.empty()) { + town.materialName = + "TownMaterial_" + std::to_string(townEntity.id()); + } + + // Create or update material + auto &matMgr = Ogre::MaterialManager::getSingleton(); + Ogre::MaterialPtr mat; + + if (matMgr.resourceExists( + town.materialName, + Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME)) { + mat = matMgr.getByName(town.materialName); + mat->unload(); + } else { + mat = matMgr.create( + town.materialName, + Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); + } + + // For now, create a simple colored material + // Full implementation would create texture atlas from color rects + auto pass = mat->getTechnique(0)->getPass(0); + pass->setDiffuse(Ogre::ColourValue(0.8f, 0.8f, 0.8f)); + pass->setAmbient(Ogre::ColourValue(0.4f, 0.4f, 0.4f)); + pass->setSpecular(Ogre::ColourValue(0.1f, 0.1f, 0.1f)); + + Ogre::LogManager::getSingleton().logMessage( + "CellGrid: Updated town material '" + town.materialName + "'"); } void CellGridSystem::rebuildCellGrid(flecs::entity entity) { - if (!entity.is_alive() || !entity.has()) return; - entity.get_mut().markDirty(); + if (!entity.is_alive() || !entity.has()) + return; + entity.get_mut().markDirty(); } -void CellGridSystem::buildDistrictPlaza(flecs::entity entity, DistrictComponent& district) +void CellGridSystem::buildDistrictPlaza(flecs::entity entity, + DistrictComponent &district) { - // Destroy existing plaza if any - auto it = m_plazaMeshes.find(entity.id()); - if (it != m_plazaMeshes.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_plazaMeshes.erase(it); - } - - if (!district.isPlaza) { - return; - } - - // Get transform from entity - if (!entity.has()) { - Ogre::LogManager::getSingleton().logMessage("CellGrid: District has no TransformComponent"); - return; - } - auto& transform = entity.get_mut(); - if (!transform.node) { - Ogre::LogManager::getSingleton().logMessage("CellGrid: District has no SceneNode"); - return; - } - - // Get material from district's procedural material entity - std::string materialName = "Ogre/StandardFloor"; // fallback - - if (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: Building plaza for district " + std::to_string(entity.id()) + - " with material: " + materialName); - - // Create plaza mesh using Lathe - Procedural::TriangleBuffer tb; - Procedural::Shape plazaShape; - - float radius = district.radius; - float height = district.height; - float elevation = district.elevation; - float mh = 4.0f; // margin height - - // Build the plaza shape profile (based on original code) - plazaShape.addPoint(0, -height - mh - mh); - plazaShape.addPoint(radius * 0.5f + mh, -height - mh - mh); - plazaShape.addPoint(radius + mh, -height - mh); - plazaShape.addPoint(radius, -height); - plazaShape.addPoint(radius, 0.0f); - plazaShape.addPoint(radius - 0.1f, 0.1f); - plazaShape.addPoint(radius - mh + 0.1f, height); - plazaShape.addPoint(radius - mh, height + 0.1f); - plazaShape.addPoint(radius * 0.5f + mh, height); - plazaShape.addPoint(0, height); - - // Generate lathe geometry - Procedural::Lathe() - .setShapeToExtrude(&plazaShape) - .setEnableNormals(true) - .setPosition(Ogre::Vector3(0.0f, height / 2.0f + elevation, 0.0f)) - .setNumSeg(24) - .addToTriangleBuffer(tb); - - // Check if we have valid geometry - size_t vertexCount = tb.getVertices().size(); - size_t indexCount = tb.getIndices().size(); - Ogre::LogManager::getSingleton().logMessage("CellGrid: Plaza triangle buffer has " + - std::to_string(vertexCount) + " vertices, " + std::to_string(indexCount) + " indices"); - - if (vertexCount < 3) { - Ogre::LogManager::getSingleton().logMessage("CellGrid: Not enough vertices for plaza mesh"); - return; - } - - // Apply UV mapping if texture rect is specified - if (!district.textureRectName.empty()) { - // Find the texture entity from the material - flecs::entity textureEntity = flecs::entity::null(); - if (district.proceduralMaterialEntity.is_alive() && - district.proceduralMaterialEntity.has()) { - const auto& mat = district.proceduralMaterialEntity.get(); - // Search for the texture that is referenced by this material - if (mat.diffuseTextureEntity.is_alive()) { - textureEntity = mat.diffuseTextureEntity; - } - } - - if (textureEntity.is_alive() && textureEntity.has()) { - const auto& texture = textureEntity.get(); - const TextureRectInfo* rect = texture.getNamedRect(district.textureRectName); - if (rect) { - // Apply UV mapping with 0.01 margin - 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; - - auto& vertices = tb.getVertices(); - for (auto& vertex : vertices) { - vertex.mUV.x = uOffset + vertex.mUV.x * uRange; - vertex.mUV.y = vOffset + vertex.mUV.y * vRange; - } - - Ogre::LogManager::getSingleton().logMessage("CellGrid: Applied UV mapping for rect '" + - district.textureRectName + "'"); - } - } - } - - // Generate mesh name - std::string meshName = "Plaza_" + std::to_string(entity.id()) + "_" + std::to_string(m_meshCount++); - - // Create mesh and entity - try { - Ogre::MeshPtr mesh = tb.transformToMesh(meshName); - - if (mesh.isNull()) { - Ogre::LogManager::getSingleton().logMessage("CellGrid: transformToMesh returned null"); - return; - } - - Ogre::LogManager::getSingleton().logMessage("CellGrid: Created mesh '" + meshName + - "' with " + std::to_string(mesh->getNumSubMeshes()) + " submeshes"); - - if (!materialName.empty() && mesh->getNumSubMeshes() > 0) { - mesh->getSubMesh(0)->setMaterialName(materialName); - Ogre::LogManager::getSingleton().logMessage("CellGrid: Set material to '" + materialName + "'"); - } - - Ogre::Entity* plazzaEntity = m_sceneMgr->createEntity(meshName); - if (!plazzaEntity) { - Ogre::LogManager::getSingleton().logMessage("CellGrid: createEntity returned null"); - return; - } - - transform.node->attachObject(plazzaEntity); - - // Force visibility - plazzaEntity->setVisible(true); - - // Store for cleanup - PlazaData data; - data.meshName = meshName; - data.entity = plazzaEntity; - m_plazaMeshes[entity.id()] = data; - - Ogre::LogManager::getSingleton().logMessage("CellGrid: Created plaza mesh for district " + std::to_string(entity.id()) + - " attached to node '" + transform.node->getName() + "'"); - - } catch (const Ogre::Exception& e) { - Ogre::LogManager::getSingleton().logMessage("CellGrid: Failed to create plaza mesh: " + e.getDescription()); - } + // Destroy existing plaza if any + auto it = m_plazaMeshes.find(entity.id()); + if (it != m_plazaMeshes.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_plazaMeshes.erase(it); + } + + if (!district.isPlaza) { + return; + } + + // Get transform from entity + if (!entity.has()) { + Ogre::LogManager::getSingleton().logMessage( + "CellGrid: District has no TransformComponent"); + return; + } + auto &transform = entity.get_mut(); + if (!transform.node) { + Ogre::LogManager::getSingleton().logMessage( + "CellGrid: District has no SceneNode"); + return; + } + + // Get material from district's procedural material entity + std::string materialName = "Ogre/StandardFloor"; // fallback + + if (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: Building plaza for district " + + std::to_string(entity.id()) + + " with material: " + materialName); + + // Create plaza mesh using Lathe + Procedural::TriangleBuffer tb; + Procedural::Shape plazaShape; + + float radius = district.radius; + float height = district.height; + float elevation = district.elevation; + float mh = 4.0f; // margin height + + // Build the plaza shape profile (based on original code) + plazaShape.addPoint(0, -height - mh - mh); + plazaShape.addPoint(radius * 0.5f + mh, -height - mh - mh); + plazaShape.addPoint(radius + mh, -height - mh); + plazaShape.addPoint(radius, -height); + plazaShape.addPoint(radius, 0.0f); + plazaShape.addPoint(radius - 0.1f, 0.1f); + plazaShape.addPoint(radius - mh + 0.1f, height); + plazaShape.addPoint(radius - mh, height + 0.1f); + plazaShape.addPoint(radius * 0.5f + mh, height); + plazaShape.addPoint(0, height); + + // Generate lathe geometry + Procedural::Lathe() + .setShapeToExtrude(&plazaShape) + .setEnableNormals(true) + .setPosition( + Ogre::Vector3(0.0f, height / 2.0f + elevation, 0.0f)) + .setNumSeg(24) + .addToTriangleBuffer(tb); + + // Check if we have valid geometry + size_t vertexCount = tb.getVertices().size(); + size_t indexCount = tb.getIndices().size(); + Ogre::LogManager::getSingleton().logMessage( + "CellGrid: Plaza triangle buffer has " + + std::to_string(vertexCount) + " vertices, " + + std::to_string(indexCount) + " indices"); + + if (vertexCount < 3) { + Ogre::LogManager::getSingleton().logMessage( + "CellGrid: Not enough vertices for plaza mesh"); + return; + } + + // Apply UV mapping if texture rect is specified + if (!district.textureRectName.empty()) { + // Find the texture entity from the material + flecs::entity textureEntity = flecs::entity::null(); + if (district.proceduralMaterialEntity.is_alive() && + district.proceduralMaterialEntity + .has()) { + const auto &mat = + district.proceduralMaterialEntity + .get(); + // Search for the texture that is referenced by this material + if (mat.diffuseTextureEntity.is_alive()) { + textureEntity = mat.diffuseTextureEntity; + } + } + + if (textureEntity.is_alive() && + textureEntity.has()) { + const auto &texture = + textureEntity.get(); + const TextureRectInfo *rect = + texture.getNamedRect(district.textureRectName); + if (rect) { + // Apply UV mapping with 0.01 margin + 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; + + auto &vertices = tb.getVertices(); + for (auto &vertex : vertices) { + vertex.mUV.x = + uOffset + vertex.mUV.x * uRange; + vertex.mUV.y = + vOffset + vertex.mUV.y * vRange; + } + + Ogre::LogManager::getSingleton().logMessage( + "CellGrid: Applied UV mapping for rect '" + + district.textureRectName + "'"); + } + } + } + + // Generate mesh name + std::string meshName = "Plaza_" + std::to_string(entity.id()) + "_" + + std::to_string(m_meshCount++); + + // Create mesh and entity + try { + Ogre::MeshPtr mesh = tb.transformToMesh(meshName); + + if (mesh.isNull()) { + Ogre::LogManager::getSingleton().logMessage( + "CellGrid: transformToMesh returned null"); + return; + } + + Ogre::LogManager::getSingleton().logMessage( + "CellGrid: Created mesh '" + meshName + "' with " + + std::to_string(mesh->getNumSubMeshes()) + " submeshes"); + + if (!materialName.empty() && mesh->getNumSubMeshes() > 0) { + mesh->getSubMesh(0)->setMaterialName(materialName); + Ogre::LogManager::getSingleton().logMessage( + "CellGrid: Set material to '" + materialName + + "'"); + } + + Ogre::Entity *plazzaEntity = m_sceneMgr->createEntity(meshName); + if (!plazzaEntity) { + Ogre::LogManager::getSingleton().logMessage( + "CellGrid: createEntity returned null"); + return; + } + + transform.node->attachObject(plazzaEntity); + + // Force visibility + plazzaEntity->setVisible(true); + + // Store for cleanup + PlazaData data; + data.meshName = meshName; + data.entity = plazzaEntity; + m_plazaMeshes[entity.id()] = data; + + Ogre::LogManager::getSingleton().logMessage( + "CellGrid: Created plaza mesh for district " + + std::to_string(entity.id()) + " attached to node '" + + transform.node->getName() + "'"); + + } catch (const Ogre::Exception &e) { + Ogre::LogManager::getSingleton().logMessage( + "CellGrid: Failed to create plaza mesh: " + + e.getDescription()); + } } - -void CellGridSystem::buildLotBase(flecs::entity entity, LotComponent& lot) +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()); - } + // 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< + ProceduralMaterialComponent>(); + 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< + ProceduralMaterialComponent>()) { + const auto &mat = + town.proceduralMaterialEntity.get< + ProceduralMaterialComponent>(); + 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< + ProceduralMaterialComponent>()) { + 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< + ProceduralMaterialComponent>()) { + 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()); + } } - -void CellGridSystem::applyUVMappingToBuffer(Procedural::TriangleBuffer& tb, const std::string& rectName, flecs::entity materialEntity) +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; - } + 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 ba23a22..b542208 100644 --- a/src/features/editScene/systems/CellGridSystem.hpp +++ b/src/features/editScene/systems/CellGridSystem.hpp @@ -46,6 +46,7 @@ private: void buildDoors(const struct CellGridComponent& grid, std::vector& doorTbs); void buildWindows(const struct CellGridComponent& grid, std::vector& windowTbs); void buildCorners(const struct CellGridComponent& grid, Procedural::TriangleBuffer& extWallTb); + void buildInternalCorners(const struct CellGridComponent& grid, Procedural::TriangleBuffer& intWallTb); // Build roofs void buildRoofs(flecs::entity lotEntity, const struct CellGridComponent& grid, Procedural::TriangleBuffer& roofTb);