Town plaza fixed

This commit is contained in:
2026-04-05 17:23:28 +03:00
parent 07101fcc64
commit cfd9dde5da
27 changed files with 2805 additions and 0 deletions

View File

@@ -22,6 +22,7 @@ set(EDITSCENE_SOURCES
systems/ProceduralTextureSystem.cpp
systems/ProceduralMaterialSystem.cpp
systems/ProceduralMeshSystem.cpp
systems/CellGridSystem.cpp
ui/TransformEditor.cpp
ui/RenderableEditor.cpp
ui/PhysicsColliderEditor.cpp
@@ -36,6 +37,13 @@ set(EDITSCENE_SOURCES
ui/ProceduralMaterialEditor.cpp
ui/PrimitiveEditor.cpp
ui/TriangleBufferEditor.cpp
ui/CellGridEditor.cpp
ui/LotEditor.cpp
ui/DistrictEditor.cpp
ui/TownEditor.cpp
ui/RoofEditor.cpp
ui/RoomEditor.cpp
ui/FurnitureTemplateEditor.cpp
ui/ComponentRegistration.cpp
components/LightModule.cpp
components/CameraModule.cpp
@@ -45,6 +53,9 @@ set(EDITSCENE_SOURCES
components/ProceduralMaterialModule.cpp
components/PrimitiveModule.cpp
components/TriangleBufferModule.cpp
components/CellGridModule.cpp
components/CellGridEditorsModule.cpp
components/CellGrid.cpp
camera/EditorCamera.cpp
gizmo/Gizmo.cpp
physics/physics.cpp
@@ -68,7 +79,9 @@ set(EDITSCENE_HEADERS
components/ProceduralMaterial.hpp
components/Primitive.hpp
components/TriangleBuffer.hpp
components/CellGrid.hpp
systems/EditorUISystem.hpp
systems/CellGridSystem.hpp
systems/ProceduralMaterialSystem.hpp
systems/ProceduralMeshSystem.hpp
systems/ProceduralTextureSystem.hpp
@@ -95,6 +108,13 @@ set(EDITSCENE_HEADERS
ui/ProceduralMaterialEditor.hpp
ui/PrimitiveEditor.hpp
ui/TriangleBufferEditor.hpp
ui/CellGridEditor.hpp
ui/LotEditor.hpp
ui/DistrictEditor.hpp
ui/TownEditor.hpp
ui/RoofEditor.hpp
ui/RoomEditor.hpp
ui/FurnitureTemplateEditor.hpp
camera/EditorCamera.hpp
gizmo/Gizmo.hpp
physics/physics.h

View File

@@ -9,6 +9,7 @@
#include "systems/ProceduralTextureSystem.hpp"
#include "systems/ProceduralMaterialSystem.hpp"
#include "systems/ProceduralMeshSystem.hpp"
#include "systems/CellGridSystem.hpp"
#include "camera/EditorCamera.hpp"
#include "components/EntityName.hpp"
#include "components/Transform.hpp"
@@ -26,6 +27,8 @@
#include "components/ProceduralMaterial.hpp"
#include "components/Primitive.hpp"
#include "components/TriangleBuffer.hpp"
#include "components/CellGrid.hpp"
#include "components/CellGridModule.hpp"
#include <OgreRTShaderSystem.h>
#include <imgui.h>
@@ -186,6 +189,10 @@ void EditorApp::setup()
// Setup ProceduralMesh system
m_proceduralMeshSystem = std::make_unique<ProceduralMeshSystem>(m_world, m_sceneMgr);
m_proceduralMeshSystem->initialize();
// Setup CellGrid system
m_cellGridSystem = std::make_unique<CellGridSystem>(m_world, m_sceneMgr);
m_cellGridSystem->initialize();
// Add default entities to UI cache
for (auto &e : m_defaultEntities) {
@@ -240,6 +247,9 @@ void EditorApp::setupECS()
// Register Primitive and TriangleBuffer components
m_world.component<PrimitiveComponent>();
m_world.component<TriangleBufferComponent>();
// Register CellGrid/Town components
CellGridModule::registerComponents(m_world);
}
void EditorApp::createDefaultEntities()
@@ -402,6 +412,11 @@ bool EditorApp::frameRenderingQueued(const Ogre::FrameEvent &evt)
if (m_proceduralMeshSystem) {
m_proceduralMeshSystem->update();
}
// Update CellGrid system
if (m_cellGridSystem) {
m_cellGridSystem->update();
}
// Don't call base class - it crashes when iterating input listeners
return true;

View File

@@ -21,6 +21,7 @@ class StaticGeometrySystem;
class ProceduralTextureSystem;
class ProceduralMaterialSystem;
class ProceduralMeshSystem;
class CellGridSystem;
/**
* RenderTargetListener for ImGui frame management
@@ -94,6 +95,7 @@ private:
std::unique_ptr<ProceduralTextureSystem> m_proceduralTextureSystem;
std::unique_ptr<ProceduralMaterialSystem> m_proceduralMaterialSystem;
std::unique_ptr<ProceduralMeshSystem> m_proceduralMeshSystem;
std::unique_ptr<CellGridSystem> m_cellGridSystem;
// State
uint16_t m_currentModifiers;

View File

@@ -0,0 +1,178 @@
#include "CellGrid.hpp"
#include <algorithm>
Cell* CellGridComponent::findCell(int x, int y, int z)
{
int64_t key = ((int64_t)x + 2048 * (int64_t)y + 2048 * 2048 * (int64_t)z);
for (auto& cell : cells) {
if (cell.getKey() == key) {
return &cell;
}
}
return nullptr;
}
const Cell* CellGridComponent::findCell(int x, int y, int z) const
{
int64_t key = ((int64_t)x + 2048 * (int64_t)y + 2048 * 2048 * (int64_t)z);
for (const auto& cell : cells) {
if (cell.getKey() == key) {
return &cell;
}
}
return nullptr;
}
Cell& CellGridComponent::getOrCreateCell(int x, int y, int z)
{
Cell* existing = findCell(x, y, z);
if (existing) {
return *existing;
}
Cell newCell;
newCell.x = x;
newCell.y = y;
newCell.z = z;
cells.push_back(newCell);
return cells.back();
}
void CellGridComponent::removeCell(int x, int y, int z)
{
int64_t key = ((int64_t)x + 2048 * (int64_t)y + 2048 * 2048 * (int64_t)z);
auto it = std::remove_if(cells.begin(), cells.end(),
[key](const Cell& c) { return c.getKey() == key; });
cells.erase(it, cells.end());
}
FurnitureCell* CellGridComponent::findFurnitureCell(int x, int y, int z)
{
int64_t key = ((int64_t)x + 2048 * (int64_t)y + 2048 * 2048 * (int64_t)z);
for (auto& cell : furnitureCells) {
if (cell.getKey() == key) {
return &cell;
}
}
return nullptr;
}
FurnitureCell& CellGridComponent::getOrCreateFurnitureCell(int x, int y, int z)
{
FurnitureCell* existing = findFurnitureCell(x, y, z);
if (existing) {
return *existing;
}
FurnitureCell newCell;
newCell.x = x;
newCell.y = y;
newCell.z = z;
furnitureCells.push_back(newCell);
return furnitureCells.back();
}
void CellGridComponent::removeFurnitureCell(int x, int y, int z)
{
int64_t key = ((int64_t)x + 2048 * (int64_t)y + 2048 * 2048 * (int64_t)z);
auto it = std::remove_if(furnitureCells.begin(), furnitureCells.end(),
[key](const FurnitureCell& c) { return c.getKey() == key; });
furnitureCells.erase(it, furnitureCells.end());
}
void CellGridComponent::clear()
{
cells.clear();
furnitureCells.clear();
markDirty();
}
Ogre::Vector3 CellGridComponent::cellToWorld(int x, int y, int z) const
{
return Ogre::Vector3(
x * cellSize,
y * cellHeight,
z * cellSize
);
}
void CellGridComponent::worldToCell(const Ogre::Vector3& worldPos, int& x, int& y, int& z) const
{
x = Ogre::Math::Floor(worldPos.x / cellSize);
y = Ogre::Math::Floor(worldPos.y / cellHeight);
z = Ogre::Math::Floor(worldPos.z / cellSize);
}
const char* CellGridComponent::getFlagName(uint64_t flag)
{
switch (flag) {
case CellFlags::Floor: return "Floor";
case CellFlags::Ceiling: return "Ceiling";
case CellFlags::WallXNeg: return "Ext Wall X-";
case CellFlags::WallXPos: return "Ext Wall X+";
case CellFlags::WallZPos: return "Ext Wall Z+";
case CellFlags::WallZNeg: return "Ext Wall Z-";
case CellFlags::DoorXNeg: return "Ext Door X-";
case CellFlags::DoorXPos: return "Ext Door X+";
case CellFlags::DoorZPos: return "Ext Door Z+";
case CellFlags::DoorZNeg: return "Ext Door Z-";
case CellFlags::WindowXNeg: return "Ext Window X-";
case CellFlags::WindowXPos: return "Ext Window X+";
case CellFlags::WindowZPos: return "Ext Window Z+";
case CellFlags::WindowZNeg: return "Ext Window Z-";
case CellFlags::IntWallXNeg: return "Int Wall X-";
case CellFlags::IntWallXPos: return "Int Wall X+";
case CellFlags::IntWallZPos: return "Int Wall Z+";
case CellFlags::IntWallZNeg: return "Int Wall Z-";
case CellFlags::IntDoorXNeg: return "Int Door X-";
case CellFlags::IntDoorXPos: return "Int Door X+";
case CellFlags::IntDoorZPos: return "Int Door Z+";
case CellFlags::IntDoorZNeg: return "Int Door Z-";
case CellFlags::IntWindowXNeg: return "Int Window X-";
case CellFlags::IntWindowXPos: return "Int Window X+";
case CellFlags::IntWindowZPos: return "Int Window Z+";
case CellFlags::IntWindowZNeg: return "Int Window Z-";
default: return "Unknown";
}
}
uint64_t CellGridComponent::getFlagByName(const std::string& name)
{
auto flags = getAllFlags();
for (const auto& [flag, flagName] : flags) {
if (name == flagName) {
return flag;
}
}
return 0;
}
std::vector<std::pair<uint64_t, const char*>> CellGridComponent::getAllFlags()
{
return {
{ CellFlags::Floor, "Floor" },
{ CellFlags::Ceiling, "Ceiling" },
{ CellFlags::WallXNeg, "Ext Wall X-" },
{ CellFlags::WallXPos, "Ext Wall X+" },
{ CellFlags::WallZPos, "Ext Wall Z+" },
{ CellFlags::WallZNeg, "Ext Wall Z-" },
{ CellFlags::DoorXNeg, "Ext Door X-" },
{ CellFlags::DoorXPos, "Ext Door X+" },
{ CellFlags::DoorZPos, "Ext Door Z+" },
{ CellFlags::DoorZNeg, "Ext Door Z-" },
{ CellFlags::WindowXNeg, "Ext Window X-" },
{ CellFlags::WindowXPos, "Ext Window X+" },
{ CellFlags::WindowZPos, "Ext Window Z+" },
{ CellFlags::WindowZNeg, "Ext Window Z-" },
{ CellFlags::IntWallXNeg, "Int Wall X-" },
{ CellFlags::IntWallXPos, "Int Wall X+" },
{ CellFlags::IntWallZPos, "Int Wall Z+" },
{ CellFlags::IntWallZNeg, "Int Wall Z-" },
{ CellFlags::IntDoorXNeg, "Int Door X-" },
{ CellFlags::IntDoorXPos, "Int Door X+" },
{ CellFlags::IntDoorZPos, "Int Door Z+" },
{ CellFlags::IntDoorZNeg, "Int Door Z-" },
{ CellFlags::IntWindowXNeg, "Int Window X-" },
{ CellFlags::IntWindowXPos, "Int Window X+" },
{ CellFlags::IntWindowZPos, "Int Window Z+" },
{ CellFlags::IntWindowZNeg, "Int Window Z-" },
};
}

View File

@@ -0,0 +1,321 @@
#pragma once
#include <cstdint>
#include <vector>
#include <string>
#include <unordered_map>
#include <flecs.h>
#include <Ogre.h>
/**
* @brief Cell flags for building geometry
*
* Each cell in the 3D grid can have these features:
* - Floor/Ceiling
* - External walls (4 directions)
* - External doors/windows (4 directions each)
* - Internal walls (4 directions)
* - Internal doors/windows (4 directions each)
*/
namespace CellFlags {
constexpr uint64_t Floor = 1ULL << 0;
constexpr uint64_t Ceiling = 1ULL << 1;
// External walls
constexpr uint64_t WallXNeg = 1ULL << 2; // Wall on -X side
constexpr uint64_t WallXPos = 1ULL << 3; // Wall on +X side
constexpr uint64_t WallZPos = 1ULL << 4; // Wall on +Z side
constexpr uint64_t WallZNeg = 1ULL << 5; // Wall on -Z side
// External doors
constexpr uint64_t DoorXNeg = 1ULL << 6;
constexpr uint64_t DoorXPos = 1ULL << 7;
constexpr uint64_t DoorZPos = 1ULL << 8;
constexpr uint64_t DoorZNeg = 1ULL << 9;
// External windows
constexpr uint64_t WindowXNeg = 1ULL << 10;
constexpr uint64_t WindowXPos = 1ULL << 11;
constexpr uint64_t WindowZPos = 1ULL << 12;
constexpr uint64_t WindowZNeg = 1ULL << 13;
// Internal walls
constexpr uint64_t IntWallXNeg = 1ULL << 14;
constexpr uint64_t IntWallXPos = 1ULL << 15;
constexpr uint64_t IntWallZPos = 1ULL << 16;
constexpr uint64_t IntWallZNeg = 1ULL << 17;
// Internal doors
constexpr uint64_t IntDoorXNeg = 1ULL << 18;
constexpr uint64_t IntDoorXPos = 1ULL << 19;
constexpr uint64_t IntDoorZPos = 1ULL << 20;
constexpr uint64_t IntDoorZNeg = 1ULL << 21;
// Internal windows
constexpr uint64_t IntWindowXNeg = 1ULL << 22;
constexpr uint64_t IntWindowXPos = 1ULL << 23;
constexpr uint64_t IntWindowZPos = 1ULL << 24;
constexpr uint64_t IntWindowZNeg = 1ULL << 25;
// Masks
constexpr uint64_t AllExternalWalls = WallXNeg | WallXPos | WallZPos | WallZNeg;
constexpr uint64_t AllInternalWalls = IntWallXNeg | IntWallXPos | IntWallZPos | IntWallZNeg;
constexpr uint64_t AllWalls = AllExternalWalls | AllInternalWalls;
constexpr uint64_t AllDoors = DoorXNeg | DoorXPos | DoorZPos | DoorZNeg |
IntDoorXNeg | IntDoorXPos | IntDoorZPos | IntDoorZNeg;
constexpr uint64_t AllWindows = WindowXNeg | WindowXPos | WindowZPos | WindowZNeg |
IntWindowXNeg | IntWindowXPos | IntWindowZPos | IntWindowZNeg;
}
/**
* @brief A single cell in the 3D grid
*/
struct Cell {
int x = 0, y = 0, z = 0;
uint64_t flags = 0;
// Generate unique key for this cell
int64_t getKey() const {
// Support -1024 to 1024 range for each axis
return (int64_t)x + 2048 * (int64_t)y + 2048 * 2048 * (int64_t)z;
}
bool hasFlag(uint64_t flag) const { return (flags & flag) != 0; }
void setFlag(uint64_t flag) { flags |= flag; }
void clearFlag(uint64_t flag) { flags &= ~flag; }
void toggleFlag(uint64_t flag) { flags ^= flag; }
};
/**
* @brief Furniture placement in a cell
*/
struct FurnitureCell {
int x = 0, y = 0, z = 0;
std::vector<std::string> tags;
std::string furnitureType;
int rotation = 0; // 0-3, representing 0, 90, 180, 270 degrees
int64_t getKey() const {
return (int64_t)x + 2048 * (int64_t)y + 2048 * 2048 * (int64_t)z;
}
};
/**
* @brief Cell grid for procedural building generation
*
* This component stores a sparse 3D grid of cells that define
* building geometry (walls, floors, doors, windows).
*
* Used by: House/Lot generation, dungeon generation
*/
struct CellGridComponent {
// Grid dimensions (in cells)
int width = 10; // X dimension
int height = 1; // Y dimension (floors)
int depth = 10; // Z dimension
// Cell size in world units
float cellSize = 4.0f;
float cellHeight = 4.0f;
// The cell data (sparse storage)
std::vector<Cell> cells;
std::vector<FurnitureCell> furnitureCells;
// Generation script (Lua or custom format)
std::string generationScript;
// Dirty flag - triggers rebuild
bool dirty = true;
unsigned int version = 0;
// Find cell at position (returns nullptr if not found)
Cell* findCell(int x, int y, int z);
const Cell* findCell(int x, int y, int z) const;
// Get or create cell at position
Cell& getOrCreateCell(int x, int y, int z);
// Remove cell at position
void removeCell(int x, int y, int z);
// Find furniture cell
FurnitureCell* findFurnitureCell(int x, int y, int z);
// Get or create furniture cell
FurnitureCell& getOrCreateFurnitureCell(int x, int y, int z);
// Remove furniture cell
void removeFurnitureCell(int x, int y, int z);
// Clear all cells
void clear();
// Mark for rebuild
void markDirty() { dirty = true; version++; }
// Convert local cell position to world position
Ogre::Vector3 cellToWorld(int x, int y, int z) const;
// Convert world position to cell coordinates
void worldToCell(const Ogre::Vector3& worldPos, int& x, int& y, int& z) const;
// Get bit name for editor display
static const char* getFlagName(uint64_t flag);
static uint64_t getFlagByName(const std::string& name);
static std::vector<std::pair<uint64_t, const char*>> getAllFlags();
};
/**
* @brief Room definition within a cell grid
*
* Rooms are rectangular areas that can be connected by doors
*/
struct RoomComponent {
// Room bounds (in cell coordinates, inclusive)
int minX = 0, minY = 0, minZ = 0;
int maxX = 0, maxY = 0, maxZ = 0;
// 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;
// Exit directions (0-3 = Z-, Z+, X-, X+)
std::vector<int> exits;
};
/**
* @brief Roof definition for a lot
*/
struct RoofComponent {
enum Type {
Flat = 0,
Normal = 1,
Normal2 = 2,
Cone = 3,
Cylinder = 4
};
Type type = Flat;
// Position (in cell coordinates)
int posX = 0, posY = 0, posZ = 0;
// Size (in cells)
int sizeX = 1, sizeZ = 1;
// Offset from cell position (world units)
float offsetX = 0.0f, offsetY = 0.0f, offsetZ = 0.0f;
// Height parameters
float baseHeight = 0.5f;
float maxHeight = 0.5f;
};
/**
* @brief Lot component - represents a single house/building lot
*
* A lot contains:
* - Cell grid (walls, floors, etc.)
* - Room definitions
* - Roof definitions
* - Furniture placement rules
*/
struct LotComponent {
// Lot dimensions (in cells)
int width = 10;
int depth = 10;
// Elevation offset from district
float elevation = 0.0f;
// Rotation around district center (degrees)
float angle = 0.0f;
// Position offset (for manual placement)
float offsetX = 0.0f, offsetZ = 0.0f;
// Template name if this lot was created from a template
std::string templateName;
// Dirty flag
bool dirty = true;
void markDirty() { dirty = true; }
};
/**
* @brief District component - contains multiple lots arranged in a circle
*/
struct DistrictComponent {
// District radius (distance from center to lots)
float radius = 50.0f;
// Elevation offset
float elevation = 0.0f;
// Height of district base (for plaza)
float height = 0.2f;
// Is this a plaza (circular base)?
bool isPlaza = false;
// Lot templates for this district
std::vector<std::string> lotTemplateNames;
// Procedural material for plaza (references entity with ProceduralMaterialComponent)
flecs::entity proceduralMaterialEntity;
std::string proceduralMaterialEntityId; // For serialization
// Texture rectangle name from ProceduralTexture for UV mapping
std::string textureRectName;
// Dirty flag
bool dirty = true;
void markDirty() { dirty = true; }
};
/**
* @brief Town component - top-level container
*/
struct TownComponent {
std::string townName = "New Town";
// Color rectangles for texture atlas
struct ColorRect {
float left = 0.0f, top = 0.0f, right = 1.0f, bottom = 1.0f;
Ogre::ColourValue color = Ogre::ColourValue::White;
};
std::unordered_map<std::string, ColorRect> colorRects;
// Material name (created from color rects)
std::string materialName;
// Dirty flag
bool dirty = true;
bool materialDirty = true;
void markDirty() { dirty = true; }
void markMaterialDirty() { materialDirty = true; }
};
/**
* @brief Furniture template - defines a furniture item
*/
struct FurnitureTemplateComponent {
std::string templateName;
std::vector<std::string> tags; // e.g., "bed", "essential", "bedroom"
// Mesh/model info
std::string meshName;
std::string materialName;
// Placement rules
bool requiresWall = false;
bool requiresFloor = true;
bool blocksPath = true;
// Offset from cell center
float offsetX = 0.0f, offsetY = 0.0f, offsetZ = 0.0f;
// Random selection weight
float weight = 1.0f;
};

View File

@@ -0,0 +1,142 @@
#include "CellGrid.hpp"
#include "../ui/ComponentRegistration.hpp"
#include "../ui/CellGridEditor.hpp"
#include "../ui/LotEditor.hpp"
#include "../ui/DistrictEditor.hpp"
#include "../ui/TownEditor.hpp"
#include "../ui/RoofEditor.hpp"
#include "../ui/RoomEditor.hpp"
#include "../ui/FurnitureTemplateEditor.hpp"
// Register CellGrid component
REGISTER_COMPONENT("Cell Grid", CellGridComponent, CellGridEditor)
{
registry.registerComponent<CellGridComponent>(
"Cell Grid",
std::make_unique<CellGridEditor>(),
[sceneMgr](flecs::entity e) {
if (!e.has<CellGridComponent>()) {
e.set<CellGridComponent>({});
}
},
[sceneMgr](flecs::entity e) {
if (e.has<CellGridComponent>()) {
e.remove<CellGridComponent>();
}
}
);
}
// Register Lot component
REGISTER_COMPONENT("Lot", LotComponent, LotEditor)
{
registry.registerComponent<LotComponent>(
"Lot",
std::make_unique<LotEditor>(),
[sceneMgr](flecs::entity e) {
if (!e.has<LotComponent>()) {
e.set<LotComponent>({});
}
},
[sceneMgr](flecs::entity e) {
if (e.has<LotComponent>()) {
e.remove<LotComponent>();
}
}
);
}
// Register District component
REGISTER_COMPONENT("District", DistrictComponent, DistrictEditor)
{
registry.registerComponent<DistrictComponent>(
"District",
std::make_unique<DistrictEditor>(),
[sceneMgr](flecs::entity e) {
if (!e.has<DistrictComponent>()) {
e.set<DistrictComponent>({});
}
},
[sceneMgr](flecs::entity e) {
if (e.has<DistrictComponent>()) {
e.remove<DistrictComponent>();
}
}
);
}
// Register Town component
REGISTER_COMPONENT("Town", TownComponent, TownEditor)
{
registry.registerComponent<TownComponent>(
"Town",
std::make_unique<TownEditor>(),
[sceneMgr](flecs::entity e) {
if (!e.has<TownComponent>()) {
e.set<TownComponent>({});
}
},
[sceneMgr](flecs::entity e) {
if (e.has<TownComponent>()) {
e.remove<TownComponent>();
}
}
);
}
// Register Roof component
REGISTER_COMPONENT("Roof", RoofComponent, RoofEditor)
{
registry.registerComponent<RoofComponent>(
"Roof",
std::make_unique<RoofEditor>(),
[sceneMgr](flecs::entity e) {
if (!e.has<RoofComponent>()) {
e.set<RoofComponent>({});
}
},
[sceneMgr](flecs::entity e) {
if (e.has<RoofComponent>()) {
e.remove<RoofComponent>();
}
}
);
}
// Register Room component
REGISTER_COMPONENT("Room", RoomComponent, RoomEditor)
{
registry.registerComponent<RoomComponent>(
"Room",
std::make_unique<RoomEditor>(),
[sceneMgr](flecs::entity e) {
if (!e.has<RoomComponent>()) {
e.set<RoomComponent>({});
}
},
[sceneMgr](flecs::entity e) {
if (e.has<RoomComponent>()) {
e.remove<RoomComponent>();
}
}
);
}
// Register FurnitureTemplate component
REGISTER_COMPONENT("Furniture Template", FurnitureTemplateComponent, FurnitureTemplateEditor)
{
registry.registerComponent<FurnitureTemplateComponent>(
"Furniture Template",
std::make_unique<FurnitureTemplateEditor>(),
[sceneMgr](flecs::entity e) {
if (!e.has<FurnitureTemplateComponent>()) {
e.set<FurnitureTemplateComponent>({});
}
},
[sceneMgr](flecs::entity e) {
if (e.has<FurnitureTemplateComponent>()) {
e.remove<FurnitureTemplateComponent>();
}
}
);
}

View File

@@ -0,0 +1,46 @@
#include "CellGridModule.hpp"
#include "CellGrid.hpp"
#include <nlohmann/json.hpp>
#include <Ogre.h>
namespace CellGridModule {
using json = nlohmann::json;
void registerComponents(flecs::world& world)
{
// Register components without .member<> for complex types (std::string, std::vector)
// Flecs doesn't have built-in reflection for these, but JSON serialization handles them
// CellGridComponent
world.component<CellGridComponent>();
// Cell struct
world.component<Cell>();
// FurnitureCell struct
world.component<FurnitureCell>();
// LotComponent
world.component<LotComponent>();
// DistrictComponent
world.component<DistrictComponent>();
// TownComponent
world.component<TownComponent>();
// TownComponent::ColorRect
world.component<TownComponent::ColorRect>();
// RoomComponent
world.component<RoomComponent>();
// RoofComponent
world.component<RoofComponent>();
// FurnitureTemplateComponent
world.component<FurnitureTemplateComponent>();
}
} // namespace CellGridModule

View File

@@ -0,0 +1,7 @@
#pragma once
#include <flecs.h>
namespace CellGridModule {
void registerComponents(flecs::world& world);
}

View File

@@ -0,0 +1,682 @@
#include "CellGridSystem.hpp"
#include "../components/CellGrid.hpp"
#include "../components/TriangleBuffer.hpp"
#include "../components/Transform.hpp"
#include "../components/StaticGeometry.hpp"
#include "../components/StaticGeometryMember.hpp"
#include "../components/ProceduralMaterial.hpp"
#include "../components/ProceduralTexture.hpp"
#include <OgreSceneManager.h>
#include <OgreMeshManager.h>
#include <OgreEntity.h>
#include <OgreSubMesh.h>
#include <OgreLogManager.h>
#include <OgreMaterialManager.h>
#include <OgreTechnique.h>
#include <OgrePass.h>
#include <OgreTextureManager.h>
#include <ProceduralTriangleBuffer.h>
#include <ProceduralBoxGenerator.h>
#include <ProceduralPlaneGenerator.h>
#include <ProceduralCylinderGenerator.h>
#include <ProceduralConeGenerator.h>
#include <ProceduralLathe.h>
#include <ProceduralShape.h>
CellGridSystem::CellGridSystem(flecs::world& world, Ogre::SceneManager* sceneMgr)
: m_world(world)
, m_sceneMgr(sceneMgr)
, m_cellGridQuery(world.query<CellGridComponent>())
, m_townQuery(world.query<TownComponent>())
, m_districtQuery(world.query<DistrictComponent>())
{
}
CellGridSystem::~CellGridSystem() = default;
void CellGridSystem::initialize()
{
if (m_initialized) return;
m_initialized = true;
Ogre::LogManager::getSingleton().logMessage("CellGridSystem initialized");
}
void CellGridSystem::update()
{
if (!m_initialized) return;
// Process dirty cell grids
m_cellGridQuery.each([&](flecs::entity entity, CellGridComponent& grid) {
if (grid.dirty) {
buildCellGrid(entity, grid);
grid.dirty = false;
}
});
// Update town materials
m_townQuery.each([&](flecs::entity entity, TownComponent& town) {
if (town.materialDirty) {
updateTownMaterial(entity);
town.materialDirty = false;
}
});
// Process dirty districts (for plaza generation)
int districtCount = 0;
int dirtyDistrictCount = 0;
m_districtQuery.each([&](flecs::entity entity, DistrictComponent& district) {
districtCount++;
if (district.dirty) {
dirtyDistrictCount++;
Ogre::LogManager::getSingleton().logMessage("CellGrid: Processing dirty district " +
std::to_string(entity.id()) + " isPlaza=" + (district.isPlaza ? "true" : "false"));
buildDistrictPlaza(entity, district);
district.dirty = false;
}
});
static int frameCount = 0;
if (++frameCount % 60 == 0) {
Ogre::LogManager::getSingleton().logMessage("CellGrid: update() - " +
std::to_string(districtCount) + " districts, " +
std::to_string(dirtyDistrictCount) + " dirty");
}
}
void CellGridSystem::buildCellGrid(flecs::entity entity, CellGridComponent& grid)
{
Ogre::LogManager::getSingleton().logMessage("CellGrid: buildCellGrid for entity " + std::to_string(entity.id()) +
" cells=" + std::to_string(grid.cells.size()));
// Destroy existing meshes
destroyCellGridMeshes(grid);
// Find parent town for material
std::string materialName = "Ogre/StandardFloor"; // default
flecs::entity townEntity = flecs::entity::null();
// Look for TownComponent in parent hierarchy
flecs::entity parent = entity.parent();
while (parent.is_alive()) {
if (parent.has<TownComponent>()) {
townEntity = parent;
auto& town = parent.get<TownComponent>();
materialName = town.materialName.empty() ? "TownMaterial_" + std::to_string(parent.id()) : town.materialName;
break;
}
parent = parent.parent();
}
if (!townEntity.is_alive()) {
Ogre::LogManager::getSingleton().logMessage("CellGrid: No parent town found for entity " + std::to_string(entity.id()) + ", using default material");
} else {
Ogre::LogManager::getSingleton().logMessage("CellGrid: Found town " + std::to_string(townEntity.id()) +
" for entity " + std::to_string(entity.id()) + " material=" + materialName);
}
MeshData& meshData = m_entityMeshes[entity.id()];
// Build geometry parts
Procedural::TriangleBuffer floorTb, ceilingTb, extWallTb, intWallTb, intWindowsTb, roofTb;
std::vector<Procedural::TriangleBuffer> doorTbs, windowTbs;
buildFloorsAndCeilings(grid, floorTb, ceilingTb);
buildWalls(grid, extWallTb, intWallTb, intWindowsTb);
buildCorners(grid, extWallTb);
buildDoors(grid, doorTbs);
buildWindows(grid, windowTbs);
buildRoofs(entity, grid, roofTb);
// Generate unique base name
std::string baseName = "CellGrid_" + std::to_string(entity.id()) + "_" + std::to_string(m_meshCount++);
// Convert to meshes and create entities
if (!entity.has<TransformComponent>()) {
Ogre::LogManager::getSingleton().logMessage("CellGrid: No TransformComponent for entity " + std::to_string(entity.id()));
return;
}
auto& transform = entity.get_mut<TransformComponent>();
if (!transform.node) {
Ogre::LogManager::getSingleton().logMessage("CellGrid: No SceneNode for entity " + std::to_string(entity.id()));
return;
}
// Create meshes for each part
if (floorTb.getVertices().size() >= 3) {
meshData.floorMesh = baseName + "_floor";
auto mesh = convertToMesh(meshData.floorMesh, floorTb, materialName);
auto entity3d = m_sceneMgr->createEntity(meshData.floorMesh);
transform.node->attachObject(entity3d);
meshData.entities.push_back(entity3d);
}
if (ceilingTb.getVertices().size() >= 3) {
meshData.ceilingMesh = baseName + "_ceiling";
auto mesh = convertToMesh(meshData.ceilingMesh, ceilingTb, materialName);
auto entity3d = m_sceneMgr->createEntity(meshData.ceilingMesh);
transform.node->attachObject(entity3d);
meshData.entities.push_back(entity3d);
}
if (extWallTb.getVertices().size() >= 3) {
meshData.extWallMesh = baseName + "_extWall";
auto mesh = convertToMesh(meshData.extWallMesh, extWallTb, materialName);
auto entity3d = m_sceneMgr->createEntity(meshData.extWallMesh);
transform.node->attachObject(entity3d);
meshData.entities.push_back(entity3d);
}
if (intWallTb.getVertices().size() >= 3) {
meshData.intWallMesh = baseName + "_intWall";
auto mesh = convertToMesh(meshData.intWallMesh, intWallTb, materialName);
auto entity3d = m_sceneMgr->createEntity(meshData.intWallMesh);
transform.node->attachObject(entity3d);
meshData.entities.push_back(entity3d);
}
if (intWindowsTb.getVertices().size() >= 3) {
meshData.intWindowsMesh = baseName + "_intWindows";
auto mesh = convertToMesh(meshData.intWindowsMesh, intWindowsTb, materialName);
auto entity3d = m_sceneMgr->createEntity(meshData.intWindowsMesh);
transform.node->attachObject(entity3d);
meshData.entities.push_back(entity3d);
}
// Roof
if (roofTb.getVertices().size() >= 3) {
meshData.roofMesh = baseName + "_roof";
auto mesh = convertToMesh(meshData.roofMesh, roofTb, materialName);
auto entity3d = m_sceneMgr->createEntity(meshData.roofMesh);
transform.node->attachObject(entity3d);
meshData.entities.push_back(entity3d);
}
Ogre::LogManager::getSingleton().logMessage("CellGrid: Built geometry for entity " + std::to_string(entity.id()));
}
void CellGridSystem::buildFloorsAndCeilings(const CellGridComponent& grid,
Procedural::TriangleBuffer& floorTb,
Procedural::TriangleBuffer& ceilingTb)
{
const float cellSize = grid.cellSize;
const float floorHeight = 0.1f;
const float ceilingOffset = grid.cellHeight - 0.3f;
for (const auto& cell : grid.cells) {
Ogre::Vector3 origin = grid.cellToWorld(cell.x, cell.y, cell.z);
// Floor
if (cell.hasFlag(CellFlags::Floor)) {
Procedural::BoxGenerator box;
box.setSizeX(cellSize - 0.1f);
box.setSizeY(floorHeight);
box.setSizeZ(cellSize - 0.1f);
box.setPosition(Ogre::Vector3(origin.x, origin.y + floorHeight/2.0f, origin.z));
box.addToTriangleBuffer(floorTb);
}
// Ceiling
if (cell.hasFlag(CellFlags::Ceiling)) {
Procedural::BoxGenerator box;
box.setSizeX(cellSize - 0.1f);
box.setSizeY(0.1f);
box.setSizeZ(cellSize - 0.1f);
box.setPosition(Ogre::Vector3(origin.x, origin.y + ceilingOffset, origin.z));
box.addToTriangleBuffer(ceilingTb);
}
}
}
void CellGridSystem::buildWalls(const CellGridComponent& grid,
Procedural::TriangleBuffer& extWallTb,
Procedural::TriangleBuffer& intWallTb,
Procedural::TriangleBuffer& intWindowsTb)
{
const float cellSize = grid.cellSize;
const float solidExtHeight = grid.cellHeight;
const float solidExtOffset = 0.0f;
const float solidIntOffset = 0.1f;
const float solidIntHeight = grid.cellHeight - 0.3f - solidIntOffset;
for (const auto& cell : grid.cells) {
Ogre::Vector3 origin = grid.cellToWorld(cell.x, cell.y, cell.z);
// External walls
if (cell.hasFlag(CellFlags::WallXNeg)) {
Procedural::BoxGenerator box;
box.setSizeX(0.2f);
box.setSizeY(solidExtHeight);
box.setSizeZ(cellSize);
box.setPosition(Ogre::Vector3(origin.x - cellSize/2.0f - 0.1f,
origin.y + solidExtHeight/2.0f, origin.z));
box.addToTriangleBuffer(extWallTb);
}
if (cell.hasFlag(CellFlags::WallXPos)) {
Procedural::BoxGenerator box;
box.setSizeX(0.2f);
box.setSizeY(solidExtHeight);
box.setSizeZ(cellSize);
box.setPosition(Ogre::Vector3(origin.x + cellSize/2.0f + 0.1f,
origin.y + solidExtHeight/2.0f, origin.z));
box.addToTriangleBuffer(extWallTb);
}
if (cell.hasFlag(CellFlags::WallZPos)) {
Procedural::BoxGenerator box;
box.setSizeX(cellSize);
box.setSizeY(solidExtHeight);
box.setSizeZ(0.2f);
box.setPosition(Ogre::Vector3(origin.x, origin.y + solidExtHeight/2.0f,
origin.z + cellSize/2.0f + 0.1f));
box.addToTriangleBuffer(extWallTb);
}
if (cell.hasFlag(CellFlags::WallZNeg)) {
Procedural::BoxGenerator box;
box.setSizeX(cellSize);
box.setSizeY(solidExtHeight);
box.setSizeZ(0.2f);
box.setPosition(Ogre::Vector3(origin.x, origin.y + solidExtHeight/2.0f,
origin.z - cellSize/2.0f - 0.1f));
box.addToTriangleBuffer(extWallTb);
}
// Internal walls
if (cell.hasFlag(CellFlags::IntWallXNeg)) {
Procedural::BoxGenerator box;
box.setSizeX(0.1f);
box.setSizeY(solidIntHeight);
box.setSizeZ(cellSize - 0.4f);
box.setPosition(Ogre::Vector3(origin.x - cellSize/2.0f + 0.05f,
origin.y + solidIntHeight/2.0f + solidIntOffset, origin.z));
box.addToTriangleBuffer(intWallTb);
}
if (cell.hasFlag(CellFlags::IntWallXPos)) {
Procedural::BoxGenerator box;
box.setSizeX(0.1f);
box.setSizeY(solidIntHeight);
box.setSizeZ(cellSize - 0.4f);
box.setPosition(Ogre::Vector3(origin.x + cellSize/2.0f - 0.05f,
origin.y + solidIntHeight/2.0f + solidIntOffset, origin.z));
box.addToTriangleBuffer(intWallTb);
}
if (cell.hasFlag(CellFlags::IntWallZPos)) {
Procedural::BoxGenerator box;
box.setSizeX(cellSize - 0.4f);
box.setSizeY(solidIntHeight);
box.setSizeZ(0.1f);
box.setPosition(Ogre::Vector3(origin.x, origin.y + solidIntHeight/2.0f + solidIntOffset,
origin.z + cellSize/2.0f - 0.05f));
box.addToTriangleBuffer(intWallTb);
}
if (cell.hasFlag(CellFlags::IntWallZNeg)) {
Procedural::BoxGenerator box;
box.setSizeX(cellSize - 0.4f);
box.setSizeY(solidIntHeight);
box.setSizeZ(0.1f);
box.setPosition(Ogre::Vector3(origin.x, origin.y + solidIntHeight/2.0f + solidIntOffset,
origin.z - cellSize/2.0f + 0.05f));
box.addToTriangleBuffer(intWallTb);
}
}
}
void CellGridSystem::buildCorners(const CellGridComponent& grid, Procedural::TriangleBuffer& extWallTb)
{
// Build external wall corners where walls meet
const float cellSize = grid.cellSize;
const float solidExtHeight = grid.cellHeight;
for (const auto& cell : grid.cells) {
Ogre::Vector3 origin = grid.cellToWorld(cell.x, cell.y, cell.z);
uint64_t wallFlags = cell.flags & CellFlags::AllExternalWalls;
// Corner cases: X- with Z+, X- with Z-, X+ with Z+, X+ with Z-
if ((cell.hasFlag(CellFlags::WallXNeg) || cell.hasFlag(CellFlags::WallZPos)) &&
(wallFlags & (CellFlags::WallXNeg | CellFlags::WallZPos)) == (CellFlags::WallXNeg | CellFlags::WallZPos)) {
// Corner at X-, Z+
Procedural::BoxGenerator box;
box.setSizeX(0.2f);
box.setSizeY(solidExtHeight);
box.setSizeZ(0.2f);
box.setPosition(Ogre::Vector3(origin.x - cellSize/2.0f - 0.1f,
origin.y + solidExtHeight/2.0f,
origin.z + cellSize/2.0f + 0.1f));
box.addToTriangleBuffer(extWallTb);
}
if ((cell.hasFlag(CellFlags::WallXNeg) || cell.hasFlag(CellFlags::WallZNeg)) &&
(wallFlags & (CellFlags::WallXNeg | CellFlags::WallZNeg)) == (CellFlags::WallXNeg | CellFlags::WallZNeg)) {
// Corner at X-, Z-
Procedural::BoxGenerator box;
box.setSizeX(0.2f);
box.setSizeY(solidExtHeight);
box.setSizeZ(0.2f);
box.setPosition(Ogre::Vector3(origin.x - cellSize/2.0f - 0.1f,
origin.y + solidExtHeight/2.0f,
origin.z - cellSize/2.0f - 0.1f));
box.addToTriangleBuffer(extWallTb);
}
if ((cell.hasFlag(CellFlags::WallXPos) || cell.hasFlag(CellFlags::WallZPos)) &&
(wallFlags & (CellFlags::WallXPos | CellFlags::WallZPos)) == (CellFlags::WallXPos | CellFlags::WallZPos)) {
// Corner at X+, Z+
Procedural::BoxGenerator box;
box.setSizeX(0.2f);
box.setSizeY(solidExtHeight);
box.setSizeZ(0.2f);
box.setPosition(Ogre::Vector3(origin.x + cellSize/2.0f + 0.1f,
origin.y + solidExtHeight/2.0f,
origin.z + cellSize/2.0f + 0.1f));
box.addToTriangleBuffer(extWallTb);
}
if ((cell.hasFlag(CellFlags::WallXPos) || cell.hasFlag(CellFlags::WallZNeg)) &&
(wallFlags & (CellFlags::WallXPos | CellFlags::WallZNeg)) == (CellFlags::WallXPos | CellFlags::WallZNeg)) {
// Corner at X+, Z-
Procedural::BoxGenerator box;
box.setSizeX(0.2f);
box.setSizeY(solidExtHeight);
box.setSizeZ(0.2f);
box.setPosition(Ogre::Vector3(origin.x + cellSize/2.0f + 0.1f,
origin.y + solidExtHeight/2.0f,
origin.z - cellSize/2.0f - 0.1f));
box.addToTriangleBuffer(extWallTb);
}
}
}
void CellGridSystem::buildDoors(const CellGridComponent& grid, std::vector<Procedural::TriangleBuffer>& doorTbs)
{
// Doors are cutouts in walls - we build the wall around them
// For now, use wall geometry with gaps
// This is simplified - full implementation needs more complex geometry
}
void CellGridSystem::buildWindows(const CellGridComponent& grid, std::vector<Procedural::TriangleBuffer>& windowTbs)
{
// Windows are cutouts in walls - similar to doors
}
void CellGridSystem::buildRoofs(flecs::entity lotEntity, const CellGridComponent& grid, Procedural::TriangleBuffer& roofTb)
{
if (!lotEntity.has<LotComponent>()) return;
// Get roof components from lot's children
lotEntity.children([&](flecs::entity child) {
if (child.has<RoofComponent>()) {
auto& roof = child.get<RoofComponent>();
Ogre::Vector3 origin = grid.cellToWorld(roof.posX, roof.posY, roof.posZ);
origin += Ogre::Vector3(roof.offsetX, roof.offsetY, roof.offsetZ);
float width = roof.sizeX * grid.cellSize;
float depth = roof.sizeZ * grid.cellSize;
float height = roof.maxHeight - roof.baseHeight;
switch (roof.type) {
case RoofComponent::Flat: {
Procedural::BoxGenerator box;
box.setSizeX(width);
box.setSizeY(roof.baseHeight);
box.setSizeZ(depth);
box.setPosition(Ogre::Vector3(origin.x + width/2.0f - grid.cellSize/2.0f,
origin.y + roof.baseHeight/2.0f,
origin.z + depth/2.0f - grid.cellSize/2.0f));
box.addToTriangleBuffer(roofTb);
break;
}
case RoofComponent::Normal:
case RoofComponent::Normal2: {
// Gable roof using two rotated boxes or a prism
// Simplified as a box for now
Procedural::BoxGenerator box;
box.setSizeX(width);
box.setSizeY(roof.baseHeight + height/2.0f);
box.setSizeZ(depth);
box.setPosition(Ogre::Vector3(origin.x + width/2.0f - grid.cellSize/2.0f,
origin.y + (roof.baseHeight + height/2.0f)/2.0f,
origin.z + depth/2.0f - grid.cellSize/2.0f));
box.addToTriangleBuffer(roofTb);
break;
}
case RoofComponent::Cone: {
Procedural::ConeGenerator cone;
cone.setRadius(width/2.0f);
cone.setHeight(roof.baseHeight + height);
cone.setPosition(Ogre::Vector3(origin.x, origin.y, origin.z));
cone.addToTriangleBuffer(roofTb);
break;
}
case RoofComponent::Cylinder: {
Procedural::CylinderGenerator cyl;
cyl.setRadius(width/2.0f);
cyl.setHeight(roof.baseHeight);
cyl.setPosition(Ogre::Vector3(origin.x, origin.y + roof.baseHeight/2.0f, origin.z));
cyl.addToTriangleBuffer(roofTb);
break;
}
}
}
});
}
Ogre::MeshPtr CellGridSystem::convertToMesh(const std::string& name, Procedural::TriangleBuffer& tb, const std::string& materialName)
{
try {
Ogre::MeshPtr mesh = tb.transformToMesh(name);
if (!materialName.empty() && mesh->getNumSubMeshes() > 0) {
mesh->getSubMesh(0)->setMaterialName(materialName);
}
return mesh;
} catch (const Ogre::Exception& e) {
Ogre::LogManager::getSingleton().logMessage("CellGrid: Failed to create mesh '" + name + "': " + e.getDescription());
return Ogre::MeshPtr();
}
}
void CellGridSystem::destroyCellGridMeshes(CellGridComponent& grid)
{
// Cleanup handled by entity destruction, but we track mesh names
}
void CellGridSystem::updateTownMaterial(flecs::entity townEntity)
{
if (!townEntity.has<TownComponent>()) return;
auto& town = townEntity.get_mut<TownComponent>();
if (town.materialName.empty()) {
town.materialName = "TownMaterial_" + std::to_string(townEntity.id());
}
// Create or update material
auto& matMgr = Ogre::MaterialManager::getSingleton();
Ogre::MaterialPtr mat;
if (matMgr.resourceExists(town.materialName, Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME)) {
mat = matMgr.getByName(town.materialName);
mat->unload();
} else {
mat = matMgr.create(town.materialName, Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
}
// For now, create a simple colored material
// Full implementation would create texture atlas from color rects
auto pass = mat->getTechnique(0)->getPass(0);
pass->setDiffuse(Ogre::ColourValue(0.8f, 0.8f, 0.8f));
pass->setAmbient(Ogre::ColourValue(0.4f, 0.4f, 0.4f));
pass->setSpecular(Ogre::ColourValue(0.1f, 0.1f, 0.1f));
Ogre::LogManager::getSingleton().logMessage("CellGrid: Updated town material '" + town.materialName + "'");
}
void CellGridSystem::rebuildCellGrid(flecs::entity entity)
{
if (!entity.is_alive() || !entity.has<CellGridComponent>()) return;
entity.get_mut<CellGridComponent>().markDirty();
}
void CellGridSystem::buildDistrictPlaza(flecs::entity entity, DistrictComponent& district)
{
// Destroy existing plaza if any
auto it = m_plazaMeshes.find(entity.id());
if (it != m_plazaMeshes.end()) {
if (it->second.entity) {
try {
m_sceneMgr->destroyEntity(it->second.entity);
} catch (...) {}
}
if (!it->second.meshName.empty()) {
try {
Ogre::MeshManager::getSingleton().remove(it->second.meshName);
} catch (...) {}
}
m_plazaMeshes.erase(it);
}
if (!district.isPlaza) {
return;
}
// Get transform from entity
if (!entity.has<TransformComponent>()) {
Ogre::LogManager::getSingleton().logMessage("CellGrid: District has no TransformComponent");
return;
}
auto& transform = entity.get_mut<TransformComponent>();
if (!transform.node) {
Ogre::LogManager::getSingleton().logMessage("CellGrid: District has no SceneNode");
return;
}
// Get material from district's procedural material entity
std::string materialName = "Ogre/StandardFloor"; // fallback
if (district.proceduralMaterialEntity.is_alive() &&
district.proceduralMaterialEntity.has<ProceduralMaterialComponent>()) {
const auto& mat = district.proceduralMaterialEntity.get<ProceduralMaterialComponent>();
if (mat.created && !mat.materialName.empty()) {
materialName = mat.materialName;
}
}
Ogre::LogManager::getSingleton().logMessage("CellGrid: Building plaza for district " + std::to_string(entity.id()) +
" with material: " + materialName);
// Create plaza mesh using Lathe
Procedural::TriangleBuffer tb;
Procedural::Shape plazaShape;
float radius = district.radius;
float height = district.height;
float elevation = district.elevation;
float mh = 4.0f; // margin height
// Build the plaza shape profile (based on original code)
plazaShape.addPoint(0, -height - mh - mh);
plazaShape.addPoint(radius * 0.5f + mh, -height - mh - mh);
plazaShape.addPoint(radius + mh, -height - mh);
plazaShape.addPoint(radius, -height);
plazaShape.addPoint(radius, 0.0f);
plazaShape.addPoint(radius - 0.1f, 0.1f);
plazaShape.addPoint(radius - mh + 0.1f, height);
plazaShape.addPoint(radius - mh, height + 0.1f);
plazaShape.addPoint(radius * 0.5f + mh, height);
plazaShape.addPoint(0, height);
// Generate lathe geometry
Procedural::Lathe()
.setShapeToExtrude(&plazaShape)
.setEnableNormals(true)
.setPosition(Ogre::Vector3(0.0f, height / 2.0f + elevation, 0.0f))
.setNumSeg(24)
.addToTriangleBuffer(tb);
// Check if we have valid geometry
size_t vertexCount = tb.getVertices().size();
size_t indexCount = tb.getIndices().size();
Ogre::LogManager::getSingleton().logMessage("CellGrid: Plaza triangle buffer has " +
std::to_string(vertexCount) + " vertices, " + std::to_string(indexCount) + " indices");
if (vertexCount < 3) {
Ogre::LogManager::getSingleton().logMessage("CellGrid: Not enough vertices for plaza mesh");
return;
}
// Apply UV mapping if texture rect is specified
if (!district.textureRectName.empty()) {
// Find the texture entity from the material
flecs::entity textureEntity = flecs::entity::null();
if (district.proceduralMaterialEntity.is_alive() &&
district.proceduralMaterialEntity.has<ProceduralMaterialComponent>()) {
const auto& mat = district.proceduralMaterialEntity.get<ProceduralMaterialComponent>();
// Search for the texture that is referenced by this material
if (mat.diffuseTextureEntity.is_alive()) {
textureEntity = mat.diffuseTextureEntity;
}
}
if (textureEntity.is_alive() && textureEntity.has<ProceduralTextureComponent>()) {
const auto& texture = textureEntity.get<ProceduralTextureComponent>();
const TextureRectInfo* rect = texture.getNamedRect(district.textureRectName);
if (rect) {
// Apply UV mapping with 0.01 margin
const float margin = 0.01f;
float uRange = (rect->u2 - rect->u1) * (1.0f - 2.0f * margin);
float vRange = (rect->v2 - rect->v1) * (1.0f - 2.0f * margin);
float uOffset = rect->u1 + (rect->u2 - rect->u1) * margin;
float vOffset = rect->v1 + (rect->v2 - rect->v1) * margin;
auto& vertices = tb.getVertices();
for (auto& vertex : vertices) {
vertex.mUV.x = uOffset + vertex.mUV.x * uRange;
vertex.mUV.y = vOffset + vertex.mUV.y * vRange;
}
Ogre::LogManager::getSingleton().logMessage("CellGrid: Applied UV mapping for rect '" +
district.textureRectName + "'");
}
}
}
// Generate mesh name
std::string meshName = "Plaza_" + std::to_string(entity.id()) + "_" + std::to_string(m_meshCount++);
// Create mesh and entity
try {
Ogre::MeshPtr mesh = tb.transformToMesh(meshName);
if (mesh.isNull()) {
Ogre::LogManager::getSingleton().logMessage("CellGrid: transformToMesh returned null");
return;
}
Ogre::LogManager::getSingleton().logMessage("CellGrid: Created mesh '" + meshName +
"' with " + std::to_string(mesh->getNumSubMeshes()) + " submeshes");
if (!materialName.empty() && mesh->getNumSubMeshes() > 0) {
mesh->getSubMesh(0)->setMaterialName(materialName);
Ogre::LogManager::getSingleton().logMessage("CellGrid: Set material to '" + materialName + "'");
}
Ogre::Entity* plazzaEntity = m_sceneMgr->createEntity(meshName);
if (!plazzaEntity) {
Ogre::LogManager::getSingleton().logMessage("CellGrid: createEntity returned null");
return;
}
transform.node->attachObject(plazzaEntity);
// Force visibility
plazzaEntity->setVisible(true);
// Store for cleanup
PlazaData data;
data.meshName = meshName;
data.entity = plazzaEntity;
m_plazaMeshes[entity.id()] = data;
Ogre::LogManager::getSingleton().logMessage("CellGrid: Created plaza mesh for district " + std::to_string(entity.id()) +
" attached to node '" + transform.node->getName() + "'");
} catch (const Ogre::Exception& e) {
Ogre::LogManager::getSingleton().logMessage("CellGrid: Failed to create plaza mesh: " + e.getDescription());
}
}

View File

@@ -0,0 +1,84 @@
#pragma once
#include <flecs.h>
#include <memory>
#include <Ogre.h>
namespace Procedural {
class TriangleBuffer;
}
class CellGridSystem {
public:
CellGridSystem(flecs::world& world, Ogre::SceneManager* sceneMgr);
~CellGridSystem();
void initialize();
void update();
// Force rebuild of a specific cell grid
void rebuildCellGrid(flecs::entity entity);
// Create/Update town material
void updateTownMaterial(flecs::entity townEntity);
private:
flecs::world& m_world;
Ogre::SceneManager* m_sceneMgr;
flecs::query<struct CellGridComponent> m_cellGridQuery;
flecs::query<struct TownComponent> m_townQuery;
flecs::query<struct DistrictComponent> m_districtQuery;
bool m_initialized = false;
int m_meshCount = 0;
// Build geometry from cell grid
void buildCellGrid(flecs::entity entity, struct CellGridComponent& grid);
// Create triangle buffers for different parts
void buildFloorsAndCeilings(const struct CellGridComponent& grid, Procedural::TriangleBuffer& floorTb, Procedural::TriangleBuffer& ceilingTb);
void buildWalls(const struct CellGridComponent& grid,
Procedural::TriangleBuffer& extWallTb,
Procedural::TriangleBuffer& intWallTb,
Procedural::TriangleBuffer& intWindowsTb);
void buildDoors(const struct CellGridComponent& grid, std::vector<Procedural::TriangleBuffer>& doorTbs);
void buildWindows(const struct CellGridComponent& grid, std::vector<Procedural::TriangleBuffer>& windowTbs);
void buildCorners(const struct CellGridComponent& grid, Procedural::TriangleBuffer& extWallTb);
// Build roofs
void buildRoofs(flecs::entity lotEntity, const struct CellGridComponent& grid, Procedural::TriangleBuffer& roofTb);
// Build plaza for district
void buildDistrictPlaza(flecs::entity entity, struct DistrictComponent& district);
// Convert triangle buffer to mesh
Ogre::MeshPtr convertToMesh(const std::string& name, Procedural::TriangleBuffer& tb, const std::string& materialName);
// Apply UV mapping from color rects
void applyUVMapping(Procedural::TriangleBuffer& tb, const struct TownComponent& town, const std::string& rectName);
// Destroy existing mesh
void destroyCellGridMeshes(struct CellGridComponent& grid);
// Mesh storage per entity
struct MeshData {
std::string floorMesh;
std::string ceilingMesh;
std::string extWallMesh;
std::string intWallMesh;
std::string intWindowsMesh;
std::vector<std::string> doorMeshes;
std::vector<std::string> windowMeshes;
std::string roofMesh;
std::vector<Ogre::Entity*> entities;
};
std::unordered_map<uint64_t, MeshData> m_entityMeshes;
// Plaza mesh storage per district entity
struct PlazaData {
std::string meshName;
Ogre::Entity* entity = nullptr;
};
std::unordered_map<uint64_t, PlazaData> m_plazaMeshes;
};

View File

@@ -15,6 +15,7 @@
#include "../components/Primitive.hpp"
#include "../components/TriangleBuffer.hpp"
#include "../components/LodSettings.hpp"
#include "../components/CellGrid.hpp"
#include "../ui/TransformEditor.hpp"
#include "../ui/RenderableEditor.hpp"
#include "../ui/PhysicsColliderEditor.hpp"
@@ -594,6 +595,55 @@ void EditorUISystem::renderComponentList(flecs::entity entity)
m_componentRegistry.render<TriangleBufferComponent>(entity, tb);
componentCount++;
}
// Render CellGrid if present
if (entity.has<CellGridComponent>()) {
auto &grid = entity.get_mut<CellGridComponent>();
m_componentRegistry.render<CellGridComponent>(entity, grid);
componentCount++;
}
// Render Lot if present
if (entity.has<LotComponent>()) {
auto &lot = entity.get_mut<LotComponent>();
m_componentRegistry.render<LotComponent>(entity, lot);
componentCount++;
}
// Render District if present
if (entity.has<DistrictComponent>()) {
auto &district = entity.get_mut<DistrictComponent>();
m_componentRegistry.render<DistrictComponent>(entity, district);
componentCount++;
}
// Render Town if present
if (entity.has<TownComponent>()) {
auto &town = entity.get_mut<TownComponent>();
m_componentRegistry.render<TownComponent>(entity, town);
componentCount++;
}
// Render Roof if present
if (entity.has<RoofComponent>()) {
auto &roof = entity.get_mut<RoofComponent>();
m_componentRegistry.render<RoofComponent>(entity, roof);
componentCount++;
}
// Render Room if present
if (entity.has<RoomComponent>()) {
auto &room = entity.get_mut<RoomComponent>();
m_componentRegistry.render<RoomComponent>(entity, room);
componentCount++;
}
// Render FurnitureTemplate if present
if (entity.has<FurnitureTemplateComponent>()) {
auto &furniture = entity.get_mut<FurnitureTemplateComponent>();
m_componentRegistry.render<FurnitureTemplateComponent>(entity, furniture);
componentCount++;
}
// Show message if no components
if (componentCount == 0) {

View File

@@ -15,6 +15,7 @@
#include "../components/ProceduralMaterial.hpp"
#include "../components/Primitive.hpp"
#include "../components/TriangleBuffer.hpp"
#include "../components/CellGrid.hpp"
#include "EditorUISystem.hpp"
#include <random>
#include <fstream>
@@ -175,6 +176,29 @@ nlohmann::json SceneSerializer::serializeEntity(flecs::entity entity)
if (entity.has<TriangleBufferComponent>()) {
json["triangleBuffer"] = serializeTriangleBuffer(entity);
}
// CellGrid/Town components
if (entity.has<CellGridComponent>()) {
json["cellGrid"] = serializeCellGrid(entity);
}
if (entity.has<TownComponent>()) {
json["town"] = serializeTown(entity);
}
if (entity.has<DistrictComponent>()) {
json["district"] = serializeDistrict(entity);
}
if (entity.has<LotComponent>()) {
json["lot"] = serializeLot(entity);
}
if (entity.has<RoomComponent>()) {
json["room"] = serializeRoom(entity);
}
if (entity.has<RoofComponent>()) {
json["roof"] = serializeRoof(entity);
}
if (entity.has<FurnitureTemplateComponent>()) {
json["furnitureTemplate"] = serializeFurnitureTemplate(entity);
}
// Serialize children
json["children"] = nlohmann::json::array();
@@ -268,6 +292,29 @@ void SceneSerializer::deserializeEntity(const nlohmann::json& json, flecs::entit
if (json.contains("triangleBuffer")) {
deserializeTriangleBuffer(entity, json["triangleBuffer"]);
}
// CellGrid/Town components
if (json.contains("cellGrid")) {
deserializeCellGrid(entity, json["cellGrid"]);
}
if (json.contains("town")) {
deserializeTown(entity, json["town"]);
}
if (json.contains("district")) {
deserializeDistrict(entity, json["district"]);
}
if (json.contains("lot")) {
deserializeLot(entity, json["lot"]);
}
if (json.contains("room")) {
deserializeRoom(entity, json["room"]);
}
if (json.contains("roof")) {
deserializeRoof(entity, json["roof"]);
}
if (json.contains("furnitureTemplate")) {
deserializeFurnitureTemplate(entity, json["furnitureTemplate"]);
}
// Add to UI system if provided
if (uiSystem) {
@@ -1204,3 +1251,383 @@ void SceneSerializer::deserializeTriangleBuffer(flecs::entity entity, const nloh
entity.set<TriangleBufferComponent>(tb);
}
// ============================================================================
// CellGrid/Town Component Serialization
// ============================================================================
nlohmann::json SceneSerializer::serializeCellGrid(flecs::entity entity)
{
auto& grid = entity.get<CellGridComponent>();
nlohmann::json json;
json["width"] = grid.width;
json["height"] = grid.height;
json["depth"] = grid.depth;
json["cellSize"] = grid.cellSize;
json["cellHeight"] = grid.cellHeight;
json["generationScript"] = grid.generationScript;
// Serialize cells
nlohmann::json cellsJson = nlohmann::json::array();
for (const auto& cell : grid.cells) {
cellsJson.push_back({
{"x", cell.x},
{"y", cell.y},
{"z", cell.z},
{"flags", cell.flags}
});
}
json["cells"] = cellsJson;
// Serialize furniture cells
nlohmann::json furnitureJson = nlohmann::json::array();
for (const auto& furn : grid.furnitureCells) {
nlohmann::json furnJson;
furnJson["x"] = furn.x;
furnJson["y"] = furn.y;
furnJson["z"] = furn.z;
furnJson["furnitureType"] = furn.furnitureType;
furnJson["rotation"] = furn.rotation;
furnJson["tags"] = furn.tags;
furnitureJson.push_back(furnJson);
}
json["furnitureCells"] = furnitureJson;
return json;
}
nlohmann::json SceneSerializer::serializeTown(flecs::entity entity)
{
auto& town = entity.get<TownComponent>();
nlohmann::json json;
json["townName"] = town.townName;
json["materialName"] = town.materialName;
// Serialize color rects
nlohmann::json colorRectsJson = nlohmann::json::object();
for (const auto& pair : town.colorRects) {
colorRectsJson[pair.first] = {
{"left", pair.second.left},
{"top", pair.second.top},
{"right", pair.second.right},
{"bottom", pair.second.bottom},
{"color", {
{"r", pair.second.color.r},
{"g", pair.second.color.g},
{"b", pair.second.color.b},
{"a", pair.second.color.a}
}}
};
}
json["colorRects"] = colorRectsJson;
return json;
}
nlohmann::json SceneSerializer::serializeDistrict(flecs::entity entity)
{
auto& district = entity.get<DistrictComponent>();
nlohmann::json json;
json["radius"] = district.radius;
json["elevation"] = district.elevation;
json["height"] = district.height;
json["isPlaza"] = district.isPlaza;
json["lotTemplateNames"] = district.lotTemplateNames;
json["proceduralMaterialEntityId"] = district.proceduralMaterialEntityId;
json["textureRectName"] = district.textureRectName;
return json;
}
nlohmann::json SceneSerializer::serializeLot(flecs::entity entity)
{
auto& lot = entity.get<LotComponent>();
nlohmann::json json;
json["width"] = lot.width;
json["depth"] = lot.depth;
json["elevation"] = lot.elevation;
json["angle"] = lot.angle;
json["offsetX"] = lot.offsetX;
json["offsetZ"] = lot.offsetZ;
json["templateName"] = lot.templateName;
return json;
}
nlohmann::json SceneSerializer::serializeRoom(flecs::entity entity)
{
auto& room = entity.get<RoomComponent>();
nlohmann::json json;
json["minX"] = room.minX;
json["minY"] = room.minY;
json["minZ"] = room.minZ;
json["maxX"] = room.maxX;
json["maxY"] = room.maxY;
json["maxZ"] = room.maxZ;
json["roomType"] = room.roomType;
json["tags"] = room.tags;
json["connectedRooms"] = room.connectedRooms;
json["exits"] = room.exits;
return json;
}
nlohmann::json SceneSerializer::serializeRoof(flecs::entity entity)
{
auto& roof = entity.get<RoofComponent>();
nlohmann::json json;
json["type"] = static_cast<int>(roof.type);
json["posX"] = roof.posX;
json["posY"] = roof.posY;
json["posZ"] = roof.posZ;
json["sizeX"] = roof.sizeX;
json["sizeZ"] = roof.sizeZ;
json["offsetX"] = roof.offsetX;
json["offsetY"] = roof.offsetY;
json["offsetZ"] = roof.offsetZ;
json["baseHeight"] = roof.baseHeight;
json["maxHeight"] = roof.maxHeight;
return json;
}
nlohmann::json SceneSerializer::serializeFurnitureTemplate(flecs::entity entity)
{
auto& furn = entity.get<FurnitureTemplateComponent>();
nlohmann::json json;
json["templateName"] = furn.templateName;
json["tags"] = furn.tags;
json["meshName"] = furn.meshName;
json["materialName"] = furn.materialName;
json["requiresWall"] = furn.requiresWall;
json["requiresFloor"] = furn.requiresFloor;
json["blocksPath"] = furn.blocksPath;
json["offsetX"] = furn.offsetX;
json["offsetY"] = furn.offsetY;
json["offsetZ"] = furn.offsetZ;
json["weight"] = furn.weight;
return json;
}
void SceneSerializer::deserializeCellGrid(flecs::entity entity, const nlohmann::json& json)
{
CellGridComponent grid;
grid.width = json.value("width", 10);
grid.height = json.value("height", 1);
grid.depth = json.value("depth", 10);
grid.cellSize = json.value("cellSize", 4.0f);
grid.cellHeight = json.value("cellHeight", 4.0f);
grid.generationScript = json.value("generationScript", "");
// Deserialize cells
if (json.contains("cells") && json["cells"].is_array()) {
for (const auto& cellJson : json["cells"]) {
Cell cell;
cell.x = cellJson.value("x", 0);
cell.y = cellJson.value("y", 0);
cell.z = cellJson.value("z", 0);
cell.flags = cellJson.value("flags", 0ULL);
grid.cells.push_back(cell);
}
}
// Deserialize furniture cells
if (json.contains("furnitureCells") && json["furnitureCells"].is_array()) {
for (const auto& furnJson : json["furnitureCells"]) {
FurnitureCell furn;
furn.x = furnJson.value("x", 0);
furn.y = furnJson.value("y", 0);
furn.z = furnJson.value("z", 0);
furn.furnitureType = furnJson.value("furnitureType", "");
furn.rotation = furnJson.value("rotation", 0);
if (furnJson.contains("tags") && furnJson["tags"].is_array()) {
for (const auto& tag : furnJson["tags"]) {
if (tag.is_string()) {
furn.tags.push_back(tag);
}
}
}
grid.furnitureCells.push_back(furn);
}
}
grid.dirty = true;
entity.set<CellGridComponent>(grid);
}
void SceneSerializer::deserializeTown(flecs::entity entity, const nlohmann::json& json)
{
TownComponent town;
town.townName = json.value("townName", "New Town");
town.materialName = json.value("materialName", "");
// Deserialize color rects
if (json.contains("colorRects") && json["colorRects"].is_object()) {
for (auto& [name, rectJson] : json["colorRects"].items()) {
TownComponent::ColorRect rect;
rect.left = rectJson.value("left", 0.0f);
rect.top = rectJson.value("top", 0.0f);
rect.right = rectJson.value("right", 1.0f);
rect.bottom = rectJson.value("bottom", 1.0f);
if (rectJson.contains("color")) {
auto& c = rectJson["color"];
rect.color.r = c.value("r", 1.0f);
rect.color.g = c.value("g", 1.0f);
rect.color.b = c.value("b", 1.0f);
rect.color.a = c.value("a", 1.0f);
}
town.colorRects[name] = rect;
}
}
town.dirty = true;
town.materialDirty = true;
entity.set<TownComponent>(town);
}
void SceneSerializer::deserializeDistrict(flecs::entity entity, const nlohmann::json& json)
{
DistrictComponent district;
district.radius = json.value("radius", 50.0f);
district.elevation = json.value("elevation", 0.0f);
district.height = json.value("height", 0.2f);
district.isPlaza = json.value("isPlaza", false);
district.proceduralMaterialEntityId = json.value("proceduralMaterialEntityId", "");
district.textureRectName = json.value("textureRectName", "");
// Resolve material entity reference
if (!district.proceduralMaterialEntityId.empty()) {
try {
uint64_t matId = std::stoull(district.proceduralMaterialEntityId);
flecs::entity matEntity(m_world, matId);
if (matEntity.is_alive() && matEntity.has<ProceduralMaterialComponent>()) {
district.proceduralMaterialEntity = matEntity;
}
} catch (...) {
// Invalid ID format, ignore
}
}
if (json.contains("lotTemplateNames") && json["lotTemplateNames"].is_array()) {
for (const auto& name : json["lotTemplateNames"]) {
if (name.is_string()) {
district.lotTemplateNames.push_back(name);
}
}
}
district.dirty = true;
entity.set<DistrictComponent>(district);
}
void SceneSerializer::deserializeLot(flecs::entity entity, const nlohmann::json& json)
{
LotComponent lot;
lot.width = json.value("width", 10);
lot.depth = json.value("depth", 10);
lot.elevation = json.value("elevation", 0.0f);
lot.angle = json.value("angle", 0.0f);
lot.offsetX = json.value("offsetX", 0.0f);
lot.offsetZ = json.value("offsetZ", 0.0f);
lot.templateName = json.value("templateName", "");
lot.dirty = true;
entity.set<LotComponent>(lot);
}
void SceneSerializer::deserializeRoom(flecs::entity entity, const nlohmann::json& json)
{
RoomComponent room;
room.minX = json.value("minX", 0);
room.minY = json.value("minY", 0);
room.minZ = json.value("minZ", 0);
room.maxX = json.value("maxX", 0);
room.maxY = json.value("maxY", 0);
room.maxZ = json.value("maxZ", 0);
room.roomType = json.value("roomType", "");
if (json.contains("tags") && json["tags"].is_array()) {
for (const auto& tag : json["tags"]) {
if (tag.is_string()) {
room.tags.push_back(tag);
}
}
}
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);
}
}
}
entity.set<RoomComponent>(room);
}
void SceneSerializer::deserializeRoof(flecs::entity entity, const nlohmann::json& json)
{
RoofComponent roof;
roof.type = static_cast<RoofComponent::Type>(json.value("type", 0));
roof.posX = json.value("posX", 0);
roof.posY = json.value("posY", 0);
roof.posZ = json.value("posZ", 0);
roof.sizeX = json.value("sizeX", 1);
roof.sizeZ = json.value("sizeZ", 1);
roof.offsetX = json.value("offsetX", 0.0f);
roof.offsetY = json.value("offsetY", 0.0f);
roof.offsetZ = json.value("offsetZ", 0.0f);
roof.baseHeight = json.value("baseHeight", 0.5f);
roof.maxHeight = json.value("maxHeight", 0.5f);
entity.set<RoofComponent>(roof);
}
void SceneSerializer::deserializeFurnitureTemplate(flecs::entity entity, const nlohmann::json& json)
{
FurnitureTemplateComponent furn;
furn.templateName = json.value("templateName", "");
furn.meshName = json.value("meshName", "");
furn.materialName = json.value("materialName", "");
furn.requiresWall = json.value("requiresWall", false);
furn.requiresFloor = json.value("requiresFloor", true);
furn.blocksPath = json.value("blocksPath", true);
furn.offsetX = json.value("offsetX", 0.0f);
furn.offsetY = json.value("offsetY", 0.0f);
furn.offsetZ = json.value("offsetZ", 0.0f);
furn.weight = json.value("weight", 1.0f);
if (json.contains("tags") && json["tags"].is_array()) {
for (const auto& tag : json["tags"]) {
if (tag.is_string()) {
furn.tags.push_back(tag);
}
}
}
entity.set<FurnitureTemplateComponent>(furn);
}

View File

@@ -55,6 +55,15 @@ private:
nlohmann::json serializePrimitive(flecs::entity entity);
nlohmann::json serializeTriangleBuffer(flecs::entity entity);
// CellGrid/Town component serialization
nlohmann::json serializeCellGrid(flecs::entity entity);
nlohmann::json serializeTown(flecs::entity entity);
nlohmann::json serializeDistrict(flecs::entity entity);
nlohmann::json serializeLot(flecs::entity entity);
nlohmann::json serializeRoom(flecs::entity entity);
nlohmann::json serializeRoof(flecs::entity entity);
nlohmann::json serializeFurnitureTemplate(flecs::entity entity);
// Component deserialization
void deserializeTransform(flecs::entity entity, const nlohmann::json& json, flecs::entity parentEntity);
void deserializeRenderable(flecs::entity entity, const nlohmann::json& json);
@@ -71,6 +80,15 @@ private:
void deserializeProceduralMaterial(flecs::entity entity, const nlohmann::json& json);
void deserializePrimitive(flecs::entity entity, const nlohmann::json& json);
void deserializeTriangleBuffer(flecs::entity entity, const nlohmann::json& json);
// CellGrid/Town component deserialization
void deserializeCellGrid(flecs::entity entity, const nlohmann::json& json);
void deserializeTown(flecs::entity entity, const nlohmann::json& json);
void deserializeDistrict(flecs::entity entity, const nlohmann::json& json);
void deserializeLot(flecs::entity entity, const nlohmann::json& json);
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);
flecs::world& m_world;
Ogre::SceneManager* m_sceneMgr;

View File

@@ -0,0 +1,225 @@
#include "CellGridEditor.hpp"
#include "../components/CellGrid.hpp"
#include <OgreStringConverter.h>
bool CellGridEditor::renderComponent(flecs::entity entity, CellGridComponent& grid)
{
bool modified = false;
if (ImGui::CollapsingHeader("Cell Grid", ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Indent();
// Grid dimensions
int dims[3] = { grid.width, grid.height, grid.depth };
if (ImGui::InputInt3("Grid Size (W/H/D)", dims)) {
grid.width = std::max(1, dims[0]);
grid.height = std::max(1, dims[1]);
grid.depth = std::max(1, dims[2]);
modified = true;
}
// Cell size
if (ImGui::DragFloat("Cell Size", &grid.cellSize, 0.1f, 0.5f, 20.0f)) {
modified = true;
}
if (ImGui::DragFloat("Cell Height", &grid.cellHeight, 0.1f, 0.5f, 20.0f)) {
modified = true;
}
ImGui::Separator();
// Statistics
ImGui::Text("Cells: %zu", grid.cells.size());
ImGui::Text("Furniture: %zu", grid.furnitureCells.size());
// Cell editor
if (ImGui::CollapsingHeader("Cells")) {
renderCellEditor(grid);
}
// Furniture editor
if (ImGui::CollapsingHeader("Furniture")) {
renderFurnitureEditor(grid);
}
// Script editor
if (ImGui::CollapsingHeader("Generation Script")) {
renderScriptEditor(grid);
}
ImGui::Separator();
// Actions
if (ImGui::Button("Clear All Cells")) {
grid.clear();
modified = true;
}
ImGui::SameLine();
if (ImGui::Button("Rebuild Geometry")) {
grid.markDirty();
modified = true;
}
if (modified) {
grid.markDirty();
}
ImGui::Unindent();
}
return modified;
}
void CellGridEditor::renderCellEditor(CellGridComponent& grid)
{
// New cell input
ImGui::InputInt3("New Cell Pos", &newCellX);
if (ImGui::Button("Add/Edit Cell")) {
auto& cell = grid.getOrCreateCell(newCellX, newCellY, newCellZ);
selectedCellX = newCellX;
selectedCellY = newCellY;
selectedCellZ = newCellZ;
}
ImGui::Separator();
// Selected cell editor
Cell* cell = grid.findCell(selectedCellX, selectedCellY, selectedCellZ);
if (cell) {
ImGui::Text("Editing Cell: %d, %d, %d", selectedCellX, selectedCellY, selectedCellZ);
auto flags = CellGridComponent::getAllFlags();
for (const auto& [flag, name] : flags) {
bool hasFlag = cell->hasFlag(flag);
if (ImGui::Checkbox(name, &hasFlag)) {
if (hasFlag) {
cell->setFlag(flag);
} else {
cell->clearFlag(flag);
}
grid.markDirty();
}
}
if (ImGui::Button("Delete Cell")) {
grid.removeCell(selectedCellX, selectedCellY, selectedCellZ);
grid.markDirty();
}
} else {
ImGui::Text("No cell selected");
}
ImGui::Separator();
// List existing cells
if (ImGui::BeginListBox("Cells", ImVec2(-FLT_MIN, 150))) {
for (const auto& cell : grid.cells) {
std::string label = std::to_string(cell.x) + ", " +
std::to_string(cell.y) + ", " +
std::to_string(cell.z) + " (flags: " +
std::to_string(cell.flags) + ")";
bool isSelected = (cell.x == selectedCellX &&
cell.y == selectedCellY &&
cell.z == selectedCellZ);
if (ImGui::Selectable(label.c_str(), isSelected)) {
selectedCellX = cell.x;
selectedCellY = cell.y;
selectedCellZ = cell.z;
}
}
ImGui::EndListBox();
}
}
void CellGridEditor::renderFurnitureEditor(CellGridComponent& grid)
{
static int fx = 0, fy = 0, fz = 0;
static char tagsBuffer[256] = {0};
static char furnitureType[128] = {0};
static int rotation = 0;
ImGui::InputInt3("Furniture Pos", &fx);
ImGui::InputText("Tags (comma separated)", tagsBuffer, sizeof(tagsBuffer));
ImGui::InputText("Furniture Type", furnitureType, sizeof(furnitureType));
ImGui::SliderInt("Rotation", &rotation, 0, 3);
if (ImGui::Button("Add/Update Furniture")) {
auto& fcell = grid.getOrCreateFurnitureCell(fx, fy, fz);
fcell.furnitureType = furnitureType;
fcell.rotation = rotation;
fcell.tags.clear();
// Parse tags
std::string tagsStr(tagsBuffer);
size_t start = 0;
size_t end = tagsStr.find(',');
while (end != std::string::npos) {
std::string tag = tagsStr.substr(start, end - start);
// Trim whitespace
tag.erase(0, tag.find_first_not_of(" \t"));
tag.erase(tag.find_last_not_of(" \t") + 1);
if (!tag.empty()) {
fcell.tags.push_back(tag);
}
start = end + 1;
end = tagsStr.find(',', start);
}
// Last tag
std::string lastTag = tagsStr.substr(start);
lastTag.erase(0, lastTag.find_first_not_of(" \t"));
lastTag.erase(lastTag.find_last_not_of(" \t") + 1);
if (!lastTag.empty()) {
fcell.tags.push_back(lastTag);
}
grid.markDirty();
}
ImGui::Separator();
// List furniture cells
if (ImGui::BeginListBox("Furniture Cells", ImVec2(-FLT_MIN, 100))) {
for (const auto& fcell : grid.furnitureCells) {
std::string label = std::to_string(fcell.x) + ", " +
std::to_string(fcell.y) + ", " +
std::to_string(fcell.z) + ": " + fcell.furnitureType;
if (ImGui::Selectable(label.c_str())) {
fx = fcell.x;
fy = fcell.y;
fz = fcell.z;
rotation = fcell.rotation;
strncpy(furnitureType, fcell.furnitureType.c_str(), sizeof(furnitureType) - 1);
// Build tags string
std::string tags;
for (size_t i = 0; i < fcell.tags.size(); ++i) {
if (i > 0) tags += ", ";
tags += fcell.tags[i];
}
strncpy(tagsBuffer, tags.c_str(), sizeof(tagsBuffer) - 1);
}
}
ImGui::EndListBox();
}
}
void CellGridEditor::renderScriptEditor(CellGridComponent& grid)
{
// Copy script to buffer if needed
if (scriptBuffer[0] == '\0' && !grid.generationScript.empty()) {
strncpy(scriptBuffer, grid.generationScript.c_str(), sizeof(scriptBuffer) - 1);
}
ImGui::InputTextMultiline("Script", scriptBuffer, sizeof(scriptBuffer),
ImVec2(-FLT_MIN, ImGui::GetTextLineHeight() * 20),
ImGuiInputTextFlags_AllowTabInput);
if (ImGui::IsItemDeactivatedAfterEdit()) {
grid.generationScript = scriptBuffer;
grid.markDirty();
}
if (ImGui::Button("Execute Script")) {
grid.generationScript = scriptBuffer;
// TODO: Execute Lua script
grid.markDirty();
}
}

View File

@@ -0,0 +1,22 @@
#pragma once
#include "ComponentEditor.hpp"
struct CellGridComponent;
class CellGridEditor : public ComponentEditor<CellGridComponent> {
public:
bool renderComponent(flecs::entity entity, CellGridComponent& grid) override;
const char* getName() const override { return "Cell Grid"; }
private:
void renderCellEditor(CellGridComponent& grid);
void renderFurnitureEditor(CellGridComponent& grid);
void renderScriptEditor(CellGridComponent& grid);
// State for UI
int selectedCellX = 0, selectedCellY = 0, selectedCellZ = 0;
int newCellX = 0, newCellY = 0, newCellZ = 0;
char scriptBuffer[16384] = {0};
bool showGrid = false;
};

View File

@@ -0,0 +1,153 @@
#include "DistrictEditor.hpp"
#include "../components/CellGrid.hpp"
#include "../components/ProceduralTexture.hpp"
#include "../components/ProceduralMaterial.hpp"
#include <imgui.h>
bool DistrictEditor::renderComponent(flecs::entity entity, DistrictComponent& district)
{
bool modified = false;
if (ImGui::CollapsingHeader("District", ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Indent();
if (ImGui::DragFloat("Radius", &district.radius, 1.0f, 10.0f, 500.0f)) {
modified = true;
}
if (ImGui::DragFloat("Elevation", &district.elevation, 0.1f, -10.0f, 10.0f)) {
modified = true;
}
if (ImGui::DragFloat("Height", &district.height, 0.05f, 0.0f, 10.0f)) {
modified = true;
}
if (ImGui::Checkbox("Is Plaza", &district.isPlaza)) {
modified = true;
Ogre::LogManager::getSingleton().logMessage(
"DistrictEditor: Is Plaza changed to " + std::string(district.isPlaza ? "true" : "false") +
" for entity " + std::to_string(entity.id()));
}
// Plaza material and texture settings
if (district.isPlaza) {
ImGui::Separator();
ImGui::Text("Plaza Material:");
// Material selector
flecs::world world = entity.world();
std::vector<flecs::entity> materialEntities;
std::vector<std::string> materialNames;
int currentMatIndex = -1;
int noneMatIndex = 0;
materialEntities.push_back(flecs::entity::null());
materialNames.push_back("None");
world.query<ProceduralMaterialComponent>().each([&](flecs::entity e, ProceduralMaterialComponent& mat) {
materialEntities.push_back(e);
std::string name = mat.materialName.empty() ? "Material " + std::to_string(e.id()) : mat.materialName;
materialNames.push_back(name);
if (district.proceduralMaterialEntity == e) {
currentMatIndex = (int)materialEntities.size() - 1;
}
});
if (currentMatIndex == -1) currentMatIndex = noneMatIndex;
std::string matComboItems;
for (size_t i = 0; i < materialNames.size(); ++i) {
if (i > 0) matComboItems += '\0';
matComboItems += materialNames[i];
}
matComboItems += '\0';
int newMatIndex = currentMatIndex;
if (ImGui::Combo("Material", &newMatIndex, matComboItems.c_str())) {
if (newMatIndex == noneMatIndex) {
district.proceduralMaterialEntity = flecs::entity::null();
district.proceduralMaterialEntityId.clear();
} else {
district.proceduralMaterialEntity = materialEntities[newMatIndex];
district.proceduralMaterialEntityId = std::to_string(materialEntities[newMatIndex].id());
}
modified = true;
}
// Texture rectangle selector
if (district.proceduralMaterialEntity.is_alive()) {
// Find the texture entity from the material
flecs::entity textureEntity = flecs::entity::null();
if (district.proceduralMaterialEntity.has<ProceduralMaterialComponent>()) {
const auto& mat = district.proceduralMaterialEntity.get<ProceduralMaterialComponent>();
// Use the material's diffuse texture entity directly
if (mat.diffuseTextureEntity.is_alive()) {
textureEntity = mat.diffuseTextureEntity;
}
}
if (textureEntity.is_alive() && textureEntity.has<ProceduralTextureComponent>()) {
const auto& texture = textureEntity.get<ProceduralTextureComponent>();
const auto& namedRects = texture.getAllNamedRects();
if (!namedRects.empty()) {
std::vector<std::string> rectNames;
int currentRectIndex = -1;
int noneRectIndex = 0;
rectNames.push_back("None (use full texture)");
for (const auto& pair : namedRects) {
rectNames.push_back(pair.first);
if (district.textureRectName == pair.first) {
currentRectIndex = (int)rectNames.size() - 1;
}
}
if (currentRectIndex == -1) currentRectIndex = noneRectIndex;
std::string rectComboItems;
for (size_t i = 0; i < rectNames.size(); ++i) {
if (i > 0) rectComboItems += '\0';
rectComboItems += rectNames[i];
}
rectComboItems += '\0';
int newRectIndex = currentRectIndex;
if (ImGui::Combo("Texture Rectangle", &newRectIndex, rectComboItems.c_str())) {
if (newRectIndex == noneRectIndex) {
district.textureRectName.clear();
} else {
district.textureRectName = rectNames[newRectIndex];
}
modified = true;
}
} else {
ImGui::TextDisabled("No named rectangles in texture");
}
} else {
ImGui::TextDisabled("Material has no associated ProceduralTexture");
}
}
}
// Count lots
int lotCount = 0;
entity.children([&](flecs::entity child) {
if (child.has<LotComponent>()) {
lotCount++;
}
});
ImGui::Text("Lots: %d", lotCount);
if (modified) {
district.markDirty();
Ogre::LogManager::getSingleton().logMessage(
"DistrictEditor: markDirty() called for entity " + std::to_string(entity.id()));
}
ImGui::Unindent();
}
return modified;
}

View File

@@ -0,0 +1,11 @@
#pragma once
#include "ComponentEditor.hpp"
struct DistrictComponent;
class DistrictEditor : public ComponentEditor<DistrictComponent> {
public:
bool renderComponent(flecs::entity entity, DistrictComponent& district) override;
const char* getName() const override { return "District"; }
};

View File

@@ -0,0 +1,81 @@
#include "FurnitureTemplateEditor.hpp"
#include "../components/CellGrid.hpp"
#include <imgui.h>
bool FurnitureTemplateEditor::renderComponent(flecs::entity entity, FurnitureTemplateComponent& furniture)
{
bool modified = false;
if (ImGui::CollapsingHeader("Furniture Template", ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Indent();
char nameBuf[128] = {0};
strncpy(nameBuf, furniture.templateName.c_str(), sizeof(nameBuf) - 1);
if (ImGui::InputText("Template Name", nameBuf, sizeof(nameBuf))) {
furniture.templateName = nameBuf;
modified = true;
}
char meshBuf[128] = {0};
strncpy(meshBuf, furniture.meshName.c_str(), sizeof(meshBuf) - 1);
if (ImGui::InputText("Mesh", meshBuf, sizeof(meshBuf))) {
furniture.meshName = meshBuf;
modified = true;
}
char matBuf[128] = {0};
strncpy(matBuf, furniture.materialName.c_str(), sizeof(matBuf) - 1);
if (ImGui::InputText("Material", matBuf, sizeof(matBuf))) {
furniture.materialName = matBuf;
modified = true;
}
// Tags
std::string tagsStr;
for (size_t i = 0; i < furniture.tags.size(); ++i) {
if (i > 0) tagsStr += ", ";
tagsStr += furniture.tags[i];
}
char tagsBuf[256] = {0};
strncpy(tagsBuf, tagsStr.c_str(), sizeof(tagsBuf) - 1);
if (ImGui::InputText("Tags", tagsBuf, sizeof(tagsBuf))) {
furniture.tags.clear();
std::string newTags(tagsBuf);
size_t start = 0;
size_t end = newTags.find(',');
while (end != std::string::npos) {
std::string tag = newTags.substr(start, end - start);
tag.erase(0, tag.find_first_not_of(" \t"));
tag.erase(tag.find_last_not_of(" \t") + 1);
if (!tag.empty()) furniture.tags.push_back(tag);
start = end + 1;
end = newTags.find(',', start);
}
std::string lastTag = newTags.substr(start);
lastTag.erase(0, lastTag.find_first_not_of(" \t"));
lastTag.erase(lastTag.find_last_not_of(" \t") + 1);
if (!lastTag.empty()) furniture.tags.push_back(lastTag);
modified = true;
}
ImGui::Checkbox("Requires Wall", &furniture.requiresWall);
ImGui::Checkbox("Requires Floor", &furniture.requiresFloor);
ImGui::Checkbox("Blocks Path", &furniture.blocksPath);
float offset[3] = { furniture.offsetX, furniture.offsetY, furniture.offsetZ };
if (ImGui::DragFloat3("Offset", offset, 0.01f)) {
furniture.offsetX = offset[0];
furniture.offsetY = offset[1];
furniture.offsetZ = offset[2];
modified = true;
}
if (ImGui::DragFloat("Weight", &furniture.weight, 0.1f, 0.0f, 100.0f)) {
modified = true;
}
ImGui::Unindent();
}
return modified;
}

View File

@@ -0,0 +1,11 @@
#pragma once
#include "ComponentEditor.hpp"
struct FurnitureTemplateComponent;
class FurnitureTemplateEditor : public ComponentEditor<FurnitureTemplateComponent> {
public:
bool renderComponent(flecs::entity entity, FurnitureTemplateComponent& furniture) override;
const char* getName() const override { return "Furniture Template"; }
};

View File

@@ -0,0 +1,72 @@
#include "LotEditor.hpp"
#include "../components/CellGrid.hpp"
#include <imgui.h>
bool LotEditor::renderComponent(flecs::entity entity, LotComponent& lot)
{
bool modified = false;
if (ImGui::CollapsingHeader("Lot", ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Indent();
// Dimensions
int dims[2] = { lot.width, lot.depth };
if (ImGui::InputInt2("Lot Size (W/D)", dims)) {
lot.width = std::max(4, dims[0]);
lot.depth = std::max(4, dims[1]);
modified = true;
}
// Position/rotation
if (ImGui::SliderFloat("Angle (degrees)", &lot.angle, -180.0f, 180.0f)) {
modified = true;
}
if (ImGui::DragFloat("Elevation", &lot.elevation, 0.1f, -10.0f, 10.0f)) {
modified = true;
}
if (ImGui::DragFloat("Offset X", &lot.offsetX, 0.1f)) {
modified = true;
}
if (ImGui::DragFloat("Offset Z", &lot.offsetZ, 0.1f)) {
modified = true;
}
// Template
char templateName[128] = {0};
strncpy(templateName, lot.templateName.c_str(), sizeof(templateName) - 1);
if (ImGui::InputText("Template", templateName, sizeof(templateName))) {
lot.templateName = templateName;
modified = true;
}
// Statistics
int cellCount = 0;
int roomCount = 0;
int roofCount = 0;
entity.children([&](flecs::entity child) {
if (child.has<CellGridComponent>()) {
cellCount = child.get<CellGridComponent>().cells.size();
}
if (child.has<RoomComponent>()) {
roomCount++;
}
if (child.has<RoofComponent>()) {
roofCount++;
}
});
ImGui::Separator();
ImGui::Text("Cells: %d", cellCount);
ImGui::Text("Rooms: %d", roomCount);
ImGui::Text("Roofs: %d", roofCount);
if (modified) {
lot.markDirty();
}
ImGui::Unindent();
}
return modified;
}

View File

@@ -0,0 +1,11 @@
#pragma once
#include "ComponentEditor.hpp"
struct LotComponent;
class LotEditor : public ComponentEditor<LotComponent> {
public:
bool renderComponent(flecs::entity entity, LotComponent& lot) override;
const char* getName() const override { return "Lot"; }
};

View File

@@ -0,0 +1,53 @@
#include "RoofEditor.hpp"
#include "../components/CellGrid.hpp"
#include <imgui.h>
bool RoofEditor::renderComponent(flecs::entity entity, RoofComponent& roof)
{
bool modified = false;
if (ImGui::CollapsingHeader("Roof", ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Indent();
const char* roofTypes[] = { "Flat", "Normal", "Normal2", "Cone", "Cylinder" };
int typeIndex = static_cast<int>(roof.type);
if (ImGui::Combo("Type", &typeIndex, roofTypes, IM_ARRAYSIZE(roofTypes))) {
roof.type = static_cast<RoofComponent::Type>(typeIndex);
modified = true;
}
int pos[3] = { roof.posX, roof.posY, roof.posZ };
if (ImGui::InputInt3("Position (cells)", pos)) {
roof.posX = pos[0];
roof.posY = pos[1];
roof.posZ = pos[2];
modified = true;
}
int size[2] = { roof.sizeX, roof.sizeZ };
if (ImGui::InputInt2("Size (cells)", size)) {
roof.sizeX = std::max(1, size[0]);
roof.sizeZ = std::max(1, size[1]);
modified = true;
}
float offset[3] = { roof.offsetX, roof.offsetY, roof.offsetZ };
if (ImGui::DragFloat3("Offset", offset, 0.1f)) {
roof.offsetX = offset[0];
roof.offsetY = offset[1];
roof.offsetZ = offset[2];
modified = true;
}
if (ImGui::DragFloat("Base Height", &roof.baseHeight, 0.1f, 0.1f, 100.0f)) {
modified = true;
}
if (ImGui::DragFloat("Max Height", &roof.maxHeight, 0.1f, roof.baseHeight, 100.0f)) {
modified = true;
}
ImGui::Unindent();
}
return modified;
}

View File

@@ -0,0 +1,11 @@
#pragma once
#include "ComponentEditor.hpp"
struct RoofComponent;
class RoofEditor : public ComponentEditor<RoofComponent> {
public:
bool renderComponent(flecs::entity entity, RoofComponent& roof) override;
const char* getName() const override { return "Roof"; }
};

View File

@@ -0,0 +1,73 @@
#include "RoomEditor.hpp"
#include "../components/CellGrid.hpp"
#include <imgui.h>
bool RoomEditor::renderComponent(flecs::entity entity, RoomComponent& room)
{
bool modified = false;
if (ImGui::CollapsingHeader("Room", ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Indent();
int minPos[3] = { room.minX, room.minY, room.minZ };
if (ImGui::InputInt3("Min (X/Y/Z)", minPos)) {
room.minX = minPos[0];
room.minY = minPos[1];
room.minZ = minPos[2];
modified = true;
}
int maxPos[3] = { room.maxX, room.maxY, room.maxZ };
if (ImGui::InputInt3("Max (X/Y/Z)", maxPos)) {
room.maxX = maxPos[0];
room.maxY = maxPos[1];
room.maxZ = maxPos[2];
modified = true;
}
char roomType[128] = {0};
strncpy(roomType, room.roomType.c_str(), sizeof(roomType) - 1);
if (ImGui::InputText("Room Type", roomType, sizeof(roomType))) {
room.roomType = roomType;
modified = true;
}
// Tags
std::string tagsStr;
for (size_t i = 0; i < room.tags.size(); ++i) {
if (i > 0) tagsStr += ", ";
tagsStr += room.tags[i];
}
char tagsBuf[256] = {0};
strncpy(tagsBuf, tagsStr.c_str(), sizeof(tagsBuf) - 1);
if (ImGui::InputText("Tags", tagsBuf, sizeof(tagsBuf))) {
room.tags.clear();
std::string newTags(tagsBuf);
size_t start = 0;
size_t end = newTags.find(',');
while (end != std::string::npos) {
std::string tag = newTags.substr(start, end - start);
tag.erase(0, tag.find_first_not_of(" \t"));
tag.erase(tag.find_last_not_of(" \t") + 1);
if (!tag.empty()) room.tags.push_back(tag);
start = end + 1;
end = newTags.find(',', start);
}
std::string lastTag = newTags.substr(start);
lastTag.erase(0, lastTag.find_first_not_of(" \t"));
lastTag.erase(lastTag.find_last_not_of(" \t") + 1);
if (!lastTag.empty()) room.tags.push_back(lastTag);
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();
}
return modified;
}

View File

@@ -0,0 +1,11 @@
#pragma once
#include "ComponentEditor.hpp"
struct RoomComponent;
class RoomEditor : public ComponentEditor<RoomComponent> {
public:
bool renderComponent(flecs::entity entity, RoomComponent& room) override;
const char* getName() const override { return "Room"; }
};

View File

@@ -0,0 +1,68 @@
#include "TownEditor.hpp"
#include "../components/CellGrid.hpp"
#include <imgui.h>
bool TownEditor::renderComponent(flecs::entity entity, TownComponent& town)
{
bool modified = false;
if (ImGui::CollapsingHeader("Town", ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Indent();
char nameBuf[128] = {0};
strncpy(nameBuf, town.townName.c_str(), sizeof(nameBuf) - 1);
if (ImGui::InputText("Town Name", nameBuf, sizeof(nameBuf))) {
town.townName = nameBuf;
modified = true;
}
// Count districts
int districtCount = 0;
entity.children([&](flecs::entity child) {
if (child.has<DistrictComponent>()) {
districtCount++;
}
});
ImGui::Text("Districts: %d", districtCount);
// Color rects
if (ImGui::CollapsingHeader("Color Rects")) {
for (auto& [name, rect] : town.colorRects) {
ImGui::PushID(name.c_str());
ImGui::Text("%s", name.c_str());
float l = rect.left, t = rect.top, r = rect.right, b = rect.bottom;
if (ImGui::SliderFloat("Left", &l, 0.0f, 1.0f)) { rect.left = l; modified = true; town.markMaterialDirty(); }
if (ImGui::SliderFloat("Top", &t, 0.0f, 1.0f)) { rect.top = t; modified = true; town.markMaterialDirty(); }
if (ImGui::SliderFloat("Right", &r, 0.0f, 1.0f)) { rect.right = r; modified = true; town.markMaterialDirty(); }
if (ImGui::SliderFloat("Bottom", &b, 0.0f, 1.0f)) { rect.bottom = b; modified = true; town.markMaterialDirty(); }
float color[4] = { rect.color.r, rect.color.g, rect.color.b, rect.color.a };
if (ImGui::ColorEdit4("Color", color)) {
rect.color.r = color[0];
rect.color.g = color[1];
rect.color.b = color[2];
rect.color.a = color[3];
modified = true;
town.markMaterialDirty();
}
ImGui::PopID();
}
// Add new rect
static char newName[64] = {0};
ImGui::InputText("New Rect Name", newName, sizeof(newName));
if (ImGui::Button("Add Color Rect") && strlen(newName) > 0) {
town.colorRects[newName] = TownComponent::ColorRect();
modified = true;
town.markMaterialDirty();
}
}
if (modified) {
town.markDirty();
}
ImGui::Unindent();
}
return modified;
}

View File

@@ -0,0 +1,11 @@
#pragma once
#include "ComponentEditor.hpp"
struct TownComponent;
class TownEditor : public ComponentEditor<TownComponent> {
public:
bool renderComponent(flecs::entity entity, TownComponent& town) override;
const char* getName() const override { return "Town"; }
};