Material fixes, playing with navmesh
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user