Integrating the Bullet physics engine

As a sample for integrating a third party library that is not built into Panda3D into a game, this recipe dives into the C++ side of Panda3D to create a scene that is driven by the excellent and free Bullet physics engine.

Getting ready

This recipe builds on the project setup described in Creating a scene using C++ found in Chapter 2. Follow the steps of this recipe before proceeding!

You also need a copy of the Bullet source code. The latest version can be retrieved from this website: http://code.google.com/p/bullet/downloads/list.

How to do it...

Integrating the Bullet physics engine into a Panda3D program involves these tasks:

  1. In the top-level solution directory, create a directory named Lib.
  2. Unpack the Bullet source code into the Lib directory so that the top-level directory, containing the file AUTHORS and the directory msvc, of Bullet is Libullet-2.77.
  3. Navigate to the msvc2008 subdirectory of the Bullet source tree and open the file BULLET_PHYSICS.sln.
  4. Switch the build configuration to Release and build the solution.
  5. Quit Visual Studio.
  6. Edit the PandaSettings.vsprops file and replace its content with the following configuration data:
    <?xml version="1.0" encoding="Windows-1252"?>
    <VisualStudioPropertySheet
    ProjectType="Visual C++"
    Version="8.00"
    Name="PandaSettings"
    >
    <Tool
    Name="VCCLCompilerTool"
    AdditionalIncludeDirectories="&quot;..Libullet-2.77src&quot;;&quot;C:Panda3D-1.7.0pythoninclude&quot;;&quot;C:Panda3D-1.7.0include&quot;"
    />
    <Tool
    Name="VCLinkerTool"
    AdditionalDependencies="BulletDynamics.lib BulletCollision.lib LinearMath.lib libp3framework.lib libpanda.lib libpandafx.lib libpandaexpress.lib libp3dtool.lib libp3dtoolconfig.lib libp3pystub.lib libp3direct.lib"
    AdditionalLibraryDirectories="&quot;..Libullet-2.77msvc2008libRelease&quot;;&quot;C:Panda3D-1.7.0pythonlibs&quot;;&quot;C:Panda3D-1.7.0lib&quot;"
    />
    </VisualStudioPropertySheet>
    
  7. Open your solution again.
  8. Edit main.cpp and add the main function:
    #include "Application.h"
    PandaFramework framework;
    int main(int argc, char* argv[])
    {
    Application app(argc, argv);
    app.run();
    return 0;
    }
    
  9. Add a new file called Application.h and add the declaration of the Application and BulletTask classes:
    #pragma once
    #include <pandaFramework.h>
    #include <pandaSystem.h>
    #include <asyncTask.h>
    #include <btBulletDynamicsCommon.h>
    class Application
    {
    public:
    Application(int argc, char* argv[]);
    ~Application();
    void run();
    private:
    void init();
    void setupBullet();
    void addGround();
    void updateBullet();
    private:
    NodePath render;
    NodePath cam;
    NodePath smiley;
    WindowFramework* win;
    PandaFramework framework;
    btBroadphaseInterface* broadphase;
    btCollisionDispatcher* dispatcher;
    btConstraintSolver* solver;
    btDefaultCollisionConfiguration* collisionConfiguration;
    btDynamicsWorld* btWorld;
    };
    class BulletTask
    {
    public:
    static AsyncTask::DoneStatus updateBullet(GenericAsyncTask* task, void* data);
    };
    
  10. Create another new file called SmileyMotionState.h. Insert the following code for declaring the SmileyMotionState class:
    #pragma once
    #include <pandaFramework.h>
    #include <pandaSystem.h>
    #include <btBulletDynamicsCommon.h>
    class SmileyMotionState : public btMotionState
    {
    public:
    SmileyMotionState(const btTransform& start, const NodePath& sm);
    virtual ~SmileyMotionState() {}
    virtual void getWorldTransform(btTransform& trans) const;
    virtual void setWorldTransform(const btTransform& trans);
    protected:
    btTransform transform;
    NodePath smiley;
    };
    
  11. Add a new header file called SmileyTask.h and add another class declaration:
    #pragma once
    #include <pandaFramework.h>
    #include <pandaSystem.h>
    #include <asyncTask.h>
    #include <randomizer.h>
    #include <btBulletDynamicsCommon.h>
    class SmileyTask
    {
    public:
    SmileyTask(NodePath& rndr, NodePath& sm, btDynamicsWorld* world);
    static AsyncTask::DoneStatus addSmiley(GenericAsyncTask* task, void* data);
    NodePath render;
    NodePath smiley;
    btDynamicsWorld* btWorld;
    int smileyCount;
    };
    
  12. Add a new code file to the solution. Call it SmileyTask.cpp and populate it with the following code:
    #include "SmileyTask.h"
    #include "SmileyMotionState.h"
    SmileyTask::SmileyTask(NodePath& rndr, NodePath& sm, btDynamicsWorld* world)
    {
    smileyCount = 0;
    smiley = sm;
    render = rndr;
    btWorld = world;
    }
    AsyncTask::DoneStatus SmileyTask::addSmiley(GenericAsyncTask* task, void* data)
    {
    SmileyTask* add = reinterpret_cast<SmileyTask*>(data);
    NodePath render = add->render;
    NodePath smiley = add->smiley;
    btDynamicsWorld* btWorld = add->btWorld;
    NodePath sm = render.attach_new_node("smiley-instance");
    Randomizer rnd;
    smiley.instance_to(sm);
    btCollisionShape* shape = new btSphereShape(btScalar(1));
    btTransform trans;
    trans.setIdentity();
    trans.setOrigin(btVector3(rnd.random_real(40) - 20, rnd.random_real(20) + 10, rnd.random_real(60) - 30));
    btScalar mass(10);
    btVector3 inertia(0, 0, 0);
    shape->calculateLocalInertia(mass, inertia);
    SmileyMotionState* ms = new SmileyMotionState(trans, sm);
    btRigidBody::btRigidBodyConstructionInfo info(mass, ms, shape, inertia);
    info.m_restitution = btScalar(0.5f);
    info.m_friction = btScalar(0.7f);
    btRigidBody* body = new btRigidBody(info);
    btWorld->addRigidBody(body);
    add->smileyCount++;
    if (add->smileyCount == 100)
    return AsyncTask::DS_done;
    return AsyncTask::DS_again;
    }
    
  13. Create another file called SmileyMotionState.cpp and fill it with this following snippet:
    #include "SmileyMotionState.h"
    SmileyMotionState::SmileyMotionState(const btTransform& start, const NodePath& sm)
    {
    transform = start;
    smiley = sm;
    }
    void SmileyMotionState::getWorldTransform(btTransform& trans) const
    {
    trans = transform;
    }
    void SmileyMotionState::setWorldTransform(const btTransform& trans)
    {
    transform = trans;
    btQuaternion rot = trans.getRotation();
    LQuaternionf prot(rot.w(), -rot.x(), -rot.z(), -rot.y());
    smiley.set_hpr(prot.get_hpr());
    btVector3 pos = trans.getOrigin();
    smiley.set_pos(pos.x(), pos.z(), pos.y());
    }
    
  14. Add one last file called Application.cpp and add the code below:
    #include <cardMaker.h>
    #include "Application.h"
    #include "SmileyTask.h"
    Application::Application(int argc, char* argv[])
    {
    framework.open_framework(argc, argv);
    win = framework.open_window();
    cam = win->get_camera_group();
    render = win->get_render();
    }
    Application::~Application()
    {
    }
    void Application::run()
    {
    init();
    framework.main_loop();
    framework.close_framework();
    }
    void Application::init()
    {
    setupBullet();
    Bullet physics engineintegratingPT(AsyncTaskManager) taskMgr = AsyncTaskManager::get_global_ptr();
    smiley = win->load_model(framework.get_models(), "frowney");
    SmileyTask* add = new SmileyTask(render, smiley, btWorld);
    PT(GenericAsyncTask) addSmiley = new GenericAsyncTask("AddSmiley", &SmileyTask::addSmiley, add);
    addSmiley->set_delay(0.01);
    taskMgr->add(addSmiley);
    PT(GenericAsyncTask) bt = new GenericAsyncTask("UpdateBullet", &BulletTask::updateBullet, btWorld);
    taskMgr->add(bt);
    addGround();
    cam.set_pos(0, -100, 10);
    }
    void Application::setupBullet()
    {
    collisionConfiguration = new btDefaultCollisionConfiguration();
    dispatcher = new btCollisionDispatcher(collisionConfiguration);
    broadphase = new btDbvtBroadphase();
    btSequentialImpulseConstraintSolver* sol = new btSequentialImpulseConstraintSolver;
    solver = sol;
    btWorld = new btDiscreteDynamicsWorld(dispatcher, broadphase, solver, collisionConfiguration);
    btWorld->setGravity(btVector3(0, -9.81f, 0));
    }
    void Application::addGround()
    {
    CardMaker cm("ground");
    cm.set_frame(-500, 500, -500, 500);
    NodePath ground = render.attach_new_node(cm.generate());
    ground.look_at(0, 0, -1);
    ground.set_color(0.2f, 0.6f, 0.2f);
    btCollisionShape* shape = new btBoxShape(btVector3(btScalar(500), btScalar(0.5f), btScalar(500)));
    Bullet physics engineintegratingbtTransform trans;
    trans.setIdentity();
    trans.setOrigin(btVector3(0, -0.5f, 0));
    btDefaultMotionState* ms = new btDefaultMotionState(trans);
    btScalar mass(0);
    btVector3 inertia(0, 0, 0);
    btRigidBody::btRigidBodyConstructionInfo info(mass, ms, shape, inertia);
    info.m_restitution = btScalar(0.5f);
    info.m_friction = btScalar(0.7f);
    btRigidBody* body = new btRigidBody(info);
    btWorld->addRigidBody(body);
    }
    AsyncTask::DoneStatus BulletTask::updateBullet(GenericAsyncTask* task, void* data)
    {
    btScalar dt(ClockObject::get_global_clock()->get_dt());
    btDynamicsWorld* btWorld = reinterpret_cast<btDynamicsWorld*>(data);
    btWorld->stepSimulation(dt);
    return AsyncTask::DS_cont;
    }
    
  15. Press Ctrl + F5 to compile and run the program:
How to do it...

How it works...

First, we need to set up our environment: We unpack Bullet to the right location and compile it. We also modify the property file to add the search paths for Bullet's libraries and header files.

After we completely rewrite main.cpp, we go on to define the interfaces of the classes for our little sample program. The Application class, just like in the Python code samples, keeps everything together and controls the application behavior. BulletTask only contains the static method that is used to perform simulation steps. A SmileyMotionState is created for every new smiley created throughout the program's runtime. Motion state objects are a convenient way for Bullet to keep object transformations up-to-date as they provide a per-object callback mechanism that allows Bullet to inject new and updated data. Lastly, we define the interface of SmileyTask, which is responsible for creating new smileys.

The next thing we add is the implementation of SmileyTask. In the static member function, we first retrieve the necessary objects from the context data we pass into the function using the data parameter.

Additionally, we can observe how Bullet's interface for adding a new object to the simulation works. We need to combine a shape, mass, inertia, and motion state into a btRigidBodyConstructionInfo object that we can also use to set the object's material parameters for bounciness and friction. This info is then used to construct the new object, which is then added to our physics world.

In the implementation of SmileyMotionState, nothing too obscure is happening. The getter and setter methods return and set transformation data and where necessary convert between the data types of Bullet and Panda3D.

Note

Bullet uses a different coordinate system than Panda3D. In Bullet's representation of the scene, the y-axis points up, while in Panda3D, the z-axis points up. Since they are different, in the sample code, the z- and y-axes are flipped to accommodate this situation.

The code in Application.cpp is responsible for opening the application window and initializing the Panda3D framework. It also sets up Bullet's btDiscreteDynamicsWorld and sets up the scene.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset