Does StaticGeometry suppoort LOD? is it possible to convert StaticGeometry to mesh?

Problems building or running the engine, queries about how to use features etc.
slapin
Bronze Sponsor
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?

Post by slapin »

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.

slapin
Bronze Sponsor
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?

Post by slapin »

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
Bronze Sponsor
Posts: 339
Joined: Fri May 23, 2025 5:04 pm
x 24

Static Geometry vs PagedGeometry

Post by slapin »

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
Bronze Sponsor
Posts: 339
Joined: Fri May 23, 2025 5:04 pm
x 24

Procedural mesh creationg from tiny parts

Post by slapin »

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

  1. Combine the parts as needed into single mesh for batching.
  2. Create LODs for mesh to look better/cleaner at distance
  3. 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 -

  1. do everything manually from scratch
  2. do something else.

Any help is appreciated.
@paroj do you have any ideas on mind which I might missed?

slapin
Bronze Sponsor
Bronze Sponsor
Posts: 339
Joined: Fri May 23, 2025 5:04 pm
x 24

Re: Procedural mesh creationg from tiny parts

Post by slapin »

I guess the algorithm should be the following:

  1. Add all used tiles meshes into set. Require all tiles to use same material and have single material.
  2. Add list of integer coordinates for each tile.
  3. Generate shared vertex buffer containing tile vertices modified by coordinates.
  4. Generate index buffer
  5. Optionally optimize for duplicate vertices
  6. Generate LODs.
  7. Generate collider.
paroj
OGRE Team Member
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?

Post by paroj »

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
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?

Post by slapin »

paroj wrote: Fri Dec 12, 2025 3:41 pm

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.

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
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?

Post by paroj »

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
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?

Post by slapin »

Hi all!

paroj wrote: Fri Dec 12, 2025 5:09 pm

StaticGeometry itself is the example for that:
https://github.com/OGRECave/ogre/blob/c ... .cpp#L1405

maybe 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?

User avatar
sercero
Bronze Sponsor
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?

Post by sercero »

Why aren't you using instancing if the objective is performance?

slapin
Bronze Sponsor
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?

Post by slapin »

Hi,
I do not use hardware instancing because my target platform is Open GL ES 2.0

slapin
Bronze Sponsor
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?

Post by slapin »

Also as it is not moving geometry I think instancing won't be much better than just making full meshes.

slapin
Bronze Sponsor
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?

Post by slapin »

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.