Furiture placement works

This commit is contained in:
2026-04-14 21:44:06 +03:00
parent febeb8ff8d
commit 77f93659d5
7 changed files with 151 additions and 85 deletions

View File

@@ -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;

View File

@@ -3,7 +3,6 @@
#include <OgreDataStream.h>
#include <OgreLogManager.h>
#include <algorithm>
#include <random>
FurnitureLibrary &FurnitureLibrary::getInstance()
{
@@ -112,15 +111,13 @@ std::vector<const FurnitureDefinition *> FurnitureLibrary::findByTags(
const FurnitureDefinition *FurnitureLibrary::selectByTags(
const std::vector<std::string> &requiredTags,
std::mt19937 &rng,
const std::vector<std::string> &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<size_t> dist(0, candidates.size() - 1);
return candidates[dist(rng)];
}

View File

@@ -4,6 +4,7 @@
#include <nlohmann/json.hpp>
#include <string>
#include <vector>
#include <random>
struct FurnitureDefinition {
std::string name;
@@ -30,6 +31,7 @@ public:
const FurnitureDefinition *selectByTags(
const std::vector<std::string> &requiredTags,
std::mt19937 &rng,
const std::vector<std::string> &forbiddenTags = {}) const;
private:

View File

@@ -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<std::string> &tags,
const std::vector<std::string> &notags,
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<std::string> &tags,
const std::vector<std::string> &fallbackTags,
const std::vector<std::string> &notags,
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<std::string> tags;
std::vector<std::string> fallbackTags;
std::vector<std::string> notagsWalls;
std::vector<std::string> notagsWindows;
bool essential;
bool filler;
};
TagData taglist[] = {
{ etags, { "nowall", "nowindow" }, { "nowindow" }, true,
std::vector<TagData> 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<std::string>(),
{ "nowall", "nowindow" },
{ "nowindow" }, false, true },
};
// Midpoints for each side
std::tuple<int, int, int> mpoints[4] = {
// Midpoints for each side - original has 8 entries (each wall twice)
std::vector<std::tuple<int, int, int>> 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;
}
}

View File

@@ -3,6 +3,7 @@
#include <flecs.h>
#include <Ogre.h>
/**
* @brief Room Layout System - processes Room components and generates cell grid
*

View File

@@ -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()) {

View File

@@ -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<int>(room.furnitureSeed);
if (ImGui::InputInt("Furniture Seed", &seed)) {
room.furnitureSeed = static_cast<unsigned int>(seed);
modified = true;
}
ImGui::TextDisabled("Change seed for different furniture arrangement");
ImGui::TextDisabled("(Windows are created after all doors)");
ImGui::Separator();