Material fixes, playing with navmesh

This commit is contained in:
2026-04-24 20:29:54 +03:00
parent a0d2561587
commit 1d2c330481
5 changed files with 996 additions and 607 deletions

View File

@@ -18,10 +18,9 @@ static bool isNavMeshGeometry(const Ogre::String &meshName)
Ogre::String lower = meshName;
std::transform(lower.begin(), lower.end(), lower.begin(),
[](unsigned char c) { return std::tolower(c); });
static const char *exclude[] = {
"wall", "ceiling", "roof", "door",
"window", "frame", "furniture"
};
static const char *exclude[] = { "wall", "ceiling", "roof",
"door", "window", "frame",
"furniture" };
for (const char *pat : exclude) {
if (lower.find(pat) != Ogre::String::npos)
return false;
@@ -80,24 +79,21 @@ struct FastLZCompressor : public dtTileCacheCompressor {
{
return (int)(bufferSize * 1.05f);
}
dtStatus compress(const unsigned char *buffer,
const int bufferSize,
dtStatus compress(const unsigned char *buffer, const int bufferSize,
unsigned char *compressed,
const int maxCompressedSize,
int *compressedSize) override
{
*compressedSize = fastlz_compress(
(const void *)buffer, bufferSize, compressed);
*compressedSize = fastlz_compress((const void *)buffer,
bufferSize, compressed);
return DT_SUCCESS;
}
dtStatus decompress(const unsigned char *compressed,
const int compressedSize,
unsigned char *buffer,
const int maxBufferSize,
int *bufferSize) override
const int compressedSize, unsigned char *buffer,
const int maxBufferSize, int *bufferSize) override
{
*bufferSize = fastlz_decompress(
compressed, compressedSize, buffer, maxBufferSize);
*bufferSize = fastlz_decompress(compressed, compressedSize,
buffer, maxBufferSize);
return *bufferSize < 0 ? DT_FAILURE : DT_SUCCESS;
}
};
@@ -155,14 +151,13 @@ struct RasterizationContext {
// Ogre mesh extraction
// ------------------------------------------------------------------
static void extractEntityMesh(Ogre::Entity *ent,
std::vector<float> &verts,
static void extractEntityMesh(Ogre::Entity *ent, std::vector<float> &verts,
std::vector<int> &tris)
{
Ogre::MeshPtr mesh = ent->getMesh();
Ogre::SceneNode *node = ent->getParentSceneNode();
Ogre::Matrix4 transform = node ? node->_getFullTransform()
: Ogre::Matrix4::IDENTITY;
Ogre::Matrix4 transform = node ? node->_getFullTransform() :
Ogre::Matrix4::IDENTITY;
bool addedShared = false;
size_t sharedOffset = 0;
@@ -171,9 +166,9 @@ static void extractEntityMesh(Ogre::Entity *ent,
for (unsigned short i = 0; i < mesh->getNumSubMeshes(); ++i) {
Ogre::SubMesh *sub = mesh->getSubMesh(i);
Ogre::VertexData *vd = sub->useSharedVertices
? mesh->sharedVertexData
: sub->vertexData;
Ogre::VertexData *vd = sub->useSharedVertices ?
mesh->sharedVertexData :
sub->vertexData;
if ((!sub->useSharedVertices) ||
(sub->useSharedVertices && !addedShared)) {
@@ -183,26 +178,25 @@ static void extractEntityMesh(Ogre::Entity *ent,
}
const Ogre::VertexElement *posElem =
vd->vertexDeclaration
->findElementBySemantic(
Ogre::VES_POSITION);
vd->vertexDeclaration->findElementBySemantic(
Ogre::VES_POSITION);
if (!posElem)
continue;
Ogre::HardwareVertexBufferSharedPtr vbuf =
vd->vertexBufferBinding->getBuffer(
posElem->getSource());
vd->vertexBufferBinding->getBuffer(
posElem->getSource());
unsigned char *vptr = static_cast<unsigned char *>(
vbuf->lock(Ogre::HardwareBuffer::HBL_READ_ONLY));
unsigned char *vptr =
static_cast<unsigned char *>(vbuf->lock(
Ogre::HardwareBuffer::HBL_READ_ONLY));
for (size_t v = 0; v < vd->vertexCount;
++v, vptr += vbuf->getVertexSize()) {
float *pReal;
posElem->baseVertexPointerToElement(
vptr, &pReal);
Ogre::Vector3 pt(pReal[0], pReal[1],
pReal[2]);
posElem->baseVertexPointerToElement(vptr,
&pReal);
Ogre::Vector3 pt(pReal[0], pReal[1], pReal[2]);
pt = transform * pt;
verts.push_back(pt.x);
verts.push_back(pt.y);
@@ -217,8 +211,7 @@ static void extractEntityMesh(Ogre::Entity *ent,
currentOffset = nextOffset;
continue;
}
Ogre::HardwareIndexBufferSharedPtr ibuf =
id->indexBuffer;
Ogre::HardwareIndexBufferSharedPtr ibuf = id->indexBuffer;
if (!ibuf) {
currentOffset = nextOffset;
continue;
@@ -226,25 +219,24 @@ static void extractEntityMesh(Ogre::Entity *ent,
bool use32 = (ibuf->getType() ==
Ogre::HardwareIndexBuffer::IT_32BIT);
void *idata = ibuf->lock(
Ogre::HardwareBuffer::HBL_READ_ONLY);
size_t offset = sub->useSharedVertices ? sharedOffset
: currentOffset;
void *idata = ibuf->lock(Ogre::HardwareBuffer::HBL_READ_ONLY);
size_t offset = sub->useSharedVertices ? sharedOffset :
currentOffset;
if (use32) {
unsigned int *p = static_cast<unsigned int *>(
idata);
unsigned int *p = static_cast<unsigned int *>(idata);
for (size_t k = 0; k < id->indexCount; ++k)
tris.push_back(static_cast<int>(p[k]) +
static_cast<int>(offset));
} else {
unsigned short *p = static_cast<unsigned short *>(
idata);
unsigned short *p =
static_cast<unsigned short *>(idata);
for (size_t k = 0; k < id->indexCount; ++k)
tris.push_back(static_cast<int>(p[k]) +
static_cast<int>(offset));
}
ibuf->unlock();
currentOffset = nextOffset;
}
}
@@ -305,8 +297,8 @@ bool TileCacheNavMesh::extractMeshData(
continue;
if (!isNavMeshGeometry(ent->getMesh()->getName())) {
Ogre::LogManager::getSingleton().logMessage(
"[NavMesh] Skipping " + ent->getName() +
" mesh=" + ent->getMesh()->getName());
"[NavMesh] Skipping " + ent->getName() +
" mesh=" + ent->getMesh()->getName());
continue;
}
int vbefore = static_cast<int>(m_verts.size() / 3);
@@ -315,22 +307,22 @@ bool TileCacheNavMesh::extractMeshData(
int vafter = static_cast<int>(m_verts.size() / 3);
int tafter = static_cast<int>(m_tris.size() / 3);
Ogre::LogManager::getSingleton().logMessage(
"[NavMesh] Entity " + ent->getName() + " verts=" +
Ogre::StringConverter::toString(vafter - vbefore) +
" tris=" +
Ogre::StringConverter::toString(tafter - tbefore));
"[NavMesh] Entity " + ent->getName() + " verts=" +
Ogre::StringConverter::toString(vafter - vbefore) +
" tris=" +
Ogre::StringConverter::toString(tafter - tbefore));
processed++;
}
Ogre::LogManager::getSingleton().logMessage(
"[NavMesh] Processed " +
Ogre::StringConverter::toString(processed) +
" entities, total verts=" +
Ogre::StringConverter::toString(
static_cast<int>(m_verts.size() / 3)) +
" tris=" +
Ogre::StringConverter::toString(
static_cast<int>(m_tris.size() / 3)));
"[NavMesh] Processed " +
Ogre::StringConverter::toString(processed) +
" entities, total verts=" +
Ogre::StringConverter::toString(
static_cast<int>(m_verts.size() / 3)) +
" tris=" +
Ogre::StringConverter::toString(
static_cast<int>(m_tris.size() / 3)));
if (m_verts.empty() || m_tris.empty())
return false;
@@ -361,8 +353,8 @@ bool TileCacheNavMesh::extractMeshData(
// Build spatial partitioning
m_partitionedMesh = std::make_unique<PartitionedMesh>();
m_partitionedMesh->PartitionMesh(m_verts.data(),
m_tris.data(), ntris, 256);
m_partitionedMesh->PartitionMesh(m_verts.data(), m_tris.data(), ntris,
256);
return true;
}
@@ -384,14 +376,14 @@ bool TileCacheNavMesh::initTileCache()
m_th = 1;
Ogre::LogManager::getSingleton().logMessage(
"[NavMesh] Grid: " + Ogre::StringConverter::toString(m_tw) +
"x" + Ogre::StringConverter::toString(m_th) +
" bounds=[" + Ogre::StringConverter::toString(m_bmin[0]) +
"," + Ogre::StringConverter::toString(m_bmin[1]) +
"," + Ogre::StringConverter::toString(m_bmin[2]) +
"]-[" + Ogre::StringConverter::toString(m_bmax[0]) +
"," + Ogre::StringConverter::toString(m_bmax[1]) +
"," + Ogre::StringConverter::toString(m_bmax[2]) + "]");
"[NavMesh] Grid: " + Ogre::StringConverter::toString(m_tw) +
"x" + Ogre::StringConverter::toString(m_th) + " bounds=[" +
Ogre::StringConverter::toString(m_bmin[0]) + "," +
Ogre::StringConverter::toString(m_bmin[1]) + "," +
Ogre::StringConverter::toString(m_bmin[2]) + "]-[" +
Ogre::StringConverter::toString(m_bmax[0]) + "," +
Ogre::StringConverter::toString(m_bmax[1]) + "," +
Ogre::StringConverter::toString(m_bmax[2]) + "]");
// Tile cache params
dtTileCacheParams tcparams;
@@ -411,16 +403,15 @@ bool TileCacheNavMesh::initTileCache()
m_tileCache = dtAllocTileCache();
if (!m_tileCache) {
Ogre::LogManager::getSingleton().logMessage(
"[NavMesh] initTileCache failed: dtAllocTileCache");
"[NavMesh] initTileCache failed: dtAllocTileCache");
return false;
}
dtStatus status =
m_tileCache->init(&tcparams, m_talloc, m_tcomp,
m_tmproc);
m_tileCache->init(&tcparams, m_talloc, m_tcomp, m_tmproc);
if (dtStatusFailed(status)) {
Ogre::LogManager::getSingleton().logMessage(
"[NavMesh] initTileCache failed: tileCache init");
"[NavMesh] initTileCache failed: tileCache init");
return false;
}
@@ -429,7 +420,7 @@ bool TileCacheNavMesh::initTileCache()
// tileBits + polyBits <= 22, leaving >= 10 salt bits.
// This matches the upstream RecastDemo calculation.
int tileBits = (int)dtIlog2(
dtNextPow2((unsigned int)(m_tw * m_th * MAX_LAYERS)));
dtNextPow2((unsigned int)(m_tw * m_th * MAX_LAYERS)));
if (tileBits > 14)
tileBits = 14;
int polyBits = 22 - tileBits;
@@ -443,25 +434,24 @@ bool TileCacheNavMesh::initTileCache()
nmparams.maxPolys = 1 << polyBits;
Ogre::LogManager::getSingleton().logMessage(
"[NavMesh] tileBits=" +
Ogre::StringConverter::toString(tileBits) + " polyBits=" +
Ogre::StringConverter::toString(polyBits) +
" maxTiles=" +
Ogre::StringConverter::toString(nmparams.maxTiles) +
" maxPolys=" +
Ogre::StringConverter::toString(nmparams.maxPolys));
"[NavMesh] tileBits=" +
Ogre::StringConverter::toString(tileBits) + " polyBits=" +
Ogre::StringConverter::toString(polyBits) + " maxTiles=" +
Ogre::StringConverter::toString(nmparams.maxTiles) +
" maxPolys=" +
Ogre::StringConverter::toString(nmparams.maxPolys));
m_navMesh = dtAllocNavMesh();
if (!m_navMesh) {
Ogre::LogManager::getSingleton().logMessage(
"[NavMesh] initTileCache failed: dtAllocNavMesh");
"[NavMesh] initTileCache failed: dtAllocNavMesh");
return false;
}
status = m_navMesh->init(&nmparams);
if (dtStatusFailed(status)) {
Ogre::LogManager::getSingleton().logMessage(
"[NavMesh] initTileCache failed: navMesh init");
"[NavMesh] initTileCache failed: navMesh init");
return false;
}
@@ -469,17 +459,16 @@ bool TileCacheNavMesh::initTileCache()
status = m_navQuery->init(m_navMesh, 2048);
if (dtStatusFailed(status)) {
Ogre::LogManager::getSingleton().logMessage(
"[NavMesh] initTileCache failed: navQuery init");
"[NavMesh] initTileCache failed: navQuery init");
return false;
}
Ogre::LogManager::getSingleton().logMessage(
"[NavMesh] initTileCache success");
"[NavMesh] initTileCache success");
return true;
}
bool TileCacheNavMesh::build(
const std::vector<Ogre::Entity *> &entities)
bool TileCacheNavMesh::build(const std::vector<Ogre::Entity *> &entities)
{
if (!extractMeshData(entities))
return false;
@@ -508,20 +497,17 @@ bool TileCacheNavMesh::build(
}
Ogre::LogManager::getSingleton().logMessage(
"[NavMesh] Built " +
Ogre::StringConverter::toString(builtTiles) + "/" +
Ogre::StringConverter::toString(m_tw * m_th) +
" tiles, " +
Ogre::StringConverter::toString(tilesWithLayers) +
" with layers, " +
Ogre::StringConverter::toString(totalPolys) +
" polygons");
"[NavMesh] Built " +
Ogre::StringConverter::toString(builtTiles) + "/" +
Ogre::StringConverter::toString(m_tw * m_th) + " tiles, " +
Ogre::StringConverter::toString(tilesWithLayers) +
" with layers, " + Ogre::StringConverter::toString(totalPolys) +
" polygons");
return true;
}
int TileCacheNavMesh::rasterizeTileLayers(int tx, int ty,
TileCacheData *tiles,
int TileCacheNavMesh::rasterizeTileLayers(int tx, int ty, TileCacheData *tiles,
int maxTiles)
{
if (!m_partitionedMesh)
@@ -569,9 +555,8 @@ int TileCacheNavMesh::rasterizeTileLayers(int tx, int ty,
// Allocate heightfield
rc.solid = rcAllocHeightfield();
if (!rc.solid ||
!rcCreateHeightfield(m_ctx, *rc.solid, tcfg.width,
tcfg.height, tcfg.bmin, tcfg.bmax,
tcfg.cs, tcfg.ch))
!rcCreateHeightfield(m_ctx, *rc.solid, tcfg.width, tcfg.height,
tcfg.bmin, tcfg.bmax, tcfg.cs, tcfg.ch))
return 0;
rc.triareas = new unsigned char[m_partitionedMesh->maxTrisPerChunk];
@@ -592,34 +577,51 @@ int TileCacheNavMesh::rasterizeTileLayers(int tx, int ty,
for (int nodeIndex : overlappingNodes) {
const PartitionedMesh::Node &node =
m_partitionedMesh->nodes[nodeIndex];
const int *tris =
&m_partitionedMesh->tris[node.triIndex * 3];
m_partitionedMesh->nodes[nodeIndex];
const int *tris = &m_partitionedMesh->tris[node.triIndex * 3];
const int ntris = node.numTris;
memset(rc.triareas, 0,
ntris * sizeof(unsigned char));
rcMarkWalkableTriangles(
m_ctx, tcfg.walkableSlopeAngle, verts, nverts,
tris, ntris, rc.triareas);
rcRasterizeTriangles(m_ctx, verts, nverts, tris,
rc.triareas, ntris,
*rc.solid,
tcfg.walkableClimb);
memset(rc.triareas, 0, ntris * sizeof(unsigned char));
rcMarkWalkableTriangles(m_ctx, tcfg.walkableSlopeAngle, verts,
nverts, tris, ntris, rc.triareas);
// After rcMarkWalkableTriangles has set area based on slope,
// additionally clear triangles whose normal faces downward
// (negative Y in world space). Some meshes (e.g. Lot base
// boxes) have bottom faces wound the same way as top faces,
// causing rcMarkWalkableTriangles to compute an upward-pointing
// normal and mark them as walkable. We detect this by
// computing the world-space normal and forcing RC_NULL_AREA.
for (int i = 0; i < ntris; ++i) {
const int *tri = &tris[i * 3];
const float *v0 = &verts[tri[0] * 3];
const float *v1 = &verts[tri[1] * 3];
const float *v2 = &verts[tri[2] * 3];
float ux = v1[0] - v0[0];
float uy = v1[1] - v0[1];
float uz = v1[2] - v0[2];
float vx = v2[0] - v0[0];
float vy = v2[1] - v0[1];
float vz = v2[2] - v0[2];
float ny = uz * vx - ux * vz;
if (ny < -1e-6f)
rc.triareas[i] = RC_NULL_AREA;
}
rcRasterizeTriangles(m_ctx, verts, nverts, tris, rc.triareas,
ntris, *rc.solid, tcfg.walkableClimb);
}
rcFilterLowHangingWalkableObstacles(m_ctx, tcfg.walkableClimb,
*rc.solid);
rcFilterLedgeSpans(m_ctx, tcfg.walkableHeight,
tcfg.walkableClimb, *rc.solid);
rcFilterWalkableLowHeightSpans(m_ctx, tcfg.walkableHeight,
*rc.solid);
rcFilterLedgeSpans(m_ctx, tcfg.walkableHeight, tcfg.walkableClimb,
*rc.solid);
rcFilterWalkableLowHeightSpans(m_ctx, tcfg.walkableHeight, *rc.solid);
rc.chf = rcAllocCompactHeightfield();
if (!rc.chf ||
!rcBuildCompactHeightfield(m_ctx, tcfg.walkableHeight,
tcfg.walkableClimb, *rc.solid,
*rc.chf))
tcfg.walkableClimb, *rc.solid, *rc.chf))
return 0;
rcFreeHeightField(rc.solid);
@@ -631,21 +633,19 @@ int TileCacheNavMesh::rasterizeTileLayers(int tx, int ty,
if (!rcBuildDistanceField(m_ctx, *rc.chf))
return 0;
if (!rcBuildRegions(m_ctx, *rc.chf, tcfg.borderSize,
tcfg.minRegionArea, tcfg.mergeRegionArea))
if (!rcBuildRegions(m_ctx, *rc.chf, tcfg.borderSize, tcfg.minRegionArea,
tcfg.mergeRegionArea))
return 0;
rc.lset = rcAllocHeightfieldLayerSet();
if (!rc.lset ||
!rcBuildHeightfieldLayers(m_ctx, *rc.chf,
tcfg.borderSize,
!rcBuildHeightfieldLayers(m_ctx, *rc.chf, tcfg.borderSize,
tcfg.walkableHeight, *rc.lset))
return 0;
for (int i = 0; i < rcMin(rc.lset->nlayers, maxTiles); ++i) {
TileCacheData *tile = &rc.tiles[rc.ntiles++];
const rcHeightfieldLayer *layer =
&rc.lset->layers[i];
const rcHeightfieldLayer *layer = &rc.lset->layers[i];
dtTileCacheLayerHeader header;
header.magic = DT_TILECACHE_MAGIC;
@@ -665,8 +665,8 @@ int TileCacheNavMesh::rasterizeTileLayers(int tx, int ty,
header.hmax = (unsigned short)layer->hmax;
dtStatus status = dtBuildTileCacheLayer(
&comp, &header, layer->heights, layer->areas,
layer->cons, &tile->data, &tile->dataSize);
&comp, &header, layer->heights, layer->areas,
layer->cons, &tile->data, &tile->dataSize);
if (dtStatusFailed(status))
return 0;
}
@@ -692,9 +692,9 @@ bool TileCacheNavMesh::buildTile(int tx, int ty)
int ntiles = rasterizeTileLayers(tx, ty, tiles, MAX_LAYERS);
for (int i = 0; i < ntiles; ++i) {
dtStatus status = m_tileCache->addTile(
tiles[i].data, tiles[i].dataSize,
DT_COMPRESSEDTILE_FREE_DATA, 0);
dtStatus status =
m_tileCache->addTile(tiles[i].data, tiles[i].dataSize,
DT_COMPRESSEDTILE_FREE_DATA, 0);
if (dtStatusFailed(status)) {
dtFree(tiles[i].data);
tiles[i].data = nullptr;
@@ -725,8 +725,7 @@ bool TileCacheNavMesh::removeTile(int tx, int ty)
return true;
}
void TileCacheNavMesh::rebuildTilesInArea(
const Ogre::AxisAlignedBox &area)
void TileCacheNavMesh::rebuildTilesInArea(const Ogre::AxisAlignedBox &area)
{
if (!m_tileCache)
return;
@@ -754,8 +753,7 @@ void TileCacheNavMesh::rebuildTilesInArea(
}
}
void TileCacheNavMesh::getTileCoords(const float *pos, int &tx,
int &ty)
void TileCacheNavMesh::getTileCoords(const float *pos, int &tx, int &ty)
{
float ts = m_params.tileSize * m_cellSize;
tx = (int)((pos[0] - m_bmin[0]) / ts);
@@ -788,21 +786,21 @@ bool TileCacheNavMesh::findPath(const Ogre::Vector3 &start,
dtPolyRef startPoly, endPoly;
float startNearest[3], endNearest[3];
dtStatus status = m_navQuery->findNearestPoly(
s, m_extents, m_filter, &startPoly, startNearest);
dtStatus status = m_navQuery->findNearestPoly(s, m_extents, m_filter,
&startPoly, startNearest);
if (dtStatusFailed(status))
return false;
status = m_navQuery->findNearestPoly(e, m_extents, m_filter,
&endPoly, endNearest);
status = m_navQuery->findNearestPoly(e, m_extents, m_filter, &endPoly,
endNearest);
if (dtStatusFailed(status))
return false;
dtPolyRef polyPath[256];
int npoly = 0;
status = m_navQuery->findPath(startPoly, endPoly, startNearest,
endNearest, m_filter,
polyPath, &npoly, 256);
endNearest, m_filter, polyPath, &npoly,
256);
if (dtStatusFailed(status) || npoly == 0)
return false;
@@ -810,10 +808,10 @@ bool TileCacheNavMesh::findPath(const Ogre::Vector3 &start,
unsigned char straightPathFlags[256];
dtPolyRef straightPathPolys[256];
int nvert = 0;
status = m_navQuery->findStraightPath(
startNearest, endNearest, polyPath, npoly,
straightPath, straightPathFlags, straightPathPolys,
&nvert, 256);
status = m_navQuery->findStraightPath(startNearest, endNearest,
polyPath, npoly, straightPath,
straightPathFlags,
straightPathPolys, &nvert, 256);
if (dtStatusFailed(status) || nvert == 0)
return false;
@@ -836,8 +834,8 @@ bool TileCacheNavMesh::findNearestPoint(const Ogre::Vector3 &pos,
dtPolyRef poly;
float nearest[3];
dtStatus status = m_navQuery->findNearestPoly(
p, m_extents, m_filter, &poly, nearest);
dtStatus status = m_navQuery->findNearestPoly(p, m_extents, m_filter,
&poly, nearest);
if (dtStatusFailed(status))
return false;
@@ -858,11 +856,9 @@ Ogre::Vector3 TileCacheNavMesh::getRandomPoint()
dtPolyRef poly;
float pt[3];
dtStatus status = m_navQuery->findRandomPoint(
m_filter,
[]() -> float {
return (float)rand() / (float)RAND_MAX;
},
&poly, pt);
m_filter,
[]() -> float { return (float)rand() / (float)RAND_MAX; },
&poly, pt);
if (dtStatusFailed(status))
return Ogre::Vector3::ZERO;
@@ -878,17 +874,15 @@ void TileCacheNavMesh::drawNavMesh()
clearDebugDraw();
if (!m_navMesh) {
Ogre::LogManager::getSingleton().logMessage(
"[NavMesh] drawNavMesh: no navmesh");
"[NavMesh] drawNavMesh: no navmesh");
return;
}
static int debugId = 0;
Ogre::String moName = "NavMeshDebug_" +
Ogre::StringConverter::toString(
debugId++);
Ogre::String moName =
"NavMeshDebug_" + Ogre::StringConverter::toString(debugId++);
Ogre::String nodeName = "NavMeshDebugNode_" +
Ogre::StringConverter::toString(
debugId++);
Ogre::StringConverter::toString(debugId++);
m_debugMO = m_sceneMgr->createManualObject(moName);
m_debugMO->setDynamic(false);
@@ -897,19 +891,18 @@ void TileCacheNavMesh::drawNavMesh()
Ogre::ColourValue groundCol(0.0f, 0.7f, 0.0f, 1.0f);
Ogre::ColourValue edgeCol(1.0f, 0.0f, 0.0f, 1.0f);
Ogre::MaterialPtr mat = Ogre::MaterialManager::getSingleton()
.getByName("BaseWhiteNoLighting");
Ogre::MaterialPtr mat = Ogre::MaterialManager::getSingleton().getByName(
"BaseWhiteNoLighting");
if (!mat) {
Ogre::LogManager::getSingleton().logMessage(
"[NavMesh] BaseWhiteNoLighting not found, creating fallback");
"[NavMesh] BaseWhiteNoLighting not found, creating fallback");
mat = Ogre::MaterialManager::getSingleton().create(
"NavMeshDebugMat",
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
"NavMeshDebugMat",
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
mat->getTechnique(0)->getPass(0)->setLightingEnabled(false);
mat->getTechnique(0)->getPass(0)->setDepthWriteEnabled(
false);
mat->getTechnique(0)->getPass(0)->setDepthWriteEnabled(false);
mat->getTechnique(0)->getPass(0)->setSceneBlending(
Ogre::SBT_TRANSPARENT_ALPHA);
Ogre::SBT_TRANSPARENT_ALPHA);
}
m_debugMO->begin("BaseWhiteNoLighting",
@@ -962,8 +955,8 @@ void TileCacheNavMesh::drawNavMesh()
if (p->getType() == DT_POLYTYPE_OFFMESH_CONNECTION)
continue;
for (int k = 0, k2 = p->vertCount - 1;
k < p->vertCount; k2 = k++) {
for (int k = 0, k2 = p->vertCount - 1; k < p->vertCount;
k2 = k++) {
unsigned short v0i = p->verts[k2];
unsigned short v1i = p->verts[k];
if (p->neis[k2] != 0)
@@ -982,8 +975,8 @@ void TileCacheNavMesh::drawNavMesh()
}
m_debugMO->end();
m_debugNode = m_sceneMgr->getRootSceneNode()
->createChildSceneNode(nodeName);
m_debugNode =
m_sceneMgr->getRootSceneNode()->createChildSceneNode(nodeName);
m_debugNode->attachObject(m_debugMO);
// Count total polygons for diagnostics
@@ -994,12 +987,10 @@ void TileCacheNavMesh::drawNavMesh()
polyCount += tile->header->polyCount;
}
Ogre::LogManager::getSingleton().logMessage(
"[NavMesh] drawNavMesh: created " + moName +
" with " +
Ogre::StringConverter::toString(m_debugMO->getNumSections()) +
" sections, " +
Ogre::StringConverter::toString(polyCount) +
" polygons");
"[NavMesh] drawNavMesh: created " + moName + " with " +
Ogre::StringConverter::toString(m_debugMO->getNumSections()) +
" sections, " + Ogre::StringConverter::toString(polyCount) +
" polygons");
}
void TileCacheNavMesh::clearDebugDraw()

View File

@@ -24,10 +24,9 @@ static bool isNavMeshGeometry(const Ogre::String &meshName)
// span the walkable surface is pushed to the top of the wall;
// if a roof is close above, rcFilterWalkableLowHeightSpans
// removes the whole column.
static const char *exclude[] = {
"wall", "ceiling", "roof", "door",
"window", "frame", "furniture"
};
static const char *exclude[] = { "wall", "ceiling", "roof",
"door", "window", "frame",
"furniture" };
for (const char *pat : exclude) {
if (lower.find(pat) != Ogre::String::npos)
return false;
@@ -42,31 +41,29 @@ static void collectEntitiesFromNode(Ogre::SceneNode *node,
if (!node)
return;
try {
for (unsigned short i = 0;
i < node->numAttachedObjects(); ++i) {
Ogre::MovableObject *obj =
node->getAttachedObject(i);
for (unsigned short i = 0; i < node->numAttachedObjects();
++i) {
Ogre::MovableObject *obj = node->getAttachedObject(i);
if (!obj)
continue;
if (obj->getMovableType() == "Entity") {
Ogre::Entity *ent =
static_cast<Ogre::Entity *>(obj);
static_cast<Ogre::Entity *>(obj);
if (!isNavMeshGeometry(
ent->getMesh()->getName()))
ent->getMesh()->getName()))
continue;
// Avoid duplicates
if (std::find(out.begin(), out.end(),
ent) == out.end())
if (std::find(out.begin(), out.end(), ent) ==
out.end())
out.push_back(ent);
}
}
for (unsigned short i = 0;
i < node->numChildren(); ++i) {
for (unsigned short i = 0; i < node->numChildren(); ++i) {
Ogre::Node *child = node->getChild(i);
if (!child)
continue;
Ogre::SceneNode *childSN = dynamic_cast<
Ogre::SceneNode *>(child);
Ogre::SceneNode *childSN =
dynamic_cast<Ogre::SceneNode *>(child);
if (childSN)
collectEntitiesFromNode(childSN, out);
}
@@ -79,8 +76,8 @@ static void collectEntitiesFromNode(Ogre::SceneNode *node,
// and all its descendants. This is needed because flecs parent/child
// hierarchy does NOT map to Ogre SceneNode hierarchy; each entity has
// its own independent scene node.
static void collectEntitiesFromFlecsEntity(
flecs::entity e, std::vector<Ogre::Entity *> &out)
static void collectEntitiesFromFlecsEntity(flecs::entity e,
std::vector<Ogre::Entity *> &out)
{
if (e.has<TransformComponent>()) {
auto &trans = e.get<TransformComponent>();
@@ -94,8 +91,7 @@ static void collectEntitiesFromFlecsEntity(
NavMeshSystem *NavMeshSystem::s_instance = nullptr;
NavMeshSystem::NavMeshSystem(flecs::world &world,
Ogre::SceneManager *sceneMgr)
NavMeshSystem::NavMeshSystem(flecs::world &world, Ogre::SceneManager *sceneMgr)
: m_world(world)
, m_sceneMgr(sceneMgr)
{
@@ -117,93 +113,89 @@ void NavMeshSystem::update(float deltaTime)
{
(void)deltaTime;
m_world.query<NavMeshComponent>().each(
[&](flecs::entity e, NavMeshComponent &comp) {
if (!comp.enabled)
return;
m_world.query<NavMeshComponent>().each([&](flecs::entity e,
NavMeshComponent &comp) {
if (!comp.enabled)
return;
auto &state = m_states[e.id()];
auto &state = m_states[e.id()];
// Handle full rebuild request
if (comp.dirty) {
buildOrUpdate(e.id(), comp, state);
comp.dirty = false;
comp.needsPartialRebuild = false;
state.lastTransformVersions.clear();
return;
}
// Handle full rebuild request
if (comp.dirty) {
buildOrUpdate(e.id(), comp, state);
comp.dirty = false;
comp.needsPartialRebuild = false;
state.lastTransformVersions.clear();
return;
}
// Check for geometry changes that need partial rebuild
bool changed = false;
Ogre::AxisAlignedBox dirtyArea;
m_world.query<TransformComponent>().each(
[&](flecs::entity ge,
TransformComponent &trans) {
bool contributes = false;
if (ge.has<RigidBodyComponent>()) {
auto &rb = ge.get<RigidBodyComponent>();
if (rb.bodyType ==
RigidBodyComponent::BodyType::Static)
contributes = true;
}
if (ge.has<StaticGeometryMemberComponent>()) {
auto &sgm = ge.get<StaticGeometryMemberComponent>();
if (!sgm.meshName.empty())
contributes = true;
}
if (ge.has<NavMeshGeometrySource>()) {
auto &nmg = ge.get<NavMeshGeometrySource>();
if (nmg.include)
contributes = true;
}
if (ge.has<CellGridComponent>())
contributes = true;
if (ge.has<DistrictComponent>())
contributes = true;
if (ge.has<LotComponent>())
contributes = true;
if (!contributes)
return;
// Check for geometry changes that need partial rebuild
bool changed = false;
Ogre::AxisAlignedBox dirtyArea;
m_world.query<TransformComponent>().each([&](flecs::entity ge,
TransformComponent
&trans) {
bool contributes = false;
if (ge.has<RigidBodyComponent>()) {
auto &rb = ge.get<RigidBodyComponent>();
if (rb.bodyType ==
RigidBodyComponent::BodyType::Static)
contributes = true;
}
if (ge.has<StaticGeometryMemberComponent>()) {
auto &sgm =
ge.get<StaticGeometryMemberComponent>();
if (!sgm.meshName.empty())
contributes = true;
}
if (ge.has<NavMeshGeometrySource>()) {
auto &nmg = ge.get<NavMeshGeometrySource>();
if (nmg.include)
contributes = true;
}
if (ge.has<CellGridComponent>())
contributes = true;
if (ge.has<DistrictComponent>())
contributes = true;
if (ge.has<LotComponent>())
contributes = true;
if (!contributes)
return;
auto it = state.lastTransformVersions.find(
ge.id());
if (it == state.lastTransformVersions.end() ||
it->second != trans.version) {
changed = true;
Ogre::AxisAlignedBox bb =
getEntityBounds(ge);
if (!bb.isNull()) {
if (dirtyArea.isNull())
dirtyArea = bb;
else
dirtyArea.merge(bb);
}
state.lastTransformVersions[ge.id()] =
trans.version;
}
});
auto it = state.lastTransformVersions.find(ge.id());
if (it == state.lastTransformVersions.end() ||
it->second != trans.version) {
changed = true;
Ogre::AxisAlignedBox bb = getEntityBounds(ge);
if (!bb.isNull()) {
if (dirtyArea.isNull())
dirtyArea = bb;
else
dirtyArea.merge(bb);
}
state.lastTransformVersions[ge.id()] =
trans.version;
}
});
if (changed && state.navmesh) {
if (!dirtyArea.isNull()) {
Ogre::Vector3 padding(
comp.agentRadius * 2,
comp.agentHeight * 2,
comp.agentRadius * 2);
dirtyArea.setMinimum(
dirtyArea.getMinimum() - padding);
dirtyArea.setMaximum(
dirtyArea.getMaximum() + padding);
state.navmesh->rebuildTilesInArea(
dirtyArea);
}
if (state.debugDraw)
state.navmesh->drawNavMesh();
}
});
if (changed && state.navmesh) {
if (!dirtyArea.isNull()) {
Ogre::Vector3 padding(comp.agentRadius * 2,
comp.agentHeight * 2,
comp.agentRadius * 2);
dirtyArea.setMinimum(dirtyArea.getMinimum() -
padding);
dirtyArea.setMaximum(dirtyArea.getMaximum() +
padding);
state.navmesh->rebuildTilesInArea(dirtyArea);
}
if (state.debugDraw)
state.navmesh->drawNavMesh();
}
});
}
void NavMeshSystem::buildOrUpdate(flecs::entity_t e,
NavMeshComponent &comp,
void NavMeshSystem::buildOrUpdate(flecs::entity_t e, NavMeshComponent &comp,
NavMeshState &state)
{
state.navmesh.reset();
@@ -222,8 +214,7 @@ void NavMeshSystem::buildOrUpdate(flecs::entity_t e,
params.regionMergeSize = comp.regionMergeSize;
params.tileSize = comp.tileSize;
state.navmesh = std::make_unique<TileCacheNavMesh>(
m_sceneMgr, params);
state.navmesh = std::make_unique<TileCacheNavMesh>(m_sceneMgr, params);
std::vector<Ogre::Entity *> entities;
std::vector<Ogre::Entity *> tempEntities;
@@ -231,7 +222,7 @@ void NavMeshSystem::buildOrUpdate(flecs::entity_t e,
if (entities.empty()) {
Ogre::LogManager::getSingleton().logMessage(
"[NavMeshSystem] No static geometry found for navmesh");
"[NavMeshSystem] No static geometry found for navmesh");
return;
}
@@ -240,53 +231,49 @@ void NavMeshSystem::buildOrUpdate(flecs::entity_t e,
if (!ok) {
Ogre::LogManager::getSingleton().logMessage(
"[NavMeshSystem] Navmesh build failed");
"[NavMeshSystem] Navmesh build failed");
return;
}
Ogre::LogManager::getSingleton().logMessage(
"[NavMeshSystem] Navmesh built successfully");
"[NavMeshSystem] Navmesh built successfully");
if (state.debugDraw)
state.navmesh->drawNavMesh();
state.firstBuild = false;
m_world.query<TransformComponent>().each(
[&](flecs::entity ge, TransformComponent &trans) {
bool contributes = false;
if (ge.has<RigidBodyComponent>()) {
auto &rb = ge.get<RigidBodyComponent>();
if (rb.bodyType ==
RigidBodyComponent::BodyType::Static)
contributes = true;
}
if (ge.has<StaticGeometryMemberComponent>()) {
auto &sgm =
ge.get<StaticGeometryMemberComponent>();
if (!sgm.meshName.empty())
contributes = true;
}
if (ge.has<NavMeshGeometrySource>()) {
auto &nmg = ge.get<NavMeshGeometrySource>();
if (nmg.include)
contributes = true;
}
if (ge.has<CellGridComponent>())
contributes = true;
if (ge.has<DistrictComponent>())
contributes = true;
if (ge.has<LotComponent>())
contributes = true;
if (contributes)
state.lastTransformVersions[ge.id()] =
trans.version;
});
m_world.query<TransformComponent>().each([&](flecs::entity ge,
TransformComponent &trans) {
bool contributes = false;
if (ge.has<RigidBodyComponent>()) {
auto &rb = ge.get<RigidBodyComponent>();
if (rb.bodyType == RigidBodyComponent::BodyType::Static)
contributes = true;
}
if (ge.has<StaticGeometryMemberComponent>()) {
auto &sgm = ge.get<StaticGeometryMemberComponent>();
if (!sgm.meshName.empty())
contributes = true;
}
if (ge.has<NavMeshGeometrySource>()) {
auto &nmg = ge.get<NavMeshGeometrySource>();
if (nmg.include)
contributes = true;
}
if (ge.has<CellGridComponent>())
contributes = true;
if (ge.has<DistrictComponent>())
contributes = true;
if (ge.has<LotComponent>())
contributes = true;
if (contributes)
state.lastTransformVersions[ge.id()] = trans.version;
});
}
void NavMeshSystem::collectStaticEntities(
std::vector<Ogre::Entity *> &out,
std::vector<Ogre::Entity *> &outTemp)
void NavMeshSystem::collectStaticEntities(std::vector<Ogre::Entity *> &out,
std::vector<Ogre::Entity *> &outTemp)
{
// Helper: add existing scene entity (do NOT destroy later)
auto addExisting = [&](Ogre::Entity *ent) {
@@ -302,121 +289,112 @@ void NavMeshSystem::collectStaticEntities(
};
Ogre::LogManager::getSingleton().logMessage(
"[NavMeshSystem] collectStaticEntities started");
"[NavMeshSystem] collectStaticEntities started");
// 1. Static rigid bodies with renderables
m_world.query<TransformComponent, RigidBodyComponent>()
.each([&](flecs::entity e, TransformComponent &trans,
RigidBodyComponent &rb) {
if (rb.bodyType !=
RigidBodyComponent::BodyType::Static)
return;
if (!e.has<RenderableComponent>())
return;
auto &rend = e.get<RenderableComponent>();
if (rend.entity) {
addExisting(rend.entity);
} else if (!rend.meshName.empty() &&
isNavMeshGeometry(rend.meshName)) {
addTemp(createTempEntity(
rend.meshName, trans.position,
trans.rotation, trans.scale));
}
});
m_world.query<TransformComponent, RigidBodyComponent>().each(
[&](flecs::entity e, TransformComponent &trans,
RigidBodyComponent &rb) {
if (rb.bodyType != RigidBodyComponent::BodyType::Static)
return;
if (!e.has<RenderableComponent>())
return;
auto &rend = e.get<RenderableComponent>();
if (rend.entity) {
addExisting(rend.entity);
} else if (!rend.meshName.empty() &&
isNavMeshGeometry(rend.meshName)) {
addTemp(createTempEntity(
rend.meshName, trans.position,
trans.rotation, trans.scale));
}
});
// 2. StaticGeometry members
m_world
.query<TransformComponent, StaticGeometryMemberComponent>()
.each([&](flecs::entity e, TransformComponent &trans,
StaticGeometryMemberComponent &sgm) {
if (sgm.meshName.empty() ||
!isNavMeshGeometry(sgm.meshName))
return;
addTemp(createTempEntity(
sgm.meshName, trans.position, trans.rotation,
trans.scale));
});
m_world.query<TransformComponent, StaticGeometryMemberComponent>().each(
[&](flecs::entity e, TransformComponent &trans,
StaticGeometryMemberComponent &sgm) {
if (sgm.meshName.empty() ||
!isNavMeshGeometry(sgm.meshName))
return;
addTemp(createTempEntity(sgm.meshName, trans.position,
trans.rotation, trans.scale));
});
// 3. Forced geometry sources
m_world.query<TransformComponent, NavMeshGeometrySource>()
.each([&](flecs::entity e, TransformComponent &trans,
NavMeshGeometrySource &nmg) {
if (!nmg.include)
return;
Ogre::String meshName;
if (e.has<RenderableComponent>()) {
auto &rend = e.get<RenderableComponent>();
if (rend.entity) {
addExisting(rend.entity);
return;
}
meshName = rend.meshName;
}
if (e.has<StaticGeometryMemberComponent>()) {
auto &sgm =
e.get<StaticGeometryMemberComponent>();
if (!sgm.meshName.empty())
meshName = sgm.meshName;
}
if (!meshName.empty() &&
isNavMeshGeometry(meshName)) {
addTemp(createTempEntity(
meshName, trans.position, trans.rotation,
trans.scale));
}
});
m_world.query<TransformComponent, NavMeshGeometrySource>().each(
[&](flecs::entity e, TransformComponent &trans,
NavMeshGeometrySource &nmg) {
if (!nmg.include)
return;
Ogre::String meshName;
if (e.has<RenderableComponent>()) {
auto &rend = e.get<RenderableComponent>();
if (rend.entity) {
addExisting(rend.entity);
return;
}
meshName = rend.meshName;
}
if (e.has<StaticGeometryMemberComponent>()) {
auto &sgm =
e.get<StaticGeometryMemberComponent>();
if (!sgm.meshName.empty())
meshName = sgm.meshName;
}
if (!meshName.empty() && isNavMeshGeometry(meshName)) {
addTemp(createTempEntity(
meshName, trans.position,
trans.rotation, trans.scale));
}
});
// 4. Cell Grid, District, and Lot geometry (always included)
// We recursively traverse the flecs hierarchy because flecs
// parent/child relationships do NOT map to Ogre SceneNode
// hierarchy; each entity has its own independent scene node.
m_world
.query<TransformComponent,
CellGridComponent>()
.each([&](flecs::entity e, TransformComponent &trans,
CellGridComponent &grid) {
(void)trans;
(void)grid;
collectEntitiesFromFlecsEntity(e, out);
});
m_world
.query<TransformComponent,
DistrictComponent>()
.each([&](flecs::entity e, TransformComponent &trans,
DistrictComponent &district) {
(void)trans;
(void)district;
collectEntitiesFromFlecsEntity(e, out);
});
m_world
.query<TransformComponent, LotComponent>()
.each([&](flecs::entity e, TransformComponent &trans,
LotComponent &lot) {
(void)trans;
(void)lot;
collectEntitiesFromFlecsEntity(e, out);
});
m_world.query<TransformComponent, CellGridComponent>().each(
[&](flecs::entity e, TransformComponent &trans,
CellGridComponent &grid) {
(void)trans;
(void)grid;
collectEntitiesFromFlecsEntity(e, out);
});
m_world.query<TransformComponent, DistrictComponent>().each(
[&](flecs::entity e, TransformComponent &trans,
DistrictComponent &district) {
(void)trans;
(void)district;
collectEntitiesFromFlecsEntity(e, out);
});
m_world.query<TransformComponent, LotComponent>().each(
[&](flecs::entity e, TransformComponent &trans,
LotComponent &lot) {
(void)trans;
(void)lot;
collectEntitiesFromFlecsEntity(e, out);
});
// 5. Frame and furniture placements from CellGridSystem
if (m_cellGridSystem) {
m_world.query<CellGridComponent>()
.each([&](flecs::entity e, CellGridComponent &grid) {
(void)grid;
// Frames and furniture are excluded from navmesh
// geometry because their vertical spans merge with
// floor spans in Recast and break walkability.
(void)m_cellGridSystem;
});
m_world.query<CellGridComponent>().each(
[&](flecs::entity e, CellGridComponent &grid) {
(void)grid;
// Frames and furniture are excluded from navmesh
// geometry because their vertical spans merge with
// floor spans in Recast and break walkability.
(void)m_cellGridSystem;
});
}
// Detailed logging
Ogre::LogManager::getSingleton().logMessage(
"[NavMeshSystem] collectStaticEntities done: existing=" +
Ogre::StringConverter::toString(
static_cast<int>(out.size() - outTemp.size())) +
" temp=" +
Ogre::StringConverter::toString(
static_cast<int>(outTemp.size())));
"[NavMeshSystem] collectStaticEntities done: existing=" +
Ogre::StringConverter::toString(
static_cast<int>(out.size() - outTemp.size())) +
" temp=" +
Ogre::StringConverter::toString(
static_cast<int>(outTemp.size())));
for (size_t i = 0; i < out.size(); ++i) {
Ogre::Entity *ent = out[i];
if (!ent)
@@ -424,35 +402,33 @@ void NavMeshSystem::collectStaticEntities(
Ogre::SceneNode *node = ent->getParentSceneNode();
Ogre::String nodeName = node ? node->getName() : "(none)";
Ogre::String meshName = ent->getMesh()->getName();
bool isTemp = std::find(outTemp.begin(), outTemp.end(),
ent) != outTemp.end();
bool isTemp = std::find(outTemp.begin(), outTemp.end(), ent) !=
outTemp.end();
Ogre::LogManager::getSingleton().logMessage(
"[NavMeshSystem] entity[" +
Ogre::StringConverter::toString(
static_cast<int>(i)) +
"] mesh=" + meshName + " node=" + nodeName +
" temp=" + (isTemp ? "yes" : "no"));
"[NavMeshSystem] entity[" +
Ogre::StringConverter::toString(static_cast<int>(i)) +
"] mesh=" + meshName + " node=" + nodeName +
" temp=" + (isTemp ? "yes" : "no"));
}
}
Ogre::Entity *NavMeshSystem::createTempEntity(
const Ogre::String &meshName, const Ogre::Vector3 &pos,
const Ogre::Quaternion &rot, const Ogre::Vector3 &scale)
Ogre::Entity *NavMeshSystem::createTempEntity(const Ogre::String &meshName,
const Ogre::Vector3 &pos,
const Ogre::Quaternion &rot,
const Ogre::Vector3 &scale)
{
static int tempId = 0;
Ogre::String uniqueName = "_NavTempEnt_" +
Ogre::StringConverter::toString(
tempId++);
Ogre::String nodeName = "_NavTempNode_" +
Ogre::StringConverter::toString(
tempId++);
Ogre::String uniqueName =
"_NavTempEnt_" + Ogre::StringConverter::toString(tempId++);
Ogre::String nodeName =
"_NavTempNode_" + Ogre::StringConverter::toString(tempId++);
try {
Ogre::Entity *ent = m_sceneMgr->createEntity(
uniqueName, meshName);
Ogre::Entity *ent =
m_sceneMgr->createEntity(uniqueName, meshName);
Ogre::SceneNode *node =
m_sceneMgr->getRootSceneNode()->createChildSceneNode(
nodeName);
m_sceneMgr->getRootSceneNode()->createChildSceneNode(
nodeName);
node->setPosition(pos);
node->setOrientation(rot);
node->setScale(scale);
@@ -463,8 +439,7 @@ Ogre::Entity *NavMeshSystem::createTempEntity(
}
}
void NavMeshSystem::destroyTempEntities(
std::vector<Ogre::Entity *> &entities)
void NavMeshSystem::destroyTempEntities(std::vector<Ogre::Entity *> &entities)
{
for (auto *ent : entities) {
if (!ent)
@@ -500,8 +475,8 @@ Ogre::AxisAlignedBox NavMeshSystem::getEntityBounds(flecs::entity e)
if (!sgm.meshName.empty()) {
try {
Ogre::MeshPtr mesh =
Ogre::MeshManager::getSingleton()
.getByName(sgm.meshName);
Ogre::MeshManager::getSingleton()
.getByName(sgm.meshName);
if (mesh) {
localBB = mesh->getBounds();
hasBounds = true;
@@ -512,8 +487,7 @@ Ogre::AxisAlignedBox NavMeshSystem::getEntityBounds(flecs::entity e)
}
if (!hasBounds && e.has<PhysicsColliderComponent>()) {
auto &col = e.get<PhysicsColliderComponent>();
localBB = Ogre::AxisAlignedBox(-col.parameters,
col.parameters);
localBB = Ogre::AxisAlignedBox(-col.parameters, col.parameters);
hasBounds = true;
}
@@ -523,27 +497,27 @@ Ogre::AxisAlignedBox NavMeshSystem::getEntityBounds(flecs::entity e)
Ogre::Matrix4 mat;
mat.makeTransform(trans.position, trans.scale, trans.rotation);
Ogre::AxisAlignedBox worldBB;
worldBB.merge(mat *
localBB.getCorner(Ogre::AxisAlignedBox::FAR_LEFT_BOTTOM));
worldBB.merge(mat *
localBB.getCorner(Ogre::AxisAlignedBox::FAR_LEFT_TOP));
worldBB.merge(mat * localBB.getCorner(
Ogre::AxisAlignedBox::FAR_LEFT_BOTTOM));
Ogre::AxisAlignedBox::FAR_RIGHT_BOTTOM));
worldBB.merge(mat *
localBB.getCorner(Ogre::AxisAlignedBox::FAR_RIGHT_TOP));
worldBB.merge(mat * localBB.getCorner(
Ogre::AxisAlignedBox::FAR_LEFT_TOP));
Ogre::AxisAlignedBox::NEAR_LEFT_BOTTOM));
worldBB.merge(mat *
localBB.getCorner(Ogre::AxisAlignedBox::NEAR_LEFT_TOP));
worldBB.merge(mat * localBB.getCorner(
Ogre::AxisAlignedBox::FAR_RIGHT_BOTTOM));
worldBB.merge(mat * localBB.getCorner(
Ogre::AxisAlignedBox::FAR_RIGHT_TOP));
worldBB.merge(mat * localBB.getCorner(
Ogre::AxisAlignedBox::NEAR_LEFT_BOTTOM));
worldBB.merge(mat * localBB.getCorner(
Ogre::AxisAlignedBox::NEAR_LEFT_TOP));
worldBB.merge(mat * localBB.getCorner(
Ogre::AxisAlignedBox::NEAR_RIGHT_BOTTOM));
worldBB.merge(mat * localBB.getCorner(
Ogre::AxisAlignedBox::NEAR_RIGHT_TOP));
Ogre::AxisAlignedBox::NEAR_RIGHT_BOTTOM));
worldBB.merge(mat *
localBB.getCorner(Ogre::AxisAlignedBox::NEAR_RIGHT_TOP));
return worldBB;
}
bool NavMeshSystem::findPath(flecs::entity navmeshEntity,
Ogre::Vector3 start, Ogre::Vector3 end,
bool NavMeshSystem::findPath(flecs::entity navmeshEntity, Ogre::Vector3 start,
Ogre::Vector3 end,
std::vector<Ogre::Vector3> &path)
{
auto it = m_states.find(navmeshEntity.id());
@@ -554,8 +528,7 @@ bool NavMeshSystem::findPath(flecs::entity navmeshEntity,
}
bool NavMeshSystem::findNearestPoint(flecs::entity navmeshEntity,
Ogre::Vector3 pos,
Ogre::Vector3 &out)
Ogre::Vector3 pos, Ogre::Vector3 &out)
{
auto it = m_states.find(navmeshEntity.id());
if (it == m_states.end() || !it->second.navmesh)
@@ -564,8 +537,7 @@ bool NavMeshSystem::findNearestPoint(flecs::entity navmeshEntity,
return it->second.navmesh->findNearestPoint(pos, out);
}
Ogre::Vector3 NavMeshSystem::getRandomPoint(
flecs::entity navmeshEntity)
Ogre::Vector3 NavMeshSystem::getRandomPoint(flecs::entity navmeshEntity)
{
auto it = m_states.find(navmeshEntity.id());
if (it == m_states.end() || !it->second.navmesh)
@@ -584,8 +556,7 @@ void NavMeshSystem::rebuild(flecs::entity navmeshEntity)
comp.dirty = false;
}
void NavMeshSystem::setDebugDraw(flecs::entity navmeshEntity,
bool enable)
void NavMeshSystem::setDebugDraw(flecs::entity navmeshEntity, bool enable)
{
auto it = m_states.find(navmeshEntity.id());
if (it == m_states.end())

View File

@@ -8,10 +8,11 @@
#include <OgreLogManager.h>
#include <OgreRTShaderSystem.h>
ProceduralMaterialSystem::ProceduralMaterialSystem(flecs::world& world, Ogre::SceneManager* sceneMgr)
: m_world(world)
, m_sceneMgr(sceneMgr)
, m_query(world.query<ProceduralMaterialComponent>())
ProceduralMaterialSystem::ProceduralMaterialSystem(flecs::world &world,
Ogre::SceneManager *sceneMgr)
: m_world(world)
, m_sceneMgr(sceneMgr)
, m_query(world.query<ProceduralMaterialComponent>())
{
}
@@ -19,134 +20,165 @@ ProceduralMaterialSystem::~ProceduralMaterialSystem() = default;
void ProceduralMaterialSystem::initialize()
{
if (m_initialized) return;
m_initialized = true;
Ogre::LogManager::getSingleton().logMessage("ProceduralMaterialSystem initialized");
if (m_initialized)
return;
m_initialized = true;
Ogre::LogManager::getSingleton().logMessage(
"ProceduralMaterialSystem initialized");
}
void ProceduralMaterialSystem::update()
{
if (!m_initialized) return;
if (!m_initialized)
return;
m_query.each([&](flecs::entity entity, ProceduralMaterialComponent& component) {
bool needsRecreate = component.dirty || !component.created;
// Check if texture has changed (new version)
if (!needsRecreate && component.diffuseTextureEntity.is_alive() &&
component.diffuseTextureEntity.has<ProceduralTextureComponent>()) {
const auto& tex = component.diffuseTextureEntity.get<ProceduralTextureComponent>();
if (tex.version != component.textureVersion) {
needsRecreate = true;
}
}
if (needsRecreate) {
createMaterial(entity, component);
}
});
m_query.each([&](flecs::entity entity,
ProceduralMaterialComponent &component) {
bool needsRecreate = component.dirty || !component.created;
// Check if texture has changed (new version)
if (!needsRecreate &&
component.diffuseTextureEntity.is_alive() &&
component.diffuseTextureEntity
.has<ProceduralTextureComponent>()) {
const auto &tex =
component.diffuseTextureEntity
.get<ProceduralTextureComponent>();
if (tex.version != component.textureVersion) {
needsRecreate = true;
}
}
if (needsRecreate) {
createMaterial(entity, component);
}
});
}
void ProceduralMaterialSystem::createMaterial(flecs::entity entity, ProceduralMaterialComponent& component)
void ProceduralMaterialSystem::createMaterial(
flecs::entity entity, ProceduralMaterialComponent &component)
{
try {
// Generate unique material name if not set
if (component.materialName.empty()) {
component.materialName = "ProceduralMat_" + std::to_string(entity.id()) + "_" + std::to_string(m_createdCount++);
}
// Destroy old material if exists
if (component.ogreMaterial) {
destroyMaterial(component);
}
// Create new material
component.ogreMaterial = Ogre::MaterialManager::getSingleton().create(
component.materialName,
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
// Get the default technique and pass
Ogre::Technique* technique = component.ogreMaterial->getTechnique(0);
Ogre::Pass* pass = technique->getPass(0);
// Set material colors
pass->setAmbient(component.ambient[0], component.ambient[1], component.ambient[2]);
pass->setDiffuse(component.diffuse[0], component.diffuse[1], component.diffuse[2], 1.0f);
pass->setSpecular(component.specular[0], component.specular[1], component.specular[2], 1.0f);
pass->setShininess(component.shininess);
// Add diffuse texture if we have a reference to a ProceduralTexture entity
unsigned int currentTexVersion = 0;
if (component.diffuseTextureEntity.is_alive() &&
component.diffuseTextureEntity.has<ProceduralTextureComponent>()) {
const auto& textureComp = component.diffuseTextureEntity.get<ProceduralTextureComponent>();
currentTexVersion = textureComp.version;
if (textureComp.generated && !textureComp.textureName.empty()) {
Ogre::TextureUnitState* texUnit = pass->createTextureUnitState();
texUnit->setTextureName(textureComp.textureName);
texUnit->setTextureAddressingMode(Ogre::TAM_CLAMP);
}
}
// Register with RTSS to generate shaders (fixes "RenderSystem does not support FixedFunction")
Ogre::RTShader::ShaderGenerator *shaderGen =
Ogre::RTShader::ShaderGenerator::getSingletonPtr();
if (shaderGen && component.ogreMaterial) {
shaderGen->createShaderBasedTechnique(
*component.ogreMaterial,
Ogre::MaterialManager::DEFAULT_SCHEME_NAME,
Ogre::RTShader::ShaderGenerator::DEFAULT_SCHEME_NAME);
shaderGen->validateMaterial(
Ogre::RTShader::ShaderGenerator::DEFAULT_SCHEME_NAME,
component.materialName);
}
try {
// Generate unique material name if not set
if (component.materialName.empty()) {
component.materialName =
"ProceduralMat_" + std::to_string(entity.id()) +
"_" + std::to_string(m_createdCount++);
}
component.created = true;
component.dirty = false;
component.textureVersion = currentTexVersion;
Ogre::LogManager::getSingleton().logMessage(
"ProceduralMaterial: Created '" + component.materialName + "'");
} catch (const Ogre::Exception& e) {
Ogre::LogManager::getSingleton().logMessage(
"ProceduralMaterial ERROR: " + e.getDescription());
component.dirty = true; // Will retry
}
// Destroy old material if exists
if (component.ogreMaterial) {
destroyMaterial(component);
}
// Create new material
component.ogreMaterial =
Ogre::MaterialManager::getSingleton().create(
component.materialName,
Ogre::ResourceGroupManager::
DEFAULT_RESOURCE_GROUP_NAME);
// Get the default technique and pass
Ogre::Technique *technique =
component.ogreMaterial->getTechnique(0);
Ogre::Pass *pass = technique->getPass(0);
// Set material colors
pass->setAmbient(component.ambient[0], component.ambient[1],
component.ambient[2]);
pass->setDiffuse(component.diffuse[0], component.diffuse[1],
component.diffuse[2], 1.0f);
pass->setSpecular(component.specular[0], component.specular[1],
component.specular[2], 1.0f);
pass->setShininess(component.shininess);
// Cull back-facing geometry
pass->setCullingMode(Ogre::CULL_CLOCKWISE);
// Add diffuse texture if we have a reference to a ProceduralTexture entity
unsigned int currentTexVersion = 0;
if (component.diffuseTextureEntity.is_alive() &&
component.diffuseTextureEntity
.has<ProceduralTextureComponent>()) {
const auto &textureComp =
component.diffuseTextureEntity
.get<ProceduralTextureComponent>();
currentTexVersion = textureComp.version;
if (textureComp.generated &&
!textureComp.textureName.empty()) {
Ogre::TextureUnitState *texUnit =
pass->createTextureUnitState();
texUnit->setTextureName(
textureComp.textureName);
texUnit->setTextureAddressingMode(
Ogre::TAM_CLAMP);
}
}
// Register with RTSS to generate shaders (fixes "RenderSystem does not support FixedFunction")
Ogre::RTShader::ShaderGenerator *shaderGen =
Ogre::RTShader::ShaderGenerator::getSingletonPtr();
if (shaderGen && component.ogreMaterial) {
shaderGen->createShaderBasedTechnique(
*component.ogreMaterial,
Ogre::MaterialManager::DEFAULT_SCHEME_NAME,
Ogre::RTShader::ShaderGenerator::
DEFAULT_SCHEME_NAME);
shaderGen->validateMaterial(
Ogre::RTShader::ShaderGenerator::
DEFAULT_SCHEME_NAME,
component.materialName);
}
component.created = true;
component.dirty = false;
component.textureVersion = currentTexVersion;
Ogre::LogManager::getSingleton().logMessage(
"ProceduralMaterial: Created '" +
component.materialName + "'");
} catch (const Ogre::Exception &e) {
Ogre::LogManager::getSingleton().logMessage(
"ProceduralMaterial ERROR: " + e.getDescription());
component.dirty = true; // Will retry
}
}
void ProceduralMaterialSystem::destroyMaterial(ProceduralMaterialComponent& component)
void ProceduralMaterialSystem::destroyMaterial(
ProceduralMaterialComponent &component)
{
if (component.ogreMaterial) {
try {
Ogre::MaterialManager::getSingleton().remove(component.ogreMaterial);
} catch (...) {
// Ignore errors during cleanup
}
component.ogreMaterial.reset();
}
if (component.ogreMaterial) {
try {
Ogre::MaterialManager::getSingleton().remove(
component.ogreMaterial);
} catch (...) {
// Ignore errors during cleanup
}
component.ogreMaterial.reset();
}
}
void ProceduralMaterialSystem::recreateMaterial(flecs::entity entity)
{
if (!entity.is_alive() || !entity.has<ProceduralMaterialComponent>()) {
return;
}
entity.get_mut<ProceduralMaterialComponent>().markDirty();
if (!entity.is_alive() || !entity.has<ProceduralMaterialComponent>()) {
return;
}
entity.get_mut<ProceduralMaterialComponent>().markDirty();
}
std::string ProceduralMaterialSystem::getMaterialName(flecs::entity entity)
{
if (!entity.is_alive() || !entity.has<ProceduralMaterialComponent>()) {
return "";
}
return entity.get<ProceduralMaterialComponent>().materialName;
if (!entity.is_alive() || !entity.has<ProceduralMaterialComponent>()) {
return "";
}
return entity.get<ProceduralMaterialComponent>().materialName;
}
bool ProceduralMaterialSystem::materialExists(const std::string& name)
bool ProceduralMaterialSystem::materialExists(const std::string &name)
{
return Ogre::MaterialManager::getSingleton().resourceExists(name);
return Ogre::MaterialManager::getSingleton().resourceExists(name);
}

View File

@@ -115,21 +115,26 @@ bool SceneSerializer::loadFromFile(const std::string &filepath,
// Clear entity map for new load
m_entityMap.clear();
// Load entities
// First pass: create all entities and populate the entity map
if (scene.contains("entities") &&
scene["entities"].is_array()) {
Ogre::LogManager::getSingleton().logMessage(
"SceneSerializer: Loading " +
Ogre::StringConverter::toString(
scene["entities"].size()) +
" entities");
" entities (first pass)");
for (const auto &entityJson : scene["entities"]) {
deserializeEntity(entityJson,
flecs::entity::null(),
uiSystem);
deserializeEntityFirstPass(
entityJson, flecs::entity::null(),
uiSystem);
}
}
// Second pass: resolve material entity references
Ogre::LogManager::getSingleton().logMessage(
"SceneSerializer: Resolving material references (second pass)");
resolveMaterialReferences();
m_entityMap.clear();
return true;
} catch (const std::exception &e) {
@@ -221,7 +226,8 @@ nlohmann::json SceneSerializer::serializeEntity(flecs::entity entity)
}
if (entity.has<AnimationTreeTemplate>()) {
json["animationTreeTemplate"] = serializeAnimationTreeTemplate(entity);
json["animationTreeTemplate"] =
serializeAnimationTreeTemplate(entity);
}
if (entity.has<StartupMenuComponent>()) {
@@ -263,7 +269,8 @@ nlohmann::json SceneSerializer::serializeEntity(flecs::entity entity)
}
if (entity.has<NavMeshGeometrySource>()) {
json["navMeshGeometrySource"] = serializeNavMeshGeometrySource(entity);
json["navMeshGeometrySource"] =
serializeNavMeshGeometrySource(entity);
}
// Buoyancy components
@@ -408,7 +415,8 @@ void SceneSerializer::deserializeEntity(const nlohmann::json &json,
}
if (json.contains("animationTreeTemplate")) {
deserializeAnimationTreeTemplate(entity, json["animationTreeTemplate"]);
deserializeAnimationTreeTemplate(entity,
json["animationTreeTemplate"]);
}
if (json.contains("startupMenu")) {
@@ -454,7 +462,8 @@ void SceneSerializer::deserializeEntity(const nlohmann::json &json,
}
if (json.contains("navMeshGeometrySource")) {
deserializeNavMeshGeometrySource(entity, json["navMeshGeometrySource"]);
deserializeNavMeshGeometrySource(entity,
json["navMeshGeometrySource"]);
}
// Buoyancy components
@@ -501,6 +510,370 @@ void SceneSerializer::deserializeEntity(const nlohmann::json &json,
}
}
void SceneSerializer::deserializeEntityFirstPass(const nlohmann::json &json,
flecs::entity parent,
EditorUISystem *uiSystem)
{
// Create new entity
flecs::entity entity = m_world.entity();
entity.add<EditorMarkerComponent>();
// Store in map for potential future reference
if (json.contains("id")) {
uint64_t id = json["id"];
m_entityMap[id] = entity;
}
// Set parent relationship
if (parent.is_valid() && parent != 0) {
entity.child_of(parent);
}
// Deserialize components that don't depend on material references
if (json.contains("name")) {
deserializeEntityName(entity, json["name"]);
} else {
entity.set<EntityNameComponent>(EntityNameComponent("Entity"));
}
if (json.contains("transform")) {
deserializeTransform(entity, json["transform"], parent);
}
if (json.contains("renderable")) {
deserializeRenderable(entity, json["renderable"]);
}
if (json.contains("rigidBody")) {
deserializeRigidBody(entity, json["rigidBody"]);
}
if (json.contains("collider")) {
deserializeCollider(entity, json["collider"]);
}
if (json.contains("light")) {
deserializeLight(entity, json["light"]);
}
if (json.contains("camera")) {
deserializeCamera(entity, json["camera"]);
}
if (json.contains("lodSettings")) {
deserializeLodSettings(entity, json["lodSettings"]);
}
if (json.contains("lod")) {
deserializeLod(entity, json["lod"]);
}
if (json.contains("staticGeometry")) {
deserializeStaticGeometry(entity, json["staticGeometry"]);
}
if (json.contains("staticGeometryMember")) {
deserializeStaticGeometryMember(entity,
json["staticGeometryMember"]);
}
if (json.contains("proceduralTexture")) {
deserializeProceduralTexture(entity, json["proceduralTexture"]);
}
if (json.contains("proceduralMaterial")) {
deserializeProceduralMaterial(entity,
json["proceduralMaterial"]);
}
if (json.contains("primitive")) {
deserializePrimitive(entity, json["primitive"]);
}
if (json.contains("character")) {
deserializeCharacter(entity, json["character"]);
}
if (json.contains("characterSlots")) {
deserializeCharacterSlots(entity, json["characterSlots"]);
}
if (json.contains("animationTree")) {
deserializeAnimationTree(entity, json["animationTree"]);
}
if (json.contains("animationTreeTemplate")) {
deserializeAnimationTreeTemplate(entity,
json["animationTreeTemplate"]);
}
if (json.contains("startupMenu")) {
deserializeStartupMenu(entity, json["startupMenu"]);
}
if (json.contains("playerController")) {
deserializePlayerController(entity, json["playerController"]);
}
if (json.contains("triangleBuffer")) {
deserializeTriangleBuffer(entity, json["triangleBuffer"]);
}
// CellGrid/Town components - deserialize WITHOUT resolving material references
// Material references will be resolved in the second pass
if (json.contains("cellGrid")) {
deserializeCellGrid(entity, json["cellGrid"]);
}
if (json.contains("town")) {
// Store the raw JSON for second-pass resolution
m_pendingTownResolutions.push_back({ entity, json["town"] });
// Still deserialize basic town data (name, colorRects, etc.)
// but skip material entity resolution
TownComponent town;
town.townName = json["town"].value("townName", "New Town");
town.materialName = json["town"].value("materialName", "");
town.proceduralMaterialEntityId =
json["town"].value("proceduralMaterialEntityId", "");
town.textureRectName =
json["town"].value("textureRectName", "");
// Deserialize color rects
if (json["town"].contains("colorRects") &&
json["town"]["colorRects"].is_object()) {
for (auto &[name, rectJson] :
json["town"]["colorRects"].items()) {
TownComponent::ColorRect rect;
rect.left = rectJson.value("left", 0.0f);
rect.top = rectJson.value("top", 0.0f);
rect.right = rectJson.value("right", 1.0f);
rect.bottom = rectJson.value("bottom", 1.0f);
if (rectJson.contains("color")) {
auto &c = rectJson["color"];
rect.color.r = c.value("r", 1.0f);
rect.color.g = c.value("g", 1.0f);
rect.color.b = c.value("b", 1.0f);
rect.color.a = c.value("a", 1.0f);
}
town.colorRects[name] = rect;
}
}
town.dirty = true;
town.materialDirty = true;
entity.set<TownComponent>(town);
}
if (json.contains("district")) {
// Store the raw JSON for second-pass resolution
m_pendingDistrictResolutions.push_back(
{ entity, json["district"] });
// Still deserialize basic district data
DistrictComponent district;
district.radius = json["district"].value("radius", 50.0f);
district.elevation = json["district"].value("elevation", 0.0f);
district.height = json["district"].value("height", 0.2f);
district.isPlaza = json["district"].value("isPlaza", false);
district.proceduralMaterialEntityId = json["district"].value(
"proceduralMaterialEntityId", "");
district.textureRectName =
json["district"].value("textureRectName", "");
district.friction = json["district"].value("friction", 0.5f);
if (json["district"].contains("lotTemplateNames") &&
json["district"]["lotTemplateNames"].is_array()) {
for (const auto &name :
json["district"]["lotTemplateNames"]) {
if (name.is_string()) {
district.lotTemplateNames.push_back(
name);
}
}
}
district.dirty = true;
entity.set<DistrictComponent>(district);
}
if (json.contains("lot")) {
// Store the raw JSON for second-pass resolution
m_pendingLotResolutions.push_back({ entity, json["lot"] });
// Still deserialize basic lot data
LotComponent lot;
lot.width = json["lot"].value("width", 10);
lot.depth = json["lot"].value("depth", 10);
lot.elevation = json["lot"].value("elevation", 0.0f);
lot.angle = json["lot"].value("angle", 0.0f);
lot.offsetX = json["lot"].value("offsetX", 0.0f);
lot.offsetZ = json["lot"].value("offsetZ", 0.0f);
lot.templateName = json["lot"].value("templateName", "");
lot.proceduralMaterialEntityId =
json["lot"].value("proceduralMaterialEntityId", "");
lot.textureRectName = json["lot"].value("textureRectName", "");
lot.friction = json["lot"].value("friction", 0.5f);
lot.dirty = true;
entity.set<LotComponent>(lot);
}
if (json.contains("room")) {
deserializeRoom(entity, json["room"]);
}
if (json.contains("roof")) {
deserializeRoof(entity, json["roof"]);
}
if (json.contains("furnitureTemplate")) {
deserializeFurnitureTemplate(entity, json["furnitureTemplate"]);
}
if (json.contains("clearArea")) {
deserializeClearArea(entity, json["clearArea"]);
}
if (json.contains("navMesh")) {
deserializeNavMesh(entity, json["navMesh"]);
}
if (json.contains("navMeshGeometrySource")) {
deserializeNavMeshGeometrySource(entity,
json["navMeshGeometrySource"]);
}
// Buoyancy components
if (json.contains("buoyancyInfo")) {
deserializeBuoyancyInfo(entity, json["buoyancyInfo"]);
}
if (json.contains("waterPhysics")) {
deserializeWaterPhysics(entity, json["waterPhysics"]);
}
if (json.contains("waterPlane")) {
deserializeWaterPlane(entity, json["waterPlane"]);
}
if (json.contains("actionDatabase")) {
deserializeActionDatabase(entity, json["actionDatabase"]);
}
if (json.contains("actionDebug")) {
deserializeActionDebug(entity, json["actionDebug"]);
}
if (json.contains("behaviorTree")) {
deserializeBehaviorTree(entity, json["behaviorTree"]);
}
if (json.contains("sun")) {
deserializeSun(entity, json["sun"]);
}
if (json.contains("skybox")) {
deserializeSkybox(entity, json["skybox"]);
}
// Add to UI system if provided
if (uiSystem) {
uiSystem->addEntity(entity);
}
// Deserialize children recursively (first pass)
if (json.contains("children") && json["children"].is_array()) {
for (const auto &childJson : json["children"]) {
deserializeEntityFirstPass(childJson, entity, uiSystem);
}
}
}
void SceneSerializer::resolveMaterialReferences()
{
// Resolve Town material references
for (auto &pending : m_pendingTownResolutions) {
flecs::entity entity = pending.entity;
if (!entity.is_alive())
continue;
auto &town = entity.get_mut<TownComponent>();
if (!town.proceduralMaterialEntityId.empty()) {
try {
uint64_t matId = std::stoull(
town.proceduralMaterialEntityId);
auto matIt = m_entityMap.find(matId);
if (matIt != m_entityMap.end()) {
flecs::entity matEntity = matIt->second;
if (matEntity.is_alive() &&
matEntity.has<
ProceduralMaterialComponent>()) {
town.proceduralMaterialEntity =
matEntity;
Ogre::LogManager::getSingleton().logMessage(
"SceneSerializer: Resolved Town material entity " +
Ogre::StringConverter::
toString(
matId));
}
}
} catch (...) {
}
}
}
// Resolve District material references
for (auto &pending : m_pendingDistrictResolutions) {
flecs::entity entity = pending.entity;
if (!entity.is_alive())
continue;
auto &district = entity.get_mut<DistrictComponent>();
if (!district.proceduralMaterialEntityId.empty()) {
try {
uint64_t matId = std::stoull(
district.proceduralMaterialEntityId);
auto matIt = m_entityMap.find(matId);
if (matIt != m_entityMap.end()) {
flecs::entity matEntity = matIt->second;
if (matEntity.is_alive() &&
matEntity.has<
ProceduralMaterialComponent>()) {
district.proceduralMaterialEntity =
matEntity;
Ogre::LogManager::getSingleton().logMessage(
"SceneSerializer: Resolved District material entity " +
Ogre::StringConverter::
toString(
matId));
}
}
} catch (...) {
}
}
}
// Resolve Lot material references
for (auto &pending : m_pendingLotResolutions) {
flecs::entity entity = pending.entity;
if (!entity.is_alive())
continue;
auto &lot = entity.get_mut<LotComponent>();
if (!lot.proceduralMaterialEntityId.empty()) {
try {
uint64_t matId = std::stoull(
lot.proceduralMaterialEntityId);
auto matIt = m_entityMap.find(matId);
if (matIt != m_entityMap.end()) {
flecs::entity matEntity = matIt->second;
if (matEntity.is_alive() &&
matEntity.has<
ProceduralMaterialComponent>()) {
lot.proceduralMaterialEntity =
matEntity;
Ogre::LogManager::getSingleton().logMessage(
"SceneSerializer: Resolved Lot material entity " +
Ogre::StringConverter::
toString(
matId));
}
}
} catch (...) {
}
}
}
// Clear pending resolutions
m_pendingTownResolutions.clear();
m_pendingDistrictResolutions.clear();
m_pendingLotResolutions.clear();
}
nlohmann::json SceneSerializer::serializeTransform(flecs::entity entity)
{
auto &transform = entity.get<TransformComponent>();
@@ -1645,7 +2018,8 @@ void SceneSerializer::deserializeAnimationTree(flecs::entity entity,
entity.set<AnimationTreeComponent>(at);
}
nlohmann::json SceneSerializer::serializeAnimationTreeTemplate(flecs::entity entity)
nlohmann::json
SceneSerializer::serializeAnimationTreeTemplate(flecs::entity entity)
{
auto &templ = entity.get<AnimationTreeTemplate>();
nlohmann::json json;
@@ -1654,8 +2028,8 @@ nlohmann::json SceneSerializer::serializeAnimationTreeTemplate(flecs::entity ent
return json;
}
void SceneSerializer::deserializeAnimationTreeTemplate(flecs::entity entity,
const nlohmann::json &json)
void SceneSerializer::deserializeAnimationTreeTemplate(
flecs::entity entity, const nlohmann::json &json)
{
AnimationTreeTemplate templ;
templ.name = json.value("name", "");
@@ -2400,7 +2774,6 @@ void SceneSerializer::deserializeWaterPhysics(flecs::entity entity,
entity.set<WaterPhysics>(water);
}
nlohmann::json SceneSerializer::serializeSun(flecs::entity entity)
{
const SunComponent &sun = entity.get<SunComponent>();
@@ -2409,8 +2782,7 @@ nlohmann::json SceneSerializer::serializeSun(flecs::entity entity)
json["enabled"] = sun.enabled;
json["timeOfDay"] = sun.timeOfDay;
json["timeSpeed"] = sun.timeSpeed;
json["sunColor"] = { sun.sunColor.r, sun.sunColor.g,
sun.sunColor.b };
json["sunColor"] = { sun.sunColor.r, sun.sunColor.g, sun.sunColor.b };
json["moonColor"] = { sun.moonColor.r, sun.moonColor.g,
sun.moonColor.b };
json["ambientDay"] = { sun.ambientDay.r, sun.ambientDay.g,
@@ -2499,11 +2871,12 @@ void SceneSerializer::deserializeSun(flecs::entity entity,
json["ambientSunrise"][0], json["ambientSunrise"][1],
json["ambientSunrise"][2]);
}
if (json.contains("ambientSunset") && json["ambientSunset"].is_array() &&
if (json.contains("ambientSunset") &&
json["ambientSunset"].is_array() &&
json["ambientSunset"].size() >= 3) {
sun.ambientSunset = Ogre::ColourValue(json["ambientSunset"][0],
json["ambientSunset"][1],
json["ambientSunset"][2]);
json["ambientSunset"][1],
json["ambientSunset"][2]);
}
sun.showSunSphere = json.value("showSunSphere", true);
sun.showMoonSphere = json.value("showMoonSphere", true);
@@ -2526,8 +2899,8 @@ void SceneSerializer::deserializeSkybox(flecs::entity entity,
if (json.contains("dayTopColor") && json["dayTopColor"].is_array() &&
json["dayTopColor"].size() >= 3) {
sky.dayTopColor = Ogre::ColourValue(json["dayTopColor"][0],
json["dayTopColor"][1],
json["dayTopColor"][2]);
json["dayTopColor"][1],
json["dayTopColor"][2]);
}
if (json.contains("dayBottomColor") &&
json["dayBottomColor"].is_array() &&
@@ -2536,18 +2909,20 @@ void SceneSerializer::deserializeSkybox(flecs::entity entity,
json["dayBottomColor"][0], json["dayBottomColor"][1],
json["dayBottomColor"][2]);
}
if (json.contains("nightTopColor") && json["nightTopColor"].is_array() &&
if (json.contains("nightTopColor") &&
json["nightTopColor"].is_array() &&
json["nightTopColor"].size() >= 3) {
sky.nightTopColor = Ogre::ColourValue(json["nightTopColor"][0],
json["nightTopColor"][1],
json["nightTopColor"][2]);
json["nightTopColor"][1],
json["nightTopColor"][2]);
}
if (json.contains("nightBottomColor") &&
json["nightBottomColor"].is_array() &&
json["nightBottomColor"].size() >= 3) {
sky.nightBottomColor = Ogre::ColourValue(
json["nightBottomColor"][0], json["nightBottomColor"][1],
json["nightBottomColor"][2]);
sky.nightBottomColor =
Ogre::ColourValue(json["nightBottomColor"][0],
json["nightBottomColor"][1],
json["nightBottomColor"][2]);
}
if (json.contains("sunriseColor") && json["sunriseColor"].is_array() &&
json["sunriseColor"].size() >= 3) {
@@ -2569,7 +2944,6 @@ void SceneSerializer::deserializeSkybox(flecs::entity entity,
entity.set<SkyboxComponent>(sky);
}
nlohmann::json SceneSerializer::serializeWaterPlane(flecs::entity entity)
{
const WaterPlane &wp = entity.get<WaterPlane>();
@@ -2602,9 +2976,10 @@ void SceneSerializer::deserializeWaterPlane(flecs::entity entity,
json.value("autoUpdateFromWaterPhysics", true);
if (json.contains("waterColor") && json["waterColor"].is_array() &&
json["waterColor"].size() >= 4) {
wp.waterColor = Ogre::ColourValue(
json["waterColor"][0], json["waterColor"][1],
json["waterColor"][2], json["waterColor"][3]);
wp.waterColor = Ogre::ColourValue(json["waterColor"][0],
json["waterColor"][1],
json["waterColor"][2],
json["waterColor"][3]);
}
wp.reflectivity = json.value("reflectivity", 0.5f);
wp.waveSpeed = json.value("waveSpeed", 1.0f);
@@ -2615,7 +2990,6 @@ void SceneSerializer::deserializeWaterPlane(flecs::entity entity,
entity.set<WaterPlane>(wp);
}
// ============================================================================
// AI/GOAP Component Serialization Helpers
// ============================================================================
@@ -2667,9 +3041,10 @@ static void deserializeGoapBlackboard(GoapBlackboard &bb,
if (json.contains("vec3Values") && json["vec3Values"].is_object()) {
for (auto &[key, val] : json["vec3Values"].items()) {
if (val.is_array() && val.size() >= 3)
bb.vec3Values[key] = Ogre::Vector3(
val[0].get<float>(), val[1].get<float>(),
val[2].get<float>());
bb.vec3Values[key] =
Ogre::Vector3(val[0].get<float>(),
val[1].get<float>(),
val[2].get<float>());
}
}
}
@@ -2812,8 +3187,8 @@ void SceneSerializer::deserializeActionDatabase(flecs::entity entity,
for (const auto &entry : json["bitNames"]) {
if (entry.contains("index") && entry.contains("name"))
GoapBlackboard::setBitName(
entry["index"].get<int>(),
entry["name"].get<std::string>());
entry["index"].get<int>(),
entry["name"].get<std::string>());
}
}
@@ -2866,7 +3241,6 @@ void SceneSerializer::deserializeBehaviorTree(flecs::entity entity,
entity.set<BehaviorTreeComponent>(bt);
}
nlohmann::json SceneSerializer::serializeNavMesh(flecs::entity entity)
{
const NavMeshComponent &nm = entity.get<NavMeshComponent>();
@@ -2908,7 +3282,8 @@ void SceneSerializer::deserializeNavMesh(flecs::entity entity,
entity.set<NavMeshComponent>(nm);
}
nlohmann::json SceneSerializer::serializeNavMeshGeometrySource(flecs::entity entity)
nlohmann::json
SceneSerializer::serializeNavMeshGeometrySource(flecs::entity entity)
{
const NavMeshGeometrySource &src = entity.get<NavMeshGeometrySource>();
nlohmann::json json;
@@ -2916,8 +3291,8 @@ nlohmann::json SceneSerializer::serializeNavMeshGeometrySource(flecs::entity ent
return json;
}
void SceneSerializer::deserializeNavMeshGeometrySource(flecs::entity entity,
const nlohmann::json &json)
void SceneSerializer::deserializeNavMeshGeometrySource(
flecs::entity entity, const nlohmann::json &json)
{
NavMeshGeometrySource src;
src.include = json.value("include", true);

View File

@@ -43,6 +43,15 @@ private:
void deserializeEntity(const nlohmann::json &json, flecs::entity parent,
EditorUISystem *uiSystem);
// First-pass deserialization: creates entities and populates entity map
// without resolving material references (deferred to second pass)
void deserializeEntityFirstPass(const nlohmann::json &json,
flecs::entity parent,
EditorUISystem *uiSystem);
// Second-pass: resolve material entity references for Town, District, Lot
void resolveMaterialReferences();
// Component serialization
nlohmann::json serializeTransform(flecs::entity entity);
nlohmann::json serializeRenderable(flecs::entity entity);
@@ -115,7 +124,7 @@ private:
void deserializeAnimationTree(flecs::entity entity,
const nlohmann::json &json);
void deserializeAnimationTreeTemplate(flecs::entity entity,
const nlohmann::json &json);
const nlohmann::json &json);
void deserializeStartupMenu(flecs::entity entity,
const nlohmann::json &json);
void deserializePlayerController(flecs::entity entity,
@@ -137,7 +146,7 @@ private:
void deserializeNavMesh(flecs::entity entity,
const nlohmann::json &json);
void deserializeNavMeshGeometrySource(flecs::entity entity,
const nlohmann::json &json);
const nlohmann::json &json);
// Buoyancy component serialization
nlohmann::json serializeBuoyancyInfo(flecs::entity entity);
@@ -156,7 +165,8 @@ private:
nlohmann::json serializeSun(flecs::entity entity);
nlohmann::json serializeSkybox(flecs::entity entity);
void deserializeSun(flecs::entity entity, const nlohmann::json &json);
void deserializeSkybox(flecs::entity entity, const nlohmann::json &json);
void deserializeSkybox(flecs::entity entity,
const nlohmann::json &json);
// AI/GOAP serialization
nlohmann::json serializeActionDatabase(flecs::entity entity);
@@ -175,6 +185,16 @@ private:
// Track entity ID mapping for parent/child relationships
std::unordered_map<uint64_t, flecs::entity> m_entityMap;
// Pending material entity resolutions (two-pass load)
// Store entity reference directly (not just ID) so we don't need to look up in m_entityMap
struct PendingResolution {
flecs::entity entity;
nlohmann::json data;
};
std::vector<PendingResolution> m_pendingTownResolutions;
std::vector<PendingResolution> m_pendingDistrictResolutions;
std::vector<PendingResolution> m_pendingLotResolutions;
};
#endif // EDITSCENE_SCENESERIALIZER_HPP