Rigid-bodies

The real-time simulation of rigid-bodies subjected to forces and contacts is the main feature of a physics engine for video-games, robotics, or animation. Rigid-bodies are typically used to simulate the dynamics of non-deformable solids as well as to integrate the trajectory of solids which velocities are controlled by the user (e.g. moving platforms). On the other hand, rigid-bodies are not enough to simulate, e.g., cars, ragdolls, or robotic systems, as those use-cases require adding restrictions on the relative motion between their parts using joints.

Note that rigid-bodies are only responsible for the dynamics and kinematics of the solid. Colliders can be attached to a rigid-body to specify its shape and enable collision-detection. A rigid-body without collider attached to it will not be affected by contacts (because there is no shape to compute contact against).

Creation and insertion#

A rigid-body is created by adding all the components part of the RigidBodyBundle bundle. It is important for all the components in the bundle to be added at the same time. To remove the rigid-body, the complete bundle must be removed simultaneously too. Adding/removing only some of these components may end up in a panic.

info

The following example shows several initialization of components to customize rigid-body being built. The input values are just random so using this example as-is will not lead to a useful result.

use bevy::prelude::*;
use bevy_rapier2d::prelude::*;
let rigid_body = RigidBodyBundle {
position: Vec2::new(0.0, 5.0).into(),
velocity: RigidBodyVelocity {
linvel: Vec2::new(1.0, 2.0).into(),
angvel: 0.2
},
forces: RigidBodyForces { gravity_scale: 0.5, ..Default::default() },
activation: RigidBodyActivation::cannot_sleep(),
ccd: RigidBodyCcd { ccd_enabled: true, ..Default::default() },
..Default::default()
};
commands.spawn_bundle(rigid_body);

All the components part of the RigidBodyBundle can be queried and modified from Bevy systems.

info

Typically, the inertia and center of mass are automatically set to the inertia and center of mass resulting from the shapes of the colliders attached to the rigid-body. But they can also be set manually.

Rigid-body type#

There are three types of rigid-bodies, identified by the RigidBodyType component:

  • RigidBodyType::Dynamic: Indicates that the body is affected by external forces and contacts.
  • RigidBodyType::Static: Indicates the body cannot move. It acts as if it has an infinite mass and will not be affected by any force. It will continue to collide with dynamic bodies but not with static nor with kinematic bodies. This is typically used for the ground or for temporarily freezing a body.
  • RigidBodyType::KinematicPositionBased: Indicates that the body position must not be altered by the physics engine. The user is free to set its next position and the body velocity will be deduced at each update accordingly to ensure a realistic behavior of dynamic bodies in contact with it. This is typically used for moving platforms, elevators, etc.
  • RigidBodyType::KinematicVelocityBased: Indicates that the body velocity must not be altered by the physics engine. The user is free to set its velocity and the next body position will be deduced at each update accordingly to ensure a realistic behavior of dynamic bodies in contact with it. This is typically used for moving platforms, elevators, etc.

Both position-based and velocity-based kinematic bodies are mostly the same. Choosing between both is mostly a matter of preference between position-based control and velocity-based control.

Position#

The position of a rigid-body represents its location (translation) in 2D or 3D world-space, as well as its orientation (rotation). It translational part is represented as a vector and its rotational part as an unit quaternion (in 3D) or a unit complex number (in 2D). Both are combined into an isometry stored in the RigidBodyPosition component.

The position of a rigid-body can be set when creating it. It can also be set after its creation as illustrated bellow.

warning

Directly changing the position of a rigid-body is equivalent to teleporting it: this is a not a physically realistic action! Teleporting a dynamic or kinematic bodies may result in odd behaviors especially if it teleports into a space occupied by other objects. For dynamic bodies, forces, impulses, or velocity modification should be preferred. For kinematic bodies, see the discussion after the examples bellow.

/* Set the position when the rigid-body bundle is created. */
let rigid_body = RigidBodyBundle {
// NOTE: pick one of the following.
position: (Vec2::new(0.0, 5.0), 5.0).into(), // Translation and rotation.
position: Vec2::new(0.0, 5.0).into(), // Translation only.
..Default::default()
};
commands.spawn_bundle(rigid_body);
/* Change the position inside of a system. */
fn modify_body_translation(mut positions: Query<&mut RigidBodyPosition>) {
for mut rb_pos in positions.iter_mut() {
rb_pos.position.translation = Vec2::new(0.0, 5.0).into();
}
}

In order to move a dynamic rigid-body it is strongly discouraged to set its position directly as it may results in weird behaviors: it's as if the rigid-body teleports itself, which is a non-physical behavior. For dynamic bodies, it is recommended to either set its velocity or to apply forces or impulses.

For velocity-based kinematic bodies, it is recommended to set its velocity instead of setting its position directly. For position-based kinematic bodies, it is recommended to modify the next_position field of the RigidBodyPosition component instead of its position field. This will let the physics engine compute the fictitious velocity of the kinematic body for more realistic intersections with other rigid-bodies. The position field of the kinematic body will automatically be set to the next_position value during the next physics update.

Sync. with Bevy Transform#

If the entity containing the RigidBodyPosition component also has a Transform component, it is possible to tell the plugin to automatically update the Transform component at each frame using the RigidBodyPosition, by adding the RigidBodyPositionSync component to the same entity:

let rigid_body = RigidBodyBundle::default();
commands.spawn_bundle(rigid_body)
.insert(Transform::default())
.insert(RigidBodyPositionSync::Discrete);

When running this update, the translation part of the transform is automatically multiplied by the RapierConfiguration::scale value (this is typically used in 2D for automatically converting between physics units (meters) to graphics units pixels).

warning

This synchronization between RigidBodyPosition and Transform is one-way: modifying the Transform will not result in the automatic modification of the RigidBodyPosition.

Velocity#

The velocity of a dynamic rigid-body controls how fast it is moving in time. The velocity is applied at the center-of-mass of the rigid-body, and is composed of two independent parts:

  1. The linear velocity is specified as a vector representing the direction an magnitude of the movement.
  2. In 3D, the angular velocity is given as a vector representing the rotation axis multiplied by the the rotation angular speed in rad/s (axis-angle representation). In 2D, the angular velocity is given as a real representing the angular speed in rad/s.
info

The velocity is only relevant to dynamic rigid-bodies. It has not effect on static rigid-bodies and the velocity of kinematic rigid-bodies are automatically computed at each timestep based on their next kinematic positions.

The velocity of a rigid-body is automatically updated by the physics pipeline after taking forces, contacts, and joints into account. It can be set when the rigid-body is created or after its creation:

/* Set the velocities when the rigid-body is created. */
let rigid_body = RigidBodyBundle {
velocity: RigidBodyVelocity {
linvel: Vec2::new(1.0, 2.0).into(),
angvel: 0.4
},
..Default::default()
};
commands.spawn_bundle(rigid_body);
/* Set the velocities inside of a system. */
fn modify_body_velocity(mut velocities: Query<&mut RigidBodyVelocity>) {
for mut rb_vel in velocities.iter_mut() {
rb_vel.linvel = Vec2::new(1.0, 2.0).into();
rb_vel.angvel = 0.4;
}
}

Alternatively, the velocity of a dynamic rigid-body can be altered indirectly by applying a force or an impulse.

Gravity#

Gravity is such a common force that it is implemented as a special case (even if it could easily be implemented by the user using force application). The gravity is given by the field RapierConfiguration::gravity of the resource RapierConfiguration and can be modified at will. Note however that a change of gravity won't automatically wake-up the sleeping bodies so keep in mind that you may want to wake them up manually before a gravity change.

note

Because static and kinematic bodies are immune to forces, they are not affected by gravity.

info

A rigid-body with no mass will not be affected by gravity either. So if your rigid-body doesn't fall when you expected it to, make sure it has a mass set explicitly, or has at least one collider with non-zero density attached to it.

It is possible to change the way gravity affects a specific rigid-body by setting the rigid-body's gravity scale to a value other than 1.0. The magnitude of the gravity applied to this body will be multiplied by this scaling factor. Therefore, a gravity scale set to 0.0 will disable gravity for the rigid-body whereas a gravity scale set to 2.0 will make it twice as strong. A negative value will flip the direction of the gravity for this rigid-body.

This gravity scale factor can be set when the rigid-body is created or after its creation:

/* Set the gravity scale when the rigid-body is created. */
let rigid_body = RigidBodyBundle {
forces: RigidBodyForces {
gravity_scale: 2.0,
..Default::default()
},
..Default::default()
};
commands.spawn_bundle(rigid_body);
/* Set the gravity scale inside of a system. */
fn modify_body_gravity_scale(mut forces: Query<&mut RigidBodyForces>) {
for mut rb_forces in forces.iter_mut() {
rb_forces.gravity_scale = 2.0;
}
}

Forces and impulses#

In addition to gravity, it is possible to apply custom forces (or torques) and impluses (or torque impulses) to dynamic rigid-bodies in order to make them move in specific ways. Forces affect the rigid-body's acceleration whereas impulses affect the rigid-body's velocity. They are both based on the familiar equations:

  • Forces: the acceleration change is equal to the force divided by the mass: Δa=m1f\Delta{}a = m^{-1}f
  • Impulses: the velocity change is equal to the impulse divided by the mass: Δv=m1i\Delta{}v = m^{-1}i

Forces and impulses can be applied to a rigid-body when it is created or after its creation:

/* Apply forces when the rigid-body is created. */
let rigid_body = RigidBodyBundle {
forces: RigidBodyForces {
force: Vec2::new(1000.0, 2000.0).into(),
torque: 140.0,
..Default::default()
},
..Default::default()
};
commands.spawn_bundle(rigid_body);
/* Apply forces and impulses inside of a system. */
fn apply_forces(mut rigid_bodies: Query<(&mut RigidBodyForces, &mut RigidBodyVelocity, &RigidBodyMassProps)>) {
for (mut rb_forces, mut rb_vel, rb_mprops) in rigid_bodies.iter_mut() {
// Apply forces.
rb_forces.force = Vec2::new(1.0, 2.0).into();
rb_forces.torque = 0.4;
// Apply impulses.
rb_vel.apply_impulse(rb_mprops, Vec2::new(100.0, 200.0).into());
rb_vel.apply_torque_impulse(rb_mprops, 80.0);
}
}
info

Keep in mind that a dynamic rigid-body with a zero mass won't be affected by a linear force/impulse, and a rigid-body with a zero angular inertia won't be affected by torques/torque impulses. So if your force doesn't appear to do anything, make sure that:

  1. The rigid-body is dynamic.
  2. It is strong enough to make the rigid-body move (try a very large value and see if it does something).
  3. The rigid-body has a non-zero mass or angular inertia either because they were set explicitly, or because they were computed automatically from colliders with non-zero densities.

Mass properties#

The mass properties of a rigid-body is composed of three parts:

  • The mass which determines the resistance of the rigid-body wrt. linear movements. A high mass implies that larger forces are needed to make the rigid-body translate.
  • The angular inertia determines the resistance of the rigid-body wrt. the angular movements. A high angular inertia implies that larger torques are needed to make the rigid-body rotate.
  • The center-of-mass determines relative to what points torques are applied to the rigid-body.
note

Zero is a special value for masses and angular inertias. A mass equal to zero is interpreted as an infinite mass. An angular inertia equal to zero is interpreted as an infinite angular inertia. Therefore, a rigid-body with a mass equal to zero will not be affected by any force, and a rigid-body with an angular inertia equal to zero will not be affected by any torque.

Computing the mass and angular-inertia can often be difficult because they depend on the geometric shape of the object being simulated. This is why they are automatically computed by Rapier when a collider is attached to the rigid-body: the collider add its own mass and angular-inertia contribution (computed based on the collider's shape and density) to the rigid-body it is attached to:

let rigid_body = RigidBodyBundle::default();
let collider = ColliderBundle {
shape: ColliderShape::ball(1.0),
// The default density is 1.0, we are setting 2.0 for this example.
mass_properties: ColliderMassProps::Density(2.0),
..Default::default()
};
// When the collider is attached, the rigid-body's mass and angular
// inertia will be automatically updated to take the collider into account.
commands.spawn_bundle(rigid_body)
.insert_bundle(collider);

Alternatively, it is possible to set the mass properties of a rigid-body when it is created. Keep in mind that this won't prevent the colliders' contributions to be added to these values. So make sure to set the attached colliders' densities to zero if you want your explicit values to be the final mass-properties values.

/* Set the mass properties when the rigid-body bundle is created. */
let rigid_body = RigidBodyBundle {
mass_properties: MassProperties::new(Vec2::new(1.0, 2.0).into(), 10.0, 0.5).into(),
..Default::default()
};
commands.spawn_bundle(rigid_body);
/* Change the mass-properties inside of a system. */
fn modify_body_mass_props(mut mprops: Query<&mut RigidBodyMassProps>) {
for mut rb_mprops in mprops.iter_mut() {
rb_mprops.local_mprops.set_mass(100.0, true);
}
}

Locking translations/rotations#

It is sometimes useful to prevent a rigid-body from rotating or translating. One typical use-case for locking rotations is to prevent a player modeled as a dynamic rigid-body from tilting. These kind of degree-of-freedom restrictions could be achieved by joints, but locking translations/rotations of a single rigid-body wrt. the cartesian coordinate axes can be done in a much more efficient and numerically stable way. That's why rigid-bodies have dedicated flags for this.

/* Lock translations and/or rotations when the rigid-body bundle is created. */
let rigid_body = RigidBodyBundle {
mass_properties: (RigidBodyMassPropsFlags::TRANSLATION_LOCKED | RigidBodyMassPropsFlags::ROTATION_LOCKED).into(),
..Default::default()
};
commands.spawn_bundle(rigid_body);
/* Lock translations and/or rotations inside of a system. */
fn modify_body_locked_flags(mut mprops: Query<&mut RigidBodyMassProps>) {
for mut rb_mprops in mprops.iter_mut() {
rb_mprops.flags = RigidBodyMassPropsFlags::ROTATION_LOCKED;
}
}

Damping#

Damping lets you slow down a rigid-body automatically. This can be used to achieve a wide variety of effects like fake air friction. Each rigid-body is given a linear damping coefficient (affecting its linear velocity) and an angular damping coefficient (affecting its angular velocity). Larger values of the damping coefficients lead to a stronger slow-downs. Their default values are 0.0 (no damping at all).

This damping coefficients can be set when the rigid-body is created or after its creation:

/* Set damping when the rigid-body bundle is created. */
let rigid_body = RigidBodyBundle {
damping: RigidBodyDamping { linear_damping: 0.5, angular_damping: 1.0 },
..Default::default()
};
commands.spawn_bundle(rigid_body);
/* Set damping inside of a system. */
fn modify_body_damping(mut dampings: Query<&mut RigidBodyDamping>) {
for mut rb_damping in dampings.iter_mut() {
rb_damping.linear_damping = 0.5;
rb_damping.angular_damping = 1.0;
}
}

Dominance#

Dominance is a non-realistic, but sometimes useful, feature. It can be used to make one rigid-body immune to forces originating from contacts with some other bodies. For example this can be used to model a player represented as a dynamic rigid-body that cannot be "pushed back" by any, or some, other dynamic rigid-bodies part of the environment.

Each rigid-body is part of a dominance group in [-127; 127] (the default group is 0). If the colliders from two rigid-bodies are in contact, the one with the highest dominance will act as if it has an infinite mass, making it immune to the contact forces the other body would apply on it. If both bodies are part of the same dominance group, then their contacts will work in the usual way (both are affected by opposite forces with the same magnitude).

For example, if a dynamic body A is in the dominance group 10, and a dynamic body B in the dominance group -20, then a contact between a collider attached to A and a collider attached B will result in A remaining immobile and B being pushed by A (independently from their mass).

info

A non-dynamic rigid-body is always considered as being part of a dominance group greater than any dynamic rigid-body. This means that dynamic/static and dynamic/kinematic contacts will continue to work normally, independently from the dominance group they were given by the user.

The dominance group can be set when the rigid-body is created or after its creation:

/* Set dominance when the rigid-body bundle is created. */
let rigid_body = RigidBodyBundle {
dominance: RigidBodyDominance(10),
..Default::default()
};
commands.spawn_bundle(rigid_body);
/* Set dominance inside of a system. */
fn modify_body_dominance(mut dominances: Query<&mut RigidBodyDominance>) {
for mut rb_dominance in dominances.iter_mut() {
rb_dominance.0 = 10;
}
}

Continuous collision detection#

Continuous Collision Detection (CCD) is used to make sure that fast-moving objects don't miss any contacts (a problem usually called tunneling). This is done by using motion-clamping, i.e., each fast-moving rigid-body with CCD enabled will be stopped at the time where their first contact happen, taking their continuous motion into account. This will result in some "time loss" for that rigid-body. This loss of time can be reduced by increasing the maximum number of CCD substeps executed (the default being 1) in the IntegrationParameters (by changing the IntegrationParameters::max_ccd_substeps field).

Rapier implements nonlinear CCD, meaning that it takes into account both the angular and translational motion of the rigid-body.

info

CCD takes action only if the CCD-enabled rigid-body is moving fast relative to another rigid-body. Therefore it is useless to enable CCD on static rigid-bodies and rigid-bodies that are expected to move slowly.

By default, CCD is disabled for all the rigid-bodies because it requires additional computations. It can be enabled when creating a rigid-body or after its creation:

/* Enable CCD when the rigid-body bundle is created. */
let rigid_body = RigidBodyBundle {
ccd: RigidBodyCcd { ccd_enabled: true, ..Default::default() },
..Default::default()
};
commands.spawn_bundle(rigid_body);
/* Enable CCD inside of a system. */
fn modify_body_ccd(mut ccds: Query<&mut RigidBodyCcd>) {
for mut rb_ccd in ccds.iter_mut() {
rb_ccd.ccd_enabled = true;
}
}

Sleeping#

When a dynamic rigid-body doesn't move (or moves very slowly) during a few seconds, it will be marked as sleeping by the physics pipeline. Rigid-bodies marked as sleeping are no longer simulated by the physics engine until they are woken up. That way the physics engine doesn't waste any computational resources simulating objects that don't actually move. They are woken up automatically whenever another non-sleeping rigid-body starts interacting with them (either with a joint, or with one of its attached colliders generating contacts).

When using the bevy_rapier plugin, rigid-bodies are also automatically woken up whenever one of the components of the rigid-body is modified (to apply forces, change its position, etc.) They will not be awaken automatically when changing the gravity though. So you may sometimes want to wake a rigid-body manually by calling RigidBodyActivation::wake_up(true) on the RigidBodyActivation component.