Common mistakes
My local build of Rapier is slower than the online demos
Make sure you are building your project (or just the Rapier dependency) in release mode, e.g., cargo build --release.
Rapier can be 100 times slower without optimizations enabled. Keep in mind that it is possible to compile your project
without optimizations while keeping optimizations enabled for Rapier itself:
# Add this to your Cargo.toml
[profile.dev.package.rapier3d]
opt-level = 3
# Add this to your Cargo.toml
[profile.dev.package.bevy_rapier3d]
opt-level = 3
[profile.dev.package.rapier3d]
opt-level = 3
See the cargo book about profile overrides for details about this technique.
Also note that setting the number codegen units to 1 will further improve performances in a noticeable way, even for a release build (though the build itself will take longer to complete):
# Add this to your Cargo.toml
[profile.release]
codegen-units = 1
My local build of Rapier is slower than the online demos
Make sure the Rapier library is built in release mode. Rapier can be 100 times slower without optimizations
enabled. Keep in mind that the optimization of the Rapier library is selected by the RAPIER_PROFILE option of CMake
(release by default), independently from the build mode of your own C code (selected by CMAKE_BUILD_TYPE): a
debug build of your application can still use an optimized Rapier library, as long as it isn't configured with
-DRAPIER_PROFILE=debug. The build mode of the library actually loaded by your application is given by
r3BuildProfile:
/* Warn if the Rapier library loaded by the application isn't optimized. */
if (strcmp(r3BuildProfile(), "release") != 0) {
fprintf(stderr, "Rapier is built in debug mode: the simulation will be very slow.\n");
}
My local build of Rapier is slower than the online demos
Make sure the bindings are built in release mode. Rapier can be 100 times slower without optimizations enabled. The
package installed by pip is always optimized, but the bindings built from source are only optimized if --release
is given to maturin, e.g., maturin develop --release -m bindings/python/rapier-py-3d/Cargo.toml (see
building from source). The build mode of the bindings actually loaded
by your application is given by the profile property of rapier3d.build_features():
# A debug build of the bindings is up to 100 times slower.
if rp.build_features().profile != "release":
print("Warning: the rapier3d bindings are built without optimizations.")
Keep in mind as well that each call from Python to the bindings has a cost: a script calling a method of every rigid-body at each frame may spend more time in Python than in the simulation itself. For example, iterate only on the rigid-bodies that moved during the last timestep (see the island manager) rather than on all of them.
Rigid-body isn't affected by gravity
If you expect your rigid-body to fall because of gravity but it doesn't, please make sure to double-check the following:
- Your gravity vector is non-zero.
- Your rigid-body is a dynamic rigid-body.
- You didn't lock the translations of the rigid-body.
- The rigid-body has a non-zero mass.
Note that a collider not attached to a dynamic rigid-body will never fall because it won't be affected by forces.
If the rigid-body has no collider attached to it, its mass will be zero unless you gave it a mass (or mass properties) explicitly. If the rigid-body has colliders attached to it and you didn't give the rigid-body a mass explicitly, make sure that at least one of the colliders has a non-zero density (or non-zero mass if you set it explicitly on the collider).
The shapes that don't enclose any volume, i.e., the polylines, the half-spaces, the segments, and the triangles in 3D, have a zero mass whatever their density. So if a rigid-body only has colliders with such shapes attached to it, you need to set its mass/angular inertia manually. Note that a triangle-mesh isn't one of them: its mass properties are computed from the volume enclosed by its faces, which assumes that the mesh is closed and that its triangles are consistently oriented.
Make sure that the descriptions of your rigid-bodies and colliders are initialized by their constructors, e.g.,
r3DynamicRigidBodyDesc() and r3BallColliderDesc(0.5). A zero-initialized description is a disabled object, and a
zero-initialized rigid-body description has a zero gravityScale. See
below.
A PhysicsWorld created without its gravity argument, e.g., PhysicsWorld(), has no gravity at all. Give it
explicitly, e.g., PhysicsWorld(gravity=(0.0, -9.81, 0.0)), or set its gravity property afterwards.
Applying a force to a rigid-body doesn't work
If applying a force or an impulse to a rigid-body doesn't work, please make sure to double-check the following:
- The rigid-body is a dynamic rigid-body.
- The rigid-body has a non-zero mass (or non-zero angular inertia for torques).
- The force or impulse must be strong enough to actually push the rigid-body. You may for example try with a very
high force/impulse value (say, with a magnitude of
100_000.0) and see if this stronger force works. - The rigid-body is awake, either by calling
RigidBody::wake_up(true)explicitly, or by usingtrueas the last argument of the force/impulse application method.
- The rigid-body is a dynamic rigid-body.
- The rigid-body has a non-zero mass (or non-zero angular inertia for torques).
- The force or impulse must be strong enough to actually push the rigid-body. You may for example try with a very
high force/impulse value (say, with a magnitude of
100_000.0) and see if this stronger force works.
- The rigid-body is a dynamic rigid-body.
- The rigid-body has a non-zero mass (or non-zero angular inertia for torques).
- The force or impulse must be strong enough to actually push the rigid-body. You may for example try with a very
high force/impulse value (say, with a magnitude of
100000.0) and see if this stronger force works.
- The rigid-body is a dynamic rigid-body.
- The rigid-body has a non-zero mass (or non-zero angular inertia for torques).
- The force or impulse must be strong enough to actually push the rigid-body. You may for example try with a very
high force/impulse value (say, with a magnitude of
100000.0) and see if this stronger force works. - The rigid-body is awake, either by calling
r3RigidBody_WakeUp(handle, 1)explicitly, or by using1as the last argument (wake_up) of the force/impulse application function.
- The rigid-body is a dynamic rigid-body.
- The rigid-body has a non-zero mass (or non-zero angular inertia for torques).
- The force or impulse must be strong enough to actually push the rigid-body. You may for example try with a very
high force/impulse value (say, with a magnitude of
100_000.0) and see if this stronger force works. - The rigid-body is awake, either by calling
RigidBody.wake_up()explicitly, or by keeping thewake_upargument of the force/impulse application method toTrue(its default value). - The force is applied to the rigid-body of the world, i.e., to the view given by
world.rigid_bodies[handle], rather than to aRigidBodybuilt by your code before its insertion (see below).
If the rigid-body has no collider attached to it, its mass will be zero unless you gave it a mass (or mass properties) explicitly. If the rigid-body has colliders attached to it and you didn't give the rigid-body a mass explicitly, make sure that at least one of the colliders has a non-zero density (or non-zero mass if you set it explicitly on the collider).
A rigid-body suddenly stops being simulated
It may happen that a rigid-body disappears from the simulation, i.e., it stops moving and stops generating contacts,
without being removed by your own code. This generally means that non-finite values (NaN or infinites) appeared in
its position or in its velocity for one reason or another (could be the simulation diverged, incorrect values provided, etc.)
Rather than panicking or letting these values propagate and corrupt the whole simulation, Rapier detects them at the beginning and at the end of each timestep, and puts the affected rigid-body, collider, or soft-body in quarantine: it is reset to its last valid position when one is known, its velocities are zeroed, and it is disabled.
The quarantined objects of the last timestep are reported by PhysicsWorld::quarantine (or by
PhysicsPipeline::quarantine if you step the pipeline yourself). This report is cleared at each timestep, so it must
be read after the step that generated it. Once the cause is fixed, a quarantined object is brought back into the
simulation with RigidBody::set_enabled(true) (resp. Collider::set_enabled(true)):
world.step();
// After the step, the objects neutralized by this step are known.
let quarantined = world.quarantine().bodies().to_vec();
for handle in quarantined {
println!("The rigid-body {:?} went non-finite and was disabled.", handle);
// Once the cause is fixed, the rigid-body is brought back into the simulation.
world.bodies[handle].set_enabled(true);
}
A warning is logged and a PhysicsQuarantineEvent message is sent after each
simulation step that quarantined something. It gives the entity of the physics context, and the entities of the
quarantined rigid-bodies, colliders, and soft-bodies (the bodies, colliders, and soft_bodies fields). The plugin
also inserts the RigidBodyDisabled (resp. ColliderDisabled, SoftBodyDisabled) component on each quarantined
entity. Once the cause is fixed (e.g. by writing a finite Velocity or Transform, or by moving the non-finite
particles of a soft-body back), a quarantined object is brought back into the simulation by removing that component:
fn handle_quarantine(
mut commands: Commands,
mut quarantine_events: MessageReader<PhysicsQuarantineEvent>,
mut velocities: Query<&mut Velocity>,
) {
for event in quarantine_events.read() {
for entity in &event.bodies {
println!("The rigid-body {entity} went non-finite and was disabled.");
// Fix the cause, e.g., a non-finite velocity set by our own code.
if let Ok(mut velocity) = velocities.get_mut(*entity) {
*velocity = Velocity::zero();
}
// Then bring the rigid-body back into the simulation.
commands.entity(*entity).remove::<RigidBodyDisabled>();
}
// The quarantined colliders and soft-bodies are re-enabled the same way.
for entity in &event.colliders {
commands.entity(*entity).remove::<ColliderDisabled>();
}
for entity in &event.soft_bodies {
commands.entity(*entity).remove::<SoftBodyDisabled>();
}
}
}
The most common configuration that leads to non-finite values is two dynamic rigid-bodies with a zero mass starting to
be in contact. Therefore, make sure that your dynamic rigid-bodies have a non-zero mass. Note that the values given to
the physics engine are checked too: a position or a velocity set to NaN by your own code (e.g. after a division by a
zero-length vector) is caught at the beginning of the next timestep.
If the rigid-body has no collider attached to it, its mass will be zero unless you gave it a mass (or mass properties) explicitly. If the rigid-body has colliders attached to it and you didn't give the rigid-body a mass explicitly, make sure that at least one of the colliders has a non-zero density (or non-zero mass if you set it explicitly on the collider).
A rigid-body suddenly stops being simulated
It may happen that a rigid-body disappears from the simulation, i.e., it stops moving and stops generating contacts,
without being removed by your own code. This generally means that non-finite values (NaN or infinites) appeared in
its position or in its velocity for one reason or another (could be the simulation diverged, incorrect values provided, etc.)
Rather than crashing or letting these values propagate and corrupt the whole simulation, Rapier detects them at the
beginning and at the end of each timestep, and puts the affected rigid-body, collider, or soft-body in quarantine:
it is reset to its last valid position when one is known, its velocities (and the forces applied to it) are zeroed,
and it is disabled. Note that a quarantined soft-body keeps its non-finite particle positions: they must be fixed
with r3SoftBody_SetParticlePosition before enabling it again.
The quarantined objects of the last timestep are copied by r3QuarantinedRigidBodies (resp.
r3QuarantinedColliders, r3QuarantinedSoftBodies). This report is cleared at each timestep, so it must be read
after the step that generated it (keep in mind that it may contain the handles of objects you removed since). Once
the cause is fixed, a quarantined object is brought back into the simulation with r3RigidBody_SetEnabled(handle, 1)
(resp. r3Collider_SetEnabled, r3SoftBody_SetEnabled):
r2Step(world, NULL, NULL);
/* After the step, the objects neutralized by this step are known. */
size_t num_quarantined = r2QuarantinedRigidBodies(world, NULL, 0);
R2RigidBodyHandle *quarantined = malloc(num_quarantined * sizeof(R2RigidBodyHandle));
num_quarantined = r2QuarantinedRigidBodies(world, quarantined, num_quarantined);
for (size_t i = 0; i < num_quarantined; i++) {
printf("The rigid-body {%u, %u} went non-finite and was disabled.\n", quarantined[i].index,
quarantined[i].generation);
/* Once the cause is fixed, the rigid-body is brought back into the simulation. */
r2RigidBody_SetEnabled(quarantined[i], 1);
}
free(quarantined);
The most common configuration that leads to non-finite values is two dynamic rigid-bodies with a zero mass starting to
be in contact. Therefore, make sure that your dynamic rigid-bodies have a non-zero mass. Note that the values given to
the physics engine are checked too: a position or a velocity set to NaN by your own code (e.g. after a division by a
zero-length vector) is caught at the beginning of the next timestep.
If the rigid-body has no collider attached to it, its mass will be zero unless you gave it a mass (or mass properties) explicitly. If the rigid-body has colliders attached to it and you didn't give the rigid-body a mass explicitly, make sure that at least one of the colliders has a non-zero density (or non-zero mass if you set it explicitly on the collider).
A rigid-body suddenly stops being simulated
It may happen that a rigid-body disappears from the simulation, i.e., it stops moving and stops generating contacts,
without being removed by your own code. This generally means that non-finite values (NaN or infinites) appeared in
its position or in its velocity for one reason or another (could be the simulation diverged, incorrect values provided, etc.)
Rather than crashing or letting these values propagate and corrupt the whole simulation, Rapier detects them at the beginning and at the end of each timestep, and puts the affected rigid-body, collider, or soft-body in quarantine: it is reset to its last valid position when one is known, its velocities are zeroed, and it is disabled.
The quarantined objects of the last timestep are reported by the PhysicsWorld.quarantine property (or by
PhysicsPipeline.quarantine if you step the pipeline yourself), which lists their handles in its bodies,
colliders, and soft_bodies properties. This report is cleared at each timestep, so it must be read after the step
that generated it. Once the cause is fixed, a quarantined object is brought back into the simulation by setting its
is_enabled property to True:
world.step()
# After the step, the objects neutralized by this step are known.
for handle in world.quarantine.bodies:
print(f"The rigid-body {handle} went non-finite and was disabled.")
# Once the cause is fixed, the rigid-body is brought back into the simulation.
world.rigid_bodies[handle].is_enabled = True
The most common configuration that leads to non-finite values is two dynamic rigid-bodies with a zero mass starting to
be in contact. Therefore, make sure that your dynamic rigid-bodies have a non-zero mass. Note that the values given to
the physics engine are checked too: a position or a velocity set to NaN by your own code (e.g. after a division by a
zero-length vector) is caught at the beginning of the next timestep.
If the rigid-body has no collider attached to it, its mass will be zero unless you gave it a mass (or mass properties) explicitly. If the rigid-body has colliders attached to it and you didn't give the rigid-body a mass explicitly, make sure that at least one of the colliders has a non-zero density (or non-zero mass if you set it explicitly on the collider).
Why is everything moving in slow-motion?
A common mistake, especially in 2D, is to use pixels as the length unit in the physics world. Let's say that in 2D
you have a 100x100 pixels sprite for your player. It may be tempting to use a 100x100 cuboid collider for this
sprite: ColliderBuilder::cuboid(50.0, 50.0)Collider::cuboid(50.0, 50.0)ColliderDesc.cuboid(50.0, 50.0)r2CuboidColliderDesc(r2Vector(50.0, 50.0))Collider.cuboid(50.0, 50.0, 50.0)50.0
because this is the half-width of the cuboid). Doing this will make it look like the simulation runs in slow-motion
because the cuboid will be huge compared to the magnitude of the
The recommended way of using Rapier is to use SI units (meters, seconds, kilograms, etc.) If the player sprite is a 100x100 cuboid, then it is as if your player is 100 meters tall and 100 meters wide, which is huge. Therefore it is recommended to have a scaling factor between the graphics and the physics. For example we can say that 1 physics meter is equal to 50 pixels. This means that we can initialize our player collider as a 2x2 cuboid while still using a 100x100 pixels sprite.
All we need to do to keep measures in sync is to multiply by our scaling factor 50 all the positions given by the physics engine before rendering:
// Scale the translation to convert from meters to pixels
sprite.set_translation(rigid_body.translation() * 50.0);
// Rotation angles don't need to be scaled
sprite.set_rotation(rigid_body.rotation().angle());
In this example, we could set RapierPhysicsPlugin::pixels_per_meter(50.0): all the transforms, collider sizes,
velocities, etc. remain expressed in pixels on your end, but this sets the
length unit of the simulation to 50, which scales the default gravity
and the internal tolerances of Rapier accordingly. The simulation then behaves as if the player was measured in
meters.
All we need to do to keep measures in sync is to multiply by our scaling factor 50 all the positions given by the physics engine before rendering.
All we need to do to keep measures in sync is to multiply by our scaling factor 50 all the positions given by the physics engine before rendering:
R2Pose pose = r2RigidBody_Position(rigid_body);
/* Scale the translation to convert from meters to pixels. */
sprite_translation = r2VectorScale(pose.translation, 50.0);
/* Rotation angles don't need to be scaled. */
sprite_rotation = pose.rotation.angle;
Alternatively, the length unit of the simulation can be set to 50 with
r2SetLengthUnit, which scales the internal tolerances of Rapier accordingly. However, every value given to the
engine (sizes, positions, velocities, and the gravity) must then be expressed in pixels.
All we need to do to keep measures in sync is to multiply by our scaling factor 50 all the positions given by the physics engine before rendering:
PIXELS_PER_METER = 50.0
# Scale the translation to convert from meters to pixels.
sprite_translation = ball.translation * PIXELS_PER_METER
# Rotations don't need to be scaled.
sprite_rotation = ball.rotation
The Python bindings being 3D only, this generally happens with the units of the renderer or of the 3D modeling tool
the scene comes from (e.g. centimeters). Alternatively, the length unit of
the simulation can be set to the number of your units that make one meter with the length_unit property of the
IntegrationParameters, which scales the internal tolerances of Rapier accordingly. However, every value given to the
engine (sizes, positions, velocities, and the gravity) must then be expressed in your own units.
My objects are not inserted, or don't behave as expected
The descriptions (R3RigidBodyDesc, R3ColliderDesc, R3JointDesc, etc.) must be initialized by one of their
constructors, which give every field a meaningful default value, before being modified. A zero-initialized
description (e.g. R3RigidBodyDesc desc = {0};) gives a zero value to every field, which is either rejected by the
insertion (e.g. a 3D rotation made of zeros isn't a valid quaternion), or accepted silently with surprising
results: a disabled object, a rigid-body with a zero gravity scale, a collider with a zero density and collision
groups preventing it from colliding with anything, etc.
/* WRONG version: every field is zero, so the rigid-body would be disabled, with a zero gravity
* scale. In 3D, its insertion even fails because a zero quaternion isn't a valid rotation. */
R3RigidBodyDesc wrong_body = {0};
/* CORRECT version: the constructor gives every field a meaningful default value. */
R3RigidBodyDesc body = r3DynamicRigidBodyDesc();
body.position.translation = r3Vector(0.0, 1.0, 0.0);
The same applies to the 3D rotations: they are unit quaternions, and a quaternion made of zeros isn't one of them. The
identity rotation is the quaternion with w = 1 and x = y = z = 0, which is what r3TranslationPose gives:
/* WRONG version: a zero-initialized quaternion isn't a valid rotation, the call fails. */
// R3Pose pose = {{1.0, 2.0, 3.0}, {0}};
/* CORRECT version: start from the identity rotation (0, 0, 0, 1), or build a rotation from an
* axis and an angle. */
R3Pose pose = r3TranslationPose(r3Vector(1.0, 2.0, 3.0));
pose.rotation = r3RotationFromAxisAngle(r3Vector(0.0, 1.0, 0.0), 0.5);
r3Collider_SetPosition(collider, pose);
A function fails with R3_INVALID_HANDLE
The handle of an object becomes stale as soon as its object is removed from the world: the functions given a
stale handle fail with the R3_INVALID_HANDLE error. Note that removing a rigid-body also removes the joints attached
to it, and its colliders if r3RemoveRigidBody is asked to, so their handles become stale as well. If an object may
have been removed by another part of your application, check its handle first, e.g., with r3RigidBody_Contains:
r3RemoveRigidBody(handle, 1);
/* The handle is now stale: using it (even to remove it again) fails with R3_INVALID_HANDLE. Check
* it first if the object may have been removed by another part of the application. */
if (r3RigidBody_Contains(handle)) {
r3RigidBody_WakeUp(handle, 1);
}
Freeing the world with r3FreeWorld invalidates all its handles, including their copies stored anywhere in your
application. Unlike the handles of removed objects, they can't be detected: using one of them is undefined behavior.
A scene query reports an R3_NOT_FOUND error
Some functions report their failure to find something as the R3_NOT_FOUND error, e.g., r3CastRay when the ray
doesn't hit anything. This error is given to the error handler (installed with r3SetErrorHandler) like any other
error, which is an issue if your error handler aborts the application. When a miss is an expected outcome, use the
Try variant of the function if there is one, e.g., r3TryCastRay, which reports a miss with its found field
instead of an error:
/* WRONG version, if missing the ground is expected: a miss is an error (R3_NOT_FOUND) given
* to the error handler, and the returned hit is meaningless. */
// R3RayHit hit = r3CastRay(world, NULL, r3Vector(0.0, 10.0, 0.0), r3Vector(0.0, -1.0, 0.0), 1.0, 1);
/* CORRECT version: a miss is reported by the `found` field. */
R3OptionalRayHit hit =
r3TryCastRay(world, NULL, r3Vector(0.0, 10.0, 0.0), r3Vector(0.0, -1.0, 0.0), 1.0, 1);
if (!hit.found) {
printf("Nothing below.\n");
}
The objects allocated by Rapier are leaking or crashing
The objects allocated by Rapier and owned by your application (the world, the event collectors, the snapshots, the
shared shapes, the controllers, etc.) must be freed by their dedicated Free function, e.g., r3FreeEventCollector,
and never with the free function of the C standard library. On the other hand, the objects inserted into the world
are owned by the world: they are freed by r3FreeWorld (or when removed), and must not be freed by your application.
The descriptions, the handles, and the other plain structures don't own anything and never need to be freed:
/* The objects allocated by Rapier are freed by their own Free function, never by `free`. */
R3EventCollector *events = r3NewEventCollector();
r3Step(world, NULL, events);
r3FreeEventCollector(events);
/* Freeing the world frees everything it contains, and invalidates all its handles. */
r3FreeWorld(world);
world = NULL;
Some fields of the Rapier structures contain garbage values
The C definitions given to the compiler must match the library your application is linked to: RAPIER_DIM2 or
RAPIER_DIM3, RAPIER_F32 or RAPIER_F64, but also RAPIER_FEM and RAPIER_ROBOTICS if the library is built with
the corresponding features, since they change the content of some structures (e.g. R3IntegrationParameters and
R3SoftBodyDesc), and RAPIER_PARALLEL which declares the functions of the parallel builds. The Rapier::rapier
CMake target defines them automatically. If you define them yourself, check them once at the start of your
application with r3CheckAbi, which reports an error if the version, the dimension, the precision, or the
definitions of the optional features (given by R3_ABI_FEATURES) don't match the library:
/* Checks the version, the dimension, the precision, and the RAPIER_FEM/RAPIER_ROBOTICS definitions,
* which change the layout of some structures (e.g. R3IntegrationParameters). */
if (r3CheckAbi(R3_ABI_VERSION, R3_DIMENSION, sizeof(R3Real), sizeof(R3Vector), sizeof(R3Pose),
R3_ABI_FEATURES) != R3_OK) {
fprintf(stderr, "Incompatible Rapier library: %s\n", r3LastError());
exit(EXIT_FAILURE);
}
A function fails with R3_WORLD_BUSY
A world can be read by several threads at once, but its modification (and its stepping) requires an exclusive
access. A modification conflicting with another access fails with the R3_WORLD_BUSY error instead of waiting. This
happens when the world is modified by another thread at the same time, or when a callback called during a timestep
(e.g. a physics hook) tries to access the world being stepped:
the callbacks must only read the world through the R3ReadContext they are given (with the r3Read functions, e.g.,
r3ReadRigidBody_Translation), and defer their modifications until the end of the timestep.
My modifications have no effect
Indexing a set of the world with a handle, e.g., world.rigid_bodies[handle], gives a live view of the object
stored in the world: modifying it modifies the simulation. However, the other objects given by the bindings are
generally copies, and modifying them has no effect on the world:
- Some structures given by the properties of another object, e.g., the
contact_softnessof theIntegrationParameters, are copies which must be assigned back to their property once modified (the docstring of each property tells whether it gives a copy or a live view). - A rigid-body or a collider built by your code (e.g. with
RigidBody.dynamic().build()) is copied into the set it is inserted into: once inserted, it must be modified through the view given by the set, not through the object you built. - The vectors, points, and rotations (e.g.
Vec3) are immutable:body.translation.y = 2.0raises anAttributeError, so a whole new value must be given to the property instead, e.g.,body.translation = (0.0, 2.0, 0.0).
# The structures given by the properties of the integration parameters are copies.
params = world.integration_parameters
softness = params.contact_softness
softness.natural_frequency = 60.0 # Modifies the copy only.
params.contact_softness = softness # Applies the modification.
# A rigid-body (or a collider) inserted into a set is copied into the set as well.
body = rp.RigidBody.dynamic(translation=(0.0, 3.0, 0.0)).build()
handle = world.rigid_bodies.insert(body)
body.linvel = (1.0, 0.0, 0.0) # No effect on the rigid-body of the world.
world.rigid_bodies[handle].linvel = (1.0, 0.0, 0.0) # Modifies the rigid-body of the world.
# The vectors are immutable: a whole new vector is given instead.
world.rigid_bodies[handle].translation = (0.0, 4.0, 0.0)
An exception is raised by PhysicsWorld.step
The exceptions raised by your physics hooks or by your
event handler don't interrupt the timestep
calling them: the first one is raised again by PhysicsWorld.step once the timestep is complete (by
PhysicsPipeline.step if you step the pipeline yourself), which is where it can be caught. Set
PhysicsWorld.event_error_policy to "strict" to skip the calls to the other callbacks of the timestep once one of
them failed:
class Hooks:
def modify_solver_contacts(self, context):
raise ValueError("Something went wrong in the hook.")
world.physics_hooks = Hooks()
try:
world.step()
except ValueError as error:
# Raised once the timestep is complete.
print("The hook failed:", error)
Keep in mind that the world is being modified while these callbacks are called: they can read the world (e.g.
through the colliders and bodies properties of the context they are given), but modifying it raises a
RuntimeError. The modifications they need are recorded, e.g., in the attributes of the hooks, and applied once the
timestep is complete:
class GroundContacts:
def __init__(self):
# The handles of the rigid-bodies touching the ground, recorded during the timestep.
self.bodies_to_push = []
def modify_solver_contacts(self, context):
# The world can be read here, but not modified: only record what must be done.
other_body = context.rigid_body2 if context.collider1 == ground else context.rigid_body1
if other_body is not None:
self.bodies_to_push.append(other_body)
hooks = GroundContacts()
world.physics_hooks = hooks
world.step()
# The world can be modified once the timestep is complete.
for body_handle in hooks.bodies_to_push:
world.rigid_bodies[body_handle].apply_impulse((0.0, 0.1, 0.0))
hooks.bodies_to_push.clear()
A RuntimeError is raised while the world is stepped
A world can't be modified while it is being stepped. A modification of the world, or of one of its structures,
raises a RuntimeError when:
- It is done by a physics hook or an event handler during the timestep (see above).
- Another thread is stepping the world at the same time (in this case, reading the world raises this error as well).
A world can be used from any thread, but not from several threads at once: synchronize these threads (e.g. with a
threading.Lock), or give each of them its own world (see threads and the GIL).
An InvalidHandle exception is raised
The handle of an object becomes stale as soon as its object is removed from the world: indexing a set with a
stale handle raises an InvalidHandle exception, and so does using a view obtained before the removal. Note that
removing a rigid-body also removes the joints attached to it, and its colliders, so their handles become stale as
well. If an object may have been removed by another part of your application, check its handle first with the in
operator, e.g., handle in world.rigid_bodies, or use the get method of the set which returns None instead (see
handles).