Page is maintained my mattias fagerlund.
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.
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.
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.
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;
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.
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.
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 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: