From 19e4d80741f6a49ab2461c85d5da15512a3cc06d Mon Sep 17 00:00:00 2001 From: Sergey Lapin Date: Mon, 6 Apr 2026 19:31:10 +0300 Subject: [PATCH] Proper walls geometry with frames --- src/features/editScene/EditorApp.hpp | 1 + .../editScene/components/CellGrid.hpp | 12 + src/features/editScene/main.cpp | 14 +- .../editScene/systems/CellGridSystem.cpp | 1032 ++++++++++++++--- .../editScene/systems/CellGridSystem.hpp | 19 + 5 files changed, 882 insertions(+), 196 deletions(-) diff --git a/src/features/editScene/EditorApp.hpp b/src/features/editScene/EditorApp.hpp index 583507a..e957765 100644 --- a/src/features/editScene/EditorApp.hpp +++ b/src/features/editScene/EditorApp.hpp @@ -72,6 +72,7 @@ public: // Getters flecs::entity getSelectedEntity() const; Ogre::SceneManager *getSceneManager() const { return m_sceneMgr; } + flecs::world *getWorld() { return &m_world; } private: // Ogre objects diff --git a/src/features/editScene/components/CellGrid.hpp b/src/features/editScene/components/CellGrid.hpp index dbd94e0..8e1eebc 100644 --- a/src/features/editScene/components/CellGrid.hpp +++ b/src/features/editScene/components/CellGrid.hpp @@ -59,6 +59,16 @@ namespace CellFlags { IntDoorXNeg | IntDoorXPos | IntDoorZPos | IntDoorZNeg; constexpr uint64_t AllWindows = WindowXNeg | WindowXPos | WindowZPos | WindowZNeg | IntWindowXNeg | IntWindowXPos | IntWindowZPos | IntWindowZNeg; + + // Combined masks for corners (walls + doors + windows in each direction) + constexpr uint64_t AllXNeg = WallXNeg | DoorXNeg | WindowXNeg; + constexpr uint64_t AllXPos = WallXPos | DoorXPos | WindowXPos; + constexpr uint64_t AllZPos = WallZPos | DoorZPos | WindowZPos; + constexpr uint64_t AllZNeg = WallZNeg | DoorZNeg | WindowZNeg; + constexpr uint64_t AllIntXNeg = IntWallXNeg | IntDoorXNeg | IntWindowXNeg; + constexpr uint64_t AllIntXPos = IntWallXPos | IntDoorXPos | IntWindowXPos; + constexpr uint64_t AllIntZPos = IntWallZPos | IntDoorZPos | IntWindowZPos; + constexpr uint64_t AllIntZNeg = IntWallZNeg | IntDoorZNeg | IntWindowZNeg; } /** @@ -127,6 +137,8 @@ struct CellGridComponent { std::string doorRectName; std::string windowRectName; std::string roofRectName; + std::string windowFrameRectName; + std::string doorFrameRectName; // Dirty flag - triggers rebuild bool dirty = true; diff --git a/src/features/editScene/main.cpp b/src/features/editScene/main.cpp index 47dc080..8df4d5b 100644 --- a/src/features/editScene/main.cpp +++ b/src/features/editScene/main.cpp @@ -1,14 +1,22 @@ #include #include "EditorApp.hpp" +#include "systems/SceneSerializer.hpp" int main(int argc, char *argv[]) { - (void)argc; - (void)argv; - try { EditorApp app; app.initApp(); + + // Auto-load scene if provided as argument + if (argc > 1) { + std::cout << "Auto-loading scene: " << argv[1] << std::endl; + SceneSerializer serializer(*app.getWorld(), app.getSceneManager()); + if (!serializer.loadFromFile(argv[1])) { + std::cerr << "Failed to load scene: " << serializer.getLastError() << std::endl; + } + } + app.getRoot()->startRendering(); app.closeApp(); } catch (const std::exception &e) { diff --git a/src/features/editScene/systems/CellGridSystem.cpp b/src/features/editScene/systems/CellGridSystem.cpp index 7121d87..7517101 100644 --- a/src/features/editScene/systems/CellGridSystem.cpp +++ b/src/features/editScene/systems/CellGridSystem.cpp @@ -336,6 +336,17 @@ void CellGridSystem::buildCellGrid(flecs::entity entity, " doors=" + std::to_string(doorTbs.size()) + " windows=" + std::to_string(windowTbs.size()) + " roof=" + std::to_string(roofTb.getVertices().size())); + + // Build window and door frames + try { + buildFrames(entity, grid, materialName); + } catch (const std::exception& e) { + Ogre::LogManager::getSingleton().logMessage( + "CellGrid: Error building frames: " + std::string(e.what())); + } catch (...) { + Ogre::LogManager::getSingleton().logMessage( + "CellGrid: Unknown error building frames"); + } } // BitSet structure matching original town.cpp @@ -590,11 +601,24 @@ void CellGridSystem::buildCorners(const CellGridComponent &grid, for (const auto &cell : grid.cells) { Ogre::Vector3 origin = grid.cellToWorld(cell.x, cell.y, cell.z); - uint64_t wallFlags = cell.flags & CellFlags::AllExternalWalls; + // Use combined masks that include walls, doors, and windows + uint64_t xNegFlags = cell.flags & CellFlags::AllXNeg; + uint64_t xPosFlags = cell.flags & CellFlags::AllXPos; + uint64_t zNegFlags = cell.flags & CellFlags::AllZNeg; + uint64_t zPosFlags = cell.flags & CellFlags::AllZPos; 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) { + // Check for corner based on which features are present + bool hasXNeg = (bit.bit & CellFlags::AllXNeg) && xNegFlags; + bool hasXPos = (bit.bit & CellFlags::AllXPos) && xPosFlags; + bool hasZNeg = (bit.bit & CellFlags::AllZNeg) && zNegFlags; + bool hasZPos = (bit.bit & CellFlags::AllZPos) && zPosFlags; + + // Corner exists if both adjacent sides have features + bool hasCorner = (hasXNeg && hasZPos) || (hasXNeg && hasZNeg) || + (hasXPos && hasZPos) || (hasXPos && hasZNeg); + + if (hasCorner) { genPlane(bit.sizeX, bit.sizeY, bit.normal, bit.offset, *bit.tb, origin); } @@ -700,27 +724,31 @@ void CellGridSystem::buildInternalCorners(const CellGridComponent &grid, for (const auto &cell : grid.cells) { Ogre::Vector3 origin = grid.cellToWorld(cell.x, cell.y, cell.z); - uint64_t intWallFlags = cell.flags & - CellFlags::AllInternalWalls; + // Use combined masks that include walls, doors, and windows + uint64_t intXNegFlags = cell.flags & CellFlags::AllIntXNeg; + uint64_t intXPosFlags = cell.flags & CellFlags::AllIntXPos; + uint64_t intZNegFlags = cell.flags & CellFlags::AllIntZNeg; + uint64_t intZPosFlags = cell.flags & CellFlags::AllIntZPos; // 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 + // Check if the corresponding direction has any feature (wall/door/window) + bool hasFeature = false; + if ((bit.bit & CellFlags::AllIntXNeg) && intXNegFlags) hasFeature = true; + if ((bit.bit & CellFlags::AllIntXPos) && intXPosFlags) hasFeature = true; + if ((bit.bit & CellFlags::AllIntZPos) && intZPosFlags) hasFeature = true; + if ((bit.bit & CellFlags::AllIntZNeg) && intZNegFlags) hasFeature = true; + + if (hasFeature) { + // Conflict resolution - skip if adjacent wall would overlap bool skip = false; - if (bit.bit == CellFlags::IntWallXNeg && - (intWallFlags & CellFlags::IntWallZPos)) + if ((bit.bit & CellFlags::AllIntXNeg) && intZPosFlags) skip = true; - if (bit.bit == CellFlags::IntWallXPos && - (intWallFlags & CellFlags::IntWallZPos)) + if ((bit.bit & CellFlags::AllIntXPos) && intZPosFlags) skip = true; - if (bit.bit == CellFlags::IntWallZPos && - (intWallFlags & CellFlags::IntWallXPos)) + if ((bit.bit & CellFlags::AllIntZPos) && intXPosFlags) skip = true; - if (bit.bit == CellFlags::IntWallZNeg && - (intWallFlags & CellFlags::IntWallXPos)) + if ((bit.bit & CellFlags::AllIntZNeg) && intXPosFlags) skip = true; if (!skip) { @@ -732,22 +760,23 @@ void CellGridSystem::buildInternalCorners(const CellGridComponent &grid, } // 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) { + bool hasFeature = false; + if ((bit.bit & CellFlags::AllIntXNeg) && intXNegFlags) hasFeature = true; + if ((bit.bit & CellFlags::AllIntXPos) && intXPosFlags) hasFeature = true; + if ((bit.bit & CellFlags::AllIntZPos) && intZPosFlags) hasFeature = true; + if ((bit.bit & CellFlags::AllIntZNeg) && intZNegFlags) hasFeature = true; + + if (hasFeature) { // Conflict resolution bool skip = false; - if (bit.bit == CellFlags::IntWallXNeg && - (intWallFlags & CellFlags::IntWallZNeg)) + if ((bit.bit & CellFlags::AllIntXNeg) && intZNegFlags) skip = true; - if (bit.bit == CellFlags::IntWallXPos && - (intWallFlags & CellFlags::IntWallZNeg)) + if ((bit.bit & CellFlags::AllIntXPos) && intZNegFlags) skip = true; - if (bit.bit == CellFlags::IntWallZPos && - (intWallFlags & CellFlags::IntWallXNeg)) + if ((bit.bit & CellFlags::AllIntZPos) && intXNegFlags) skip = true; - if (bit.bit == CellFlags::IntWallZNeg && - (intWallFlags & CellFlags::IntWallXNeg)) + if ((bit.bit & CellFlags::AllIntZNeg) && intXNegFlags) skip = true; if (!skip) { @@ -763,10 +792,19 @@ void CellGridSystem::buildInternalCorners(const CellGridComponent &grid, void CellGridSystem::buildDoors(const CellGridComponent &grid, std::vector &doorTbs) { - const float cellSize = grid.cellSize; - const float doorHeight = grid.cellHeight * 0.7f; - const float doorWidth = cellSize * 0.6f; - const float frameThickness = 0.1f; + // Based on original town.cpp door construction + // Uses plane-based door frames around the opening + const float hScale = grid.cellSize / 2.0f; + const float halfCell = 1.0f * hScale; + const float cornerWidth = 0.2f; + const float solidExtOffset = 0.0f; + const float solidIntOffset = 0.1f; + const float solidExtHeight = grid.cellHeight; + const float solidIntHeight = grid.cellHeight - 0.3f - solidIntOffset; + + // Door dimensions (original values scaled) + const float doorWidth = 1.4f * hScale; + const float doorHeightBase = 3.0f; for (const auto &cell : grid.cells) { Ogre::Vector3 origin = grid.cellToWorld(cell.x, cell.y, cell.z); @@ -778,59 +816,46 @@ void CellGridSystem::buildDoors(const CellGridComponent &grid, cell.hasFlag(CellFlags::DoorZPos)) { Procedural::TriangleBuffer tb; - // Door frame (simplified as thin boxes around the opening) + float sideWidth = (2.0f * hScale - doorWidth) / 2.0f; + float moffset = (2.0f * hScale - sideWidth) / 2.0f; + float doorHeight = doorHeightBase - solidExtOffset; + float doorTopHeight = solidExtHeight - doorHeight; + float baseOffsetY = solidExtOffset; + if (cell.hasFlag(CellFlags::DoorXNeg)) { + // Left side frame + genPlane(doorHeight, sideWidth, Ogre::Vector3::NEGATIVE_UNIT_X, + Ogre::Vector3(-halfCell - cornerWidth, baseOffsetY + doorHeight / 2.0f, -moffset), tb, origin); + // Right side frame + genPlane(doorHeight, sideWidth, Ogre::Vector3::NEGATIVE_UNIT_X, + Ogre::Vector3(-halfCell - cornerWidth, baseOffsetY + doorHeight / 2.0f, moffset), tb, origin); // 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); + genPlane(doorTopHeight, 2.0f * hScale, Ogre::Vector3::NEGATIVE_UNIT_X, + Ogre::Vector3(-halfCell - cornerWidth, baseOffsetY + doorHeight + doorTopHeight / 2.0f, 0), tb, origin); } 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); + genPlane(doorHeight, sideWidth, Ogre::Vector3::UNIT_X, + Ogre::Vector3(halfCell + cornerWidth, baseOffsetY + doorHeight / 2.0f, -moffset), tb, origin); + genPlane(doorHeight, sideWidth, Ogre::Vector3::UNIT_X, + Ogre::Vector3(halfCell + cornerWidth, baseOffsetY + doorHeight / 2.0f, moffset), tb, origin); + genPlane(doorTopHeight, 2.0f * hScale, Ogre::Vector3::UNIT_X, + Ogre::Vector3(halfCell + cornerWidth, baseOffsetY + doorHeight + doorTopHeight / 2.0f, 0), tb, origin); } 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); + genPlane(doorHeight, sideWidth, Ogre::Vector3::NEGATIVE_UNIT_Z, + Ogre::Vector3(-moffset, baseOffsetY + doorHeight / 2.0f, -halfCell - cornerWidth), tb, origin); + genPlane(doorHeight, sideWidth, Ogre::Vector3::NEGATIVE_UNIT_Z, + Ogre::Vector3(moffset, baseOffsetY + doorHeight / 2.0f, -halfCell - cornerWidth), tb, origin); + genPlane(doorTopHeight, 2.0f * hScale, Ogre::Vector3::NEGATIVE_UNIT_Z, + Ogre::Vector3(0, baseOffsetY + doorHeight + doorTopHeight / 2.0f, -halfCell - cornerWidth), tb, origin); } 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); + genPlane(doorHeight, sideWidth, Ogre::Vector3::UNIT_Z, + Ogre::Vector3(-moffset, baseOffsetY + doorHeight / 2.0f, halfCell + cornerWidth), tb, origin); + genPlane(doorHeight, sideWidth, Ogre::Vector3::UNIT_Z, + Ogre::Vector3(moffset, baseOffsetY + doorHeight / 2.0f, halfCell + cornerWidth), tb, origin); + genPlane(doorTopHeight, 2.0f * hScale, Ogre::Vector3::UNIT_Z, + Ogre::Vector3(0, baseOffsetY + doorHeight + doorTopHeight / 2.0f, halfCell + cornerWidth), tb, origin); } if (tb.getVertices().size() > 0) { @@ -838,64 +863,50 @@ void CellGridSystem::buildDoors(const CellGridComponent &grid, } } - // Internal doors (similar but thinner) + // Internal doors if (cell.hasFlag(CellFlags::IntDoorXNeg) || cell.hasFlag(CellFlags::IntDoorXPos) || cell.hasFlag(CellFlags::IntDoorZNeg) || cell.hasFlag(CellFlags::IntDoorZPos)) { Procedural::TriangleBuffer tb; + float sideWidth = (2.0f * hScale - 0.2f - doorWidth) / 2.0f; + float moffset = (2.0f * hScale - 0.2f - sideWidth) / 2.0f; + float doorHeight = doorHeightBase - solidIntOffset; + float doorTopHeight = solidIntHeight - doorHeight; + float baseOffsetY = solidIntOffset; + 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); + genPlane(doorHeight, sideWidth, Ogre::Vector3::UNIT_X, + Ogre::Vector3(-halfCell + 0.1f, baseOffsetY + doorHeight / 2.0f, -moffset), tb, origin); + genPlane(doorHeight, sideWidth, Ogre::Vector3::UNIT_X, + Ogre::Vector3(-halfCell + 0.1f, baseOffsetY + doorHeight / 2.0f, moffset), tb, origin); + genPlane(doorTopHeight, 2.0f * hScale - 0.2f, Ogre::Vector3::UNIT_X, + Ogre::Vector3(-halfCell + 0.1f, baseOffsetY + doorHeight + doorTopHeight / 2.0f, 0), tb, origin); } 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); + genPlane(doorHeight, sideWidth, Ogre::Vector3::NEGATIVE_UNIT_X, + Ogre::Vector3(halfCell - 0.1f, baseOffsetY + doorHeight / 2.0f, -moffset), tb, origin); + genPlane(doorHeight, sideWidth, Ogre::Vector3::NEGATIVE_UNIT_X, + Ogre::Vector3(halfCell - 0.1f, baseOffsetY + doorHeight / 2.0f, moffset), tb, origin); + genPlane(doorTopHeight, 2.0f * hScale - 0.2f, Ogre::Vector3::NEGATIVE_UNIT_X, + Ogre::Vector3(halfCell - 0.1f, baseOffsetY + doorHeight + doorTopHeight / 2.0f, 0), tb, origin); } 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); + genPlane(doorHeight, sideWidth, Ogre::Vector3::UNIT_Z, + Ogre::Vector3(-moffset, baseOffsetY + doorHeight / 2.0f, -halfCell + 0.1f), tb, origin); + genPlane(doorHeight, sideWidth, Ogre::Vector3::UNIT_Z, + Ogre::Vector3(moffset, baseOffsetY + doorHeight / 2.0f, -halfCell + 0.1f), tb, origin); + genPlane(doorTopHeight, 2.0f * hScale - 0.2f, Ogre::Vector3::UNIT_Z, + Ogre::Vector3(0, baseOffsetY + doorHeight + doorTopHeight / 2.0f, -halfCell + 0.1f), tb, origin); } 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); + genPlane(doorHeight, sideWidth, Ogre::Vector3::NEGATIVE_UNIT_Z, + Ogre::Vector3(-moffset, baseOffsetY + doorHeight / 2.0f, halfCell - 0.1f), tb, origin); + genPlane(doorHeight, sideWidth, Ogre::Vector3::NEGATIVE_UNIT_Z, + Ogre::Vector3(moffset, baseOffsetY + doorHeight / 2.0f, halfCell - 0.1f), tb, origin); + genPlane(doorTopHeight, 2.0f * hScale - 0.2f, Ogre::Vector3::NEGATIVE_UNIT_Z, + Ogre::Vector3(0, baseOffsetY + doorHeight + doorTopHeight / 2.0f, halfCell - 0.1f), tb, origin); } if (tb.getVertices().size() > 0) { @@ -909,11 +920,22 @@ void CellGridSystem::buildWindows( const CellGridComponent &grid, std::vector &windowTbs) { - 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; - + // Based on original town.cpp window construction + // Uses plane-based window frames around the opening + const float hScale = grid.cellSize / 2.0f; + const float halfCell = 1.0f * hScale; + const float cornerWidth = 0.2f; + const float solidExtOffset = 0.0f; + const float solidIntOffset = 0.1f; + + // Window dimensions (original values scaled) + // Original: windowBottomOffset = 0.8f, windowHeight = 2.0f + // Window Y center = 0.8f + 2.0f/2 = 1.8f for cellHeight=4.0f + const float windowWidth = 1.6f * hScale; + const float windowHeight = 2.0f * (grid.cellHeight / 4.0f); + const float windowBottomOffset = 0.8f * (grid.cellHeight / 4.0f); + const float windowY = windowBottomOffset + windowHeight / 2.0f; + for (const auto &cell : grid.cells) { Ogre::Vector3 origin = grid.cellToWorld(cell.x, cell.y, cell.z); @@ -923,48 +945,103 @@ void CellGridSystem::buildWindows( cell.hasFlag(CellFlags::WindowZNeg) || cell.hasFlag(CellFlags::WindowZPos)) { Procedural::TriangleBuffer tb; + + // External cell width = 2.0 * hScale (full cell size) + float externalCellWidth = 2.0f * hScale; + float sideWidth = (externalCellWidth - windowWidth) / 2.0f; + // Window frame pieces to match full wall height + // Bottom piece: from solidExtOffset to window bottom + float bottomSize = windowBottomOffset - solidExtOffset; + // Mid piece (window opening height): windowHeight + // Top piece: remaining height to ceiling (external walls go to full cellHeight) + float topSize = grid.cellHeight - windowBottomOffset - windowHeight; + // Side pieces span full height: bottom + window + top = cellHeight + float sideHeight = bottomSize + windowHeight + topSize; + float offsetY = solidExtOffset; - // 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); + // Left side - spans full height (bottom + window + top) + genPlane(sideHeight, sideWidth, Ogre::Vector3::NEGATIVE_UNIT_X, + Ogre::Vector3(-halfCell - cornerWidth, offsetY + sideHeight/2.0f, -windowWidth/2 - sideWidth/2), tb, origin); + // Right side - spans full height (bottom + window + top) + genPlane(sideHeight, sideWidth, Ogre::Vector3::NEGATIVE_UNIT_X, + Ogre::Vector3(-halfCell - cornerWidth, offsetY + sideHeight/2.0f, windowWidth/2 + sideWidth/2), tb, origin); + // Top + genPlane(topSize, sideWidth, Ogre::Vector3::NEGATIVE_UNIT_X, + Ogre::Vector3(-halfCell - cornerWidth, windowY + windowHeight/2 + topSize/2, -windowWidth/2 - sideWidth/2), tb, origin); + genPlane(topSize, sideWidth, Ogre::Vector3::NEGATIVE_UNIT_X, + Ogre::Vector3(-halfCell - cornerWidth, windowY + windowHeight/2 + topSize/2, windowWidth/2 + sideWidth/2), tb, origin); + // Bottom - Y position includes solidExtOffset to match internal window's lower edge + genPlane(bottomSize, sideWidth, Ogre::Vector3::NEGATIVE_UNIT_X, + Ogre::Vector3(-halfCell - cornerWidth, solidExtOffset + bottomSize/2, -windowWidth/2 - sideWidth/2), tb, origin); + genPlane(bottomSize, sideWidth, Ogre::Vector3::NEGATIVE_UNIT_X, + Ogre::Vector3(-halfCell - cornerWidth, solidExtOffset + bottomSize/2, windowWidth/2 + sideWidth/2), tb, origin); + // Top center + genPlane(topSize, windowWidth, Ogre::Vector3::NEGATIVE_UNIT_X, + Ogre::Vector3(-halfCell - cornerWidth, windowY + windowHeight/2 + topSize/2, 0), tb, origin); + // Bottom center - Y position includes solidExtOffset to match internal window's lower edge + genPlane(bottomSize, windowWidth, Ogre::Vector3::NEGATIVE_UNIT_X, + Ogre::Vector3(-halfCell - cornerWidth, solidExtOffset + bottomSize/2, 0), tb, origin); } 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); + // Left side - spans full height + genPlane(sideHeight, sideWidth, Ogre::Vector3::UNIT_X, + Ogre::Vector3(halfCell + cornerWidth, offsetY + sideHeight/2.0f, -windowWidth/2 - sideWidth/2), tb, origin); + // Right side - spans full height + genPlane(sideHeight, sideWidth, Ogre::Vector3::UNIT_X, + Ogre::Vector3(halfCell + cornerWidth, offsetY + sideHeight/2.0f, windowWidth/2 + sideWidth/2), tb, origin); + genPlane(topSize, sideWidth, Ogre::Vector3::UNIT_X, + Ogre::Vector3(halfCell + cornerWidth, windowY + windowHeight/2 + topSize/2, -windowWidth/2 - sideWidth/2), tb, origin); + genPlane(topSize, sideWidth, Ogre::Vector3::UNIT_X, + Ogre::Vector3(halfCell + cornerWidth, windowY + windowHeight/2 + topSize/2, windowWidth/2 + sideWidth/2), tb, origin); + genPlane(bottomSize, sideWidth, Ogre::Vector3::UNIT_X, + Ogre::Vector3(halfCell + cornerWidth, solidExtOffset + bottomSize/2, -windowWidth/2 - sideWidth/2), tb, origin); + genPlane(bottomSize, sideWidth, Ogre::Vector3::UNIT_X, + Ogre::Vector3(halfCell + cornerWidth, solidExtOffset + bottomSize/2, windowWidth/2 + sideWidth/2), tb, origin); + genPlane(topSize, windowWidth, Ogre::Vector3::UNIT_X, + Ogre::Vector3(halfCell + cornerWidth, windowY + windowHeight/2 + topSize/2, 0), tb, origin); + genPlane(bottomSize, windowWidth, Ogre::Vector3::UNIT_X, + Ogre::Vector3(halfCell + cornerWidth, solidExtOffset + bottomSize/2, 0), tb, origin); } 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); + // Left side - spans full height + genPlane(sideHeight, sideWidth, Ogre::Vector3::NEGATIVE_UNIT_Z, + Ogre::Vector3(-windowWidth/2 - sideWidth/2, offsetY + sideHeight/2.0f, -halfCell - cornerWidth), tb, origin); + // Right side - spans full height + genPlane(sideHeight, sideWidth, Ogre::Vector3::NEGATIVE_UNIT_Z, + Ogre::Vector3(windowWidth/2 + sideWidth/2, offsetY + sideHeight/2.0f, -halfCell - cornerWidth), tb, origin); + genPlane(topSize, sideWidth, Ogre::Vector3::NEGATIVE_UNIT_Z, + Ogre::Vector3(-windowWidth/2 - sideWidth/2, windowY + windowHeight/2 + topSize/2, -halfCell - cornerWidth), tb, origin); + genPlane(topSize, sideWidth, Ogre::Vector3::NEGATIVE_UNIT_Z, + Ogre::Vector3(windowWidth/2 + sideWidth/2, windowY + windowHeight/2 + topSize/2, -halfCell - cornerWidth), tb, origin); + genPlane(bottomSize, sideWidth, Ogre::Vector3::NEGATIVE_UNIT_Z, + Ogre::Vector3(-windowWidth/2 - sideWidth/2, solidExtOffset + bottomSize/2, -halfCell - cornerWidth), tb, origin); + genPlane(bottomSize, sideWidth, Ogre::Vector3::NEGATIVE_UNIT_Z, + Ogre::Vector3(windowWidth/2 + sideWidth/2, solidExtOffset + bottomSize/2, -halfCell - cornerWidth), tb, origin); + genPlane(topSize, windowWidth, Ogre::Vector3::NEGATIVE_UNIT_Z, + Ogre::Vector3(0, windowY + windowHeight/2 + topSize/2, -halfCell - cornerWidth), tb, origin); + genPlane(bottomSize, windowWidth, Ogre::Vector3::NEGATIVE_UNIT_Z, + Ogre::Vector3(0, solidExtOffset + bottomSize/2, -halfCell - cornerWidth), tb, origin); } 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); + // Left side - spans full height + genPlane(sideHeight, sideWidth, Ogre::Vector3::UNIT_Z, + Ogre::Vector3(-windowWidth/2 - sideWidth/2, offsetY + sideHeight/2.0f, halfCell + cornerWidth), tb, origin); + // Right side - spans full height + genPlane(sideHeight, sideWidth, Ogre::Vector3::UNIT_Z, + Ogre::Vector3(windowWidth/2 + sideWidth/2, offsetY + sideHeight/2.0f, halfCell + cornerWidth), tb, origin); + genPlane(topSize, sideWidth, Ogre::Vector3::UNIT_Z, + Ogre::Vector3(-windowWidth/2 - sideWidth/2, windowY + windowHeight/2 + topSize/2, halfCell + cornerWidth), tb, origin); + genPlane(topSize, sideWidth, Ogre::Vector3::UNIT_Z, + Ogre::Vector3(windowWidth/2 + sideWidth/2, windowY + windowHeight/2 + topSize/2, halfCell + cornerWidth), tb, origin); + genPlane(bottomSize, sideWidth, Ogre::Vector3::UNIT_Z, + Ogre::Vector3(-windowWidth/2 - sideWidth/2, solidExtOffset + bottomSize/2, halfCell + cornerWidth), tb, origin); + genPlane(bottomSize, sideWidth, Ogre::Vector3::UNIT_Z, + Ogre::Vector3(windowWidth/2 + sideWidth/2, solidExtOffset + bottomSize/2, halfCell + cornerWidth), tb, origin); + genPlane(topSize, windowWidth, Ogre::Vector3::UNIT_Z, + Ogre::Vector3(0, windowY + windowHeight/2 + topSize/2, halfCell + cornerWidth), tb, origin); + genPlane(bottomSize, windowWidth, Ogre::Vector3::UNIT_Z, + Ogre::Vector3(0, solidExtOffset + bottomSize/2, halfCell + cornerWidth), tb, origin); } if (tb.getVertices().size() > 0) { @@ -972,52 +1049,92 @@ void CellGridSystem::buildWindows( } } - // Internal windows (thinner frames) + // Internal windows if (cell.hasFlag(CellFlags::IntWindowXNeg) || cell.hasFlag(CellFlags::IntWindowXPos) || cell.hasFlag(CellFlags::IntWindowZNeg) || cell.hasFlag(CellFlags::IntWindowZPos)) { Procedural::TriangleBuffer tb; + + float intWallOffset = 0.1f; + // Internal cell width = 2.0 * hScale - 2 * intWallOffset (1.8f for cellSize=4.0) + float internalCellWidth = 2.0f * hScale - 2.0f * intWallOffset; + float sideWidth = (internalCellWidth - windowWidth) / 2.0f; + // Internal windows start at solidIntOffset, so bottom piece is shorter + float bottomSize = windowBottomOffset - solidIntOffset; + float topSize = grid.cellHeight - windowY - windowHeight / 2.0f - 0.3f; 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); + genPlane(windowHeight, sideWidth, Ogre::Vector3::UNIT_X, + Ogre::Vector3(-halfCell + intWallOffset, windowY, -windowWidth/2 - sideWidth/2), tb, origin); + genPlane(windowHeight, sideWidth, Ogre::Vector3::UNIT_X, + Ogre::Vector3(-halfCell + intWallOffset, windowY, windowWidth/2 + sideWidth/2), tb, origin); + genPlane(topSize, sideWidth, Ogre::Vector3::UNIT_X, + Ogre::Vector3(-halfCell + intWallOffset, windowY + windowHeight/2 + topSize/2, -windowWidth/2 - sideWidth/2), tb, origin); + genPlane(topSize, sideWidth, Ogre::Vector3::UNIT_X, + Ogre::Vector3(-halfCell + intWallOffset, windowY + windowHeight/2 + topSize/2, windowWidth/2 + sideWidth/2), tb, origin); + genPlane(bottomSize, sideWidth, Ogre::Vector3::UNIT_X, + Ogre::Vector3(-halfCell + intWallOffset, solidIntOffset + bottomSize/2, -windowWidth/2 - sideWidth/2), tb, origin); + genPlane(bottomSize, sideWidth, Ogre::Vector3::UNIT_X, + Ogre::Vector3(-halfCell + intWallOffset, solidIntOffset + bottomSize/2, windowWidth/2 + sideWidth/2), tb, origin); + genPlane(topSize, windowWidth, Ogre::Vector3::UNIT_X, + Ogre::Vector3(-halfCell + intWallOffset, windowY + windowHeight/2 + topSize/2, 0), tb, origin); + genPlane(bottomSize, windowWidth, Ogre::Vector3::UNIT_X, + Ogre::Vector3(-halfCell + intWallOffset, solidIntOffset + bottomSize/2, 0), tb, origin); } 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); + genPlane(windowHeight, sideWidth, Ogre::Vector3::NEGATIVE_UNIT_X, + Ogre::Vector3(halfCell - intWallOffset, windowY, -windowWidth/2 - sideWidth/2), tb, origin); + genPlane(windowHeight, sideWidth, Ogre::Vector3::NEGATIVE_UNIT_X, + Ogre::Vector3(halfCell - intWallOffset, windowY, windowWidth/2 + sideWidth/2), tb, origin); + genPlane(topSize, sideWidth, Ogre::Vector3::NEGATIVE_UNIT_X, + Ogre::Vector3(halfCell - intWallOffset, windowY + windowHeight/2 + topSize/2, -windowWidth/2 - sideWidth/2), tb, origin); + genPlane(topSize, sideWidth, Ogre::Vector3::NEGATIVE_UNIT_X, + Ogre::Vector3(halfCell - intWallOffset, windowY + windowHeight/2 + topSize/2, windowWidth/2 + sideWidth/2), tb, origin); + genPlane(bottomSize, sideWidth, Ogre::Vector3::NEGATIVE_UNIT_X, + Ogre::Vector3(halfCell - intWallOffset, solidIntOffset + bottomSize/2, -windowWidth/2 - sideWidth/2), tb, origin); + genPlane(bottomSize, sideWidth, Ogre::Vector3::NEGATIVE_UNIT_X, + Ogre::Vector3(halfCell - intWallOffset, solidIntOffset + bottomSize/2, windowWidth/2 + sideWidth/2), tb, origin); + genPlane(topSize, windowWidth, Ogre::Vector3::NEGATIVE_UNIT_X, + Ogre::Vector3(halfCell - intWallOffset, windowY + windowHeight/2 + topSize/2, 0), tb, origin); + genPlane(bottomSize, windowWidth, Ogre::Vector3::NEGATIVE_UNIT_X, + Ogre::Vector3(halfCell - intWallOffset, solidIntOffset + bottomSize/2, 0), tb, origin); } 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); + genPlane(windowHeight, sideWidth, Ogre::Vector3::UNIT_Z, + Ogre::Vector3(-windowWidth/2 - sideWidth/2, windowY, -halfCell + intWallOffset), tb, origin); + genPlane(windowHeight, sideWidth, Ogre::Vector3::UNIT_Z, + Ogre::Vector3(windowWidth/2 + sideWidth/2, windowY, -halfCell + intWallOffset), tb, origin); + genPlane(topSize, sideWidth, Ogre::Vector3::UNIT_Z, + Ogre::Vector3(-windowWidth/2 - sideWidth/2, windowY + windowHeight/2 + topSize/2, -halfCell + intWallOffset), tb, origin); + genPlane(topSize, sideWidth, Ogre::Vector3::UNIT_Z, + Ogre::Vector3(windowWidth/2 + sideWidth/2, windowY + windowHeight/2 + topSize/2, -halfCell + intWallOffset), tb, origin); + genPlane(bottomSize, sideWidth, Ogre::Vector3::UNIT_Z, + Ogre::Vector3(-windowWidth/2 - sideWidth/2, solidIntOffset + bottomSize/2, -halfCell + intWallOffset), tb, origin); + genPlane(bottomSize, sideWidth, Ogre::Vector3::UNIT_Z, + Ogre::Vector3(windowWidth/2 + sideWidth/2, solidIntOffset + bottomSize/2, -halfCell + intWallOffset), tb, origin); + genPlane(topSize, windowWidth, Ogre::Vector3::UNIT_Z, + Ogre::Vector3(0, windowY + windowHeight/2 + topSize/2, -halfCell + intWallOffset), tb, origin); + genPlane(bottomSize, windowWidth, Ogre::Vector3::UNIT_Z, + Ogre::Vector3(0, solidIntOffset + bottomSize/2, -halfCell + intWallOffset), tb, origin); } 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); + genPlane(windowHeight, sideWidth, Ogre::Vector3::NEGATIVE_UNIT_Z, + Ogre::Vector3(-windowWidth/2 - sideWidth/2, windowY, halfCell - intWallOffset), tb, origin); + genPlane(windowHeight, sideWidth, Ogre::Vector3::NEGATIVE_UNIT_Z, + Ogre::Vector3(windowWidth/2 + sideWidth/2, windowY, halfCell - intWallOffset), tb, origin); + genPlane(topSize, sideWidth, Ogre::Vector3::NEGATIVE_UNIT_Z, + Ogre::Vector3(-windowWidth/2 - sideWidth/2, windowY + windowHeight/2 + topSize/2, halfCell - intWallOffset), tb, origin); + genPlane(topSize, sideWidth, Ogre::Vector3::NEGATIVE_UNIT_Z, + Ogre::Vector3(windowWidth/2 + sideWidth/2, windowY + windowHeight/2 + topSize/2, halfCell - intWallOffset), tb, origin); + genPlane(bottomSize, sideWidth, Ogre::Vector3::NEGATIVE_UNIT_Z, + Ogre::Vector3(-windowWidth/2 - sideWidth/2, solidIntOffset + bottomSize/2, halfCell - intWallOffset), tb, origin); + genPlane(bottomSize, sideWidth, Ogre::Vector3::NEGATIVE_UNIT_Z, + Ogre::Vector3(windowWidth/2 + sideWidth/2, solidIntOffset + bottomSize/2, halfCell - intWallOffset), tb, origin); + genPlane(topSize, windowWidth, Ogre::Vector3::NEGATIVE_UNIT_Z, + Ogre::Vector3(0, windowY + windowHeight/2 + topSize/2, halfCell - intWallOffset), tb, origin); + genPlane(bottomSize, windowWidth, Ogre::Vector3::NEGATIVE_UNIT_Z, + Ogre::Vector3(0, solidIntOffset + bottomSize/2, halfCell - intWallOffset), tb, origin); } if (tb.getVertices().size() > 0) { @@ -1184,6 +1301,9 @@ void CellGridSystem::destroyCellGridMeshes(flecs::entity entity) removeMesh(name); } + // Destroy frames + destroyFrames(entity); + m_entityMeshes.erase(it); } } @@ -1914,3 +2034,529 @@ void CellGridSystem::applyUVMappingToBuffer(Procedural::TriangleBuffer &tb, v.mUV.y = vOffset + v.mUV.y * vRange; } } + + +// ============================================================================ +// Window and Door Frame Methods +// ============================================================================ + +void CellGridSystem::buildFrames(flecs::entity entity, const CellGridComponent& grid, const std::string& materialName) +{ + // Destroy old frames first + destroyFrames(entity); + + // Get material entity for UV mapping + flecs::entity materialEntity = flecs::entity::null(); + flecs::entity parent = entity.parent(); + while (parent.is_alive()) { + if (parent.has()) { + auto& lot = parent.get(); + if (lot.proceduralMaterialEntity.is_alive()) { + materialEntity = lot.proceduralMaterialEntity; + break; + } + } + if (parent.has()) { + auto& district = parent.get(); + if (district.proceduralMaterialEntity.is_alive()) { + materialEntity = district.proceduralMaterialEntity; + break; + } + } + if (parent.has()) { + auto& town = parent.get(); + if (town.proceduralMaterialEntity.is_alive()) { + materialEntity = town.proceduralMaterialEntity; + break; + } + } + parent = parent.parent(); + } + + // Create frame meshes with unique names per entity + std::string meshPrefix = "CellGrid_" + std::to_string(entity.id()) + "_"; + createWindowFrameMeshes(grid, materialName, meshPrefix, materialEntity); + createDoorFrameMeshes(grid, materialName, meshPrefix, materialEntity); + + // Create StaticGeometry for frames + std::string geoName = "CellGridFrames_" + std::to_string(entity.id()); + Ogre::LogManager::getSingleton().logMessage( + "CellGrid: Creating StaticGeometry: " + geoName); + Ogre::StaticGeometry* geo = m_sceneMgr->createStaticGeometry(geoName); + if (!geo) { + Ogre::LogManager::getSingleton().logMessage( + "CellGrid: Failed to create StaticGeometry!"); + return; + } + geo->setCastShadows(true); + + // Set region to cover a large area to ensure all frames are included + geo->setRegionDimensions(Ogre::Vector3(1000, 1000, 1000)); + geo->setOrigin(Ogre::Vector3::ZERO); + + // Place frames + std::vector frameEntities; + placeWindowFrames(entity, grid, geo, frameEntities); + placeDoorFrames(entity, grid, geo, frameEntities); + + // Build the static geometry + Ogre::LogManager::getSingleton().logMessage( + "CellGrid: Building StaticGeometry..."); + geo->build(); + Ogre::LogManager::getSingleton().logMessage( + "CellGrid: StaticGeometry built successfully"); + + // Destroy temporary frame entities after build + for (auto* ent : frameEntities) { + m_sceneMgr->destroyEntity(ent); + } + + // Store reference + auto it = m_entityMeshes.find(entity.id()); + if (it != m_entityMeshes.end()) { + it->second.framesStaticGeo = geo; + } +} + +void CellGridSystem::createWindowFrameMeshes(const CellGridComponent& grid, const std::string& materialName, + const std::string& meshPrefix, flecs::entity materialEntity) +{ + // Create unique mesh names per CellGrid entity + std::string extFrameMeshName = meshPrefix + "window_external"; + std::string intFrameMeshName = meshPrefix + "window_internal"; + + // Destroy old meshes if they exist + auto& meshMgr = Ogre::MeshManager::getSingleton(); + if (meshMgr.resourceExists(extFrameMeshName)) { + meshMgr.remove(extFrameMeshName); + } + if (meshMgr.resourceExists(intFrameMeshName)) { + meshMgr.remove(intFrameMeshName); + } + + // Window dimensions (scaled by cellSize/cellHeight) + float windowWidth = 1.6f * (grid.cellSize / 4.0f); + float windowHeight = 2.0f * (grid.cellHeight / 4.0f); + float frameDepth = 0.34f * (grid.cellSize / 4.0f); + float frameThickness = 0.1f * (grid.cellSize / 4.0f); + + // Create external window frame mesh + Procedural::TriangleBuffer extFrameTb; + + // Left frame + Procedural::BoxGenerator() + .setSizeX(frameThickness) + .setSizeY(windowHeight) + .setSizeZ(frameDepth) + .setNumSegX(1) + .setNumSegY(2) + .setNumSegZ(1) + .setPosition(Ogre::Vector3(-windowWidth/2 + frameThickness/2, windowHeight/2, 0)) + .setEnableNormals(true) + .addToTriangleBuffer(extFrameTb); + + // Right frame + Procedural::BoxGenerator() + .setSizeX(frameThickness) + .setSizeY(windowHeight) + .setSizeZ(frameDepth) + .setNumSegX(1) + .setNumSegY(2) + .setNumSegZ(1) + .setPosition(Ogre::Vector3(windowWidth/2 - frameThickness/2, windowHeight/2, 0)) + .setEnableNormals(true) + .addToTriangleBuffer(extFrameTb); + + // Top frame + Procedural::BoxGenerator() + .setSizeX(windowWidth) + .setSizeY(frameThickness) + .setSizeZ(frameDepth) + .setNumSegX(2) + .setNumSegY(1) + .setNumSegZ(1) + .setPosition(Ogre::Vector3(0, windowHeight - frameThickness/2, 0)) + .setEnableNormals(true) + .addToTriangleBuffer(extFrameTb); + + // Windowsill (extends outward) + float sillDepth = frameDepth * 1.5f; + Procedural::BoxGenerator() + .setSizeX(windowWidth + frameThickness * 2) + .setSizeY(frameThickness * 1.5f) + .setSizeZ(sillDepth) + .setNumSegX(2) + .setNumSegY(1) + .setNumSegZ(1) + .setPosition(Ogre::Vector3(0, frameThickness * 0.75f, -(sillDepth - frameDepth)/2)) + .setEnableNormals(true) + .addToTriangleBuffer(extFrameTb); + + // Apply UV mapping and create mesh + applyUVMappingToBuffer(extFrameTb, grid.windowFrameRectName, materialEntity); + Ogre::MeshPtr extMesh = extFrameTb.transformToMesh(extFrameMeshName); + if (!materialName.empty() && extMesh->getNumSubMeshes() > 0) { + extMesh->getSubMesh(0)->setMaterialName(materialName); + } + + // Create internal window frame mesh (simpler, no sill) + Procedural::TriangleBuffer intFrameTb; + float intFrameDepth = frameDepth * 0.5f; + + // Left frame + Procedural::BoxGenerator() + .setSizeX(frameThickness) + .setSizeY(windowHeight) + .setSizeZ(intFrameDepth) + .setNumSegX(1) + .setNumSegY(2) + .setNumSegZ(1) + .setPosition(Ogre::Vector3(-windowWidth/2 + frameThickness/2, windowHeight/2, 0)) + .setEnableNormals(true) + .addToTriangleBuffer(intFrameTb); + + // Right frame + Procedural::BoxGenerator() + .setSizeX(frameThickness) + .setSizeY(windowHeight) + .setSizeZ(intFrameDepth) + .setNumSegX(1) + .setNumSegY(2) + .setNumSegZ(1) + .setPosition(Ogre::Vector3(windowWidth/2 - frameThickness/2, windowHeight/2, 0)) + .setEnableNormals(true) + .addToTriangleBuffer(intFrameTb); + + // Top frame + Procedural::BoxGenerator() + .setSizeX(windowWidth) + .setSizeY(frameThickness) + .setSizeZ(intFrameDepth) + .setNumSegX(2) + .setNumSegY(1) + .setNumSegZ(1) + .setPosition(Ogre::Vector3(0, windowHeight - frameThickness/2, 0)) + .setEnableNormals(true) + .addToTriangleBuffer(intFrameTb); + + applyUVMappingToBuffer(intFrameTb, grid.windowFrameRectName, materialEntity); + Ogre::MeshPtr intMesh = intFrameTb.transformToMesh(intFrameMeshName); + if (!materialName.empty() && intMesh->getNumSubMeshes() > 0) { + intMesh->getSubMesh(0)->setMaterialName(materialName); + } +} + +void CellGridSystem::createDoorFrameMeshes(const CellGridComponent& grid, const std::string& materialName, + const std::string& meshPrefix, flecs::entity materialEntity) +{ + // Create unique mesh names per CellGrid entity + std::string extFrameMeshName = meshPrefix + "door_external"; + std::string intFrameMeshName = meshPrefix + "door_internal"; + + // Destroy old meshes if they exist + auto& meshMgr = Ogre::MeshManager::getSingleton(); + if (meshMgr.resourceExists(extFrameMeshName)) { + meshMgr.remove(extFrameMeshName); + } + if (meshMgr.resourceExists(intFrameMeshName)) { + meshMgr.remove(intFrameMeshName); + } + + // Door dimensions + float doorWidth = 1.4f * (grid.cellSize / 4.0f); + float doorHeight = 3.0f * (grid.cellHeight / 4.0f); + float frameDepth = 0.34f * (grid.cellSize / 4.0f); + float frameThickness = 0.1f * (grid.cellSize / 4.0f); + + // Create external door frame mesh + Procedural::TriangleBuffer extFrameTb; + float stepDepth = frameDepth * 2.0f; + + // Left frame with step + Procedural::BoxGenerator() + .setSizeX(frameThickness) + .setSizeY(doorHeight) + .setSizeZ(stepDepth) + .setNumSegX(1) + .setNumSegY(3) + .setNumSegZ(1) + .setPosition(Ogre::Vector3(-doorWidth/2 + frameThickness/2, doorHeight/2, (stepDepth - frameDepth)/2)) + .setEnableNormals(true) + .addToTriangleBuffer(extFrameTb); + + // Right frame with step + Procedural::BoxGenerator() + .setSizeX(frameThickness) + .setSizeY(doorHeight) + .setSizeZ(stepDepth) + .setNumSegX(1) + .setNumSegY(3) + .setNumSegZ(1) + .setPosition(Ogre::Vector3(doorWidth/2 - frameThickness/2, doorHeight/2, (stepDepth - frameDepth)/2)) + .setEnableNormals(true) + .addToTriangleBuffer(extFrameTb); + + // Top frame + Procedural::BoxGenerator() + .setSizeX(doorWidth + frameThickness * 2) + .setSizeY(frameThickness) + .setSizeZ(frameDepth) + .setNumSegX(2) + .setNumSegY(1) + .setNumSegZ(1) + .setPosition(Ogre::Vector3(0, doorHeight - frameThickness/2, 0)) + .setEnableNormals(true) + .addToTriangleBuffer(extFrameTb); + + // Step + float stepWidth = doorWidth + frameThickness * 4; + Procedural::BoxGenerator() + .setSizeX(stepWidth) + .setSizeY(frameThickness) + .setSizeZ(stepDepth) + .setNumSegX(2) + .setNumSegY(1) + .setNumSegZ(1) + .setPosition(Ogre::Vector3(0, frameThickness/2, -(stepDepth - frameDepth)/2)) + .setEnableNormals(true) + .addToTriangleBuffer(extFrameTb); + + applyUVMappingToBuffer(extFrameTb, grid.doorFrameRectName, materialEntity); + Ogre::MeshPtr extMesh = extFrameTb.transformToMesh(extFrameMeshName); + if (!materialName.empty() && extMesh->getNumSubMeshes() > 0) { + extMesh->getSubMesh(0)->setMaterialName(materialName); + } + + // Create internal door frame (simpler) + Procedural::TriangleBuffer intFrameTb; + float intFrameDepth = frameDepth * 0.5f; + + Procedural::BoxGenerator() + .setSizeX(frameThickness) + .setSizeY(doorHeight) + .setSizeZ(intFrameDepth) + .setNumSegX(1) + .setNumSegY(2) + .setNumSegZ(1) + .setPosition(Ogre::Vector3(-doorWidth/2 + frameThickness/2, doorHeight/2, 0)) + .setEnableNormals(true) + .addToTriangleBuffer(intFrameTb); + + Procedural::BoxGenerator() + .setSizeX(frameThickness) + .setSizeY(doorHeight) + .setSizeZ(intFrameDepth) + .setNumSegX(1) + .setNumSegY(2) + .setNumSegZ(1) + .setPosition(Ogre::Vector3(doorWidth/2 - frameThickness/2, doorHeight/2, 0)) + .setEnableNormals(true) + .addToTriangleBuffer(intFrameTb); + + Procedural::BoxGenerator() + .setSizeX(doorWidth) + .setSizeY(frameThickness) + .setSizeZ(intFrameDepth) + .setNumSegX(2) + .setNumSegY(1) + .setNumSegZ(1) + .setPosition(Ogre::Vector3(0, doorHeight - frameThickness/2, 0)) + .setEnableNormals(true) + .addToTriangleBuffer(intFrameTb); + + applyUVMappingToBuffer(intFrameTb, grid.doorFrameRectName, materialEntity); + Ogre::MeshPtr intMesh = intFrameTb.transformToMesh(intFrameMeshName); + if (!materialName.empty() && intMesh->getNumSubMeshes() > 0) { + intMesh->getSubMesh(0)->setMaterialName(materialName); + } +} + +void CellGridSystem::placeWindowFrames(flecs::entity entity, const CellGridComponent& grid, Ogre::StaticGeometry* geo, std::vector& frameEntities) +{ + std::string meshPrefix = "CellGrid_" + std::to_string(entity.id()) + "_"; + Ogre::MeshPtr extMesh = Ogre::MeshManager::getSingleton().getByName(meshPrefix + "window_external"); + Ogre::MeshPtr intMesh = Ogre::MeshManager::getSingleton().getByName(meshPrefix + "window_internal"); + + if (!extMesh && !intMesh) return; + + // Get the CellGrid entity's world position (Lot position) + Ogre::Vector3 lotWorldPos(0, 0, 0); + if (entity.has()) { + auto& transform = entity.get(); + if (transform.node) { + lotWorldPos = transform.node->_getDerivedPosition(); + } + } + + float halfCell = grid.cellSize / 2.0f; + float frameOffset = 0.2f * (grid.cellSize / 4.0f); + float windowY = 1.5f * (grid.cellHeight / 4.0f); + + for (const auto& cell : grid.cells) { + Ogre::Vector3 origin = grid.cellToWorld(cell.x, cell.y, cell.z); + + auto addFrame = [&](Ogre::MeshPtr mesh, const Ogre::Vector3& pos, const Ogre::Quaternion& rot, const char* name) { + if (!mesh) return; + // Add lot world position to get actual world coordinates + Ogre::Vector3 worldPos = lotWorldPos + pos; + Ogre::LogManager::getSingleton().logMessage( + "CellGrid: Placing " + Ogre::String(name) + " frame at world pos=" + + std::to_string(worldPos.x) + "," + std::to_string(worldPos.y) + "," + std::to_string(worldPos.z)); + Ogre::Entity* ent = m_sceneMgr->createEntity(mesh->getName()); + geo->addEntity(ent, worldPos, rot); + frameEntities.push_back(ent); + }; + + if (extMesh) { + if (cell.hasFlag(CellFlags::WindowXNeg)) { + Ogre::Vector3 pos = origin + Ogre::Vector3(-halfCell - frameOffset, windowY, 0); + Ogre::Quaternion rot(Ogre::Degree(-90), Ogre::Vector3::UNIT_Y); + addFrame(extMesh, pos, rot, "extWindowXNeg"); + } + if (cell.hasFlag(CellFlags::WindowXPos)) { + Ogre::Vector3 pos = origin + Ogre::Vector3(halfCell + frameOffset, windowY, 0); + Ogre::Quaternion rot(Ogre::Degree(90), Ogre::Vector3::UNIT_Y); + addFrame(extMesh, pos, rot, "extWindowXPos"); + } + if (cell.hasFlag(CellFlags::WindowZNeg)) { + Ogre::Vector3 pos = origin + Ogre::Vector3(0, windowY, -halfCell - frameOffset); + Ogre::Quaternion rot(Ogre::Degree(180), Ogre::Vector3::UNIT_Y); + addFrame(extMesh, pos, rot, "extWindowZNeg"); + } + if (cell.hasFlag(CellFlags::WindowZPos)) { + Ogre::Vector3 pos = origin + Ogre::Vector3(0, windowY, halfCell + frameOffset); + Ogre::Quaternion rot(Ogre::Degree(0), Ogre::Vector3::UNIT_Y); + addFrame(extMesh, pos, rot, "extWindowZPos"); + } + } + + if (intMesh) { + if (cell.hasFlag(CellFlags::IntWindowXNeg)) { + Ogre::Vector3 pos = origin + Ogre::Vector3(-halfCell + frameOffset, windowY, 0); + Ogre::Quaternion rot(Ogre::Degree(-90), Ogre::Vector3::UNIT_Y); + addFrame(intMesh, pos, rot, "intWindowXNeg"); + } + if (cell.hasFlag(CellFlags::IntWindowXPos)) { + Ogre::Vector3 pos = origin + Ogre::Vector3(halfCell - frameOffset, windowY, 0); + Ogre::Quaternion rot(Ogre::Degree(90), Ogre::Vector3::UNIT_Y); + addFrame(intMesh, pos, rot, "intWindowXPos"); + } + if (cell.hasFlag(CellFlags::IntWindowZNeg)) { + Ogre::Vector3 pos = origin + Ogre::Vector3(0, windowY, -halfCell + frameOffset); + Ogre::Quaternion rot(Ogre::Degree(180), Ogre::Vector3::UNIT_Y); + addFrame(intMesh, pos, rot, "intWindowZNeg"); + } + if (cell.hasFlag(CellFlags::IntWindowZPos)) { + Ogre::Vector3 pos = origin + Ogre::Vector3(0, windowY, halfCell - frameOffset); + Ogre::Quaternion rot(Ogre::Degree(0), Ogre::Vector3::UNIT_Y); + addFrame(intMesh, pos, rot, "intWindowZPos"); + } + } + } +} + +void CellGridSystem::placeDoorFrames(flecs::entity entity, const CellGridComponent& grid, Ogre::StaticGeometry* geo, std::vector& frameEntities) +{ + std::string meshPrefix = "CellGrid_" + std::to_string(entity.id()) + "_"; + Ogre::MeshPtr extMesh = Ogre::MeshManager::getSingleton().getByName(meshPrefix + "door_external"); + Ogre::MeshPtr intMesh = Ogre::MeshManager::getSingleton().getByName(meshPrefix + "door_internal"); + + if (!extMesh && !intMesh) return; + + // Get the CellGrid entity's world position (Lot position) + Ogre::Vector3 lotWorldPos(0, 0, 0); + if (entity.has()) { + auto& transform = entity.get(); + if (transform.node) { + lotWorldPos = transform.node->_getDerivedPosition(); + } + } + + float halfCell = grid.cellSize / 2.0f; + float frameOffset = 0.2f * (grid.cellSize / 4.0f); + float doorHeight = 3.0f * (grid.cellHeight / 4.0f); + float doorY = doorHeight / 2.0f; + + auto addFrame = [&](Ogre::MeshPtr mesh, const Ogre::Vector3& pos, const Ogre::Quaternion& rot, const char* name) { + if (!mesh) return; + // Add lot world position to get actual world coordinates + Ogre::Vector3 worldPos = lotWorldPos + pos; + Ogre::LogManager::getSingleton().logMessage( + "CellGrid: Placing " + Ogre::String(name) + " frame at world pos=" + + std::to_string(worldPos.x) + "," + std::to_string(worldPos.y) + "," + std::to_string(worldPos.z)); + Ogre::Entity* ent = m_sceneMgr->createEntity(mesh->getName()); + geo->addEntity(ent, worldPos, rot); + frameEntities.push_back(ent); + }; + + for (const auto& cell : grid.cells) { + Ogre::Vector3 origin = grid.cellToWorld(cell.x, cell.y, cell.z); + + if (extMesh) { + if (cell.hasFlag(CellFlags::DoorXNeg)) { + Ogre::Vector3 pos = origin + Ogre::Vector3(-halfCell - frameOffset, doorY, 0); + Ogre::Quaternion rot(Ogre::Degree(-90), Ogre::Vector3::UNIT_Y); + addFrame(extMesh, pos, rot, "extDoorXNeg"); + } + if (cell.hasFlag(CellFlags::DoorXPos)) { + Ogre::Vector3 pos = origin + Ogre::Vector3(halfCell + frameOffset, doorY, 0); + Ogre::Quaternion rot(Ogre::Degree(90), Ogre::Vector3::UNIT_Y); + addFrame(extMesh, pos, rot, "extDoorXPos"); + } + if (cell.hasFlag(CellFlags::DoorZNeg)) { + Ogre::Vector3 pos = origin + Ogre::Vector3(0, doorY, -halfCell - frameOffset); + Ogre::Quaternion rot(Ogre::Degree(180), Ogre::Vector3::UNIT_Y); + addFrame(extMesh, pos, rot, "extDoorZNeg"); + } + if (cell.hasFlag(CellFlags::DoorZPos)) { + Ogre::Vector3 pos = origin + Ogre::Vector3(0, doorY, halfCell + frameOffset); + Ogre::Quaternion rot(Ogre::Degree(0), Ogre::Vector3::UNIT_Y); + addFrame(extMesh, pos, rot, "extDoorZPos"); + } + } + + if (intMesh) { + if (cell.hasFlag(CellFlags::IntDoorXNeg)) { + Ogre::Vector3 pos = origin + Ogre::Vector3(-halfCell + frameOffset, doorY, 0); + Ogre::Quaternion rot(Ogre::Degree(-90), Ogre::Vector3::UNIT_Y); + addFrame(intMesh, pos, rot, "intDoorXNeg"); + } + if (cell.hasFlag(CellFlags::IntDoorXPos)) { + Ogre::Vector3 pos = origin + Ogre::Vector3(halfCell - frameOffset, doorY, 0); + Ogre::Quaternion rot(Ogre::Degree(90), Ogre::Vector3::UNIT_Y); + addFrame(intMesh, pos, rot, "intDoorXPos"); + } + if (cell.hasFlag(CellFlags::IntDoorZNeg)) { + Ogre::Vector3 pos = origin + Ogre::Vector3(0, doorY, -halfCell + frameOffset); + Ogre::Quaternion rot(Ogre::Degree(180), Ogre::Vector3::UNIT_Y); + addFrame(intMesh, pos, rot, "intDoorZNeg"); + } + if (cell.hasFlag(CellFlags::IntDoorZPos)) { + Ogre::Vector3 pos = origin + Ogre::Vector3(0, doorY, halfCell - frameOffset); + Ogre::Quaternion rot(Ogre::Degree(0), Ogre::Vector3::UNIT_Y); + addFrame(intMesh, pos, rot, "intDoorZPos"); + } + } + } +} + +void CellGridSystem::destroyFrames(flecs::entity entity) +{ + auto it = m_entityMeshes.find(entity.id()); + if (it != m_entityMeshes.end()) { + if (it->second.framesStaticGeo) { + try { + m_sceneMgr->destroyStaticGeometry(it->second.framesStaticGeo); + } catch (...) {} + it->second.framesStaticGeo = nullptr; + } + + auto& meshMgr = Ogre::MeshManager::getSingleton(); + std::string meshPrefix = "CellGrid_" + std::to_string(entity.id()) + "_"; + + try { meshMgr.remove(meshPrefix + "window_external"); } catch (...) {} + try { meshMgr.remove(meshPrefix + "window_internal"); } catch (...) {} + try { meshMgr.remove(meshPrefix + "door_external"); } catch (...) {} + try { meshMgr.remove(meshPrefix + "door_internal"); } catch (...) {} + } +} diff --git a/src/features/editScene/systems/CellGridSystem.hpp b/src/features/editScene/systems/CellGridSystem.hpp index b542208..2f95642 100644 --- a/src/features/editScene/systems/CellGridSystem.hpp +++ b/src/features/editScene/systems/CellGridSystem.hpp @@ -57,6 +57,16 @@ private: // Build lot base geometry void buildLotBase(flecs::entity entity, struct LotComponent& lot); + // Build window and door frames (3D frame meshes) + void buildFrames(flecs::entity entity, const struct CellGridComponent& grid, const std::string& materialName); + void createWindowFrameMeshes(const struct CellGridComponent& grid, const std::string& materialName, + const std::string& meshPrefix, flecs::entity materialEntity); + void createDoorFrameMeshes(const struct CellGridComponent& grid, const std::string& materialName, + const std::string& meshPrefix, flecs::entity materialEntity); + void placeWindowFrames(flecs::entity entity, const struct CellGridComponent& grid, Ogre::StaticGeometry* geo, std::vector& frameEntities); + void placeDoorFrames(flecs::entity entity, const struct CellGridComponent& grid, Ogre::StaticGeometry* geo, std::vector& frameEntities); + void destroyFrames(flecs::entity entity); + // Convert triangle buffer to mesh Ogre::MeshPtr convertToMesh(const std::string& name, Procedural::TriangleBuffer& tb, const std::string& materialName); @@ -81,6 +91,15 @@ private: std::vector windowMeshes; std::string roofMesh; std::vector entities; + + // Frame meshes (unique per CellGrid for cellSize/cellHeight adaptation) + std::string externalWindowFrameMesh; + std::string internalWindowFrameMesh; + std::string externalDoorFrameMesh; + std::string internalDoorFrameMesh; + + // StaticGeometry for frames (per CellGrid) + Ogre::StaticGeometry* framesStaticGeo = nullptr; }; std::unordered_map m_entityMeshes;