Table of Contents

Debug draw

Box3D can draw what it is simulating — shapes, contacts, joints, bounds, islands — into whatever renderer you already have. It knows nothing about OpenGL, Vulkan, Unity or Godot: it hands over points, segments and boxes in world space and you decide what a line looks like.

Two interfaces, and a drawn frame allocates nothing.

The two halves

Called Job
IDebugShapeFactory once per shape Turn a shape into a drawable and return an opaque handle
IDebugDrawer every frame Receive the handles with their transforms, plus every other primitive

The split is what makes debug draw usable rather than a slideshow: a real renderer uploads a mesh once in the factory and issues a draw call per frame in the drawer.

The factory has to be supplied when the world is built, because Box3D needs those callbacks at construction time:

var factory = new MyShapeFactory();
using var world = new PhysicsWorld(WorldSettings.Default, factory);

DestroyShape is called when a shape is modified or destroyed, and for everything still alive when the world is disposed. Neither factory method may touch the world.

Drawing a frame

var drawer = new MyDrawer(renderer);

world.Draw(ref drawer, DebugDrawOptions.Default with
{
    DrawShapes = true,
    DrawJoints = true,
});

Implement the drawer on a struct passed by ref. The calls arrive through function pointers with no delegate, no closure and no boxing, so the JIT can inline them and the frame allocates nothing — provided your implementation does not allocate either.

Every method is called synchronously, on the thread that called Draw, and none of them may touch the world being drawn. Reading or writing the simulation from inside a draw callback is the same race as doing it mid-step.

A drawer that has no shape factory can leave DrawShape empty and still get every segment, point and box.

What can be drawn

DebugDrawOptions.Default has everything off. Turn on what you want to see:

Option Shows
DrawShapes The geometry itself, through the factory
DrawBounds Broad-phase bounding boxes
DrawMass Centres of mass, with the mass as text
DrawSleep Which bodies are asleep
DrawJoints, DrawJointExtras, DrawAnchorA Joint frames, and how hard they are working
DrawContacts, DrawContactNormals, DrawContactFeatures, DrawContactForces What the solver is working with
DrawIslands, DrawGraphColors How the solver has partitioned the scene
DrawBodyNames The Name from each body's definition

ForceScale and JointScale set how long the force and joint markers are; Bounds clips drawing to a region, which is how you draw only what the camera can see.

Drawing part of a world

CategoryMask restricts a call to some collision categories, and the options apply to the whole call, so seeing annotations on some bodies and not others means two calls:

// Everything, plainly.
world.Draw(ref drawer, DebugDrawOptions.Default with { DrawShapes = true });

// Contacts and bounds, but only for the dynamic bodies.
world.Draw(ref drawer, DebugDrawOptions.Default with
{
    DrawContacts = true,
    DrawContactNormals = true,
    DrawBounds = true,
    CategoryMask = (ulong)Layers.Dynamic,
});

Without the mask, the floor's bounding box — eighty metres across — is the only thing in the picture.

Text and colour

Box3D emits labels through DrawString: the mass over a body, the separation at a contact point. It uses the ninety-five printable ASCII characters and nothing else, so a bitmap font is enough.

DebugColor is the suggested colour, packed as 0xMMRRGGBB — the top byte is a DebugMaterial hint, and ToUnitRgb gives the three floats a shader wants. Suggested is the operative word: a drawer is free to ignore it.

A worked implementation

src/Box3D.NET.Visualizer is a complete drawer: a software rasterizer with its own PNG and GIF writers, no dependencies beyond the base class library, using Box3D.NET through these two interfaces and no privileged access. Every picture in the gallery comes out of it.

The smaller debug-draw sample is the shorter read if all you want is the shape of the code.