[ODE] Simpler Suspension Geometries

Erwin Coumans erwin.coumans at havok.com
Tue Dec 11 11:19:02 2001


Havok (and Ipion which have merged) do have two optimized solutions for
simulating vehicles
and these are built on top of the physics engine, and indeed as Russell
states:

the first solution uses a special constraint (Wheel Constraint),
the second one is a raycast solution (with something like 160 tweaking
parameters).
The hard part is the Coulomb friction (approximation) calculations.
For 'fast' racing games the raycast solution is recommended, and in this
case
the inaccurate collision response is not really an issue.

by the way, how does ODE handle 'fast' cars,
the case where a wheel is rotating more then 360 degrees per simulation
step.
I wouldn't be surprised if an LCP approach had difficulties with this.

Erwin Coumans

----- Original Message -----
> > What are other people using for simplified geometries? ipion and
> > havok have special classes just for building cars. That fastcars guy
> > too. What are they doing? Special fancy joints help out, but the
> > parts still add up quickly.
>
> hi,
>
> one way to simplify is, like you say, use special joints - then you
> only need one body and four wheels. in the simple case the joint can
> be like ODE's hinge2 (see the docs) - basically a shopping trolley
> joint with suspension. adding more complicated kinematics should
> not be too hard and may actually improve the vehicle behavior.
>
> a further simplification is to recognize that the wheel rigid bodies
> dont contribute much to the variable component of the chassis-
> relatve inertia, so you can basically just treat them as constants and
> use a single rigid body for the chassis. once you do this you can
> optimize the entire engine to support this single-rigid-body case. the
> disadvantage of this approach is that it's hard to get realistic
> collision response in the wheels if they don't have associated rigid
> bodies. but you can kinematically fake it. this MAY be what havok and
> fastcar do (i'm not really sure).
>
> the lesson here is that for specific problem domains, you can always
> optimize the physics model and solver for that case. i would expect
> an optimized car engine to go 10-50 times faster that ODE - but it
> will only be able to simulate cars. another example: i have implemented
> an ODE-like solver for vascular catheters, and it's WAY faster when you
> only have one fixed system structure to think about.
>
> russ.
>
> --
> Russell Smith
> http://www.q12.org
>
> --__--__--
>
> Message: 3
> Date: Mon, 10 Dec 2001 17:49:13 -0800
> From: Russ Smith <russ@q12.org>
> Reply-To: russ@q12.org
> To: Marco Correia <marco.c@iname.com>
> CC: ode@q12.org
> Subject: Re: [ODE] friction
>
>
> >   So how does ODE determines the 'default' friction direction? I
> > mean, if I have an object standing static on a table and a robot
> > grabs it and lift it up, will setting mode=0 and mu=something give me
> > the right behaviour (i.e. the friction vectors will be set correctly,
> > pointing down?)
>
> ODE uses the dPlaneSpace() function to get two vectors that are
> orthogonal to the contact normal vector. the orientation of these
> vectors for a given normal is predictable, but arbitrary.
>
> russ.
>
> --
> Russell Smith
> http://www.q12.org
>
> --__--__--
>
> Message: 4
> Date: Mon, 10 Dec 2001 18:03:21 -0800
> From: Russ Smith <russ@q12.org>
> Reply-To: russ@q12.org
> To: Andrew Stunell <astunell@r3interactive.com>
> CC: ode@q12.org
> Subject: Re: [ODE] Slip values and Pacejka Magic Formula
>
>
> Andrew Stunell wrote:
> >
> > see http://www.esbconsult.com.au/ogden/locost/phors/phors.htm
> >
> > about magic formula part 21 and 22 I think. This uses a variety of a
> > formula developed in a paper by Pacejka.
>
> okay, to try and answer your question:
>
> consider a rotating tyre in contact with the ground (i.e. the vehicle
> is moving with a velocity V). if a transverse force F is applied to the
> wheel (i.e. perpendicular to the direction of motion), the tyre will
> reach a steady state where it 'slips' in the transverse direction with
> a velocity proportional to F and V. the scaling factors depend on the
> environment. this behavior can be simulated in ODE with the 'slip'
> contact parameter. 'slip' basically says that a force at the contact
> should produce a resulting velocity (NOT an acceleration, as would
> normally be the case). note also that slip is independent of
> static/dynamic friction - there is no threshold.
>
> does this help?
>
> russ.
>
> --
> Russell Smith
> http://www.q12.org
>
> --__--__--
>
> Message: 5
> From: "Marco Correia" <marco.c@iname.com>
> To: <ode@q12.org>
> Date: Tue, 11 Dec 2001 03:36:41 -0000
> Subject: [ODE] alloca.h
>
> Hi,
>
>  When I try to 'make' ode code and samples under win2k, using the make.exe
> downloaded from the web page, I get a missing alloca.h file.
>  I checked and there is no alloca.h file indeed (I have VC++ (6)).
>  So I commented the #include "alloca.h" statement on the complaining
files:
>
>  drawstuff.cpp, matrix.cpp, ode.cpp, step.cpp, lcp.cpp, space.cpp,
timer.cpp
>
>  and everything built fine.
>
>  If I understand correctly, you are using "malloc.h" for the alloca
> function, so, what's the need to include "alloca.h", on a win32 build ?
>
> Regards
>  Marco
>
>
> --__--__--
>
> Message: 6
> From: Ruud van Gaal <ruud@marketgraph.nl>
> To: ode@q12.org
> Date: Tue, 11 Dec 2001 12:06:34 +0100
> Subject: [ODE] Re: Slip values and Pacejka Magic Formula
>
> > To anyone who is using ODE for vehicle simulation, has anyone
> > formulated =
> > a corolation between slip values in ODE and Pacejka' s
> > formula for tire =
> > forces?
>
> I have no idea what the ODE slip means. What I do think is that you won't
> use it when doing Pacejka. Tire slipping isn't something you can just put
> into a Coulomb static/dynamic friction model; it's too complex (there is
> hardly any dynamics slipping, but an integral over mostly static and some
> dynamic friction).
>
> Well, that's my view anyway. I use ODE for the body collisions (OBB vs.
> planes from triangles), but the wheels are an entity completely outside
ODE
> (until they break off in a future version).
>
> Pacejka btw is a big formula with some vibrational parts in it
(A*exp(...))
> and sines and cosines to estimate force curves that are found empirically.
> It has some 30 or so coefficients. I think it is too high level for the
> goals of ODE to implement in any way, as it takes as input slip ratio,
slip
> angle and such parameters that require careful treatment (keeping slip
ratio
> numerically stable is a chapter in itself).
>
> Ruud
>
> --__--__--
>
> Message: 7
> From: "Gildas Bayard" <gildas.bayard@mail.com>
> To: <ode@q12.org>
> Subject: [ODE] simulator behaviour problem
> Date: Tue, 11 Dec 2001 14:49:47 +0100
>
> This is a multi-part message in MIME format.
>
> ------=_NextPart_000_003E_01C18253.14EFC980
> Content-Type: multipart/alternative;
> boundary="----=_NextPart_001_003F_01C18253.14EFC980"
>
>
> ------=_NextPart_001_003F_01C18253.14EFC980
> Content-Type: text/plain;
> charset="iso-8859-1"
> Content-Transfer-Encoding: quoted-printable
>
>
> Hi everybody,
>
> I'm about to use ODE for my PhD stuff. My goal is to design adaptive
> controllers based on new types of neural network which include adaptive
> synapses and simulation of neuromodulators behaviour.
>
> To obtain a good controller through artificial evolution, I need to
> perform thousands of virtual test in the same simulator with
> successively different animats. That's where I get into a problem with =
> ODE.
>
> I try the following:
> 1) I create a world and a space.
> 2) I add bodies and joints for my first hexapod and run the
> simulation. After a certain amount of time I want to test an other
> one. So I destroy all the bodies and joints of the first hexapod and
> add all the bodies and joints of the second one.
> 3) When I've tested enough hexapods I destroy the world and space.
>
> The exact problem is the following: right after the morphology has
> been re-created again I got an "ODE Message 3: LCP internal error,=20
> s <=3D 0" error and the hexapod performs a bad move.
>
> Even if I recreate world and space each time I want to test a new
> hexapod (in addition to the morphology), I got the same problem.
> I probably need to initialize something each time I test a new agent
> but I don't know what...
>
> Gildas Bayard
>
> AnimatLab
> Laboratoire d'Informatique de Paris 6
>
> NB: In the example file I've attached the morphology is always the same =
> so
> it doesn't seem useful to destroy the hexapod to create it again right
> away. The idea is that in my application the morphology might vary
> from an animat to another one.
>
> ------=_NextPart_001_003F_01C18253.14EFC980
> Content-Type: text/html;
> charset="iso-8859-1"
> Content-Transfer-Encoding: quoted-printable
>
> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
> <HTML><HEAD>
> <META http-equiv=3DContent-Type content=3D"text/html; =
> charset=3Diso-8859-1">
> <META content=3D"MSHTML 6.00.2600.0" name=3DGENERATOR>
> <STYLE></STYLE>
> </HEAD>
> <BODY bgColor=3D#ffffff><FONT face=3DArial size=3D2>
> <DIV><BR>Hi everybody,</DIV>
> <DIV>&nbsp;</DIV>
> <DIV>I'm about to use ODE for my PhD stuff. My goal is to design=20
> adaptive<BR>controllers based on new types of neural network which =
> include=20
> adaptive<BR>synapses and simulation of neuromodulators behaviour.</DIV>
> <DIV>&nbsp;</DIV>
> <DIV>To obtain a good controller through artificial evolution, I need=20
> to<BR>perform thousands of virtual test in the same simulator=20
> with<BR>successively different animats. That's where I get into a =
> problem with=20
> ODE.</DIV>
> <DIV>&nbsp;</DIV>
> <DIV>I try the following:<BR>1) I create a world and a space.<BR>2) I =
> add bodies=20
> and joints for my first hexapod and run the<BR>simulation. After a =
> certain=20
> amount of time I want to test an other<BR>one. So I destroy all the =
> bodies and=20
> joints of the first hexapod and<BR>add all the bodies and joints of the =
> second=20
> one.<BR>3) When I've tested enough hexapods I destroy the world and =
> space.</DIV>
> <DIV>&nbsp;</DIV>
> <DIV>The exact problem is the following: right after the morphology =
> has<BR>been=20
> re-created again I got an "ODE Message 3: LCP internal error, <BR>s =
> &lt;=3D 0"=20
> error and the hexapod performs a bad move.</DIV>
> <DIV>&nbsp;</DIV>
> <DIV>Even if I recreate world and space each time I want to test a=20
> new<BR>hexapod (in addition to the morphology), I got the same =
> problem.<BR>I=20
> probably need to initialize something each time I test a new =
> agent<BR>but I=20
> don't know what...</DIV>
> <DIV>&nbsp;</DIV>
> <DIV>Gildas Bayard</DIV>
> <DIV>&nbsp;</DIV>
> <DIV>AnimatLab<BR>Laboratoire d'Informatique de Paris 6</DIV>
> <DIV>&nbsp;</DIV>
> <DIV>NB: In the example file I've attached the morphology is always the =
> same=20
> so<BR>it doesn't seem useful to destroy the hexapod to create it again=20
> right<BR>away. The idea is that in my application the morphology might=20
> vary<BR>from an animat to another one.</FONT></DIV></BODY></HTML>
>
> ------=_NextPart_001_003F_01C18253.14EFC980--
>
> ------=_NextPart_000_003E_01C18253.14EFC980
> Content-Type: application/octet-stream;
> name="hexapod.cpp"
> Content-Transfer-Encoding: quoted-printable
> Content-Disposition: attachment;
> filename="hexapod.cpp"
>
> /******************=0A=
>  *                *=0A=
>  *     hexapod    *=0A=
>  *                *=0A=
>  ******************=0A=
> =0A=
> bodies are enclosed by [], joints are enclosed by ()=0A=
> =0A=
> FRONT=0A=
> =0A=
>         LEFT            RIGHT=0A=
> =0A=
>     (13)    (07)    (06)    (12)=0A=
> [14]----[08]----[02]----[07]----[13]=0A=
>                  |=0A=
>                  | (01)=0A=
>                  |=0A=
>     (11)    (05) |  (04)    (10)=0A=
> [12]----[06]----[01]----[05]----[11]=0A=
>                  |=0A=
>                  | (00)=0A=
>                  |=0A=
>     (09)    (03) |  (02)    (08)=0A=
> [10]----[04]----[00]----[03]----[09]=0A=
> =0A=
> BACK=0A=
> =0A=
> */=0A=
> =0A=
> #include <iostream>=0A=
> #include <string>=0A=
> #include <stdio.h>=0A=
> #include "ode/ode.h"=0A=
> =0A=
> #include "../drawstuff/src/drawstuff.cpp"=0A=
> #include "../drawstuff/src/x11.cpp"=0A=
> =0A=
> // select correct drawing functions=0A=
> =0A=
> #ifdef dDOUBLE=0A=
> #define dsDrawBox dsDrawBoxD=0A=
> #define dsDrawSphere dsDrawSphereD=0A=
> #define dsDrawCylinder dsDrawCylinderD=0A=
> #define dsDrawCappedCylinder dsDrawCappedCylinderD=0A=
> #endif=0A=
> =0A=
> // some constants=0A=
> #define FOREARMLENGTH 0.05 // forearm length=0A=
> #define FOREARMWIDTH 0.05  // forearm width (also forearm height)=0A=
> #define FOREARMMASS 0.1    // forearm mass=0A=
> =0A=
> #define ARMLENGTH 0.2   // arm length=0A=
> #define ARMWIDTH 0.05 // arm width (also arm height)=0A=
> #define ARMMASS 0.2     // arm mass=0A=
> =0A=
> #define BODYRADIUS 0.1 // body radius=0A=
> #define BODYMASS 0.5 // body mass=0A=
> =0A=
> #define STARTX 0    // hexapod start position along x=0A=
> #define STARTY 0    // hexapod start position along y=0A=
> #define STARTZ 0.2  // hexapod start position along z=0A=
> =0A=
> #define STEPSIZE 0.01 // integrator step size in seconds=0A=
> #define MAXFORCE 3=0A=
> #define GRAPHICS 1=0A=
>   =0A=
> dWorld *world;=0A=
> dSpace *space;=0A=
> dJointGroup *contactgroup;=0A=
> dGeom *ground;=0A=
> dGeom *ground_box;=0A=
> dBody *body[15];=0A=
> dJoint *joint[14];=0A=
> dGeom *box[12];=0A=
> dGeom *sphere[3];=0A=
> dContact contact;=0A=
> =0A=
> // this is called by dSpaceCollide when two objects in space are=0A=
> // potentially colliding.=0A=
> void nearCallback (void *data, dGeomID o1, dGeomID o2) {=0A=
>   =0A=
>   // if both objects are static -> return=0A=
>   if ((dGeomGetBody(o1) =3D=3D NULL) && (dGeomGetBody(o2) =3D=3D NULL))
{=0A=
>     return;=0A=
>   }=0A=
> =0A=
>   // if both objects are dynamic -> test if they are connected, if they =
> are -> return=0A=
>   if ((dGeomGetBody(o1) !=3D NULL) && (dGeomGetBody(o2) !=3D NULL)) {=0A=
>     if (dAreConnected(dGeomGetBody(o1), dGeomGetBody(o2))) {=0A=
>       return;=0A=
>     }=0A=
>   }=0A=
> =0A=
>   if (dCollide (o1,o2,0,&contact.geom,sizeof(dContactGeom))) {=0A=
>     dJoint *joint =3D new dJoint();=0A=
>     joint->createContact (*world, contactgroup, &contact);=0A=
>     dJointAttach (joint->id(), dGeomGetBody(o1), dGeomGetBody(o2));=0A=
>   }=0A=
> }=0A=
> =0A=
> int gaitPart;=0A=
> int secondElapsed;=0A=
> dReal timeElapsed;=0A=
> =0A=
> void control() {=0A=
>   // implement bugs tripod gait=0A=
>   =0A=
>   dReal bodyForearmGroup1;  // group 1 is two arms on the right (joints =
> 2 and 6), one on the left (joint 5)=0A=
>   dReal bodyForearmGroup2;  // group 2 is two arms on the left (joints 3 =
> and 7), one on the right (joint 4)=0A=
>   dReal forearmArmGroup1;=0A=
>   dReal forearmArmGroup2;=0A=
> =0A=
>   timeElapsed +=3D STEPSIZE;=0A=
> =0A=
>   if ((int)timeElapsed - secondElapsed >=3D 1)=0A=
>     cerr << "second elapsed =3D " << ++secondElapsed << endl;=0A=
>   =0A=
>   // target orientations, determine gait=0A=
>   if ((int)timeElapsed % 2 < 1) {=0A=
>     if (gaitPart !=3D 1) {=0A=
>       gaitPart =3D 1;=0A=
>       // cout << "gait part 1" << endl;=0A=
>     }=0A=
>     bodyForearmGroup1 =3D M_PI /16;=0A=
>     bodyForearmGroup2 =3D - M_PI / 16;=0A=
>     forearmArmGroup1 =3D 0;=0A=
>     forearmArmGroup2 =3D - M_PI / 4;=0A=
>   }=0A=
>   else {=0A=
>     if (gaitPart !=3D 2) {=0A=
>       gaitPart =3D 2;=0A=
>       // cout << "gait part 2" << endl;=0A=
>     }=0A=
>     bodyForearmGroup1 =3D - M_PI / 16;=0A=
>     bodyForearmGroup2 =3D M_PI / 16;=0A=
>     forearmArmGroup1 =3D - M_PI / 4;=0A=
>     forearmArmGroup2 =3D 0;=0A=
>   }=0A=
> =0A=
>   dReal targetSpeed;=0A=
>   dReal maxSpeed =3D 0;=0A=
> =0A=
>   for (int i =3D 0; i < 14; i++) {=0A=
>     // body/body joint should remain around zero orientation=0A=
>     if (i < 2) {=0A=
>       targetSpeed =3D - 10 * dJointGetHingeAngle(joint[i]->id());=0A=
>       maxSpeed =3D 2;=0A=
>     }=0A=
> =0A=
>     // body/forearm orientation=0A=
>     else if (i < 8) {=0A=
>       if ((i =3D=3D 2) || (i =3D=3D 5) || (i=3D=3D 6)) // group 1=0A=
> if (i % 2 =3D=3D 0) // i even=0A=
>   targetSpeed =3D 10 * (bodyForearmGroup1 - =
> dJointGetHingeAngle(joint[i]->id()));=0A=
> else // i odd=0A=
>   targetSpeed =3D 10 * (- bodyForearmGroup1 - =
> dJointGetHingeAngle(joint[i]->id()));=0A=
>       else // group 2=0A=
> if (i % 2 =3D=3D 0) // i even=0A=
>   targetSpeed =3D 10 * (bodyForearmGroup2 - =
> dJointGetHingeAngle(joint[i]->id()));=0A=
> else // i odd=0A=
>   targetSpeed =3D 10 * (- bodyForearmGroup2 - =
> dJointGetHingeAngle(joint[i]->id()));=0A=
>       //      cerr << "Body/Forearm target speed: " << targetSpeed << =
> endl;=0A=
>       maxSpeed =3D 1;=0A=
>     }=0A=
> =0A=
>     // forearm/arm orientation=0A=
>     else if (i < 14) {=0A=
>       if ((i =3D=3D 8) || (i =3D=3D 11) || (i=3D=3D 12)) { // group 1=0A=
> if (i % 2 =3D=3D 0) // i even=0A=
>   targetSpeed =3D 10 * (forearmArmGroup1 - =
> dJointGetHingeAngle(joint[i]->id()));=0A=
> else // i odd=0A=
>   targetSpeed =3D 10 * (- forearmArmGroup1 - =
> dJointGetHingeAngle(joint[i]->id()));=0A=
>       }=0A=
>       else { // group 2=0A=
> if (i % 2 =3D=3D 0) // i even=0A=
>   targetSpeed =3D 10 * (forearmArmGroup2 - =
> dJointGetHingeAngle(joint[i]->id()));=0A=
> else // i odd=0A=
>   targetSpeed =3D 10 * (- forearmArmGroup2 - =
> dJointGetHingeAngle(joint[i]->id()));=0A=
>       }=0A=
>       //      cerr << "Forearm/Arm target speed: " << targetSpeed << =
> endl;=0A=
>       maxSpeed =3D 1;=0A=
>     }=0A=
> =0A=
>     if (targetSpeed > maxSpeed) targetSpeed =3D maxSpeed;=0A=
>     if (targetSpeed < -maxSpeed) targetSpeed =3D -maxSpeed;=0A=
> =0A=
>     if (i < 14) {=0A=
>       // cerr << "archi output value: " <<  targetSpeed << endl;=0A=
>       dJointSetHingeParam(joint[i]->id(), dParamVel, targetSpeed);=0A=
>     }=0A=
>     else {=0A=
>       dJointSetHingeParam(joint[i]->id(), dParamVel, 0);=0A=
>     }=0A=
>     //      dJointSetHingeParam(joint[i], dParamVel, targetSpeed);=0A=
>   }=0A=
> }=0A=
> =0A=
> void start3dDisplay(int window_width, int window_height) {=0A=
>   current_state =3D 1;=0A=
>   use_textures =3D 0;=0A=
>   initMotionModel();=0A=
>   createMainWindow (window_width, window_height);=0A=
>   glXMakeCurrent (display,win,glx_context);=0A=
> =0A=
>   char *prefix =3D "../drawstuff/textures";=0A=
>   char *s =3D (char*) alloca (strlen(prefix) + 20);=0A=
> =0A=
>   strcpy (s,prefix);=0A=
>   strcat (s,"/sky.ppm");=0A=
>   sky_texture =3D new Texture (s);=0A=
> =0A=
>   strcpy (s,prefix);=0A=
>   strcat (s,"/ground.ppm");=0A=
>   ground_texture =3D new Texture (s);=0A=
> =0A=
>   strcpy (s,prefix);=0A=
>   strcat (s,"/wood.ppm");=0A=
>   wood_texture =3D new Texture (s);=0A=
> }=0A=
> =0A=
> void initFrame (int width, int height) {=0A=
>   if (current_state < 1) dsDebug ("internal error");=0A=
>   current_state =3D 2;=0A=
> =0A=
>   // setup stuff=0A=
>   glEnable (GL_LIGHTING);=0A=
>   glEnable (GL_LIGHT0);=0A=
>   glDisable (GL_TEXTURE_2D);=0A=
>   glDisable (GL_TEXTURE_GEN_S);=0A=
>   glDisable (GL_TEXTURE_GEN_T);=0A=
>   glShadeModel (GL_FLAT);=0A=
>   glEnable (GL_DEPTH_TEST);=0A=
>   glDepthFunc (GL_LESS);=0A=
>   glEnable (GL_CULL_FACE);=0A=
>   glCullFace (GL_BACK);=0A=
>   glFrontFace (GL_CCW);=0A=
> =0A=
>   // setup viewport=0A=
>   glViewport (0,0,width,height);=0A=
>   glMatrixMode (GL_PROJECTION);=0A=
>   glLoadIdentity();=0A=
>   const float vnear =3D 0.1f;=0A=
>   const float vfar =3D 100.0f;=0A=
>   const float k =3D 0.8f;     // view scale, 1 =3D +/- 45 degrees=0A=
>   if (width >=3D height) {=0A=
>     float k2 =3D float(height)/float(width);=0A=
>     glFrustum (-vnear*k,vnear*k,-vnear*k*k2,vnear*k*k2,vnear,vfar);=0A=
>   }=0A=
>   else {=0A=
>     float k2 =3D float(width)/float(height);=0A=
>     glFrustum (-vnear*k*k2,vnear*k*k2,-vnear*k,vnear*k,vnear,vfar);=0A=
>   }=0A=
> =0A=
>   // setup lights. it makes a difference whether this is done in the=0A=
>   // GL_PROJECTION matrix mode (lights are scene relative) or the=0A=
>   // GL_MODELVIEW matrix mode (lights are camera relative, bad!).=0A=
>   static GLfloat light_ambient[] =3D { 0.5, 0.5, 0.5, 1.0 };=0A=
>   static GLfloat light_diffuse[] =3D { 1.0, 1.0, 1.0, 1.0 };=0A=
>   static GLfloat light_specular[] =3D { 1.0, 1.0, 1.0, 1.0 };=0A=
>   glLightfv (GL_LIGHT0, GL_AMBIENT, light_ambient);=0A=
>   glLightfv (GL_LIGHT0, GL_DIFFUSE, light_diffuse);=0A=
>   glLightfv (GL_LIGHT0, GL_SPECULAR, light_specular);=0A=
>   glColor3f (1.0, 1.0, 1.0);=0A=
> =0A=
>   // clear the window=0A=
>   glClearColor (0.5,0.5,0.5,0);=0A=
>   glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);=0A=
> =0A=
>   // snapshot camera position (in MS Windows it is changed by the GUI =
> thread)=0A=
>   float view2_xyz[3];=0A=
>   float view2_hpr[3];=0A=
>   memcpy (view2_xyz,view_xyz,sizeof(float)*3);=0A=
>   memcpy (view2_hpr,view_hpr,sizeof(float)*3);=0A=
> =0A=
>   // go to GL_MODELVIEW matrix mode and set the camera=0A=
>   glMatrixMode (GL_MODELVIEW);=0A=
>   glLoadIdentity();=0A=
>   setCamera (view2_xyz[0],view2_xyz[1],view2_xyz[2],=0A=
>      view2_hpr[0],view2_hpr[1],view2_hpr[2]);=0A=
> =0A=
>   // set the light position (for some reason we have to do this in model =
> view.=0A=
>   static GLfloat light_position[] =3D { LIGHTX, LIGHTY, 1.0, 0.0 };=0A=
>   glLightfv (GL_LIGHT0, GL_POSITION, light_position);=0A=
> =0A=
>   // draw the background (ground, sky etc)=0A=
>   drawSky (view2_xyz);=0A=
>   drawGround();=0A=
> =0A=
>   // draw the little markers on the ground=0A=
>   drawPyramidGrid();=0A=
> =0A=
>   // leave openGL in a known state - flat shaded white, no textures=0A=
>   glEnable (GL_LIGHTING);=0A=
>   glDisable (GL_TEXTURE_2D);=0A=
>   glShadeModel (GL_FLAT);=0A=
>   glEnable (GL_DEPTH_TEST);=0A=
>   glDepthFunc (GL_LESS);=0A=
>   glColor3f (1,1,1);=0A=
>   setColor (1,1,1,1);=0A=
> =0A=
>   // draw the rest of the objects. set drawing state first.=0A=
>   color[0] =3D 1;=0A=
>   color[0] =3D 1;=0A=
>   color[0] =3D 1;=0A=
>   tnum =3D 0;=0A=
> }=0A=
> =0A=
> void drawObjects(void) {=0A=
>   for (int i =3D 0; i < 3; i++) {=0A=
>     dsDrawSphere (dGeomGetPosition(sphere[i]->id()), =
> dGeomGetRotation(sphere[i]->id()), BODYRADIUS);=0A=
>   }=0A=
>   dReal forearmSides[3] =3D {FOREARMLENGTH, FOREARMWIDTH,
FOREARMWIDTH};=0A=
>   for (int i =3D 0; i < 6; i++) {=0A=
>     dsDrawBox (dGeomGetPosition(box[i]->id()), =
> dGeomGetRotation(box[i]->id()), forearmSides);=0A=
>   }=0A=
>   dReal armSides[3] =3D {ARMLENGTH, ARMWIDTH, ARMWIDTH};=0A=
>   for (int i =3D 6; i < 12; i++) {=0A=
>     dsDrawBox (dGeomGetPosition(box[i]->id()), =
> dGeomGetRotation(box[i]->id()), armSides);=0A=
>   }=0A=
>   =0A=
>   dVector3 ss;=0A=
>   dGeomBoxGetLengths (ground_box->id(),ss);=0A=
>   dsDrawBox =
> (dGeomGetPosition(ground_box->id()),dGeomGetRotation(ground_box->id()),ss=
> );=0A=
> }=0A=
> =0A=
> void createHexapod(dWorld *world, dSpace *space) {=0A=
>   int i;=0A=
>   dMass *mass =3D new dMass();=0A=
> =0A=
>   // bodies=0A=
>   for (i =3D 0; i < 3; i++) {=0A=
>     body[i] =3D new dBody(*world);=0A=
>     //    cerr << "position body " << i << "=3D " << STARTX << ", " << =
> STARTY + i * 2 * BODYRADIUS << ", " << STARTZ << endl;=0A=
> =0A=
>     body[i]->setPosition(STARTX, STARTY + (i - 1) * 2 * BODYRADIUS, =
> STARTZ);=0A=
>     mass->setSphere(1, BODYRADIUS);=0A=
>     mass->adjust(BODYMASS);=0A=
>     body[i]->setMass(mass);=0A=
>     sphere[i] =3D new dGeom();=0A=
>     sphere[i]->createSphere (*space, BODYRADIUS);=0A=
>     sphere[i]->setBody(*body[i]);=0A=
>   }=0A=
> =0A=
>   // forearms=0A=
>   for (i =3D 3; i < 9; i++) {=0A=
>     body[i] =3D new dBody(*world);=0A=
>     //    cerr << "position body " << i << "=3D " << (i%2 =3D=3D 0 ? =
> STARTX - FOREARMLENGTH / 2.0 - BODYRADIUS : STARTX + FOREARMLENGTH / 2.0 =
> + BODYRADIUS) << ", " << STARTY + (i - 3)/2 * 2 *BODYRADIUS << ", " << =
> STARTZ << endl;=0A=
> =0A=
>     body[i]->setPosition(i%2 =3D=3D 0 ? STARTX - BODYRADIUS - =
> FOREARMLENGTH / 2.0: STARTX +  BODYRADIUS + FOREARMLENGTH / 2.0, \=0A=
>      STARTY + ((i - 3)/2 - 1) * 2 *BODYRADIUS, \=0A=
>      STARTZ);=0A=
>     mass->setBox(1, FOREARMLENGTH, FOREARMWIDTH, FOREARMWIDTH);=0A=
>     mass->adjust(FOREARMMASS);=0A=
>     body[i]->setMass(mass);=0A=
>     box[i - 3] =3D new dGeom();=0A=
>     box[i - 3]->createBox(*space, FOREARMLENGTH, FOREARMWIDTH, =
> FOREARMWIDTH);=0A=
>     box[i - 3]->setBody(*body[i]);=0A=
>   }=0A=
> =0A=
>   // arms=0A=
>   for (i =3D 9; i < 15; i++) {=0A=
>     body[i] =3D new dBody(*world);=0A=
>     //    cerr << "position body " << i << "=3D " << (i%2 =3D=3D 0 ? =
> STARTX - FOREARMLENGTH - ARMLENGTH / 2.0 - BODYRADIUS : STARTX + =
> FOREARMLENGTH + ARMLENGTH / 2.0 + BODYRADIUS) << ", " << STARTY + (i - =
> 9)/2 * 2 *BODYRADIUS << ", " << STARTZ << endl;=0A=
>     body[i]->setPosition(i%2 =3D=3D 0 ? STARTX - BODYRADIUS - =
> FOREARMLENGTH - ARMLENGTH / 2.0 : STARTX + BODYRADIUS + FOREARMLENGTH + =
> ARMLENGTH / 2.0, \=0A=
>      STARTY + ((i - 9)/2 - 1) * 2 *BODYRADIUS, \=0A=
>      STARTZ);=0A=
>     mass->setBox(1, ARMLENGTH, ARMWIDTH, ARMWIDTH);=0A=
>     mass->adjust(ARMMASS);=0A=
>     body[i]->setMass(mass);=0A=
>     box[i - 3] =3D new dGeom();=0A=
>     box[i - 3]->createBox(*space, ARMLENGTH, ARMWIDTH, ARMWIDTH);=0A=
>     box[i - 3]->setBody(*body[i]);=0A=
>   }=0A=
> =0A=
>   // links body/body (number 0 and 1)=0A=
>   for (i =3D 0; i < 2; i++) {=0A=
>     joint[i] =3D new dJoint();=0A=
>     joint[i]->createHinge(*world, 0);=0A=
>     joint[i]->attach(*body[i], *body[i + 1]);=0A=
>     //    cerr << "anchor joint " << i << "=3D " << STARTX << ", " << =
> STARTY + (2 * i + 1) / 2.0 * BODYRADIUS << ", " << STARTZ << " | axis =
> joint " << i << "=3D 0, 1, 0" << endl;=0A=
>     joint[i]->setHingeAnchor(STARTX, STARTY + (2 * i - 1) * BODYRADIUS , =
> STARTZ); // i / 2 * 2.0 !=3D i=0A=
>     joint[i]->setHingeAxis(0, 1, 0);=0A=
>     joint[i]->setHingeParam(dParamFMax, MAXFORCE);=0A=
>   }=0A=
>   =0A=
>   // links body/forearm allowing front/back moves (number 2 to 7)=0A=
>   for (i =3D 2; i < 8; i++) {=0A=
>     joint[i] =3D new dJoint();=0A=
>     joint[i]->createHinge(*world, 0);=0A=
>     joint[i]->attach(*body[(i - 2) / 2], *body[i + 1]);=0A=
>     //    cerr << "anchor joint " << i << "=3D " << (i%2 =3D=3D 0 ? =
> STARTX - BODYRADIUS : STARTX + BODYRADIUS) << ", " << STARTY + (i - 2)/2 =
> * 2.0 * BODYRADIUS << ", " << STARTZ << " | axis joint " << i << "=3D 0, =
> 0, 1" << endl;=0A=
>     joint[i]->setHingeAnchor(i%2 =3D=3D 0 ? STARTX - BODYRADIUS : STARTX =
> + BODYRADIUS , \=0A=
>      STARTY + ((i - 2)/2 - 1) * 2.0 * BODYRADIUS, \=0A=
>      STARTZ);=0A=
>     joint[i]->setHingeAxis(0, 0, 1);=0A=
>     joint[i]->setHingeParam(dParamFMax, MAXFORCE);=0A=
>   }=0A=
> =0A=
>   // links forearm/arm allowing up/down moves (number 8 to 13)=0A=
>   for (i =3D 8; i < 14; i++) {=0A=
>     joint[i] =3D new dJoint();=0A=
>     joint[i]->createHinge(*world, 0);=0A=
>     joint[i]->attach(*body[i - 5], *body[i + 1]);=0A=
>     //    cerr << "anchor joint " << i << "=3D " << (i%2 =3D=3D 0 ? =
> STARTX + BODYRADIUS + FOREARMLENGTH: STARTX - BODYRADIUS - =
> FOREARMLENGTH) << ", " << STARTY + (i - 8)/2 * 2.0 * BODYRADIUS << ", " =
> << STARTZ << " | axis joint " << i << "=3D 0, 1, 0" << endl;=0A=
>     joint[i]->setHingeAnchor(i%2 =3D=3D 0 ? STARTX + BODYRADIUS + =
> FOREARMLENGTH: STARTX - BODYRADIUS - FOREARMLENGTH, \=0A=
>      STARTY + ((i - 8)/2 - 1) * 2.0 * BODYRADIUS, STARTZ);=0A=
>     joint[i]->setHingeAxis(0, 1, 0);=0A=
>     joint[i]->setHingeParam(dParamFMax, MAXFORCE);=0A=
>   }=0A=
> }=0A=
> =0A=
> void destroyHexapod() {=0A=
>   int i;=0A=
>   =0A=
>   for (i =3D 0; i < 12; i++)=0A=
>     delete box[i];=0A=
>   for (i =3D 0; i < 3; i++)=0A=
>     delete sphere[i];=0A=
>   for (i =3D 0; i < 15; i++)=0A=
>     delete body[i];=0A=
>   for (i =3D 0; i < 14; i++)=0A=
>     delete joint[i];=0A=
> }=0A=
> =0A=
> void createEnvironement(dWorld *world, dSpace *space) {=0A=
>   ground =3D new dGeom();=0A=
>   ground->createPlane (*space,0,0,1,0);=0A=
>   ground_box =3D new dGeom();=0A=
>   ground_box->createBox (*space, 2, 1.5, 1);=0A=
>   dMatrix3 R;=0A=
>   dRFromAxisAndAngle (R, 1, 0, 0, 0.15);=0A=
>   ground_box->setPosition(0, 2, -0.4);=0A=
>   ground_box->setRotation(R);=0A=
> }=0A=
> =0A=
> void destroyEnvironement() {=0A=
>   delete ground;=0A=
>   delete ground_box;=0A=
> }=0A=
> =0A=
> int main (int argc, char **argv) {=0A=
>   contact.surface.mode =3D dContactSlip1 | dContactSlip2 | =
> dContactBounce;=0A=
>   contact.surface.mu =3D dInfinity;=0A=
>   contact.surface.slip1 =3D 0.05;=0A=
>   contact.surface.slip2 =3D 0.05;=0A=
>   contact.surface.bounce =3D 0.5;=0A=
>   contact.surface.bounce_vel =3D 0.01;=0A=
>   contact.surface.soft_erp =3D 0.9;=0A=
>   contact.surface.soft_cfm =3D 0.05;=0A=
> =0A=
>   // create virtual world=0A=
>   world =3D new dWorld();=0A=
>   space =3D new dSpace();=0A=
>   contactgroup =3D new dJointGroup(1000);=0A=
>   world->setGravity (0, 0, -9.81);=0A=
>     =0A=
>   createEnvironement(world, space);=0A=
> =0A=
>   // run graphical simulation=0A=
>   start3dDisplay(352, 288);=0A=
>   for (int nbExec =3D 0; nbExec < 4; nbExec++) {=0A=
>     createHexapod(world, space);=0A=
>     =0A=
>     // reset controler=0A=
>     gaitPart =3D 0;=0A=
>     secondElapsed =3D 0;=0A=
>     timeElapsed =3D 0;=0A=
>     =0A=
>     for (int i =3D 0; i < 100; i++) {=0A=
>       // drive the hexapod=0A=
>       control();=0A=
>       =0A=
>       // make a step forward=0A=
>       space->collide(NULL, &nearCallback);=0A=
>       world->step(STEPSIZE);=0A=
> =0A=
>       // remove all contact joints=0A=
>       contactgroup->empty();=0A=
>       cerr << "#";=0A=
> =0A=
>       // draw the scene=0A=
>       initFrame(352, 288);=0A=
>       drawObjects();=0A=
>       glFlush();=0A=
>       glXSwapBuffers (display,win);=0A=
>       XSync (display,0);=0A=
>     }=0A=
>     destroyHexapod();=0A=
>   }=0A=
>   dsStopGraphics();=0A=
>   destroyMainWindow();=0A=
> =0A=
>   destroyEnvironement();=0A=
>   =0A=
>   delete contactgroup;=0A=
>   delete world;=0A=
>   delete space;=0A=
> =0A=
>   return 0;=0A=
> }=0A=
>
> ------=_NextPart_000_003E_01C18253.14EFC980
> Content-Type: application/octet-stream;
> name="Makefile"
> Content-Transfer-Encoding: quoted-printable
> Content-Disposition: attachment;
> filename="Makefile"
>
>
#########################################################################=0A
=
> #
#=0A=
> # Open Dynamics Engine, Copyright (C) 2001 Russell L. Smith.
#=0A=
> #   Email: russ@q12.org   Web: www.q12.org
#=0A=
> #
#=0A=
> # This library is free software; you can redistribute it and/or
#=0A=
> # modify it under the terms of the GNU Lesser General Public
#=0A=
> # License as published by the Free Software Foundation; either
#=0A=
> # version 2.1 of the License, or (at your option) any later version.
#=0A=
> #
#=0A=
> # This library is distributed in the hope that it will be useful,
#=0A=
> # but WITHOUT ANY WARRANTY; without even the implied warranty of
#=0A=
> # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
#=0A=
> # Lesser General Public License for more details.
#=0A=
> #
#=0A=
> # You should have received a copy of the GNU Lesser General Public
#=0A=
> # License along with this library (see the file LICENSE.TXT); if not,
#=0A=
> # write to the Free Software Foundation, Inc., 59 Temple Place,
#=0A=
> # Suite 330, Boston, MA 02111-1307 USA.
#=0A=
> #
#=0A=
>
#########################################################################=0A
=
> =0A=
> MAKEFILE_INC=3D../build/Makefile.inc=0A=
> include $(MAKEFILE_INC)=0A=
> =0A=
> APPNAMES =3D $(call fEXENAME,hexapod)=0A=
> =0A=
> INCLUDE_PATHS =3D ../include=0A=
> LIB_PATHS =3D ../lib=0A=
> EXTRA_CLEAN =3D $(APPNAMES)=0A=
> DEFINES=3Dd$(DREAL)=0A=
> =0A=
> ifeq ($(PLATFORM_IS_WINDOWS),1)=0A=
>   RSRC=3D../lib/drawstuff.res=0A=
> endif=0A=
> =0A=
> all: $(APPNAMES)=0A=
> =0A=
> $(call fEXENAME,hexapod): $(call fTARGETS,hexapod.cpp)=0A=
> $(call fLINK,$@,$(call fTARGETS,hexapod.cpp) $(call fLIB,ode) $(call =
> fLIB,drawstuff) $(LINKOPENGL) $(LINKWIN) $(LINKMATH) $(RSRC))=0A=
> =0A=
> $(call fEXENAME,test): $(call fTARGETS,test.cpp)=0A=
> $(call fLINK,$@,$(call fTARGETS,test.cpp) $(call fLIB,ode) $(call =
> fLIB,drawstuff) $(LINKOPENGL) $(LINKWIN) $(LINKMATH) $(RSRC))=0A=
>
> ------=_NextPart_000_003E_01C18253.14EFC980
> Content-Type: image/jpeg;
> name="snapshot.jpg"
> Content-Transfer-Encoding: base64
> Content-Disposition: attachment;
> filename="snapshot.jpg"
>
>
/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0a
>
HBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIy
>
MjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAE+AWADASIA
>
AhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQA
>
AAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3
>
ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWm
>
p6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEA
>
AwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSEx
>
BhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElK
>
U1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3
>
uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDh4I9c
>
1rxLJpWlSTy3UssgihWfYDt3MeSQBgA9+1dB/wAK4+JP/Phc/wDgwi/+OVh+GWuv+E8u/sHm/bDB
>
qHkeRnzPM+zzbduOd2cYxzmug8MH4k/8JZo328eLPsf26Dz/AD/tPl+XvXduzxtxnOeMV7NSrNP3
>
WvmZpHBR6jdzapbeZdTPsjnZdzk4PlNyPelOrX/a9uT/ANtm/wAapWrf8TKIn/njP/6Kao9+OKuM
>
tX/XRCNH+1tQ/wCf25/7/N/jSDV78DAvbjn/AKaN/jWcXANBfJzVcwGj/a1+P+X24z/11b/Gj+1r
>
8D/j8uP+/rVm7+fWgueO1HMBo/2tqB/5frjPb96f8aP7Wv8AP/H9cf8Af1uf1rO3+4pC5o5gNH+1
>
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
>
zmT/ABr0iij+xqP80vw/yD+28R/LH8f8zzf/AIRrXP8AoH/+Rk/xo/4RrXP+gf8A+Rk/xr0iij+x
>
qP8ANL8P8g/tvEfyx/H/ADPNv+EZ1z/oH/8AkZP8aP8AhGtc/wCgf/5GT/GvSaKX9jUf5pfh/kH9
>
t4j+WP4/5nmv/CM65/z4f+Rk/wAaP+EY1zPNh/5GT/GvSqKP7Fo/zS/D/IP7bxH8sfx/zPNf+EY1
>
z/nw/wDIyf40f8IxruMfYP8AyKn+NelUUf2LR/ml+H+Qf23iP5Y/j/meanwxrvH+g/X98n+NB8Ma
>
4f8Alx/8jJ/jXpVFH9i0f5pfh/kH9t4j+WP4/wCZ5p/wi+ubs/YP/Iqf41KfDet/ZI4BYcrI7k+a
>
n8QUev8As16NRS/sSh/NL8P8g/tzEfyx/H/M80PhbXP+fD/yMn+NJ/wi2uEY+wgD/rqn+NemUUf2
>
LQ/ml+H+Qf23iP5Y/j/mFFFFeyeMFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUA
>
FFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAU
>
UUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRR
>
RQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFF
>
ABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUA
>
FFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAU
>
UUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRR
>
RQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFF
>
ABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUA
>
FFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAU
>
UUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRR
>
RQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFF
>
ABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFAFj7L/t/pR9l/wBv9Kt0V+d/6x43
>
+b8F/kfa/wBkYP8Ak/F/5lT7L/t/pR9l/wBv9Kt0Uf6x43+b8F/kH9kYP+T8X/mVPsv+3+lH2X/b
>
/SrdFH+seN/m/Bf5B/ZGD/k/F/5lT7L/ALf6UfZf9v8ASrdFH+seN/m/Bf5B/ZGD/k/F/wCZU+y/
>
7f6UfZf9v9Kt0Uf6x43+b8F/kH9kYP8Ak/F/5lT7L/t/pR9l/wBv9Kt0Uf6x43+b8F/kH9kYP+T8
>
X/mVPsv+3+lH2X/b/SrdFH+seN/m/Bf5B/ZGD/k/F/5lT7L/ALf6UfZf9v8ASrdFH+seN/m/Bf5B
>
/ZGD/k/F/wCZU+y/7f6UfZf9v9Kt0Uf6x43+b8F/kH9kYP8Ak/F/5lT7L/t/pR9l/wBv9Kt0Uf6x
>
43+b8F/kH9kYP+T8X/mVPsv+3+lH2X/b/SrdFH+seN/m/Bf5B/ZGD/k/F/5lT7L/ALf6UfZf9v8A
>
SrdFH+seN/m/Bf5B/ZGD/k/F/wCZU+y/7f6UfZf9v9Kt0Uf6x43+b8F/kH9kYP8Ak/F/5lT7L/t/
>
pR9l/wBv9Kt0Uf6x43+b8F/kH9kYP+T8X/mVPsv+3+lH2X/b/SrdFH+seN/m/Bf5B/ZGD/k/F/5l
>
T7L/ALf6UfZf9v8ASrdFH+seN/m/Bf5B/ZGD/k/F/wCZU+y/7f6UfZf9v9Kt0Uf6x43+b8F/kH9k
>
YP8Ak/F/5lT7L/t/pR9l/wBv9Kt0Uf6x43+b8F/kH9kYP+T8X/mVPsv+3+lH2X/b/SrdFH+seN/m
>
/Bf5B/ZGD/k/F/5lT7L/ALf6UfZf9v8ASrdFH+seN/m/Bf5B/ZGD/k/F/wCZU+y/7f6UfZf9v9Kt
>
0Uf6x43+b8F/kH9kYP8Ak/F/5lT7L/t/pR9l/wBv9Kt0Uf6x43+b8F/kH9kYP+T8X/mFFFFeAemF
>
FFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUU
>
UUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRR
>
QAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFA
>
BRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAF
>
FFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUV
>
c07S7vVJJEtYt/lqWYngD0GfU9B/+uq0sbwyvFINroxVh6EdahVYObgnqt0FnuMoooqwCiiigAoo
>
ooAKKKkt7ea6nSCCNpJXOFVe/wDn1pSkopyk7JAR0VPe2c1heS2s4AkjODg5B7g/lUFEZKcVKLum
>
AUUUUwCiiigAooooAKKDwM1bvtNutOMX2mJkEqB0JBGcjkexGcEVLqQUlBvV7ILFSiimvIkYy7qo
>
6ZY4qwHUVTm1awgGZLuLrjCncc/QVUm8S6dHt2NJLnrsTGPzxSOing8RU+GDfyNeiuck8WIHIjtG
>
ZOxZ9p/LBqpJ4pvW3hI4UBztOCSv64z+FFzshk+LlvG3q0ddRXCya5qUqFGumAP91Qp/MDNVZbu5
>
nULNcSyKDkB3JGfxpXOuGQVX8c0vS7/yO+lu7aBgs1xFGxGcO4Bx+NVZNc02JyjXSkj+6pYfmBiu
>
FooudUMgpL45t+ll/mda3iqzCnbDOWxwCAAT9c1Vk8WSFCI7RVfsWfcPywK5yii52QyfCR+zf5s9
>
KooopnxQUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUVnXGuafbSmJpw8gIDL
>
GN23nByegxzkdeOlMunTnUfLBNvyNGiq1rqFreki3mV2HVeQfrg84560l1qNlZEi5uoYmC79jONx
>
HsOp6HpRuEqc4y5ZJp9i1RWOPEti0u1FnZMgeYEwv15OePpWlbXUN3D5sEgdM4zjGD9DSNKmFr0o
>
qc4NJ+RNRWdqWtWmltGk295X5EcYBYD1OSMD/PY1Vt/EsEkaGa3ljcn5tuGVefXgn8qHpuXSwWIr
>
R56cG0bdFIrK6hlIKkZBB4IpaDl2CiiigArNv9M85jLAAHP3lPAb/wCvWlRTTsBxlzaLLkEbJAeu
>
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
>
> ------=_NextPart_000_003E_01C18253.14EFC980--
>
>
>
> --__--__--
>
> _______________________________________________
> ODE mailing list
> ODE@q12.org
> http://q12.org/mailman/listinfo/ode
>
>
> End of ODE Digest
>