diff --git a/src/features/editScene/components/CellGrid.hpp b/src/features/editScene/components/CellGrid.hpp index 55ca829..4ec69c7 100644 --- a/src/features/editScene/components/CellGrid.hpp +++ b/src/features/editScene/components/CellGrid.hpp @@ -207,6 +207,8 @@ struct RoomComponent { bool createCeiling = true; // Create ceiling cells bool createInteriorWalls = true; // Create iwallx-/+, iwallz-/+ around the room bool createWindows = false; // Convert exterior-facing walls to windows + bool fillRoomWithFurniture = false; // Automatically place furniture based on tags + unsigned int furnitureSeed = 42; // Seed for deterministic furniture placement // Dirty flag - triggers regeneration of cell grid bool dirty = true; diff --git a/src/features/editScene/systems/FurnitureLibrary.cpp b/src/features/editScene/systems/FurnitureLibrary.cpp index f84eb11..c1a5e93 100644 --- a/src/features/editScene/systems/FurnitureLibrary.cpp +++ b/src/features/editScene/systems/FurnitureLibrary.cpp @@ -3,7 +3,6 @@ #include #include #include -#include FurnitureLibrary &FurnitureLibrary::getInstance() { @@ -112,15 +111,13 @@ std::vector FurnitureLibrary::findByTags( const FurnitureDefinition *FurnitureLibrary::selectByTags( const std::vector &requiredTags, + std::mt19937 &rng, const std::vector &forbiddenTags) const { auto candidates = findByTags(requiredTags, forbiddenTags); if (candidates.empty()) return nullptr; - static std::mt19937 rng((unsigned)std::chrono::steady_clock::now() - .time_since_epoch() - .count()); std::uniform_int_distribution dist(0, candidates.size() - 1); return candidates[dist(rng)]; } diff --git a/src/features/editScene/systems/FurnitureLibrary.hpp b/src/features/editScene/systems/FurnitureLibrary.hpp index 2bcd717..c14deb8 100644 --- a/src/features/editScene/systems/FurnitureLibrary.hpp +++ b/src/features/editScene/systems/FurnitureLibrary.hpp @@ -4,6 +4,7 @@ #include #include #include +#include struct FurnitureDefinition { std::string name; @@ -30,6 +31,7 @@ public: const FurnitureDefinition *selectByTags( const std::vector &requiredTags, + std::mt19937 &rng, const std::vector &forbiddenTags = {}) const; private: diff --git a/src/features/editScene/systems/RoomLayoutSystem.cpp b/src/features/editScene/systems/RoomLayoutSystem.cpp index 4395874..efc15dd 100644 --- a/src/features/editScene/systems/RoomLayoutSystem.cpp +++ b/src/features/editScene/systems/RoomLayoutSystem.cpp @@ -860,6 +860,8 @@ void RoomLayoutSystem::generateRoomFurniture(RoomComponent& room, if (room.tags.empty()) return; + std::mt19937 rng(room.furnitureSeed); + // Clear existing furniture in room bounds for (auto it = grid.furnitureCells.begin(); it != grid.furnitureCells.end();) { @@ -889,12 +891,6 @@ void RoomLayoutSystem::generateRoomFurniture(RoomComponent& room, CellFlags::IntWindowXNeg | CellFlags::WindowXNeg, CellFlags::IntWindowXPos | CellFlags::WindowXPos }; - const uint64_t DOOR_FLAGS_BY_SIDE[4] = { - CellFlags::IntDoorZNeg | CellFlags::DoorZNeg, - CellFlags::IntDoorZPos | CellFlags::DoorZPos, - CellFlags::IntDoorXNeg | CellFlags::DoorXNeg, - CellFlags::IntDoorXPos | CellFlags::DoorXPos - }; auto cellHasAnyDoor = [&](int x, int y, int z) -> bool { const Cell *cell = grid.findCell(x, y, z); @@ -908,61 +904,61 @@ void RoomLayoutSystem::generateRoomFurniture(RoomComponent& room, return false; return (cell->flags & CellFlags::AllWindows) != 0; }; - auto cellHasAnyWall = [&](int x, int y, int z) -> bool { - const Cell *cell = grid.findCell(x, y, z); - if (!cell) - return false; - return (cell->flags & CellFlags::AllWalls) != 0; - }; auto canPlaceFurniture = [&](const FurnitureDefinition *def, int x, int y, int z) -> bool { if (!def) return false; - // If cell has any door, furniture must have "door" tag if (cellHasAnyDoor(x, y, z)) { if (std::find(def->tags.begin(), def->tags.end(), "door") == def->tags.end()) return false; } - // If cell has any window, furniture must NOT have "nowindow" tag if (cellHasAnyWindow(x, y, z)) { if (std::find(def->tags.begin(), def->tags.end(), "nowindow") != def->tags.end()) return false; } - // If cell has no walls, furniture must have "nowall" tag - if (!cellHasAnyWall(x, y, z)) { - if (std::find(def->tags.begin(), def->tags.end(), - "nowall") == def->tags.end()) - return false; - } return true; }; + auto tryPlace = [&](int cx, int cy, int cz, int side, + const std::vector &tags, + const std::vector ¬ags, + bool onWindow, + std::mt19937 &rng) -> const FurnitureDefinition * { + if (grid.findFurnitureCell(cx, cy, cz)) + return nullptr; + const Cell *cell = grid.findCell(cx, cy, cz); + if (!cell) + return nullptr; + uint64_t sideFlags = onWindow ? WINDOW_FLAGS_BY_SIDE[side] : + WALL_FLAGS_BY_SIDE[side]; + if ((cell->flags & sideFlags) == 0) + return nullptr; + auto *def = FurnitureLibrary::getInstance().selectByTags( + tags, rng, notags); + if (!def) + return nullptr; + if (!canPlaceFurniture(def, cx, cy, cz)) + return nullptr; + return def; + }; + auto placeOne = [&](int cx, int cy, int cz, int side, const std::vector &tags, + const std::vector &fallbackTags, const std::vector ¬ags, - bool onWindow) -> bool { - if (grid.findFurnitureCell(cx, cy, cz)) - return false; - const Cell *cell = grid.findCell(cx, cy, cz); - if (!cell) - return false; - - uint64_t sideFlags = onWindow ? WINDOW_FLAGS_BY_SIDE[side] : - WALL_FLAGS_BY_SIDE[side]; - if ((cell->flags & sideFlags) == 0) - return false; - - auto *def = FurnitureLibrary::getInstance().selectByTags( - tags, notags); + bool onWindow, + std::mt19937 &rng) -> bool { + auto *def = tryPlace(cx, cy, cz, side, tags, notags, + onWindow, rng); + if (!def && !fallbackTags.empty()) { + def = tryPlace(cx, cy, cz, side, fallbackTags, + notags, onWindow, rng); + } if (!def) return false; - - if (!canPlaceFurniture(def, cx, cy, cz)) - return false; - auto &fcell = grid.getOrCreateFurnitureCell(cx, cy, cz); fcell.furnitureType = def->name; fcell.rotation = ROTATION_BY_SIDE[side]; @@ -982,60 +978,91 @@ void RoomLayoutSystem::generateRoomFurniture(RoomComponent& room, struct TagData { std::vector tags; + std::vector fallbackTags; std::vector notagsWalls; std::vector notagsWindows; bool essential; bool filler; }; - TagData taglist[] = { - { etags, { "nowall", "nowindow" }, { "nowindow" }, true, + std::vector taglist = { + { etags, {}, { "nowall", "nowindow" }, { "nowindow" }, true, false }, - { itags, { "nowall", "nowindow" }, { "nowindow" }, false, + { itags, {}, { "nowall", "nowindow" }, { "nowindow" }, false, false }, - { otags, { "nowall", "nowindow" }, { "nowindow" }, false, + { otags, {}, { "nowall", "nowindow" }, { "nowindow" }, false, false }, - { ftags, { "nowall", "nowindow" }, { "nowindow" }, false, - true }, + { ftags, + room.fillRoomWithFurniture ? baseTags : + std::vector(), + { "nowall", "nowindow" }, + { "nowindow" }, false, true }, }; - // Midpoints for each side - std::tuple mpoints[4] = { + // Midpoints for each side - original has 8 entries (each wall twice) + std::vector> mpoints = { { midX, room.minY, room.minZ }, // side 0 (Z-) { midX, room.minY, room.maxZ - 1 }, // side 1 (Z+) { room.minX, room.minY, midZ }, // side 2 (X-) { room.maxX - 1, room.minY, midZ }, // side 3 (X+) + { midX, room.minY, room.minZ }, // side 0 again + { midX, room.minY, room.maxZ - 1 }, // side 1 again + { room.minX, room.minY, midZ }, // side 2 again + { room.maxX - 1, room.minY, midZ }, // side 3 again }; for (const auto &mtags : taglist) { bool placed = false; // Try wall midpoints - for (int side = 0; side < 4 && !placed; ++side) { - int cx = std::get<0>(mpoints[side]); - int cy = std::get<1>(mpoints[side]); - int cz = std::get<2>(mpoints[side]); - placed = placeOne(cx, cy, cz, side, mtags.tags, - mtags.notagsWalls, false); - if (placed && !mtags.filler) - break; + for (auto &p : mpoints) { + int cx = std::get<0>(p); + int cy = std::get<1>(p); + int cz = std::get<2>(p); + int side = -1; + if (cz == room.minZ) + side = 0; + else if (cz == room.maxZ - 1) + side = 1; + else if (cx == room.minX) + side = 2; + else if (cx == room.maxX - 1) + side = 3; + if (side < 0) + continue; + if (!cellHasAnyDoor(cx, cy, cz)) { + placed = placeOne(cx, cy, cz, side, + mtags.tags, + mtags.fallbackTags, + mtags.notagsWalls, + false, + rng); + if (placed && !mtags.filler) + break; + } if (mtags.filler) placed = false; } // Try wall ranges (all edge cells on each side) - if (!placed) { - for (int side = 0; side < 4 && !placed; ++side) { + if (!placed || mtags.filler) { + for (int side = 0; side < 4; ++side) { auto edgeCells = getRoomEdgeCells(room, side); for (const auto &cell : edgeCells) { int cx = cell.first; int cz = cell.second; int cy = room.minY; - placed = placeOne(cx, cy, cz, side, - mtags.tags, - mtags.notagsWalls, - false); - if (placed && !mtags.filler) - break; + if (!cellHasAnyDoor(cx, cy, cz)) { + placed = placeOne(cx, cy, cz, side, + mtags.tags, + mtags.fallbackTags, + mtags.notagsWalls, + false, + rng); + if (placed && !mtags.filler) + break; + } + if (mtags.filler) + placed = false; } if (placed && !mtags.filler) break; @@ -1046,42 +1073,68 @@ void RoomLayoutSystem::generateRoomFurniture(RoomComponent& room, placed = false; // Try window midpoints - if (!placed) { - for (int side = 0; side < 4 && !placed; ++side) { - int cx = std::get<0>(mpoints[side]); - int cy = std::get<1>(mpoints[side]); - int cz = std::get<2>(mpoints[side]); - placed = placeOne(cx, cy, cz, side, mtags.tags, - mtags.notagsWindows, true); - if (placed && !mtags.filler) - break; + if (!placed || mtags.filler) { + for (auto &p : mpoints) { + int cx = std::get<0>(p); + int cy = std::get<1>(p); + int cz = std::get<2>(p); + int side = -1; + if (cz == room.minZ) + side = 0; + else if (cz == room.maxZ - 1) + side = 1; + else if (cx == room.minX) + side = 2; + else if (cx == room.maxX - 1) + side = 3; + if (side < 0) + continue; + if (!cellHasAnyDoor(cx, cy, cz)) { + placed = placeOne(cx, cy, cz, side, + mtags.tags, + mtags.fallbackTags, + mtags.notagsWindows, + true, + rng); + if (placed && !mtags.filler) + break; + } if (mtags.filler) placed = false; } } // Try window ranges - if (!placed) { - for (int side = 0; side < 4 && !placed; ++side) { + if (!placed || mtags.filler) { + for (int side = 0; side < 4; ++side) { auto edgeCells = getRoomEdgeCells(room, side); for (const auto &cell : edgeCells) { int cx = cell.first; int cz = cell.second; int cy = room.minY; - placed = placeOne(cx, cy, cz, side, - mtags.tags, - mtags.notagsWindows, - true); - if (placed && !mtags.filler) - break; + if (!cellHasAnyDoor(cx, cy, cz)) { + placed = placeOne(cx, cy, cz, side, + mtags.tags, + mtags.fallbackTags, + mtags.notagsWindows, + true, + rng); + if (placed && !mtags.filler) + break; + } + if (mtags.filler) + placed = false; } if (placed && !mtags.filler) break; } } - if (!placed && !mtags.filler) { - break; // do not place anything if essentials were not placed + // Only break if essential was not placed. + // For important/optional/filler, continue to next stage + // even if nothing was placed. + if (!placed && mtags.essential) { + break; } } diff --git a/src/features/editScene/systems/RoomLayoutSystem.hpp b/src/features/editScene/systems/RoomLayoutSystem.hpp index cd0fd40..f658e54 100644 --- a/src/features/editScene/systems/RoomLayoutSystem.hpp +++ b/src/features/editScene/systems/RoomLayoutSystem.hpp @@ -3,6 +3,7 @@ #include #include + /** * @brief Room Layout System - processes Room components and generates cell grid * diff --git a/src/features/editScene/systems/SceneSerializer.cpp b/src/features/editScene/systems/SceneSerializer.cpp index afaed35..20014a7 100644 --- a/src/features/editScene/systems/SceneSerializer.cpp +++ b/src/features/editScene/systems/SceneSerializer.cpp @@ -1405,6 +1405,8 @@ nlohmann::json SceneSerializer::serializeRoom(flecs::entity entity) json["createCeiling"] = room.createCeiling; json["createInteriorWalls"] = room.createInteriorWalls; json["createWindows"] = room.createWindows; + json["fillRoomWithFurniture"] = room.fillRoomWithFurniture; + json["furnitureSeed"] = room.furnitureSeed; // Serialize exits (bool array for Z-, Z+, X-, X+) nlohmann::json exits = nlohmann::json::array(); @@ -1656,6 +1658,8 @@ void SceneSerializer::deserializeRoom(flecs::entity entity, const nlohmann::json room.createCeiling = json.value("createCeiling", true); room.createInteriorWalls = json.value("createInteriorWalls", true); room.createWindows = json.value("createWindows", false); + room.fillRoomWithFurniture = json.value("fillRoomWithFurniture", false); + room.furnitureSeed = json.value("furnitureSeed", 42u); // Load exits (bool array for Z-, Z+, X-, X+) if (json.contains("exits") && json["exits"].is_array()) { diff --git a/src/features/editScene/ui/RoomEditor.cpp b/src/features/editScene/ui/RoomEditor.cpp index fcbc5a3..ff4b990 100644 --- a/src/features/editScene/ui/RoomEditor.cpp +++ b/src/features/editScene/ui/RoomEditor.cpp @@ -44,6 +44,13 @@ bool RoomEditor::renderComponent(flecs::entity entity, RoomComponent& room) if (ImGui::Checkbox("Create Ceiling", &room.createCeiling)) modified = true; if (ImGui::Checkbox("Create Interior Walls", &room.createInteriorWalls)) modified = true; if (ImGui::Checkbox("Create Windows", &room.createWindows)) modified = true; + if (ImGui::Checkbox("Fill Room with Furniture", &room.fillRoomWithFurniture)) modified = true; + int seed = static_cast(room.furnitureSeed); + if (ImGui::InputInt("Furniture Seed", &seed)) { + room.furnitureSeed = static_cast(seed); + modified = true; + } + ImGui::TextDisabled("Change seed for different furniture arrangement"); ImGui::TextDisabled("(Windows are created after all doors)"); ImGui::Separator();