From Irrlicht mesh to Bullet Physics mesh

By , last updated July 19, 2019

There is typically no physics functionality in graphics engines like Irrlicht. If you are using graphics and physics engines instead of one like Unity or Unreal Engine then you will run into a problem called “Irrlicht physics”. Recall that Irrlicht is just a graphics engine. It renders object on the screen and that is all. In order for an object to be able to have physics properties you would need to integrate physics and graphics engines.

Problem

In our game “Burnt Islands” we saw the need for having a more detailed physics mesh for certain models. Being indie developers, manually making physics mesh for each model is not the way to go.

Instead, I made a method, which extracts the mesh from an Irrlicht node (if it has any), and then converts it into Bullet Physics mesh.

Algorithm

On paper the simplest algorithm is easy.:

  1. Get the mesh from Irrlicht
  2. Make a Bullet Physics btTriangleMesh.
  3. Loop through all meshbuffers
    1. Loop through all vertices and save them
    2. Loop through all indices and save them

However, we can’t do it like that. To keep the code tidy, and I really don’t like to mix Irrlicht code with Bullet Physics code. But for the sake of simplicity, this code sample will do it all-in-one.

Code

btRigidBody * makeBulletMeshFromIrrlichtNode( const irr::scene::ISceneNode * node )
{
	using irr::core::vector2df;
	using irr::core::vector3df;
	using irr::scene::IMesh;
	using irr::scene::IMeshBuffer;
	using irr::scene::IMeshSceneNode;
	using irr::scene::IAnimatedMeshSceneNode;

	// Handy lambda for converting from irr::vector to btVector
	auto toBtVector = [ &]( const vector3df & vec ) -> btVector3
	{
		btVector3 bt( vec.X, vec.Y, vec.Z );

		return bt;
	};

	irr::scene::ESCENE_NODE_TYPE type = node->getType();

	IAnimatedMeshSceneNode * meshnode = nullptr;
	btRigidBody	* body = nullptr;

	switch ( type )
	{
		case irr::scene::ESNT_ANIMATED_MESH:
			{
				meshnode = (IAnimatedMeshSceneNode *)node;
			}
			break;
	}

	if ( meshnode )
	{
		const vector3df nodescale = meshnode->getScale();

		IMesh * mesh = meshnode->getMesh();
		const size_t buffercount = mesh->getMeshBufferCount();

		// Save position
		btVector3 position = toBtVector( meshnode->getPosition() );

		// Save data here
		std::vector<irr::video::S3DVertex>	verticesList;
		std::vector<int>					indicesList;

		for ( size_t i=0; i<buffercount; ++i )
		{
			// Current meshbuffer
			IMeshBuffer * buffer = mesh->getMeshBuffer( i );

			// EVT_STANDARD -> video::S3DVertex
			// EVT_2TCOORDS -> video::S3DVertex2TCoords
			// EVT_TANGENTS -> video::S3DVertexTangents
			const irr::video::E_VERTEX_TYPE vertexType		= buffer->getVertexType();

			// EIT_16BIT
			// EIT_32BIT
			const irr::video::E_INDEX_TYPE	indexType		= buffer->getIndexType();

			// Get working data
			const size_t numVerts		= buffer->getVertexCount();
			const size_t numInd			= buffer->getIndexCount();

			// Resize save buffers
			verticesList.resize( verticesList.size() + numVerts );
			indicesList.resize( indicesList.size() + numInd );

			void * vertices				= buffer->getVertices();
			void * indices				= buffer->getIndices();

			irr::video::S3DVertex			* standard		= reinterpret_cast<irr::video::S3DVertex*>( vertices );
			irr::video::S3DVertex2TCoords	* two2coords	= reinterpret_cast<irr::video::S3DVertex2TCoords*>( vertices );
			irr::video::S3DVertexTangents	* tangents		= reinterpret_cast<irr::video::S3DVertexTangents*>( vertices );

			int16_t	* ind16		= reinterpret_cast<int16_t*>( indices );
			int32_t	* ind32		= reinterpret_cast<int32_t*>( indices );

			for ( size_t v = 0; v < numVerts; ++v )
			{
				auto & vert = verticesList[ v ];

				switch ( vertexType )
				{
					case irr::video::EVT_STANDARD:
						{
							const auto & irrv = standard[ v ];

							vert = irrv;
						}
						break;
					case irr::video::EVT_2TCOORDS:
						{
							const auto & irrv = two2coords[ v ];
							(void)irrv;

							// Not implemented
						}
						//break;
					case irr::video::EVT_TANGENTS:
						{
							const auto & irrv = tangents[ v ];
							(void)irrv;

							// Not implemented
						}
						//break;
					default:
						BOOST_ASSERT( 0 && "unknown vertex type" );
				}

			}

			for ( size_t n = 0; n < numInd; ++n )
			{
				auto & index = indicesList[ n ];

				switch ( indexType )
				{
					case irr::video::EIT_16BIT:
					{
						index = ind16[ n ];
					}
						break;
					case irr::video::EIT_32BIT:
					{
						index = ind32[ n ];
					}
						break;
					default:
						BOOST_ASSERT( 0 && "unkown index type" );
				}

			}

		}

		// Make bullet rigid body
		if ( ! verticesList.empty() && ! indicesList.empty() )
		{
			// Working numbers
			const size_t numIndices		= indicesList.size();
			const size_t numTriangles	= numIndices / 3;

			// Error checking
			BOOST_ASSERT( numTriangles * 3 == numIndices && "Number of indices does not make complete triangles" );

			// Create triangles
			btTriangleMesh * btmesh = new btTriangleMesh();

			// Build btTriangleMesh
			for ( size_t i=0; i<numIndices; i+=3 )
			{
				const btVector3 &A = toBtVector( verticesList[ indicesList[ i+0 ] ].Pos );
				const btVector3 &B = toBtVector( verticesList[ indicesList[ i+1 ] ].Pos );
				const btVector3 &C = toBtVector( verticesList[ indicesList[ i+2 ] ].Pos );

				bool removeDuplicateVertices = true;
				btmesh->addTriangle( A, B, C, removeDuplicateVertices );
			}

			// Give it a default MotionState
			btTransform transform;
			transform.setIdentity();
			transform.setOrigin( position );
			btDefaultMotionState *motionState = new btDefaultMotionState( transform );

			// Create the shape
			btCollisionShape *btShape = new btBvhTriangleMeshShape( btmesh, true );
			btShape->setMargin( 0.05f );

			// Create the rigid body object
			btScalar mass = 0.0f;
			body = new btRigidBody( mass, motionState, btShape );

		}

	}

	// Return Bullet rigid body
	return body;
}

In short, you will have to use the userpointer feature of the physics entities in Bullet to store a pointer to the Irrlicht nodes. On every frame, for all entities that have moved, update the Irrlicht node position (and rotation) from the Bullet physics entity.

Screenshots

Here are some screenshots where Bullet is drawing debug triangles over Irrlicht mesh. Draw debug triangles in order to check your result.

screenshot_20140330T204418.218864

screenshot_20140330T204420.340986

screenshot_20140330T205139.268091

screenshot_20140330T205136.951959

Senior Software Engineer developing all kinds of stuff.