Rigid Body Dynamics
In this chapter we cover a number of topics that are also important to understand once you are comfortable with setting up a basic rigid body simulation world.
Applying Forces and Torques
The most physics-friendly way to interact with a body is to apply a force to it. In classical mechanics, most interactions between bodies are typically solved by using forces. Because of the law:
f = m*a (force = mass * acceleration)
Forces directly control a body's acceleration, but its velocity and position only indirectly. For this reason control by force may be inconvenient if you need immediate response. The advantage of forces is that regardless of what forces you apply to the bodies in the scene, the simulation will be able to keep all the defined constraints (joints and contacts) satisfied. For example gravity works by applying a force to bodies.
Unfortunately applying large forces to articulated bodies at the resonant frequency of a system may lead to ever increasing velocities, and eventually to the failure of the solver to maintain the joint constraints. This is not unlike a real world system, where the joints would ultimately break.
The forces acting on a body are accumulated before each simulation frame, applied to the simulation, and then reset to zero in preparation for the next frame. The relevant methods of PxRigidBody and PxRigidBodyExt are listed below. Please refer to the API reference for more detail:
void PxRigidBody::addForce(const PxVec3& force, PxForceMode::Enum mode, bool autowake); void PxRigidBody::addTorque(const PxVec3& torque, PxForceMode::Enum mode, bool autowake); void PxRigidBodyExt::addForceAtPos(PxRigidBody& body, const PxVec3& force, const PxVec3& pos, PxForceMode::Enum mode, bool wakeup); void PxRigidBodyExt::addForceAtLocalPos(PxRigidBody& body, const PxVec3& force, const PxVec3& pos, PxForceMode::Enum mode, bool wakeup); void PxRigidBodyExt::addLocalForceAtPos(PxRigidBody& body, const PxVec3& force, const PxVec3& pos, PxForceMode::Enum mode, bool wakeup); void PxRigidBodyExt::addLocalForceAtLocalPos(PxRigidBody& body, const PxVec3& force, const PxVec3& pos, PxForceMode::Enum mode, bool wakeup);
The PxForceMode member defaults to PxForceMode::eFORCE to apply simple forces. There are other possibilities. For example PxForceMode::eIMPULSE will apply an impulsive force. PxForceMode::eVELOCITY_CHANGE will do the same, but also ignore the mass of the body, effectively leading to an instantaneous velocity change. See the API documentation of PxForceMode for the other possibilities.
The methods in PxRigidBodyExt support only the force modes eFORCE and eIMPULSE.
Gravity is such a common force in simulations that PhysX makes it particularly simple to apply. For a scene-wide gravity effect, or any other uniform force field, set the PxScene class' gravity vector using PxScene::setGravity().
The parameter is the acceleration due to gravity. In meters and seconds, this works out to have a magnitude of about 9.8 on earth, and should point downwards. The force that will be applied at the center of mass of each body in the scene is this acceleration vector times the actor's mass.
Certain special effects can require that some dynamic actors are not influenced by gravity. To specify this set the flag:
Be careful when changing gravity (or enabling/disabling it) during the simulation. For performance reasons the change will not wake up sleeping actors automatically. Thus it may be necessary to iterate through all actors and call PxRigidDynamic::wakeUp() manually.
Setting the Velocity
To immediately get a body moving in a certain direction, set its velocity:
void PxRigidBody::setLinearVelocity(const PxVec3& linVel, bool autowake); void PxRigidBody::setAngularVelocity(const PxVec3& angVel, bool autowake);
During simulation, PhysX will modify the velocity of an object in accordance with gravity and other applied forces, and override the velocity if it is in conflict with a joint or a collision constraint. For example, if a ball is resting on a table and has a downward velocity, this will be clamped to 0. If you set the velocity of a chain link that is jointed to a bunch of other chain links, that velocity will be diminished, and the other chain links' velocity will be increased, to permit the chain to stay together.
Sometimes controlling an actor using forces or constraints is not sufficiently robust, precise or flexible. For example moving platforms or character controllers often need to manipulate an actor's position or have it exactly follow a specific path. Such a control scheme is provided by kinematic actors.
A kinematic actor is controlled using the PxRigidDynamic::setKinematicTarget() function. Each simulation step PhysX moves the actor to its target position, regardless of external forces, gravity, collision, etc. Thus one must continually call setKinematicTarget(), every time step, for each kinematic actor, to make them move along their desired paths. The movement of a kinematic actor affects dynamic actors with which it collides or to which it is constrained with a joint. The actor will appear to have infinite mass and will push regular dynamic actors out of the way.
To create a kinematic actor, simply create a regular dynamic actor then set its kinematic flag:
Use the same function to transform a kinematic actor back to a regular dynamic actor. While you do need to provide a mass for the kinematic actor as for all dynamic actors, this mass will not actually be used for anything while the actor is in kinematic mode.
- It is important to understand the difference between PxRigidDynamic::setKinematicTarget() and PxRigidActor::setGlobalPose() here. While setGlobalPose() would also move the actor to the desired position, it would not make that actor properly interact with other objects. In particular, with setGlobalPose() the kinematic actor would not push away other dynamic actors in its path, instead it would go right through them. The setGlobalPose() function can still be used though, if one simply wants to teleport a kinematic actor to a new position.
- A kinematic actor can push away dynamic objects, but nothing pushes it back. As a result, a kinematic can easily squish a dynamic actor against a static actor, or against another kinematic actor. As a result, the squished dynamic object can deeply penetrate the geometry it has been pushed into.
- There is no interaction or collision between kinematic actors and static actors. However, it is possible to request contact information for these cases if PxSceneFlag::eENABLE_KINEMATIC_PAIRS or ::eENABLE_KINEMATIC_STATIC_PAIRS gets set.
When an actor does not move for a period of time, it is assumed that it will not move in the future either until some external force acts on it that throws it out of equilibrium. Until then it is no longer simulated in order to save resources. This state is called sleeping. You can query an actor's sleep state with the following method:
bool PxRigidDynamic::isSleeping() const;
It is however often more convenient to listen for events that the SDK sends when actors fall asleep or wake up. To receive the following events, PxActorFlag::eSEND_SLEEP_NOTIFIES must be set for the actor:
void PxSimulationEventCallback::onWake(PxActor** actors, PxU32 count) = 0; void PxSimulationEventCallback::onSleep(PxActor** actors, PxU32 count) = 0;
See the section Callbacks and Customization for more information. An actor goes to sleep when its kinematic energy is below a given threshold for a certain time. This threshold can be manipulated using the following methods:
void PxRigidDynamic::setSleepThreshold(PxReal threshold); PxReal PxRigidDynamic::getSleepThreshold() const;
Kinematic actors go to sleep immediately if no target pose is set before a simulation step.
Objects automatically wake up when touched by an awake object, or the application changes the position or velocity. To explicitly wake up a sleeping object, or force an object to sleep, use:
void PxRigidDynamic::wakeUp(PxReal wakeCounterValue=PX_SLEEP_INTERVAL); void PxRigidDynamic::putToSleep();
The API reference documents exactly which methods cause an actor to be woken up.
When the motion of a rigid body is constrained either by contacts or joints, the constraint solver comes into play. The solver satisfies the constraints on the bodies by iterating over all the constraints restricting the motion of the body a certain number of times. The more iterations, the more accurate the results become. The solver iteration count defaults to 4 position iterations and 1 velocity iteration. Those counts may be set individually for each body using the following function:
void PxRigidDynamic::setSolverIterationCounts(PxU32 minPositionIters, PxU32 minVelocityIters);
Typically it is only necessary to significantly increase these values for objects with lots of joints and a small tolerance for joint error. If you find a need to use a setting higher than 30, you may wish to reconsider the configuration of your simulation.
Objects shaped like a pencil are difficult to simulate because they can store a lot of energy while rotating around a short axis, which is then converted to a very high rotational velocity when they start to rotate around a longer axis. High rotational velocities can lead to problems because certain linear approximations of the rotational motion fail to hold. For this reason the SDK automatically limits the rotational velocity of a body to a user definable maximum value. Because this may prevent intentional fast rotation in objects such as wheels, the user can override it on an per body basis:
void PxRigidDynamic::setMaxAngularVelocity(PxReal maxAngVel);
A dynamic actor needs mass properties: the mass, moment of inertia, and the center of mass frame which specifies the position of the actor's center of mass and its principal inertia axes. The easiest way to calculate mass properties is to use the PxRigidBodyExt::updateMassAndInertia() helper function, which will set all three properties based on the actor's shapes and a uniform density value. Variants of this function allow combinations of per-shape densites and manual specification of some mass properties. See the reference for PxRigidBodyExt for more details.
The Wobbly Snowmen in the North Pole Sample illustrate the use of different mass properties. The snowmen act like roly-poly toys, which are usually just an empty shell with the bottom filled with some heavy material. The low centers of mass cause them to move back to an upright position after they have been tilted. They come in different flavors, depending on how the mass properties are set:
The first is basically massless. There is just a little sphere with a relatively high mass at the bottom of the Actor. This results in a quite rapid movement due to the small resulting moments of inertia. The snowman feels light.
The second uses the mass of the bottom snowball only, resulting in a bigger inertia. Later on, the center of mass is moved to the bottom of the actor. This approximation is by no means physically correct, but the resulting snowman feels a bit more filled.
The third and fourth snowman use shapes to calculate the mass. The difference is that one calculates the moments of inertia first (from the real center of mass) and then the center of mass is moved to the bottom. The other calculates the moments of inertia about the low center of mass that we pass to the calculation routine. Note how much slower the wobbling is for the second case although both have the same mass. This is because the head accounts for much more in the moment of inertia (the distance from the center of mass squared).
The last snowman's mass properties are set up manually. The sample uses rough values for the moment of inertia to create a specific desired behavior. The diagonal tensor has a low value in X, and high values in Y and Z, producing a low resistance to rotation around the X-axis and high resistance around Y and Z. As a consequence, the snowman will wobble back and forth only around the X axis.
If you have a 3x3 inertia matrix (for example, you have real-life inertia tensors for your objects) use the PxDiagonalize() function to obtain principal axes and diagonal inertia tensors to initialize PxRigidDynamic actors.
The rates at which rigid bodies dissipate angular and linear momentum are governed by damping rates. In the PhysX SDK two damping rates may be specified for a rigid body:
void PxRigidDynamic::setLinearDamping(PxReal linDamp); void PxRigidDynamic::setAngularDamping(PxReal angDamp);
With a linear damping value of linDamp a rigid body will experience a damping force each update equal to -linDamp*velocity. Similarly, a rigid body with damping rate angDamp will experience a damping torque -angDamp*angularVelocity. The damping forces and torques always act against the velocity and angular velocity and are applied in a way that ensures that a moving and rotating rigid body will asymptotically approach the rest state in the absence of other forces.