Physics

Urho3D implements rigid body physics simulation using the Bullet library.

To use, a PhysicsWorld component must first be created to the Scene.

The physics simulation has its own fixed update rate, which by default is 60Hz. When the rendering framerate is higher than the physics update rate, physics motion is interpolated so that it always appears smooth. The update rate can be changed with SetFps() function. The physics update rate also determines the frequency of fixed timestep scene logic updates. Hard limit for physics steps per frame or adaptive timestep can be configured with SetMaxSubSteps() function. These can help to prevent a "spiral of death" due to the CPU being unable to handle the physics load. However, note that using either can lead to time slowing down (when steps are limited) or inconsistent physics behavior (when using adaptive step.)

The other physics components are:

  • RigidBody: a physics object instance. Its parameters include mass, linear/angular velocities, friction and restitution.
  • CollisionShape: defines physics collision geometry. The supported shapes are box, sphere, cylinder, capsule, cone, triangle mesh, convex hull and heightfield terrain (requires the Terrain component in the same node.)
  • Constraint: connects two RigidBodies together, or one RigidBody to a static point in the world. Point, hinge, slider and cone twist constraints are supported.

Movement and collision

Both a RigidBody and at least one CollisionShape component must exist in a scene node for it to behave physically (a collision shape by itself does nothing.) Several collision shapes may exist in the same node to create compound shapes. An offset position and rotation relative to the node's transform can be specified for each. Triangle mesh and convex hull geometries require specifying a Model resource and the LOD level to use.

CollisionShape provides two APIs for defining the collision geometry. Either setting individual properties such as the shape type or size, or specifying both the shape type and all its properties at once: see for example SetBox(), SetCapsule() or SetTriangleMesh().

RigidBodies can be either static or moving. A body is static if its mass is 0, and moving if the mass is greater than 0. Note that the triangle mesh collision shape is not supported for moving objects; it will not collide properly due to limitations in the Bullet library. In this case the convex hull or GImpact triangle mesh shape can be used instead.

The collision behaviour of a rigid body is controlled by several variables. First, the collision layer and mask define which other objects to collide with: see SetCollisionLayer() and SetCollisionMask(). By default a rigid body is on layer 1; the layer will be ANDed with the other body's collision mask to see if the collision should be reported. A rigid body can also be set to trigger mode to only report collisions without actually applying collision forces. This can be used to implement trigger areas. Finally, the friction, rolling friction and restitution coefficients (between 0 - 1) control how kinetic energy is transferred in the collisions. Note that rolling friction is by default zero, and if you want for example a sphere rolling on the floor to eventually stop, you need to set a non-zero rolling friction on both the sphere and floor rigid bodies.

By default rigid bodies can move and rotate about all 3 coordinate axes when forces are applied. To limit the movement, use SetLinearFactor() and SetAngularFactor() and set the axes you wish to use to 1 and those you do not wish to use to 0. For example moving humanoid characters are often represented by a capsule shape: to ensure they stay upright and only rotate when you explicitly set the rotation in code, set the angular factor to 0, 0, 0.

To prevent tunneling of a fast moving rigid body through obstacles, continuous collision detection can be used. It approximates the object as a swept sphere, but has a performance cost, so it should be used only when necessary. Call SetCcdRadius() and SetCcdMotionThreshold() with non-zero values to enable. To prevent false collisions, the body's actual collision shape should completely contain the radius. The motion threshold is the required motion per simulation step for CCD to kick in: for example a box with size 1 should have motion threshold 1 as well.

All physics calculations are performed in world space. Nodes containing a RigidBody component should preferably be parented to the Scene (root node) to ensure independent motion. For ragdolls this is not absolute, as retaining proper bone hierarchy is more important, but be aware that the ragdoll bones may drift far from the animated model's root scene node.

When several collision shapes are present in the same node, edits to them can cause redundant mass/inertia update computation in the RigidBody. To optimize performance in these cases, the edits can be enclosed between calls to DisableMassUpdate() and EnableMassUpdate().

Constraint parameters

Constraint position (and rotation if relevant) need to be defined in relation to both connected bodies, see SetPosition() and SetOtherPosition(). If the constraint connects a body to the static world, then the "other body position" and "other body rotation" mean the static end's transform in world space. There is also a helper function SetWorldPosition() to assign the constraint to a world-space position; this sets both relative positions.

Specifying the constraint's motion axis instead of rotation is provided as an alternative as it can be more intuitive, see SetAxis(). However, by explicitly specifying a rotation you can be sure the constraint is oriented precisely as you want.

Hinge, slider and cone twist constraints support defining limits for the motion. To be generic, these are encoded slightly unintuitively into Vector2's. For a hinge constraint, the low and high limit X coordinates define the minimum and maximum angle in degrees. For example -45 to 45. For a slider constraint, the X coordinates define the maximum linear motion in world space units, and the Y coordinates define maximum angular motion in degrees. The cone twist constraint uses only the high limit to define the maximum angles (minimum angle is always -maximum) in the following manner: The X coordinate is the limit of the twist (main) axis, while Y is the limit of the swinging motion about the other axes.

Physics events

The physics world sends 8 types of events during its update step:

  • E_PHYSICSPRESTEP before the simulation is stepped.
  • E_PHYSICSCOLLISIONSTART for each new collision during the simulation step. The participating scene nodes will also send E_NODECOLLISIONSTART events.
  • E_PHYSICSCOLLISION for each ongoing collision during the simulation step. The participating scene nodes will also send E_NODECOLLISION events.
  • E_PHYSICSCOLLISIONEND for each collision which has ceased. The participating scene nodes will also send E_NODECOLLISIONEND events.
  • E_PHYSICSPOSTSTEP after the simulation has been stepped.

Note that if the rendering framerate is high, the physics might not be stepped at all on each frame: in that case those events will not be sent.

Reading collision events

A new or ongoing physics collision event will report the collided scene nodes and rigid bodies, whether either of the bodies is a trigger, and the list of contact points.

The contact points are encoded in a byte buffer, which can be read using the VectorBuffer or MemoryBuffer helper class. The following structure repeats for each contact:

  • World-space position (Vector3)
  • Normal vector (Vector3)
  • Distance, negative when interpenetrating (float)
  • Impulse applied in collision (float)

An example of reading collision event and contact point data in script, from NinjaSnowWar game object collision handling code:

void HandleNodeCollision(StringHash eventType, VariantMap& eventData)
{
Node@ otherNode = eventData["OtherNode"].GetPtr();
RigidBody@ otherBody = eventData["OtherBody"].GetPtr();
VectorBuffer contacts = eventData["Contacts"].GetBuffer();
while (!contacts.eof)
{
Vector3 contactPosition = contacts.ReadVector3();
Vector3 contactNormal = contacts.ReadVector3();
float contactDistance = contacts.ReadFloat();
float contactImpulse = contacts.ReadFloat();
// Do something with the contact data...
}
}

Physics queries

The following queries into the physics world are provided: