diff --git a/src/features/editScene/components/CellGrid.hpp b/src/features/editScene/components/CellGrid.hpp index 2b35cd4..363500e 100644 --- a/src/features/editScene/components/CellGrid.hpp +++ b/src/features/editScene/components/CellGrid.hpp @@ -140,6 +140,10 @@ struct CellGridComponent { std::string extWindowFrameRectName; std::string intWindowFrameRectName; + // Roof texture rectangles + std::string roofTopRectName; + std::string roofSideRectName; + // Dirty flag - triggers rebuild bool dirty = true; unsigned int version = 0; diff --git a/src/features/editScene/systems/CellGridSystem.cpp b/src/features/editScene/systems/CellGridSystem.cpp index 0040832..0de4c07 100644 --- a/src/features/editScene/systems/CellGridSystem.cpp +++ b/src/features/editScene/systems/CellGridSystem.cpp @@ -216,7 +216,7 @@ void CellGridSystem::buildCellGrid(flecs::entity entity, // Build geometry parts Procedural::TriangleBuffer floorTb, ceilingTb, extWallTb, intWallTb, - extWindowsTb, intWindowsTb, extDoorsTb, intDoorsTb, roofTb; + extWindowsTb, intWindowsTb, extDoorsTb, intDoorsTb, roofTopTb, roofSideTb; buildFloorsAndCeilings(grid, floorTb, ceilingTb); buildWalls(grid, extWallTb, intWallTb, intWindowsTb); @@ -224,7 +224,7 @@ void CellGridSystem::buildCellGrid(flecs::entity entity, buildInternalCorners(grid, intWallTb); buildDoors(grid, extDoorsTb, intDoorsTb); buildWindows(grid, extWindowsTb, intWindowsTb); - buildRoofs(entity, grid, roofTb); + buildRoofs(entity, grid, roofTopTb, roofSideTb); // Apply UV mapping for each part (use rect if specified, otherwise default) Ogre::LogManager::getSingleton().logMessage( @@ -243,7 +243,8 @@ void CellGridSystem::buildCellGrid(flecs::entity entity, materialEntity); applyUVMappingToBuffer(intDoorsTb, grid.intWallRectName, materialEntity); - applyUVMappingToBuffer(roofTb, grid.extWallRectName, materialEntity); + applyUVMappingToBuffer(roofTopTb, grid.roofTopRectName, materialEntity); + applyUVMappingToBuffer(roofSideTb, grid.roofSideRectName, materialEntity); // Generate unique base name std::string baseName = "CellGrid_" + std::to_string(entity.id()) + "_" + @@ -311,15 +312,25 @@ void CellGridSystem::buildCellGrid(flecs::entity entity, meshData.entities.push_back(entity3d); } - // Roof - if (roofTb.getVertices().size() >= 3) { - meshData.roofMesh = baseName + "_roof"; + // Roof top pieces + if (roofTopTb.getVertices().size() >= 3) { + meshData.roofMesh = baseName + "_roofTop"; auto mesh = - convertToMesh(meshData.roofMesh, roofTb, materialName); + convertToMesh(meshData.roofMesh, roofTopTb, materialName); auto entity3d = m_sceneMgr->createEntity(meshData.roofMesh); transform.node->attachObject(entity3d); meshData.entities.push_back(entity3d); } + + // Roof side pieces (trim/gable ends) + if (roofSideTb.getVertices().size() >= 3) { + meshData.roofSideMesh = baseName + "_roofSide"; + auto mesh = + convertToMesh(meshData.roofSideMesh, roofSideTb, materialName); + auto entity3d = m_sceneMgr->createEntity(meshData.roofSideMesh); + transform.node->attachObject(entity3d); + meshData.entities.push_back(entity3d); + } // External doors if (extDoorsTb.getVertices().size() >= 3) { @@ -378,7 +389,8 @@ void CellGridSystem::buildCellGrid(flecs::entity entity, std::to_string(extWindowsTb.getVertices().size()) + " extDoors=" + std::to_string(extDoorsTb.getVertices().size()) + " intDoors=" + std::to_string(intDoorsTb.getVertices().size()) + - " roof=" + std::to_string(roofTb.getVertices().size())); + " roofTop=" + std::to_string(roofTopTb.getVertices().size()) + + " roofSide=" + std::to_string(roofSideTb.getVertices().size())); // Build window and door frames try { @@ -1734,9 +1746,60 @@ void CellGridSystem::buildWindows(const CellGridComponent &grid, } } +/** + * Deform roof side vertices to create triangular gable ends + * + * For Normal roof (deformXAxis=true): Deforms side pieces along X axis + * For Normal2 roof (deformXAxis=false): Deforms side pieces along Z axis + */ +static void deformRoofSideVertices(Procedural::TriangleBuffer &tb, + size_t startVertexIndex, + bool deformXAxis) +{ + auto &vertices = tb.getVertices(); + if (vertices.size() <= startVertexIndex) + return; + + // Find min/max Y and coord (X or Z depending on axis) + float minY = std::numeric_limits::max(); + float maxY = std::numeric_limits::lowest(); + float minCoord = std::numeric_limits::max(); + float maxCoord = std::numeric_limits::lowest(); + + for (size_t i = startVertexIndex; i < vertices.size(); ++i) { + const auto &v = vertices[i]; + minY = std::min(minY, v.mPosition.y); + maxY = std::max(maxY, v.mPosition.y); + float coord = deformXAxis ? v.mPosition.x : v.mPosition.z; + minCoord = std::min(minCoord, coord); + maxCoord = std::max(maxCoord, coord); + } + + float midCoord = minCoord + (maxCoord - minCoord) / 2.0f; + float coordRange = (maxCoord - minCoord) / 2.0f; + if (coordRange < 0.001f) + return; + + // Deform vertices to create triangular shape + for (size_t i = startVertexIndex; i < vertices.size(); ++i) { + auto &v = vertices[i]; + float md = maxY - v.mPosition.y; + float hm = (deformXAxis ? v.mPosition.x : v.mPosition.z) - + midCoord; + float he = (hm / coordRange) * md; + + if (deformXAxis) { + v.mPosition.x = he + midCoord; + } else { + v.mPosition.z = he + midCoord; + } + } +} + void CellGridSystem::buildRoofs(flecs::entity cellGridEntity, const CellGridComponent &grid, - Procedural::TriangleBuffer &roofTb) + Procedural::TriangleBuffer &roofTopTb, + Procedural::TriangleBuffer &roofSideTb) { // Get roof components from CellGrid's children // Roof components are children of the CellGrid entity @@ -1776,7 +1839,7 @@ void CellGridSystem::buildRoofs(flecs::entity cellGridEntity, switch (roof.type) { case RoofComponent::Flat: { - // Main roof box + // Main roof box -> roofTopTb float baseY = origin.y + roof.baseHeight / 2.0f; Procedural::BoxGenerator() .setSizeX(width) @@ -1788,11 +1851,12 @@ void CellGridSystem::buildRoofs(flecs::entity cellGridEntity, .setEnableNormals(true) .setPosition(Ogre::Vector3( origin.x, baseY, origin.z)) - .addToTriangleBuffer(roofTb); + .addToTriangleBuffer(roofTopTb); - // Edge trim pieces (Z+ edge) + // Edge trim pieces -> roofSideTb float trimBaseY = origin.y + roof.baseHeight / 2.0f; + // Z+ edge Procedural::BoxGenerator() .setSizeX(width) .setSizeY(roof.baseHeight + @@ -1806,7 +1870,7 @@ void CellGridSystem::buildRoofs(flecs::entity cellGridEntity, origin.x, trimBaseY + EXTEND, origin.z + depth / 2.0f + EXTEND / 2.0f)) - .addToTriangleBuffer(roofTb); + .addToTriangleBuffer(roofSideTb); // Z- edge Procedural::BoxGenerator() @@ -1822,7 +1886,7 @@ void CellGridSystem::buildRoofs(flecs::entity cellGridEntity, origin.x, trimBaseY + EXTEND, origin.z - depth / 2.0f - EXTEND / 2.0f)) - .addToTriangleBuffer(roofTb); + .addToTriangleBuffer(roofSideTb); // X+ edge Procedural::BoxGenerator() @@ -1838,7 +1902,7 @@ void CellGridSystem::buildRoofs(flecs::entity cellGridEntity, origin.x + width / 2.0f + EXTEND / 2.0f, trimBaseY + EXTEND, origin.z)) - .addToTriangleBuffer(roofTb); + .addToTriangleBuffer(roofSideTb); // X- edge Procedural::BoxGenerator() @@ -1854,12 +1918,12 @@ void CellGridSystem::buildRoofs(flecs::entity cellGridEntity, origin.x - width / 2.0f - EXTEND / 2.0f, trimBaseY + EXTEND, origin.z)) - .addToTriangleBuffer(roofTb); + .addToTriangleBuffer(roofSideTb); break; } case RoofComponent::Normal: { // Gable roof along X axis (ridge runs along Z) - // Two angled boxes forming a roof + // Two angled boxes -> roofTopTb float q = width / 2.0f; float m = 1.0f; float d = Ogre::Math::Sqrt(2.0f) * (q + m); @@ -1883,7 +1947,7 @@ void CellGridSystem::buildRoofs(flecs::entity cellGridEntity, baseY + roof.baseHeight + q / 2.0f - m / 2.0f, origin.z)) - .addToTriangleBuffer(roofTb); + .addToTriangleBuffer(roofTopTb); // Second angled box Procedural::BoxGenerator() @@ -1903,11 +1967,48 @@ void CellGridSystem::buildRoofs(flecs::entity cellGridEntity, baseY + roof.baseHeight + q / 2.0f - m / 2.0f, origin.z)) - .addToTriangleBuffer(roofTb); + .addToTriangleBuffer(roofTopTb); + + // Side trim pieces (gable ends) -> roofSideTb + size_t sideVertexStart = roofSideTb.getVertices().size(); + float sideY = baseY + q / 2.0f; + float sideHeight = q + roof.baseHeight * 3.0f; + + // Z+ edge + Procedural::BoxGenerator() + .setSizeX(width + EXTEND * 2.0f) + .setSizeY(sideHeight) + .setSizeZ(EXTEND) + .setNumSegX(3) + .setNumSegY(3) + .setNumSegZ(1) + .setEnableNormals(true) + .setPosition(Ogre::Vector3( + origin.x, sideY + roof.baseHeight, + origin.z + depth / 2.0f + EXTEND / 2.0f)) + .addToTriangleBuffer(roofSideTb); + + // Z- edge + Procedural::BoxGenerator() + .setSizeX(width + EXTEND * 2.0f) + .setSizeY(sideHeight) + .setSizeZ(EXTEND) + .setNumSegX(3) + .setNumSegY(3) + .setNumSegZ(1) + .setEnableNormals(true) + .setPosition(Ogre::Vector3( + origin.x, sideY + roof.baseHeight, + origin.z - depth / 2.0f - EXTEND / 2.0f)) + .addToTriangleBuffer(roofSideTb); + + // Apply vertex deformation to side pieces + deformRoofSideVertices(roofSideTb, sideVertexStart, true); break; } case RoofComponent::Normal2: { // Gable roof along Z axis (ridge runs along X) + // Two angled boxes -> roofTopTb float q = depth / 2.0f; float m = 1.0f; float d = Ogre::Math::Sqrt(2.0f) * (q + m); @@ -1931,7 +2032,7 @@ void CellGridSystem::buildRoofs(flecs::entity cellGridEntity, q / 2.0f - m / 2.0f, origin.z - depth / 2.0f + q / 2.0f - m / 2.0f)) - .addToTriangleBuffer(roofTb); + .addToTriangleBuffer(roofTopTb); // Second angled box Procedural::BoxGenerator() @@ -1951,19 +2052,58 @@ void CellGridSystem::buildRoofs(flecs::entity cellGridEntity, q / 2.0f - m / 2.0f, origin.z - depth / 2.0f + q + q / 2.0f + m / 2.0f)) - .addToTriangleBuffer(roofTb); + .addToTriangleBuffer(roofTopTb); + + // Side trim pieces (gable ends) -> roofSideTb + size_t sideVertexStart = roofSideTb.getVertices().size(); + float sideY = baseY + q / 2.0f; + float sideHeight = q + roof.baseHeight * 3.0f; + float sideDepth = depth + 2.0f * roof.baseHeight * 3.0f; + + // X+ edge + Procedural::BoxGenerator() + .setSizeX(EXTEND) + .setSizeY(sideHeight) + .setSizeZ(sideDepth) + .setNumSegX(2) + .setNumSegY(2) + .setNumSegZ(1) + .setEnableNormals(true) + .setPosition(Ogre::Vector3( + origin.x + width / 2.0f + EXTEND / 2.0f, + sideY + roof.baseHeight, origin.z)) + .addToTriangleBuffer(roofSideTb); + + // X- edge + Procedural::BoxGenerator() + .setSizeX(EXTEND) + .setSizeY(sideHeight) + .setSizeZ(sideDepth) + .setNumSegX(2) + .setNumSegY(2) + .setNumSegZ(1) + .setEnableNormals(true) + .setPosition(Ogre::Vector3( + origin.x - width / 2.0f - EXTEND / 2.0f, + sideY + roof.baseHeight, origin.z)) + .addToTriangleBuffer(roofSideTb); + + // Apply vertex deformation to side pieces + deformRoofSideVertices(roofSideTb, sideVertexStart, false); break; } case RoofComponent::Cone: { + // Cone -> roofTopTb 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); + cone.addToTriangleBuffer(roofTopTb); break; } case RoofComponent::Cylinder: { + // Cylinder -> roofTopTb Procedural::CylinderGenerator cyl; cyl.setRadius(width / 2.0f); cyl.setHeight(roof.baseHeight); @@ -1971,7 +2111,7 @@ void CellGridSystem::buildRoofs(flecs::entity cellGridEntity, origin.x, origin.y + roof.baseHeight / 2.0f, origin.z)); - cyl.addToTriangleBuffer(roofTb); + cyl.addToTriangleBuffer(roofTopTb); break; } } @@ -2043,6 +2183,7 @@ void CellGridSystem::destroyCellGridMeshes(flecs::entity entity) removeMesh(it->second.intWallMesh); removeMesh(it->second.intWindowsMesh); removeMesh(it->second.roofMesh); + removeMesh(it->second.roofSideMesh); for (const auto &name : it->second.doorMeshes) { removeMesh(name); } diff --git a/src/features/editScene/systems/CellGridSystem.hpp b/src/features/editScene/systems/CellGridSystem.hpp index 44e39bc..11f95aa 100644 --- a/src/features/editScene/systems/CellGridSystem.hpp +++ b/src/features/editScene/systems/CellGridSystem.hpp @@ -49,7 +49,8 @@ private: void buildInternalCorners(const struct CellGridComponent& grid, Procedural::TriangleBuffer& intWallTb); // Build roofs - void buildRoofs(flecs::entity lotEntity, const struct CellGridComponent& grid, Procedural::TriangleBuffer& roofTb); + void buildRoofs(flecs::entity lotEntity, const struct CellGridComponent& grid, + Procedural::TriangleBuffer& roofTopTb, Procedural::TriangleBuffer& roofSideTb); // Build plaza for district void buildDistrictPlaza(flecs::entity entity, struct DistrictComponent& district); @@ -90,6 +91,7 @@ private: std::vector doorMeshes; std::vector windowMeshes; std::string roofMesh; + std::string roofSideMesh; std::vector entities; // Frame meshes (unique per CellGrid for cellSize/cellHeight adaptation) diff --git a/src/features/editScene/systems/SceneSerializer.cpp b/src/features/editScene/systems/SceneSerializer.cpp index 677f67e..d6379cf 100644 --- a/src/features/editScene/systems/SceneSerializer.cpp +++ b/src/features/editScene/systems/SceneSerializer.cpp @@ -1278,6 +1278,8 @@ nlohmann::json SceneSerializer::serializeCellGrid(flecs::entity entity) json["intDoorFrameRectName"] = grid.intDoorFrameRectName; json["extWindowFrameRectName"] = grid.extWindowFrameRectName; json["intWindowFrameRectName"] = grid.intWindowFrameRectName; + json["roofTopRectName"] = grid.roofTopRectName; + json["roofSideRectName"] = grid.roofSideRectName; // Serialize cells nlohmann::json cellsJson = nlohmann::json::array(); @@ -1452,6 +1454,8 @@ void SceneSerializer::deserializeCellGrid(flecs::entity entity, const nlohmann:: grid.intDoorFrameRectName = json.value("intDoorFrameRectName", ""); grid.extWindowFrameRectName = json.value("extWindowFrameRectName", ""); grid.intWindowFrameRectName = json.value("intWindowFrameRectName", ""); + grid.roofTopRectName = json.value("roofTopRectName", ""); + grid.roofSideRectName = json.value("roofSideRectName", ""); // Deserialize cells if (json.contains("cells") && json["cells"].is_array()) { diff --git a/src/features/editScene/ui/CellGridEditor.cpp b/src/features/editScene/ui/CellGridEditor.cpp index 374e05d..d5cdf76 100644 --- a/src/features/editScene/ui/CellGridEditor.cpp +++ b/src/features/editScene/ui/CellGridEditor.cpp @@ -352,6 +352,12 @@ void CellGridEditor::renderTextureRectEditor(flecs::entity entity, CellGridCompo if (renderRectCombo("Int Window Frames", grid.intWindowFrameRectName)) { grid.markDirty(); } + if (renderRectCombo("Roof Top", grid.roofTopRectName)) { + grid.markDirty(); + } + if (renderRectCombo("Roof Side", grid.roofSideRectName)) { + grid.markDirty(); + } ImGui::Unindent(); }