DelphiODE

Home | Screen Shots | Download | Demos | Tutorial | Buoyancy

Page is maintained my mattias fagerlund.


Using DelphiODE

This document describes how to use DelphiODE. ODE stands for Open Dynamics Engine, and is a free, open source, physics simulation package. To learn more about ODE, please visit http://opende.sourceforge.net/ . DelphiODE is a package that imports ODE into Delphi. All demos included with DelphiODE use Eric Grange's wonderful GLScene, so a some work has been performed making ODE easy to use with GLScene. The core of DelphiODE is included with GLScene, but DelphiODE also has a website located at  , where you can find several demos that are not included in GLScene.

This document will not work as a tutorial, it is intended as a help to understanding the DelphiODE demos. You will have to look through the code to the demos to really understand what I'm talking about here.

Dynamics

First of all, ODE requires that a physics world is created. All dynamics objects exist in a world. You can have several worlds, but generally, you will not need to. Dynamics objects are Bodies and Joints. A body is an object  that can twist and turn and a joint is something that connects two bodies in one way or another, restricting the twisting and turning of said bodies. There are several types of joints, but only one type of body.

Collision response

When two Geoms collide (see below), a contact joint is temporarily created, and the joint is applied to the two bodies. The joint will tend to separate the two bodies by as much as they interpenetrate. This is called collision response.

Collision detection

In ODE there's a very strict separation between dynamics and collision detection. Collision objects are called Geoms, and they know how to collide with other Geoms, but they know nothing about dynamics. ODE can be used with other collision packages, but the one included is fine for most uses. There are several kinds of geoms, likes boxes, spheres and capped cylinders. If you need a more complex object, you can typically approximate it with several primitive geoms.

Geoms exist in spaces, a space is to a geom what a world is to a body. Remember;

Advancing the world, colliding the space

You, the application developer, will be responsible for advancing the ODE world. This is done with a call to dWorldStep. The world can be advanced as much or as little as you want. 0.01 seconds seem to work well for me. I place world stepping in the progress of my scene cadencer. That way, pausing the cadencer also pauses the physics simulation. The collision must also be updated, and since worlds know nothing about collisions, the space is called to do that. Collisions are checked before the world is stepped, since the world will have to respond to the collisions found.

Physics and rendering on the same time frame

procedure TfrmSpinningTop.GLCadencer1Progress(Sender: TObject; const deltaTime, newTime: Double);
begin
  // Check for collisions (bodies are notified about collisions 
  dSpaceCollide (space,nil,nearCallback);

  // Step the world
  dWorldStep (world, deltaTime);

  // Empty the contact group "contactgroup", because it's used to store temporary
  // joints like collision joints
  dJointGroupEmpty (contactgroup);
  // DRAW STUFF!
end;

The above method will simulate very short physics time steps when frame rates are good, and very long time steps when frame rates are bad. If your frame rate drops to 2 FPS, each world step will be 0.5 seconds. This is not a good idea, because ODE doesn't like long time steps, and it doesn't like varying time steps. This can get really bad when some form of user interaction freezes the GLScene cadencer for several seconds. When the cadencer starts moving again, the time step can be several seconds. Using a MaxDeltaTime on the Cadencer does help, but it still means that the physics step will be varying.

Physics on constant time step

var
  PhysicsTime : single = 0;
procedure TfrmSpinningTop.GLCadencer1Progress(Sender: TObject; const deltaTime, newTime: Double);
const
  PHYSICS_STEP = 0.01;
begin
  while PhysicsTime<newTime do
  begin
    // Check for collisions (bodies are notified about collisions 
    dSpaceCollide (space,nil,nearCallback);

    // Step the world
    dWorldStep (world, PHYSICS_STEP);

    // Empty the contact group "contactgroup", because it's used to store temporary
    // joints like collision joints
    dJointGroupEmpty (contactgroup);

    // Update the physics time counter
    PhysicsTime := PhysicsTime + PHYSICS_STEP;
  end;

  // DRAW STUFF!
end;

The method above will always step the physics world in increments of 0.01 seconds. If the frame rate is higher than 100 FPS, then ODE won't be updated for some of the frames. But this is OK, because it will not be noticeable. If there's a really big freeze of the cadencer, say for 2 seconds, then the physics will be updated 2 / 0.01 = 200 times before it is rendered.

The major disadvantage with this method is that if the time it takes to do the physics calculations are longer than 0.01 seconds, frame rates will drop to nothing. Say that calculating the physics takes 0.02 seconds, the first time you run the loop, you'd be 2 iterations behind. So you run the physics twice, which takes 0.04 seconds. Then you're 4 iterations behind. From there, things don't exactly improve. So, make sure that physics never take longer than the physics step you use.

With this method, you can also vary how much time physics should be allowed to consume, because if you reduce the physics update rate (i.e. increase the physics time step), you will perform fewer physics updates per second. Set the value to 1/30 = 0,0333 seconds, and it will only be performed 30 times per second. This will use 30% of the CPU cycles of the 0.01 time step, but it will not be as accurate.

nearCallback

nearCallback is the means by which the application developer instructs ODE how to handle collisions. nearCallback is a call back function, as the name implies. ODE will use this call-back when it determines that a collision may occur. I'd like to stress that there may not be an actual collision, but this is a means for the users of ODE to cull more expensive collision testing (see the call to dCollide). ODE keeps geoms in a structure called a hash space, which it uses to determine if the objects are close enough to possibly collide.

Here's a documented version of a typical nearCallback method;

procedure nearCallback (data : pointer; o1, o2 : PdxGeom); cdecl;
const
  // Number of collision points to allow, 1 is unstable, 3 is ok, 4 is good for boxstacking.
  // Howver, this comes at a price, more collisions mean more constraints, and execution time
  // memory usage grows O(^3). (3 is 9 times as bad as 1)
  cCOL_MAX = 4;
var
  i : integer;
  b1, b2 : PdxBody;
  numc : integer;
  contact : array[0..cCOL_MAX-1] of TdContact;
  c : TdJointID;
begin
  // Retrieve the bodies for the geoms. The geoms don't have to have bodies, they can be
  // planes for instance, plances don't have bodies. Any geom can be created without a body,
  // but it will be solid as a rock. (won't budge no matter what)
  b1 := dGeomGetBody(o1);
  b2 := dGeomGetBody(o2);

  // This test says that if both geoms have bodies, and they are connected with a joint,
  // then they should not collide.
  if (assigned(b1) and assigned(b2) and (dAreConnected (b1,b2)<>0)) then
    exit;

  // Here you can make other "early out" decision, if you decide that collision 
  // should be ignored for these two geoms

  // Prepare the collision structure
  for i :=0 to cCOL_MAX-1 do
  begin
    contact[i].surface.mode := dContactBounce;

    // This determines friction, play around with it!
    contact[i].surface.mu := 1000; 
    contact[i].surface.mu2 := 0;
    contact[i].surface.bounce := 0.5;
    contact[i].surface.bounce_vel := 0.1;
  end;

  // Call the more expensive collision method. numc will be set to the number of
  // actual collision returned. A box standing on a plane sureface needs three collisions
  // to be stable. A sphere only needs one.
  numc := dCollide (o1,o2,cCOL_MAX,contact[0].geom,sizeof(TdContact));

  // numc will be zero if the objects didn't actually collide. 
  if (numc>0) then
  begin
    // If you'd like to add a sound to note the collision to the user, this is a good spot
    
    for i := 0 to numc-1 do
    begin
      // Add a contact joint that will prevent the two geoms from inter-penetrating
      c := dJointCreateContact (Form1.world, Form1.contactgroup,contact[i]);

      // Attach the joint to the two bodies. Remember than only one of them _must_ be a
      // body. One may be nil. But dJointAttach is prepared for that.
      dJointAttach (c,b1,b2);
    end;
  end;
end;

Click here to go back to the top. Or you can visit mattias' homepage.

Number of visitors: