Town plaza fixed
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
178
src/features/editScene/components/CellGrid.cpp
Normal file
178
src/features/editScene/components/CellGrid.cpp
Normal 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-" },
|
||||
};
|
||||
}
|
||||
321
src/features/editScene/components/CellGrid.hpp
Normal file
321
src/features/editScene/components/CellGrid.hpp
Normal 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;
|
||||
};
|
||||
142
src/features/editScene/components/CellGridEditorsModule.cpp
Normal file
142
src/features/editScene/components/CellGridEditorsModule.cpp
Normal 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>();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
46
src/features/editScene/components/CellGridModule.cpp
Normal file
46
src/features/editScene/components/CellGridModule.cpp
Normal 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
|
||||
7
src/features/editScene/components/CellGridModule.hpp
Normal file
7
src/features/editScene/components/CellGridModule.hpp
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <flecs.h>
|
||||
|
||||
namespace CellGridModule {
|
||||
void registerComponents(flecs::world& world);
|
||||
}
|
||||
682
src/features/editScene/systems/CellGridSystem.cpp
Normal file
682
src/features/editScene/systems/CellGridSystem.cpp
Normal 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());
|
||||
}
|
||||
}
|
||||
84
src/features/editScene/systems/CellGridSystem.hpp
Normal file
84
src/features/editScene/systems/CellGridSystem.hpp
Normal 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;
|
||||
};
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
225
src/features/editScene/ui/CellGridEditor.cpp
Normal file
225
src/features/editScene/ui/CellGridEditor.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
22
src/features/editScene/ui/CellGridEditor.hpp
Normal file
22
src/features/editScene/ui/CellGridEditor.hpp
Normal 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;
|
||||
};
|
||||
153
src/features/editScene/ui/DistrictEditor.cpp
Normal file
153
src/features/editScene/ui/DistrictEditor.cpp
Normal 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;
|
||||
}
|
||||
11
src/features/editScene/ui/DistrictEditor.hpp
Normal file
11
src/features/editScene/ui/DistrictEditor.hpp
Normal 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"; }
|
||||
};
|
||||
81
src/features/editScene/ui/FurnitureTemplateEditor.cpp
Normal file
81
src/features/editScene/ui/FurnitureTemplateEditor.cpp
Normal 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;
|
||||
}
|
||||
11
src/features/editScene/ui/FurnitureTemplateEditor.hpp
Normal file
11
src/features/editScene/ui/FurnitureTemplateEditor.hpp
Normal 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"; }
|
||||
};
|
||||
72
src/features/editScene/ui/LotEditor.cpp
Normal file
72
src/features/editScene/ui/LotEditor.cpp
Normal 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;
|
||||
}
|
||||
11
src/features/editScene/ui/LotEditor.hpp
Normal file
11
src/features/editScene/ui/LotEditor.hpp
Normal 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"; }
|
||||
};
|
||||
53
src/features/editScene/ui/RoofEditor.cpp
Normal file
53
src/features/editScene/ui/RoofEditor.cpp
Normal 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;
|
||||
}
|
||||
11
src/features/editScene/ui/RoofEditor.hpp
Normal file
11
src/features/editScene/ui/RoofEditor.hpp
Normal 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"; }
|
||||
};
|
||||
73
src/features/editScene/ui/RoomEditor.cpp
Normal file
73
src/features/editScene/ui/RoomEditor.cpp
Normal 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;
|
||||
}
|
||||
11
src/features/editScene/ui/RoomEditor.hpp
Normal file
11
src/features/editScene/ui/RoomEditor.hpp
Normal 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"; }
|
||||
};
|
||||
68
src/features/editScene/ui/TownEditor.cpp
Normal file
68
src/features/editScene/ui/TownEditor.cpp
Normal 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;
|
||||
}
|
||||
11
src/features/editScene/ui/TownEditor.hpp
Normal file
11
src/features/editScene/ui/TownEditor.hpp
Normal 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"; }
|
||||
};
|
||||
Reference in New Issue
Block a user