Furniture enabled
This commit is contained in:
@@ -24,6 +24,7 @@ set(EDITSCENE_SOURCES
|
||||
systems/ProceduralMeshSystem.cpp
|
||||
systems/CellGridSystem.cpp
|
||||
systems/RoomLayoutSystem.cpp
|
||||
systems/FurnitureLibrary.cpp
|
||||
ui/TransformEditor.cpp
|
||||
ui/RenderableEditor.cpp
|
||||
ui/PhysicsColliderEditor.cpp
|
||||
@@ -87,6 +88,7 @@ set(EDITSCENE_HEADERS
|
||||
systems/EditorUISystem.hpp
|
||||
systems/CellGridSystem.hpp
|
||||
systems/RoomLayoutSystem.hpp
|
||||
systems/FurnitureLibrary.hpp
|
||||
systems/ProceduralMaterialSystem.hpp
|
||||
systems/ProceduralMeshSystem.hpp
|
||||
systems/ProceduralTextureSystem.hpp
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
#include "CellGridSystem.hpp"
|
||||
#include "FurnitureLibrary.hpp"
|
||||
#include "../components/CellGrid.hpp"
|
||||
#include "../components/TriangleBuffer.hpp"
|
||||
#include "../components/Transform.hpp"
|
||||
#include "../components/StaticGeometry.hpp"
|
||||
#include "../components/StaticGeometryMember.hpp"
|
||||
#include <OgreStaticGeometry.h>
|
||||
#include "../components/ProceduralMaterial.hpp"
|
||||
#include "../components/ProceduralTexture.hpp"
|
||||
#include <OgreSceneManager.h>
|
||||
@@ -485,6 +487,18 @@ void CellGridSystem::buildCellGrid(flecs::entity entity,
|
||||
" roofTop=" + std::to_string(roofTopTb.getVertices().size()) +
|
||||
" roofSide=" + std::to_string(roofSideTb.getVertices().size()));
|
||||
|
||||
// Build furniture
|
||||
try {
|
||||
buildFurniture(entity, grid);
|
||||
} catch (const std::exception &e) {
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"CellGrid: Error building furniture: " +
|
||||
std::string(e.what()));
|
||||
} catch (...) {
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"CellGrid: Unknown error building furniture");
|
||||
}
|
||||
|
||||
// Build window and door frames
|
||||
try {
|
||||
buildFrames(entity, grid, materialName);
|
||||
@@ -2251,6 +2265,108 @@ Ogre::MeshPtr CellGridSystem::convertToMesh(const std::string &name,
|
||||
}
|
||||
}
|
||||
|
||||
void CellGridSystem::buildFurniture(flecs::entity entity,
|
||||
const CellGridComponent &grid)
|
||||
{
|
||||
destroyFurniture(entity);
|
||||
|
||||
if (!entity.has<TransformComponent>())
|
||||
return;
|
||||
auto &transform = entity.get_mut<TransformComponent>();
|
||||
if (!transform.node)
|
||||
return;
|
||||
|
||||
FurnitureLibrary::getInstance().loadAll();
|
||||
auto &meshData = m_entityMeshes[entity.id()];
|
||||
|
||||
for (const auto &fcell : grid.furnitureCells) {
|
||||
const FurnitureDefinition *def =
|
||||
FurnitureLibrary::getInstance().findByName(
|
||||
fcell.furnitureType);
|
||||
if (!def) {
|
||||
// Fallback: try to find by exact mesh name
|
||||
for (const auto &d :
|
||||
FurnitureLibrary::getInstance().getDefinitions()) {
|
||||
if (d.meshName == fcell.furnitureType ||
|
||||
d.name == fcell.furnitureType) {
|
||||
def = &d;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!def)
|
||||
continue;
|
||||
|
||||
Ogre::MeshPtr mesh =
|
||||
Ogre::MeshManager::getSingleton().getByName(
|
||||
def->meshName);
|
||||
if (!mesh) {
|
||||
Ogre::String group =
|
||||
Ogre::ResourceGroupManager::getSingleton()
|
||||
.findGroupContainingResource(
|
||||
def->meshName);
|
||||
if (!group.empty()) {
|
||||
mesh = Ogre::MeshManager::getSingleton().load(
|
||||
def->meshName, group);
|
||||
}
|
||||
}
|
||||
if (!mesh) {
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"CellGrid: Could not load furniture mesh '" +
|
||||
def->meshName + "'");
|
||||
continue;
|
||||
}
|
||||
|
||||
Ogre::Entity *ent =
|
||||
m_sceneMgr->createEntity(def->meshName);
|
||||
if (!ent)
|
||||
continue;
|
||||
|
||||
Ogre::SceneNode *fnode =
|
||||
transform.node->createChildSceneNode();
|
||||
Ogre::Vector3 pos = grid.cellToWorld(fcell.x, fcell.y,
|
||||
fcell.z);
|
||||
// Apply small Y offset so furniture sits on floor
|
||||
pos.y += 0.05f;
|
||||
fnode->setPosition(pos);
|
||||
fnode->setOrientation(Ogre::Quaternion(
|
||||
Ogre::Degree(90.0f * fcell.rotation),
|
||||
Ogre::Vector3::UNIT_Y));
|
||||
fnode->attachObject(ent);
|
||||
ent->setRenderingDistance(100.0f);
|
||||
|
||||
// Try to set material if mesh has submesh material
|
||||
if (!mesh->getSubMesh(0)->getMaterialName().empty()) {
|
||||
ent->setMaterialName(
|
||||
mesh->getSubMesh(0)->getMaterialName());
|
||||
}
|
||||
|
||||
meshData.furnitureEntities.push_back(ent);
|
||||
}
|
||||
}
|
||||
|
||||
void CellGridSystem::destroyFurniture(flecs::entity entity)
|
||||
{
|
||||
auto it = m_entityMeshes.find(entity.id());
|
||||
if (it == m_entityMeshes.end())
|
||||
return;
|
||||
|
||||
for (auto *ent : it->second.furnitureEntities) {
|
||||
if (!ent)
|
||||
continue;
|
||||
Ogre::SceneNode *node = ent->getParentSceneNode();
|
||||
if (node) {
|
||||
node->detachObject(ent);
|
||||
m_sceneMgr->destroySceneNode(node);
|
||||
}
|
||||
try {
|
||||
m_sceneMgr->destroyEntity(ent);
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
it->second.furnitureEntities.clear();
|
||||
}
|
||||
|
||||
void CellGridSystem::destroyCellGridMeshes(CellGridComponent &grid)
|
||||
{
|
||||
// This is called from buildCellGrid which uses the entity ID as key in m_entityMeshes
|
||||
@@ -2305,6 +2421,9 @@ void CellGridSystem::destroyCellGridMeshes(flecs::entity entity)
|
||||
removeMesh(name);
|
||||
}
|
||||
|
||||
// Destroy furniture
|
||||
destroyFurniture(entity);
|
||||
|
||||
// Destroy frames
|
||||
destroyFrames(entity);
|
||||
|
||||
@@ -3100,7 +3219,7 @@ void CellGridSystem::buildFrames(flecs::entity entity,
|
||||
parent = parent.parent();
|
||||
}
|
||||
|
||||
// Get the transform node to attach frames to
|
||||
// Get the transform for frame positioning
|
||||
Ogre::SceneNode *transformNode = nullptr;
|
||||
if (entity.has<TransformComponent>()) {
|
||||
auto &transform = entity.get<TransformComponent>();
|
||||
@@ -3111,32 +3230,51 @@ void CellGridSystem::buildFrames(flecs::entity entity,
|
||||
"CellGrid: No transform node for frames!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get parent transform for world position calculation
|
||||
Ogre::Vector3 parentPos = transformNode->_getDerivedPosition();
|
||||
Ogre::Quaternion parentRot = transformNode->_getDerivedOrientation();
|
||||
Ogre::Vector3 parentScale = transformNode->_getDerivedScale();
|
||||
|
||||
// Create frame meshes with unique names per entity
|
||||
std::string meshPrefix =
|
||||
"CellGrid_" + std::to_string(entity.id()) + "_";
|
||||
createWindowFrameMeshes(grid, materialName, meshPrefix, materialEntity);
|
||||
createDoorFrameMeshes(grid, materialName, meshPrefix, materialEntity);
|
||||
|
||||
// Place frames as child entities of the transform node
|
||||
// This way they move automatically when the parent transform changes
|
||||
std::vector<Ogre::Entity *> frameEntities;
|
||||
placeWindowFrames(entity, grid, transformNode, frameEntities);
|
||||
placeDoorFrames(entity, grid, transformNode, frameEntities);
|
||||
|
||||
// Create or get StaticGeometry region for frames
|
||||
std::string regionName = "FrameRegion_" + std::to_string(entity.id());
|
||||
Ogre::StaticGeometry* staticGeom = nullptr;
|
||||
|
||||
auto it = m_entityMeshes.find(entity.id());
|
||||
if (it != m_entityMeshes.end() && it->second.frameStaticGeometry) {
|
||||
// Reuse existing static geometry - destroy it first
|
||||
m_sceneMgr->destroyStaticGeometry(it->second.frameStaticGeometry);
|
||||
it->second.frameStaticGeometry = nullptr;
|
||||
}
|
||||
|
||||
staticGeom = m_sceneMgr->createStaticGeometry(regionName);
|
||||
staticGeom->setCastShadows(true);
|
||||
staticGeom->setRegionDimensions(Ogre::Vector3(1000.0f)); // Large region for all frames
|
||||
|
||||
// Place frames into StaticGeometry (batched into single draw call)
|
||||
int frameCount = 0;
|
||||
placeWindowFramesInStaticGeometry(entity, grid, parentPos, parentRot, parentScale, staticGeom, frameCount);
|
||||
placeDoorFramesInStaticGeometry(entity, grid, parentPos, parentRot, parentScale, staticGeom, frameCount);
|
||||
|
||||
// Build the static geometry
|
||||
staticGeom->build();
|
||||
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"CellGrid: Placed " + std::to_string(frameEntities.size()) +
|
||||
" frame entities");
|
||||
"CellGrid: Batched " + std::to_string(frameCount) +
|
||||
" frames into StaticGeometry");
|
||||
|
||||
// Store reference
|
||||
auto it = m_entityMeshes.find(entity.id());
|
||||
if (it != m_entityMeshes.end()) {
|
||||
it->second.frameEntities = std::move(frameEntities);
|
||||
it->second.frameStaticGeometry = staticGeom;
|
||||
} else {
|
||||
// Clean up if we can't store them
|
||||
for (auto *ent : frameEntities) {
|
||||
m_sceneMgr->destroyEntity(ent);
|
||||
}
|
||||
// Clean up if we can't store it
|
||||
m_sceneMgr->destroyStaticGeometry(staticGeom);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3704,7 +3842,16 @@ void CellGridSystem::destroyFrames(flecs::entity entity)
|
||||
{
|
||||
auto it = m_entityMeshes.find(entity.id());
|
||||
if (it != m_entityMeshes.end()) {
|
||||
// Destroy frame entities (and their child nodes)
|
||||
// Destroy StaticGeometry if present (replaces individual entities)
|
||||
if (it->second.frameStaticGeometry) {
|
||||
try {
|
||||
m_sceneMgr->destroyStaticGeometry(it->second.frameStaticGeometry);
|
||||
} catch (...) {
|
||||
}
|
||||
it->second.frameStaticGeometry = nullptr;
|
||||
}
|
||||
|
||||
// Destroy old frame entities (legacy cleanup)
|
||||
for (auto *ent : it->second.frameEntities) {
|
||||
if (ent) {
|
||||
// Get the node that owns this entity
|
||||
@@ -3748,3 +3895,189 @@ void CellGridSystem::destroyFrames(flecs::entity entity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// StaticGeometry Frame Placement (Batched for better performance)
|
||||
// ============================================================================
|
||||
|
||||
void CellGridSystem::placeWindowFramesInStaticGeometry(
|
||||
flecs::entity entity, const CellGridComponent &grid,
|
||||
const Ogre::Vector3 &parentPos, const Ogre::Quaternion &parentRot,
|
||||
const Ogre::Vector3 &parentScale, Ogre::StaticGeometry *staticGeom,
|
||||
int &frameCount)
|
||||
{
|
||||
std::string meshPrefix =
|
||||
"CellGrid_" + std::to_string(entity.id()) + "_";
|
||||
Ogre::MeshPtr extMesh = Ogre::MeshManager::getSingleton().getByName(
|
||||
meshPrefix + "window_external");
|
||||
Ogre::MeshPtr intMesh = Ogre::MeshManager::getSingleton().getByName(
|
||||
meshPrefix + "window_internal");
|
||||
|
||||
if (!extMesh && !intMesh)
|
||||
return;
|
||||
|
||||
float halfCell = grid.cellSize / 2.0f;
|
||||
float frameOffset = 0.0f;
|
||||
float windowHeight = 2.0f * (grid.cellHeight / 4.0f);
|
||||
float windowBottomOffset = 0.8f * (grid.cellHeight / 4.0f);
|
||||
float windowY = windowBottomOffset;
|
||||
|
||||
for (const auto &cell : grid.cells) {
|
||||
Ogre::Vector3 origin = grid.cellToWorld(cell.x, cell.y, cell.z);
|
||||
|
||||
auto addFrame = [&](Ogre::MeshPtr mesh, const Ogre::Vector3 &localPos,
|
||||
const Ogre::Quaternion &localRot) {
|
||||
if (!mesh)
|
||||
return;
|
||||
|
||||
// Calculate world position relative to parent
|
||||
Ogre::Vector3 worldPos = parentPos + (parentRot * (localPos * parentScale));
|
||||
Ogre::Quaternion worldRot = parentRot * localRot;
|
||||
|
||||
// Create temporary entity to add to StaticGeometry
|
||||
Ogre::Entity* tempEnt = m_sceneMgr->createEntity(mesh->getName());
|
||||
staticGeom->addEntity(tempEnt, worldPos, worldRot, parentScale);
|
||||
m_sceneMgr->destroyEntity(tempEnt); // StaticGeometry copies the data
|
||||
frameCount++;
|
||||
};
|
||||
|
||||
if (extMesh) {
|
||||
frameOffset = 0.1f;
|
||||
if (cell.hasFlag(CellFlags::WindowXNeg)) {
|
||||
Ogre::Vector3 pos = origin + Ogre::Vector3(-halfCell - frameOffset, windowY, 0);
|
||||
Ogre::Quaternion rot(Ogre::Degree(-90), Ogre::Vector3::UNIT_Y);
|
||||
addFrame(extMesh, pos, rot);
|
||||
}
|
||||
if (cell.hasFlag(CellFlags::WindowXPos)) {
|
||||
Ogre::Vector3 pos = origin + Ogre::Vector3(halfCell + frameOffset, windowY, 0);
|
||||
Ogre::Quaternion rot(Ogre::Degree(90), Ogre::Vector3::UNIT_Y);
|
||||
addFrame(extMesh, pos, rot);
|
||||
}
|
||||
if (cell.hasFlag(CellFlags::WindowZNeg)) {
|
||||
Ogre::Vector3 pos = origin + Ogre::Vector3(0, windowY, -halfCell - frameOffset);
|
||||
Ogre::Quaternion rot(Ogre::Degree(180), Ogre::Vector3::UNIT_Y);
|
||||
addFrame(extMesh, pos, rot);
|
||||
}
|
||||
if (cell.hasFlag(CellFlags::WindowZPos)) {
|
||||
Ogre::Vector3 pos = origin + Ogre::Vector3(0, windowY, halfCell + frameOffset);
|
||||
Ogre::Quaternion rot(Ogre::Degree(0), Ogre::Vector3::UNIT_Y);
|
||||
addFrame(extMesh, pos, rot);
|
||||
}
|
||||
}
|
||||
|
||||
if (intMesh) {
|
||||
frameOffset = 0.1f;
|
||||
if (cell.hasFlag(CellFlags::IntWindowXNeg)) {
|
||||
Ogre::Vector3 pos = origin + Ogre::Vector3(-halfCell + frameOffset, windowY, 0);
|
||||
Ogre::Quaternion rot(Ogre::Degree(-90), Ogre::Vector3::UNIT_Y);
|
||||
addFrame(intMesh, pos, rot);
|
||||
}
|
||||
if (cell.hasFlag(CellFlags::IntWindowXPos)) {
|
||||
Ogre::Vector3 pos = origin + Ogre::Vector3(halfCell - frameOffset, windowY, 0);
|
||||
Ogre::Quaternion rot(Ogre::Degree(90), Ogre::Vector3::UNIT_Y);
|
||||
addFrame(intMesh, pos, rot);
|
||||
}
|
||||
if (cell.hasFlag(CellFlags::IntWindowZNeg)) {
|
||||
Ogre::Vector3 pos = origin + Ogre::Vector3(0, windowY, -halfCell + frameOffset);
|
||||
Ogre::Quaternion rot(Ogre::Degree(180), Ogre::Vector3::UNIT_Y);
|
||||
addFrame(intMesh, pos, rot);
|
||||
}
|
||||
if (cell.hasFlag(CellFlags::IntWindowZPos)) {
|
||||
Ogre::Vector3 pos = origin + Ogre::Vector3(0, windowY, halfCell - frameOffset);
|
||||
Ogre::Quaternion rot(Ogre::Degree(0), Ogre::Vector3::UNIT_Y);
|
||||
addFrame(intMesh, pos, rot);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CellGridSystem::placeDoorFramesInStaticGeometry(
|
||||
flecs::entity entity, const CellGridComponent &grid,
|
||||
const Ogre::Vector3 &parentPos, const Ogre::Quaternion &parentRot,
|
||||
const Ogre::Vector3 &parentScale, Ogre::StaticGeometry *staticGeom,
|
||||
int &frameCount)
|
||||
{
|
||||
std::string meshPrefix =
|
||||
"CellGrid_" + std::to_string(entity.id()) + "_";
|
||||
Ogre::MeshPtr extMesh = Ogre::MeshManager::getSingleton().getByName(
|
||||
meshPrefix + "door_external");
|
||||
Ogre::MeshPtr intMesh = Ogre::MeshManager::getSingleton().getByName(
|
||||
meshPrefix + "door_internal");
|
||||
|
||||
if (!extMesh && !intMesh)
|
||||
return;
|
||||
|
||||
float halfCell = grid.cellSize / 2.0f;
|
||||
float frameOffset = 0.1f;
|
||||
float solidExtOffset = 0.0f;
|
||||
float solidIntOffset = 0.1f;
|
||||
float doorHeightBase = 3.0f * (grid.cellHeight / 4.0f);
|
||||
float extDoorFrameY = solidExtOffset;
|
||||
float intDoorFrameY = solidIntOffset;
|
||||
|
||||
auto addFrame = [&](Ogre::MeshPtr mesh, const Ogre::Vector3 &localPos,
|
||||
const Ogre::Quaternion &localRot) {
|
||||
if (!mesh)
|
||||
return;
|
||||
|
||||
// Calculate world position relative to parent
|
||||
Ogre::Vector3 worldPos = parentPos + (parentRot * (localPos * parentScale));
|
||||
Ogre::Quaternion worldRot = parentRot * localRot;
|
||||
|
||||
// Create temporary entity to add to StaticGeometry
|
||||
Ogre::Entity* tempEnt = m_sceneMgr->createEntity(mesh->getName());
|
||||
staticGeom->addEntity(tempEnt, worldPos, worldRot, parentScale);
|
||||
m_sceneMgr->destroyEntity(tempEnt); // StaticGeometry copies the data
|
||||
frameCount++;
|
||||
};
|
||||
|
||||
for (const auto &cell : grid.cells) {
|
||||
Ogre::Vector3 origin = grid.cellToWorld(cell.x, cell.y, cell.z);
|
||||
|
||||
if (extMesh) {
|
||||
if (cell.hasFlag(CellFlags::DoorXNeg)) {
|
||||
Ogre::Vector3 pos = origin + Ogre::Vector3(-halfCell - frameOffset, extDoorFrameY, 0);
|
||||
Ogre::Quaternion rot(Ogre::Degree(-90), Ogre::Vector3::UNIT_Y);
|
||||
addFrame(extMesh, pos, rot);
|
||||
}
|
||||
if (cell.hasFlag(CellFlags::DoorXPos)) {
|
||||
Ogre::Vector3 pos = origin + Ogre::Vector3(halfCell + frameOffset, extDoorFrameY, 0);
|
||||
Ogre::Quaternion rot(Ogre::Degree(90), Ogre::Vector3::UNIT_Y);
|
||||
addFrame(extMesh, pos, rot);
|
||||
}
|
||||
if (cell.hasFlag(CellFlags::DoorZNeg)) {
|
||||
Ogre::Vector3 pos = origin + Ogre::Vector3(0, extDoorFrameY, -halfCell - frameOffset);
|
||||
Ogre::Quaternion rot(Ogre::Degree(180), Ogre::Vector3::UNIT_Y);
|
||||
addFrame(extMesh, pos, rot);
|
||||
}
|
||||
if (cell.hasFlag(CellFlags::DoorZPos)) {
|
||||
Ogre::Vector3 pos = origin + Ogre::Vector3(0, extDoorFrameY, halfCell + frameOffset);
|
||||
Ogre::Quaternion rot(Ogre::Degree(0), Ogre::Vector3::UNIT_Y);
|
||||
addFrame(extMesh, pos, rot);
|
||||
}
|
||||
}
|
||||
|
||||
if (intMesh) {
|
||||
if (cell.hasFlag(CellFlags::IntDoorXNeg)) {
|
||||
Ogre::Vector3 pos = origin + Ogre::Vector3(-halfCell + frameOffset, intDoorFrameY, 0);
|
||||
Ogre::Quaternion rot(Ogre::Degree(-90), Ogre::Vector3::UNIT_Y);
|
||||
addFrame(intMesh, pos, rot);
|
||||
}
|
||||
if (cell.hasFlag(CellFlags::IntDoorXPos)) {
|
||||
Ogre::Vector3 pos = origin + Ogre::Vector3(halfCell - frameOffset, intDoorFrameY, 0);
|
||||
Ogre::Quaternion rot(Ogre::Degree(90), Ogre::Vector3::UNIT_Y);
|
||||
addFrame(intMesh, pos, rot);
|
||||
}
|
||||
if (cell.hasFlag(CellFlags::IntDoorZNeg)) {
|
||||
Ogre::Vector3 pos = origin + Ogre::Vector3(0, intDoorFrameY, -halfCell + frameOffset);
|
||||
Ogre::Quaternion rot(Ogre::Degree(180), Ogre::Vector3::UNIT_Y);
|
||||
addFrame(intMesh, pos, rot);
|
||||
}
|
||||
if (cell.hasFlag(CellFlags::IntDoorZPos)) {
|
||||
Ogre::Vector3 pos = origin + Ogre::Vector3(0, intDoorFrameY, halfCell - frameOffset);
|
||||
Ogre::Quaternion rot(Ogre::Degree(0), Ogre::Vector3::UNIT_Y);
|
||||
addFrame(intMesh, pos, rot);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,10 @@ namespace Procedural {
|
||||
class TriangleBuffer;
|
||||
}
|
||||
|
||||
namespace Ogre {
|
||||
class StaticGeometry;
|
||||
}
|
||||
|
||||
class CellGridSystem {
|
||||
public:
|
||||
CellGridSystem(flecs::world& world, Ogre::SceneManager* sceneMgr);
|
||||
@@ -58,6 +62,10 @@ private:
|
||||
// Build lot base geometry
|
||||
void buildLotBase(flecs::entity entity, struct LotComponent& lot);
|
||||
|
||||
// Build furniture meshes
|
||||
void buildFurniture(flecs::entity entity, const struct CellGridComponent& grid);
|
||||
void destroyFurniture(flecs::entity entity);
|
||||
|
||||
// Build window and door frames (3D frame meshes)
|
||||
void buildFrames(flecs::entity entity, const struct CellGridComponent& grid, const std::string& materialName);
|
||||
void createWindowFrameMeshes(const struct CellGridComponent& grid, const std::string& materialName,
|
||||
@@ -68,6 +76,14 @@ private:
|
||||
void placeDoorFrames(flecs::entity entity, const struct CellGridComponent& grid, Ogre::SceneNode* parentNode, std::vector<Ogre::Entity*>& frameEntities);
|
||||
void destroyFrames(flecs::entity entity);
|
||||
|
||||
// StaticGeometry versions (batched for better performance)
|
||||
void placeWindowFramesInStaticGeometry(flecs::entity entity, const struct CellGridComponent& grid,
|
||||
const Ogre::Vector3& parentPos, const Ogre::Quaternion& parentRot, const Ogre::Vector3& parentScale,
|
||||
Ogre::StaticGeometry* staticGeom, int& frameCount);
|
||||
void placeDoorFramesInStaticGeometry(flecs::entity entity, const struct CellGridComponent& grid,
|
||||
const Ogre::Vector3& parentPos, const Ogre::Quaternion& parentRot, const Ogre::Vector3& parentScale,
|
||||
Ogre::StaticGeometry* staticGeom, int& frameCount);
|
||||
|
||||
// Convert triangle buffer to mesh
|
||||
Ogre::MeshPtr convertToMesh(const std::string& name, Procedural::TriangleBuffer& tb, const std::string& materialName);
|
||||
|
||||
@@ -106,6 +122,12 @@ private:
|
||||
|
||||
// Frame entities attached to the SceneNode (so they move with transform)
|
||||
std::vector<Ogre::Entity*> frameEntities;
|
||||
|
||||
// StaticGeometry for frames (batches all frames into single draw call)
|
||||
Ogre::StaticGeometry* frameStaticGeometry = nullptr;
|
||||
|
||||
// Furniture entities
|
||||
std::vector<Ogre::Entity*> furnitureEntities;
|
||||
};
|
||||
std::unordered_map<uint64_t, MeshData> m_entityMeshes;
|
||||
|
||||
|
||||
126
src/features/editScene/systems/FurnitureLibrary.cpp
Normal file
126
src/features/editScene/systems/FurnitureLibrary.cpp
Normal file
@@ -0,0 +1,126 @@
|
||||
#include "FurnitureLibrary.hpp"
|
||||
#include <OgreResourceGroupManager.h>
|
||||
#include <OgreDataStream.h>
|
||||
#include <OgreLogManager.h>
|
||||
#include <algorithm>
|
||||
#include <random>
|
||||
|
||||
FurnitureLibrary &FurnitureLibrary::getInstance()
|
||||
{
|
||||
static FurnitureLibrary instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void FurnitureLibrary::loadAll()
|
||||
{
|
||||
if (m_loaded)
|
||||
return;
|
||||
m_loaded = true;
|
||||
m_definitions.clear();
|
||||
|
||||
std::vector<Ogre::String> names;
|
||||
const std::vector<Ogre::String> &groups =
|
||||
Ogre::ResourceGroupManager::getSingleton()
|
||||
.getResourceGroups();
|
||||
for (const auto &group : groups) {
|
||||
std::vector<Ogre::String> groupNames =
|
||||
*Ogre::ResourceGroupManager::getSingleton()
|
||||
.findResourceNames(group, "furniture-*.json");
|
||||
names.insert(names.end(), groupNames.begin(),
|
||||
groupNames.end());
|
||||
}
|
||||
|
||||
for (const auto &name : names) {
|
||||
Ogre::String group =
|
||||
Ogre::ResourceGroupManager::getSingleton()
|
||||
.findGroupContainingResource(name);
|
||||
Ogre::DataStreamPtr stream =
|
||||
Ogre::ResourceGroupManager::getSingleton()
|
||||
.openResource(name, group);
|
||||
Ogre::String jsonStr = stream->getAsString();
|
||||
try {
|
||||
nlohmann::json jdata = nlohmann::json::parse(jsonStr);
|
||||
FurnitureDefinition def;
|
||||
def.name = jdata.value("name", name);
|
||||
def.meshName = jdata.value("mesh", "");
|
||||
def.jsonData = jdata;
|
||||
if (jdata.contains("tags") && jdata["tags"].is_array()) {
|
||||
for (auto &tag : jdata["tags"]) {
|
||||
def.tags.push_back(tag.get<std::string>());
|
||||
}
|
||||
}
|
||||
if (!def.meshName.empty()) {
|
||||
m_definitions.push_back(std::move(def));
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"FurnitureLibrary: Failed to parse " + name +
|
||||
": " + e.what());
|
||||
}
|
||||
}
|
||||
|
||||
Ogre::LogManager::getSingleton().logMessage(
|
||||
"FurnitureLibrary: Loaded " +
|
||||
std::to_string(m_definitions.size()) +
|
||||
" furniture definitions");
|
||||
}
|
||||
|
||||
const std::vector<FurnitureDefinition> &FurnitureLibrary::getDefinitions()
|
||||
const
|
||||
{
|
||||
return m_definitions;
|
||||
}
|
||||
|
||||
const FurnitureDefinition *FurnitureLibrary::findByName(
|
||||
const std::string &name) const
|
||||
{
|
||||
for (const auto &def : m_definitions) {
|
||||
if (def.name == name)
|
||||
return &def;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<const FurnitureDefinition *> FurnitureLibrary::findByTags(
|
||||
const std::vector<std::string> &requiredTags,
|
||||
const std::vector<std::string> &forbiddenTags) const
|
||||
{
|
||||
std::vector<const FurnitureDefinition *> results;
|
||||
for (const auto &def : m_definitions) {
|
||||
bool match = true;
|
||||
for (const auto &req : requiredTags) {
|
||||
if (std::find(def.tags.begin(), def.tags.end(), req) ==
|
||||
def.tags.end()) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!match)
|
||||
continue;
|
||||
for (const auto &forb : forbiddenTags) {
|
||||
if (std::find(def.tags.begin(), def.tags.end(), forb) !=
|
||||
def.tags.end()) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match)
|
||||
results.push_back(&def);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
const FurnitureDefinition *FurnitureLibrary::selectByTags(
|
||||
const std::vector<std::string> &requiredTags,
|
||||
const std::vector<std::string> &forbiddenTags) const
|
||||
{
|
||||
auto candidates = findByTags(requiredTags, forbiddenTags);
|
||||
if (candidates.empty())
|
||||
return nullptr;
|
||||
|
||||
static std::mt19937 rng((unsigned)std::chrono::steady_clock::now()
|
||||
.time_since_epoch()
|
||||
.count());
|
||||
std::uniform_int_distribution<size_t> dist(0, candidates.size() - 1);
|
||||
return candidates[dist(rng)];
|
||||
}
|
||||
39
src/features/editScene/systems/FurnitureLibrary.hpp
Normal file
39
src/features/editScene/systems/FurnitureLibrary.hpp
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <Ogre.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct FurnitureDefinition {
|
||||
std::string name;
|
||||
std::string meshName;
|
||||
std::vector<std::string> tags;
|
||||
nlohmann::json jsonData;
|
||||
};
|
||||
|
||||
class FurnitureLibrary {
|
||||
public:
|
||||
static FurnitureLibrary &getInstance();
|
||||
|
||||
void loadAll();
|
||||
bool isLoaded() const { return m_loaded; }
|
||||
void reload() { m_loaded = false; loadAll(); }
|
||||
|
||||
const std::vector<FurnitureDefinition> &getDefinitions() const;
|
||||
|
||||
const FurnitureDefinition *findByName(const std::string &name) const;
|
||||
|
||||
std::vector<const FurnitureDefinition *> findByTags(
|
||||
const std::vector<std::string> &requiredTags,
|
||||
const std::vector<std::string> &forbiddenTags = {}) const;
|
||||
|
||||
const FurnitureDefinition *selectByTags(
|
||||
const std::vector<std::string> &requiredTags,
|
||||
const std::vector<std::string> &forbiddenTags = {}) const;
|
||||
|
||||
private:
|
||||
FurnitureLibrary() = default;
|
||||
std::vector<FurnitureDefinition> m_definitions;
|
||||
bool m_loaded = false;
|
||||
};
|
||||
@@ -1,9 +1,11 @@
|
||||
#include "RoomLayoutSystem.hpp"
|
||||
#include "FurnitureLibrary.hpp"
|
||||
#include "../components/CellGrid.hpp"
|
||||
#include <OgreLogManager.h>
|
||||
#include <climits>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
#include <random>
|
||||
|
||||
RoomLayoutSystem::RoomLayoutSystem(flecs::world& world, Ogre::SceneManager* sceneMgr)
|
||||
: m_world(world)
|
||||
@@ -131,6 +133,15 @@ void RoomLayoutSystem::reprocessAll()
|
||||
processExteriorGeneration(*grid);
|
||||
}
|
||||
});
|
||||
|
||||
// STEP 7: Generate furniture for all rooms
|
||||
FurnitureLibrary::getInstance().loadAll();
|
||||
m_roomQuery.each([&](flecs::entity roomEntity, RoomComponent& room) {
|
||||
CellGridComponent* grid = findParentGrid(roomEntity);
|
||||
if (grid) {
|
||||
generateRoomFurniture(room, *grid);
|
||||
}
|
||||
});
|
||||
|
||||
Ogre::LogManager::getSingleton().logMessage("RoomLayoutSystem: Full reprocess complete.");
|
||||
}
|
||||
@@ -842,3 +853,237 @@ void RoomLayoutSystem::clearWallFlags(CellGridComponent& grid, int x, int y, int
|
||||
|
||||
cell->flags &= ~flagsToClear;
|
||||
}
|
||||
|
||||
void RoomLayoutSystem::generateRoomFurniture(RoomComponent& room,
|
||||
CellGridComponent& grid)
|
||||
{
|
||||
if (room.tags.empty())
|
||||
return;
|
||||
|
||||
// Clear existing furniture in room bounds
|
||||
for (auto it = grid.furnitureCells.begin();
|
||||
it != grid.furnitureCells.end();) {
|
||||
if (it->x >= room.minX && it->x < room.maxX &&
|
||||
it->y >= room.minY && it->y < room.maxY &&
|
||||
it->z >= room.minZ && it->z < room.maxZ) {
|
||||
it = grid.furnitureCells.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
int midX = (room.minX + room.maxX - 1) / 2;
|
||||
int midZ = (room.minZ + room.maxZ - 1) / 2;
|
||||
|
||||
// Side: 0=Z- (minZ), 1=Z+ (maxZ), 2=X- (minX), 3=X+ (maxX)
|
||||
const int ROTATION_BY_SIDE[4] = { 2, 0, 3, 1 };
|
||||
const uint64_t WALL_FLAGS_BY_SIDE[4] = {
|
||||
CellFlags::IntWallZNeg | CellFlags::WallZNeg,
|
||||
CellFlags::IntWallZPos | CellFlags::WallZPos,
|
||||
CellFlags::IntWallXNeg | CellFlags::WallXNeg,
|
||||
CellFlags::IntWallXPos | CellFlags::WallXPos
|
||||
};
|
||||
const uint64_t WINDOW_FLAGS_BY_SIDE[4] = {
|
||||
CellFlags::IntWindowZNeg | CellFlags::WindowZNeg,
|
||||
CellFlags::IntWindowZPos | CellFlags::WindowZPos,
|
||||
CellFlags::IntWindowXNeg | CellFlags::WindowXNeg,
|
||||
CellFlags::IntWindowXPos | CellFlags::WindowXPos
|
||||
};
|
||||
const uint64_t DOOR_FLAGS_BY_SIDE[4] = {
|
||||
CellFlags::IntDoorZNeg | CellFlags::DoorZNeg,
|
||||
CellFlags::IntDoorZPos | CellFlags::DoorZPos,
|
||||
CellFlags::IntDoorXNeg | CellFlags::DoorXNeg,
|
||||
CellFlags::IntDoorXPos | CellFlags::DoorXPos
|
||||
};
|
||||
|
||||
auto cellHasAnyDoor = [&](int x, int y, int z) -> bool {
|
||||
const Cell *cell = grid.findCell(x, y, z);
|
||||
if (!cell)
|
||||
return false;
|
||||
return (cell->flags & CellFlags::AllDoors) != 0;
|
||||
};
|
||||
auto cellHasAnyWindow = [&](int x, int y, int z) -> bool {
|
||||
const Cell *cell = grid.findCell(x, y, z);
|
||||
if (!cell)
|
||||
return false;
|
||||
return (cell->flags & CellFlags::AllWindows) != 0;
|
||||
};
|
||||
auto cellHasAnyWall = [&](int x, int y, int z) -> bool {
|
||||
const Cell *cell = grid.findCell(x, y, z);
|
||||
if (!cell)
|
||||
return false;
|
||||
return (cell->flags & CellFlags::AllWalls) != 0;
|
||||
};
|
||||
|
||||
auto canPlaceFurniture = [&](const FurnitureDefinition *def,
|
||||
int x, int y, int z) -> bool {
|
||||
if (!def)
|
||||
return false;
|
||||
// If cell has any door, furniture must have "door" tag
|
||||
if (cellHasAnyDoor(x, y, z)) {
|
||||
if (std::find(def->tags.begin(), def->tags.end(),
|
||||
"door") == def->tags.end())
|
||||
return false;
|
||||
}
|
||||
// If cell has any window, furniture must NOT have "nowindow" tag
|
||||
if (cellHasAnyWindow(x, y, z)) {
|
||||
if (std::find(def->tags.begin(), def->tags.end(),
|
||||
"nowindow") != def->tags.end())
|
||||
return false;
|
||||
}
|
||||
// If cell has no walls, furniture must have "nowall" tag
|
||||
if (!cellHasAnyWall(x, y, z)) {
|
||||
if (std::find(def->tags.begin(), def->tags.end(),
|
||||
"nowall") == def->tags.end())
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
auto placeOne = [&](int cx, int cy, int cz, int side,
|
||||
const std::vector<std::string> &tags,
|
||||
const std::vector<std::string> ¬ags,
|
||||
bool onWindow) -> bool {
|
||||
if (grid.findFurnitureCell(cx, cy, cz))
|
||||
return false;
|
||||
const Cell *cell = grid.findCell(cx, cy, cz);
|
||||
if (!cell)
|
||||
return false;
|
||||
|
||||
uint64_t sideFlags = onWindow ? WINDOW_FLAGS_BY_SIDE[side] :
|
||||
WALL_FLAGS_BY_SIDE[side];
|
||||
if ((cell->flags & sideFlags) == 0)
|
||||
return false;
|
||||
|
||||
auto *def = FurnitureLibrary::getInstance().selectByTags(
|
||||
tags, notags);
|
||||
if (!def)
|
||||
return false;
|
||||
|
||||
if (!canPlaceFurniture(def, cx, cy, cz))
|
||||
return false;
|
||||
|
||||
auto &fcell = grid.getOrCreateFurnitureCell(cx, cy, cz);
|
||||
fcell.furnitureType = def->name;
|
||||
fcell.rotation = ROTATION_BY_SIDE[side];
|
||||
fcell.tags = tags;
|
||||
return true;
|
||||
};
|
||||
|
||||
std::vector<std::string> baseTags = room.tags;
|
||||
std::vector<std::string> etags = baseTags;
|
||||
std::vector<std::string> itags = baseTags;
|
||||
std::vector<std::string> otags = baseTags;
|
||||
std::vector<std::string> ftags = baseTags;
|
||||
etags.push_back("essential");
|
||||
itags.push_back("important");
|
||||
otags.push_back("optional");
|
||||
ftags.push_back("filler");
|
||||
|
||||
struct TagData {
|
||||
std::vector<std::string> tags;
|
||||
std::vector<std::string> notagsWalls;
|
||||
std::vector<std::string> notagsWindows;
|
||||
bool essential;
|
||||
bool filler;
|
||||
};
|
||||
TagData taglist[] = {
|
||||
{ etags, { "nowall", "nowindow" }, { "nowindow" }, true,
|
||||
false },
|
||||
{ itags, { "nowall", "nowindow" }, { "nowindow" }, false,
|
||||
false },
|
||||
{ otags, { "nowall", "nowindow" }, { "nowindow" }, false,
|
||||
false },
|
||||
{ ftags, { "nowall", "nowindow" }, { "nowindow" }, false,
|
||||
true },
|
||||
};
|
||||
|
||||
// Midpoints for each side
|
||||
std::tuple<int, int, int> mpoints[4] = {
|
||||
{ midX, room.minY, room.minZ }, // side 0 (Z-)
|
||||
{ midX, room.minY, room.maxZ - 1 }, // side 1 (Z+)
|
||||
{ room.minX, room.minY, midZ }, // side 2 (X-)
|
||||
{ room.maxX - 1, room.minY, midZ }, // side 3 (X+)
|
||||
};
|
||||
|
||||
for (const auto &mtags : taglist) {
|
||||
bool placed = false;
|
||||
|
||||
// Try wall midpoints
|
||||
for (int side = 0; side < 4 && !placed; ++side) {
|
||||
int cx = std::get<0>(mpoints[side]);
|
||||
int cy = std::get<1>(mpoints[side]);
|
||||
int cz = std::get<2>(mpoints[side]);
|
||||
placed = placeOne(cx, cy, cz, side, mtags.tags,
|
||||
mtags.notagsWalls, false);
|
||||
if (placed && !mtags.filler)
|
||||
break;
|
||||
if (mtags.filler)
|
||||
placed = false;
|
||||
}
|
||||
|
||||
// Try wall ranges (all edge cells on each side)
|
||||
if (!placed) {
|
||||
for (int side = 0; side < 4 && !placed; ++side) {
|
||||
auto edgeCells = getRoomEdgeCells(room, side);
|
||||
for (const auto &cell : edgeCells) {
|
||||
int cx = cell.first;
|
||||
int cz = cell.second;
|
||||
int cy = room.minY;
|
||||
placed = placeOne(cx, cy, cz, side,
|
||||
mtags.tags,
|
||||
mtags.notagsWalls,
|
||||
false);
|
||||
if (placed && !mtags.filler)
|
||||
break;
|
||||
}
|
||||
if (placed && !mtags.filler)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (mtags.filler)
|
||||
placed = false;
|
||||
|
||||
// Try window midpoints
|
||||
if (!placed) {
|
||||
for (int side = 0; side < 4 && !placed; ++side) {
|
||||
int cx = std::get<0>(mpoints[side]);
|
||||
int cy = std::get<1>(mpoints[side]);
|
||||
int cz = std::get<2>(mpoints[side]);
|
||||
placed = placeOne(cx, cy, cz, side, mtags.tags,
|
||||
mtags.notagsWindows, true);
|
||||
if (placed && !mtags.filler)
|
||||
break;
|
||||
if (mtags.filler)
|
||||
placed = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Try window ranges
|
||||
if (!placed) {
|
||||
for (int side = 0; side < 4 && !placed; ++side) {
|
||||
auto edgeCells = getRoomEdgeCells(room, side);
|
||||
for (const auto &cell : edgeCells) {
|
||||
int cx = cell.first;
|
||||
int cz = cell.second;
|
||||
int cy = room.minY;
|
||||
placed = placeOne(cx, cy, cz, side,
|
||||
mtags.tags,
|
||||
mtags.notagsWindows,
|
||||
true);
|
||||
if (placed && !mtags.filler)
|
||||
break;
|
||||
}
|
||||
if (placed && !mtags.filler)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!placed && !mtags.filler) {
|
||||
break; // do not place anything if essentials were not placed
|
||||
}
|
||||
}
|
||||
|
||||
grid.markDirty();
|
||||
}
|
||||
|
||||
@@ -62,6 +62,9 @@ private:
|
||||
// This always runs at the end of the pipeline for all rooms
|
||||
void processExteriorGeneration(class CellGridComponent& grid);
|
||||
|
||||
// Generate furniture for a room based on its tags
|
||||
void generateRoomFurniture(class RoomComponent& room, class CellGridComponent& grid);
|
||||
|
||||
// Helper: Get the parent CellGridComponent for an entity
|
||||
class CellGridComponent* findParentGrid(flecs::entity entity);
|
||||
|
||||
|
||||
@@ -2,10 +2,13 @@
|
||||
#include "../components/CellGrid.hpp"
|
||||
#include "../components/ProceduralMaterial.hpp"
|
||||
#include "../components/ProceduralTexture.hpp"
|
||||
#include "../systems/FurnitureLibrary.hpp"
|
||||
#include <OgreStringConverter.h>
|
||||
|
||||
|
||||
bool CellGridEditor::renderComponent(flecs::entity entity, CellGridComponent& grid)
|
||||
{
|
||||
m_currentEntity = entity;
|
||||
bool modified = false;
|
||||
|
||||
if (ImGui::CollapsingHeader("Cell Grid", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
@@ -145,17 +148,38 @@ 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 selectedFurnitureIndex = -1;
|
||||
static int rotation = 0;
|
||||
|
||||
FurnitureLibrary::getInstance().loadAll();
|
||||
const auto& defs = FurnitureLibrary::getInstance().getDefinitions();
|
||||
|
||||
ImGui::InputInt3("Furniture Pos", &fx);
|
||||
ImGui::InputText("Tags (comma separated)", tagsBuffer, sizeof(tagsBuffer));
|
||||
ImGui::InputText("Furniture Type", furnitureType, sizeof(furnitureType));
|
||||
|
||||
// Furniture type dropdown
|
||||
std::string preview = selectedFurnitureIndex >= 0 && selectedFurnitureIndex < (int)defs.size()
|
||||
? defs[selectedFurnitureIndex].name + " (" + defs[selectedFurnitureIndex].meshName + ")"
|
||||
: "Select furniture type...";
|
||||
|
||||
if (ImGui::BeginCombo("Furniture Type", preview.c_str())) {
|
||||
for (int i = 0; i < (int)defs.size(); ++i) {
|
||||
std::string label = defs[i].name + " (" + defs[i].meshName + ")";
|
||||
bool isSelected = (selectedFurnitureIndex == i);
|
||||
if (ImGui::Selectable(label.c_str(), isSelected)) {
|
||||
selectedFurnitureIndex = i;
|
||||
}
|
||||
if (isSelected)
|
||||
ImGui::SetItemDefaultFocus();
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
|
||||
ImGui::SliderInt("Rotation", &rotation, 0, 3);
|
||||
|
||||
if (ImGui::Button("Add/Update Furniture")) {
|
||||
if (ImGui::Button("Add/Update Furniture") && selectedFurnitureIndex >= 0) {
|
||||
auto& fcell = grid.getOrCreateFurnitureCell(fx, fy, fz);
|
||||
fcell.furnitureType = furnitureType;
|
||||
fcell.furnitureType = defs[selectedFurnitureIndex].name;
|
||||
fcell.rotation = rotation;
|
||||
fcell.tags.clear();
|
||||
// Parse tags
|
||||
@@ -164,7 +188,6 @@ void CellGridEditor::renderFurnitureEditor(CellGridComponent& grid)
|
||||
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()) {
|
||||
@@ -173,7 +196,6 @@ void CellGridEditor::renderFurnitureEditor(CellGridComponent& grid)
|
||||
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);
|
||||
@@ -183,6 +205,12 @@ void CellGridEditor::renderFurnitureEditor(CellGridComponent& grid)
|
||||
grid.markDirty();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Delete Furniture")) {
|
||||
grid.removeFurnitureCell(fx, fy, fz);
|
||||
grid.markDirty();
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// List furniture cells
|
||||
@@ -196,8 +224,14 @@ void CellGridEditor::renderFurnitureEditor(CellGridComponent& grid)
|
||||
fy = fcell.y;
|
||||
fz = fcell.z;
|
||||
rotation = fcell.rotation;
|
||||
strncpy(furnitureType, fcell.furnitureType.c_str(), sizeof(furnitureType) - 1);
|
||||
// Build tags string
|
||||
// Find index in defs
|
||||
selectedFurnitureIndex = -1;
|
||||
for (int i = 0; i < (int)defs.size(); ++i) {
|
||||
if (defs[i].name == fcell.furnitureType) {
|
||||
selectedFurnitureIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
std::string tags;
|
||||
for (size_t i = 0; i < fcell.tags.size(); ++i) {
|
||||
if (i > 0) tags += ", ";
|
||||
|
||||
@@ -20,4 +20,5 @@ private:
|
||||
int newCellX = 0, newCellY = 0, newCellZ = 0;
|
||||
char scriptBuffer[16384] = {0};
|
||||
bool showGrid = false;
|
||||
flecs::entity m_currentEntity = flecs::entity::null();
|
||||
};
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#include "RoomEditor.hpp"
|
||||
#include "../components/CellGrid.hpp"
|
||||
#include "../components/EntityName.hpp"
|
||||
#include "../systems/FurnitureLibrary.hpp"
|
||||
#include <imgui.h>
|
||||
#include <algorithm>
|
||||
#include <set>
|
||||
|
||||
bool RoomEditor::renderComponent(flecs::entity entity, RoomComponent& room)
|
||||
{
|
||||
@@ -80,6 +82,53 @@ bool RoomEditor::renderComponent(flecs::entity entity, RoomComponent& room)
|
||||
if (!lastTag.empty()) room.tags.push_back(lastTag);
|
||||
modified = true;
|
||||
}
|
||||
|
||||
// Validate that furniture exists for these tags
|
||||
if (!room.tags.empty()) {
|
||||
FurnitureLibrary::getInstance().loadAll();
|
||||
auto matches = FurnitureLibrary::getInstance().findByTags(room.tags, {});
|
||||
if (matches.empty()) {
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f),
|
||||
"Warning: No furniture found for these tags");
|
||||
} else {
|
||||
ImGui::TextDisabled("%zu furniture item(s) match", matches.size());
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::CollapsingHeader("Available Furniture", ImGuiTreeNodeFlags_None)) {
|
||||
ImGui::Indent();
|
||||
FurnitureLibrary::getInstance().loadAll();
|
||||
const auto& defs = FurnitureLibrary::getInstance().getDefinitions();
|
||||
if (defs.empty()) {
|
||||
ImGui::TextDisabled("No furniture definitions loaded");
|
||||
} else {
|
||||
// Build a sorted set of all tags
|
||||
std::set<std::string> allTags;
|
||||
for (const auto& def : defs) {
|
||||
for (const auto& tag : def.tags) {
|
||||
allTags.insert(tag);
|
||||
}
|
||||
}
|
||||
ImGui::TextDisabled("All tags:");
|
||||
std::string tagList;
|
||||
for (const auto& tag : allTags) {
|
||||
if (!tagList.empty()) tagList += ", ";
|
||||
tagList += tag;
|
||||
}
|
||||
ImGui::TextWrapped("%s", tagList.c_str());
|
||||
ImGui::Separator();
|
||||
|
||||
for (const auto& def : defs) {
|
||||
std::string tagStr;
|
||||
for (size_t i = 0; i < def.tags.size(); ++i) {
|
||||
if (i > 0) tagStr += ", ";
|
||||
tagStr += def.tags[i];
|
||||
}
|
||||
ImGui::BulletText("%s [%s]", def.name.c_str(), tagStr.c_str());
|
||||
}
|
||||
}
|
||||
ImGui::Unindent();
|
||||
}
|
||||
|
||||
ImGui::Unindent();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user