Bullet Physics
Loading...
Searching...
No Matches
Static Map

Introduction

A common task when working with a physics simulation is to have some sort of fixed map, this comes up in most simulations that have gravity so that at least your objects will not just fall forever.

For basic platforms feel free to use any of the built in collision shapes, but in most cases your map may be designed in a 3rd party program such as blender.

btBVHTriangleMeshShape

When working with arbitrary geometry like this, the class you'll want to use is btBvhTriangleMeshShape, breaking the name of this function down we can see that we first have Bvh, which stands for "Bounding volume hierarchy", here's a quick description pulled from wikipedia:

‍A bounding volume hierarchy (BVH) is a tree structure on a set of geometric objects. All geometric objects, which form the leaf nodes of the tree, are wrapped in bounding volumes

Example of bounding volume hierarchy

The next part of the name is Triangle Mesh.

At this point the name of this function should tell that you that when you use this class you'll have an internal BVH representation for acceleration purposes, it builds this acceleration structure from

Loading in Data

Loading Triangles Manually

Focusing on the signature for this function, we can see that it expects a btStridingMeshInterface, which mentions that that is an "interface class for high performance generic access to triangle meshes", if you don't already know, an interface class is one that's not meant to be initialized. Taking a look at it's inheritance diagram you'll be able to find subclasses that actually provide an implementation for this concept.

The class we'll first focus our attention on is btTriangleMesh. The general process of using a btTriangleMesh is like this:

  1. create a new btTriangleMesh
  2. create vertices of triangles add them to the mesh
  3. create a btCollisionShape with btBvhTriangleMeshShape and pass in the triangle mesh we've built up in the previous steps
  4. set up a rigid body with our btCollisionShape

A sample interaction could look like this:

// 1
btVector3 vertex1, vertex2, vertex3, vertex4;
btTriangleMesh* triangleMeshTerrain = new btTriangleMesh();
// 2 create flat mesh made up of 10x10 squares
for (int i = -250; i < 250; i = i + 10) {
for (int j = -250; j < 250; j = j + 10) {
// vertices of a square width side lengths of 10
vertex1 = btVector3(i, 0.0f, j);
vertex2 = btVector3(i + 10.0f, 0.0f, j);
vertex3 = btVector3(i + 10.0f, 0.0f, j + 10.0f);
vertex4 = btVector3(i, 0.0f, j + 10.0f);
// add the two halfs of the square
triangleMeshTerrain->addTriangle(vertex1, vertex2, vertex3);
triangleMeshTerrain->addTriangle(vertex1, vertex3, vertex4);
}
}
// 3
btCollisionShape* collisionShapeTerrain = new btBvhTriangleMeshShape(triangleMeshTerrain, true);
// 4
btDefaultMotionState* motionState = new btDefaultMotionState(btTransform(btQuaternion(0, 0, 0, 1), btVector3(0, -15, 0)));
btRigidBody::btRigidBodyConstructionInfo rigidBodyConstructionInfo(0.0f, motionState, collisionShapeTerrain, btVector3(0, 0, 0));
btRigidBody* rigidBodyTerrain = new btRigidBody(rigidBodyConstructionInfo);
rigidBodyTerrain->setFriction(btScalar(0.9));
m_dynamicsWorld->addRigidBody(rigidBodyTerrain);

Obj Files

While programatically creating collision shapes can work, we'll need a way to load in arbitrary vertex information so that we can load in assets created by artists and files created in software such as obj files.

Before going any further, please become aquainted with the obj format.

For the most part we'll you'll want to load in your obj files through some obj loader program that has been already created and is external from bullet. The datastructure that you store it in will determine how you will end up loading it into bullet, but we can make a few safe assumptions.

Todo
figure out if bullet has an obj importer and if it does mention it here

We know that in general a model is a collection of meshes, and a mesh is a collection of vertices along with attributes stores on those vertices such as normals, texture coordinates and so on.

On top of this your obj file defines faces by passing indices that map to certain vertices, the benefit is that you don't have to repeat the same verticies again. For example if we were working on the corner of a cube, the corner vertex would have to be referenced three times, if we didn't do it this way then we'd have to list out that vertex 3 times for each triangle at that corner and doing it this way makes the obj file bigger for no reason.

Therefore we'll assume that your datastructure has an array of meshes, and in each mesh we create an array of indicies which is generated by iterating over each face in the mesh and then inserting each predefined (by the obj file itself) vertex index for the face into the overarching mesh index array, at the end this array should be 3 * the number of faces on the mesh. A god name for this variable could be sequential_face_indices, because of the way it was generated by iterating sequentially over each face and then just inserting each vertex index directly into it. Then we can create our triangle mesh as follows

for (int i = 0; i < model->meshes.size(); i++) {
Mesh mesh = model->meshes[i];
btTriangleMesh* triangle_mesh = new btTriangleMesh();
// this only works if we know each face is a triangle
for (int j = 0; j < mesh.indices.size(); j += 3) {
unsigned int j1 = mesh.indices[j];
unsigned int j2 = mesh.indices[j + 1];
unsigned int j3 = mesh.indices[j + 2];
btVector3 v1 = convert_vector_from_glm_to_bullet(mesh.vertices[j1].position);
btVector3 v2 = convert_vector_from_glm_to_bullet(mesh.vertices[j2].position);
btVector3 v3 = convert_vector_from_glm_to_bullet(mesh.vertices[j3].position);
triangle_mesh->addTriangle(v1, v2, v3);
}
btCollisionShape* triangle_mesh_shape = new btBvhTriangleMeshShape(triangle_mesh, true);
btDefaultMotionState* motion_state = new btDefaultMotionState(btTransform(btQuaternion(0, 0, 0, 1), btVector3(0, -15, 0)));
btRigidBody::btRigidBodyConstructionInfo rigidBodyConstructionInfo(0.0f, motion_state, triangle_mesh_shape, btVector3(0, 0, 0));
btRigidBody* rigidBodyTerrain = new btRigidBody(rigidBodyConstructionInfo);
rigidBodyTerrain->setFriction(btScalar(0.9));
this->dynamics_world->addRigidBody(rigidBodyTerrain);
Todo
the following text was my attempt to use the triangle index array method, but I never got it to work, if you can write a minimal working example of this one, that would be great.

From this point onward, we can reference the code for the method createMeshInterface in Extras/Serialize/BulletWorldImporter/btWorldImporter.cpp.

The datastructure we were working with already has sequential_face_indices, but it looks like this code doesn't have that defined, so during the first part of the process it defines that, in the second stage it didn't define the vertex array so it also creates that, the vertex array is simply created by moving through the vertices in the current group defined in the obj file (a group is like the same thing as a mesh but in their format, see the original link to obj file for more info) and then simply appending it into an array, note the order follows the same order as the obj file.

Along the way it stores the sequential_face_indices along with the vertices into an object that has the type btIndexedMesh, this object doesn't take in these two pieces of data upon initialization so we have to pass them in after the fact like this:

indexed_mesh->m_triangleIndexBase = (const unsigned char*) sequential_face_indices;
indexed_mesh->m_vertexBase = (const unsigned char*) verticies;

Then after storing this information into indexed_mesh, we can then construct a btTriangleIndexVertexArray by doing

btTriangleIndexVertexArray triangle_index_vertex_array;
triangle_index_vertex_array->addIndexedMesh(indexed_mesh);

At which point, we can then constuct a btBvhTriangleMeshShape from this data like this

btCollisionShape* collisionShapeTerrain = new btBvhTriangleMeshShape(triangle_index_vertex_array, true);

All in all