Bullet Debug Renderer

In the previous “hello world” sample we have been introduced to a few Bullet physics objects, for example the rigid body (BulletRigidBodyNode) or box and plane collision shapes (BulletPlaneShape, BulletBoxShape).

These objects are part of the Panda3D scene graph. But they are not visible. In order to be able to actually see a rigid body we had to reparent a visible geometry below the rigid body node. This is fine, since we (1) can control the way an object looks like, by choosing whatever visible geometry we want, and (2) we can create invisible objects too, by not reparenting any geometry below a rigid body.

But when developing a game it sometimes would be handy to actually see where the physical objects are. This is what the BulletDebugNode is for. It’s not meant for users playing the game, but as an aid in finding problems while developing the game.

The debug node is pretty easy to use. We just need to create such a node, place it in the scene graph, and tell the Bullet world that we have such a node. From now on Bullet will create a “debug” visualisation of the world’s content within the debug node, whenever do_physics() is called. The following code snippet shows how to do this:

#include "bulletDebugNode.h"
...
PT(BulletDebugNode) bullet_dbg_node;
bullet_dbg_node = new BulletDebugNode("Debug");
bullet_dbg_node->show_bounding_boxes(true);
bullet_dbg_node->show_constraints(true);
bullet_dbg_node->show_normals(true);
bullet_dbg_node->show_wireframe(true);

NodePath np_dbg_node = window->get_render().attach_new_node(bullet_dbg_node);
np_dbg_node.show();

world->set_debug_node(bullet_dbg_node);
...

We can control the amount of information rendered using the following methods:

show_wireframe()

Displays collision shapes in wireframe mode.

show_constraints()

Display limits defined for constraints, e.g. a pivot axis or maximum amplitude.

show_bounding_boxes()

Displays axis aligned bounding boxes for objects.

show_normals()

Displays normal vectors for triangle mesh and heightfield faces.

There is one thing to pay attention to: By default the BulletDebugNode is hidden right after creation. If we want to see the debug visualisation from the first frame on we have to unhide it, using show().

Since debug rendering is not very fast we can turn debug rendering on and off, without having to remove the debug node from the scene graph. Turning debug rendering on and of is simply done by hiding or showing the debug node. The following code shows how to toggle debug node visibility on and off, using the F1 key:

framework.define_key("f1", "Toggle Physics debug", [](const Event *e) {
  if (np_dbg_node.is_hidden()) {
    np_dbg_node.show();
  } else {
    np_dbg_node.hide();
  }
});
// Bullet Debug Node Example.
// The following example is done from Python sources, Panda Reference and Panda Manual,
// for more information, visit Panda3D and/or Bullet physics web site.

// Compiling and Linking documentation and notes are not
// covered in this file, check manual for mor information.

#include "pandaFramework.h"
#include "windowFramework.h"
#include "nodePath.h"
#include "clockObject.h"

#include "asyncTask.h"

#include "bulletWorld.h"
#include "bulletDebugNode.h"
#include "bulletPlaneShape.h"
#include "bulletBoxShape.h"

int main(int argc, char *argv[]) {
  // All variables.
  PandaFramework framework;
  WindowFramework *window;

  // Init everything :D
  framework.open_framework(argc, argv);
  framework.set_window_title("Bullet Physics");

  window = framework.open_window();
  window->enable_keyboard();
  window->setup_trackball();

  // Make physics simulation.
  // Static world stuff.
  PT(BulletWorld) world = new BulletWorld;
  world->set_gravity(0, 0, -9.8);

  PT(BulletPlaneShape) floor_shape = new BulletPlaneShape(LVecBase3(0, 0, 1), 1);
  PT(BulletRigidBodyNode) floor_rigid_node = new BulletRigidBodyNode("Ground");

  floor_rigid_node->add_shape(floor_shape);

  NodePath np_ground = window->get_render().attach_new_node(floor_rigid_node);
  np_ground.set_pos(0, 0, -2);
  world->attach(floor_rigid_node);

  // Dynamic world stuff.
  PT(BulletBoxShape) box_shape = new BulletBoxShape(LVecBase3(0.5, 0.5, 0.5));
  PT(BulletRigidBodyNode) box_rigid_node = new BulletRigidBodyNode("Box");

  box_rigid_node->set_mass(1.0); // Gravity affects this rigid node.
  box_rigid_node->add_shape(box_shape);

  NodePath np_box = window->get_render().attach_new_node(box_rigid_node);
  np_box.set_pos(0, 0, 2);
  world->attach(box_rigid_node);

  NodePath np_box_model = window->load_model(framework.get_models(), "models/box");
  np_box_model.set_pos(-0.5, -0.5, -0.5);
  np_box.flatten_light();
  np_box_model.reparent_to(np_box);

  // Debug stuff.
  BulletDebugNode *bullet_dbg_node = new BulletDebugNode("Debug");
  bullet_dbg_node->show_bounding_boxes(true);
  bullet_dbg_node->show_constraints(true);
  bullet_dbg_node->show_normals(true);
  bullet_dbg_node->show_wireframe(true);

  NodePath np_dbg_node = window->get_render().attach_new_node(bullet_dbg_node);
  np_dbg_node.show();

  world->set_debug_node(bullet_dbg_node);

  // Add a key to toggle debug visibility.
  bool show_state = true;
  framework.define_key("f1", "Toggle Physics debug", [=, &show_state](const Event *) {
    show_state = !show_state;
    bullet_dbg_node->show_bounding_boxes(show_state);
    bullet_dbg_node->show_constraints(show_state);
    bullet_dbg_node->show_normals(show_state);
    bullet_dbg_node->show_wireframe(show_state);
  });

  framework.get_task_mgr().add("update", [=](AsyncTask *task) {
    // Get dt and apply to do_physics(float, int, int);
    ClockObject *clock = ClockObject::get_global_clock();
    world->do_physics(clock->get_dt(), 10, 1.0 / 180.0);

    return AsyncTask::DS_cont;
  });

  framework.main_loop();
  return 0;
}