The Open Dynamics Engine (ODE) is a free, industrial quality library for simulating articulated rigid body dynamics. For example, it is good for simulating ground vehicles, legged creatures, and moving objects in VR environments. It is fast, flexible and robust, and it has built-in collision detection. ODE is being developed by Russell Smith with help from several contributors.
If ``rigid body simulation'' does not make much sense to you, check out What is a Physics SDK?.
This is the user guide for ODE version 0.039. Despite the low version number, ODE is reasonably mature and stable.
ODE is best for simulating articulated rigid body structures. An articulated structure is created when rigid bodies of various shapes are connected together with joints of various kinds. Examples are ground vehicles (where the wheels are connected to the chassis), legged creatures (where the legs are connected to the body), or stacks of objects.
ODE is designed to be used in interactive or real-time simulation. It is particularly good for simulating moving objects in changeable virtual reality environments. This is because it is fast, robust and stable, and the user has complete freedom to change the structure of the system even while the simulation is running.
ODE uses a highly stable integrator, so that the simulation errors should not grow out of control. The physical meaning of this is that the simulated system should not "explode" for no reason (believe me, this happens a lot with other simulators if you are not careful). ODE emphasizes speed and stability over physical accuracy.
ODE has hard contacts. This means that a special non-penetration constraint is used whenever two bodies collide. The alternative, used in many other simulators, is to use virtual springs to represent contacts. This is difficult to do right and extremely error-prone.
ODE has a built-in collision detection system. However you can ignore it and do your own collision detection if you want to. The current collision primitives are sphere, box, capped cylinder, plane, ray, and triangular mesh - more collision objects will come later. ODE's collision system provides fast identification of potentially intersecting objects, through the concept of ``spaces''.
Here are the features:
ODE is Copyright © 2001-2003 Russell L. Smith. All rights reserved.
This library is free software; you can redistribute it and/or modify it under the terms of EITHER:
Do you have questions or comments about ODE? Think you can help? Please write to the ODE mailing list.
Step 1: Unpack the ODE archive.
Step 2: Get the GNU make tool. Many Unix platforms come with this, although sometimes it is called gmake. I have provided a version of GNU make for windows here.
Step 3: Edit the settings in the file config/user-settings. The list of supported platforms is given in that file.
Step 4: Run make to configure and build ODE and the graphical test programs. Be sure you are using the correct version of GNU make. The make targets for building the parts of ODE are:
Step 5: To install the ODE library onto your system you should copy the lib/ and include/ directories to a suitable place, e.g. on Unix:
There is currently no built-in support to build Windows DLLs or Unix shared libraries --- although it is not hard to add this yourself.
ODE has been verified to build on at least the following platforms:
ODE uses XWindows and OpenGL to render the scene being simulated. In order to build the example you will need to install Apple X11 server and the X11SDK (as well as the normal developer tools).
These are available from Apple. As of writing this can be found at: http://www.apple.com/macosx/x11. NOTE: there is a tiny link at the bottom right of the page for the SDK
Once the software is installed follow the normal build instructions.
Since ODE uses X11 you need to run the X11 server (which you should have installed, it's in the Applications Folder).
If you run the test app in the XTerm that the X11 server opens by default then they should run fine. If however you run them from a MacOS X Terminal then you need to define the enviroment variable DISPLAY. If DISPLAY is not defined then you will get a message saying: "cannot open X11 display".
For example to run the boxstack test you would type
cd ode/test
DISPLAY=:0.0 ./test_boxstack.exe
|
The best way to understand how to use ODE is to look at the test/example programs that come with it. Note the following things:
#include <ode/ode.h> |
gcc -c -I /home/username/ode/include myprogram.cpp |
[Here is where I will write some background information about rigid body dynamics and simulation. But in the meantime, please refer to Baraff's excellent SIGGRAPH tutorial].
A rigid body has various properties from the point of view of the simulation. Some properties change over time:

The origin of this coordinate frame is the body's point of reference. Some values in ODE (vectors, matrices etc) are relative to the body coordinate frame, and others are relative to the global coordinate frame.
Note that the shape of a rigid body is not a dynamical property (except insofar as it influences the various mass properties). It is only collision detection that cares about the detailed shape of the body.
Bodies are connected to each other with joints. An ``island'' of bodies is a group that can not be pulled apart - in other words each body is connected somehow to every other body in the island.
Each island in the world is treated separately when the simulation step is taken. This is useful to know: if there are N similar islands in the simulation then the step computation time will be O(N).
Each body can be enabled or disabled. Disabled bodies are effectively ``turned off'' and are not updated during a simulation step. Disabling bodies is an effective way to save computation time when it is known that the bodies are motionless or otherwise irrelevent to the simulation.
If there are any enabled bodies in an island then every body in the island will be enabled at the next simulation step. Thus to effectively disable an island of bodies, every body in the island must be disabled. If a disabled island is touched by another enabled body then the entire island will be enabled, as a contact joint will join the enabled body to the island.
The process of simulating the rigid body system through time is called integration. Each integration step advances the current time by a given step size, adjusting the state of all the rigid bodies for the new time value. There are two main issues to consider when working with any integrator:
Between each integrator step the user can call functions to apply forces to the rigid body. These forces are added to "force accumulators" in the rigid body object. When the next integrator step happens, the sum of all the applied forces will be used to push the body around. The forces accumulators are set to zero after each integrator step.
In real life a joint is something like a hinge, that is used to connect two objects. In ODE a joint is very similar: It is a relationship that is enforced between two bodies so that they can only have certain positions and orientations relative to each other. This relationship is called a constraint -- the words joint and constraint are often used interchangeably. The following picture shows three different constraint types:

The first is a ball and socket joint that constraints the ``ball'' of one body to be in the same location as the ``socket'' of another body. The second is a hinge joint that constraints the two parts of the hinge to be in the same location and to line up along the hinge axle. The third is a slider joint that constraints the ``piston'' and ``socket'' to line up, and additionally constraints the two bodies to have the same orientation.
Each time the integrator takes a step all the joints are allowed to apply constraint forces to the bodies they affect. These forces are calculated such that the bodies move in such a way to preserve all the joint relationships.
Each joint has a number of parameters controlling its geometry. An example is the position of the ball-and-socket point for a ball-and-socket joint. The functions to set joint parameters all take global coordinates, not body-relative coordinates. A consequence of this is that the rigid bodies that a joint connects must be positioned correctly before the joint is attached.
A joint group is a special container that holds joints in a world. Joints can be added to a group, and then when those joints are no longer needed the entire group of joints can be very quickly destroyed with one function call. However, individual joints in a group can not be destroyed before the entire group is emptied.
This is most useful with contact joints, which are added and remove from the world in groups every time step.
When a joint attaches two bodies, those bodies are required to have certain positions and orientations relative to each other. However, it is possible for the bodies to be in positions where the joint constraints are not met. This ``joint error'' can happen in two ways:

There is a mechanism to reduce joint error: during each simulation step each joint applies a special force to bring its bodies back into correct alignment. This force is controlled by the error reduction parameter (ERP), which has a value between 0 and 1.
The ERP specifies what proportion of the joint error will be fixed during the next simulation step. If ERP=0 then no correcting force is applied and the bodies will eventually drift apart as the simulation proceeds. If ERP=1 then the simulation will attempt to fix all joint error during the next time step. However, setting ERP=1 is not recommended, as the joint error will not be completely fixed due to various internal approximations. A value of ERP=0.1 to 0.8 is recommended (0.2 is the default).
A global ERP value can be set that affects most joints in the simulation. However some joints have local ERP values that control various aspects of the joint.
Most constraints are by nature ``hard''. This means that the constraints represent conditions that are never violated. For example, the ball must always be in the socket, and the two parts of the hinge must always be lined up. In practice constraints can be voilated by unintentional introduction of errors into the system, but the error reduction parameter can be set to correct these errors.
Not all constraints are hard. Some ``soft'' constraints are designed to be violated. For example, the contact constraint that prevents colliding objects from penetrating is hard by default, so it acts as though the colliding surfaces are made of steel. But it can be made into a soft constraint to simulate softer materials, thereby allowing some natural penetration of the two objects when they are forced together.
There are two parameters that control the distinction between hard and soft constraints. The first is the error reduction parameter (ERP) that has already been introduced. The second is the constraint force mixing (CFM) value, that is described below.
What follows is a somewhat technical description of the meaning of CFM. If you just want to know how it is used in practice then skip to the next section.
Traditionally the constraint equation for every joint has the form
where v is a velocity vector for the bodies involved, J is a ``Jacobian'' matrix with one row for every degree of freedom the joint removes from the system, and c is a right hand side vector. At the next time step, a vector lambda is calculated (of the same size as c) such that the forces applied to the bodies to preserve the joint constraint are
ODE adds a new twist. ODE's constraint equation has the form
where CFM is a square diagonal matrix. CFM mixes the resulting constraint force in with the constraint that produces it. A nonzero (positive) value of CFM allows the original constraint equation to be violated by an amount proportional to CFM times the restoring force lamda that is needed to enforce the constraint. Solving for lambda gives
Thus CFM simply adds to the diagonal of the original system matrix. Using a positive value of CFM has the additional benefit of taking the system away from any singularity and thus improving the factorizer accuracy.
ERP and CFM can be independently set in many joints. They can be set in contact joints, in joint limits and various other places, to control the spongyness and springyness of the joint (or joint limit).
If CFM is set to zero, the constraint will be hard. If CFM is set to a positive value, it will be possible to violate the constraint by ``pushing on it'' (for example, for contact constraints by forcing the two contacting objects together). In other words the constraint will be soft, and the softness will increase as CFM increases. What is actually happeneng here is that the constraint is allowed to be violated by an amount proportional to CFM times the restoring force that is needed to enforce the constraint. Note that setting CFM to a negative value can have undesirable bad effects, such as instability. Don't do it.
By adjusting the values of ERP and CFM, you can achieve various effects. For example you can simulate springy constraints, where the two bodies oscillate as though connected by springs. Or you can simulate more spongy constraints, without the oscillation. In fact, ERP and CFM can be selected to have the same effect as any desired spring and damper constants. If you have a spring constant kp and damping constant kd, then the corresponding ODE constants are:
where h is the stepsize. These values will give the same effect as a spring-and-damper system simulated with implicit first order integration.
Increasing CFM, especially the global CFM, can reduce the numerical errors in the simulation. If the system is near-singular, then this can markedly increase stability. In fact, if the system is mis-behaving, one of the first things to try is to increase the global CFM.
[There is a lot that needs to be written about collision handling.]
Collisions between bodies or between bodies and the static environment are handled as follows:
A typical simulation will proceed like this:
The various methods and approximations that are used in ODE are discussed here.
[We really need more pictures here.]
The Coulomb friction model is a simple, but effective way to model friction at contact points. It is a simple relationship between the normal and tangential forces present at a contact point (see the contact joint section for a description of these forces). The rule is:
where fN and fT are the normal and tangential force vectors respectively, and mu is the friction coefficient (typically a number around 1.0). This equation defines a "friction cone" - imagine a cone with fN as the axis and the contact point as the vertex. If the total friction force vector is within the cone then the contact is in "sticking mode", and the friction force is enough to prevent the contacting surfaces from moving with respect to each other. If the force vector is on the surface of the cone then the contact is in "sliding mode", and the friction force is typically not large enough to prevent the contacting surfaces from sliding. The parameter mu thus specifies the maximum ratio of tangential to normal force.
ODE's friction models are approximations to the friction cone, for reasons of efficiency. There are currently two approximations to chose from:
and then proceeds to solve for the entire system with these fixed limits (in a manner similar to approximation 1 above). This differs from a true friction pyramid in that the "effective" mu is not quite fixed. This approximation is easier to use as mu is a unitless ratio the same as the normal Coloumb friction coefficient, and thus can be set to a constant value around 1.0 without regard for the specific simulation.
The ODE library can be built to use either single or double precision floating point numbers. Single precision is faster and uses less memory, but the simulation will have more numerical error that can result in visible problems. You will get less accuracy and stability with single precision.
[must describe what factors influence accuracy and stability].
The floating point data type is dReal. Other commonly used types are dVector3, dVector4, dMatrix3, dMatrix4, dQuaternion.
There are various kinds of object that can be created:
All 3-vectors (x,y,z) supplied to ``set'' functions are given as individual x,y,z arguments.
All 3-vector result arguments to get() function are pointers to arrays of dReal.
Larger vectors are always supplied and returned as pointers to arrays of dReal.
All coordinates are in the global frame except where otherwise specified.
The ODE library is written in C++, but its public interface is made of simple C functions, not classes. Why is this?
The ODE library can be compiled in "debugging" or "release" mode. Debugging mode is slower, but function arguments are checked and many run-time tests are done to ensure internal consistency. Release mode is faster, but no checking is done.
The world object is a container for rigid bodies and joints. Objects in different worlds can not interact, for example rigid bodies from two different worlds can not collide.
All the objects in a world exist at the same point in time, thus one reason to use separate worlds is to simulate systems at different rates.
Most applications will only need one world.
dWorldID dWorldCreate();
Create a new, empty world and return its ID number.
void dWorldDestroy (dWorldID);
Destroy a world and everything in it. This includes all bodies, and all
joints that are not part of a joint group.
Joints that are part of a joint group will be deactivated, and can be
destroyed by calling, for example, dJointGroupEmpty().
void dWorldSetGravity (dWorldID, dReal x, dReal y, dReal z);
void dWorldGetGravity (dWorldID, dVector3 gravity);
Set and get the world's global gravity vector. The units are m/s/s, so Earth's
gravity vector would be (0,0,-9.81), assuming that +z is up.
The default is no gravity, i.e. (0,0,0).
void dWorldSetERP (dWorldID, dReal erp);
dReal dWorldGetERP (dWorldID);
Set and get the global ERP value, that controls how much error correction is
performed in each time step.
Typical values are in the range 0.1--0.8.
The default is 0.2.
void dWorldSetCFM (dWorldID, dReal cfm);
dReal dWorldGetCFM (dWorldID);
Set and get the global CFM (constraint force mixing) value.
Typical values are in the range 1e-9 -- 1.
The default is 1e-5 if single precision is being used, or 1e-10 if double
precision is being used.
void dWorldStep (dWorldID, dReal stepsize);
Step the world.
This function is given the desired impulse as (ix,iy,iz) and puts the force vector in force. The current algorithm simply scales the impulse by 1/stepsize, where stepsize is the step size for the next step that will be taken.
This function is given a dWorldID because, in the future, the force computation may depend on integrator parameters that are set as properties of the world.
void dCloseODE();
This deallocates some extra memory used by ODE that can not be deallocated
using the normal destroy functions, e.g. dWorldDestroy().
You can use this function at the end of your application to prevent
memory leak checkers from complaining about ODE.
void dBodySetPosition (dBodyID, dReal x, dReal y, dReal z);
void dBodySetRotation (dBodyID, const dMatrix3 R);
void dBodySetQuaternion (dBodyID, const dQuaternion q);
void dBodySetLinearVel (dBodyID, dReal x, dReal y, dReal z);
void dBodySetAngularVel (dBodyID, dReal x, dReal y, dReal z);
const dReal * dBodyGetPosition (dBodyID);
const dReal * dBodyGetRotation (dBodyID);
const dReal * dBodyGetQuaternion (dBodyID);
const dReal * dBodyGetLinearVel (dBodyID);
const dReal * dBodyGetAngularVel (dBodyID);
These functions set and get the position, rotation, linear and angular
velocity of the body.
After setting a group of bodies, the outcome of the simulation is undefined
if the new configuration is inconsistent with the joints/constraints that are
present.
When getting, the returned values are pointers to internal data structures,
so the vectors are valid until any changes are made to the rigid body system
structure.
Hmmm. dBodyGetRotation returns a 4x3 rotation matrix.
void dBodySetMass (dBodyID, const dMass *mass);
void dBodyGetMass (dBodyID, dMass *mass);
Set/get the mass of the body (see the mass functions).
void dBodyAddForce (dBodyID, dReal fx, dReal fy, dReal fz);
void dBodyAddTorque (dBodyID, dReal fx, dReal fy, dReal fz);
void dBodyAddRelForce (dBodyID, dReal fx, dReal fy, dReal fz);
void dBodyAddRelTorque (dBodyID, dReal fx, dReal fy, dReal fz);
void dBodyAddForceAtPos (dBodyID, dReal fx, dReal fy, dReal fz,
dReal px, dReal py, dReal pz);
void dBodyAddForceAtRelPos (dBodyID, dReal fx, dReal fy, dReal fz,
dReal px, dReal py, dReal pz);
void dBodyAddRelForceAtPos (dBodyID, dReal fx, dReal fy, dReal fz,
dReal px, dReal py, dReal pz);
void dBodyAddRelForceAtRelPos (dBodyID, dReal fx, dReal fy, dReal fz,
dReal px, dReal py, dReal pz);
Add forces to bodies (absolute or relative coordinates).
The forces are accumulated on to each body, and the accumulators are zeroed
after each time step.
The ...RelForce and ...RelTorque functions take force vectors that are relative to the body's own frame of reference.
The ...ForceAtPos and ...ForceAtRelPos functions take an extra position vector (in global or body-relative coordinates respectively) that specifies the point at which the force is applied. All other functions apply the force at the center of mass.
const dReal * dBodyGetForce (dBodyID);
const dReal * dBodyGetTorque (dBodyID);
Return the current accumulated force and torque vector.
The returned pointers point to an array of 3 dReals.
The returned values are pointers to internal data structures, so the vectors
are only valid until any changes are made to the rigid body system.
void dBodySetForce (dBodyID b, dReal x, dReal y, dReal z);
void dBodySetTorque (dBodyID b, dReal x, dReal y, dReal z);
Set the body force and torque accumulation vectors.
This is mostly useful to zero the force and torque for deactivated bodies
before they are reactivated, in the case where the force-adding functions
were called on them while they were deactivated.
void dBodyGetRelPointPos (dBodyID, dReal px, dReal py, dReal pz,
dVector3 result);
void dBodyGetRelPointVel (dBodyID, dReal px, dReal py, dReal pz,
dVector3 result);
void dBodyGetPointVel (dBodyID, dReal px, dReal py, dReal pz,
dVector3 result);
Utility functions that take a point on a body (px,py,pz) and
return that point's position or velocity in global coordinates
(in result).
The dBodyGetRelPointXXX functions are given the point in body
relative coordinates, and the dBodyGetPointVel function is given
the point in global coordinates.
void dBodyGetPosRelPoint (dBodyID, dReal px, dReal py, dReal pz,
dVector3 result);
This is the inverse of dBodyGetRelPointPos().
It takes a point in global coordinates (x,y,z) and returns
the point's position in body-relative coordinates (result).
void dBodyVectorToWorld (dBodyID, dReal px, dReal py, dReal pz,
dVector3 result);
void dBodyVectorFromWorld (dBodyID, dReal px, dReal py, dReal pz,
dVector3 result);
Given a vector expressed in the body (or world) coordinate system
(x,y,z), rotate it to the world (or body) coordinate system
(result).
void dBodySetData (dBodyID, void *data);
void *dBodyGetData (dBodyID);
Get and set the body's user-data pointer.
void dBodyEnable (dBodyID);
void dBodyDisable (dBodyID);
int dBodyIsEnabled (dBodyID);
Enable and disable a body.
Disabled bodies are effectively ``turned off'' and are not updated during a
simulation step.
However, if a disabled body is connected to an island containing one or more
enabled bodies then it will be re-enabled at the next simulation step.
dBodyIsEnabled() returns 1 if a body is enabled or 0 if it is disabled. New bodies are created in the enabled state.
void dBodySetFiniteRotationAxis (dBodyID, dReal x, dReal y, dReal z);
This sets the finite rotation axis for a body.
This is axis only has meaning when the finite rotation mode is set
(see dBodySetFiniteRotationMode()).
If this axis is zero (0,0,0), full finite rotations are performed on the body.
If this axis is nonzero, the body is rotated by performing a partial finite rotation along the axis direction followed by an infitesimal rotation along an orthogonal direction.
This can be useful to alleviate certain sources of error caused by quickly spinning bodies. For example, if a car wheel is rotating at high speed you can call this function with the wheel's hinge axis as the argument to try and improve its behavior.
int dBodyGetNumJoints (dBodyID b);
Return the number of joints that are attached to this body.
dJointID dBodyGetJoint (dBodyID, int index);
Return a joint attached to this body, given by index.
Valid indexes are 0 to n-1 where n is the value returned by
dBodyGetNumJoints().
void dBodySetGravityMode (dBodyID b, int mode);
int dBodyGetGravityMode (dBodyID b);
Set/get whether the body is influenced by the world's gravity or not.
If mode is nonzero it is, if mode is zero, it isn't.
Newly created bodies are always influenced by the world's gravity.
dJointID dJointCreateBall (dWorldID, dJointGroupID);
dJointID dJointCreateHinge (dWorldID, dJointGroupID);
dJointID dJointCreateSlider (dWorldID, dJointGroupID);
dJointID dJointCreateContact (dWorldID, dJointGroupID,
const dContact *);
dJointID dJointCreateUniversal (dWorldID, dJointGroupID);
dJointID dJointCreateHinge2 (dWorldID, dJointGroupID);
dJointID dJointCreateFixed (dWorldID, dJointGroupID);
dJointID dJointCreateAMotor (dWorldID, dJointGroupID);
Create a new joint of a given type.
The joint is initially in "limbo" (i.e. it has no effect on the simulation)
because it does not connect to any bodies.
The joint group ID is 0 to allocate the joint normally.
If it is nonzero the joint is allocated in the given joint group.
The contact joint will be initialized with the given dContact
structure.
Some joints, like hinge-2 need to be attached to two bodies to work.
void dJointSetData (dJointID, void *data);
void *dJointGetData (dJointID);
Get and set the joint's user-data pointer.
int dJointGetType (dJointID);
Get the joint's type. One of the following constants will be returned:
| dJointTypeBall | A ball-and-socket joint. |
| dJointTypeHinge | A hinge joint. |
| dJointTypeSlider | A slider joint. |
| dJointTypeContact | A contact joint. |
| dJointTypeUniversal | A universal joint. |
| dJointTypeHinge2 | A hinge-2 joint. |
| dJointTypeFixed | A fixed joint. |
| dJointTypeAMotor | An angular motor joint. |
void dJointSetFeedback (dJointID, dJointFeedback *);
dJointFeedback *dJointGetFeedback (dJointID);
During the world time step, the forces that are applied by each joint are
computed. These forces are added directly to the joined bodies, and the user
normally has no way of telling which joint contributed how much force.
If this information is desired then the user can allocate a dJointFeedback structure and pass its pointer to the dJointSetFeedback() function. The feedback information structure is defined as follows:
typedef struct dJointFeedback {
dVector3 f1; // force that joint applies to body 1
dVector3 t1; // torque that joint applies to body 1
dVector3 f2; // force that joint applies to body 2
dVector3 t2; // torque that joint applies to body 2
} dJointFeedback;
|
During the time step any feedback structures that are attached to joints will be filled in with the joint's force and torque information. The dJointGetFeedback() function returns the current feedback structure pointer, or 0 if none is used (this is the default). dJointSetFeedback() can be passed 0 to disable feedback for that joint.
Now for some API design notes. It might seem strange to require that users perform the allocation of these structures. Why not just store the data statically in each joint? The reason is that not all users will use the feedback information, and even when it is used not all joints will need it. It will waste memory to store it statically, especially as this structure could grow to store a lot of extra information in the future.
Why not have ODE allocate the structure itself, at the user's request? The reason is that contact joints (which are created and destroyed every time step) would require a lot of time to be spent in memory allocation if feedback is required. Letting the user do the allocation means that a better allocation strategy can be provided, e.g simply allocating them out of a fixed array.
The alternative to this API is to have a joint-force callback. This would work of course, but it has a few problems. First, callbacks tend to pollute APIs and sometimes require the user to go through unnatural contortions to get the data to the right place. Second, this would expose ODE to being changed in the middle of a step (which would have bad consequences), and there would have to be some kind of guard against this or a debugging check for it - which would complicate things.
A ball and socket joint looks like this:

void dJointGetBallAnchor2 (dJointID, dVector3 result);
Get the joint anchor point, in world coordinates. This returns the point on
body 2. You can think of a ball and socket joint as trying to keep the result
of dJointGetBallAnchor() and dJointGetBallAnchor2() the same. If the joint is
perfectly satisfied, this function will return the same value
as dJointGetBallAnchor() to within roundoff errors. dJointGetBallAnchor2()
can be used, along with dJointGetBallAnchor(), to see how far the joint has come apart.
A hinge joint looks like this:

void dJointSetHingeAnchor (dJointID, dReal x, dReal y, dReal z);
void dJointSetHingeAxis (dJointID, dReal x, dReal y, dReal z);
Set hinge anchor and axis parameters.
void dJointGetHingeAnchor2 (dJointID, dVector3 result);
Get the joint anchor point, in world coordinates. This returns the point on
body 2. If the joint is perfectly satisfied, this will return the same value
as dJointGetHingeAnchor(). If not, this value will be slightly different.
This can be used, for example, to see how far the joint has come apart.
void dJointGetHingeAxis (dJointID, dVector3 result);
Get hinge axis parameter.
dReal dJointGetHingeAngle (dJointID);
dReal dJointGetHingeAngleRate (dJointID);
Get the hinge angle and the time derivative of this value.
The angle is measured between the two bodies, or between the body and
the static environment.
The angle will be between -pi..pi.
When the hinge anchor or axis is set, the current position of the attached bodies is examined and that position will be the zero angle.
A slider joint looks like this:

void dJointSetSliderAxis (dJointID, dReal x, dReal y, dReal z);
Set the slider axis parameter.
void dJointGetSliderAxis (dJointID, dVector3 result);
Get the slider axis parameter.
dReal dJointGetSliderPosition (dJointID);
dReal dJointGetSliderPositionRate (dJointID);
Get the slider linear position (i.e. the slider's ``extension'') and the time
derivative of this value.
When the axis is set, the current position of the attached bodies is examined and that position will be the zero position.
A universal joint looks like this:

A universal joint is like a ball and socket joint that constrains an extra degree of rotational freedom. Given axis 1 on body 1, and axis 2 on body 2 that is perpendicular to axis 1, it keeps them perpendicular. In other words, rotation of the two bodies about the direction perpendicular to the two axes will be equal.
In the picture, the two bodies are joined together by a cross. Axis 1 is attached to body 1, and axis 2 is attached to body 2. The cross keeps these axes at 90 degrees, so if you grab body 1 and twist it, body 2 will twist as well.
Universal joints show up in cars, where the engine causes a shaft, the drive shaft, to rotate along its own axis. At some point you'd like to change the direction of the shaft. The problem is, if you just bend the shaft, then the part after the bend won't rotate about its own axis. So if you cut it at the bend location and insert a universal joint, you can use the constraint to force the second shaft to rotate about the same angle as the first shaft.
Another use of this joint is to attach the arms of a simple virtual creature to its body. Imagine a person holding their arms straight out. You may want the arm to be able to move up and down, and forward and back, but not to rotate about its own axis.
Here are the universal joint functions:
void dJointSetUniversalAnchor (dJointID, dReal x, dReal y, dReal z);
void dJointSetUniversalAxis1 (dJointID, dReal x, dReal y, dReal z);
void dJointSetUniversalAxis2 (dJointID, dReal x, dReal y, dReal z);
Set universal anchor and axis parameters.
Axis 1 and axis 2 should be perpendicular to each other.
void dJointGetUniversalAnchor2 (dJointID, dVector3 result);
Get the joint anchor point, in world coordinates. This returns the point on
body 2. You can think of the ball and socket part of a universal joint as
trying to keep the result of dJointGetBallAnchor() and dJointGetBallAnchor2()
the same. If the joint is
perfectly satisfied, this function will return the same value
as dJointGetUniversalAnchor() to within roundoff errors. dJointGetUniveralAnchor2()
can be used, along with dJointGetUniversalAnchor(), to see how far the joint has come apart.
void dJointGetUniversalAxis1 (dJointID, dVector3 result);
void dJointGetUniversalAxis2 (dJointID, dVector3 result);
Get univeral axis parameters.
A hinge-2 joint looks like this:

The hinge-2 joint is the same as two hinges connected in series, with different hinge axes. An example, shown in the above picture is the steering wheel of a car, where one axis allows the wheel to be steered and the other axis allows the wheel to rotate.
The hinge-2 joint has an anchor point and two hinge axes. Axis 1 is specified relative to body 1 (this would be the steering axis if body 1 is the chassis). Axis 2 is specified relative to body 2 (this would be the wheel axis if body 2 is the wheel).
Axis 1 can have joint limits and a motor, axis 2 can only have a motor.
Axis 1 can function as a suspension axis, i.e. the constraint can be compressible along that axis.
void dJointSetHinge2Anchor (dJointID, dReal x, dReal y, dReal z);
void dJointSetHinge2Axis1 (dJointID, dReal x, dReal y, dReal z);
void dJointSetHinge2Axis2 (dJointID, dReal x, dReal y, dReal z);
Set hinge-2 anchor and axis parameters.
Axis 1 and axis 2 must not lie along the same line.
void dJointGetHinge2Anchor2 (dJointID, dVector3 result);
Get the joint anchor point, in world coordinates. This returns the point on
body 2. If the joint is perfectly satisfied, this will return the same value
as dJointGetHinge2Anchor(). If not, this value will be slightly different.
This can be used, for example, to see how far the joint has come apart.
void dJointGetHinge2Axis1 (dJointID, dVector3 result);
void dJointGetHinge2Axis2 (dJointID, dVector3 result);
Get hinge-2 axis parameters.
dReal dJointGetHinge2Angle1 (dJointID);
dReal dJointGetHinge2Angle1Rate (dJointID);
dReal dJointGetHinge2Angle2Rate (dJointID);
Get the hinge-2 angles (around axis 1 and axis 2) and the time derivatives
of these values.
When the anchor or axis is set, the current position of the attached bodies is examined and that position will be the zero angle.
The fixed joint maintains a fixed relative position and orientation between two bodies, or between a body and the static environment. Using this joint is almost never a good idea in practice, except when debugging. If you need two bodies to be glued together it is better to represent that as a single body.
Currently the fixed joint does not support a non-identity relative rotation between two bodies, it only supports a relative offset.
A contact joint looks like this:

The contact joint prevents body 1 and body 2 from inter-penetrating at the contact point. It does this by only allowing the bodies to have an ``outgoing'' velocity in the direction of the contact normal. Contact joints typically have a lifetime of one time step. They are created and deleted in response to collision detection.
Contact joints can simulate friction at the contact by applying special forces in the two friction directions that are perpendicular to the normal.
When a contact joint is created, a dContact structure must be supplied. This has the following definition:
struct dContact {
dSurfaceParameters surface;
dContactGeom geom;
dVector3 fdir1;
};
|
fdir1 is a "first friction direction" vector that defines a direction along which frictional force is applied. It must be of unit length and perpendicular to the contact normal (so it is typically tangential to the contact surface). It should only be defined if the dContactFDir1 flag is set in surface.mode. The "second friction direction" is a vector computed to be perpendicular to both the contact normal and fdir1.
surface is a substructure that is set by the user. Its members define the properties of the colliding surfaces. It has the following members:
| dContactMu2 | If not set, use mu for both friction directions. If set, use mu for friction direction 1, use mu2 for friction direction 2. |
| dContactFDir1 | If set, take fdir1 as friction direction 1, otherwise automatically compute friction direction 1 to be perpendicular to the contact normal (in which case its resulting orientation is unpredictable). |
| dContactBounce | If set, the contact surface is bouncy, in other words the bodies will bounce off each other. The exact amount of bouncyness is controlled by the bounce parameter. |
| dContactSoftERP | If set, the error reduction parameter of the contact normal can be set with the soft_erp parameter. This is useful to make surfaces soft. |
| dContactSoftCFM | If set, the constraint force mixing parameter of the contact normal can be set with the soft_cfm parameter. This is useful to make surfaces soft. |
| dContactMotion1 | If set, the contact surface is assumed to be moving independently of the motion of the bodies. This is kind of like a conveyor belt running over the surface. When this flag is set, motion1 defines the surface velocity in friction direction 1. |
| dContactMotion2 | The same thing as above, but for friction direction 2. |
| dContactSlip1 | Force-dependent-slip (FDS) in friction direction 1. |
| dContactSlip2 | Force-dependent-slip (FDS) in friction direction 2. |
| dContactApprox1_1 | Use the friction pyramid approximation for friction direction 1. If this is not specified then the constant-force-limit approximation is used (and mu is a force limit). |
| dContactApprox1_2 | Use the friction pyramid approximation for friction direction 2. If this is not specified then the constant-force-limit approximation is used (and mu is a force limit). |
| dContactApprox1 | Equivalent to both dContactApprox1_1 and dContactApprox1_2. |
FDS is an effect that causes the contacting surfaces to side past each other with a velocity that is proportional to the force that is being applied tangentially to that surface.
Consider a contact point where the coefficient of friction mu is infinite. Normally, if a force f is applied to the two contacting surfaces, to try and get them to slide past each other, they will not move. However, if the FDS coefficient is set to a positive value k then the surfaces will slide past each other, building up to a steady velocity of k*f relative to each other.
Note that this is quite different from normal frictional effects: the force does not cause a constant acceleration of the surfaces relative to each other - it causes a brief acceleration to achieve the steady velocity.
This is useful for modeling some situations, in particular tires. For example consider a car at rest on a road. Pushing the car in its direction of travel will cause it to start moving (i.e. the tires will start rolling). Pushing the car in the perpendicular direction will have no effect, as the tires do not roll in that direction. However - if the car is moving at a velocity v, applying a force f in the perpendicular direction will cause the tires to slip on the road with a velocity proportional to f*v (Yes, this really happens).
To model this in ODE set the tire-road contact parameters as follows: set friction direction 1 in the direction that the tire is rolling in, and set the FDS slip coefficient in friction direction 2 to k*v, where v is the tire rolling velocity and k is a tire parameter that you can chose based on experimentation.
Note that FDS is quite separate from the sticking/slipping effects of Coulomb friction - both modes can be used together at a single contact point.
An angular motor (AMotor) allows the relative angular velocities of two bodies to be controlled. The angular velocity can be controlled on up to three axes, allowing torque motors and stops to be set for rotation about those axes (see the ``Stops and motor parameters'' section below). This is mainly useful in conjunction will ball joints (which do not constrain the angular degrees of freedom at all), but it can be used in any situation where angular control is needed. To use an AMotor with a ball joint, simply attach it to the same two bodies that the ball joint is attached to.
The AMotor can be used in different modes. In dAMotorUser mode, the user directly sets the axes that the AMotor controls. In dAMotorEuler mode, AMotor computes the euler angles corresponding to the relative rotation, allowing euler angle torque motors and stops to be set. An AMotor joint with euler angles looks like this:

In this diagram, a0, a1 and a2 are the three axes along which angular motion is controlled. The green axes (including a0) are anchored to body 1. The blue axes (including a2) are anchored to body 2. To get the body 2 axes from the body 1 axes the following sequence of rotations is performed:
There is an important restriction when using euler angles: the theta1 angle must not be allowed to get outside the range -pi/2 ... pi/2. If this happens then the AMotor joint will become unstable (there is a singularity at +/- pi/2). Thus you must set the appropriate stops on axis number 1.
void dJointSetAMotorMode (dJointID, int mode);
int dJointGetAMotorMode (dJointID);
Set (and get) the angular motor mode. The mode parameter must be one
of the following constants:
| dAMotorUser | The AMotor axes and joint angle settings are entirely controlled by the user. This is the default mode. |
| dAMotorEuler | Euler angles are automatically computed. The axis a1 is also automatically computed. The AMotor axes must be set correctly when in this mode, as described below. When this mode is initially set the current relative orientations of the bodies will correspond to all euler angles at zero. |
void dJointSetAMotorNumAxes (dJointID, int num);
int dJointGetAMotorNumAxes (dJointID);
Set (and get) the number of angular axes that will be controlled by the
AMotor.
The argument num can range from 0 (which effectively deactivates the
joint) to 3.
This is automatically set to 3 in dAMotorEuler mode.
void dJointSetAMotorAxis (dJointID, int anum, int rel,
dReal x, dReal y, dReal z);
void dJointGetAMotorAxis (dJointID, int anum, dVector3 result);
int dJointGetAMotorAxisRel (dJointID, int anum);
Set (and get) the AMotor axes.
The anum argument selects the axis to change (0,1 or 2).
Each axis can have one of three ``relative orientation'' modes, selected by
rel:
The axis vector (x,y,z) is always specified in global
coordinates regardless of the setting of rel.
There are two GetAMotorAxis functions, one to return the axis and one to
return the relative mode.
For dAMotorEuler mode:
dReal dJointGetAMotorAngle (dJointID, int anum);
Return the current angle for axis anum.
In dAMotorUser mode this is simply the value that was set with
dJointSetAMotorAngle.
In dAMotorEuler mode this is the corresponding euler angle.
The joint geometry parameter setting functions should only be called after the joint has been attached to bodies, and those bodies have been correctly positioned, otherwise the joint may not be initialized correctly. If the joint is not already attached, these functions will do nothing.
For the parameter getting functions, if the system is out of alignment (i.e. there is some joint error) then the anchor/axis values will be correct with respect to body 1 only (or body 2 if you specified body 1 as zero in the dJointAttach() function).
The default anchor for all joints is (0,0,0). The default axis for all joints is (1,0,0).
When an axis is set it will be normalized to unit length. The adjusted axis is what the axis getting functions will return.
When measuring a joint angle or position, a value of zero corresponds to the initial position of the bodies relative to each other.
Note that there are no functions to set joint angles or positions (or their rates) directly, instead you must set the corresponding body positions and velocities.
When a joint is first created there is nothing to prevent it from moving through its entire range of motion. For example a hinge will be able to move through its entire angle, and a slider will slide to any length.
This range of motion can be limited by setting stops on the joint. The joint angle (or position) will be prevented from going below the low stop value, or from going above the high stop value. Note that a joint angle (or position) of zero corresponds to the initial body positions.
As well as stops, many joint types can have motors. A motor applies a torque (or force) to a joint's degree(s) of freedom to get it to pivot (or slide) at a desired speed. Motors have force limits, which means they can apply no more than a given maximum force/torque to the joint.
Motors have two parameters: a desired speed, and the maximum force that is available to reach that speed. This is a very simple model of real life motors, engines or servos. However, is it quite useful when modeling a motor (or engine or servo) that is geared down with a gearbox before being connected to the joint. Such devices are often controlled by setting a desired speed, and can only generate a maximum amount of power to achieve that speed (which corresponds to a certain amount of force available at the joint).
Motors can also be used to accurately model dry (or Coulomb) friction in joints. Simply set the desired velocity to zero and set the maximum force to some constant value - then all joint motion will be impeded by that force.
The alternative to using joint stops and motors is to simply apply forces to the affected bodies yourself. Applying motor forces is easy, and joint stops can be emulated with restraining spring forces. However applying forces directly is often not a good approach and can lead to severe stability problems if it is not done carefully.
Consider the case of applying a force to a body to achieve a desired velocity. To calculate this force you use information about the current velocity, something like this:
This has several problems. First, the parameter k must be tuned by hand. If it is too low the body will take a long time to come up to speed. If it is too high the simulation will become unstable. Second, even if k is chosen well the body will still take a few time steps to come up to speed. Third, if any other ``external'' forces are being applied to the body, the desired velocity may never even be reached (a more complicated force equation would be needed, which would have extra parameters and its own problems).
Joint motors solve all these problems: they bring the body up to speed in one time step, provided that does not take more force than is allowed. Joint motors need no extra parameters because they are actually implemented as constraints. They can effectively see one time step into the future to work out the correct force. This makes joint motors more computationally expensive than computing the forces yourself, but they are much more robust and stable, and far less time consuming to design with. This is especially true with larger rigid body systems.
Similar arguments apply to joint stops.
Here are the functions that set stop and motor parameters (as well as other kinds of parameters) on a joint:
void dJointSetHingeParam (dJointID, int parameter, dReal value);
void dJointSetSliderParam (dJointID, int parameter, dReal value);
void dJointSetHinge2Param (dJointID, int parameter, dReal value);
void dJointSetAMotorParam (dJointID, int parameter, dReal value);
dReal dJointGetHingeParam (dJointID, int parameter);
dReal dJointGetSliderParam (dJointID, int parameter);
dReal dJointGetHinge2Param (dJointID, int parameter);
dReal dJointGetAMotorParam (dJointID, int parameter);
Set/get limit/motor parameters for each joint type.
The parameter numbers are:
| dParamLoStop | Low stop angle or position. Setting this to -dInfinity (the default value) turns off the low stop. For rotational joints, this stop must be greater than -Pi to be effective. |
| dParamHiStop | High stop angle or position. Setting this to dInfinity (the default value) turns off the high stop. For rotational joints, this stop must be less than Pi to be effective. If the high stop is less than the low stop then both stops will be ineffective. |
| dParamVel | Desired motor velocity (this will be an angular or linear velocity). |
| dParamFMax | The maximum force or torque that the motor will use to achieve the desired velocity. This must always be greater than or equal to zero. Setting this to zero (the default value) turns off the motor. |
| dParamFudgeFactor | The current joint stop/motor implementation has a small problem: when the joint is at one stop and the motor is set to move it away from the stop, too much force may be applied for one time step, causing a ``jumping'' motion. This fudge factor is used to scale this excess force. It should have a value between zero and one (the default value). If the jumping motion is too visible in a joint, the value can be reduced. Making this value too small can prevent the motor from being able to move the joint away from a stop. |
| dParamBounce | The bouncyness of the stops. This is a restitution parameter in the range 0..1. 0 means the stops are not bouncy at all, 1 means maximum bouncyness. |
| dParamCFM | The constraint force mixing (CFM) value used when not at a stop. |
| dParamStopERP | The error reduction parameter (ERP) used by the stops. |
| dParamStopCFM | The constraint force mixing (CFM) value used by the stops. Together with the ERP value this can be used to get spongy or soft stops. Note that this is intended for unpowered joints, it does not really work as expected when a powered joint reaches its limit. |
| dParamSuspensionERP | Suspension error reduction parameter (ERP). Currently this is only implemented on the hinge-2 joint. |
| dParamSuspensionCFM | Suspension constraint force mixing (CFM) value. Currently this is only implemented on the hinge-2 joint. |
If a particular parameter is not implemented by a given joint, setting it will have no effect.
These parameter names can be optionally followed by a digit (2 or 3) to indicate the second or third set of parameters, e.g. for the second axis in a hinge-2 joint, or the third axis in an AMotor joint. A constant dParamGroup is also defined such that:
ODE's dWorldStep function currently uses a "big matrix" method to step the system. For some large systems this can be slow and can require a lot of memory. The StepFast1 algorithm provides an alternative way to step the system, that sacrifices some accuracy for a big gain in speed and memory. To use it, you simply call dWorldStepFast1 instead of dWorldStep.
The chart below illustrates this speed advantage over the standard dWorldStep algorithm:

The graph relates the number of Degrees Of Freedom (DOFs) removed from a system to the running time of the step. You may be able to tell that the dWorldStep algorithm's running time is proportional to the cube of the number of DOF's removed. The StepFast1 algorithm, however, is roughly linear. So as islands increase in size (for example, when there is a large pile-up of cars, a pile of "ragdoll corpses", or a wall of bricks) the StepFast1 algorithm scales better than dWorldStep. All this means that your application is more likely to keep a steady framerate, even in the worst case scenario.
The graph of DOFs removed to memory looks quite similar.

dWorldStep requires memory proportional only to the square of the number of DOF's removed. StepFast1, though, is still linear, but it has nothing to do with the number of iterations per step. So this means the dreaded "There was a big pile-up and ODE crashed without an error message" problems (usually stack overflows) won't happen with StepFast1. Or at least that you'll be rendering at a minute per frame or slower before they do.
As shown above, StepFast1 is quite good when it comes to speed and memory usage. All this power doesn't come for free, though; all optimizations are a trade-off of one kind or another. I've already mentioned that StepFast1 trades off accuracy for it's speed and memory advantages. You actually get to choose just how much accuracy you give away though, at the cost of speed, by adjusting the number of iterations per step. Though you may never reach the accuracy of dWorldStep (or you may surpass it, depending on the type of inaccuracy), you can be almost certain that a larger number of iterations will give you more accurate results (more slowly). So StepFast1 can be used in a good variety of situations.
The general answer to this question then, is: use StepFast1 when you don't mind having a few more parameters to play with to get the system stable, and you want to take advantage of it's speed or memory advantages. If you find yourself running into situations in your simulation where large numbers of bodies come in contact, and dWorldStep becomes too slow, try switching to StepFast1. Many systems will work just fine with nothing more than changing the dWorldStep function call to dWorldStepFast1. Others will require a little tweaking to get them to work well with StepFast1, usually in the masses of the bodies. When a joint connects two bodies with a large mass ratio (i.e. one body has several times the mass of the other body) StepFast1 may have trouble solving it.
Another prospect for StepFast1 is designing for it from the ground up. If you know you are going to build large worlds with many physically based objects in them, then go ahead and plan to use StepFast1. Noting the mass ratio problem above, you might want to consider making the mass of every body in your system equal to 1.0. Or in a very small range, for example between 0.5 and 1.5. Most of the other suggestions for speed and stability apply to StepFast1, except that the object is no longer to remove as many joints as possible from the equation. It can likely be shown that you will get a better performance to stability ratio by spreading out mass among several bodies connected by fixed joints rather than trying to implement it as one massive body, especially if that one massive body means you have to switch back to dWorldStep to keep things stable.
A final prospect for StepFast1 is to use it only when you need to. Since StepFast1 uses the body and world structures in exactly the same way as dWorldStep, you can actually switch back and forth between the two solvers at will. A good heuristic for when to make this switch is to simply count contact joints while you are running the collision detection. Since collision detection is normally called before the step, using this method will ensure that the large island that would slow you down is never sent to the dWorldStep solver (as opposed to waiting until after you've already taken a step at 1 fps...). The only better solution would be a hybrid island creation function, that sends small islands to dWorldStep, and large islands to dWorldStepFast1. This may make it in the source at some point in the future.
Though there are several specific situations when it as advisable not to use StepFast1, I believe they can all be summed up in a single statement: Don't use StepFast1 when accuracy is more important than speed or memory to you. You may still want to evaluate it in this case and see if the inaccuracies are even noticeable, perhaps with a relatively large number of iterations (20+).
For any interested parties out there, here's a quick rundown of how the StepFast1 algorithm works. The general problem that ODE tries to solve is a system of linear (in)equalities in (m = constraints) unknowns, where one constraint constrains 1 Degree of Freedom in one joint. For large islands of bodies with many joints between them, this can take a rather large O(m2) array, which takes O(m3) time to solve. StepFast1 completely avoids creating the large matrix by making an assumption: at relatively small timesteps, the effect of any given joint is so localized that it can be calculated without respect to any other joint in the system, and any conflicting joints will cancel each other out before the body actually moves. So StepFast1 uses the same solution method (LCP) to solve the same problem, only localized to a single joint (where m <= 6). It gets away with this by sub-dividing the timestep and repeating the process over really small timesteps (i = maxiterations) times. So the running time of StepFast1 is "roughly" O(m i). It's really closer to O(j i (m/j)3) = O(m i (m/j)2), where j = joints, but (m/j) is never > 6, so (m/j)2 is factored out as a constant.
Several experimental functions have been added to ODE as part of the StepFast1 flow of code, at least until they are validated. Most have to do with the automatic disabling and enabling of bodies as yet another bit of optimization. Here's the general idea:
void dWorldSetAutoEnableDepthSF1(dWorldID, int autoEnableDepth);
int dWorldGetAutoEnableDepthSF1(dWorldID);
Set and get the AutoEnableDepth parameter used by the StepFast1 algorithm.
void dBodySetAutoDisableThresholdSF1(dBodyID, dReal autoDisableThreshold);
dReal dBodyGetAutoDisableThresholdSF1(dBodyID);
Set and get the per-body AutoDisableThreshold parameter used by the
StepFast1 algorithm.
void dBodySetAutoDisableStepsSF1(dBodyID, int AutoDisableSteps);
int dBodyGetAutoDisableStepsSF1(dBodyID);
Set and get the per-body AutoDisableSteps parameter used by the StepFast1
algorithm.
void dBodySetAutoDisableSF1(dBodyID, int doAutoDisable);
int dBodyGetAutoDisableSF1(dBodyID);
Set and get the per-body AutoDisable flag used by the StepFast1 algorithm.
If doAutoDisable is nonzero, auto-disabling is enabled.
If doAutoDisable is zero, auto-disabling is disabled.
Rigid body orientations are represented with quaternions. A quaternion is four numbers [cos(theta/2) sin(theta/2)*u] where theta is a rotation angle and `u' is a unit length rotation axis.
Every rigid body also has a 3x3 rotation matrix that is derived from the quaternion. The rotation matrix and the quaternion always match.
Some information about quaternions:
The following are utility functions for dealing with rotation matrices and quaternions.
void dRSetIdentity (dMatrix3 R);
Set R to the identity matrix (i.e. no rotation).
void dQSetIdentity (dQuaternion q);
Set q to the identity rotation (i.e. no rotation).
void dQMultiply0 (dQuaternion qa,
const dQuaternion qb, const dQuaternion qc);
void dQMultiply1 (dQuaternion qa,
const dQuaternion qb, const dQuaternion qc);
void dQMultiply2 (dQuaternion qa,
const dQuaternion qb, const dQuaternion qc);
void dQMultiply3 (dQuaternion qa,
const dQuaternion qb, const dQuaternion qc);
Set qa = qb*qc.
This is that same as qa = rotation qc followed by rotation
qb.
The 0/1/2 versions are analogous to the multiply functions, i.e. 1 uses the
inverse of qb, and 2 uses the inverse of qc.
Option 3 uses the inverse of both.
void dQtoR (const dQuaternion q, dMatrix3 R);
Convert quaternion q to rotation matrix R.
void dRtoQ (const dMatrix3 R, dQuaternion q);
Convert rotation matrix R to quaternion q.
The mass parameters of a rigid body are described by a dMass structure:
typedef struct dMass {
dReal mass; // total mass of the rigid body
dVector4 c; // center of gravity position in body frame (x,y,z)
dMatrix3 I; // 3x3 inertia tensor in body frame, about POR
} dMass;
|
The following functions operate on this structure:
void dMassSetZero (dMass *);
Set all the mass parameters to zero.
void dMassSetSphere (dMass *, dReal density, dReal radius);
void dMassSetSphereTotal (dMass *, dReal total_mass, dReal radius);
Set the mass parameters to represent a sphere of the given radius and
density, with the center of mass at (0,0,0) relative to the body. The
first function accepts the density of the sphere, the second accepts
the total mass of the sphere.
void dMassSetCappedCylinder (dMass *, dReal density, int direction,
dReal radius, dReal length);
void dMassSetCappedCylinderTotal (dMass *, dReal total_mass, int direction,
dReal radius, dReal length);
Set the mass parameters to represent a capped cylinder of the given parameters
and density, with the center of mass at (0,0,0) relative to the body.
The radius of the cylinder (and the spherical cap) is radius.
The length of the cylinder (not counting the spherical cap) is length.
The cylinder's long axis is oriented along the body's x, y or z axis according
to the value of direction (1=x, 2=y, 3=z). The
first function accepts the density of the object, the second accepts
its total mass.
void dMassSetCylinder (dMass *, dReal density, int direction,
dReal radius, dReal length);
void dMassSetCylinderTotal (dMass *, dReal total_mass, int direction,
dReal radius, dReal length);
Set the mass parameters to represent a flat-ended cylinder of the given
parameters and density, with the center of mass at (0,0,0) relative to the
body.
The radius of the cylinder is radius.
The length of the cylinder is length.
The cylinder's long axis is oriented along the body's x, y or z axis according
to the value of direction (1=x, 2=y, 3=z). The
first function accepts the density of the object, the second accepts
its total mass.
void dMassSetBox (dMass *, dReal density,
dReal lx, dReal ly, dReal lz);
void dMassSetBoxTotal (dMass *, dReal total_mass,
dReal lx, dReal ly, dReal lz);
Set the mass parameters to represent a box of the given dimensions
and density, with the center of mass at (0,0,0) relative to the body.
The side lengths of the box along the x, y and z axes are lx, ly
and lz. The
first function accepts the density of the object, the second accepts
its total mass.