Posts
Search
Contact
Cookies
About
RSS

C and Physics – hinge2 Vehicle

Added 15 Mar 2019, 12:41 a.m. edited 18 Jun 2023, 7:24 p.m.

After our previous demo featuring falling bodies, we're now tackling something much more interesting and potentially useful, vehicles. The hinge2 joint if you look at the manuals diagram, looks like it was made solely with vehicles in mind, it's just begging to be used! Let's dive right into the additions to the collision callback

        // if we have a body look up its "phys" struct if that is
        // marked as none colliding then return
        phys_t* phys1 = NULL;
        if (b1) phys1 = (phys_t*)dBodyGetData(b1);
        phys_t* phys2 = NULL;
        if (b2) phys2 = (phys_t*)dBodyGetData(b2);
        if (phys1 && phys1->collide==0) return;
        if (phys2 && phys2->collide==0) return;

By looking up the phys structure that's attached to the body we can check to see if the body has been setup as a none colliding body, the first term of the if statement is a NULL guard and ensures its safe to check the collide field. We'll see later why having a none colliding body might be useful.

While ODE itself does offer category and collision bitfields I decided to do it myself as I have future plans for the phys structure inside the near callback...

This time our ground is a static trimesh, basically a soup of triangles, I'll leave it as an exercise for the reader to look up the functions and work out what's going on here. There was a time when trimeshes were not quite as robust as they are today, thankfully these days they seem rock solid, there's obviously been a lot of hard word done here by the ODE devs.

The main render loop also has a few little changes

            if (phys[i].sz[0]==0) continue; // signals invisible phys
            
            if (phys[i].body == NULL) {
                if (phys[i].geom == NULL) continue;
                // geom only phys are static
                pos = (float *) dGeomGetPosition(phys[i].geom);
                rot = (float *) dGeomGetRotation(phys[i].geom);
            } else {

The x axis size is used to signal invisible objects, that object is just skipped by the renderer (more on invisible bodies later!). Next presence of a body is checked for, if there is no body the geom field is checked, if neither is set then the phys was never setup, so its skipped (just for safety). If there is a geom then that is used to get the position and rotation info and use that instead, this means that static primitives can be rendered without special cases - handy for things like walls and a ramp :D !

Our last change of note is for our camera, and to be honest it's just a quick hack and its fairly horrid!

        // find the camera position by taking the vehicle orientation
        // and translating back and up...
        {
            float m[16], t[16];
            const dReal *p = dBodyGetPosition (car.chassis.body);
            const dReal *r = dBodyGetRotation (car.chassis.body);
            setTransform(p, r, &m[0]);
            mat4Translation(t, -20, 8, 0);
            mat4Mul(m, m, t);
            camera.pEye[0] = m[12];
            camera.pEye[1] = m[13];
            camera.pEye[2] = m[14];

By taking the orientation and position of the vehicle as a starting point, and using the right order of matrix multiplication we can translate in the bodies local reference frame rather than the world axis. The end result is, our calculated point will always directly and rigidly behind and above the vehicle, hence the reason it bounces around so horribly... We can lift the calculated position directly from the matrix (sadly the same can't be said for rotations!)

There are obviously more changes to our main source file, but in the main (sorry!) they are to do with setting up static geoms and should be fairly straight forward to pick apart.

A new source file (vehicle.c) has a fair chunk of code so I'm going to take it as read that by now you should be able to understand the basics of how a body and its geom are created, and concentrate on the new stuff. Our vehicle consists of a chassis, four wheels and a counter weight, now this counter weight has nothing to do with reality, but its a neat trick that makes it quite difficult to end up with the car on its roof (and as you know its a Hollywood law that a car that comes to rest on its roof will explode!). It's such a useful trick its been used in some AAA games, and this is where the invisible none colliding bodies come it. The counter weight is far enough below the vehicle that the distance acts as a great lever and automagically puts more force on the chassis the further the chassis is tilted. This also helps to some extent when a vehicle leaves a ramp...

    v->counterWeight.sz[0] = 0;
    v->counterWeight.collide = 0;
        ...
    dBodySetPosition(v->counterWeight.body, 0, CARheight-5, 0);
    
    // use a fixed joint to join the counter weight and chassis
    v->cW = dJointCreateFixed(wrld, 0);
    dJointAttach(v->cW, v->chassis.body, v->counterWeight.body);
    dJointSetFixed(v->cW);

While fixed joints are generally frowned on, in this case they are actually really useful. This is just one solution and as ever with this sort of thing there are plenty of other options. Looking closely at the rest of the code, the only difference between the wheel joints are that the rear two wheels have their steering locked. I could waste a lot of time explaining in detail all the joint parameters but the manual does this quite well, at the very least you should check out the parameter functions while looking through the code. When updating the vehicles steering and drive forces its simply a case updating the appropriate joint parameters.

Getting a vehicle to behave how you want it is a bit of a fine art, mu and mu2 in the collision callback help with drive friction and skidding and the various ERP and CFM parameters are crucial to the behaviour of the joint, put somewhat crudely a high ERP (aproching 1) and a low CFM will make things stiffer, however you should be very careful about using extreme values, and you're much better of just changing a single thing at a time.

While fixed joints are generally frowned on, in this case they are actually really useful. This is just one solution and as ever with this sort of thing there are plenty of other options. Looking closely at the rest of the code, the only difference between the wheel joints are that the rear two wheels have their steering locked. I could waste a lot of time explaining in detail all the joint parameters but the manual does this quite well, at the very least you should check out the parameter functions. When updating the vehicles steering and drive forces its simply a case updating the appropriate joint parameters. Getting a vehicle to behave how you want it is a bit of a fine art, mu and mu2 in the collision callback help with drive friction and skidding and the various ERP and CFM parameters are crucial to the behaviour of the joint, put somewhat crudely a high ERP (aproching 1) and a low CFM will make things stiffer, however you should be very careful about using extreme values.

One final thing of note for our vehicle is that it is contained within its own space this helps speed the collider up, especially if there are multiple cars running around.

... Right I have to stop tweaking parameters otherwise you'll never be able to download the code.