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, the DebugRenderPipeline gives the set of lines that needs to be displayed by your own graphics engine (e.g. matplotlib, pygame, or any 3D viewer) to a backend. This makes it possible to integrate the debug-rendering to any graphics stack your application might use. The backend can be any object with a draw_line method (as described by the DebugRenderBackend protocol), which is called for each line with the kind of object the line belongs to (a DebugRenderObject), the two end points of the line, and its color. Note that the colors are given in the HSLA format (the hue being in degrees), therefore a backend expecting RGBA colors is expected to convert them, e.g., with the DebugColor.rgba property:

# The backend receives the lines to be drawn. A real one would push them to the renderer of the
# application instead of collecting them.
class LineCollector:
def __init__(self):
self.lines = []

def draw_line(self, object, a, b, color):
# The color is given in the HSLA format: `color.rgba` converts it to RGBA.
self.lines.append((a, b, color.rgba))

The DebugRenderPipeline is then given the sets of the world once per rendered frame (the soft-bodies being optional). Its DebugRenderStyle specifies the colors and various length properties, whereas its DebugRenderMode selects what is drawn (its flags being combined with the | operator) so you can select only the element types you are interested in debugging:

# The style gives the colors and sizes, the mode selects what is drawn.
debug_render = rp.DebugRenderPipeline(
style=rp.DebugRenderStyle(),
mode=rp.DebugRenderMode.COLLIDER_SHAPES | rp.DebugRenderMode.CONTACTS,
)
backend = LineCollector()

for _ in range(10):
world.step()

# The debug-rendering is done after the step, once per frame to be drawn.
backend.lines.clear()
debug_render.render(
world.rigid_bodies,
world.colliders,
world.impulse_joints,
world.multibody_joints,
world.narrow_phase,
backend,
soft_bodies=world.soft_bodies,
)

print(f"{len(backend.lines)} lines to draw")

Calling a Python method for each line is slow. If you don't need to process the lines one by one, DebugRenderPipeline.render_to_arrays computes all of them without calling back into Python, and gives them as NumPy arrays. Giving a DebugLineCollector as the backend of DebugRenderPipeline.render also avoids these calls, the lines being collected until DebugLineCollector.clear is called:

# All the lines are computed without calling back into Python.
lines, colors, objects = debug_render.render_to_arrays(
world.rigid_bodies,
world.colliders,
world.impulse_joints,
world.multibody_joints,
world.narrow_phase,
soft_bodies=world.soft_bodies,
)
# `lines` has the shape (N, 2, 3): the two end points of each line.
# `colors` has the shape (N, 4): the RGBA color of each line.
# `objects` has the shape (N,): the kind of object each line belongs to.
collider_lines = lines[objects == rp.DebugRenderObject.COLLIDER.kind]
print(f"{len(collider_lines)} lines for the collider shapes")

Finally, the style and the mode can be changed at any time with the style and mode properties of the pipeline. The style property gives the style used by the pipeline itself, so modifying it changes the next renders (a whole DebugRenderStyle can also be assigned to it):

# The style is modified in place: the next renders take it into account.
debug_render.style.subdivisions = 40 # Smoother curved shapes.
debug_render.style.contact_normal_length = 0.5
debug_render.style.collider_fixed_color = rp.DebugColor.from_rgba(0.5, 0.5, 0.5, 1.0)

# Also draw the AABBs of the colliders.
debug_render.mode = debug_render.mode | rp.DebugRenderMode.COLLIDER_AABBS
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.