These tutorials focus mainly on OpenGL, Win32 programming and the ODE physics engine. OpenGL has moved on to great heights and I don't cover the newest features but cover all of the basic concepts you will need with working example programs.
Working with the Win32 API is a great way to get to the heart of Windows and is just as relevant today as ever before. Whereas ODE has been marginalized as hardware accelerated physics becomes more common.
Games and graphics utilities can be made quickly and easily using game engines like Unity so this and Linux development in general will be the focus of my next tutorials.
Common ODE Joints
By Alan Baylis 04/03/2006
In the first tutorial here we looked at how to create a single object. Now we can move on to creating multiple objects and joining them together with joints. The types of joints we are covering today are the three most oft used types, namely the Ball and Socket joint, the Hinge joint and a Slider joint.
Download for Visual Studio 7.0
* Contains a separate example program for each type of joint
Starting out with the basic code from the first tutorial we now expand on the base code. To show the joints in action we will need to increase the number of objects by one, and then create a new joint group and a joint ID. So our global variables for ODE, which are the same for all three types of joints, now look like this:
MATRIX GeomMatrix; dWorldID World; dSpaceID Space; MyObject Object[2]; // two geom objects dJointGroupID contactgroup; dJointGroupID jointgroup; // contact group for the new joint dJointID Joint; // the joint ID
We now move on to the initialization routine for ODE which I called InitODE. I will list the whole function again, but I'll just comment on the new code required to make the joints.
void InitODE() { World = dWorldCreate(); Space = dHashSpaceCreate(0); contactgroup = dJointGroupCreate(0); // As well as the contact group for collisions we need to create a new joint group and // assign its ID to jointgroup jointgroup = dJointGroupCreate(0); dCreatePlane(Space, 0, 1, 0, 0); dWorldSetGravity(World, 0, -1.0, 0); dWorldSetCFM(World, 1e-5); dWorldSetERP(World, 0.2); dWorldSetContactMaxCorrectingVel(World, 0.9); dWorldSetContactSurfaceLayer(World, 0); dWorldSetAutoDisableFlag(World, 1); dReal sides[3]; dMass m; dMatrix3 R; VECTOR tempVect(0.0, 0.0, 0.0); sides[0] = 2.0; sides[1] = 2.0; sides[2] = 2.0; // Here we instantiate the two bodies we will be using Object[0].Body = dBodyCreate(World); Object[1].Body = dBodyCreate(World); // Set up for body 1. Nothing much has changed here, we are now dealing with an array // instead of a single object and I have changed its initial position and rotated the // object 45 degrees around the Y axis using dRFromEulerAngles. dBodySetPosition(Object[0].Body, -1.4142, 1.0, -5.0); dBodySetLinearVel(Object[0].Body, tempVect.x, tempVect.y, tempVect.z); dBodySetData(Object[0].Body, (void*)i); dRFromEulerAngles(R, 0.0, pi / 4, 0.0); dBodySetRotation(Object[0].Body, R); dMassSetBox(&m, DENSITY, sides[0], sides[1], sides[2]); Object[0].Geom[0] = dCreateBox(Space, sides[0], sides[1], sides[2]); dGeomSetBody(Object[0].Geom[0], Object[0].Body); dBodySetMass(Object[0].Body, &m); // Set up for body 2. As above, except for its initial position. dBodySetPosition(Object[1].Body, 1.4142, 3.0, -5.0); dBodySetLinearVel(Object[1].Body, tempVect.x, tempVect.y, tempVect.z); dBodySetData(Object[1].Body, (void*)i); dRFromEulerAngles(R, 0.0, pi / 4, 0.0); dBodySetRotation(Object[1].Body, R); dMassSetBox(&m, DENSITY, sides[0], sides[1], sides[2]); Object[1].Geom[0] = dCreateBox(Space, sides[0], sides[1], sides[2]); dGeomSetBody(Object[1].Geom[0], Object[1].Body); dBodySetMass(Object[1].Body, &m); // To join the two objects together we first need to create a new joint with a call to // dJointCreateBall and retain the ID returned in our Joint variable. Joint = dJointCreateBall(World, jointgroup); // We now instruct ODE that this new joint will be attached to our two object bodies. dJointAttach(Joint, Object[0].Body, Object[1].Body); // And lastly we tell ODE where exactly the two objects are joined in world coordinates. dJointSetBallAnchor(Joint, 0.0, 2.0, -5.0); }
Now there is only one more change needed to get this ball and socket example working, and that is to change the call to DrawGeom in our original SimLoop routine to the following.
DrawGeom(Object[0].Geom[0], 0, 0, 0); DrawGeom(Object[1].Geom[0], 0, 0, 0);
Pretty simple huh? The most significant change to the InitODE routine was the addition of the last three lines of code which create and initialize the ball and socket joint. To create a hinge joint you would use the following lines of code instead.
Joint = dJointCreateHinge(World, jointgroup); dJointAttach(Joint, Object[0].Body, Object[1].Body); dJointSetHingeAnchor(Joint, 0.0, 2.0, -5.0); dJointSetHingeAxis(Joint, 0, 0, 1);
And to create a slider joint you would use these lines of code:
Joint = dJointCreateSlider(World, jointgroup); dJointAttach(Joint, Object[0].Body, Object[1].Body); dJointSetSliderAxis(Joint, 0, 1, 0); dBodyAddForce(Object[1].Body, 0, 200, 0); // Added a force to show the slider joint in action
Also, don't forget to clean up the extra joint group by adding the following line of code to the CloseODE routine. I'm notorious for not cleaning up properly but I know you will do the right thing.
dJointGroupDestroy(jointgroup);
In the next tutorial we will look at constraining the range of movement of a joint (using stops) and also mess around with applying forces to the objects.
Alan Baylis