Almost good generation of room layout
This commit is contained in:
@@ -23,6 +23,7 @@ set(EDITSCENE_SOURCES
|
||||
systems/ProceduralMaterialSystem.cpp
|
||||
systems/ProceduralMeshSystem.cpp
|
||||
systems/CellGridSystem.cpp
|
||||
systems/RoomLayoutSystem.cpp
|
||||
ui/TransformEditor.cpp
|
||||
ui/RenderableEditor.cpp
|
||||
ui/PhysicsColliderEditor.cpp
|
||||
@@ -43,6 +44,9 @@ set(EDITSCENE_SOURCES
|
||||
ui/TownEditor.cpp
|
||||
ui/RoofEditor.cpp
|
||||
ui/RoomEditor.cpp
|
||||
|
||||
|
||||
ui/ClearAreaEditor.cpp
|
||||
ui/FurnitureTemplateEditor.cpp
|
||||
ui/ComponentRegistration.cpp
|
||||
components/LightModule.cpp
|
||||
@@ -82,6 +86,7 @@ set(EDITSCENE_HEADERS
|
||||
components/CellGrid.hpp
|
||||
systems/EditorUISystem.hpp
|
||||
systems/CellGridSystem.hpp
|
||||
systems/RoomLayoutSystem.hpp
|
||||
systems/ProceduralMaterialSystem.hpp
|
||||
systems/ProceduralMeshSystem.hpp
|
||||
systems/ProceduralTextureSystem.hpp
|
||||
@@ -114,6 +119,9 @@ set(EDITSCENE_HEADERS
|
||||
ui/TownEditor.hpp
|
||||
ui/RoofEditor.hpp
|
||||
ui/RoomEditor.hpp
|
||||
|
||||
|
||||
ui/ClearAreaEditor.hpp
|
||||
ui/FurnitureTemplateEditor.hpp
|
||||
camera/EditorCamera.hpp
|
||||
gizmo/Gizmo.hpp
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "systems/ProceduralMaterialSystem.hpp"
|
||||
#include "systems/ProceduralMeshSystem.hpp"
|
||||
#include "systems/CellGridSystem.hpp"
|
||||
#include "systems/RoomLayoutSystem.hpp"
|
||||
#include "camera/EditorCamera.hpp"
|
||||
#include "components/EntityName.hpp"
|
||||
#include "components/Transform.hpp"
|
||||
@@ -194,6 +195,10 @@ void EditorApp::setup()
|
||||
// Setup CellGrid system
|
||||
m_cellGridSystem = std::make_unique<CellGridSystem>(m_world, m_sceneMgr);
|
||||
m_cellGridSystem->initialize();
|
||||
|
||||
// Setup RoomLayout system
|
||||
m_roomLayoutSystem = std::make_unique<RoomLayoutSystem>(m_world, m_sceneMgr);
|
||||
m_roomLayoutSystem->initialize();
|
||||
|
||||
// Add default entities to UI cache
|
||||
for (auto &e : m_defaultEntities) {
|
||||
@@ -414,7 +419,12 @@ bool EditorApp::frameRenderingQueued(const Ogre::FrameEvent &evt)
|
||||
m_proceduralMeshSystem->update();
|
||||
}
|
||||
|
||||
// Update CellGrid system
|
||||
// Update RoomLayout system FIRST (generates cells for CellGrid)
|
||||
if (m_roomLayoutSystem) {
|
||||
m_roomLayoutSystem->update();
|
||||
}
|
||||
|
||||
// Update CellGrid system (builds mesh from cells)
|
||||
if (m_cellGridSystem) {
|
||||
m_cellGridSystem->update();
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ class ProceduralTextureSystem;
|
||||
class ProceduralMaterialSystem;
|
||||
class ProceduralMeshSystem;
|
||||
class CellGridSystem;
|
||||
class RoomLayoutSystem;
|
||||
|
||||
/**
|
||||
* RenderTargetListener for ImGui frame management
|
||||
@@ -97,6 +98,7 @@ private:
|
||||
std::unique_ptr<ProceduralMaterialSystem> m_proceduralMaterialSystem;
|
||||
std::unique_ptr<ProceduralMeshSystem> m_proceduralMeshSystem;
|
||||
std::unique_ptr<CellGridSystem> m_cellGridSystem;
|
||||
std::unique_ptr<RoomLayoutSystem> m_roomLayoutSystem;
|
||||
|
||||
// State
|
||||
uint16_t m_currentModifiers;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <chrono>
|
||||
#include <flecs.h>
|
||||
#include <Ogre.h>
|
||||
|
||||
@@ -188,22 +189,165 @@ struct CellGridComponent {
|
||||
/**
|
||||
* @brief Room definition within a cell grid
|
||||
*
|
||||
* Rooms are rectangular areas that can be connected by doors
|
||||
* Rooms are rectangular areas that can be connected by doors.
|
||||
* This is the ECS version of the Lua room() function.
|
||||
*/
|
||||
struct RoomComponent {
|
||||
// Room bounds (in cell coordinates, inclusive)
|
||||
// Room bounds (in cell coordinates)
|
||||
// A room from (minX, minZ) to (maxX-1, maxZ-1) inclusive
|
||||
int minX = 0, minY = 0, minZ = 0;
|
||||
int maxX = 0, maxY = 0, maxZ = 0;
|
||||
int maxX = 1, maxY = 1, maxZ = 1; // exclusive max (size = max - min)
|
||||
|
||||
// Room name/tag for furniture placement rules
|
||||
std::string roomType; // e.g., "bedroom", "kitchen", "hallway"
|
||||
std::vector<std::string> tags;
|
||||
|
||||
// Connected room indices
|
||||
std::vector<int> connectedRooms;
|
||||
// Generation flags
|
||||
bool createFloor = true; // Create floor cells
|
||||
bool createCeiling = true; // Create ceiling cells
|
||||
bool createInteriorWalls = true; // Create iwallx-/+, iwallz-/+ around the room
|
||||
bool createWindows = false; // Convert exterior-facing walls to windows
|
||||
|
||||
// Exit directions (0-3 = Z-, Z+, X-, X+)
|
||||
std::vector<int> exits;
|
||||
// Dirty flag - triggers regeneration of cell grid
|
||||
bool dirty = true;
|
||||
|
||||
void markDirty() { dirty = true; }
|
||||
|
||||
// Helper to get size
|
||||
int getSizeX() const { return maxX - minX; }
|
||||
int getSizeY() const { return maxY - minY; }
|
||||
int getSizeZ() const { return maxZ - minZ; }
|
||||
|
||||
// Check if a cell position is inside this room
|
||||
bool contains(int x, int y, int z) const {
|
||||
return x >= minX && x < maxX &&
|
||||
y >= minY && y < maxY &&
|
||||
z >= minZ && z < maxZ;
|
||||
}
|
||||
|
||||
// Check if a cell is on the room edge (for wall placement)
|
||||
bool isOnEdge(int x, int z) const {
|
||||
return x == minX || x == maxX - 1 || z == minZ || z == maxZ - 1;
|
||||
}
|
||||
|
||||
// Get the side of the room this cell is on (0=Z-, 1=Z+, 2=X-, 3=X+, -1=not on edge)
|
||||
int getEdgeSide(int x, int z) const {
|
||||
if (!isOnEdge(x, z)) return -1;
|
||||
if (z == minZ) return 0; // Z- (north)
|
||||
if (z == maxZ - 1) return 1; // Z+ (south)
|
||||
if (x == minX) return 2; // X- (west)
|
||||
if (x == maxX - 1) return 3; // X+ (east)
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Persistent unique ID for this room (survives save/load)
|
||||
// If empty, will be auto-generated during serialization
|
||||
std::string persistentId;
|
||||
|
||||
// Connected room persistent IDs (bidirectional connections)
|
||||
// Using persistent IDs instead of entity IDs because entity IDs change on save/load
|
||||
std::vector<std::string> connectedRoomIds;
|
||||
|
||||
// Exit doors for outside-facing walls (0=Z-, 1=Z+, 2=X-, 3=X+)
|
||||
// An exit is always a door placed on a wall that faces outside (not connected to another room)
|
||||
bool exits[4] = { false, false, false, false }; // Z-, Z+, X-, X+
|
||||
|
||||
// Generate a persistent ID if one doesn't exist
|
||||
void ensurePersistentId(flecs::entity_t entityId = 0) {
|
||||
if (persistentId.empty()) {
|
||||
// Generate unique ID based on timestamp + random + optional entity ID
|
||||
auto now = std::chrono::high_resolution_clock::now();
|
||||
auto nanos = std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
now.time_since_epoch()).count();
|
||||
persistentId = "room_" + std::to_string(nanos);
|
||||
if (entityId != 0) {
|
||||
persistentId += "_" + std::to_string(entityId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add a connection to another room (bidirectional, by persistent ID)
|
||||
void addConnection(const std::string& otherRoomId) {
|
||||
// Check if not already connected
|
||||
for (const auto& id : connectedRoomIds) {
|
||||
if (id == otherRoomId) return;
|
||||
}
|
||||
connectedRoomIds.push_back(otherRoomId);
|
||||
}
|
||||
|
||||
// Remove a connection to another room
|
||||
void removeConnection(const std::string& otherRoomId) {
|
||||
connectedRoomIds.erase(
|
||||
std::remove(connectedRoomIds.begin(), connectedRoomIds.end(), otherRoomId),
|
||||
connectedRoomIds.end()
|
||||
);
|
||||
}
|
||||
|
||||
// Check if connected to a room (by persistent ID)
|
||||
bool isConnectedTo(const std::string& otherRoomId) const {
|
||||
for (const auto& id : connectedRoomIds) {
|
||||
if (id == otherRoomId) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Helper to set all exits at once
|
||||
void setAllExits(bool value) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
exits[i] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to set a specific exit
|
||||
void setExit(int side, bool value) {
|
||||
if (side >= 0 && side < 4) {
|
||||
exits[side] = value;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Room exits - defines external exits from a room
|
||||
*
|
||||
* This replaces the Lua create_exit0/1/2/3() functions.
|
||||
* Creates external walls/doors/windows on the room edges that face outside.
|
||||
*/
|
||||
/**
|
||||
* @brief Clear Area - clears cells in a region before room generation
|
||||
*
|
||||
* This replaces the Lua clear_area() and clear_furniture_area() functions.
|
||||
* It clears all cells within the specified bounds before any room generation happens.
|
||||
* This component is processed first in the RoomLayoutSystem.
|
||||
*/
|
||||
struct ClearAreaComponent {
|
||||
// Area bounds (in cell coordinates, inclusive min, exclusive max)
|
||||
int minX = 0, minY = 0, minZ = 0;
|
||||
int maxX = 1, maxY = 1, maxZ = 1;
|
||||
|
||||
// What to clear
|
||||
bool clearCells = true; // Clear cell flags (walls, floors, etc.)
|
||||
bool clearFurniture = true; // Clear furniture placements
|
||||
bool clearRoofs = false; // Clear roof definitions (children with RoofComponent)
|
||||
bool clearRooms = false; // Remove existing room entities (children with RoomComponent)
|
||||
|
||||
// Processed flag - cleared each time dirty is set
|
||||
bool processed = false;
|
||||
|
||||
// Dirty flag - clear again when true
|
||||
bool dirty = true;
|
||||
|
||||
void markDirty() { dirty = true; }
|
||||
|
||||
// Helper to set bounds
|
||||
void setBounds(int x1, int z1, int x2, int z2, int y = 0) {
|
||||
minX = x1; minZ = z1; minY = y;
|
||||
maxX = x2; maxZ = z2; maxY = y + 1;
|
||||
}
|
||||
|
||||
// Helper to get size
|
||||
int getSizeX() const { return maxX - minX; }
|
||||
int getSizeY() const { return maxY - minY; }
|
||||
int getSizeZ() const { return maxZ - minZ; }
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include "../ui/TownEditor.hpp"
|
||||
#include "../ui/RoofEditor.hpp"
|
||||
#include "../ui/RoomEditor.hpp"
|
||||
|
||||
#include "../ui/ClearAreaEditor.hpp"
|
||||
#include "../ui/FurnitureTemplateEditor.hpp"
|
||||
|
||||
// Register CellGrid component
|
||||
@@ -103,11 +105,11 @@ REGISTER_COMPONENT_GROUP("Roof", "Cell Grid", RoofComponent, RoofEditor)
|
||||
);
|
||||
}
|
||||
|
||||
// Register Room component
|
||||
REGISTER_COMPONENT_GROUP("Room", "Cell Grid", RoomComponent, RoomEditor)
|
||||
// Register Room component (in Room Layout group)
|
||||
REGISTER_COMPONENT_GROUP("Room", "Room Layout", RoomComponent, RoomEditor)
|
||||
{
|
||||
registry.registerComponent<RoomComponent>(
|
||||
"Room", "Cell Grid",
|
||||
"Room", "Room Layout",
|
||||
std::make_unique<RoomEditor>(),
|
||||
[sceneMgr](flecs::entity e) {
|
||||
if (!e.has<RoomComponent>()) {
|
||||
@@ -140,3 +142,26 @@ REGISTER_COMPONENT_GROUP("Furniture Template", "Cell Grid", FurnitureTemplateCom
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Register ClearArea component (in Room Layout group)
|
||||
REGISTER_COMPONENT_GROUP("Clear Area", "Room Layout", ClearAreaComponent, ClearAreaEditor)
|
||||
{
|
||||
registry.registerComponent<ClearAreaComponent>(
|
||||
"Clear Area", "Room Layout",
|
||||
std::make_unique<ClearAreaEditor>(),
|
||||
[sceneMgr](flecs::entity e) {
|
||||
if (!e.has<ClearAreaComponent>()) {
|
||||
e.set<ClearAreaComponent>({});
|
||||
}
|
||||
},
|
||||
[sceneMgr](flecs::entity e) {
|
||||
if (e.has<ClearAreaComponent>()) {
|
||||
e.remove<ClearAreaComponent>();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Note: ExteriorGenerationComponent has been removed.
|
||||
// Exterior generation now automatically runs at the end of the RoomLayoutSystem pipeline.
|
||||
// This mirrors the original Lua behavior where create_exterior() was called after all rooms were defined.
|
||||
|
||||
@@ -7,6 +7,8 @@ FileSystem=resources/materials/scripts
|
||||
FileSystem=resources/meshes
|
||||
FileSystem=resources/textures
|
||||
FileSystem=resources/buildings
|
||||
FileSystem=resources/buildings/parts/pier
|
||||
FileSystem=resources/buildings/parts/furniture
|
||||
FileSystem=resources/vehicles
|
||||
|
||||
[Popular]
|
||||
|
||||
@@ -655,7 +655,22 @@ void EditorUISystem::renderComponentList(flecs::entity entity)
|
||||
// Render Room if present
|
||||
if (entity.has<RoomComponent>()) {
|
||||
auto &room = entity.get_mut<RoomComponent>();
|
||||
m_componentRegistry.render<RoomComponent>(entity, room);
|
||||
if (m_componentRegistry.render<RoomComponent>(entity, room)) {
|
||||
room.markDirty();
|
||||
}
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Render ClearArea if present
|
||||
if (entity.has<ClearAreaComponent>()) {
|
||||
auto &clearArea = entity.get_mut<ClearAreaComponent>();
|
||||
if (m_componentRegistry.render<ClearAreaComponent>(entity, clearArea)) {
|
||||
clearArea.markDirty();
|
||||
}
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
|
||||
767
src/features/editScene/systems/RoomLayoutSystem.cpp
Normal file
767
src/features/editScene/systems/RoomLayoutSystem.cpp
Normal file
@@ -0,0 +1,767 @@
|
||||
#include "RoomLayoutSystem.hpp"
|
||||
#include "../components/CellGrid.hpp"
|
||||
#include <OgreLogManager.h>
|
||||
#include <climits>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
|
||||
RoomLayoutSystem::RoomLayoutSystem(flecs::world& world, Ogre::SceneManager* sceneMgr)
|
||||
: m_world(world)
|
||||
, m_sceneMgr(sceneMgr)
|
||||
, m_roomQuery(world.query<RoomComponent>())
|
||||
, m_clearAreaQuery(world.query<ClearAreaComponent>())
|
||||
{
|
||||
}
|
||||
|
||||
RoomLayoutSystem::~RoomLayoutSystem() = default;
|
||||
|
||||
void RoomLayoutSystem::initialize()
|
||||
{
|
||||
if (m_initialized)
|
||||
return;
|
||||
m_initialized = true;
|
||||
Ogre::LogManager::getSingleton().logMessage("RoomLayoutSystem initialized");
|
||||
}
|
||||
|
||||
void RoomLayoutSystem::update()
|
||||
{
|
||||
if (!m_initialized)
|
||||
return;
|
||||
|
||||
// Check if any component is dirty - if so, we need to reprocess everything
|
||||
// because room layout is interdependent
|
||||
bool anyDirty = false;
|
||||
|
||||
m_clearAreaQuery.each([&](flecs::entity e, ClearAreaComponent& c) {
|
||||
if (c.dirty || !c.processed) anyDirty = true;
|
||||
});
|
||||
m_roomQuery.each([&](flecs::entity e, RoomComponent& r) {
|
||||
if (r.dirty) anyDirty = true;
|
||||
});
|
||||
|
||||
// If anything changed, reprocess everything
|
||||
if (anyDirty || m_needsFullReprocess) {
|
||||
reprocessAll();
|
||||
m_needsFullReprocess = false;
|
||||
}
|
||||
}
|
||||
|
||||
void RoomLayoutSystem::reprocessAll()
|
||||
{
|
||||
Ogre::LogManager::getSingleton().logMessage("RoomLayoutSystem: Full reprocess starting...");
|
||||
|
||||
// STEP 1: Clear areas FIRST
|
||||
m_clearAreaQuery.each([&](flecs::entity clearEntity, ClearAreaComponent& clearArea) {
|
||||
CellGridComponent* grid = findParentGrid(clearEntity);
|
||||
if (grid) {
|
||||
processClearArea(clearEntity, clearArea, *grid);
|
||||
clearArea.dirty = false;
|
||||
clearArea.processed = true;
|
||||
}
|
||||
});
|
||||
|
||||
// STEP 2: Process all rooms (creates floor, ceiling, interior walls)
|
||||
m_roomQuery.each([&](flecs::entity roomEntity, RoomComponent& room) {
|
||||
CellGridComponent* grid = findParentGrid(roomEntity);
|
||||
if (grid) {
|
||||
processRoom(roomEntity, room, *grid);
|
||||
room.dirty = false;
|
||||
}
|
||||
});
|
||||
|
||||
// STEP 3: Process connections for all rooms (creates doors between rooms)
|
||||
// Build a map of persistent ID -> room entity for connection lookup
|
||||
std::unordered_map<std::string, flecs::entity> roomIdMap;
|
||||
m_roomQuery.each([&](flecs::entity roomEntity, RoomComponent& room) {
|
||||
room.ensurePersistentId(roomEntity.id());
|
||||
roomIdMap[room.persistentId] = roomEntity;
|
||||
});
|
||||
|
||||
// Track which room pairs we've processed to avoid duplicates
|
||||
std::set<std::pair<std::string, std::string>> processedPairs;
|
||||
m_roomQuery.each([&](flecs::entity roomEntity, RoomComponent& room) {
|
||||
CellGridComponent* grid = findParentGrid(roomEntity);
|
||||
if (grid) {
|
||||
// Only process connections where this room's ID is "less than" the other
|
||||
// to avoid creating duplicate doors
|
||||
for (const std::string& otherRoomId : room.connectedRoomIds) {
|
||||
std::string id1 = room.persistentId;
|
||||
std::string id2 = otherRoomId;
|
||||
if (id1 > id2) std::swap(id1, id2);
|
||||
|
||||
auto pair = std::make_pair(id1, id2);
|
||||
if (processedPairs.insert(pair).second) {
|
||||
// New pair - process it
|
||||
auto it = roomIdMap.find(otherRoomId);
|
||||
if (it != roomIdMap.end()) {
|
||||
flecs::entity otherRoom = it->second;
|
||||
if (otherRoom.is_alive() && otherRoom.has<RoomComponent>()) {
|
||||
processRoomConnection(roomEntity, room, otherRoom, otherRoom.get<RoomComponent>(), *grid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// STEP 4: Process exits for all rooms (creates external walls/doors/windows)
|
||||
m_roomQuery.each([&](flecs::entity roomEntity, RoomComponent& room) {
|
||||
CellGridComponent* grid = findParentGrid(roomEntity);
|
||||
if (grid) {
|
||||
processRoomExits(roomEntity, room, *grid);
|
||||
}
|
||||
});
|
||||
|
||||
// STEP 5: Process windows for rooms that have createWindows=true
|
||||
// This runs after all doors/exits are created
|
||||
m_roomQuery.each([&](flecs::entity roomEntity, RoomComponent& room) {
|
||||
if (room.createWindows) {
|
||||
CellGridComponent* grid = findParentGrid(roomEntity);
|
||||
if (grid) {
|
||||
processRoomWindows(roomEntity, room, *grid);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// STEP 6: Process exterior generation LAST (always runs for all rooms)
|
||||
// This mirrors the original Lua create_exterior() function
|
||||
m_roomQuery.each([&](flecs::entity roomEntity, RoomComponent& room) {
|
||||
CellGridComponent* grid = findParentGrid(roomEntity);
|
||||
if (grid) {
|
||||
processExteriorGeneration(*grid);
|
||||
}
|
||||
});
|
||||
|
||||
Ogre::LogManager::getSingleton().logMessage("RoomLayoutSystem: Full reprocess complete.");
|
||||
}
|
||||
|
||||
void RoomLayoutSystem::processClearArea(flecs::entity clearEntity, ClearAreaComponent& clearArea,
|
||||
CellGridComponent& grid)
|
||||
{
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"RoomLayoutSystem: Clearing area [" +
|
||||
std::to_string(clearArea.minX) + "," + std::to_string(clearArea.minZ) + "] to [" +
|
||||
std::to_string(clearArea.maxX) + "," + std::to_string(clearArea.maxZ) + "]");
|
||||
|
||||
// Clear cells in the area
|
||||
if (clearArea.clearCells) {
|
||||
for (int y = clearArea.minY; y < clearArea.maxY; y++) {
|
||||
for (int z = clearArea.minZ; z < clearArea.maxZ; z++) {
|
||||
for (int x = clearArea.minX; x < clearArea.maxX; x++) {
|
||||
grid.removeCell(x, y, z);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear furniture cells in the area
|
||||
if (clearArea.clearFurniture) {
|
||||
for (int y = clearArea.minY; y < clearArea.maxY; y++) {
|
||||
for (int z = clearArea.minZ; z < clearArea.maxZ; z++) {
|
||||
for (int x = clearArea.minX; x < clearArea.maxX; x++) {
|
||||
grid.removeFurnitureCell(x, y, z);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear roof entities (children of the grid entity)
|
||||
if (clearArea.clearRoofs) {
|
||||
flecs::entity gridEntity = clearEntity.parent();
|
||||
if (gridEntity.is_alive()) {
|
||||
std::vector<flecs::entity> toRemove;
|
||||
gridEntity.children([&](flecs::entity child) {
|
||||
if (child.has<RoofComponent>()) {
|
||||
const auto& roof = child.get<RoofComponent>();
|
||||
if (roof.posX >= clearArea.minX && roof.posX < clearArea.maxX &&
|
||||
roof.posZ >= clearArea.minZ && roof.posZ < clearArea.maxZ) {
|
||||
toRemove.push_back(child);
|
||||
}
|
||||
}
|
||||
});
|
||||
for (auto e : toRemove) {
|
||||
e.destruct();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear room entities (children of the grid entity)
|
||||
if (clearArea.clearRooms) {
|
||||
flecs::entity gridEntity = clearEntity.parent();
|
||||
if (gridEntity.is_alive()) {
|
||||
std::vector<flecs::entity> toRemove;
|
||||
gridEntity.children([&](flecs::entity child) {
|
||||
if (child.has<RoomComponent>()) {
|
||||
const auto& room = child.get<RoomComponent>();
|
||||
bool overlaps = !(room.maxX <= clearArea.minX || room.minX >= clearArea.maxX ||
|
||||
room.maxZ <= clearArea.minZ || room.minZ >= clearArea.maxZ);
|
||||
if (overlaps) {
|
||||
toRemove.push_back(child);
|
||||
}
|
||||
}
|
||||
});
|
||||
for (auto e : toRemove) {
|
||||
e.destruct();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
grid.markDirty();
|
||||
}
|
||||
|
||||
void RoomLayoutSystem::processExteriorGeneration(CellGridComponent& grid)
|
||||
{
|
||||
// This mirrors the original Lua create_exterior() function
|
||||
// It processes ALL rooms and adds exterior flags where interior walls face empty space
|
||||
// Note: This runs AFTER all rooms are created, so it can properly detect shared walls
|
||||
|
||||
flecs::entity gridEntity = flecs::entity::null();
|
||||
|
||||
// Find the grid entity from the grid component
|
||||
m_world.each([&](flecs::entity e, CellGridComponent& cg) {
|
||||
if (&cg == &grid) {
|
||||
gridEntity = e;
|
||||
}
|
||||
});
|
||||
|
||||
if (!gridEntity.is_alive()) return;
|
||||
|
||||
// Iterate through all rooms in the grid
|
||||
gridEntity.children([&](flecs::entity roomEntity) {
|
||||
if (!roomEntity.has<RoomComponent>()) return true;
|
||||
|
||||
const auto& room = roomEntity.get<RoomComponent>();
|
||||
int y = room.minY;
|
||||
|
||||
// Process Z- side (north)
|
||||
for (int x = room.minX; x < room.maxX; x++) {
|
||||
// Check if there's a cell OUTSIDE the room (at z-1)
|
||||
// If there IS a cell there, skip - this is a shared wall
|
||||
if (grid.findCell(x, y, room.minZ - 1)) continue;
|
||||
|
||||
Cell* cellPtr = grid.findCell(x, y, room.minZ);
|
||||
if (!cellPtr) continue;
|
||||
|
||||
// Add exterior flags for interior walls/doors/windows (without clearing interior)
|
||||
if (cellPtr->hasFlag(CellFlags::IntWallZNeg))
|
||||
cellPtr->setFlag(CellFlags::WallZNeg);
|
||||
if (cellPtr->hasFlag(CellFlags::IntDoorZNeg))
|
||||
cellPtr->setFlag(CellFlags::DoorZNeg);
|
||||
if (cellPtr->hasFlag(CellFlags::IntWindowZNeg))
|
||||
cellPtr->setFlag(CellFlags::WindowZNeg);
|
||||
}
|
||||
|
||||
// Process Z+ side (south)
|
||||
for (int x = room.minX; x < room.maxX; x++) {
|
||||
if (grid.findCell(x, y, room.maxZ)) continue;
|
||||
|
||||
Cell* cellPtr = grid.findCell(x, y, room.maxZ - 1);
|
||||
if (!cellPtr) continue;
|
||||
|
||||
if (cellPtr->hasFlag(CellFlags::IntWallZPos))
|
||||
cellPtr->setFlag(CellFlags::WallZPos);
|
||||
if (cellPtr->hasFlag(CellFlags::IntDoorZPos))
|
||||
cellPtr->setFlag(CellFlags::DoorZPos);
|
||||
if (cellPtr->hasFlag(CellFlags::IntWindowZPos))
|
||||
cellPtr->setFlag(CellFlags::WindowZPos);
|
||||
}
|
||||
|
||||
// Process X+ side (east)
|
||||
for (int z = room.minZ; z < room.maxZ; z++) {
|
||||
if (grid.findCell(room.maxX, y, z)) continue;
|
||||
|
||||
Cell* cellPtr = grid.findCell(room.maxX - 1, y, z);
|
||||
if (!cellPtr) continue;
|
||||
|
||||
if (cellPtr->hasFlag(CellFlags::IntWallXPos))
|
||||
cellPtr->setFlag(CellFlags::WallXPos);
|
||||
if (cellPtr->hasFlag(CellFlags::IntDoorXPos))
|
||||
cellPtr->setFlag(CellFlags::DoorXPos);
|
||||
if (cellPtr->hasFlag(CellFlags::IntWindowXPos))
|
||||
cellPtr->setFlag(CellFlags::WindowXPos);
|
||||
}
|
||||
|
||||
// Process X- side (west)
|
||||
for (int z = room.minZ; z < room.maxZ; z++) {
|
||||
if (grid.findCell(room.minX - 1, y, z)) continue;
|
||||
|
||||
Cell* cellPtr = grid.findCell(room.minX, y, z);
|
||||
if (!cellPtr) continue;
|
||||
|
||||
if (cellPtr->hasFlag(CellFlags::IntWallXNeg))
|
||||
cellPtr->setFlag(CellFlags::WallXNeg);
|
||||
if (cellPtr->hasFlag(CellFlags::IntDoorXNeg))
|
||||
cellPtr->setFlag(CellFlags::DoorXNeg);
|
||||
if (cellPtr->hasFlag(CellFlags::IntWindowXNeg))
|
||||
cellPtr->setFlag(CellFlags::WindowXNeg);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
grid.markDirty();
|
||||
}
|
||||
|
||||
void RoomLayoutSystem::processRoom(flecs::entity roomEntity, RoomComponent& room,
|
||||
CellGridComponent& grid)
|
||||
{
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"RoomLayoutSystem: Processing room " + std::to_string(roomEntity.id()) +
|
||||
" at [" + std::to_string(room.minX) + "," + std::to_string(room.minZ) +
|
||||
"] size [" + std::to_string(room.getSizeX()) + "," + std::to_string(room.getSizeZ()) + "]");
|
||||
|
||||
int y = room.minY;
|
||||
|
||||
// Create floor and ceiling for the room area
|
||||
for (int z = room.minZ; z < room.maxZ; z++) {
|
||||
for (int x = room.minX; x < room.maxX; x++) {
|
||||
Cell& cell = grid.getOrCreateCell(x, y, z);
|
||||
|
||||
if (room.createFloor)
|
||||
cell.setFlag(CellFlags::Floor);
|
||||
if (room.createCeiling)
|
||||
cell.setFlag(CellFlags::Ceiling);
|
||||
}
|
||||
}
|
||||
|
||||
// Create interior walls around the room perimeter
|
||||
if (room.createInteriorWalls) {
|
||||
// Z- side (north)
|
||||
for (int x = room.minX; x < room.maxX; x++) {
|
||||
Cell& cell = grid.getOrCreateCell(x, y, room.minZ);
|
||||
cell.setFlag(CellFlags::IntWallZNeg);
|
||||
}
|
||||
// Z+ side (south)
|
||||
for (int x = room.minX; x < room.maxX; x++) {
|
||||
Cell& cell = grid.getOrCreateCell(x, y, room.maxZ - 1);
|
||||
cell.setFlag(CellFlags::IntWallZPos);
|
||||
}
|
||||
// X- side (west)
|
||||
for (int z = room.minZ; z < room.maxZ; z++) {
|
||||
Cell& cell = grid.getOrCreateCell(room.minX, y, z);
|
||||
cell.setFlag(CellFlags::IntWallXNeg);
|
||||
}
|
||||
// X+ side (east)
|
||||
for (int z = room.minZ; z < room.maxZ; z++) {
|
||||
Cell& cell = grid.getOrCreateCell(room.maxX - 1, y, z);
|
||||
cell.setFlag(CellFlags::IntWallXPos);
|
||||
}
|
||||
}
|
||||
|
||||
grid.markDirty();
|
||||
}
|
||||
|
||||
void RoomLayoutSystem::processRoomConnections(flecs::entity roomEntity, RoomComponent& room,
|
||||
CellGridComponent& grid)
|
||||
{
|
||||
// This is now handled in reprocessAll() to avoid duplicates
|
||||
}
|
||||
|
||||
void RoomLayoutSystem::processRoomConnection(flecs::entity roomAEntity, const RoomComponent& roomA,
|
||||
flecs::entity roomBEntity, const RoomComponent& roomB,
|
||||
CellGridComponent& grid)
|
||||
{
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"RoomLayoutSystem: Processing connection between rooms " +
|
||||
std::to_string(roomAEntity.id()) + " and " + std::to_string(roomBEntity.id()));
|
||||
|
||||
int y = roomA.minY;
|
||||
|
||||
// Find adjacent cells between the two rooms
|
||||
std::vector<std::pair<int, int>> cellsA, cellsB;
|
||||
findAdjacentCells(roomA, roomB, cellsA, cellsB);
|
||||
|
||||
if (cellsA.empty()) {
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"RoomLayoutSystem: No adjacent cells found between rooms");
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the cell closest to the geometric center of all adjacent cells
|
||||
// This mirrors the original Lua connectRooms() logic
|
||||
size_t index = 0;
|
||||
if (cellsA.size() > 1) {
|
||||
// Calculate average (center) point
|
||||
int sumX = 0, sumZ = 0;
|
||||
for (const auto& cell : cellsA) {
|
||||
sumX += cell.first;
|
||||
sumZ += cell.second;
|
||||
}
|
||||
int avgX = sumX / cellsA.size();
|
||||
int avgZ = sumZ / cellsA.size();
|
||||
|
||||
// Find closest point to average
|
||||
int minDistance = -1;
|
||||
for (size_t i = 0; i < cellsA.size(); i++) {
|
||||
int dx = cellsA[i].first - avgX;
|
||||
int dz = cellsA[i].second - avgZ;
|
||||
int dist = dx * dx + dz * dz;
|
||||
if (minDistance == -1 || dist < minDistance) {
|
||||
minDistance = dist;
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Door position in roomA (closest to center of shared wall)
|
||||
int xA = cellsA[index].first;
|
||||
int zA = cellsA[index].second;
|
||||
|
||||
// Door position in roomB (adjacent to roomA's door)
|
||||
int xB = cellsB[index].first;
|
||||
int zB = cellsB[index].second;
|
||||
|
||||
// Determine which side of roomA faces roomB
|
||||
int sideA = -1;
|
||||
if (zA == roomA.minZ) sideA = 0; // Z-
|
||||
else if (zA == roomA.maxZ - 1) sideA = 1; // Z+
|
||||
else if (xA == roomA.minX) sideA = 2; // X-
|
||||
else if (xA == roomA.maxX - 1) sideA = 3; // X+
|
||||
|
||||
// Determine which side of roomB faces roomA
|
||||
int sideB = -1;
|
||||
if (zB == roomB.minZ) sideB = 0; // Z-
|
||||
else if (zB == roomB.maxZ - 1) sideB = 1; // Z+
|
||||
else if (xB == roomB.minX) sideB = 2; // X-
|
||||
else if (xB == roomB.maxX - 1) sideB = 3; // X+
|
||||
|
||||
// Create door in roomA
|
||||
if (sideA >= 0) {
|
||||
clearWallFlags(grid, xA, y, zA, sideA);
|
||||
setWallFlags(grid, xA, y, zA, sideA, false, true, false); // Interior door
|
||||
}
|
||||
|
||||
// Create door in roomB
|
||||
if (sideB >= 0) {
|
||||
clearWallFlags(grid, xB, y, zB, sideB);
|
||||
setWallFlags(grid, xB, y, zB, sideB, false, true, false); // Interior door
|
||||
}
|
||||
|
||||
grid.markDirty();
|
||||
}
|
||||
|
||||
void RoomLayoutSystem::processRoomExits(flecs::entity roomEntity, RoomComponent& room,
|
||||
CellGridComponent& grid)
|
||||
{
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"RoomLayoutSystem: Processing exits for room " + std::to_string(roomEntity.id()));
|
||||
|
||||
flecs::entity gridEntity = roomEntity.parent();
|
||||
if (!gridEntity.is_alive()) return;
|
||||
|
||||
int y = room.minY;
|
||||
|
||||
// Process each exit side
|
||||
// This mirrors the original Lua createExit0-3 functions:
|
||||
// - Collect all outside-facing wall positions
|
||||
// - Average them to find the middle
|
||||
// - Create a SINGLE door at that position
|
||||
for (int side = 0; side < 4; side++) {
|
||||
if (!room.exits[side]) continue;
|
||||
|
||||
auto cells = getRoomEdgeCells(room, side);
|
||||
|
||||
// Collect positions of all cells that face outside and have interior walls
|
||||
int sumPos = -1; // Running sum of positions (X or Z depending on side)
|
||||
int count = 0; // Number of valid positions found
|
||||
|
||||
for (const auto& cell : cells) {
|
||||
int x = cell.first;
|
||||
int z = cell.second;
|
||||
|
||||
// Check if this cell faces outside (no cell at adjacent position)
|
||||
bool facesOutside = false;
|
||||
switch (side) {
|
||||
case 0: facesOutside = !grid.findCell(x, y, z - 1); break;
|
||||
case 1: facesOutside = !grid.findCell(x, y, z + 1); break;
|
||||
case 2: facesOutside = !grid.findCell(x - 1, y, z); break;
|
||||
case 3: facesOutside = !grid.findCell(x + 1, y, z); break;
|
||||
}
|
||||
|
||||
if (!facesOutside) continue;
|
||||
|
||||
// Check if this cell has an interior wall on this side
|
||||
Cell* cellPtr = grid.findCell(x, y, z);
|
||||
if (!cellPtr) continue;
|
||||
|
||||
bool hasIntWall = false;
|
||||
switch (side) {
|
||||
case 0: hasIntWall = cellPtr->hasFlag(CellFlags::IntWallZNeg); break;
|
||||
case 1: hasIntWall = cellPtr->hasFlag(CellFlags::IntWallZPos); break;
|
||||
case 2: hasIntWall = cellPtr->hasFlag(CellFlags::IntWallXNeg); break;
|
||||
case 3: hasIntWall = cellPtr->hasFlag(CellFlags::IntWallXPos); break;
|
||||
}
|
||||
|
||||
if (hasIntWall) {
|
||||
// Add position to sum (use X for Z- and Z+ sides, Z for X- and X+ sides)
|
||||
int pos = (side < 2) ? x : z;
|
||||
if (sumPos == -1)
|
||||
sumPos = pos;
|
||||
else
|
||||
sumPos += pos;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
// Create a single door at the averaged position
|
||||
if (count > 0) {
|
||||
int midPos = sumPos / count;
|
||||
|
||||
// Calculate the actual cell coordinates
|
||||
int doorX, doorZ;
|
||||
switch (side) {
|
||||
case 0: // Z- side: X varies, Z is minZ
|
||||
doorX = midPos;
|
||||
doorZ = room.minZ;
|
||||
break;
|
||||
case 1: // Z+ side: X varies, Z is maxZ-1
|
||||
doorX = midPos;
|
||||
doorZ = room.maxZ - 1;
|
||||
break;
|
||||
case 2: // X- side: X is minX, Z varies
|
||||
doorX = room.minX;
|
||||
doorZ = midPos;
|
||||
break;
|
||||
case 3: // X+ side: X is maxX-1, Z varies
|
||||
doorX = room.maxX - 1;
|
||||
doorZ = midPos;
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
// Convert interior wall to interior door at the middle position
|
||||
clearWallFlags(grid, doorX, y, doorZ, side);
|
||||
setWallFlags(grid, doorX, y, doorZ, side, false, true, false); // Interior door
|
||||
}
|
||||
}
|
||||
|
||||
grid.markDirty();
|
||||
}
|
||||
|
||||
void RoomLayoutSystem::processRoomWindows(flecs::entity roomEntity, RoomComponent& room,
|
||||
CellGridComponent& grid)
|
||||
{
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"RoomLayoutSystem: Processing windows for room " + std::to_string(roomEntity.id()));
|
||||
|
||||
flecs::entity gridEntity = roomEntity.parent();
|
||||
if (!gridEntity.is_alive()) return;
|
||||
|
||||
int y = room.minY;
|
||||
|
||||
// Process each side of the room
|
||||
for (int side = 0; side < 4; side++) {
|
||||
auto cells = getRoomEdgeCells(room, side);
|
||||
|
||||
for (const auto& cell : cells) {
|
||||
int x = cell.first;
|
||||
int z = cell.second;
|
||||
|
||||
// Check if this cell faces outside (not adjacent to another room)
|
||||
bool facesOutside = false;
|
||||
switch (side) {
|
||||
case 0: facesOutside = !grid.findCell(x, y, z - 1); break;
|
||||
case 1: facesOutside = !grid.findCell(x, y, z + 1); break;
|
||||
case 2: facesOutside = !grid.findCell(x - 1, y, z); break;
|
||||
case 3: facesOutside = !grid.findCell(x + 1, y, z); break;
|
||||
}
|
||||
|
||||
if (facesOutside) {
|
||||
Cell* cellPtr = grid.findCell(x, y, z);
|
||||
if (!cellPtr) continue;
|
||||
|
||||
// Convert interior wall to interior window
|
||||
// (exterior generation will convert to exterior window later)
|
||||
switch (side) {
|
||||
case 0: // Z-
|
||||
if (cellPtr->hasFlag(CellFlags::IntWallZNeg)) {
|
||||
cellPtr->clearFlag(CellFlags::IntWallZNeg);
|
||||
cellPtr->setFlag(CellFlags::IntWindowZNeg);
|
||||
}
|
||||
break;
|
||||
case 1: // Z+
|
||||
if (cellPtr->hasFlag(CellFlags::IntWallZPos)) {
|
||||
cellPtr->clearFlag(CellFlags::IntWallZPos);
|
||||
cellPtr->setFlag(CellFlags::IntWindowZPos);
|
||||
}
|
||||
break;
|
||||
case 2: // X-
|
||||
if (cellPtr->hasFlag(CellFlags::IntWallXNeg)) {
|
||||
cellPtr->clearFlag(CellFlags::IntWallXNeg);
|
||||
cellPtr->setFlag(CellFlags::IntWindowXNeg);
|
||||
}
|
||||
break;
|
||||
case 3: // X+
|
||||
if (cellPtr->hasFlag(CellFlags::IntWallXPos)) {
|
||||
cellPtr->clearFlag(CellFlags::IntWallXPos);
|
||||
cellPtr->setFlag(CellFlags::IntWindowXPos);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
grid.markDirty();
|
||||
}
|
||||
|
||||
CellGridComponent* RoomLayoutSystem::findParentGrid(flecs::entity entity)
|
||||
{
|
||||
flecs::entity current = entity;
|
||||
int safety = 0;
|
||||
|
||||
while (current.is_alive() && safety < 100) {
|
||||
if (current.has<CellGridComponent>()) {
|
||||
// get_mut returns a reference, convert to pointer
|
||||
return ¤t.get_mut<CellGridComponent>();
|
||||
}
|
||||
current = current.parent();
|
||||
safety++;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
flecs::entity RoomLayoutSystem::findRoomAt(flecs::entity gridEntity, int x, int y, int z)
|
||||
{
|
||||
flecs::entity result = flecs::entity::null();
|
||||
gridEntity.children([&](flecs::entity child) {
|
||||
if (child.has<RoomComponent>()) {
|
||||
const auto& room = child.get<RoomComponent>();
|
||||
if (room.contains(x, y, z)) {
|
||||
result = child;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::pair<int, int>> RoomLayoutSystem::getRoomEdgeCells(const RoomComponent& room, int side)
|
||||
{
|
||||
std::vector<std::pair<int, int>> cells;
|
||||
|
||||
switch (side) {
|
||||
case 0:
|
||||
for (int x = room.minX; x < room.maxX; x++) {
|
||||
cells.push_back({ x, room.minZ });
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
for (int x = room.minX; x < room.maxX; x++) {
|
||||
cells.push_back({ x, room.maxZ - 1 });
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
for (int z = room.minZ; z < room.maxZ; z++) {
|
||||
cells.push_back({ room.minX, z });
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
for (int z = room.minZ; z < room.maxZ; z++) {
|
||||
cells.push_back({ room.maxX - 1, z });
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return cells;
|
||||
}
|
||||
|
||||
bool RoomLayoutSystem::areCellsAdjacent(int x1, int z1, int x2, int z2)
|
||||
{
|
||||
int dx = std::abs(x1 - x2);
|
||||
int dz = std::abs(z1 - z2);
|
||||
return (dx == 0 && dz == 1) || (dx == 1 && dz == 0);
|
||||
}
|
||||
|
||||
void RoomLayoutSystem::findAdjacentCells(const RoomComponent& roomA, const RoomComponent& roomB,
|
||||
std::vector<std::pair<int, int>>& outCellsA,
|
||||
std::vector<std::pair<int, int>>& outCellsB)
|
||||
{
|
||||
const int sides[] = { 0, 1, 2, 3 };
|
||||
|
||||
for (int sideA : sides) {
|
||||
auto edgeA = getRoomEdgeCells(roomA, sideA);
|
||||
|
||||
for (int sideB : sides) {
|
||||
if (sideA == sideB) continue;
|
||||
|
||||
auto edgeB = getRoomEdgeCells(roomB, sideB);
|
||||
|
||||
for (const auto& cellA : edgeA) {
|
||||
for (const auto& cellB : edgeB) {
|
||||
if (areCellsAdjacent(cellA.first, cellA.second, cellB.first, cellB.second)) {
|
||||
outCellsA.push_back(cellA);
|
||||
outCellsB.push_back(cellB);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RoomLayoutSystem::setWallFlags(CellGridComponent& grid, int x, int y, int z,
|
||||
int side, bool isExternal, bool isDoor, bool isWindow)
|
||||
{
|
||||
Cell* cell = grid.findCell(x, y, z);
|
||||
if (!cell) return;
|
||||
|
||||
uint64_t wallFlag = 0;
|
||||
uint64_t doorFlag = 0;
|
||||
uint64_t windowFlag = 0;
|
||||
|
||||
if (isExternal) {
|
||||
switch (side) {
|
||||
case 0: wallFlag = CellFlags::WallZNeg; doorFlag = CellFlags::DoorZNeg; windowFlag = CellFlags::WindowZNeg; break;
|
||||
case 1: wallFlag = CellFlags::WallZPos; doorFlag = CellFlags::DoorZPos; windowFlag = CellFlags::WindowZPos; break;
|
||||
case 2: wallFlag = CellFlags::WallXNeg; doorFlag = CellFlags::DoorXNeg; windowFlag = CellFlags::WindowXNeg; break;
|
||||
case 3: wallFlag = CellFlags::WallXPos; doorFlag = CellFlags::DoorXPos; windowFlag = CellFlags::WindowXPos; break;
|
||||
}
|
||||
} else {
|
||||
switch (side) {
|
||||
case 0: wallFlag = CellFlags::IntWallZNeg; doorFlag = CellFlags::IntDoorZNeg; windowFlag = CellFlags::IntWindowZNeg; break;
|
||||
case 1: wallFlag = CellFlags::IntWallZPos; doorFlag = CellFlags::IntDoorZPos; windowFlag = CellFlags::IntWindowZPos; break;
|
||||
case 2: wallFlag = CellFlags::IntWallXNeg; doorFlag = CellFlags::IntDoorXNeg; windowFlag = CellFlags::IntWindowXNeg; break;
|
||||
case 3: wallFlag = CellFlags::IntWallXPos; doorFlag = CellFlags::IntDoorXPos; windowFlag = CellFlags::IntWindowXPos; break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isDoor) {
|
||||
cell->setFlag(doorFlag);
|
||||
} else if (isWindow) {
|
||||
cell->setFlag(windowFlag);
|
||||
} else {
|
||||
cell->setFlag(wallFlag);
|
||||
}
|
||||
}
|
||||
|
||||
void RoomLayoutSystem::clearWallFlags(CellGridComponent& grid, int x, int y, int z, int side)
|
||||
{
|
||||
Cell* cell = grid.findCell(x, y, z);
|
||||
if (!cell) return;
|
||||
|
||||
uint64_t flagsToClear = 0;
|
||||
|
||||
switch (side) {
|
||||
case 0:
|
||||
flagsToClear = CellFlags::WallZNeg | CellFlags::DoorZNeg | CellFlags::WindowZNeg |
|
||||
CellFlags::IntWallZNeg | CellFlags::IntDoorZNeg | CellFlags::IntWindowZNeg;
|
||||
break;
|
||||
case 1:
|
||||
flagsToClear = CellFlags::WallZPos | CellFlags::DoorZPos | CellFlags::WindowZPos |
|
||||
CellFlags::IntWallZPos | CellFlags::IntDoorZPos | CellFlags::IntWindowZPos;
|
||||
break;
|
||||
case 2:
|
||||
flagsToClear = CellFlags::WallXNeg | CellFlags::DoorXNeg | CellFlags::WindowXNeg |
|
||||
CellFlags::IntWallXNeg | CellFlags::IntDoorXNeg | CellFlags::IntWindowXNeg;
|
||||
break;
|
||||
case 3:
|
||||
flagsToClear = CellFlags::WallXPos | CellFlags::DoorXPos | CellFlags::WindowXPos |
|
||||
CellFlags::IntWallXPos | CellFlags::IntDoorXPos | CellFlags::IntWindowXPos;
|
||||
break;
|
||||
}
|
||||
|
||||
cell->flags &= ~flagsToClear;
|
||||
}
|
||||
106
src/features/editScene/systems/RoomLayoutSystem.hpp
Normal file
106
src/features/editScene/systems/RoomLayoutSystem.hpp
Normal file
@@ -0,0 +1,106 @@
|
||||
#pragma once
|
||||
|
||||
#include <flecs.h>
|
||||
#include <Ogre.h>
|
||||
|
||||
/**
|
||||
* @brief Room Layout System - processes Room components and generates cell grid
|
||||
*
|
||||
* This system replaces the Lua-based room generation from town.cpp.
|
||||
* It processes in order:
|
||||
* 1. ClearAreaComponent: Clears cells before generation
|
||||
* 2. RoomComponent: Creates floor/ceiling cells and interior walls
|
||||
* 3. Room connections: Creates doors between connected rooms
|
||||
* 4. Room exits: Creates external walls/doors/windows based on RoomComponent::exits
|
||||
* 5. Room windows: Creates windows for rooms with createWindows=true
|
||||
* 6. Exterior generation: Converts interior to exterior on outer edges (always runs last)
|
||||
*
|
||||
* When ANY component is modified, ALL components are reprocessed to ensure
|
||||
* the cell grid is consistent.
|
||||
*
|
||||
* The system operates on entities with CellGridComponent (children of Lot/District/Town).
|
||||
* Rooms should be children of the CellGrid entity.
|
||||
*/
|
||||
class RoomLayoutSystem {
|
||||
public:
|
||||
RoomLayoutSystem(flecs::world& world, Ogre::SceneManager* sceneMgr);
|
||||
~RoomLayoutSystem();
|
||||
|
||||
// Initialize the system
|
||||
void initialize();
|
||||
|
||||
// Update - process dirty rooms and connections
|
||||
void update();
|
||||
|
||||
private:
|
||||
// Process a single room - creates floor, ceiling, interior walls
|
||||
void processRoom(flecs::entity roomEntity, class RoomComponent& room,
|
||||
class CellGridComponent& grid);
|
||||
|
||||
// Process connections for a room - creates doors to connected rooms
|
||||
void processRoomConnections(flecs::entity roomEntity, class RoomComponent& room,
|
||||
class CellGridComponent& grid);
|
||||
|
||||
// Process a single room connection
|
||||
void processRoomConnection(flecs::entity roomAEntity, const class RoomComponent& roomA,
|
||||
flecs::entity roomBEntity, const class RoomComponent& roomB,
|
||||
class CellGridComponent& grid);
|
||||
|
||||
// Process room exits - creates external walls/doors/windows
|
||||
void processRoomExits(flecs::entity roomEntity, class RoomComponent& room,
|
||||
class CellGridComponent& grid);
|
||||
|
||||
// Process room windows for a specific room (called after all doors are created)
|
||||
void processRoomWindows(flecs::entity roomEntity, class RoomComponent& room,
|
||||
class CellGridComponent& grid);
|
||||
|
||||
// Process clear area - clears cells before room generation
|
||||
void processClearArea(flecs::entity clearEntity, class ClearAreaComponent& clearArea,
|
||||
class CellGridComponent& grid);
|
||||
|
||||
// Process exterior generation - converts interior walls to exterior on outer edges
|
||||
// This always runs at the end of the pipeline for all rooms
|
||||
void processExteriorGeneration(class CellGridComponent& grid);
|
||||
|
||||
// Helper: Get the parent CellGridComponent for an entity
|
||||
class CellGridComponent* findParentGrid(flecs::entity entity);
|
||||
|
||||
// Helper: Find the room entity that contains a given cell position
|
||||
flecs::entity findRoomAt(flecs::entity gridEntity, int x, int y, int z);
|
||||
|
||||
// Helper: Check if a cell at (x,z) has ANY cell in the grid at the adjacent position
|
||||
// side: 0=Z-, 1=Z+, 2=X-, 3=X+
|
||||
bool hasAdjacentCell(class CellGridComponent& grid, int x, int y, int z, int side);
|
||||
|
||||
// Helper: Get all cells on the edge of a room on a specific side
|
||||
std::vector<std::pair<int, int>> getRoomEdgeCells(const class RoomComponent& room, int side);
|
||||
|
||||
// Helper: Check if two cells are adjacent
|
||||
bool areCellsAdjacent(int x1, int z1, int x2, int z2);
|
||||
|
||||
// Helper: Find adjacent cells between two rooms
|
||||
void findAdjacentCells(const class RoomComponent& roomA, const class RoomComponent& roomB,
|
||||
std::vector<std::pair<int, int>>& outCellsA,
|
||||
std::vector<std::pair<int, int>>& outCellsB);
|
||||
|
||||
// Helper: Set cell flags for a wall/door/window
|
||||
void setWallFlags(class CellGridComponent& grid, int x, int y, int z,
|
||||
int side, bool isExternal, bool isDoor, bool isWindow);
|
||||
|
||||
// Helper: Clear wall flags (for creating openings)
|
||||
void clearWallFlags(class CellGridComponent& grid, int x, int y, int z, int side);
|
||||
|
||||
flecs::world& m_world;
|
||||
Ogre::SceneManager* m_sceneMgr;
|
||||
bool m_initialized = false;
|
||||
|
||||
// Queries
|
||||
flecs::query<class RoomComponent> m_roomQuery;
|
||||
flecs::query<class ClearAreaComponent> m_clearAreaQuery;
|
||||
|
||||
// Track if any component was modified - triggers full reprocess
|
||||
bool m_needsFullReprocess = false;
|
||||
|
||||
// Reprocess all components (called when anything changes)
|
||||
void reprocessAll();
|
||||
};
|
||||
@@ -199,6 +199,9 @@ nlohmann::json SceneSerializer::serializeEntity(flecs::entity entity)
|
||||
if (entity.has<FurnitureTemplateComponent>()) {
|
||||
json["furnitureTemplate"] = serializeFurnitureTemplate(entity);
|
||||
}
|
||||
if (entity.has<ClearAreaComponent>()) {
|
||||
json["clearArea"] = serializeClearArea(entity);
|
||||
}
|
||||
|
||||
// Serialize children
|
||||
json["children"] = nlohmann::json::array();
|
||||
@@ -315,6 +318,9 @@ void SceneSerializer::deserializeEntity(const nlohmann::json& json, flecs::entit
|
||||
if (json.contains("furnitureTemplate")) {
|
||||
deserializeFurnitureTemplate(entity, json["furnitureTemplate"]);
|
||||
}
|
||||
if (json.contains("clearArea")) {
|
||||
deserializeClearArea(entity, json["clearArea"]);
|
||||
}
|
||||
|
||||
// Add to UI system if provided
|
||||
if (uiSystem) {
|
||||
@@ -1377,9 +1383,14 @@ nlohmann::json SceneSerializer::serializeLot(flecs::entity entity)
|
||||
|
||||
nlohmann::json SceneSerializer::serializeRoom(flecs::entity entity)
|
||||
{
|
||||
auto& room = entity.get<RoomComponent>();
|
||||
auto& room = entity.get_mut<RoomComponent>();
|
||||
|
||||
// Ensure room has a persistent ID
|
||||
room.ensurePersistentId(entity.id());
|
||||
|
||||
nlohmann::json json;
|
||||
|
||||
json["persistentId"] = room.persistentId;
|
||||
json["minX"] = room.minX;
|
||||
json["minY"] = room.minY;
|
||||
json["minZ"] = room.minZ;
|
||||
@@ -1388,8 +1399,24 @@ nlohmann::json SceneSerializer::serializeRoom(flecs::entity entity)
|
||||
json["maxZ"] = room.maxZ;
|
||||
json["roomType"] = room.roomType;
|
||||
json["tags"] = room.tags;
|
||||
json["connectedRooms"] = room.connectedRooms;
|
||||
json["exits"] = room.exits;
|
||||
json["createFloor"] = room.createFloor;
|
||||
json["createCeiling"] = room.createCeiling;
|
||||
json["createInteriorWalls"] = room.createInteriorWalls;
|
||||
json["createWindows"] = room.createWindows;
|
||||
|
||||
// Serialize exits (bool array for Z-, Z+, X-, X+)
|
||||
nlohmann::json exits = nlohmann::json::array();
|
||||
for (int i = 0; i < 4; i++) {
|
||||
exits.push_back(room.exits[i]);
|
||||
}
|
||||
json["exits"] = exits;
|
||||
|
||||
// Serialize connected room persistent IDs (not entity IDs, as they change on save/load)
|
||||
nlohmann::json connections = nlohmann::json::array();
|
||||
for (const std::string& id : room.connectedRoomIds) {
|
||||
connections.push_back(id);
|
||||
}
|
||||
json["connectedRoomIds"] = connections;
|
||||
|
||||
return json;
|
||||
}
|
||||
@@ -1610,6 +1637,12 @@ void SceneSerializer::deserializeRoom(flecs::entity entity, const nlohmann::json
|
||||
{
|
||||
RoomComponent room;
|
||||
|
||||
// Load persistent ID (auto-generate if not present for backward compatibility)
|
||||
room.persistentId = json.value("persistentId", "");
|
||||
if (room.persistentId.empty()) {
|
||||
room.ensurePersistentId(entity.id());
|
||||
}
|
||||
|
||||
room.minX = json.value("minX", 0);
|
||||
room.minY = json.value("minY", 0);
|
||||
room.minZ = json.value("minZ", 0);
|
||||
@@ -1617,6 +1650,22 @@ void SceneSerializer::deserializeRoom(flecs::entity entity, const nlohmann::json
|
||||
room.maxY = json.value("maxY", 0);
|
||||
room.maxZ = json.value("maxZ", 0);
|
||||
room.roomType = json.value("roomType", "");
|
||||
room.createFloor = json.value("createFloor", true);
|
||||
room.createCeiling = json.value("createCeiling", true);
|
||||
room.createInteriorWalls = json.value("createInteriorWalls", true);
|
||||
room.createWindows = json.value("createWindows", false);
|
||||
|
||||
// Load exits (bool array for Z-, Z+, X-, X+)
|
||||
if (json.contains("exits") && json["exits"].is_array()) {
|
||||
int i = 0;
|
||||
for (const auto& exitVal : json["exits"]) {
|
||||
if (i >= 4) break;
|
||||
if (exitVal.is_boolean()) {
|
||||
room.exits[i] = exitVal.get<bool>();
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
if (json.contains("tags") && json["tags"].is_array()) {
|
||||
for (const auto& tag : json["tags"]) {
|
||||
@@ -1626,22 +1675,19 @@ void SceneSerializer::deserializeRoom(flecs::entity entity, const nlohmann::json
|
||||
}
|
||||
}
|
||||
|
||||
if (json.contains("connectedRooms") && json["connectedRooms"].is_array()) {
|
||||
for (const auto& idx : json["connectedRooms"]) {
|
||||
if (idx.is_number()) {
|
||||
room.connectedRooms.push_back(idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (json.contains("exits") && json["exits"].is_array()) {
|
||||
for (const auto& exit : json["exits"]) {
|
||||
if (exit.is_number()) {
|
||||
room.exits.push_back(exit);
|
||||
// Load connected rooms using persistent IDs
|
||||
if (json.contains("connectedRoomIds") && json["connectedRoomIds"].is_array()) {
|
||||
// New format: persistent IDs
|
||||
for (const auto& id : json["connectedRoomIds"]) {
|
||||
if (id.is_string()) {
|
||||
room.connectedRoomIds.push_back(id.get<std::string>());
|
||||
}
|
||||
}
|
||||
}
|
||||
// Backward compatibility: old format used entity IDs which are invalid after reload
|
||||
// We skip loading these as they would be garbage
|
||||
|
||||
room.markDirty();
|
||||
entity.set<RoomComponent>(room);
|
||||
}
|
||||
|
||||
@@ -1689,3 +1735,41 @@ void SceneSerializer::deserializeFurnitureTemplate(flecs::entity entity, const n
|
||||
|
||||
entity.set<FurnitureTemplateComponent>(furn);
|
||||
}
|
||||
|
||||
nlohmann::json SceneSerializer::serializeClearArea(flecs::entity entity)
|
||||
{
|
||||
auto& clearArea = entity.get<ClearAreaComponent>();
|
||||
nlohmann::json json;
|
||||
|
||||
json["minX"] = clearArea.minX;
|
||||
json["minY"] = clearArea.minY;
|
||||
json["minZ"] = clearArea.minZ;
|
||||
json["maxX"] = clearArea.maxX;
|
||||
json["maxY"] = clearArea.maxY;
|
||||
json["maxZ"] = clearArea.maxZ;
|
||||
json["clearCells"] = clearArea.clearCells;
|
||||
json["clearFurniture"] = clearArea.clearFurniture;
|
||||
json["clearRoofs"] = clearArea.clearRoofs;
|
||||
json["clearRooms"] = clearArea.clearRooms;
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
void SceneSerializer::deserializeClearArea(flecs::entity entity, const nlohmann::json& json)
|
||||
{
|
||||
ClearAreaComponent clearArea;
|
||||
|
||||
clearArea.minX = json.value("minX", 0);
|
||||
clearArea.minY = json.value("minY", 0);
|
||||
clearArea.minZ = json.value("minZ", 0);
|
||||
clearArea.maxX = json.value("maxX", 1);
|
||||
clearArea.maxY = json.value("maxY", 1);
|
||||
clearArea.maxZ = json.value("maxZ", 1);
|
||||
clearArea.clearCells = json.value("clearCells", true);
|
||||
clearArea.clearFurniture = json.value("clearFurniture", true);
|
||||
clearArea.clearRoofs = json.value("clearRoofs", false);
|
||||
clearArea.clearRooms = json.value("clearRooms", false);
|
||||
|
||||
clearArea.markDirty();
|
||||
entity.set<ClearAreaComponent>(clearArea);
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ private:
|
||||
nlohmann::json serializeRoom(flecs::entity entity);
|
||||
nlohmann::json serializeRoof(flecs::entity entity);
|
||||
nlohmann::json serializeFurnitureTemplate(flecs::entity entity);
|
||||
nlohmann::json serializeClearArea(flecs::entity entity);
|
||||
|
||||
// Component deserialization
|
||||
void deserializeTransform(flecs::entity entity, const nlohmann::json& json, flecs::entity parentEntity);
|
||||
@@ -89,6 +90,7 @@ private:
|
||||
void deserializeRoom(flecs::entity entity, const nlohmann::json& json);
|
||||
void deserializeRoof(flecs::entity entity, const nlohmann::json& json);
|
||||
void deserializeFurnitureTemplate(flecs::entity entity, const nlohmann::json& json);
|
||||
void deserializeClearArea(flecs::entity entity, const nlohmann::json& json);
|
||||
|
||||
flecs::world& m_world;
|
||||
Ogre::SceneManager* m_sceneMgr;
|
||||
|
||||
56
src/features/editScene/ui/ClearAreaEditor.cpp
Normal file
56
src/features/editScene/ui/ClearAreaEditor.cpp
Normal file
@@ -0,0 +1,56 @@
|
||||
#include "ClearAreaEditor.hpp"
|
||||
#include "../components/CellGrid.hpp"
|
||||
#include <imgui.h>
|
||||
|
||||
bool ClearAreaEditor::renderComponent(flecs::entity entity, ClearAreaComponent& clearArea)
|
||||
{
|
||||
bool modified = false;
|
||||
|
||||
if (ImGui::CollapsingHeader("Clear Area", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
ImGui::Indent();
|
||||
|
||||
ImGui::Text("Area Size: %dx%dx%d", clearArea.getSizeX(), clearArea.getSizeY(), clearArea.getSizeZ());
|
||||
|
||||
int minPos[3] = { clearArea.minX, clearArea.minY, clearArea.minZ };
|
||||
if (ImGui::InputInt3("Min (X/Y/Z)", minPos)) {
|
||||
clearArea.minX = minPos[0];
|
||||
clearArea.minY = minPos[1];
|
||||
clearArea.minZ = minPos[2];
|
||||
modified = true;
|
||||
}
|
||||
|
||||
int maxPos[3] = { clearArea.maxX, clearArea.maxY, clearArea.maxZ };
|
||||
if (ImGui::InputInt3("Max (X/Y/Z)", maxPos)) {
|
||||
clearArea.maxX = maxPos[0];
|
||||
clearArea.maxY = maxPos[1];
|
||||
clearArea.maxZ = maxPos[2];
|
||||
modified = true;
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Clear Options:");
|
||||
|
||||
if (ImGui::Checkbox("Clear Cells (Walls/Floors)", &clearArea.clearCells)) modified = true;
|
||||
if (ImGui::Checkbox("Clear Furniture", &clearArea.clearFurniture)) modified = true;
|
||||
if (ImGui::Checkbox("Clear Roofs", &clearArea.clearRoofs)) modified = true;
|
||||
if (ImGui::Checkbox("Clear Rooms", &clearArea.clearRooms)) modified = true;
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// Status
|
||||
if (clearArea.processed) {
|
||||
ImGui::TextColored(ImVec4(0, 1, 0, 1), "Status: Processed");
|
||||
} else {
|
||||
ImGui::TextColored(ImVec4(1, 1, 0, 1), "Status: Pending");
|
||||
}
|
||||
|
||||
if (ImGui::Button("Clear Area Now")) {
|
||||
clearArea.markDirty();
|
||||
modified = true;
|
||||
}
|
||||
|
||||
ImGui::Unindent();
|
||||
}
|
||||
|
||||
return modified;
|
||||
}
|
||||
11
src/features/editScene/ui/ClearAreaEditor.hpp
Normal file
11
src/features/editScene/ui/ClearAreaEditor.hpp
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "ComponentEditor.hpp"
|
||||
|
||||
struct ClearAreaComponent;
|
||||
|
||||
class ClearAreaEditor : public ComponentEditor<ClearAreaComponent> {
|
||||
public:
|
||||
bool renderComponent(flecs::entity entity, ClearAreaComponent& clearArea) override;
|
||||
const char* getName() const override { return "Clear Area"; }
|
||||
};
|
||||
58
src/features/editScene/ui/ExteriorGenerationEditor.cpp
Normal file
58
src/features/editScene/ui/ExteriorGenerationEditor.cpp
Normal file
@@ -0,0 +1,58 @@
|
||||
#include "ExteriorGenerationEditor.hpp"
|
||||
#include "../components/CellGrid.hpp"
|
||||
#include <imgui.h>
|
||||
|
||||
bool ExteriorGenerationEditor::renderComponent(flecs::entity entity, ExteriorGenerationComponent& exterior)
|
||||
{
|
||||
bool modified = false;
|
||||
|
||||
if (ImGui::CollapsingHeader("Exterior Generation", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
ImGui::Indent();
|
||||
|
||||
ImGui::Text("Converts interior walls to exterior on building outer edges");
|
||||
ImGui::Text("This runs AFTER all room processing is complete.");
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// Floor Y
|
||||
if (ImGui::InputInt("Floor Y", &exterior.floorY)) {
|
||||
modified = true;
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Generate:");
|
||||
|
||||
if (ImGui::Checkbox("Walls", &exterior.generateWalls)) modified = true;
|
||||
if (ImGui::Checkbox("Doors", &exterior.generateDoors)) modified = true;
|
||||
if (ImGui::Checkbox("Windows", &exterior.generateWindows)) modified = true;
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// Target rooms
|
||||
ImGui::Text("Target Rooms: %zu", exterior.targetRooms.size());
|
||||
ImGui::TextDisabled("(Empty = all rooms)");
|
||||
if (!exterior.targetRooms.empty()) {
|
||||
for (size_t i = 0; i < exterior.targetRooms.size(); i++) {
|
||||
ImGui::Text(" Room %zu: ID %lu", i, exterior.targetRooms[i]);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// Status
|
||||
if (exterior.processed) {
|
||||
ImGui::TextColored(ImVec4(0, 1, 0, 1), "Status: Processed");
|
||||
} else {
|
||||
ImGui::TextColored(ImVec4(1, 1, 0, 1), "Status: Pending");
|
||||
}
|
||||
|
||||
if (ImGui::Button("Generate Exterior Now")) {
|
||||
exterior.markDirty();
|
||||
modified = true;
|
||||
}
|
||||
|
||||
ImGui::Unindent();
|
||||
}
|
||||
|
||||
return modified;
|
||||
}
|
||||
11
src/features/editScene/ui/ExteriorGenerationEditor.hpp
Normal file
11
src/features/editScene/ui/ExteriorGenerationEditor.hpp
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "ComponentEditor.hpp"
|
||||
|
||||
struct ExteriorGenerationComponent;
|
||||
|
||||
class ExteriorGenerationEditor : public ComponentEditor<ExteriorGenerationComponent> {
|
||||
public:
|
||||
bool renderComponent(flecs::entity entity, ExteriorGenerationComponent& exterior) override;
|
||||
const char* getName() const override { return "Exterior Generation"; }
|
||||
};
|
||||
@@ -1,14 +1,26 @@
|
||||
#include "RoomEditor.hpp"
|
||||
#include "../components/CellGrid.hpp"
|
||||
#include "../components/EntityName.hpp"
|
||||
#include <imgui.h>
|
||||
#include <algorithm>
|
||||
|
||||
bool RoomEditor::renderComponent(flecs::entity entity, RoomComponent& room)
|
||||
{
|
||||
bool modified = false;
|
||||
|
||||
// Ensure room has a persistent ID
|
||||
room.ensurePersistentId(entity.id());
|
||||
|
||||
if (ImGui::CollapsingHeader("Room", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
ImGui::Indent();
|
||||
|
||||
// Display room identification info
|
||||
ImGui::TextDisabled("Entity ID: %lu", entity.id());
|
||||
ImGui::TextDisabled("Persistent ID: %s", room.persistentId.c_str());
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::Text("Room Size: %dx%dx%d", room.getSizeX(), room.getSizeY(), room.getSizeZ());
|
||||
|
||||
int minPos[3] = { room.minX, room.minY, room.minZ };
|
||||
if (ImGui::InputInt3("Min (X/Y/Z)", minPos)) {
|
||||
room.minX = minPos[0];
|
||||
@@ -25,6 +37,15 @@ bool RoomEditor::renderComponent(flecs::entity entity, RoomComponent& room)
|
||||
modified = true;
|
||||
}
|
||||
|
||||
// Creation options
|
||||
if (ImGui::Checkbox("Create Floor", &room.createFloor)) modified = true;
|
||||
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;
|
||||
ImGui::TextDisabled("(Windows are created after all doors)");
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
char roomType[128] = {0};
|
||||
strncpy(roomType, room.roomType.c_str(), sizeof(roomType) - 1);
|
||||
if (ImGui::InputText("Room Type", roomType, sizeof(roomType))) {
|
||||
@@ -60,14 +81,185 @@ bool RoomEditor::renderComponent(flecs::entity entity, RoomComponent& room)
|
||||
modified = true;
|
||||
}
|
||||
|
||||
// Connected rooms
|
||||
ImGui::Text("Connected Rooms: %zu", room.connectedRooms.size());
|
||||
for (size_t i = 0; i < room.connectedRooms.size(); ++i) {
|
||||
ImGui::Text(" Room %d", room.connectedRooms[i]);
|
||||
ImGui::Unindent();
|
||||
}
|
||||
|
||||
// Exits Section
|
||||
if (ImGui::CollapsingHeader("Exits (To Outside)", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
ImGui::Indent();
|
||||
|
||||
ImGui::Text("Create exit doors to outside:");
|
||||
ImGui::TextDisabled("(Internal doors on outside-facing walls)");
|
||||
ImGui::TextDisabled("(Converted to exterior by Exterior Generation)");
|
||||
|
||||
const char* sideNames[] = { "Z- (North)", "Z+ (South)", "X- (West)", "X+ (East)" };
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
ImGui::PushID(i);
|
||||
if (ImGui::Checkbox(sideNames[i], &room.exits[i])) {
|
||||
modified = true;
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
// Quick presets
|
||||
ImGui::Separator();
|
||||
if (ImGui::Button("All Exits")) {
|
||||
room.setAllExits(true);
|
||||
modified = true;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("No Exits")) {
|
||||
room.setAllExits(false);
|
||||
modified = true;
|
||||
}
|
||||
|
||||
ImGui::Unindent();
|
||||
}
|
||||
|
||||
// Connections Section
|
||||
if (ImGui::CollapsingHeader("Connections", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
ImGui::Indent();
|
||||
|
||||
ImGui::Text("Connected to %zu room(s):", room.connectedRoomIds.size());
|
||||
|
||||
// Build a map of persistent ID -> room info for displaying connections
|
||||
std::unordered_map<std::string, flecs::entity> roomMap;
|
||||
std::unordered_map<std::string, std::string> roomLabels;
|
||||
flecs::entity parent = entity.parent();
|
||||
if (parent.is_alive()) {
|
||||
parent.children([&](flecs::entity child) {
|
||||
if (child.has<RoomComponent>()) {
|
||||
const auto& r = child.get<RoomComponent>();
|
||||
std::string label = formatRoomLabel(child, r);
|
||||
roomMap[r.persistentId] = child;
|
||||
roomLabels[r.persistentId] = label;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// List current connections with remove buttons
|
||||
int removeIndex = -1;
|
||||
for (size_t i = 0; i < room.connectedRoomIds.size(); i++) {
|
||||
const std::string& connectedId = room.connectedRoomIds[i];
|
||||
ImGui::PushID((int)i);
|
||||
|
||||
auto it = roomLabels.find(connectedId);
|
||||
if (it != roomLabels.end()) {
|
||||
ImGui::Text(" %zu: %s", i + 1, it->second.c_str());
|
||||
} else {
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f),
|
||||
" %zu: (Invalid room ID: %s)", i + 1, connectedId.c_str());
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::SmallButton("Remove")) {
|
||||
removeIndex = (int)i;
|
||||
modified = true;
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
if (removeIndex >= 0) {
|
||||
// Remove from this room
|
||||
std::string removedId = room.connectedRoomIds[removeIndex];
|
||||
room.connectedRoomIds.erase(room.connectedRoomIds.begin() + removeIndex);
|
||||
|
||||
// Also remove this room from the other room's list (bidirectional)
|
||||
auto it = roomMap.find(removedId);
|
||||
if (it != roomMap.end() && it->second.is_alive() && it->second.has<RoomComponent>()) {
|
||||
auto& otherRoom = it->second.get_mut<RoomComponent>();
|
||||
otherRoom.removeConnection(room.persistentId);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// Add connection section
|
||||
ImGui::Text("Add Connection:");
|
||||
|
||||
// Find all available rooms in the same parent
|
||||
std::vector<flecs::entity> availableRooms;
|
||||
std::vector<std::string> availableRoomLabels;
|
||||
|
||||
if (parent.is_alive()) {
|
||||
parent.children([&](flecs::entity child) {
|
||||
if (child.has<RoomComponent>() && child != entity) {
|
||||
const auto& r = child.get<RoomComponent>();
|
||||
// Check if not already connected
|
||||
if (!room.isConnectedTo(r.persistentId)) {
|
||||
availableRooms.push_back(child);
|
||||
availableRoomLabels.push_back(formatRoomLabel(child, r));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (availableRooms.empty()) {
|
||||
ImGui::TextDisabled(" No available rooms to connect");
|
||||
} else {
|
||||
static int selectedRoomIdx = 0;
|
||||
if (selectedRoomIdx >= (int)availableRooms.size()) selectedRoomIdx = 0;
|
||||
|
||||
// Build combo items
|
||||
std::string comboItems;
|
||||
for (size_t i = 0; i < availableRoomLabels.size(); i++) {
|
||||
if (i > 0) comboItems += "\0";
|
||||
comboItems += availableRoomLabels[i];
|
||||
}
|
||||
comboItems += "\0";
|
||||
|
||||
ImGui::PushID("add_conn");
|
||||
if (ImGui::Combo("Room", &selectedRoomIdx, comboItems.c_str())) {
|
||||
// Selection changed
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Connect")) {
|
||||
flecs::entity selectedRoom = availableRooms[selectedRoomIdx];
|
||||
const auto& selectedRoomComp = selectedRoom.get<RoomComponent>();
|
||||
|
||||
// Add bidirectional connection using persistent IDs
|
||||
room.addConnection(selectedRoomComp.persistentId);
|
||||
auto& otherRoom = selectedRoom.get_mut<RoomComponent>();
|
||||
otherRoom.addConnection(room.persistentId);
|
||||
|
||||
modified = true;
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
ImGui::Unindent();
|
||||
}
|
||||
|
||||
if (modified) {
|
||||
room.markDirty();
|
||||
}
|
||||
|
||||
return modified;
|
||||
}
|
||||
|
||||
std::string RoomEditor::formatRoomLabel(flecs::entity entity, const RoomComponent& room)
|
||||
{
|
||||
// Format: "Name [E:entityId P:persistentId] (Type)"
|
||||
std::string label;
|
||||
|
||||
// Try to get entity name if available
|
||||
if (entity.has<EntityNameComponent>()) {
|
||||
label = entity.get<EntityNameComponent>().name;
|
||||
}
|
||||
|
||||
if (label.empty()) {
|
||||
label = room.roomType.empty() ? "Unnamed" : room.roomType;
|
||||
}
|
||||
|
||||
// Add entity ID and persistent ID
|
||||
label += " [E:" + std::to_string(entity.id()) + " P:" + room.persistentId.substr(0, 8) + "]";
|
||||
|
||||
// Add room type if different from name
|
||||
if (!room.roomType.empty() && label.find(room.roomType) == std::string::npos) {
|
||||
label += " (" + room.roomType + ")";
|
||||
}
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "ComponentEditor.hpp"
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
struct RoomComponent;
|
||||
|
||||
@@ -8,4 +10,9 @@ class RoomEditor : public ComponentEditor<RoomComponent> {
|
||||
public:
|
||||
bool renderComponent(flecs::entity entity, RoomComponent& room) override;
|
||||
const char* getName() const override { return "Room"; }
|
||||
|
||||
private:
|
||||
// Format a room label for display in connection lists
|
||||
// Format: "Name [E:entityId P:persistentId] (Type)"
|
||||
std::string formatRoomLabel(flecs::entity entity, const RoomComponent& room);
|
||||
};
|
||||
|
||||
71
src/features/editScene/ui/RoomWindowsEditor.cpp
Normal file
71
src/features/editScene/ui/RoomWindowsEditor.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
#include "RoomWindowsEditor.hpp"
|
||||
#include "../components/CellGrid.hpp"
|
||||
#include <imgui.h>
|
||||
|
||||
bool RoomWindowsEditor::renderComponent(flecs::entity entity, RoomWindowsComponent& windows)
|
||||
{
|
||||
bool modified = false;
|
||||
|
||||
if (ImGui::CollapsingHeader("Room Windows", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
ImGui::Indent();
|
||||
|
||||
// Linked room
|
||||
ImGui::Text("Linked Room:");
|
||||
if (windows.room.is_alive()) {
|
||||
ImGui::Text(" Entity ID: %llu", windows.room.id());
|
||||
if (windows.room.has<RoomComponent>()) {
|
||||
const auto& room = windows.room.get<RoomComponent>();
|
||||
ImGui::Text(" Type: %s", room.roomType.c_str());
|
||||
}
|
||||
} else {
|
||||
ImGui::TextDisabled(" (Not set)");
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// Floor Y
|
||||
if (ImGui::InputInt("Floor Y", &windows.floorY)) {
|
||||
modified = true;
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// Window sides
|
||||
ImGui::Text("Create windows on sides:");
|
||||
if (ImGui::Checkbox("Z- (North)", &windows.windowZNeg)) modified = true;
|
||||
if (ImGui::Checkbox("Z+ (South)", &windows.windowZPos)) modified = true;
|
||||
if (ImGui::Checkbox("X- (West)", &windows.windowXNeg)) modified = true;
|
||||
if (ImGui::Checkbox("X+ (East)", &windows.windowXPos)) modified = true;
|
||||
|
||||
// Quick presets
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Quick Presets:");
|
||||
if (ImGui::Button("All Sides")) {
|
||||
windows.setAllSides(true);
|
||||
modified = true;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("None")) {
|
||||
windows.setAllSides(false);
|
||||
modified = true;
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// Status
|
||||
if (windows.processed) {
|
||||
ImGui::TextColored(ImVec4(0, 1, 0, 1), "Status: Processed");
|
||||
} else {
|
||||
ImGui::TextColored(ImVec4(1, 1, 0, 1), "Status: Pending");
|
||||
}
|
||||
|
||||
if (ImGui::Button("Reprocess Windows")) {
|
||||
windows.markDirty();
|
||||
modified = true;
|
||||
}
|
||||
|
||||
ImGui::Unindent();
|
||||
}
|
||||
|
||||
return modified;
|
||||
}
|
||||
11
src/features/editScene/ui/RoomWindowsEditor.hpp
Normal file
11
src/features/editScene/ui/RoomWindowsEditor.hpp
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "ComponentEditor.hpp"
|
||||
|
||||
struct RoomWindowsComponent;
|
||||
|
||||
class RoomWindowsEditor : public ComponentEditor<RoomWindowsComponent> {
|
||||
public:
|
||||
bool renderComponent(flecs::entity entity, RoomWindowsComponent& windows) override;
|
||||
const char* getName() const override { return "Room Windows"; }
|
||||
};
|
||||
Reference in New Issue
Block a user