ODE Tutorial

This tutorial explains the basics of ODE through the construction of a simple applications that displays cubes falling on the ground.

Preliminary setup

Discovering the QGLViewer

We are going to use ODE for the physics and a very handy library for OpenGL display of the physic simulation, named QGLViewer. This library is based on Qt and encapsulates a viewer for rendering and manipulating (flying through, rotating with a trackball,...) a 3D scene. There are excellent tutorials on the web site, so I just present here the very fundamentals of the library.

Here is for example a simple viewer that shows a ball whose color is changing over the time.

This section corresponds to code in tutorial1/A of the snippets directory. It contains the following files. Click a file to see the whole code in another window. Click here to download a tarball.
1
#ifndef VIEWER_H
2
#define VIEWER_H
3
 
4
#include <QGLViewer/qglviewer.h>
5
 
6
class Viewer : public QGLViewer
7
{
8
  Q_OBJECT;
9
public:
10
  Viewer(QWidget *parent=NULL);
11
  ~Viewer();
12
 
13
protected:
14
  virtual void init();
15
  virtual void animate();
16
  virtual void draw();
17
private:
18
  float _t;
19
};
20
 
21
#endif // VIEWER_H
22
 
1
#include "viewer.h"
2
#include <GL/glut.h>
3
 
4
using namespace std;
5
using namespace qglviewer;
6
 
7
Viewer::Viewer(QWidget *parent)
8
  : QGLViewer(QGLFormat(QGL::SampleBuffers |
9
                        QGL::DoubleBuffer |
10
                        QGL::DepthBuffer |
11
                        QGL::Rgba |
12
                        QGL::AlphaChannel |
13
                        QGL::StencilBuffer),parent)
14
  , _t(0.0f)
15
{
16
}
17
Viewer::~Viewer()
18
{
19
}
20
void Viewer::init()
21
{
22
  // Indicate the size of the scene
23
  setSceneRadius(2.0f);
24
  setSceneCenter(Vec(0.0f,0.0f,0.0f));
25
  // Try to restore previous state or focus the whole scene
26
  if (!restoreStateFromFile())
27
  {
28
    showEntireScene();
29
  }
30
}
31
void Viewer::animate()
32
{
33
  _t += animationPeriod()/1000.0f;
34
  if (_t>1) _t = 0.0f;
35
}
36
void Viewer::draw()
37
{
38
  static const Vec red  (1.0f,0.0f,0.0f);
39
  static const Vec green(0.0f,1.0f,0.0f);
40
 
41
  glColor3fv((1-_t)*red+_t*green)
42
  glutSolidSphere(0.5f,30,30);
43
}
44
 
1
#include "viewer.h"
2
#include <QApplication>
3
#include <GL/glut.h>
4
 
5
using namespace std;
6
 
7
int main(int argc,char **argv)
8
{
9
  glutInit(&argc,argv);
10
 
11
  QApplication application(argc,argv);
12
 
13
  Viewer viewer;
14
  viewer.show();
15
 
16
  return application.exec();
17
}
18
 

Compiling with qmake in a nutshell

Qt has a very nice tool to manage compilation, called qmake. You write a simple project file describing the sources and some configuration, and qmake generates the appropriate Makefile for your platform. Here is such a .pro for our simple example abobe:

tutorial1/A/ode-1-A.pro
1
CONFIG  *= qglviewer glut ode
2
 
3
HEADERS *=  viewer.h
4
SOURCES *=  viewer.cpp main.cpp

If you notice the first line, I indicate that I want the project to use qglviewer and glut configuration. These are non standard Qt configuration, so I need to tell Qt what to do when they are presents. For that, I define a qglviewer.prf and a glut.prf files that I put in some directory pointed by the environment variable QMAKEFEATURES. Here are what it looks like on my linux desktop:

1
CONFIG *= qt
2
QT     *= opengl xml
3
 
4
LIBS        *= -lQGLViewer
5
 
1
QT     *= opengl
2
LIBS   *= -lglut
3
 

I let you adapt this to you particular machine setup. The cool thing about the "feature" mechanism is that it separates machine-dependent configuration (where a particular library is installed) from project-dependent configuration (which file to compile, which library to link with).

Testing our example

Ok, now we can run our example. It displays a simple sphere. Use the three mouse button (+wheel) to turn around the sphere. This is the trackball metaphor. You can press space to switch to a fly metaphor. Press enter to toggle the animation. Quit and re-run the application: the viewpoint is restored! If you want to find more about the existing keybindings, press H, or (better) read the QGLViewer excellent documentation.

ODE in a nutshell

There are many things to know to use ODE correctly, because a lot of magical parameters have to be tuned for your particular application to run as you want. Fortunately, the documentation is quite verbose about this. Conversely (and unfortunately), it lacks a gentle introduction. Let's remedy to this. Here are what you need to know to begin.

Dynamics

ODE has a number of base concepts. Let's start with the two fundamental ones:

So basically, here is how you create a physical simulation.

tutorial1/B/body.cpp
1
  dWorldID world = dWorldCreate();
2
 
3
  // Create a body
4
  dBodyID body = dBodyCreate(_world);
5
 
6
  dMass mass;
7
  dMassSetBox(&mass,1,1,1,1);
8
  dMassAdjust(&mass,0.2f);
9
 
10
  dBodySetMass(body,&mass);
11
  dBodySetPosition(body,0,6,0);
12
 
13
  // Create another body
14
  // ... 
15
 

Here, we create just a cube but you can add as many objects as you want. We'll see more about this later on. For the moment, notice how we specified the mass. We used helper functions. Indeed, what the dynamics need is not the mass as you usually think of it (I weight xxx), but the mass inertia matrix, which also gives you how the weight is distributed over the body (it drives how the body spins). This inertia matrix is inintuitive to specify in matrix form. Luckily, for specific cases, such as a uniformly dense cube or sphere, it is easy to compute from the box/sphere dimensions and density. This is what the helper functions do for you. You can find more in section 9.2.0 of the ODE manual.

Collision

I said above that dynamics simulation itself does not need to know the shape of each body. Well, that is true only if you consider that objects do not interact with each other. In reality, a physical simulation is interesting if bodies interact, namely if they collide, bounce and push each other. To compute such interactions, you know need to know the shape of objects, because this is what commands when/where/how bodies interact. Thus, ODE has two other concepts.

So here is how we would define a geom in ODE, continuing the body example of above.

tutorial1/B/geom.cpp
1
  dSpaceID space = dHashSpaceCreate(0);
2
 
3
  // Create a geom
4
  dGeomID geom = dCreateBox(space,1,1,1);
5
  // Bind together our previously created body and the geom
6
  dGeomSetBody(geom,body);
7
 

The important thing to notice is that we "bind" together a body and a geom. Once this is done, changing the position or orientation of one will change that of the other, that is we can call indifferently dBodySetPosition(body,0,6,0); or dGeomSetPosition(body,geom,6,0);.

The interest of separating the body from the (collision) geometry is that we can create "static" geometry. For that, we just create a geom in the space, and not associated body in the world. Typically, this is used to create a ground plane.

Joints

Bodies do not interact only because of collision. They can also interact because they are "attached" together. In ODE terminology, such an attachment is called a joint. I will not describe here the types of join and how to create them, because in this tutorial, we will only consider indpendent objects (e.g. falling cubes on a plane). Moreover, you will find detailed information in the ODE manual.

However, I will describe a particular type of joints: contact ones. Indeed, we have seen that the space manages the collision detection based on the given geoms. However, once collisions are detected, the dynamics must account for them. It turns out that collision/contact interaction can be described as temporary joints that exists only during the time of the contact. Thus, during simulation, we will:

We will see more about how to do this in a moment.

Preparing for the show

Before we go to the full monthy and a working example, we should prepare the terrain a little bit, so that the code is organized cleanly and you can quickly identify where to put additional codes to extend the tutorial example. This involves some code engineering that I describe shortly here.

Object class

Since bodies and geoms are tightly linked in a normal ODE application, we will encapsulate them together in a bigger entity called object. We will also encapsulate how to render the object to make our application more sexy. For the moment, we will make a very crude encapsulation; we will see later how to make a better object based one.

An object owns a body and a geom, publicly accessible for simplicity. It can also be required to render itself (using OpenGL), and finally, it can be placed at a given position. The interface and implementation are:

1
#ifndef OBJECT_H
2
#define OBJECT_H
3
 
4
#include <ode/ode.h>
5
 
6
class Object
7
{
8
public:
9
  dBodyID body;
10
  dGeomID geom;
11
 
12
  virtual ~Object();
13
  void render() const;
14
  void setPosition(dReal x,dReal y,dReal z);
15
protected:
16
  virtual void renderInLocalFrame() const = 0;
17
 
18
};
19
 
20
#endif // OBJECT_H
21
 
1
#include "object.h"
2
#include <GL/gl.h>
3
 
4
using namespace std;
5
 
6
Object::~Object()
7
{
8
}
9
void Object::render() const
10
{
11
  glPushMatrix();
12
 
13
  const dReal *p = dBodyGetPosition(body);
14
  const dReal *r = dBodyGetRotation(body);
15
  float m[16];
16
  m[ 0] = r[ 0];m[ 1] = r[ 4];m[ 2] = r[ 8];m[ 3] = 0;
17
  m[ 4] = r[ 1];m[ 5] = r[ 5];m[ 6] = r[ 9];m[ 7] = 0;
18
  m[ 8] = r[ 2];m[ 9] = r[ 6];m[10] = r[10];m[11] = 0;
19
  m[12] = p[ 0];m[13] = p[ 1];m[14] = p[ 2];m[15] = 1;
20
  glMultMatrixf(m);
21
 
22
  renderInLocalFrame();
23
  glPopMatrix();
24
}
25
void Object::setPosition(dReal x,dReal y,dReal z)
26
{
27
  // Equivalent to dGeomSetPosition(geom,x,y,z);
28
  dBodySetPosition(body,x,y,z);
29
}
30
 

The render() function does some fiddling to pass from ODE representation (a position and rotation) to OpenGL representation (a general 4x4 matrix) of transformations. After that matrix is added to OpenGL model view matrix, and object can render itself by using coordinates in its local frame. That's the purpose of the pure virtual function renderInLocalFrame().

We can now subclass Object for various shapes we want to handle. Here is what we could come up with for a cube, for example:

1
#ifndef CUBE_H
2
#define CUBE_H
3
 
4
#include "object.h"
5
 
6
class Cube : public Object
7
{
8
public:
9
  Cube(dWorldID world,dSpaceID space,dReal width,dReal height,dReal depth,dReal mass);
10
  unsigned char color[3];
11
protected:
12
  virtual void renderInLocalFrame() const;
13
};
14
 
15
 
16
#endif // CUBE_H
17
 
1
#include "cube.h"
2
#include <GL/glut.h>
3
 
4
using namespace std;
5
 
6
Cube::Cube(dWorldID world,dSpaceID space,dReal width,dReal height,dReal depth,dReal mass)
7
{
8
  body = dBodyCreate(world);
9
  geom = dCreateBox(space,width,height,depth);
10
 
11
  dMass m;
12
  dMassSetBox(&m,1.0f,width,height,depth);   
13
  dMassAdjust(&m,mass);  
14
  dBodySetMass(body,&m);
15
 
16
  dGeomSetBody(geom,body)
17
}
18
void Cube::renderInLocalFrame() const
19
{
20
  dVector3 lengths;
21
  dGeomBoxGetLengths(geom,lengths);
22
  glScalef(lengths[0],lengths[1],lengths[2]);
23
  glColor3ubv(color);
24
  glutSolidCube(1.0f);
25
}
26
 

Bounding box

As we said earlier, the QGLViewer needs to know the size of the world to correclty setup the camera for rendering. Here is a helper function that takes a vector of objects and compute their axis aligned bounding box.

tutorial1/C/viewer.cpp
9
namespace
10
{
11
  void getAABB(const QVector<Object *>& objects,dReal aabb[6])
12
  {
13
    dGeomGetAABB(objects[0]->geom,aabb);
14
    foreach (Object *o,objects)
15
    {
16
      dReal aabb[6];
17
      dGeomGetAABB(o->geom,aabb);
18
      for (int i=0;i<3;++i)
19
      {
20
        aabb[  i] = min(aabb[  i],aabb[  i]);
21
        aabb[3+i] = max(aabb[3+i],aabb[3+i]);
22
      }
23
    }
24
  }
............................
43
}
............................

Note that the foreach is a Qt helper construction, very handy. If you are adapting this code to be Qt independent, just use a for loop.

Plane rendering

Finally, we need a little helper function to render an infinite plane, such as the ground plane.

tutorial1/C/viewer.cpp
9
namespace
10
{
............................
25
  void renderPlane(dVector4 equation,float size)
26
  {
27
    Vec z = Vec(equation).unit();
28
    Vec o = equation[3]*z;
29
    Vec x = z.orthogonalVec().unit();
30
    Vec y = (z^x).unit();
31
    glBegin(GL_QUADS);
32
    glNormal3fv(z);
33
    glVertex3fv(o-size*x-size*y);
34
    glVertex3fv(o+size*x-size*y);
35
    glVertex3fv(o+size*x+size*y);
36
    glVertex3fv(o-size*x+size*y);
37
    glEnd();
38
  }
............................
43
}
............................

The full monthy

This section corresponds to code in tutorial1/C of the snippets directory. It contains the following files. Click a file to see the whole code in another window.

Viewer

We create our Viewer class that extends the QGLViewer. We make the world and space be private members. We also add members for storing objects, and additional static collision geoms. Here we will just use planes.

tutorial1/C/viewer.h
9
class Viewer : public QGLViewer
10
{
11
  Q_OBJECT;
12
public:
13
  Viewer(QWidget *parent=NULL);
14
  ~Viewer();
............................
17
protected:
18
  virtual void init();
19
  virtual void startAnimation();
20
  virtual void animate();
21
  virtual void draw();
22
private:
23
  dWorldID         _world;
24
  dSpaceID         _space;
............................
26
  QVector<Object*> _objects;
27
  QVector<dGeomID> _planes;
28
  dReal            _aabb[6];
............................
30
};
............................

In the constructor, we create the world, space, objects and static planes.

tutorial1/C/viewer.cpp
45
Viewer::Viewer(QWidget *parent)
46
  : QGLViewer(QGLFormat(QGL::SampleBuffers |
47
                        QGL::DoubleBuffer |
48
                        QGL::DepthBuffer |
49
                        QGL::Rgba |
50
                        QGL::AlphaChannel |
51
                        QGL::StencilBuffer),parent)
52
{
53
  ////////////////////////////////////////////////////////////
54
  // Create world, collision space and contact group
55
  ////////////////////////////////////////////////////////////
56
  _world = dWorldCreate();
57
  _space = dHashSpaceCreate(0);
58
  // Set up gravity force
59
  dWorldSetGravity (_world,0,0,-9.81);
60
  // Create contact group
61
  _contactgroup = dJointGroupCreate(0);  
62
  ////////////////////////////////////////////////////////////
63
  // Creating objects
64
  ////////////////////////////////////////////////////////////s
65
  Cube *cube = new Cube(_world,_space,0.3,0.3,0.3,1);
66
  cube->setPosition(0,0,1);
67
  cube->color[0] = qrand()%255;
68
  cube->color[1] = qrand()%255;
69
  cube->color[2] = qrand()%255;
70
  _objects.push_back(cube);
71
  // Create static planes
72
  _planes.push_back(dCreatePlane(_space,0,0,1,0));
73
  // Compute the bounding box
74
  getAABB(_objects,_aabb);
75
}
............................

In the destructor, we clean up everything. Note that world and space take care of deleting the bodies and geoms create inside them. The qDeleteAll() is a handy Qt algorithm: it calls delete on each element of the container.

tutorial1/C/viewer.cpp
76
Viewer::~Viewer()
77
{
78
  //////////////////////////////////////////////////////////////
79
  // Deleting objects
80
  //////////////////////////////////////////////////////////////
81
  qDeleteAll(_objects);
82
  //////////////////////////////////////////////////////////////
83
  // Terminate with ODE
84
  //////////////////////////////////////////////////////////////
85
  dSpaceDestroy(_space);
86
  dWorldDestroy(_world);
87
  dCloseODE()
88
}
............................

For the viewer initialization, we use the computed bounding box to set up scene center and radius. We stick the center to the ground plane because it feels more natural for the trackball. We also setup OpenGL lighting.

tutorial1/C/viewer.cpp
89
void Viewer::init()
90
{
91
  Vec min(_aabb);
92
  Vec max(_aabb+3);
93
  Vec center = (min+max)/2.0f;
94
  center.z = 0.0f;
95
  setSceneRadius((max-min).norm());
96
  setSceneCenter(center);
97
  // Try to restore previous state or focus the whole scene
98
  if (!restoreStateFromFile())
99
  {
100
    showEntireScene();
101
  }
102
  // Setup OpenGL
103
  GLfloat ambient[] = { 0.3f, 0.3f, 0.3f };
104
  GLfloat diffuse[] = { 0.1f, 0.1f, 0.1f , 1.0f};
105
  GLfloat specular[] = { 0.0f, 0.0f, 0.0f , 1.0f};
106
  glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
107
  glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse)
108
  glLightfv(GL_LIGHT0, GL_SPECULAR, specular)
109
}
............................

Finally, the drawing just traverses the objects and planes to render them.

tutorial1/C/viewer.cpp
110
void Viewer::draw()
111
{
112
  // Draw the objects
113
  foreach (Object *o,_objects)
114
  {
115
    o->render();
116
  }
117
  // Draw the planes
118
  qglColor(Qt::white);
119
  foreach (dGeomID g,_planes)
120
  {
121
    if (dGeomGetClass(g))
122
    {
123
      dVector4 equation;
124
      dGeomPlaneGetParams(g,equation);
125
      renderPlane(equation,sceneRadius());
126
    }
127
  }
128
}
............................

Simulation

For the simulation, we must be carreful with time management. Between two QGLViewer::animate() calls, a certain amount of time has elapsed. Naively, we would advance the world time of that amount. But because ODE uses a numerical integration of the underlying differential equation, we cannot make big steps, or the simulation will be innacurate, or even worse, unstable. So instead, we make "enough" small steps to cover the time delta. For measuring the elapsed time between two successive calls, we use the handy Qt class QTime, of which we store an occurence _time in the viewer.

tutorial1/C/viewer.h
9
class Viewer : public QGLViewer
10
{
............................
22
private:
23
  dWorldID         _world;
24
  dSpaceID         _space;
............................
26
  QVector<Object*> _objects;
27
  QVector<dGeomID> _planes;
28
  dReal            _aabb[6];
29
  QTime            _time;
30
};
............................

Since the use can toggle the animation (by pressing the Return key), we need to "start" this time in the QGLViewe::startAnimation() function. Then, at the end of QGLViewer::animate(), we restart it, so that next time we are in animate(), the value _time.elapsed() is what we want.

tutorial1/C/viewer.cpp
129
void Viewer::startAnimation()
130
{
131
  _time.start();
132
  QGLViewer::startAnimation();
133
}
134
void Viewer::animate()
135
{
136
  static float nbSecondsByStep = 0.001f;
137
 
138
  // Find the time elapsed between last time
139
  float nbSecsElapsed = _time.elapsed()/1000.0f;
140
  // Find the corresponding number of steps that must be taken
141
  int nbStepsToPerform = static_cast<int>(nbSecsElapsed/nbSecondsByStep);
142
  // Make these steps to advance world time
143
  for (int i=0;i<nbStepsToPerform;++i)
144
  {
............................
147
    // Step world
148
    dWorldQuickStep(_world, nbSecondsByStep);
149
    // Remove all temporary collision joints now that the world has been stepped
150
    dJointGroupEmpty(_contactgroup)
151
  }
152
  // Restart the elapsed time counter
153
  _time.restart();
154
}
............................

Collision handling

The last thing we need to do is to handle collision. The way ODE does it (in a nutshell, collision detection can be managed manually completely) is by calling dSpaceCollide. This function finds all collision between geoms of the space, and invoke a callback function on each pair of colliding geoms. Callbacks must be "global" functions. But the callback will need to access world and space, which are private members of the viewer. Luckily, the callback accept a void * data pointer, so we can pass a pointer to the viewer. We add the following public method to the viewer.

tutorial1/C/viewer.h
9
class Viewer : public QGLViewer
10
{
11
  Q_OBJECT;
12
public:
............................
16
  void handleCollisionBetween(dGeomID o0, dGeomID o1);
............................
30
};
............................

Then, we define the callback as a global function local to the viewer.cpp file.

tutorial1/C/viewer.cpp
9
namespace
10
{
............................
39
  void nearCallback(void *data, dGeomID o0, dGeomID o1)
40
  {
41
    reinterpret_cast<Viewer*>(data)->handleCollisionBetween(o0,o1);
42
  } 
43
}
............................

Now we can invoke the collision detection prior to doing small step in the world simulation.

tutorial1/C/viewer.cpp
134
void Viewer::animate()
135
{
136
  static float nbSecondsByStep = 0.001f;
137
 
138
  // Find the time elapsed between last time
139
  float nbSecsElapsed = _time.elapsed()/1000.0f;
140
  // Find the corresponding number of steps that must be taken
141
  int nbStepsToPerform = static_cast<int>(nbSecsElapsed/nbSecondsByStep);
142
  // Make these steps to advance world time
143
  for (int i=0;i<nbStepsToPerform;++i)
144
  {
145
    // Detect collision
146
    dSpaceCollide(_space,this,&nearCallback);
147
    // Step world
148
    dWorldQuickStep(_world, nbSecondsByStep);
............................
151
  }
152
  // Restart the elapsed time counter
153
  _time.restart();
154
}
............................

The next thing is to create a joint group to hold temporary joints. Remember that we said that ODE handles collision by creating temporary, local joints that "repulses" colliding objects. We add a private member to the viewer:

tutorial1/C/viewer.h
9
class Viewer : public QGLViewer
10
{
............................
22
private:
23
  dWorldID         _world;
24
  dSpaceID         _space;
............................
26
  QVector<Object*> _objects;
27
  QVector<dGeomID> _planes;
28
  dReal            _aabb[6];
29
  QTime            _time;
30
};
............................

We create this member in the constructor.

tutorial1/C/viewer.cpp
45
Viewer::Viewer(QWidget *parent)
46
  : QGLViewer(QGLFormat(QGL::SampleBuffers |
47
                        QGL::DoubleBuffer |
48
                        QGL::DepthBuffer |
49
                        QGL::Rgba |
50
                        QGL::AlphaChannel |
51
                        QGL::StencilBuffer),parent)
52
{
............................
60
  // Create contact group
61
  _contactgroup = dJointGroupCreate(0);  
............................

And in the simulation loop, we empty this group after every step.

tutorial1/C/viewer.cpp
134
void Viewer::animate()
135
{
136
  static float nbSecondsByStep = 0.001f;
137
 
138
  // Find the time elapsed between last time
139
  float nbSecsElapsed = _time.elapsed()/1000.0f;
140
  // Find the corresponding number of steps that must be taken
141
  int nbStepsToPerform = static_cast<int>(nbSecsElapsed/nbSecondsByStep);
142
  // Make these steps to advance world time
143
  for (int i=0;i<nbStepsToPerform;++i)
144
  {
145
    // Detect collision
146
    dSpaceCollide(_space,this,&nearCallback);
147
    // Step world
148
    dWorldQuickStep(_world, nbSecondsByStep);
149
    // Remove all temporary collision joints now that the world has been stepped
150
    dJointGroupEmpty(_contactgroup)
151
  }
152
  // Restart the elapsed time counter
153
  _time.restart();
154
}
............................

The last thing we have to do is to implement handleCollisionBetween(). We borrow the code and comments of another tutorial on the web.

tutorial1/C/viewer.cpp
............................

Now we set the joint properties of each contact. Going into the full details here would require a tutorial of its own. I'll just say that the members of the dContact structure control the joint behaviour, such as friction, velocity and bounciness. See section 7.3.7 of the ODE manual and have fun experimenting to learn more.

tutorial1/C/viewer.cpp
161
    for (int i = 0; i < MAX_CONTACTS; i++)
162
    {
163
      contact[i].surface.mode = dContactBounce | dContactSoftCFM;
164
      contact[i].surface.mu = dInfinity;
165
      contact[i].surface.mu2 = 0;
166
      contact[i].surface.bounce = 0.8;
167
      contact[i].surface.bounce_vel = 0.1;
168
      contact[i].surface.soft_cfm = 0.01;
169
    }
............................

Here we do the actual collision test by calling dCollide. It returns the number of actual contact points or zero if there were none. As well as the geom IDs, max number of contacts we also pass the address of a dContactGeom as the fourth parameter. dContactGeom is a substructure of a dContact object so we simply pass the address of the first dContactGeom from our array of dContact objects and then pass the offset to the next dContactGeom as the fifth paramater, which is the size of a dContact structure.

tutorial1/C/viewer.cpp
170
    if (int numc = dCollide(o0, o1, MAX_CONTACTS, &contact[0].geom, sizeof(dContact)))
171
    {
172
      // Get the dynamics body for each geom
173
      dBodyID b1 = dGeomGetBody(o0);
174
      dBodyID b2 = dGeomGetBody(o1);
175
      // To add each contact point found to our joint group we call dJointCreateContact which is just one of the many
176
      // different joint types available. 
177
      for (int i = 0; i < numc; i++)
178
      {
179
        // dJointCreateContact needs to know which world and joint group to work with as well as the dContact
180
        // object itself. It returns a new dJointID which we then use with dJointAttach to finally create the
181
        // temporary contact joint between the two geom bodies.
182
        dJointID c = dJointCreateContact(_world, _contactgroup, contact + i);
183
        dJointAttach(c, b1, b2);
184
      }
185
    } 
186
}
............................

Collision handling

That's it, we can run the program and we will see a single cube falling on the ground.

Start End

Going further

This section corresponds to code in tutorial1/D of the snippets directory. It contains the following files. Click a file to see the whole code in another window. Click here to download a tarball.

Now we have a skeleton to play with. For example, we can now add more cubes and a second plane to our scene.

tutorial1/D/viewer.cpp
45
Viewer::Viewer(QWidget *parent)
46
  : QGLViewer(QGLFormat(QGL::SampleBuffers |
47
                        QGL::DoubleBuffer |
48
                        QGL::DepthBuffer |
49
                        QGL::Rgba |
50
                        QGL::AlphaChannel |
51
                        QGL::StencilBuffer),parent)
52
{
............................
62
  ////////////////////////////////////////////////////////////
63
  // Creating objects
64
  ////////////////////////////////////////////////////////////s
65
  static const int nb = 5;
66
  for (int k=0;k<3;++k)
67
  {
68
    float z = 2.0+0.15*k;
69
    for (int i=-nb;i<=nb;++i)
70
    {
71
      float x = 0.15*i+(k%2)*0.01;
72
      for (int j=-nb;j<=nb;++j)
73
      {
74
        float y = 0.15*j+(k%2)*0.01;
75
        Cube *cube = new Cube(_world,_space,0.1,0.1,0.1,1);
76
        cube->setPosition(x,y,z);
77
        cube->color[0] = qrand()%255;
78
        cube->color[1] = qrand()%255;
79
        cube->color[2] = qrand()%255;
80
        _objects.push_back(cube);
81
      }
82
    }
83
  }
84
  // Create static planes
85
  _planes.push_back(dCreatePlane(_space,0,0,1,0));
86
  _planes.push_back(dCreatePlane(_space,1,0,1,0.5));
87
  // Compute the bounding box
88
  getAABB(_objects,_aabb);
89
}
............................

Running the simulation is a bit slower, but here is the result.

Start Middle End

From now, it is probably time to read the ODE manual in more details. I suggest in particular the following sections.