Posts
Search
Contact
Cookies
About
RSS

3d Physics with raylib and ODE

Added 13 Jun 2019, 9:45 p.m. edited 18 Jun 2023, 7:20 p.m.

Open Dynamic Engine (ODE) has been around for quite some time, it has a nice C API and is both fast and stable, that said as with any dynamics library, play fast and loose with the settings and it will bite you and invite you to keep both pieces...

https://www.youtube.com/watch?v=sjsPUW12WYE

You might notice a certain amount of lighting abuse going on here, I basically started off from my previous tutorial code... The first thing you need to do is grab ODE I'd recommend you use the following configuration.

./configure --enable-libccd --with-box-cylinder=libccd --with-drawstuff=none --disable-demos

The static library gets stashed in a hidden directory so go ahead and copy it into the lib directory once you have extracted the example code

cp ../ode-0.16/ode/src/.libs/libode.a lib/

Now you have the source lets have a look at some of the important features, that should help you understand what is going on. The basic idea of a dynamics engine is that you set up object (bodies) with geometries, from there on you should only interact with bodies via forces, attempting to manually position objects, defeats the whole idea... Once the dynamics engine has arbitrated collisions and forces, you can then take the new position and orientation of each body and update your visual scene appropriately. The first function you come across is:

void setTransform(const float pos[3], const float R[12], Matrix* matrix)

This allows you to take a bodies position and rotational matrix, and update a raylib model matrix, so when you draw the model it will appear correctly positioned and oriented. You'll see it in action shortly...

ODE has a very useful callback which is used when two bodies might potentially collide, while this simple example just does the minimum, its quite possible to rule out certain collisions, or even have custom collision properties for example you might want a rubber ball and an iron block to behave differently when they hit the floor. For now its better to leave this for later as what's in place will be sufficient to help you learn the basics and latter you can look in more detail as to how this works.

Setting up ODE is fairly straight forward

    dInitODE2(0);   // initialise and create the physics
    world = dWorldCreate();
    space = dHashSpaceCreate(NULL);
    contactgroup = dJointGroupCreate(0);
    dWorldSetGravity(world, 0, -9.8, 0);    // gravity
    dCreatePlane (space,0,1,0,0);

In ODE a "world" can contain multiple spaces while we're using just a single space here, you can use multiple spaces to partition your world. For example you could simulate a world with a number of buildings, the interior of buildings would be a great candidate for separate spaces. Another use case is if for example if you have a complex vehicle you can put the vehicle in its own space, the engine can then optimize things by only colliding the vehicles space with external bodies.

Contact groups are used to hold a special type of constraint (or joint) that helps mediates collisions. This is used when we "step" the physics world, like any other force gravity is nothing special, however rather than having to apply it manually each frame to each object, we can set the force we want and let the engine apply it for us.

There is one static geometry in our world, the plane is added to the world without a body making it completely immobile.


obj[i] = dBodyCreate(world);
geom = dCreateBox(space, 1,1,1);
dMassSetBoxTotal (&m, 1, 0.5, 0.5, 0.5);
dBodySetPosition(obj[i],x,y,z);
dBodySetMass(obj[i], &m);
dGeomSetBody(geom, obj[i]);

Looking at the example code above, you can see there are functions to set up not just the geometry but also the mass for different types of geometries. Once your body has geometry and mass its basically ready to go.


// check for collisions
dSpaceCollide(space, 0, &nearCallback);
// step the world
dWorldQuickStep(world, 1. / 60.0);
dJointGroupEmpty(contactgroup);

Each frame we need to collide our space (this is where the collision callback comes into play), as we're running our simulation at 60 FPS the world is stepped with a delta time of 1.0/60.0th of a second. The contact group is emptied each frame in readiness for the next time the world is collided.


float* pos = (float *) dBodyGetPosition(obj[i]);
float* rot = (float *) dBodyGetRotation(obj[i]);
setTransform(pos, rot, &box.transform);
DrawModel(box, (Vector3){0,0,0}, 1.0f, WHITE);

Finally setTransorm in action! (box is a raylib model of a cube)

While normally you don't position a body (other than during setup) in this example I'm re-positioning bodies if they get too far away

if(fabs(pos[0]) > 20 || fabs(pos[2]) > 20) {
    // teleport back if too far away
    dBodySetPosition(obj[i], dRandReal() * 10 - 5,
                        8, dRandReal() * 10 - 5);
    dBodySetLinearVel(obj[i], 0, 0, 0);
    dBodySetAngularVel(obj[i], 0, 0, 0);
    pos = (float *) dBodyGetPosition(obj[i]);
}

As well as re-positioning a body the velocity and angular velocity (spin) are set to zero, to stop things getting out of hand.

Obviously with such a complex topic as a dynamics engine, a simple tutorial isn't able to cover everything. I'd recommend reading the manual there's plenty to learn and hopefully the code from this tutorial should be a fair base to try experimenting with ODE

Enjoy!