Bullet Physics Engine Tutorial: Hello World Application

In this article we will show you how, in the most concise program possible, to initialise Bullet, set up a dynamics world, and allow a sphere to fall onto a surface. This is useful to verify that the build is working properly and also as a very top-level guide to using the Bullet API. First, we assume that Bullet is installed correctly and that the compiler is set up with the right Bullet include path (e.g. /usr/local/include/bullet) and is linking to the right libraries. Otherwise seeInstallation. If you're using gcc make sure you link to the static libraries in reverse order of dependency, i.e. your object files, dynamics, collision, math.

Full code is given at the bottom of the page

Initial Program

Start with a standard helloworld program:

#include <iostream>

        int main ()
        {
                std::cout << "Hello World!" << std::endl;
                return 0;
        }

Creating The World

Now, we will add a Bullet simulation. First, an appropriate include:

#include <btBulletDynamicsCommon.h>

We want to instantiate a btDiscreteDynamicsWorld but to do this we need some other things too. For a "hello world" example they are cumbersome because we don't need them. However, as we mature our software into a useful product, they will be useful for fine-tuning the simulation.

We need to specify what Broadphase algorithm we want to use. Choosing the broadphase is important if the world will have a lot of rigid bodies in it, since it has to somehow check every pair which when implemented naively is an O(n^2) problem.


For alternative broadphase algorithms, see the Broadphase page.

btBroadphaseInterface* broadphase = new btDbvtBroadphase();

The broadphase is an excellent place for eliminating object pairs that should not collide. This can be for performance or gameplay reasons. You can use the collision dispatcher to register a callback that filters overlapping broadphase proxies so that the collisions are not processed by the rest of the system. More information in Collision Things.

The collision configuration allows you to fine tune the algorithms used for the full (not broadphase) collision detection. Here be dragons!

btDefaultCollisionConfiguration* collisionConfiguration = new btDefaultCollisionConfiguration();
        btCollisionDispatcher* dispatcher = new btCollisionDispatcher(collisionConfiguration);

We also need a "solver". This is what causes the objects to interact properly, taking into account gravity, game logic supplied forces, collisions, and hinge constraints. It does a good job as long as you don't push it to extremes, and is one of the bottlenecks in any high performance simulation. There are parallel versions available for some threading models.

btSequentialImpulseConstraintSolver* solver = new btSequentialImpulseConstraintSolver;

Now, we can finally instantiate the dynamics world:

btDiscreteDynamicsWorld* dynamicsWorld = new btDiscreteDynamicsWorld(dispatcher,broadphase,solver,collisionConfiguration);

One last (rather obvious) line sets the gravity. We have chosen the Y axis to be "up".

dynamicsWorld->setGravity(btVector3(0,-10,0));

Bullet has a policy of "whoever allocates, also deletes" memory, so all of these structures must be deleted at the end of main().

We now have prepared the first few lines that are common to *any* Bullet application. The code up to this point looks like this:

#include <btBulletDynamicsCommon.h>
#include <iostream>

int main () {
    std::cout << "Hello World!" << std::endl;

    // Build the broadphase
    btBroadphaseInterface* broadphase = new btDbvtBroadphase();

    // Set up the collision configuration and dispatcher
    btDefaultCollisionConfiguration* collisionConfiguration = new btDefaultCollisionConfiguration();
    btCollisionDispatcher* dispatcher = new btCollisionDispatcher(collisionConfiguration);

    // The actual physics solver
    btSequentialImpulseConstraintSolver* solver = new btSequentialImpulseConstraintSolver;

    // The world.
    btDiscreteDynamicsWorld* dynamicsWorld = new btDiscreteDynamicsWorld(dispatcher,broadphase,solver,collisionConfiguration);
    dynamicsWorld->setGravity(btVector3(0,-10,0));

    // Do_everything_else_here


    // Clean up behind ourselves like good little programmers
    delete dynamicsWorld;
    delete solver;
    delete dispatcher;
    delete collisionConfiguration;
    delete broadphase;

    return 0;
}

Collision Shapes

We will create a ground plane [a static rigid body], and a sphere that will fall onto the ground [a dynamic rigid body]. Each rigid body needs to reference a collision shape. The collision shape is for collisions only, and thus has no concept of mass, inertia, restitution, etc. If you have many bodies that use the same collision shape [eg every spaceship in your simulation is a 5-unit-radius sphere], it is good practice to have only one Bullet collision shape, and share it among all those bodies. However, we only have two rigid bodies and they are not the same shape so they will need a shape each.

In this demonstration, we will place a ground plane running through the origin. For pedantic purposes, we will define the collision shape with an offset of 1 unit from the origin. Later, we will place a rigid body using this shape in the world -1 unit away from the origin so the two offsets cancel and the plane winds up intersecting the origin. You could just use 0 for both values and achieve the same result, but here we place a plane defined byy = 1:

btCollisionShape* groundShape = new btStaticPlaneShape(btVector3(0,1,0),1);

The shape that we will let fall from the sky is a sphere with a radius of 1 metre.

btCollisionShape* fallShape = new btSphereShape(1);

Collision shapes must be deleted at the end of the show just like everything else.

Rigid Bodies

Now we can add the collision shapes into our scene, positioning them with rigid body instances.

Lets first instantiate the ground. Its orientation is the identity, Bullet quaternions are specified in x,y,z,w form. The position is 1 metre below the ground, which compensates the 1m offset we had to put into the shape itself. Motionstates are covered in detail on a page dedicated to them: MotionStates

btDefaultMotionState* groundMotionState =
                new btDefaultMotionState(btTransform(btQuaternion(0,0,0,1),btVector3(0,-1,0)));

The first and last parameters of the following constructor are the mass and inertia of the ground. Since the ground is static, we represent this by filling these values with zeros. Bullet considers passing a mass of zero equivalent to making a body with infinite mass - it is immovable.

btRigidBody::btRigidBodyConstructionInfo
                groundRigidBodyCI(0,groundMotionState,groundShape,btVector3(0,0,0));
        btRigidBody* groundRigidBody = new btRigidBody(groundRigidBodyCI);

Finally, we add the ground to the world:

dynamicsWorld->addRigidBody(groundRigidBody);

Adding the falling sphere is very similar. We will place it 50m above the ground.

btDefaultMotionState* fallMotionState =
                new btDefaultMotionState(btTransform(btQuaternion(0,0,0,1),btVector3(0,50,0)));

Since it's dynamic we will give it a mass of 1kg. I can't remember how to calculate the inertia of a sphere, but that doesn't matter because Bullet provides a utility function:

btScalar mass = 1;
        btVector3 fallInertia(0,0,0);
        fallShape->calculateLocalInertia(mass,fallInertia);

Now we can construct the rigid body just like before, and add it to the world:

btRigidBody::btRigidBodyConstructionInfo fallRigidBodyCI(mass,fallMotionState,fallShape,fallInertia);
        btRigidBody* fallRigidBody = new btRigidBody(fallRigidBodyCI);
        dynamicsWorld->addRigidBody(fallRigidBody);

A quick explanation of btRigidBody::btRigidBodyConstructionInfo is in order; when bodies are constructed, they are passed certain parameters. This is done through a special structure Bullet provides for this. The components of the btRigidBodyConstructionInfo are copied into the body when you construct it, and only used at initialisation time. If you want to create a thousand bodies with exactly the same properties, you only need to build one btRigidBodyConstructionInfo, and pass that same one to all the bodies that you create.

Stepping The Simulation

This is where the fun begins. We will step the simulation 300 times, at an interval of 60hz. This will give the sphere enough time to hit the ground under the influence of gravity. Each step, we will print out its height above the ground.

The stepSimulation function does what you'd expect, but its interface is fairly complicated. ReadStepping The World for more details.

After stepping, we examine the state of the falling sphere. The position and orientation are encapsulated in the btTransform object, which we extract from the falling sphere's motion state. We are only interested in the position, which we pull out of the transform with getOrigin(). We then print the y component of the position vector.

for (int i=0 ; i<300 ; i++) {

                dynamicsWorld->stepSimulation(1/60.f,10);

                btTransform trans;
                fallRigidBody->getMotionState()->getWorldTransform(trans);

                std::cout << "sphere height: " << trans.getOrigin().getY() << std::endl;
        }

This should yield an output that looks like this:

        sphere height: 49.9917
        sphere height: 49.9833
        sphere height: 49.9722
        sphere height: 49.9583
        sphere height: 49.9417
        sphere height: 49.9222
        sphere height: 49.9
        ...
        sphere height: 1
        sphere height: 1
        sphere height: 1
        sphere height: 1
        sphere height: 1

Looks good so far. If you graph this output against the number of iterations, you get this:

Image:HelloWorldGraph.png

The sphere comes to rest 1 metre above the ground. This is because the position is taken from the centre of the sphere and it has a radius of 1 metre. The sharp-eyed will note that the sphere penetrates the ground slightly before bouncing and coming to rest. This is to be expected in realtime physics engines, but it can be minimised by increasing the frequency of the simulation steps. Try it and see!

Now you can integrate the dynamics world into your application and render falling spheres. Check out the otherCollision Shapes as well. Try stacking boxes or cylinders and then shooting them over with spheres.

Full Source Code

#include <iostream>

#include <btBulletDynamicsCommon.h>

int main (void)
{

        btBroadphaseInterface* broadphase = new btDbvtBroadphase();

        btDefaultCollisionConfiguration* collisionConfiguration = new btDefaultCollisionConfiguration();
        btCollisionDispatcher* dispatcher = new btCollisionDispatcher(collisionConfiguration);

        btSequentialImpulseConstraintSolver* solver = new btSequentialImpulseConstraintSolver;

        btDiscreteDynamicsWorld* dynamicsWorld = new btDiscreteDynamicsWorld(dispatcher,broadphase,solver,collisionConfiguration);

        dynamicsWorld->setGravity(btVector3(0,-10,0));


        btCollisionShape* groundShape = new btStaticPlaneShape(btVector3(0,1,0),1);

        btCollisionShape* fallShape = new btSphereShape(1);


        btDefaultMotionState* groundMotionState = new btDefaultMotionState(btTransform(btQuaternion(0,0,0,1),btVector3(0,-1,0)));
        btRigidBody::btRigidBodyConstructionInfo
                groundRigidBodyCI(0,groundMotionState,groundShape,btVector3(0,0,0));
        btRigidBody* groundRigidBody = new btRigidBody(groundRigidBodyCI);
        dynamicsWorld->addRigidBody(groundRigidBody);


        btDefaultMotionState* fallMotionState =
                new btDefaultMotionState(btTransform(btQuaternion(0,0,0,1),btVector3(0,50,0)));
        btScalar mass = 1;
        btVector3 fallInertia(0,0,0);
        fallShape->calculateLocalInertia(mass,fallInertia);
        btRigidBody::btRigidBodyConstructionInfo fallRigidBodyCI(mass,fallMotionState,fallShape,fallInertia);
        btRigidBody* fallRigidBody = new btRigidBody(fallRigidBodyCI);
        dynamicsWorld->addRigidBody(fallRigidBody);


        for (int i=0 ; i<300 ; i++) {
                dynamicsWorld->stepSimulation(1/60.f,10);

                btTransform trans;
                fallRigidBody->getMotionState()->getWorldTransform(trans);

                std::cout << "sphere height: " << trans.getOrigin().getY() << std::endl;
        }

        dynamicsWorld->removeRigidBody(fallRigidBody);
        delete fallRigidBody->getMotionState();
        delete fallRigidBody;

        dynamicsWorld->removeRigidBody(groundRigidBody);
        delete groundRigidBody->getMotionState();
        delete groundRigidBody;


        delete fallShape;

        delete groundShape;


        delete dynamicsWorld;
        delete solver;
        delete collisionConfiguration;
        delete dispatcher;
        delete broadphase;

        return 0;
}



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值