The Rapier testbed
The Rapier testbed is a small physics sandbox based on the kiss3d renderer.
It is designed to easily open a window and render a
The testbed is published as the rapier_testbed2d and rapier_testbed3d crates, which share the same version as Rapier
itself.
Example
A scene is one function building a PhysicsWorld, giving it to the viewer, and then stepping it from the rendering
loop. Note that the loop is what owns the simulation: the viewer only draws the state of the world it is given, and
tells whether the user asked for the simulation to keep running or to stop:
async fn bouncing_ball(viewer: &mut TestbedViewer) {
// The scene itself, built like in any other application.
let mut world = PhysicsWorld::new();
world.insert_collider(ColliderBuilder::cuboid(100.0, 0.1, 100.0), None);
world.insert(
RigidBodyBuilder::dynamic().translation(Vector::new(0.0, 10.0, 0.0)),
ColliderBuilder::ball(0.5).restitution(0.7),
);
// Hand the world to the viewer, and place the camera.
viewer.set_world(&mut world);
viewer.look_at(Vec3::new(10.0, 10.0, 10.0), Vec3::ZERO);
// The rendering loop: it ends when the user closes the window or selects another scene.
while viewer.render_frame(&mut world).await {
if viewer.simulating() {
world.step();
}
}
}
The testbed itself is started from the main function of the application, which lists the scenes it can switch
between, and runs the one to display. Note that this function relies on the kiss3d::main attribute, therefore
kiss3d must be a dependency of the application as well:
#[kiss3d::main]
async fn main() {
// The scenes listed in the side panel of the testbed, as (group, name) pairs.
let entries = vec![ExampleEntry::new("Demos", "Bouncing ball")];
let mut viewer = TestbedViewer::new(entries).await;
bouncing_ball(&mut viewer).await;
}
Running the demos
The examples of Rapier are all combined in a single application listing them in a side panel. Running them is the fastest way to get an overview of Rapier’s features and performances.
git clone https://github.com/dimforge/rapier
cd rapier
cargo run --release --bin all_examples2 --features parallel # 2D demos
cargo run --release --bin all_examples3 --features parallel # 3D demos
Always run the testbed (and Rapier in general) in release mode: a debug build is up to 100 times slower.
Parallelism is enabled with --features parallel.
The C bindings come with their own testbed: a small physics sandbox written in C, based on the raylib renderer and on Dear ImGui for its user interface. It is designed to render a physics world, and provides basic controls like play/pause, grabbing objects with the mouse, changing simulation settings (timestep, gravity, solver iterations, number of threads, etc.), saving and restoring snapshots, debug-rendering, etc. It runs the C ports of all the demos of Rapier, which are also the most complete examples of usage of the C API.
Running the demos
The testbed is part of the c folder of the Rapier repository, and is built
by enabling the RAPIER_BUILD_TESTBED option of CMake. It downloads its graphics dependencies during its first
configuration (on Linux, the X11 and OpenGL development packages must be installed). From the root of the repository:
cmake -S bindings/c -B build/c3 -DRAPIER_BUILD_TESTBED=ON -DRAPIER_DIMENSION=3 -DCMAKE_BUILD_TYPE=Release
cmake --build build/c3 --target rapier_testbed --config Release --parallel
./build/c3/testbed/rapier_testbed # 3D demos
The 2D demos are built the same way, in another build directory, with -DRAPIER_DIMENSION=2. The demos relying on
optional features (the FEM solver of the soft-bodies, the URDF and MJCF loaders) are only listed if these features
are enabled, e.g., with -DRAPIER_FEATURES=fem,robotics. A demo can be selected at startup with the --example
argument (e.g. --example restitution3), and the number of threads used by the simulation with --threads (the
parallelism is enabled by default for the testbed).
Always run the testbed (and Rapier in general) in release mode: a debug build is up to 100 times slower. The build mode of the Rapier library actually loaded is displayed by the testbed.
Controls
T: play/pause.S: execute one timestep.R: restart the demo.F: frame the whole simulation.- Left drag: pull a dynamic object (or a particle of a soft-body) with a spring joint.
- Right drag: rotate the camera around its target in 3D, or pan it in 2D. Shift + right drag (or middle drag): pan the camera in 3D. Mouse wheel: zoom.
- The side panel gives access to the list of demos, the simulation settings, the performance measurements (the time spent by the engine for each timestep, etc.), and the debug-rendering options.
Headless runs
The rapier_testbed_headless executable (built together with the testbed) runs the same demos without opening any
window, which is useful to check that a demo runs without errors, or to measure its performance:
./build/c3/testbed/rapier_testbed_headless --list
./build/c3/testbed/rapier_testbed_headless --example restitution3 --steps 120 --threads 1
Writing a scene
Unlike the Rust testbed, the C testbed isn't a library meant to be used by your own application: it is an
application of the Rapier repository, which demos are compiled with it. Each demo is one C function (e.g.
tbRestitution3 in bindings/c/testbed/examples3d/restitution3.c) building a world like in any other application, giving it
to the viewer, and then stepping it from the rendering loop. Note that the loop is what owns the simulation: the viewer
only draws the state of the world it is given, and tells whether the user asked for the simulation to keep running or
to stop:
void tbBouncingBall3(Testbed *testbed) {
/* The scene itself, built like in any other application. */
R3World *world = r3NewWorld();
R3ColliderDesc ground = r3CuboidColliderDesc(r3Vector(100.0, 0.1, 100.0));
r3InsertColliderWithoutParent(world, &ground);
R3RigidBodyDesc ball_body = r3DynamicRigidBodyDesc();
ball_body.position.translation = r3Vector(0.0, 10.0, 0.0);
R3ColliderDesc ball = r3BallColliderDesc(0.5);
ball.restitution = 0.7;
r3InsertCollider(r3InsertRigidBody(world, &ball_body), &ball);
/* Hand the world to the viewer, and place the camera (eye, then target). */
tbSetWorld(testbed, world);
tbCamera(testbed, 10, 10, 10, 0, 0, 0);
/* The rendering loop: it ends when the user closes the window or selects another scene. The
* viewer may replace the world, e.g., when a snapshot is restored, hence its address. */
while (tbRenderFrame(testbed, &world)) {
if (tbSimulating(testbed)) {
r3Step(world, NULL, NULL);
}
}
r3FreeWorld(world);
}
Therefore, prototyping a scene with the testbed (e.g. for reproducing a problem before opening an issue) is done in
your local copy of the repository: its source file is added to the bindings/c/testbed/examples3d folder (resp.
examples2d), which files are all compiled with the testbed, and its function is added to the demos listed by the
tbExamples array of bindings/c/testbed/registry3.c (resp. registry2.c):
/* An entry of the `tbExamples` array: identifier, group, name, source, scene function, and the
* reason why it is unavailable (NULL if it is available). */
TbExample entry = {"bouncing_ball3", "Demos", "Bouncing ball", "examples3d/bouncing_ball3.c",
tbBouncingBall3, NULL};
The registries are generated from the list of the Rust demos by the bindings/c/testbed/update_catalog.py script, which
doesn't know about the entries added by hand: they are lost whenever the registries are generated again.
The Python bindings come with their own testbed: a small physics sandbox written in Python, based on the Panda3D renderer. It is designed to easily open a window and render a simulation, and provides basic controls like play/pause, single-stepping, restarting the scene, switching between scenes, or showing the wireframe of the colliders with the debug-renderer. It runs the Python ports of most of the 3D demos of Rapier, which are also good examples of usage of the bindings. It is generally the quickest way of prototyping a scene, or of reproducing a problem before opening an issue.
Installation
The testbed is published as the rapier-testbed package, which depends on rapier3d, NumPy, and Panda3D:
pip install rapier-testbed
It can also be installed from the bindings/python/rapier-testbed folder of the
Rapier repository, e.g., to use it with bindings
built from source. From the root of the repository, and with your
virtual environment activated (--no-deps keeps the bindings you built instead of downloading them, and -e makes
your modifications of the testbed effective without reinstalling it):
pip install panda3d numpy
pip install --no-deps -e ./bindings/python/rapier-testbed
Running the demos
The demos are the modules of the rapier_testbed.examples3 package (e.g. rapier_testbed/examples3/domino3.py).
Running the rapier_testbed module lists them by category in the terminal, and asks which one to open. A demo can
also be opened directly by running its module:
python -m rapier_testbed # List the demos, and open the selected one.
python -m rapier_testbed.examples3.domino3 # Open the domino demo.
The bindings/python/examples_tour.py script of the repository opens every demo in turn, each of them being opened when the
window of the previous one is closed.
Always run the testbed with bindings built in release mode (see common mistakes): a debug build is up to 100 times slower.
Controls
Space: play/pause. Right arrow: execute one timestep.R: restart the demo.Tab: open the next demo (the digits1to9open the demo with this number).W: toggle the wireframe of the colliders.Esc: close the demo.- Left drag: rotate the camera around its target. Right drag: pan the camera. Mouse wheel: zoom.
- The time spent by the simulation and by the rendering of each frame, as well as the frame rate, are displayed at the top of the window.
Headless runs
If the PANDA_NO_WINDOW environment variable is set to 1, the testbed runs the demo for a fixed number of timesteps
without opening any window, which is useful to check that a demo runs without errors (e.g. in a CI):
PANDA_NO_WINDOW=1 python -m rapier_testbed.examples3.domino3
Writing a scene
A scene is one function building a physics world, and giving it to the
testbed with Testbed.set_world. Unlike the testbed of the Rust version of Rapier, the testbed owns the simulation
loop: it steps the world it is given at each frame (with its gravity, integration parameters, physics hooks, and event
handler), and calls the functions registered by the scene with Testbed.add_callback after each timestep, e.g., to
control the scene or to read the results of the simulation:
def bouncing_ball(testbed):
# The scene itself, built like in any other application.
world = rp.PhysicsWorld(gravity=(0.0, -9.81, 0.0))
world.add_collider(rp.Collider.cuboid(100.0, 0.1, 100.0))
ball_handle = world.add_body(
rp.RigidBody.dynamic(translation=(0.0, 10.0, 0.0)),
colliders=[rp.Collider.ball(0.5).restitution(0.7)],
)
# Hand the world to the testbed, and place the camera.
testbed.set_world(world)
testbed.look_at((10.0, 10.0, 10.0), (0.0, 0.0, 0.0))
# A function called after each timestep, e.g., to control the scene.
def print_altitude(testbed):
print("Ball altitude:", world.rigid_bodies[ball_handle].translation.y)
testbed.add_callback(print_altitude)
The scene is then registered with a category and a name, which makes it available in the list of the testbed together with the demos, and the testbed can be opened on it directly:
from rapier_testbed import register, run
# The scenes listed by the picker of the testbed, as (category, name) pairs.
register("Demos", "Bouncing ball", bouncing_ball)
if __name__ == "__main__":
# Open the testbed directly on this scene.
run(initial="Demos / Bouncing ball")
Instead of a world, Testbed.set_world also accepts the sets of a simulation (the rigid-body, collider,
impulse-joint, and multibody-joint sets, and optionally the soft-body set), which the testbed then steps with its own
pipeline and integration parameters, and with a gravity of along the axis. In this case, the gravity, the
physics hooks, and the
event handler of the simulation are given
with Testbed.set_gravity, Testbed.set_hooks, and Testbed.set_event_handler.