Bullet Hello World
This page intends to lead through a minimal “hello world” program using Panda3D and Bullet physics.
In order to use Bullet physics we need to have a BulletWorld. The world is Panda3D’s term for a “space” or “scene”. The world holds physical objects like rigid bodies, soft bodies or character controllers. It controls global parameters, such a gravity, and it advances the simulation state.
from panda3d.bullet import BulletWorld world = BulletWorld() world.setGravity(Vec3(0, 0, -9.81))
The above code creates a new world, and it sets the worlds gravity to a downward vector with length 9.81. While Bullet is in theory independent from any particular units it is recommended to stick with SI units (kilogram, meter, second). In SI units 9.81 m/s² is the gravity on Earth’s surface.
Next we need to advance the simulation state. This is best done by a task which
gets called each frame. We find out about the elapsed time (dt), and pass this
value to the
def update(task): dt = globalClock.getDt() world.doPhysics(dt) return task.cont taskMgr.add(update, 'update')
doPhysics method allows finer control on the way the simulation state is
advanced. Internally Bullet splits a timestep into several substeps. We can pass
a maximum number of substeps and the size of each substep, like show in the
world.doPhysics(dt, 10, 1.0/180.0)
Here we have a maximum of 10 substeps, each with 1/180 seconds. Choosing smaller substeps will make the simulation more realistic, but performance will decrease too. Smaller substeps also reduce jitter.
So far we just have an empty world. We next need to add some objects. The most simple objects are static bodies. Static object don’t change their position or orientation with time. Typical static objects are the ground or terrain, and houses or other non-moveable obstacles. Here we create a simple plane which will serve as a ground.
from panda3d.bullet import BulletPlaneShape from panda3d.bullet import BulletRigidBodyNode shape = BulletPlaneShape(Vec3(0, 0, 1), 1) node = BulletRigidBodyNode('Ground') node.addShape(shape) np = render.attachNewNode(node) np.setPos(0, 0, -2) world.attachRigidBody(node)
First we create a collision shape, in the case a
BulletPlaneShape. We pass
the plane’s constant and normal vector within the shape’s constructor. There is
a separate page about setting up the various collision shapes offered by Bullet,
so we won’t go into more detail here.
Next we create a rigid body and add the previously created shape.
BulletRigidBodyNode is derived from
PandaNode, and thus the rigid body
can be placed within the Panda3D scene graph. you can also use methods like
setH to place the rigid body node where you want it to be.
Finally we need to attach the newly created rigid body node to the world. Only rigid bodies attached to the world will be considered when advancing the simulation state.
Dynamic bodies are similar to static bodies. Except that dynamic bodies can be moved around the world by applying force or torque. To setup a dynamic body is almost the same as for static bodies. We will have to set one additional property though, the body’s mass. Setting a positive finite mass will create a dynamic body, while setting the mass to zero will create a static body. Zero mass is a convention for setting an infinite mass, which is the same as making the body unmovable (static).
from panda3d.bullet import BulletBoxShape shape = BulletBoxShape(Vec3(0.5, 0.5, 0.5)) node = BulletRigidBodyNode('Box') node.setMass(1.0) node.addShape(shape) np = render.attachNewNode(node) np.setPos(0, 0, 2) world.attachRigidBody(node)
Bullet will automatically update a rigid body node’s position and orientation if
is has changed after advancing the simulation state. So, if you have a
GeomNode- e. g. a textured box - and reparent this geom node below the rigid
body node, then the geom node will move around together with the rigid body. You
don’t have to synchronize the visual world with the physics world.
Let’s put everything learned on this page together into a single script, which is shown below. It assumes that you have an .egg model of a 1 by 1 by 1 box.
when running the script you will see a box falling down onto an invisible plane. The plane is invisible simply because we didn’t parent a visual mode below the plane’s rigid body node. Of course we could have done so.
The model cube.egg used in this hello word sample can be found in the following archive: https://www.panda3d.org/download/noversion/bullet-samples.zip
import direct.directbase.DirectStart from panda3d.core import Vec3 from panda3d.bullet import BulletWorld from panda3d.bullet import BulletPlaneShape from panda3d.bullet import BulletRigidBodyNode from panda3d.bullet import BulletBoxShape base.cam.setPos(0, -10, 0) base.cam.lookAt(0, 0, 0) # World world = BulletWorld() world.setGravity(Vec3(0, 0, -9.81)) # Plane shape = BulletPlaneShape(Vec3(0, 0, 1), 1) node = BulletRigidBodyNode('Ground') node.addShape(shape) np = render.attachNewNode(node) np.setPos(0, 0, -2) world.attachRigidBody(node) # Box shape = BulletBoxShape(Vec3(0.5, 0.5, 0.5)) node = BulletRigidBodyNode('Box') node.setMass(1.0) node.addShape(shape) np = render.attachNewNode(node) np.setPos(0, 0, 2) world.attachRigidBody(node) model = loader.loadModel('models/box.egg') model.flattenLight() model.reparentTo(np) # Update def update(task): dt = globalClock.getDt() world.doPhysics(dt) return task.cont taskMgr.add(update, 'update') base.run()