Fixed material for Lot geometry and CellGrid is re-created now

This commit is contained in:
2026-04-05 19:51:22 +03:00
parent cfd9dde5da
commit 64b03abb48
7 changed files with 645 additions and 48 deletions

View File

@@ -235,6 +235,13 @@ struct LotComponent {
// Template name if this lot was created from a template
std::string templateName;
// Procedural material for lot base (optional - falls back to District, then Town)
flecs::entity proceduralMaterialEntity;
std::string proceduralMaterialEntityId; // For serialization
// Texture rectangle name from ProceduralTexture for UV mapping
std::string textureRectName;
// Dirty flag
bool dirty = true;
@@ -289,6 +296,13 @@ struct TownComponent {
// Material name (created from color rects)
std::string materialName;
// Procedural material for town (used by districts and lots)
flecs::entity proceduralMaterialEntity;
std::string proceduralMaterialEntityId; // For serialization
// Texture rectangle name from ProceduralTexture for UV mapping
std::string textureRectName;
// Dirty flag
bool dirty = true;
bool materialDirty = true;

View File

@@ -29,6 +29,7 @@ CellGridSystem::CellGridSystem(flecs::world& world, Ogre::SceneManager* sceneMgr
, m_cellGridQuery(world.query<CellGridComponent>())
, m_townQuery(world.query<TownComponent>())
, m_districtQuery(world.query<DistrictComponent>())
, m_lotQuery(world.query<LotComponent>())
{
}
@@ -81,6 +82,14 @@ void CellGridSystem::update()
std::to_string(districtCount) + " districts, " +
std::to_string(dirtyDistrictCount) + " dirty");
}
// Process dirty lots (for base geometry)
m_lotQuery.each([&](flecs::entity entity, LotComponent& lot) {
if (lot.dirty) {
buildLotBase(entity, lot);
lot.dirty = false;
}
});
}
void CellGridSystem::buildCellGrid(flecs::entity entity, CellGridComponent& grid)
@@ -89,7 +98,7 @@ void CellGridSystem::buildCellGrid(flecs::entity entity, CellGridComponent& grid
" cells=" + std::to_string(grid.cells.size()));
// Destroy existing meshes
destroyCellGridMeshes(grid);
destroyCellGridMeshes(entity);
// Find parent town for material
std::string materialName = "Ogre/StandardFloor"; // default
@@ -474,7 +483,51 @@ Ogre::MeshPtr CellGridSystem::convertToMesh(const std::string& name, Procedural:
void CellGridSystem::destroyCellGridMeshes(CellGridComponent& grid)
{
// Cleanup handled by entity destruction, but we track mesh names
// This is called from buildCellGrid which uses the entity ID as key in m_entityMeshes
// The actual cleanup needs to happen there
}
void CellGridSystem::destroyCellGridMeshes(flecs::entity entity)
{
auto it = m_entityMeshes.find(entity.id());
if (it != m_entityMeshes.end()) {
// Destroy all entities first
for (auto* ent : it->second.entities) {
if (ent) {
try {
m_sceneMgr->destroyEntity(ent);
} catch (...) {}
}
}
it->second.entities.clear();
// Destroy meshes
auto& meshMgr = Ogre::MeshManager::getSingleton();
auto removeMesh = [&](const std::string& name) {
if (!name.empty()) {
try {
meshMgr.remove(name);
} catch (...) {}
}
};
removeMesh(it->second.floorMesh);
removeMesh(it->second.ceilingMesh);
removeMesh(it->second.extWallMesh);
removeMesh(it->second.intWallMesh);
removeMesh(it->second.intWindowsMesh);
removeMesh(it->second.roofMesh);
for (const auto& name : it->second.doorMeshes) {
removeMesh(name);
}
for (const auto& name : it->second.windowMeshes) {
removeMesh(name);
}
m_entityMeshes.erase(it);
}
}
void CellGridSystem::updateTownMaterial(flecs::entity townEntity)
@@ -680,3 +733,300 @@ void CellGridSystem::buildDistrictPlaza(flecs::entity entity, DistrictComponent&
Ogre::LogManager::getSingleton().logMessage("CellGrid: Failed to create plaza mesh: " + e.getDescription());
}
}
void CellGridSystem::buildLotBase(flecs::entity entity, LotComponent& lot)
{
// Cleanup old mesh
auto it = m_lotBaseMeshes.find(entity.id());
if (it != m_lotBaseMeshes.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_lotBaseMeshes.erase(it);
}
// Validate transform
if (!entity.has<TransformComponent>()) {
Ogre::LogManager::getSingleton().logMessage("CellGrid: Lot has no TransformComponent");
return;
}
auto& transform = entity.get_mut<TransformComponent>();
if (!transform.node) {
Ogre::LogManager::getSingleton().logMessage("CellGrid: Lot has no SceneNode");
return;
}
// Find parent district and town for radius and material
flecs::entity districtEntity = flecs::entity::null();
flecs::entity townEntity = flecs::entity::null();
float districtRadius = 50.0f;
std::string materialName = "Ogre/StandardFloor";
// Material hierarchy: Lot -> District -> Town
// 1. Check Lot's own material first
if (lot.proceduralMaterialEntity.is_alive() &&
lot.proceduralMaterialEntity.has<ProceduralMaterialComponent>()) {
const auto& mat = lot.proceduralMaterialEntity.get<ProceduralMaterialComponent>();
if (mat.created && !mat.materialName.empty()) {
materialName = mat.materialName;
Ogre::LogManager::getSingleton().logMessage("CellGrid: Using Lot's own material: " + materialName);
}
}
// 2. Walk up hierarchy for District and Town, and fallback materials
flecs::entity parent = entity.parent();
while (parent.is_alive()) {
if (parent.has<DistrictComponent>()) {
districtEntity = parent;
auto& district = parent.get<DistrictComponent>();
districtRadius = district.radius;
// Use District material if Lot doesn't have one
if (materialName == "Ogre/StandardFloor" &&
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: Using District material: " + materialName);
}
}
}
if (parent.has<TownComponent>()) {
townEntity = parent;
// Check Town's ProceduralMaterial first, then generated material
if (materialName == "Ogre/StandardFloor") {
auto& town = parent.get<TownComponent>();
if (town.proceduralMaterialEntity.is_alive() &&
town.proceduralMaterialEntity.has<ProceduralMaterialComponent>()) {
const auto& mat = town.proceduralMaterialEntity.get<ProceduralMaterialComponent>();
if (mat.created && !mat.materialName.empty()) {
materialName = mat.materialName;
Ogre::LogManager::getSingleton().logMessage("CellGrid: Using Town ProceduralMaterial: " + materialName);
}
}
// Fallback to Town's generated material
if (materialName == "Ogre/StandardFloor" && !town.materialName.empty()) {
materialName = town.materialName;
Ogre::LogManager::getSingleton().logMessage("CellGrid: Using Town generated material: " + materialName);
}
}
break;
}
parent = parent.parent();
}
if (!districtEntity.is_alive()) {
Ogre::LogManager::getSingleton().logMessage("CellGrid: Lot has no parent District");
return;
}
Ogre::LogManager::getSingleton().logMessage("CellGrid: Material selection for lot " + std::to_string(entity.id()) +
" final materialName='" + materialName + "'");
// Position the Lot's SceneNode at the correct location around the district
Ogre::Quaternion lotRotation(Ogre::Degree(lot.angle), Ogre::Vector3::UNIT_Y);
Ogre::Vector3 lotOffset = lotRotation * (Ogre::Vector3::UNIT_Z * districtRadius);
lotOffset += Ogre::Vector3(lot.offsetX, 0, lot.offsetZ);
// Set the Lot's position (CellGrid children will inherit this)
transform.node->setPosition(lotOffset);
transform.node->setOrientation(lotRotation);
// Update the TransformComponent to match
transform.position = lotOffset;
transform.rotation = lotRotation;
Ogre::LogManager::getSingleton().logMessage(
"CellGrid: Building lot base for entity " + std::to_string(entity.id()) +
" at position (" + std::to_string(lotOffset.x) + ", " +
std::to_string(lotOffset.y) + ", " + std::to_string(lotOffset.z) +
") with material: " + materialName);
// Build lot base geometry (box foundation)
Procedural::TriangleBuffer tb;
const float cellSize = 4.0f;
const float baseHeight = 4.0f;
float width = cellSize * lot.width;
float depth = cellSize * lot.depth;
// Create box for lot base
Procedural::BoxGenerator()
.setSizeX(width)
.setSizeY(baseHeight)
.setSizeZ(depth)
.setNumSegY(8)
.setNumSegX(8)
.setNumSegZ(8)
.setEnableNormals(true)
.setPosition(Ogre::Vector3(0.0f, -baseHeight / 2.0f + lot.elevation - 0.01f, 0.0f))
.addToTriangleBuffer(tb);
// Determine which texture rect to use (Lot -> District -> Town)
std::string textureRectToUse;
flecs::entity materialEntityToUse;
// If Lot has its own material, use its texture rect
if (lot.proceduralMaterialEntity.is_alive()) {
textureRectToUse = lot.textureRectName;
materialEntityToUse = lot.proceduralMaterialEntity;
if (!textureRectToUse.empty()) {
Ogre::LogManager::getSingleton().logMessage("CellGrid: Using Lot's own texture rect: " + textureRectToUse);
}
} else {
// Lot uses District/Town material - look up hierarchy for texture rect
flecs::entity parent = entity.parent();
while (parent.is_alive()) {
if (parent.has<DistrictComponent>()) {
auto& district = parent.get<DistrictComponent>();
if (!district.textureRectName.empty()) {
textureRectToUse = district.textureRectName;
materialEntityToUse = district.proceduralMaterialEntity;
Ogre::LogManager::getSingleton().logMessage("CellGrid: Using District texture rect: " + textureRectToUse);
break;
}
}
if (parent.has<TownComponent>()) {
auto& town = parent.get<TownComponent>();
if (!town.textureRectName.empty()) {
textureRectToUse = town.textureRectName;
materialEntityToUse = town.proceduralMaterialEntity;
Ogre::LogManager::getSingleton().logMessage("CellGrid: Using Town texture rect: " + textureRectToUse);
}
break;
}
parent = parent.parent();
}
}
// Apply UV mapping
if (!textureRectToUse.empty()) {
// Find texture entity from the material that was selected
flecs::entity textureEntity = flecs::entity::null();
if (materialEntityToUse.is_alive() &&
materialEntityToUse.has<ProceduralMaterialComponent>()) {
const auto& mat = materialEntityToUse.get<ProceduralMaterialComponent>();
if (mat.diffuseTextureEntity.is_alive()) {
textureEntity = mat.diffuseTextureEntity;
}
}
// Fallback to District/Town texture
if (!textureEntity.is_alive()) {
flecs::entity parent = entity.parent();
while (parent.is_alive()) {
if (parent.has<DistrictComponent>()) {
auto& district = parent.get<DistrictComponent>();
if (district.proceduralMaterialEntity.is_alive() &&
district.proceduralMaterialEntity.has<ProceduralMaterialComponent>()) {
const auto& mat = district.proceduralMaterialEntity.get<ProceduralMaterialComponent>();
if (mat.diffuseTextureEntity.is_alive()) {
textureEntity = mat.diffuseTextureEntity;
break;
}
}
}
if (parent.has<TownComponent>()) {
auto& town = parent.get<TownComponent>();
if (town.proceduralMaterialEntity.is_alive() &&
town.proceduralMaterialEntity.has<ProceduralMaterialComponent>()) {
const auto& mat = town.proceduralMaterialEntity.get<ProceduralMaterialComponent>();
if (mat.diffuseTextureEntity.is_alive()) {
textureEntity = mat.diffuseTextureEntity;
break;
}
}
break;
}
parent = parent.parent();
}
}
if (textureEntity.is_alive() && textureEntity.has<ProceduralTextureComponent>()) {
const auto& texture = textureEntity.get<ProceduralTextureComponent>();
const TextureRectInfo* rect = texture.getNamedRect(textureRectToUse);
if (rect) {
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;
for (auto& v : tb.getVertices()) {
v.mUV.x = uOffset + v.mUV.x * uRange;
v.mUV.y = vOffset + v.mUV.y * vRange;
}
Ogre::LogManager::getSingleton().logMessage("CellGrid: Applied UV mapping for rect '" +
textureRectToUse + "'");
} else {
Ogre::LogManager::getSingleton().logMessage("CellGrid: WARNING - Rect not found: " + textureRectToUse);
}
} else {
Ogre::LogManager::getSingleton().logMessage("CellGrid: WARNING - No texture entity for rect: " + textureRectToUse);
}
} else {
// Default UV mapping for texture atlas (same as original town.cpp)
Ogre::LogManager::getSingleton().logMessage("CellGrid: Using default UV mapping (no texture rect set)");
for (auto& v : tb.getVertices()) {
v.mUV *= 0.08f;
v.mUV.x += 0.41f;
v.mUV.x = Ogre::Math::Clamp(v.mUV.x, 0.4f, 0.5f);
v.mUV.y = Ogre::Math::Clamp(v.mUV.y, 0.0f, 0.1f);
}
}
// Create mesh and entity
std::string meshName = "LotBase_" + std::to_string(entity.id()) + "_" + std::to_string(m_meshCount++);
try {
if (tb.getVertices().size() < 3) {
Ogre::LogManager::getSingleton().logMessage("CellGrid: ERROR - Not enough vertices for lot base mesh");
return;
}
Ogre::MeshPtr mesh = tb.transformToMesh(meshName);
if (mesh.isNull()) {
Ogre::LogManager::getSingleton().logMessage("CellGrid: ERROR - transformToMesh returned null for lot base");
return;
}
if (!materialName.empty() && mesh->getNumSubMeshes() > 0) {
mesh->getSubMesh(0)->setMaterialName(materialName);
Ogre::LogManager::getSingleton().logMessage("CellGrid: Set lot base material to: " + materialName);
} else {
Ogre::LogManager::getSingleton().logMessage("CellGrid: WARNING - Could not set material. materialName='" +
materialName + "' subMeshes=" + std::to_string(mesh->getNumSubMeshes()));
}
Ogre::Entity* lotBaseEntity = m_sceneMgr->createEntity(meshName);
if (!lotBaseEntity) {
Ogre::LogManager::getSingleton().logMessage("CellGrid: ERROR - createEntity returned null for lot base");
return;
}
// Attach to the Lot's SceneNode directly (position already set above)
transform.node->attachObject(lotBaseEntity);
// Debug: Check actual material
if (lotBaseEntity->getNumSubEntities() > 0) {
std::string actualMat = lotBaseEntity->getSubEntity(0)->getMaterialName();
Ogre::LogManager::getSingleton().logMessage("CellGrid: Lot base actual material: " + actualMat);
}
LotBaseData data;
data.meshName = meshName;
data.entity = lotBaseEntity;
m_lotBaseMeshes[entity.id()] = data;
Ogre::LogManager::getSingleton().logMessage(
"CellGrid: Successfully created lot base mesh for entity " +
std::to_string(entity.id()) + " with " +
std::to_string(tb.getVertices().size()) + " vertices");
} catch (const Ogre::Exception& e) {
Ogre::LogManager::getSingleton().logMessage(
"CellGrid: Failed to create lot base mesh: " + e.getDescription());
}
}

View File

@@ -29,6 +29,7 @@ private:
flecs::query<struct CellGridComponent> m_cellGridQuery;
flecs::query<struct TownComponent> m_townQuery;
flecs::query<struct DistrictComponent> m_districtQuery;
flecs::query<struct LotComponent> m_lotQuery;
bool m_initialized = false;
int m_meshCount = 0;
@@ -52,6 +53,9 @@ private:
// Build plaza for district
void buildDistrictPlaza(flecs::entity entity, struct DistrictComponent& district);
// Build lot base geometry
void buildLotBase(flecs::entity entity, struct LotComponent& lot);
// Convert triangle buffer to mesh
Ogre::MeshPtr convertToMesh(const std::string& name, Procedural::TriangleBuffer& tb, const std::string& materialName);
@@ -60,6 +64,7 @@ private:
// Destroy existing mesh
void destroyCellGridMeshes(struct CellGridComponent& grid);
void destroyCellGridMeshes(flecs::entity entity);
// Mesh storage per entity
struct MeshData {
@@ -81,4 +86,11 @@ private:
Ogre::Entity* entity = nullptr;
};
std::unordered_map<uint64_t, PlazaData> m_plazaMeshes;
// Lot base mesh storage per lot entity
struct LotBaseData {
std::string meshName;
Ogre::Entity* entity = nullptr;
};
std::unordered_map<uint64_t, LotBaseData> m_lotBaseMeshes;
};

View File

@@ -1305,6 +1305,8 @@ nlohmann::json SceneSerializer::serializeTown(flecs::entity entity)
json["townName"] = town.townName;
json["materialName"] = town.materialName;
json["proceduralMaterialEntityId"] = town.proceduralMaterialEntityId;
json["textureRectName"] = town.textureRectName;
// Serialize color rects
nlohmann::json colorRectsJson = nlohmann::json::object();
@@ -1355,6 +1357,8 @@ nlohmann::json SceneSerializer::serializeLot(flecs::entity entity)
json["offsetX"] = lot.offsetX;
json["offsetZ"] = lot.offsetZ;
json["templateName"] = lot.templateName;
json["proceduralMaterialEntityId"] = lot.proceduralMaterialEntityId;
json["textureRectName"] = lot.textureRectName;
return json;
}
@@ -1471,6 +1475,21 @@ void SceneSerializer::deserializeTown(flecs::entity entity, const nlohmann::json
town.townName = json.value("townName", "New Town");
town.materialName = json.value("materialName", "");
town.proceduralMaterialEntityId = json.value("proceduralMaterialEntityId", "");
town.textureRectName = json.value("textureRectName", "");
// Resolve material entity reference
if (!town.proceduralMaterialEntityId.empty()) {
try {
uint64_t matId = std::stoull(town.proceduralMaterialEntityId);
flecs::entity matEntity(m_world, matId);
if (matEntity.is_alive() && matEntity.has<ProceduralMaterialComponent>()) {
town.proceduralMaterialEntity = matEntity;
}
} catch (...) {
// Invalid ID format, ignore
}
}
// Deserialize color rects
if (json.contains("colorRects") && json["colorRects"].is_object()) {
@@ -1543,6 +1562,21 @@ void SceneSerializer::deserializeLot(flecs::entity entity, const nlohmann::json&
lot.offsetX = json.value("offsetX", 0.0f);
lot.offsetZ = json.value("offsetZ", 0.0f);
lot.templateName = json.value("templateName", "");
lot.proceduralMaterialEntityId = json.value("proceduralMaterialEntityId", "");
lot.textureRectName = json.value("textureRectName", "");
// Resolve material entity reference
if (!lot.proceduralMaterialEntityId.empty()) {
try {
uint64_t matId = std::stoull(lot.proceduralMaterialEntityId);
flecs::entity matEntity(m_world, matId);
if (matEntity.is_alive() && matEntity.has<ProceduralMaterialComponent>()) {
lot.proceduralMaterialEntity = matEntity;
}
} catch (...) {
// Invalid ID format, ignore
}
}
lot.dirty = true;
entity.set<LotComponent>(lot);

View File

@@ -27,55 +27,54 @@ bool DistrictEditor::renderComponent(flecs::entity entity, DistrictComponent& di
" for entity " + std::to_string(entity.id()));
}
// Plaza material and texture settings
if (district.isPlaza) {
ImGui::Separator();
ImGui::Text("Plaza Material:");
// District material and texture settings
ImGui::Separator();
ImGui::Text("District 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("Use Town Material");
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);
// 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];
if (district.proceduralMaterialEntity == e) {
currentMatIndex = (int)materialEntities.size() - 1;
}
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;
});
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());
}
// Texture rectangle selector
if (district.proceduralMaterialEntity.is_alive()) {
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>()) {
@@ -129,6 +128,11 @@ bool DistrictEditor::renderComponent(flecs::entity entity, DistrictComponent& di
ImGui::TextDisabled("Material has no associated ProceduralTexture");
}
}
// Plaza settings
if (district.isPlaza) {
ImGui::Separator();
ImGui::Text("Plaza uses District Material");
}
// Count lots

View File

@@ -1,5 +1,7 @@
#include "LotEditor.hpp"
#include "../components/CellGrid.hpp"
#include "../components/ProceduralTexture.hpp"
#include "../components/ProceduralMaterial.hpp"
#include <imgui.h>
bool LotEditor::renderComponent(flecs::entity entity, LotComponent& lot)
@@ -39,6 +41,95 @@ bool LotEditor::renderComponent(flecs::entity entity, LotComponent& lot)
modified = true;
}
ImGui::Separator();
ImGui::Text("Lot Material:");
// Material selector
flecs::world world = entity.world();
std::vector<flecs::entity> materialEntities;
std::vector<std::string> materialNames;
materialEntities.push_back(flecs::entity::null());
materialNames.push_back("Use District/Town");
int currentMatIndex = -1;
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 (lot.proceduralMaterialEntity == e) {
currentMatIndex = (int)materialEntities.size() - 1;
}
});
if (currentMatIndex == -1) currentMatIndex = 0;
std::string comboItems;
for (size_t i = 0; i < materialNames.size(); ++i) {
if (i > 0) comboItems += '\0';
comboItems += materialNames[i];
}
comboItems += '\0';
int newIndex = currentMatIndex;
if (ImGui::Combo("Material", &newIndex, comboItems.c_str())) {
if (newIndex == 0) {
lot.proceduralMaterialEntity = flecs::entity::null();
lot.proceduralMaterialEntityId.clear();
} else {
lot.proceduralMaterialEntity = materialEntities[newIndex];
lot.proceduralMaterialEntityId = std::to_string(materialEntities[newIndex].id());
}
modified = true;
}
// Texture rectangle selector
if (lot.proceduralMaterialEntity.is_alive()) {
flecs::entity textureEntity = flecs::entity::null();
if (lot.proceduralMaterialEntity.has<ProceduralMaterialComponent>()) {
const auto& mat = lot.proceduralMaterialEntity.get<ProceduralMaterialComponent>();
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;
rectNames.push_back("None (use full texture)");
int currentRectIndex = -1;
for (const auto& pair : namedRects) {
rectNames.push_back(pair.first);
if (lot.textureRectName == pair.first) {
currentRectIndex = (int)rectNames.size() - 1;
}
}
if (currentRectIndex == -1) currentRectIndex = 0;
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 == 0) {
lot.textureRectName.clear();
} else {
lot.textureRectName = rectNames[newRectIndex];
}
modified = true;
}
}
}
}
// Statistics
int cellCount = 0;
int roomCount = 0;

View File

@@ -1,5 +1,7 @@
#include "TownEditor.hpp"
#include "../components/CellGrid.hpp"
#include "../components/ProceduralMaterial.hpp"
#include "../components/ProceduralTexture.hpp"
#include <imgui.h>
bool TownEditor::renderComponent(flecs::entity entity, TownComponent& town)
@@ -25,6 +27,96 @@ bool TownEditor::renderComponent(flecs::entity entity, TownComponent& town)
});
ImGui::Text("Districts: %d", districtCount);
ImGui::Separator();
ImGui::Text("Town Material:");
// Material selector
flecs::world world = entity.world();
std::vector<flecs::entity> materialEntities;
std::vector<std::string> materialNames;
materialEntities.push_back(flecs::entity::null());
materialNames.push_back("Use Generated (Color Rects)");
int currentMatIndex = -1;
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 (town.proceduralMaterialEntity == e) {
currentMatIndex = (int)materialEntities.size() - 1;
}
});
if (currentMatIndex == -1) currentMatIndex = 0;
std::string comboItems;
for (size_t i = 0; i < materialNames.size(); ++i) {
if (i > 0) comboItems += '\0';
comboItems += materialNames[i];
}
comboItems += '\0';
int newIndex = currentMatIndex;
if (ImGui::Combo("Material", &newIndex, comboItems.c_str())) {
if (newIndex == 0) {
town.proceduralMaterialEntity = flecs::entity::null();
town.proceduralMaterialEntityId.clear();
} else {
town.proceduralMaterialEntity = materialEntities[newIndex];
town.proceduralMaterialEntityId = std::to_string(materialEntities[newIndex].id());
}
modified = true;
town.markMaterialDirty();
}
// Texture rectangle selector
if (town.proceduralMaterialEntity.is_alive()) {
flecs::entity textureEntity = flecs::entity::null();
if (town.proceduralMaterialEntity.has<ProceduralMaterialComponent>()) {
const auto& mat = town.proceduralMaterialEntity.get<ProceduralMaterialComponent>();
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;
rectNames.push_back("None (use full texture)");
int currentRectIndex = -1;
for (const auto& pair : namedRects) {
rectNames.push_back(pair.first);
if (town.textureRectName == pair.first) {
currentRectIndex = (int)rectNames.size() - 1;
}
}
if (currentRectIndex == -1) currentRectIndex = 0;
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 == 0) {
town.textureRectName.clear();
} else {
town.textureRectName = rectNames[newRectIndex];
}
modified = true;
}
}
}
}
// Color rects
if (ImGui::CollapsingHeader("Color Rects")) {
for (auto& [name, rect] : town.colorRects) {