Moving Platforms

From ODE
Jump to: navigation, search

Games often need movable geometry, like lifting platforms, that aren't subject to the physics caused by collisions with other objects, like a game character. Although this can be approximated with a very heavy body moved by a very strong force, this will soon cause simulation instabilities. The best solution is to make the platform static (no body attached at all), and use the contact surface parameters to simulate the motion.

Suppose the lifting platform is created as:

dGeomID platform;

platform = dCreateBox(...);

And at a given moment the platform has "velocity" given by a velocity variable declared as:

dVector3 platvel;

At each step we call dGeomSetPosition() to place the platform in a new position depending on the platvel variable. ODE doesn't know the platform has any movement, we are actually "teleporting" the platform to a new position. So we add an "extra" velocity to its contacts so they behave as if the platform was moving.

The contact creation against the platform would look like this:

for each contact i:
   contact[i].surface.mode = ... | 
                 dContactMotion1 | dContactMotion2 | dContactMotionN | 
                 dContactFDir1;
   contact[i].surface.mu = ...;

Note that we are using all the dContactMotion flags. The contact joint has its own coordinate system, formed by the normal, fdir1 and fdir2 vectors (see the manual for more details.) We want to represent the platform's velocity vector (expressed in world coordinates) in the joint's coordinate system; thus we will need the motion1, motion2 and motionN surface members.

Unless we are trying to achieve some other special effect, we can just let ODE create fdir1 and fdir2 for us, through dPlaneSpace():

    const dReal * normal = contact[i].geom.normal;
    dVector3 fdir1, fdir2;
    dPlaneSpace(normal, fdir1, fdir2);

And now we give ODE our fdir1:

    contact[i].fdir1[0] = fdir1[0];
    contact[i].fdir1[1] = fdir1[1];
    contact[i].fdir1[2] = fdir1[2];

(ODE will derive fdir2 from normal and fdir1)

Now that we know the contact joint basis (formed by the normal, fdir1, fdir2 axes), we perform a change of basis. Because the world uses the canonical basis, and the joint uses an orthonormal basis, all we need to do is project the platform velocity onto each joint axis; in other words, a dot product (using dDOT):

    contact[i].surface.motion1 = dDOT(platvel, fdir1);
    contact[i].surface.motion2 = dDOT(platvel, fdir2);
    contact[i].surface.motionN = dDOT(platvel, normal);

But there's a problem with this code: it only works if the normal is pointing out from the platform; that is, the platform must be the second geom passed to dCollide() (the contact[i].geom.g2 member will be the platform), as specified in the manual. So we have 3 options:

  • Make sure the platform is always the second argument to dCollide(), swapping the geoms when necessary before calling it. This is not always possible, dCollide() might have been called somewhere else where we don't control.
  • Massage the contact information generated by dCollide(); when necessary, invert the normal and swap g1 and g2, to look as if dCollide() was called with the geoms swapped in the first place. Again, it's not always possible: some other code might already "know" what's in the contact information, and assumes it'll remain that way.
  • Just perform the calculations as if normal was inverted. This inverts the signal of dDOT(platvel, normal); and fdir2 would be inverted too, so dDOT(platvel, fdir2) will also be negated. The code becomes:
    dReal inv = 1;
    if (contact[i].geom.g1 == platform)
        inv = -1;

    contact[i].surface.motion1 =       dDOT(platvel, fdir1);
    contact[i].surface.motion2 = inv * dDOT(platvel, fdir2);
    contact[i].surface.motionN = inv * dDOT(platvel, normal);

This is demonstrated in demo_motion.cpp.

Complex Movement

If the platform rotates each contact point has its own velocity; instead of just platvel we have plat_linvel and plat_angvel, for linear and angular velocities of the platform.

In pseudo-code (similar to dBodyGetPointVel()):

    pos = contact[i].geom.pos;
    relpos = pos - center;
    pointvel = plat_linvel + cross(plat_angvel, relpos);

    // now use pointvel instead of platvel in the motion calculations