Hi, all!
Does StaticGeometry suppoort LOD? is it possible to convert StaticGeometry to mesh?
The reason I ask is when add lots of details to StaticGeometry it become too detailed and aliasing occurs at distance,
so I'd like to reduce details at distance.
Does StaticGeometry suppoort LOD? is it possible to convert StaticGeometry to mesh?
-
slapin
- Bronze Sponsor

- Posts: 339
- Joined: Fri May 23, 2025 5:04 pm
- x 24
Does StaticGeometry suppoort LOD? is it possible to convert StaticGeometry to mesh?
-
paroj
- OGRE Team Member

- Posts: 2274
- Joined: Sun Mar 30, 2014 2:51 pm
- x 1239
-
slapin
- Bronze Sponsor

- Posts: 339
- Joined: Fri May 23, 2025 5:04 pm
- x 24
Re: Does StaticGeometry suppoort LOD? is it possible to convert StaticGeometry to mesh?
Toying with LODs lead to crash.
Code: Select all
what(): InternalErrorException: Somehow we couldn't fit the requested geometry even in a brand new GeometryBucket!! Must be a bug, please report. in StaticGeometry::MaterialBucket::assign at /media/slapin/library/ogre3/ogre/OgreMain/src/OgreStaticGeometry.cpp (line 1192)
The actual code doing this:
Code: Select all
struct harbourMaker {
Ogre::Entity *planks, *pillar, *beam;
harbourMaker(flecs::entity e)
{
planks = ECS::get<EngineData>().mScnMgr->createEntity(
"Plank" + Ogre::StringConverter::toString(e.id()),
"pier-plank.glb");
beam = ECS::get<EngineData>().mScnMgr->createEntity(
"Beam" + Ogre::StringConverter::toString(e.id()),
"pier-beam.glb");
pillar = ECS::get<EngineData>().mScnMgr->createEntity(
"Pillar" + Ogre::StringConverter::toString(e.id()),
"pier-pillar.glb");
Ogre::Entity *entities[] = { planks, pillar, beam };
for (Ogre::Entity *ent : entities) {
Ogre::MeshPtr mesh = ent->getMesh();
Ogre::LodConfig config(mesh);
config.advanced.useCompression = true;
config.advanced.preventPunchingHoles = true;
config.advanced.preventBreakingLines = true;
config.createGeneratedLodLevel(5, 0.5f);
config.createGeneratedLodLevel(7, 0.7f);
config.createGeneratedLodLevel(10, 0.85);
config.createGeneratedLodLevel(20, 0.95);
config.advanced.useBackgroundQueue = false;
Ogre::MeshLodGenerator::getSingleton().generateLodLevels(
config);
}
}
};
-
slapin
- Bronze Sponsor

- Posts: 339
- Joined: Fri May 23, 2025 5:04 pm
- x 24
Static Geometry vs PagedGeometry
Hi, all!
I need to procedurally produce a set of static geometry (piers out wood planks) from modular parts.
I tried to use StaticGemetry but it does not support manual LODs which prevents me making models look good at lower LOD levels as heavy Z-fighting is produced by detailed ones at distance and automatic LODs can't help there. So I ask, will PagedGeometry help with this case or I better go with manual mesh making and handling?
-
slapin
- Bronze Sponsor

- Posts: 339
- Joined: Fri May 23, 2025 5:04 pm
- x 24
Procedural mesh creationg from tiny parts
Hi, all!
I have a set of things which need to be made of tiny parts and support LOD properly.
The models are not moving. To make them I need to
- Combine the parts as needed into single mesh for batching.
- Create LODs for mesh to look better/cleaner at distance
- Create physical collider from it.
The form and set is known at run time when the location is opened with editor and the model is created to fit with terrain.
That stuff is handled. The problem is how to proceed farther. I started with StaticGeometry but I found that it does not support either manual LODs nor mesh production which means I can't use it.
Later I looked at PagedGeometry and found it is more intelligent with LODs but still can't help me with producing the collider. Also it only supports individual mesh LODs but not whole thing LOD which is both less efficient and looks ugly, too. So I can't use it for this task too (but it is great for grass/trees).
So now I'm scratching the head about what I should do -
- do everything manually from scratch
- do something else.
Any help is appreciated.
@paroj do you have any ideas on mind which I might missed?
-
slapin
- Bronze Sponsor

- Posts: 339
- Joined: Fri May 23, 2025 5:04 pm
- x 24
Re: Procedural mesh creationg from tiny parts
I guess the algorithm should be the following:
- Add all used tiles meshes into set. Require all tiles to use same material and have single material.
- Add list of integer coordinates for each tile.
- Generate shared vertex buffer containing tile vertices modified by coordinates.
- Generate index buffer
- Optionally optimize for duplicate vertices
- Generate LODs.
- Generate collider.
-
paroj
- OGRE Team Member

- Posts: 2274
- Joined: Sun Mar 30, 2014 2:51 pm
- x 1239
Re: Does StaticGeometry suppoort LOD? is it possible to convert StaticGeometry to mesh?
you cannot generate LOD for StaticGeometry, but StaticGeometry does handle Meshes that have LOD on them.
Take a look on the linked Test for how it is set-up.
The same applies to PagedGeometry. If you want to generate LOD for a merged mesh, then it is up to you to do it.
-
slapin
- Bronze Sponsor

- Posts: 339
- Joined: Fri May 23, 2025 5:04 pm
- x 24
Re: Does StaticGeometry suppoort LOD? is it possible to convert StaticGeometry to mesh?
paroj wrote: Fri Dec 12, 2025 3:41 pmyou cannot generate LOD for StaticGeometry, but StaticGeometry does handle Meshes that have LOD on them.
Take a look on the linked Test for how it is set-up.
The same applies to PagedGeometry. If you want to generate LOD for a merged mesh, then it is up to you to do it.
The way StaticGeometry and PagedGeometry handle LODs is not good for modular geometry as these treated as individual pieces chunked together
and automatic LOD produce extremely ugly result in this case (as individual modules are low poly already) and it uses LOD data of pieces.
So I see that I have to write my own merged mesh then produce lods for it and then feed it to physics and StaticGeometry/PagedGeometry.
Would appreciate example for efficient mesh merging (with actual merging and not producing lots of submeshes).
The rest is quite easy.
-
paroj
- OGRE Team Member

- Posts: 2274
- Joined: Sun Mar 30, 2014 2:51 pm
- x 1239
Re: Does StaticGeometry suppoort LOD? is it possible to convert StaticGeometry to mesh?
StaticGeometry itself is the example for that:
https://github.com/OGRECave/ogre/blob/c ... .cpp#L1405
maybe implementing StaticGeometry::convertToMesh is all you need
-
slapin
- Bronze Sponsor

- Posts: 339
- Joined: Fri May 23, 2025 5:04 pm
- x 24
Re: Does StaticGeometry suppoort LOD? is it possible to convert StaticGeometry to mesh?
Hi all!
paroj wrote: Fri Dec 12, 2025 5:09 pmStaticGeometry itself is the example for that:
https://github.com/OGRECave/ogre/blob/c ... .cpp#L1405maybe implementing StaticGeometry::convertToMesh is all you need
Toying with mesh merging I implemented this code:
Code: Select all
struct TiledMeshes {
struct Tile {
Ogre::String materialName;
std::shared_ptr<Ogre::VertexData> vertexData;
std::shared_ptr<Ogre::IndexData> indexData;
std::set<uint32_t> positions;
};
std::map<Ogre::String, Tile> tiles;
uint32_t packKey(const Ogre::Vector3i &position)
{
uint32_t key = 0;
key |= (uint32_t)(position[2] + 512) << 20;
key |= (uint32_t)(position[1] + 512) << 10;
key |= (uint32_t)(position[0] + 512) << 0;
return key;
}
void unpackKey(uint32_t key, Ogre::Vector3i &position)
{
uint32_t mask = 0x3ff;
position[0] = (int)(key & mask) - 512;
position[1] = (int)((key >> 10) & mask) - 512;
position[2] = (int)((key >> 20) & mask) - 512;
}
void setTile(const Ogre::String &name, const Ogre::Vector3i &position)
{
if (tiles.find(name) == tiles.end())
return;
tiles[name].positions.insert(packKey(position));
}
void clearTile(const Ogre::String &name, const Ogre::Vector3i &position)
{
if (tiles.find(name) == tiles.end())
return;
tiles[name].positions.erase(packKey(position));
}
void addTile(const Ogre::String &name, Ogre::MeshPtr mesh)
{
if (mesh->getSubMeshes().size() != 1)
return;
Ogre::SubMesh *submesh = mesh->getSubMesh(0);
Ogre::VertexData *vertexData;
if (submesh->useSharedVertices)
vertexData = mesh->sharedVertexData->clone();
else
vertexData = submesh->vertexData->clone();
tiles[name] = { submesh->getMaterialName(),
std::shared_ptr<Ogre::VertexData>(vertexData),
std::shared_ptr<Ogre::IndexData>(
submesh->indexData->clone()),
{} };
}
struct buildSettings {
Ogre::String meshName;
Ogre::String materialName;
Ogre::MeshPtr mesh;
Ogre::SubMesh *sm;
int vertexCount, vertexOffset;
int indexCount, indexOffset;
Ogre::VertexDeclaration *vdecl;
Ogre::HardwareVertexBufferSharedPtr vbuf;
Ogre::AxisAlignedBox bounds;
bool setBounds;
};
void configureSettings(struct buildSettings &settings)
{
settings.mesh = Ogre::MeshManager::getSingleton().createManual(
settings.meshName,
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
settings.mesh->createVertexData();
settings.sm = settings.mesh->createSubMesh();
settings.vertexCount = 0;
settings.indexCount = 0;
settings.vdecl = nullptr;
for (const auto &tile : tiles) {
settings.vertexCount +=
tile.second.vertexData->vertexCount *
tile.second.positions.size();
settings.indexCount +=
tile.second.indexData->indexCount *
tile.second.positions.size();
if (!settings.vdecl) {
settings.vdecl =
tile.second.vertexData
->vertexDeclaration->clone();
settings.materialName =
tile.second.materialName;
}
}
settings.mesh->sharedVertexData->vertexStart = 0;
settings.mesh->sharedVertexData->vertexCount =
settings.vertexCount;
settings.mesh->sharedVertexData->vertexDeclaration =
settings.vdecl;
settings.vbuf =
Ogre::HardwareBufferManager::getSingleton()
.createVertexBuffer(
settings.vdecl->getVertexSize(0),
settings.vertexCount,
Ogre::HBU_GPU_ONLY);
settings.mesh->sharedVertexData->vertexBufferBinding->setBinding(
0, settings.vbuf);
settings.sm->indexData->indexStart = 0;
settings.sm->indexData->indexCount = settings.indexCount;
settings.sm->indexData->indexBuffer =
Ogre::HardwareBufferManager::getSingleton()
.createIndexBuffer(
Ogre::HardwareIndexBuffer::IT_32BIT,
settings.sm->indexData->indexCount,
Ogre::HBU_GPU_ONLY);
settings.sm->setMaterialName(settings.materialName);
settings.setBounds = true;
}
void processIndex(struct buildSettings &settings,
const struct Tile &tile, unsigned int *dstIndices)
{
int j;
std::shared_ptr<Ogre::IndexData> srcIndexData = tile.indexData;
int srcIndexCount = srcIndexData->indexCount;
std::shared_ptr<Ogre::VertexData> srcVertexData =
tile.vertexData;
int srcVertexCount = srcVertexData->vertexCount;
Ogre::HardwareIndexBufferSharedPtr srcIbuf =
srcIndexData->indexBuffer;
Ogre::HardwareBufferLockGuard srcIndexLock(
srcIbuf, Ogre::HardwareBuffer::HBL_READ_ONLY);
if (srcIndexData->indexBuffer->getType() ==
Ogre::HardwareIndexBuffer::IT_32BIT) {
unsigned int *indices =
static_cast<unsigned int *>(srcIndexLock.pData);
for (j = 0; j < srcIndexCount; j++)
dstIndices[settings.indexOffset + j] =
indices[j] + settings.vertexOffset;
} else if (srcIndexData->indexBuffer->getType() ==
Ogre::HardwareIndexBuffer::IT_16BIT) {
unsigned short *indices = static_cast<unsigned short *>(
srcIndexLock.pData);
for (j = 0; j < srcIndexCount; j++)
dstIndices[settings.indexOffset + j] =
indices[j] + settings.vertexOffset;
}
}
void processSingleVertex(
struct buildSettings &settings,
const Ogre::VertexDeclaration::VertexElementList &srcElements,
uint32_t offset, unsigned char *srcData, unsigned char *dstData)
{
for (const auto &srcElement : srcElements) {
unsigned char *srcPtr, *dstPtr;
const Ogre::VertexElement *destElement =
settings.vdecl->findElementBySemantic(
srcElement.getSemantic(),
srcElement.getIndex());
if (!destElement)
goto out;
if (srcElement.getType() != destElement->getType() ||
srcElement.getSize() != destElement->getSize())
goto out;
srcPtr = srcData + srcElement.getOffset();
dstPtr = dstData + destElement->getOffset();
if (destElement->getSemantic() == Ogre::VES_POSITION) {
float *srcPositionData =
reinterpret_cast<float *>(srcPtr);
float *dstPositionData =
reinterpret_cast<float *>(dstPtr);
Ogre::Vector3 position(srcPositionData[0],
srcPositionData[1],
srcPositionData[2]);
Ogre::Vector3i offsetv;
unpackKey(offset, offsetv);
position.x += (float)offsetv[0];
position.y += (float)offsetv[1];
position.z += (float)offsetv[2];
dstPositionData[0] = position.x;
dstPositionData[1] = position.y;
dstPositionData[2] = position.z;
if (settings.setBounds) {
settings.bounds.setMinimum(position);
settings.bounds.setMaximum(position);
settings.setBounds = false;
} else
settings.bounds.merge(position);
} else if (destElement->getSemantic() ==
Ogre::VES_NORMAL) {
float *srcNormalData =
reinterpret_cast<float *>(srcPtr);
float *dstNormalData =
reinterpret_cast<float *>(dstPtr);
Ogre::Vector3 normal(srcNormalData[0],
srcNormalData[1],
srcNormalData[2]);
dstNormalData[0] = normal.x;
dstNormalData[1] = normal.y;
dstNormalData[2] = normal.z;
} else
memcpy(dstPtr, srcPtr, srcElement.getSize());
out:;
}
}
void processTile(struct buildSettings &settings,
const struct Tile &tile, uint32_t position,
unsigned char *dstpData)
{
std::shared_ptr<Ogre::VertexData> srcVertexData =
tile.vertexData;
const Ogre::VertexDeclaration *srcDecl =
srcVertexData->vertexDeclaration;
const Ogre::VertexBufferBinding *srcBind =
srcVertexData->vertexBufferBinding;
int srcVertexCount = srcVertexData->vertexCount;
Ogre::HardwareVertexBufferSharedPtr srcVbuf =
srcBind->getBuffer(0);
std::shared_ptr<Ogre::IndexData> srcIndexData = tile.indexData;
int srcIndexCount = srcIndexData->indexCount;
Ogre::HardwareBufferLockGuard srcVertexLock(
srcVbuf, 0, srcVertexCount * srcDecl->getVertexSize(0),
Ogre::HardwareBuffer::HBL_READ_ONLY);
const Ogre::VertexDeclaration::VertexElementList &srcElements =
srcDecl->getElements();
int j;
unsigned char *srcpData =
static_cast<unsigned char *>(srcVertexLock.pData);
for (j = 0; j < srcVertexCount; j++) {
unsigned char *srcData =
srcpData + j * srcVbuf->getVertexSize();
unsigned char *dstData =
dstpData + (settings.vertexOffset +
j) * settings.vbuf->getVertexSize();
processSingleVertex(settings, srcElements, position,
srcData, dstData);
}
}
Ogre::MeshPtr build(const Ogre::String &meshName)
{
buildSettings settings;
settings.meshName = meshName;
configureSettings(settings);
{
Ogre::HardwareBufferLockGuard vertexLock(
settings.vbuf, 0,
settings.vertexCount *
settings.vdecl->getVertexSize(0),
Ogre::HardwareBuffer::HBL_NO_OVERWRITE);
Ogre::HardwareBufferLockGuard indexLock(
settings.sm->indexData->indexBuffer,
Ogre::HardwareBuffer::HBL_NO_OVERWRITE);
settings.vertexOffset = 0;
settings.indexOffset = 0;
unsigned char *dstpData =
static_cast<unsigned char *>(vertexLock.pData);
unsigned int *dstIndices =
static_cast<unsigned int *>(indexLock.pData);
for (const auto &tile : tiles) {
std::shared_ptr<Ogre::IndexData> srcIndexData =
tile.second.indexData;
int srcIndexCount = srcIndexData->indexCount;
std::shared_ptr<Ogre::VertexData> srcVertexData =
tile.second.vertexData;
int srcVertexCount = srcVertexData->vertexCount;
for (const auto &position :
tile.second.positions) {
processTile(settings, tile.second,
position, dstpData);
processIndex(settings, tile.second,
dstIndices);
settings.vertexOffset += srcVertexCount;
settings.indexOffset += srcIndexCount;
Ogre::Vector3i vposition;
unpackKey(position, vposition);
std::cout << "position: " << position
<< " " << vposition
<< std::endl;
}
}
settings.mesh->_setBounds(settings.bounds);
}
#if 0
Ogre::LodConfig config(settings.mesh);
// config.advanced.useCompression = false;
// config.advanced.useVertexNormals = true;
config.advanced.preventPunchingHoles = true;
config.advanced.preventBreakingLines = true;
config.createGeneratedLodLevel(2, 0.15f);
#if 0
config.createGeneratedLodLevel(10, 0.40f);
config.createGeneratedLodLevel(15, 0.49f);
config.createGeneratedLodLevel(150, 0.75f);
#endif
config.advanced.useBackgroundQueue = false;
Ogre::MeshLodGenerator::getSingleton().generateLodLevels(
config);
#endif
return settings.mesh;
}
void removeTile(const Ogre::String &name)
{
tiles.erase(name);
}
TiledMeshes()
{
}
};
The usage is
Code: Select all
// get some meshes
Ogre::Entity e1 = mScnMgr->createEntity("pillar1", "pillar1.glb");
Ogre::Entity e2 = mScnMgr->createEntity("beam1", "beam1.glb");
Ogre::Entity e3 = mScnMgr->createEntity("planks1", "planks1.glb");
TiledMeshes tmesh;
tmesh.addTile("pillar", e1->getMesh());
tmesh.addTile("beam", e2->getMesh());
tmesh.addTile("planks", e3->getMesh());
int i;
for (i = 0; i < 30; i+=2) {
tmesh.setTile{"planks", {-4, 0, i + 1});
tmesh.setTile{"planks", {0, 0, i + 1});
tmesh.setTile{"planks", {4, 0, i + 1});
}
for (i = 0; i < 30; i+=6) {
tmesh.setTile{"beam", {-5, 0, i + 3});
tmesh.setTile{"beam", {0, 0, i + 3});
tmesh.setTile{"beam", {5, 0, i + 3});
}
for (i = 0; i < 30; i+=6) {
tmesh.setTile{"pillar", {-5, 0, i});
tmesh.setTile{"pillar", {0, 0, i});
tmesh.setTile{"pillar", {5, 0, i});
}
Ogre::MeshPtr mesh = tmesh.build("Test");
Ogre::Entity *ent = mScnMgr->createEntity("TestEnt", mesh);
mScnMgr->getRootSceneNode()->createChildSceneNode()->attachObject(ent);
}
This way it works perfectly and valgrind does not complain. The generated mesh shows fine and physics collider from this mesh generates and works in both Jolt and Bullet.
However if I enable lod generation code #if 0'ed above I get weird crashes and valgrind shows access way beyond hardware index buffer. Any ideas?
-
sercero
- Bronze Sponsor

- Posts: 544
- Joined: Sun Jan 18, 2015 4:20 pm
- Location: Buenos Aires, Argentina
- x 200
Re: Does StaticGeometry suppoort LOD? is it possible to convert StaticGeometry to mesh?
Why aren't you using instancing if the objective is performance?
-
slapin
- Bronze Sponsor

- Posts: 339
- Joined: Fri May 23, 2025 5:04 pm
- x 24
Re: Does StaticGeometry suppoort LOD? is it possible to convert StaticGeometry to mesh?
Hi,
I do not use hardware instancing because my target platform is Open GL ES 2.0
-
slapin
- Bronze Sponsor

- Posts: 339
- Joined: Fri May 23, 2025 5:04 pm
- x 24
Re: Does StaticGeometry suppoort LOD? is it possible to convert StaticGeometry to mesh?
Also as it is not moving geometry I think instancing won't be much better than just making full meshes.
-
slapin
- Bronze Sponsor

- Posts: 339
- Joined: Fri May 23, 2025 5:04 pm
- x 24
Re: Does StaticGeometry suppoort LOD? is it possible to convert StaticGeometry to mesh?
Looks like it is impossible to generate LODs for manual mesh. Looks like it misses something expected by LOD generator and without documentation I will debug it till next millenia.
Will have to drop the idea and will use StaticGeometry/PagedGeometry or fully made models for LODs.