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.
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.
On paper the simplest algorithm is easy.:
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.
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.
Here are some screenshots where Bullet is drawing debug triangles over Irrlicht mesh. Draw debug triangles in order to check your result.
Senior Software Engineer developing all kinds of stuff.