Corners finally match

This commit is contained in:
2026-04-06 01:45:26 +03:00
parent 64b03abb48
commit 0ebba40867
6 changed files with 712 additions and 168 deletions

View File

@@ -119,6 +119,15 @@ struct CellGridComponent {
// Generation script (Lua or custom format)
std::string generationScript;
// Texture rectangle names for different parts (from ProceduralTexture)
std::string floorRectName;
std::string ceilingRectName;
std::string extWallRectName;
std::string intWallRectName;
std::string doorRectName;
std::string windowRectName;
std::string roofRectName;
// Dirty flag - triggers rebuild
bool dirty = true;
unsigned int version = 0;

View File

@@ -95,33 +95,66 @@ void CellGridSystem::update()
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()));
" cells=" + std::to_string(grid.cells.size()) + " cellSize=" + std::to_string(grid.cellSize) +
" cellHeight=" + std::to_string(grid.cellHeight));
// Destroy existing meshes
destroyCellGridMeshes(entity);
// Find parent town for material
// Find material - prefer ProceduralMaterial from Town/District/Lot
std::string materialName = "Ogre/StandardFloor"; // default
flecs::entity townEntity = flecs::entity::null();
flecs::entity materialEntity = flecs::entity::null();
// Look for TownComponent in parent hierarchy
// Look for ProceduralMaterial in parent hierarchy (Lot -> District -> Town)
flecs::entity parent = entity.parent();
while (parent.is_alive()) {
if (parent.has<LotComponent>()) {
auto& lot = parent.get<LotComponent>();
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;
materialEntity = lot.proceduralMaterialEntity;
break;
}
}
}
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.created && !mat.materialName.empty()) {
materialName = mat.materialName;
materialEntity = district.proceduralMaterialEntity;
break;
}
}
}
if (parent.has<TownComponent>()) {
townEntity = parent;
auto& town = parent.get<TownComponent>();
// Prefer ProceduralMaterial over generated material
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;
materialEntity = town.proceduralMaterialEntity;
break;
}
}
// Fall back to generated material
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);
}
Ogre::LogManager::getSingleton().logMessage("CellGrid: Using material '" + materialName +
"' for entity " + std::to_string(entity.id()));
MeshData& meshData = m_entityMeshes[entity.id()];
@@ -136,6 +169,22 @@ void CellGridSystem::buildCellGrid(flecs::entity entity, CellGridComponent& grid
buildWindows(grid, windowTbs);
buildRoofs(entity, grid, roofTb);
// Apply UV mapping for each part (use rect if specified, otherwise default)
Ogre::LogManager::getSingleton().logMessage("CellGrid: UV mapping - floor: " + grid.floorRectName +
" ceiling: " + grid.ceilingRectName + " extWall: " + grid.extWallRectName);
applyUVMappingToBuffer(floorTb, grid.floorRectName, materialEntity);
applyUVMappingToBuffer(ceilingTb, grid.ceilingRectName, materialEntity);
applyUVMappingToBuffer(extWallTb, grid.extWallRectName, materialEntity);
applyUVMappingToBuffer(intWallTb, grid.intWallRectName, materialEntity);
applyUVMappingToBuffer(intWindowsTb, grid.intWallRectName, materialEntity);
for (auto& tb : doorTbs) {
applyUVMappingToBuffer(tb, grid.doorRectName, materialEntity);
}
for (auto& tb : windowTbs) {
applyUVMappingToBuffer(tb, grid.windowRectName, materialEntity);
}
applyUVMappingToBuffer(roofTb, grid.roofRectName, materialEntity);
// Generate unique base name
std::string baseName = "CellGrid_" + std::to_string(entity.id()) + "_" + std::to_string(m_meshCount++);
@@ -200,38 +249,99 @@ void CellGridSystem::buildCellGrid(flecs::entity entity, CellGridComponent& grid
meshData.entities.push_back(entity3d);
}
Ogre::LogManager::getSingleton().logMessage("CellGrid: Built geometry for entity " + std::to_string(entity.id()));
// Doors
int doorIdx = 0;
for (auto& tb : doorTbs) {
if (tb.getVertices().size() >= 3) {
std::string meshName = baseName + "_door" + std::to_string(doorIdx++);
meshData.doorMeshes.push_back(meshName);
auto mesh = convertToMesh(meshName, tb, materialName);
auto entity3d = m_sceneMgr->createEntity(meshName);
transform.node->attachObject(entity3d);
meshData.entities.push_back(entity3d);
}
}
// Windows
int windowIdx = 0;
for (auto& tb : windowTbs) {
if (tb.getVertices().size() >= 3) {
std::string meshName = baseName + "_window" + std::to_string(windowIdx++);
meshData.windowMeshes.push_back(meshName);
auto mesh = convertToMesh(meshName, tb, materialName);
auto entity3d = m_sceneMgr->createEntity(meshName);
transform.node->attachObject(entity3d);
meshData.entities.push_back(entity3d);
}
}
Ogre::LogManager::getSingleton().logMessage("CellGrid: Built geometry for entity " + std::to_string(entity.id()) +
" floor=" + std::to_string(floorTb.getVertices().size()) +
" ceiling=" + std::to_string(ceilingTb.getVertices().size()) +
" extWall=" + std::to_string(extWallTb.getVertices().size()) +
" intWall=" + std::to_string(intWallTb.getVertices().size()) +
" intWindows=" + std::to_string(intWindowsTb.getVertices().size()) +
" doors=" + std::to_string(doorTbs.size()) +
" windows=" + std::to_string(windowTbs.size()) +
" roof=" + std::to_string(roofTb.getVertices().size()));
}
void CellGridSystem::buildFloorsAndCeilings(const CellGridComponent& grid,
Procedural::TriangleBuffer& floorTb,
// BitSet structure matching original town.cpp
struct BitSet {
uint64_t bit;
float sizeX;
float sizeY;
Ogre::Vector3 normal;
Ogre::Vector3 offset;
Procedural::TriangleBuffer* tb;
};
// Plane generator matching original town.cpp
// The sizeX/sizeY values from original BitSet are the CORRECT full sizes for PlaneGenerator
// - sizeX = wall height (vertical dimension of the plane)
// - sizeY = wall half-width (horizontal dimension of the plane, gets doubled by PlaneGenerator internally)
static void genPlane(float sizeX, float sizeY, const Ogre::Vector3& normal,
const Ogre::Vector3& offset, Procedural::TriangleBuffer& tb,
const Ogre::Vector3& origin) {
// nsegY is based on the height (sizeX in the original, which is vertical)
int nsegY = (sizeX < 1.0f) ? 1 : std::max(1, (int)(sizeX / 2.0f));
Procedural::PlaneGenerator()
.setNormal(normal)
.setSizeX(sizeX) // This is the height of the wall
.setSizeY(sizeY) // This is already correct (half-width * 2 = full width)
.setNumSegX(1)
.setNumSegY(nsegY)
.setEnableNormals(true)
.setPosition(origin + offset)
.addToTriangleBuffer(tb);
}
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;
// Original uses 2.0f as half-size for 4.0f cell (cellSize/2)
// Use same hScale as walls for consistency
const float hScale = grid.cellSize / 2.0f;
// Scale factor for original 4.0f cell height reference
const float vScale = grid.cellHeight / 4.0f;
const float floorY = 0.1f * vScale;
const float ceilingY = grid.cellHeight - 0.3f * vScale;
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);
uint64_t flags = cell.flags;
// Floor: original sizeX=2.0f, sizeY=2.0f (half-sizes), scaled by hScale
if (flags & CellFlags::Floor) {
genPlane(2.0f * hScale, 2.0f * hScale, Ogre::Vector3::UNIT_Y,
Ogre::Vector3(0, floorY, 0), floorTb, origin);
}
// 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);
// Ceiling: original sizeX=2.0f, sizeY=2.0f (half-sizes), scaled by hScale
if (flags & CellFlags::Ceiling) {
genPlane(2.0f * hScale, 2.0f * hScale, Ogre::Vector3::NEGATIVE_UNIT_Y,
Ogre::Vector3(0, ceilingY, 0), ceilingTb, origin);
}
}
}
@@ -241,168 +351,350 @@ void CellGridSystem::buildWalls(const CellGridComponent& grid,
Procedural::TriangleBuffer& intWallTb,
Procedural::TriangleBuffer& intWindowsTb)
{
const float cellSize = grid.cellSize;
// Original tables designed for cellSize=2.0 (2x2 base, 4.0 height)
// In original: sizeY=2.0f represents FULL cell width (not half!)
// Scale factor: cellSize / 2.0f (not 4.0f!)
const float hScale = grid.cellSize / 2.0f; // Horizontal scale
const float halfCell = 1.0f * hScale; // half of cellSize
const float cornerWidth = 0.2f; // Fixed corner width, don't scale
const float solidExtHeight = grid.cellHeight;
const float solidExtOffset = 0.0f;
const float solidIntOffset = 0.1f;
const float solidIntOffset = 0.1f * hScale;
const float solidIntHeight = grid.cellHeight - 0.3f - solidIntOffset;
// Tables matching original town.cpp
// Original cellSize=2.0: sizeY=2.0f (full width), offset=+/-(1.0 + 0.2)=+/-1.1?
// Wait, original offset is -2.2f which is -(2.0 + 0.2)
// So 2.0f in offset IS half-cell for original (cellSize=4.0)
// Let me re-read: original uses 4.0f cellSize, 2.0f half-cell
// But you said tables are for 2x2... so 2.0f in table = full width of 2.0
// Then for cellSize=4.0, wall width should be 4.0
// Scale = cellSize / 2.0f = 4.0 / 2.0 = 2.0
// sizeY = 2.0f * 2.0 = 4.0 ✓
std::vector<BitSet> bits_solid = {
// External walls: sizeX=height, sizeY=cellSize (full width = 2.0f * hScale)
{ CellFlags::WallXNeg, solidExtHeight, 2.0f * hScale,
Ogre::Vector3::NEGATIVE_UNIT_X,
{ -1.0f * hScale - cornerWidth, solidExtHeight / 2.0f + solidExtOffset, 0 }, &extWallTb },
{ CellFlags::WallXPos, solidExtHeight, 2.0f * hScale,
Ogre::Vector3::UNIT_X,
{ 1.0f * hScale + cornerWidth, solidExtHeight / 2.0f + solidExtOffset, 0 }, &extWallTb },
{ CellFlags::WallZPos, solidExtHeight, 2.0f * hScale,
Ogre::Vector3::UNIT_Z,
{ 0, solidExtHeight / 2.0f + solidExtOffset, 1.0f * hScale + cornerWidth }, &extWallTb },
{ CellFlags::WallZNeg, solidExtHeight, 2.0f * hScale,
Ogre::Vector3::NEGATIVE_UNIT_Z,
{ 0, solidExtHeight / 2.0f + solidExtOffset, -1.0f * hScale - cornerWidth }, &extWallTb },
// Internal walls: sizeY=2.0f-0.2f (in original units), offset=+/-(1.0f - 0.1f)
{ CellFlags::IntWallXNeg, solidIntHeight, (2.0f - 0.2f) * hScale,
Ogre::Vector3::UNIT_X,
{ -1.0f * hScale + cornerWidth/2, solidIntHeight / 2.0f + solidIntOffset, 0 }, &intWallTb },
{ CellFlags::IntWallXPos, solidIntHeight, (2.0f - 0.2f) * hScale,
Ogre::Vector3::NEGATIVE_UNIT_X,
{ 1.0f * hScale - cornerWidth/2, solidIntHeight / 2.0f + solidIntOffset, 0 }, &intWallTb },
{ CellFlags::IntWallZPos, solidIntHeight, (2.0f - 0.2f) * hScale,
Ogre::Vector3::NEGATIVE_UNIT_Z,
{ 0, solidIntHeight / 2.0f + solidIntOffset, 1.0f * hScale - cornerWidth/2 }, &intWallTb },
{ CellFlags::IntWallZNeg, solidIntHeight, (2.0f - 0.2f) * hScale,
Ogre::Vector3::UNIT_Z,
{ 0, solidIntHeight / 2.0f + solidIntOffset, -1.0f * hScale + cornerWidth/2 }, &intWallTb },
};
for (const auto& cell : grid.cells) {
Ogre::Vector3 origin = grid.cellToWorld(cell.x, cell.y, cell.z);
uint64_t flags = cell.flags;
// 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);
for (const auto& bit : bits_solid) {
if ((flags & bit.bit) == bit.bit) {
genPlane(bit.sizeX, bit.sizeY, bit.normal, bit.offset, *bit.tb, origin);
}
}
}
}
void CellGridSystem::buildCorners(const CellGridComponent& grid, Procedural::TriangleBuffer& extWallTb)
{
// Build external wall corners where walls meet
const float cellSize = grid.cellSize;
// Build external wall corners where walls meet using PlaneGenerator
// Original tables for cellSize=2.0 (2x2 base), so halfCell=1.0
const float hScale = grid.cellSize / 2.0f;
const float halfCell = 1.0f * hScale; // = cellSize / 2
const float cornerWidth = 0.2f; // Fixed corner width
const float solidExtHeight = grid.cellHeight;
const float solidExtOffset = 0.0f;
// Tables for corners matching original town.cpp bits_ext_corners
// Format: bit, sizeX (height), sizeY (corner width), normal, offset
// Corner width is FIXED (0.2f) - don't scale to avoid thin polygons
// Each corner plane is shifted by cornerWidth/2 toward the corner to close the gap
const float cornerOffset = cornerWidth / 2.0f;
std::vector<BitSet> bits_ext_corners = {
// X- with Z+ corners
// First corner plane: normal along -X, at X=-(halfCell+cornerWidth), Z=+halfCell+cornerOffset
{ CellFlags::WallXNeg | CellFlags::WallZPos, solidExtHeight, cornerWidth,
Ogre::Vector3::NEGATIVE_UNIT_X,
{ -halfCell - cornerWidth, solidExtHeight / 2.0f + solidExtOffset, halfCell + cornerOffset }, &extWallTb },
// Second corner plane: normal along +Z, at X=-halfCell-cornerOffset, Z=+(halfCell+cornerWidth)
{ CellFlags::WallXNeg | CellFlags::WallZPos, solidExtHeight, cornerWidth,
Ogre::Vector3::UNIT_Z,
{ -halfCell - cornerOffset, solidExtHeight / 2.0f + solidExtOffset, halfCell + cornerWidth }, &extWallTb },
// X- with Z- corners
{ CellFlags::WallXNeg | CellFlags::WallZNeg, solidExtHeight, cornerWidth,
Ogre::Vector3::NEGATIVE_UNIT_X,
{ -halfCell - cornerWidth, solidExtHeight / 2.0f + solidExtOffset, -halfCell - cornerOffset }, &extWallTb },
{ CellFlags::WallXNeg | CellFlags::WallZNeg, solidExtHeight, cornerWidth,
Ogre::Vector3::NEGATIVE_UNIT_Z,
{ -halfCell - cornerOffset, solidExtHeight / 2.0f + solidExtOffset, -halfCell - cornerWidth }, &extWallTb },
// X+ with Z+ corners
{ CellFlags::WallXPos | CellFlags::WallZPos, solidExtHeight, cornerWidth,
Ogre::Vector3::UNIT_X,
{ halfCell + cornerWidth, solidExtHeight / 2.0f + solidExtOffset, halfCell + cornerOffset }, &extWallTb },
{ CellFlags::WallXPos | CellFlags::WallZPos, solidExtHeight, cornerWidth,
Ogre::Vector3::UNIT_Z,
{ halfCell + cornerOffset, solidExtHeight / 2.0f + solidExtOffset, halfCell + cornerWidth }, &extWallTb },
// X+ with Z- corners
{ CellFlags::WallXPos | CellFlags::WallZNeg, solidExtHeight, cornerWidth,
Ogre::Vector3::UNIT_X,
{ halfCell + cornerWidth, solidExtHeight / 2.0f + solidExtOffset, -halfCell - cornerOffset }, &extWallTb },
{ CellFlags::WallXPos | CellFlags::WallZNeg, solidExtHeight, cornerWidth,
Ogre::Vector3::NEGATIVE_UNIT_Z,
{ halfCell + cornerOffset, solidExtHeight / 2.0f + solidExtOffset, -halfCell - cornerWidth }, &extWallTb },
};
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);
for (const auto& bit : bits_ext_corners) {
// Check if both wall flags are set (corner needs both adjacent walls)
if ((wallFlags & bit.bit) == bit.bit) {
genPlane(bit.sizeX, bit.sizeY, bit.normal, bit.offset, *bit.tb, origin);
}
}
}
}
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
const float cellSize = grid.cellSize;
const float doorHeight = grid.cellHeight * 0.7f;
const float doorWidth = cellSize * 0.6f;
const float frameThickness = 0.1f;
for (const auto& cell : grid.cells) {
Ogre::Vector3 origin = grid.cellToWorld(cell.x, cell.y, cell.z);
// External doors
if (cell.hasFlag(CellFlags::DoorXNeg) || cell.hasFlag(CellFlags::DoorXPos) ||
cell.hasFlag(CellFlags::DoorZNeg) || cell.hasFlag(CellFlags::DoorZPos)) {
Procedural::TriangleBuffer tb;
// Door frame (simplified as thin boxes around the opening)
if (cell.hasFlag(CellFlags::DoorXNeg)) {
// Top frame
Procedural::BoxGenerator box;
box.setSizeX(0.15f);
box.setSizeY(grid.cellHeight - doorHeight);
box.setSizeZ(doorWidth + 0.2f);
box.setPosition(Ogre::Vector3(origin.x - cellSize/2.0f - 0.075f,
origin.y + doorHeight + (grid.cellHeight - doorHeight)/2.0f, origin.z));
box.addToTriangleBuffer(tb);
}
if (cell.hasFlag(CellFlags::DoorXPos)) {
Procedural::BoxGenerator box;
box.setSizeX(0.15f);
box.setSizeY(grid.cellHeight - doorHeight);
box.setSizeZ(doorWidth + 0.2f);
box.setPosition(Ogre::Vector3(origin.x + cellSize/2.0f + 0.075f,
origin.y + doorHeight + (grid.cellHeight - doorHeight)/2.0f, origin.z));
box.addToTriangleBuffer(tb);
}
if (cell.hasFlag(CellFlags::DoorZNeg)) {
Procedural::BoxGenerator box;
box.setSizeX(doorWidth + 0.2f);
box.setSizeY(grid.cellHeight - doorHeight);
box.setSizeZ(0.15f);
box.setPosition(Ogre::Vector3(origin.x,
origin.y + doorHeight + (grid.cellHeight - doorHeight)/2.0f,
origin.z - cellSize/2.0f - 0.075f));
box.addToTriangleBuffer(tb);
}
if (cell.hasFlag(CellFlags::DoorZPos)) {
Procedural::BoxGenerator box;
box.setSizeX(doorWidth + 0.2f);
box.setSizeY(grid.cellHeight - doorHeight);
box.setSizeZ(0.15f);
box.setPosition(Ogre::Vector3(origin.x,
origin.y + doorHeight + (grid.cellHeight - doorHeight)/2.0f,
origin.z + cellSize/2.0f + 0.075f));
box.addToTriangleBuffer(tb);
}
if (tb.getVertices().size() > 0) {
doorTbs.push_back(std::move(tb));
}
}
// Internal doors (similar but thinner)
if (cell.hasFlag(CellFlags::IntDoorXNeg) || cell.hasFlag(CellFlags::IntDoorXPos) ||
cell.hasFlag(CellFlags::IntDoorZNeg) || cell.hasFlag(CellFlags::IntDoorZPos)) {
Procedural::TriangleBuffer tb;
if (cell.hasFlag(CellFlags::IntDoorXNeg)) {
Procedural::BoxGenerator box;
box.setSizeX(0.08f);
box.setSizeY(grid.cellHeight - doorHeight);
box.setSizeZ(doorWidth + 0.2f);
box.setPosition(Ogre::Vector3(origin.x - cellSize/2.0f + 0.04f,
origin.y + doorHeight + (grid.cellHeight - doorHeight)/2.0f, origin.z));
box.addToTriangleBuffer(tb);
}
if (cell.hasFlag(CellFlags::IntDoorXPos)) {
Procedural::BoxGenerator box;
box.setSizeX(0.08f);
box.setSizeY(grid.cellHeight - doorHeight);
box.setSizeZ(doorWidth + 0.2f);
box.setPosition(Ogre::Vector3(origin.x + cellSize/2.0f - 0.04f,
origin.y + doorHeight + (grid.cellHeight - doorHeight)/2.0f, origin.z));
box.addToTriangleBuffer(tb);
}
if (cell.hasFlag(CellFlags::IntDoorZNeg)) {
Procedural::BoxGenerator box;
box.setSizeX(doorWidth + 0.2f);
box.setSizeY(grid.cellHeight - doorHeight);
box.setSizeZ(0.08f);
box.setPosition(Ogre::Vector3(origin.x,
origin.y + doorHeight + (grid.cellHeight - doorHeight)/2.0f,
origin.z - cellSize/2.0f + 0.04f));
box.addToTriangleBuffer(tb);
}
if (cell.hasFlag(CellFlags::IntDoorZPos)) {
Procedural::BoxGenerator box;
box.setSizeX(doorWidth + 0.2f);
box.setSizeY(grid.cellHeight - doorHeight);
box.setSizeZ(0.08f);
box.setPosition(Ogre::Vector3(origin.x,
origin.y + doorHeight + (grid.cellHeight - doorHeight)/2.0f,
origin.z + cellSize/2.0f - 0.04f));
box.addToTriangleBuffer(tb);
}
if (tb.getVertices().size() > 0) {
doorTbs.push_back(std::move(tb));
}
}
}
}
void CellGridSystem::buildWindows(const CellGridComponent& grid, std::vector<Procedural::TriangleBuffer>& windowTbs)
{
// Windows are cutouts in walls - similar to doors
const float cellSize = grid.cellSize;
const float windowHeight = grid.cellHeight * 0.4f;
const float windowWidth = cellSize * 0.5f;
const float windowY = grid.cellHeight * 0.4f;
for (const auto& cell : grid.cells) {
Ogre::Vector3 origin = grid.cellToWorld(cell.x, cell.y, cell.z);
// External windows
if (cell.hasFlag(CellFlags::WindowXNeg) || cell.hasFlag(CellFlags::WindowXPos) ||
cell.hasFlag(CellFlags::WindowZNeg) || cell.hasFlag(CellFlags::WindowZPos)) {
Procedural::TriangleBuffer tb;
// Window frame (surrounding the window opening)
if (cell.hasFlag(CellFlags::WindowXNeg)) {
// Frame around the window
Procedural::BoxGenerator box;
box.setSizeX(0.12f);
box.setSizeY(windowHeight + 0.2f);
box.setSizeZ(windowWidth + 0.2f);
box.setPosition(Ogre::Vector3(origin.x - cellSize/2.0f - 0.06f,
origin.y + windowY, origin.z));
box.addToTriangleBuffer(tb);
}
if (cell.hasFlag(CellFlags::WindowXPos)) {
Procedural::BoxGenerator box;
box.setSizeX(0.12f);
box.setSizeY(windowHeight + 0.2f);
box.setSizeZ(windowWidth + 0.2f);
box.setPosition(Ogre::Vector3(origin.x + cellSize/2.0f + 0.06f,
origin.y + windowY, origin.z));
box.addToTriangleBuffer(tb);
}
if (cell.hasFlag(CellFlags::WindowZNeg)) {
Procedural::BoxGenerator box;
box.setSizeX(windowWidth + 0.2f);
box.setSizeY(windowHeight + 0.2f);
box.setSizeZ(0.12f);
box.setPosition(Ogre::Vector3(origin.x, origin.y + windowY,
origin.z - cellSize/2.0f - 0.06f));
box.addToTriangleBuffer(tb);
}
if (cell.hasFlag(CellFlags::WindowZPos)) {
Procedural::BoxGenerator box;
box.setSizeX(windowWidth + 0.2f);
box.setSizeY(windowHeight + 0.2f);
box.setSizeZ(0.12f);
box.setPosition(Ogre::Vector3(origin.x, origin.y + windowY,
origin.z + cellSize/2.0f + 0.06f));
box.addToTriangleBuffer(tb);
}
if (tb.getVertices().size() > 0) {
windowTbs.push_back(std::move(tb));
}
}
// Internal windows (thinner frames)
if (cell.hasFlag(CellFlags::IntWindowXNeg) || cell.hasFlag(CellFlags::IntWindowXPos) ||
cell.hasFlag(CellFlags::IntWindowZNeg) || cell.hasFlag(CellFlags::IntWindowZPos)) {
Procedural::TriangleBuffer tb;
if (cell.hasFlag(CellFlags::IntWindowXNeg)) {
Procedural::BoxGenerator box;
box.setSizeX(0.06f);
box.setSizeY(windowHeight + 0.15f);
box.setSizeZ(windowWidth + 0.15f);
box.setPosition(Ogre::Vector3(origin.x - cellSize/2.0f + 0.03f,
origin.y + windowY, origin.z));
box.addToTriangleBuffer(tb);
}
if (cell.hasFlag(CellFlags::IntWindowXPos)) {
Procedural::BoxGenerator box;
box.setSizeX(0.06f);
box.setSizeY(windowHeight + 0.15f);
box.setSizeZ(windowWidth + 0.15f);
box.setPosition(Ogre::Vector3(origin.x + cellSize/2.0f - 0.03f,
origin.y + windowY, origin.z));
box.addToTriangleBuffer(tb);
}
if (cell.hasFlag(CellFlags::IntWindowZNeg)) {
Procedural::BoxGenerator box;
box.setSizeX(windowWidth + 0.15f);
box.setSizeY(windowHeight + 0.15f);
box.setSizeZ(0.06f);
box.setPosition(Ogre::Vector3(origin.x, origin.y + windowY,
origin.z - cellSize/2.0f + 0.03f));
box.addToTriangleBuffer(tb);
}
if (cell.hasFlag(CellFlags::IntWindowZPos)) {
Procedural::BoxGenerator box;
box.setSizeX(windowWidth + 0.15f);
box.setSizeY(windowHeight + 0.15f);
box.setSizeZ(0.06f);
box.setPosition(Ogre::Vector3(origin.x, origin.y + windowY,
origin.z + cellSize/2.0f - 0.03f));
box.addToTriangleBuffer(tb);
}
if (tb.getVertices().size() > 0) {
windowTbs.push_back(std::move(tb));
}
}
}
}
void CellGridSystem::buildRoofs(flecs::entity lotEntity, const CellGridComponent& grid, Procedural::TriangleBuffer& roofTb)
@@ -471,8 +763,11 @@ Ogre::MeshPtr CellGridSystem::convertToMesh(const std::string& name, Procedural:
{
try {
Ogre::MeshPtr mesh = tb.transformToMesh(name);
Ogre::LogManager::getSingleton().logMessage("CellGrid: convertToMesh '" + name + "' vertices=" +
std::to_string(tb.getVertices().size()) + " material=" + materialName);
if (!materialName.empty() && mesh->getNumSubMeshes() > 0) {
mesh->getSubMesh(0)->setMaterialName(materialName);
Ogre::LogManager::getSingleton().logMessage("CellGrid: Set material on mesh '" + name + "' to '" + materialName + "'");
}
return mesh;
} catch (const Ogre::Exception& e) {
@@ -518,6 +813,12 @@ void CellGridSystem::destroyCellGridMeshes(flecs::entity entity)
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);
}
for (const auto& name : it->second.doorMeshes) {
removeMesh(name);
@@ -1030,3 +1331,86 @@ void CellGridSystem::buildLotBase(flecs::entity entity, LotComponent& lot)
"CellGrid: Failed to create lot base mesh: " + e.getDescription());
}
}
void CellGridSystem::applyUVMappingToBuffer(Procedural::TriangleBuffer& tb, const std::string& rectName, flecs::entity materialEntity)
{
if (tb.getVertices().empty()) return;
// If no rect specified, use default UV mapping
if (rectName.empty()) {
// Default mapping - scale down UVs to a small atlas region
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);
}
return;
}
Ogre::LogManager::getSingleton().logMessage("CellGrid: Looking for texture rect: " + rectName);
// Find texture entity from material
flecs::entity textureEntity = flecs::entity::null();
if (materialEntity.is_alive() && materialEntity.has<ProceduralMaterialComponent>()) {
const auto& mat = materialEntity.get<ProceduralMaterialComponent>();
if (mat.diffuseTextureEntity.is_alive()) {
textureEntity = mat.diffuseTextureEntity;
}
}
if (!textureEntity.is_alive()) {
Ogre::LogManager::getSingleton().logMessage("CellGrid: WARNING - No texture entity for rect: " + rectName);
// Fall back to default UV mapping
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);
}
return;
}
if (!textureEntity.has<ProceduralTextureComponent>()) {
Ogre::LogManager::getSingleton().logMessage("CellGrid: WARNING - Texture entity has no ProceduralTextureComponent for rect: " + rectName);
// Fall back to default UV mapping
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);
}
return;
}
const auto& texture = textureEntity.get<ProceduralTextureComponent>();
const TextureRectInfo* rect = texture.getNamedRect(rectName);
if (!rect) {
Ogre::LogManager::getSingleton().logMessage("CellGrid: WARNING - Rect not found: " + rectName);
// Fall back to default UV mapping
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);
}
return;
}
Ogre::LogManager::getSingleton().logMessage("CellGrid: Applying rect " + rectName +
" u=" + std::to_string(rect->u1) + "-" + std::to_string(rect->u2) +
" v=" + std::to_string(rect->v1) + "-" + std::to_string(rect->v2));
// Apply texture rectangle UV mapping
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;
}
}

View File

@@ -60,7 +60,10 @@ private:
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);
void applyUVMapping(Procedural::TriangleBuffer& tb, flecs::entity entity, const std::string& rectName);
// Apply UV mapping using texture coordinates directly
void applyUVMappingToBuffer(Procedural::TriangleBuffer& tb, const std::string& rectName, flecs::entity materialEntity);
// Destroy existing mesh
void destroyCellGridMeshes(struct CellGridComponent& grid);

View File

@@ -1269,6 +1269,15 @@ nlohmann::json SceneSerializer::serializeCellGrid(flecs::entity entity)
json["cellHeight"] = grid.cellHeight;
json["generationScript"] = grid.generationScript;
// Serialize texture rectangles
json["floorRectName"] = grid.floorRectName;
json["ceilingRectName"] = grid.ceilingRectName;
json["extWallRectName"] = grid.extWallRectName;
json["intWallRectName"] = grid.intWallRectName;
json["doorRectName"] = grid.doorRectName;
json["windowRectName"] = grid.windowRectName;
json["roofRectName"] = grid.roofRectName;
// Serialize cells
nlohmann::json cellsJson = nlohmann::json::array();
for (const auto& cell : grid.cells) {
@@ -1433,6 +1442,15 @@ void SceneSerializer::deserializeCellGrid(flecs::entity entity, const nlohmann::
grid.cellHeight = json.value("cellHeight", 4.0f);
grid.generationScript = json.value("generationScript", "");
// Deserialize texture rectangles
grid.floorRectName = json.value("floorRectName", "");
grid.ceilingRectName = json.value("ceilingRectName", "");
grid.extWallRectName = json.value("extWallRectName", "");
grid.intWallRectName = json.value("intWallRectName", "");
grid.doorRectName = json.value("doorRectName", "");
grid.windowRectName = json.value("windowRectName", "");
grid.roofRectName = json.value("roofRectName", "");
// Deserialize cells
if (json.contains("cells") && json["cells"].is_array()) {
for (const auto& cellJson : json["cells"]) {

View File

@@ -1,5 +1,7 @@
#include "CellGridEditor.hpp"
#include "../components/CellGrid.hpp"
#include "../components/ProceduralMaterial.hpp"
#include "../components/ProceduralTexture.hpp"
#include <OgreStringConverter.h>
bool CellGridEditor::renderComponent(flecs::entity entity, CellGridComponent& grid)
@@ -42,6 +44,11 @@ bool CellGridEditor::renderComponent(flecs::entity entity, CellGridComponent& gr
renderFurnitureEditor(grid);
}
// Texture rectangle editor
if (ImGui::CollapsingHeader("Texture Rectangles")) {
renderTextureRectEditor(entity, grid);
}
// Script editor
if (ImGui::CollapsingHeader("Generation Script")) {
renderScriptEditor(grid);
@@ -91,6 +98,7 @@ void CellGridEditor::renderCellEditor(CellGridComponent& grid)
auto flags = CellGridComponent::getAllFlags();
for (const auto& [flag, name] : flags) {
ImGui::PushID((int)flag);
bool hasFlag = cell->hasFlag(flag);
if (ImGui::Checkbox(name, &hasFlag)) {
if (hasFlag) {
@@ -100,6 +108,7 @@ void CellGridEditor::renderCellEditor(CellGridComponent& grid)
}
grid.markDirty();
}
ImGui::PopID();
}
if (ImGui::Button("Delete Cell")) {
@@ -223,3 +232,123 @@ void CellGridEditor::renderScriptEditor(CellGridComponent& grid)
grid.markDirty();
}
}
void CellGridEditor::renderTextureRectEditor(flecs::entity entity, CellGridComponent& grid)
{
// Find available texture rectangles from parent material
flecs::entity textureEntity = flecs::entity::null();
flecs::entity parent = entity.parent();
while (parent.is_alive()) {
if (parent.has<LotComponent>()) {
auto& lot = parent.get<LotComponent>();
if (lot.proceduralMaterialEntity.is_alive() &&
lot.proceduralMaterialEntity.has<ProceduralMaterialComponent>()) {
const auto& mat = lot.proceduralMaterialEntity.get<ProceduralMaterialComponent>();
if (mat.diffuseTextureEntity.is_alive()) {
textureEntity = mat.diffuseTextureEntity;
break;
}
}
}
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>()) {
ImGui::TextDisabled("No ProceduralMaterial with texture found in parent hierarchy");
return;
}
const auto& texture = textureEntity.get<ProceduralTextureComponent>();
const auto& namedRects = texture.getAllNamedRects();
if (namedRects.empty()) {
ImGui::TextDisabled("No named rectangles defined in texture");
return;
}
// Build list of rect names
std::vector<std::string> rectNames;
rectNames.push_back("(default)");
for (const auto& pair : namedRects) {
rectNames.push_back(pair.first);
}
auto renderRectCombo = [&](const char* label, std::string& currentValue) {
int currentIndex = 0;
for (size_t i = 1; i < rectNames.size(); ++i) {
if (currentValue == rectNames[i]) {
currentIndex = (int)i;
break;
}
}
std::string comboItems;
for (size_t i = 0; i < rectNames.size(); ++i) {
if (i > 0) comboItems += '\0';
comboItems += rectNames[i];
}
comboItems += '\0';
int newIndex = currentIndex;
if (ImGui::Combo(label, &newIndex, comboItems.c_str())) {
if (newIndex == 0) {
currentValue.clear();
} else {
currentValue = rectNames[newIndex];
}
return true;
}
return false;
};
ImGui::Text("Select texture rectangles for each part:");
ImGui::Indent();
if (renderRectCombo("Floor", grid.floorRectName)) {
grid.markDirty();
}
if (renderRectCombo("Ceiling", grid.ceilingRectName)) {
grid.markDirty();
}
if (renderRectCombo("External Walls", grid.extWallRectName)) {
grid.markDirty();
}
if (renderRectCombo("Internal Walls", grid.intWallRectName)) {
grid.markDirty();
}
if (renderRectCombo("Doors", grid.doorRectName)) {
grid.markDirty();
}
if (renderRectCombo("Windows", grid.windowRectName)) {
grid.markDirty();
}
if (renderRectCombo("Roof", grid.roofRectName)) {
grid.markDirty();
}
ImGui::Unindent();
}

View File

@@ -13,6 +13,7 @@ private:
void renderCellEditor(CellGridComponent& grid);
void renderFurnitureEditor(CellGridComponent& grid);
void renderScriptEditor(CellGridComponent& grid);
void renderTextureRectEditor(flecs::entity entity, CellGridComponent& grid);
// State for UI
int selectedCellX = 0, selectedCellY = 0, selectedCellZ = 0;