Skip to main content

Debug-renderer

Rapier is a physics engine, it doesn't render anything. What is displayed by your application comes from your own renderer, and game assets generally don’t match the shapes seen by the physics engine exactly. Therefore a collider given the wrong size, a joint attached at the wrong place, or a rigid-body that is not where its sprite is, can be difficult to debug (and easy to misinterpret as physics-engine-bugs).

To help with debugging physics, Rapier’s debug-renderer exists to convert the content of the physics scene into a set of colored lines that your application to obtain a wireframe view of what Rapier actually sees.

Despite its name, the debug-renderer doesn’t actually contains any windowing/rasterization/shader code. Instead, r3DebugRender copies the set of lines that needs to be displayed by your own graphics engine into a buffer of R3DebugLine given by the application. Each line is given by its world-space end points (a and b) and its color. This makes it possible to integrate the debug-rendering to any graphics stack your application might use. Note that the colors are given in the HSLA format (the hue being in degrees), therefore a renderer expecting RGBA colors is expected to convert them:

// The lines are given with HSLA colors. A renderer expecting RGBA colors has to convert them.
static void hsla_to_rgba(const float hsla[4], float rgba[4]) {
float h = fmodf(hsla[0], 360.0f) / 60.0f;
float c = (1.0f - fabsf(2.0f * hsla[2] - 1.0f)) * hsla[1];
float x = c * (1.0f - fabsf(fmodf(h, 2.0f) - 1.0f));
float m = hsla[2] - c / 2.0f;
float r = 0.0f, g = 0.0f, b = 0.0f;

if (h < 1.0f) {
r = c;
g = x;
} else if (h < 2.0f) {
r = x;
g = c;
} else if (h < 3.0f) {
g = c;
b = x;
} else if (h < 4.0f) {
g = x;
b = c;
} else if (h < 5.0f) {
r = x;
b = c;
} else {
r = c;
b = x;
}

rgba[0] = r + m;
rgba[1] = g + m;
rgba[2] = b + m;
rgba[3] = hsla[3];
}

r3DebugRender is then called once per rendered frame. Its mode argument is a combination of the R3_DEBUG_* flags (e.g. R3_DEBUG_COLLIDER_SHAPES or R3_DEBUG_CONTACTS) that selects what is drawn, so you can select only the element types you are interested in debugging. Just like the scene queries returning several results, calling it with a NULL buffer and a zero capacity gives the number of lines, then a second call with a buffer large enough copies them (keep in mind that each of these calls computes all the lines):

// The mode selects what is drawn.
uint32_t mode = R2_DEBUG_COLLIDER_SHAPES | R2_DEBUG_CONTACTS;
// The buffer receiving the lines is kept from one frame to the next.
R2DebugLine *lines = NULL;
size_t capacity = 0;
size_t num_lines = 0;

for (int i = 0; i < 10; i++) {
r2Step(world, NULL, NULL);

// The debug-rendering is done after the step, once per frame to be drawn:
// get the number of lines, grow the buffer if needed, then copy the lines.
num_lines = r2DebugRender(world, mode, NULL, 0);
if (num_lines > capacity) {
capacity = num_lines;
lines = realloc(lines, capacity * sizeof(*lines));
}
num_lines = r2DebugRender(world, mode, lines, capacity);

for (size_t j = 0; j < num_lines; j++) {
float rgba[4];
hsla_to_rgba(lines[j].color, rgba);
// Give the segment from `lines[j].a` to `lines[j].b`, with the color `rgba`,
// to the renderer of the application.
}
}

printf("%zu lines to draw\n", num_lines);
free(lines);

The lines are drawn with the default style. The colors and various length properties of the lines can be customized by giving an R3DebugRenderStyle, initialized by r3DefaultDebugRenderStyle, to r3DebugRenderWithStyle:

// The style gives the colors (in HSLA) and sizes of the lines.
R2DebugRenderStyle style = r2DefaultDebugRenderStyle();
// Draw the colliders attached to dynamic rigid-bodies in blue.
style.collider_dynamic_color[0] = 240.0f;
style.collider_dynamic_color[1] = 1.0f;
style.collider_dynamic_color[2] = 0.5f;
style.collider_dynamic_color[3] = 1.0f;
// Draw longer contact normals.
style.contact_normal_length = 0.5;

num_lines = r2DebugRenderWithStyle(world, mode, &style, NULL, 0);
lines = malloc(num_lines * sizeof(*lines));
num_lines = r2DebugRenderWithStyle(world, mode, &style, lines, num_lines);
warning

The debug-rendering is not free: it walks every collider of the scene and converts its shape into lines at each frame. Therefore it is meant to be enabled only when debugging rather than a player-facing representation of the game objects.