Collision Detection and Physics in Panda3D Game Development

Exclusive offer: get 50% off this eBook here
Panda3D 1.7 Game Developer's Cookbook

Panda3D 1.7 Game Developer's Cookbook — Save 50%

Over 80 recipes for developing 3D games with Panda3D, a full-scale 3D game engine

£16.99    £8.50
by Christoph Lang | March 2011 | Cookbooks Open Source Web Graphics & Video

Panda3D is a free and open source game engine. It has been used successfully by hobbyists as well as big studios to create games ranging from quick prototypes to full-scale commercial MMOs. Panda3D makes it easy to use models, textures, and sounds to create impressive interactive experiences. With this article, you too will be able to leverage the full power of the Panda3D engine.

In this article by Christoph Lang, author of Panda3D game developer’s cookbook, we will cover:

  • Using the built-in collision detection system
  • Using the built-in physics system
  • Using the ODE physics engine
  • Using the PhysX physics engine
  • Integrating the Bullet physics engine

 

Panda3D 1.7 Game Developer's Cookbook

Panda3D 1.7 Game Developer's Cookbook

Over 80 recipes for developing 3D games with Panda3D, a full-scale 3D game engine

        Read more about this book      

(For more resources on this subject, see here.)

Introduction

In a video game, the game world or level defines the boundaries within which the player is allowed to interact with the game environment. But how do we enforce these boundaries? How do we keep the player from running through walls?

This is where collision detection and response come into play.

Collision detection and response not only allow us to keep players from passing through the level boundaries, but also are the basis for many forms of interaction. For example, lots of actions in games are started when the player hits an invisible collision mesh, called a trigger, which initiates a scripted sequence as a response to the player entering its boundaries.

Simple collision detection and response form the basis for nearly all forms of interaction in video games. It’s responsible for keeping the player within the level, for crates being pushable, for telling if and where a bullet hit the enemy.

What if we could add some extra magic to the mix to make our games even more believable, immersive, and entertaining? Let’s think again about pushing crates around: What happens if the player pushes a stack of crates? Do they just move like they have been glued together, or will they start to tumble and eventually topple over?

This is where we add physics to the mix to make things more interesting, realistic, and dynamic.

In this article, we will take a look at the various collision detection and physics libraries that the Panda3D engine allows us to work with. Putting in some extra effort, we will also see that it is not very hard to integrate a physics engine that is not part of the Panda3D SDK.

Using the built-in collision detection system

Not all problems concerning world and player interaction need to be handled by a fully fledged physics API—sometimes a much more basic and lightweight system is just enough for our purposes. This is why in this recipe we dive into the collision handling system that is built into the Panda3D engine.

Getting ready

This recipe relies upon the project structure created in Setting up the game structure (code download-Ch:1), Setting Up Panda3D and Configuring Development Tools.

How to do it...

Let’s go through this recipe’s tasks:

  1. Open Application.py and add the include statements as well as the constructor of the Application class:

    from direct.showbase.ShowBase import ShowBase
    from panda3d.core import *
    import random

    class Application(ShowBase):
    def __init__(self):
    ShowBase.__init__(self)
    self.cam.setPos(0, -50, 10)
    self.setupCD()
    self.addSmiley()
    self.addFloor()
    taskMgr.add(self.updateSmiley, "UpdateSmiley")

  2. Next, add the method that initializes the collision detection system:

    def setupCD(self):
    base.cTrav = CollisionTraverser()
    base.cTrav.showCollisions(render)
    self.notifier = CollisionHandlerEvent()
    self.notifier.addInPattern("%fn-in-%in")
    self.accept("frowney-in-floor", self.onCollision)

  3. Next, implement the method for adding the frowney model to the scene:

    def addSmiley(self):
    self.frowney = loader.loadModel("frowney")
    self.frowney.reparentTo(render)
    self.frowney.setPos(0, 0, 10)
    self.frowney.setPythonTag("velocity", 0)

    col = self.frowney.attachNewNode(CollisionNode("frowney"))
    col.node().addSolid(CollisionSphere(0, 0, 0, 1.1))
    col.show()
    base.cTrav.addCollider(col, self.notifier)

  4. The following methods will add a floor plane to the scene and handle the collision response:

    def addFloor(self):
    floor = render.attachNewNode(CollisionNode("floor"))
    floor.node().addSolid(CollisionPlane(Plane(Vec3(0, 0, 1),
    Point3(0, 0, 0))))
    floor.show()

    def onCollision(self, entry):
    vel = random.uniform(0.01, 0.2)
    self.frowney.setPythonTag("velocity", vel)

  5. Add this last piece of code. This will make the frowney model bounce up and down:

    def updateSmiley(self, task):
    vel = self.frowney.getPythonTag("velocity")
    z = self.frowney.getZ()
    self.frowney.setZ(z + vel)
    vel -= 0.001
    self.frowney.setPythonTag("velocity", vel)
    return task.cont

  6. Hit the F6 key to launch the program:

    Panda3D Game Development tutorial

How it works...

We start off by adding some setup code that calls the other initialization routines. We also add the task that will update the smiley’s position.

In the setupCD() method, we initialize the collision detection system. To be able to find out which scene objects collided and issue the appropriate responses, we create an instance of the CollisionTraverser class and assign it to base.cTrav. The variable name is important, because this way, Panda3D will automatically update the CollisionTraverser every frame. The engine checks if a CollisionTraverser was assigned to that variable and will automatically add the required tasks to Panda3D’s update loop.

Additionally, we enable debug drawing, so collisions are being visualized at runtime. This will overlay a visualization of the collision meshes the collision detection system uses internally.

In the last lines of setupCD(), we instantiate a collision handler that sends a message using Panda3D’s event system whenever a collision is detected. The method call addInPattern(“%fn-in-%in”) defines the pattern for the name of the event that is created when a collision is encountered the first time. %fn will be replaced by the name of the object that bumps into another object that goes by the name that will be inserted in the place of %in. Take a look at the event handler that is added below to get an idea of what these events will look like.

After the code for setting up the collision detection system is ready, we add the addSmiley() method, where we first load the model and then create a new collision node, which we attach to the model’s node so it is moved around together with the model. We also add a sphere collision shape, defined by its local center coordinates and radius. This is the shape that defines the boundaries; the collision system will test against it to determine whether two objects have touched.

To complete this step, we register our new collision node with the collision traverser and configure it to use the collision handler that sends events as a collision response.

Next, we add an infinite floor plane and add the event handling method for reacting on collision notifications. Although the debug visualization shows us a limited rectangular area, this plane actually has an unlimited width and height. In our case, this means that at any given x- and y-coordinate, objects will register a collision when any point on their bounding volume reaches a z-coordinate of 0. It’s also important to note that the floor is not registered as a collider here. This is contrary to what we did for the frowney model and guarantees that the model will act as the collider, and the floor will be treated as the collidee when a contact between the two is encountered.

While the onCollision() method makes the smiley model go up again, the code in updateSmiley() constantly drags it downwards. Setting the velocity tag on the frowney model to a positive or negative value, respectively, does this in these two methods. We can think of that as forces being applied. Whenever we encounter a collision with the ground plane, we add a one-shot bounce to our model. But what goes up must come down, eventually. Therefore, we continuously add a gravity force by decreasing the model’s velocity every frame.

There’s more...

This sample only touched a few of the features of Panda3D’s collision system. The following sections are meant as an overview to give you an impression of what else is possible. For more details, take a look into Panda3D’s API reference.

Collision Shapes

In the sample code, we used CollisionPlane and CollisionSphere, but there are several more shapes available:

  • CollisionBox: A simple rectangular shape. Crates, boxes, and walls are example usages for this kind of collision shape.
  • CollisionTube: A cylinder with rounded ends. This type of collision mesh is often used as a bounding volume for first and third person game characters.
  • CollisionInvSphere: This shape can be thought of as a bubble that contains objects, like a fish bowl. Everything that is outside the bubble is reported to be colliding. A CollisionInvSphere may be used to delimit the boundaries of a game world, for example.
  • CollisionPolygon: This collision shape is formed from a set of vertices, and allows for the creating of freeform collision meshes. This kind of shape is the most complex to test for collisions, but also the most accurate one. Whenever polygon-level collision detection is important, when doing hit detection in a shooter for example, this collision mesh comes in handy.
  • CollisionRay: This is a line that, starting from one point, extends to infinity in a given direction. Rays are usually shot into a scene to determine whether one or more objects intersect with them. This can be used for various tasks like finding out if a bullet shot in the given direction hit a target, or simple AI tasks like finding out whether a bot is approaching a wall.
  • CollisionLine: Like CollisionRay, but stretches to infinity in both directions.
  • CollisionSegment: This is a special form of ray that is limited by two end points.
  • CollisionParabola: Another special type of ray that is bent. The flying curves of ballistic objects are commonly described as parabolas. Naturally, we would use this kind of ray to find collisions for bullets, for example.

Collision Handlers

Just like it is the case with collision shapes for this recipe, we only used CollisionHandlerEvent for our sample program, even though there are several more collision handler classes available:

  • CollisionHandlerPusher: This collision handler automatically keeps the collider out of intersecting vertical geometry, like walls.
  • CollisionHandlerFloor: Like CollisionHandlerPusher, but works in the horizontal plane.
  • CollisionHandlerQueue: A very simple handler. All it does is add any intersecting objects to a list.
  • PhysicsCollisionHandler: This collision handler should be used in connection with Panda3D’s built-in physics engine. Whenever a collision is found by this collision handler, the appropriate response is calculated by the simple physics engine that is built into the engine.

Using the built-in physics system

Panda3D has a built-in physics system that treats its entities as simple particles with masses to which forces may be applied. This physics system is a great amount simpler than a fully featured rigid body one. But it still is enough for cheaply, quickly, and easily creating some nice and simple physics effects.

Getting ready

To be prepared for this recipe, please first follow the steps found in Setting up the game structure (code download-Ch:1). Also, the collision detection system of Panda3D will be used, so reading up on it in Using the built-in collision detection system might be a good idea!

How to do it...

The following steps are required to work with Panda3D’s built-in physics system:

  1. Edit Application.py and add the required import statements as well as the constructor of the Application class:

    from direct.showbase.ShowBase import ShowBase
    from panda3d.core import *
    from panda3d.physics import *

    class Application(ShowBase):
    def __init__(self):
    ShowBase.__init__(self)
    self.cam.setPos(0, -50, 10)
    self.setupCD()
    self.setupPhysics()
    self.addSmiley()
    self.addFloor()

  2. Next, add the methods for initializing the collision detection and physics systems to the Application class:

    def setupCD(self):
    base.cTrav = CollisionTraverser()
    base.cTrav.showCollisions(render)
    self.notifier = CollisionHandlerEvent()
    self.notifier.addInPattern("%fn-in-%in")
    self.notifier.addOutPattern("%fn-out-%in")
    self.accept("smiley-in-floor", self.onCollisionStart)
    self.accept("smiley-out-floor", self.onCollisionEnd)

    def setupPhysics(self):
    base.enableParticles()
    gravNode = ForceNode("gravity")
    render.attachNewNode(gravNode)
    gravityForce = LinearVectorForce(0, 0, -9.81)
    gravNode.addForce(gravityForce)
    base.physicsMgr.addLinearForce(gravityForce)

  3. Next, implement the method for adding a model and physics actor to the scene:

    def addSmiley(self):
    actor = ActorNode("physics")
    actor.getPhysicsObject().setMass(10)
    self.phys = render.attachNewNode(actor)
    base.physicsMgr.attachPhysicalNode(actor)

    self.smiley = loader.loadModel("smiley")
    self.smiley.reparentTo(self.phys)
    self.phys.setPos(0, 0, 10)

    thrustNode = ForceNode("thrust")
    self.phys.attachNewNode(thrustNode)
    self.thrustForce = LinearVectorForce(0, 0, 400)
    self.thrustForce.setMassDependent(1)
    thrustNode.addForce(self.thrustForce)

    col = self.smiley.attachNewNode(CollisionNode("smiley"))
    col.node().addSolid(CollisionSphere(0, 0, 0, 1.1))
    col.show()
    base.cTrav.addCollider(col, self.notifier)

  4. Add this last piece of source code that adds the floor plane to the scene to Application.py:

    Application.py:
    def addFloor(self):
    floor = render.attachNewNode(CollisionNode("floor"))
    floor.node().addSolid(CollisionPlane(Plane(Vec3(0, 0, 1),
    Point3(0, 0, 0))))
    floor.show()

    def onCollisionStart(self, entry):
    base.physicsMgr.addLinearForce(self.thrustForce)

    def onCollisionEnd(self, entry):
    base.physicsMgr.removeLinearForce(self.thrustForce)

  5. Start the program by pressing F6:

    Panda3D Game Development tutorial

How it works...

After adding the mandatory libraries and initialization code, we proceed to the code that sets up the collision detection system. Here we register event handlers for when the smiley starts or stops colliding with the floor. The calls involved in setupCD() are very similar to the ones used in Using the built-in collision detection system. Instead of moving the smiley model in our own update task, we use the built-in physics system to calculate new object positions based on the forces applied to them.

In setupPhysics(), we call base.enableParticles() to fire up the physics system. We also attach a new ForceNode to the scene graph, so all physics objects will be affected by the gravity force. We also register the force with base.physicsMgr, which is automatically defined when the physics engine is initialized and ready.

In the first couple of lines in addSmiley(), we create a new ActorNode, give it a mass, attach it to the scene graph and register it with the physics manager class. The graphical representation, which is the smiley model in this case, is then added to the physics node as a child so it will be moved automatically as the physics system updates.

We also add a ForceNode to the physics actor. This acts as a thruster that applies a force that pushes the smiley upwards whenever it intersects the floor. As opposed to the gravity force, the thruster force is set to be mass dependant. This means that no matter how heavy we set the smiley to be, it will always be accelerated at the same rate by the gravity force. The thruster force, on the other hand, would need to be more powerful if we increased the mass of the smiley.

The last step when adding a smiley is adding its collision node and shape, which leads us to the last methods added in this recipe, where we add the floor plane and define that the thruster should be enabled when the collision starts, and disabled when the objects’ contact phase ends.

Panda3D 1.7 Game Developer's Cookbook Over 80 recipes for developing 3D games with Panda3D, a full-scale 3D game engine
Published: March 2011
eBook Price: £16.99
Book Price: £27.99
See more
Select your format and quantity:
        Read more about this book      

(For more resources on this subject, see here.)

Using the ODE physics engine

The Open Dynamics Engine (ODE) in short, is a very powerful and feature-rich implementation of a rigid body physics system. It has been successfully integrated into various commercial simulation and game projects like World of Goo and Nail’d, for example. Panda3D comes with this proven piece of physics technology included out of the box. This leaves it to us to enable and use ODE in our code, so let’s get started!

Getting ready

To get set for this recipe, please follow Setting up the game structure first. You can find this recipe in Setting up the game structure (code download-Ch:1).

How to do it...

The ODE physics engine is used like the following in a Panda3D application:

  1. Open Application.py and add this code:

    from direct.showbase.ShowBase import ShowBase
    from panda3d.core import *
    from panda3d.ode import *
    import random

    class Application(ShowBase):
    def __init__(self):
    ShowBase.__init__(self)
    self.smiley = loader.loadModel("smiley")
    self.smileyCount = 0
    self.cam.setPos(0, -100, 10)

    self.setupODE()
    self.addGround()

    taskMgr.doMethodLater(0.01, self.addSmiley, "AddSmiley")
    taskMgr.add(self.updateODE, "UpdateODE")

  2. Append the following code to the file:

    def setupODE(self):
    self.odeWorld = OdeWorld()
    self.odeWorld.setGravity(0, 0, -9.81)
    self.odeWorld.initSurfaceTable(1)
    self.odeWorld.setSurfaceEntry(0, 0, 200, 0.7, 0.2, 0.9,
    0.00001, 0.0, 0.002)

    self.space = OdeSimpleSpace()
    self.space.setAutoCollideWorld(self.odeWorld)
    self.contacts = OdeJointGroup()
    self.space.setAutoCollideJointGroup(self.contacts)

    def addGround(self):
    cm = CardMaker("ground")
    cm.setFrame(-500, 500, -500, 500)
    ground = render.attachNewNode(cm.generate())
    ground.setColor(0.2, 0.4, 0.8)
    ground.lookAt(0, 0, -1)
    groundGeom = OdePlaneGeom(self.space, Vec4(0, 0, 1, 0))

  3. Next, add these methods:

    def addSmiley(self, task):
    sm = render.attachNewNode("smiley-instance")
    sm.setPos(random.uniform(-20, 20), random.uniform(-30,
    30), random.uniform(10, 30))
    self.smiley.instanceTo(sm)

    body = OdeBody(self.odeWorld)
    mass = OdeMass()
    mass.setSphereTotal(10, 1)
    body.setMass(mass)
    body.setPosition(sm.getPos())
    geom = OdeSphereGeom(self.space, 1)
    geom.setBody(body)

    sm.setPythonTag("body", body)
    self.smileyCount += 1

    if self.smileyCount == 1000:
    return task.done
    return task.again

    def updateODE(self, task):
    self.space.autoCollide()
    self.odeWorld.quickStep(globalClock.getDt())

    for smiley in render.findAllMatches("smiley-instance"):
    body = smiley.getPythonTag("body")
    smiley.setPosQuat(body.getPosition(), Quat(body.
    getQuaternion()))

    self.contacts.empty()
    return task.cont

  4. Press F6 to launch the program and see the smileys roll, tumble, and bounce:

    Panda3D Game Development tutorial

How it works...

For ODE to work, we need to do the following things.

We create a new OdeWorld, set its gravity, and add a default surface description. The surface description defines the properties of two surfaces that collide. The first two parameters of setSurfaceEntry() are indices in the surface table, with index zero being the default surface. The next parameter is the friction coefficient, which sets how slippery or sticky the surfaces react to each other. The higher this value is set, the higher the friction will be. The fourth parameter describes the bounciness on a scale between 0 and 1, while the fifth parameter sets the minimum velocity that is required for an object to bounce at all. The next two parameters define the values for error reduction and constraint force mixing, which influence to which extent objects are allowed to penetrate each other and how much force will be applied to push objects out of each other. This is followed by the slip parameter that is used to set a force-dependent slip value, which is useful for simulating car tires, for example. The last parameter sets a damping coefficient, which helps to keep the simulation more stable.

Next, we create an OdeSimpleSpace, which represents a space within which ODE tests for collisions. We configure ODE to automatically detect and resolve collisions. This requires a joint group, because whenever two objects intersect in ODE, a temporary joint is created between them that pushes the objects off each other.

In the addGround() method, we create a static ground plane, both in our visible scene as well as in ODE’s simulation world.

Whenever a new smiley is added, we create a new OdeBody, set its mass and position and assign it to a new OdeSphereGeom. We then assign the ODE physics body to a tag of the smiley NodePath. This part of the code also shows a very important principle of ODE—the OdeBody is used for physics calculations, whereas the geometry is used for collision detection.

The updateOde() method is run every frame as a task. It first runs collision detection, then steps the physics simulation, and updates the positions of the objects in the scene graph. Before the method returns, we must not forget to clear the list of contact groups to keep the automatic collision response working properly.

Using the PhysX physics engine

PhysX is a proprietary physics programming library made by the graphics card specialist nVidia that simulates the behavior of rigid bodies. One of the library's major features is its ability to leverage the computing abilities of a graphics card to accelerate the calculation of the internal physics formulas used for the simulation. If a suitable graphics adapter cannot be found, the physics engine falls back to the system’s CPU so that nobody’s left behind.

This recipe will give you some insight into the wrapper that the Panda3D developers have created around this physics API.

Getting ready

This recipe builds upon the framework created in Setting up the game structure found in Setting up the game structure (code download-Ch:1).

Additionally, you need to have the PhysX SDK installed on your system. For more information on licensing and how to download, see this website: http://developer.nvidia.com/object/physx_downloads.html.

How to do it...

These are the tasks required to create a program that uses PhysX:

  1. Copy the file NxCharacter.dll from C:\Program Files (x86)\NVIDIA Corporation\NVIDIA PhysX SDK\v2.8.4_win\Bin\win32 to C:\Panda3D-1.7.0\bin.
  2. Open Application.py and add the required imports and the constructor:

    from direct.showbase.ShowBase import ShowBase
    from panda3d.core import *
    from panda3d.physx import *
    import random

    class Application(ShowBase):
    def __init__(self):
    ShowBase.__init__(self)
    self.box = loader.loadModel("misc/rgbCube")
    self.boxCount = 0
    self.cam.setPos(0, -100, 10)

    self.setupPhysX()
    self.addGround()

    taskMgr.doMethodLater(0.01, self.addBox, "AddBox")
    taskMgr.add(self.updatePhysX, "UpdatePhysX")

  3. Append the methods for initializing PhysX and adding a ground plane:

    def setupPhysX(self):
    scene = PhysxSceneDesc()
    scene.setGravity(Vec3(0, 0, -9.81))
    self.physxScene = PhysxManager.getGlobalPtr().
    createScene(scene)

    mat = self.physxScene.getMaterial(0)
    mat.setRestitution(0.7)
    mat.setStaticFriction(0.5)
    mat.setDynamicFriction(0.8)
    def addGround(self):
    cm = CardMaker("ground")
    cm.setFrame(-500, 500, -500, 500)
    ground = render.attachNewNode(cm.generate())
    ground.setColor(0.2, 0.2, 0.2)
    ground.lookAt(0, 0, -1)

    shape = PhysxPlaneShapeDesc()
    shape.setPlane(Vec3(0, 0, 1), 0)
    actor = PhysxActorDesc()
    actor.addShape(shape)
    self.physxScene.createActor(actor)

  4. Add the methods for adding boxes and updating the physics simulation:

    def addBox(self, task):
    bx = render.attachNewNode("box-instance")
    self.box.instanceTo(bx)
    shape = PhysxBoxShapeDesc()
    shape.setDimensions(Vec3(0.5, 0.5, 0.5))
    body = PhysxBodyDesc()
    body.setMass(10)
    actor = PhysxActorDesc()
    actor.setBody(body)
    actor.addShape(shape)
    actor.setGlobalPos(Point3(random.uniform(-20, 20), random.
    uniform(-30, 30), random.uniform(10, 30)))
    physxActor = self.physxScene.createActor(actor)
    physxActor.attachNodePath(bx)

    self.boxCount += 1

    if self.boxCount == 1000:
    return task.done
    return task.again

    def updatePhysX(self, task):
    self.physxScene.simulate(globalClock.getDt())
    self.physxScene.fetchResults()
    return task.cont

  5. Press F6 to run the sample:

    Panda3D Game Development tutorial

How it works...

First, we make sure to place NxCharacter.dll in the right location, or else we won’t be able to use the API. The exact path of the source directory might vary slightly, depending on which version of the PhysX SDK is installed.

After adding the boilerplate code for loading modules and setting up the Application class, we can proceed to the setupPhysX() method, where we create a new PhysX scene with earth-like gravitation. We also modify the material stored at index zero—the default material—to be a bit more bouncy and also have more friction.

We then add a ground plane by creating a shape description, adding it to an actor description, and creating the new physics actor using the createActor() method.

In the addSmiley() method, we connect a shape, used for collision detection, and body, which is used for physics calculations, to form a new actor. We also attach the created smiley NodePath to the newly created actor, so its transformation is automatically updated as PhysX advances the simulation.

Finally, we add the code for the task that keeps on updating the simulation. This method first performs a simulation step and afterwards instructs the PhysX API to collect and apply the results of the calculations it performed.

Panda3D 1.7 Game Developer's Cookbook Over 80 recipes for developing 3D games with Panda3D, a full-scale 3D game engine
Published: March 2011
eBook Price: £16.99
Book Price: £27.99
See more
Select your format and quantity:
        Read more about this book      

(For more resources on this subject, see here.)

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.

This recipe builds on the project setup described in Creating a scene using C++ found in code bundle-Ch: 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 Lib\bullet-2.77.
  3. Navigate to the msvc\2008 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;..\Lib\bullet-2.77\
    src&quot;;&quot;C:\Panda3D-1.7.0\python\include&quot;;&quot;C:\
    Panda3D-1.7.0\include&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;..\Lib\bullet-2.77\
    msvc\2008\lib\Release&quot;;&quot;C:\Panda3D-1.7.0\python\
    libs&quot;;&quot;C:\Panda3D-1.7.0\lib&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();
    PT(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)));

    btTransform 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*>(da
    ta);
    btWorld->stepSimulation(dt);
    return AsyncTask::DS_cont;
    }

  15. Press Ctrl + F5 to compile and run the program:

    Panda3D Game Development tutorial

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.

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.


Further resources on this subject:


About the Author :


Christoph Lang

Christoph Lang is a game developer currently working for Mi’pu’mi Games in Vienna. He has a BSc in Computer Science and an MSc in Game Engineering and Simulation Technology, both from UAS Technikum Wien. You can find him blogging on altdevblogaday.com and tweeting as @moorx.

Books From Packt


Panda3D 1.6 Game Engine Beginner's Guide
Panda3D 1.6 Game Engine Beginner's Guide

3D Graphics with XNA Game Studio 4.0
3D Graphics with XNA Game Studio 4.0

Flash Game Development by Example
Flash Game Development by Example

ZBrush 4 Sculpting for Games: Beginner's Guide
ZBrush 4 Sculpting for Games: Beginner's Guide

Unity 3D Game Development by Example Beginner's Guide
Unity 3D Game Development by Example Beginner's Guide

OGRE 3D 1.7 Beginner's Guide
OGRE 3D 1.7 Beginner's Guide

Flash Multiplayer Virtual Worlds
Flash Multiplayer Virtual Worlds

Away3D 3.6 Cookbook
Away3D 3.6 Cookbook


Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software