The Rapier testbed
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.