17. 2d Debug Drawing
In this lesson, we'll extend the debug drawing system with some 2D shapes. This allows us to visualise collision shapes both in 3D and 2D.
Overview
Our debug drawing system currently supports:
- 3D shapes (rectangles, circles, spheres, boxes) rendered in world space
We'll add:
- 2D shapes (rectangles, circles) - rendered as screen overlays in this case
- Text rendering with custom fonts
Adding Font Support
First, we need to add font support for clean text rendering. Add a new font file to assets (https://github.com/i-tu/Hasklig):
assets/fonts/Hasklig-Regular.ttf
You can use any font you like. I think this font is nice as a debug font.
Then update the game state to include font loading:
// In main.odin - Game_State struct
debug: struct {
font: rl.Font,
draw_commands: [dynamic]Debug_Draw_Command,
draw_commands_2d: [dynamic]Debug_Draw_Command_2D,
},
Load the font during initialisation:
// In main() - after rl.InitWindow()
gs.debug.font = rl.LoadFontEx("assets/fonts/Hasklig-Regular.ttf", 16, nil, 128)
Debug Command Structure Refactoring
Let's reorganise our debug command structures for better consistency. Move the color field to the end of each struct:
Debug_Draw_Command_Rect_3D :: struct {
rect: Rect,
center: Vec3,
z_offset: f32,
rotation_matrix: Mat4,
color: rl.Color,
}
Debug_Draw_Command_Circle_3D :: struct {
center: Vec3,
radius: f32,
axis: Vec3,
angle: f32,
color: rl.Color,
}
Debug_Draw_Command_Sphere :: struct {
center: Vec3,
radius: f32,
color: rl.Color,
}
Debug_Draw_Command_Box :: struct {
obb: OBB3,
color: rl.Color,
}
Adding 2D Debug Commands
Create new structures for 2D debug drawing:
Debug_Draw_Command_Rect :: struct {
rect: Rect,
color: rl.Color,
}
Debug_Draw_Command_Circle :: struct {
center: Vec2,
radius: f32,
color: rl.Color,
}
Debug_Draw_Command_2D :: union {
Debug_Draw_Command_Rect,
Debug_Draw_Command_Circle,
}
Debug Drawing Functions
Add functions to push 2D debug commands and flush them:
debug_draw_push_2d :: proc(cmd: Debug_Draw_Command_2D) {
append(&gs.debug.draw_commands_2d, cmd)
}
debug_draw_flush_2d :: proc() {
for cmd in gs.debug.draw_commands_2d {
switch v in cmd {
case Debug_Draw_Command_Rect:
rl.DrawRectangleRec(v.rect, v.color)
case Debug_Draw_Command_Circle:
rl.DrawCircleV(v.center, v.radius, v.color)
}
}
clear(&gs.debug.draw_commands_2d)
}
Text Rendering Helpers
Create convenient text rendering functions:
debug_draw_text_ex :: proc(pos: Vec2, color: rl.Color, format: string, args: ..any) {
rl.DrawTextEx(gs.debug.font, fmt.ctprintf(format, ..args), pos, 16, 0, color)
}
debug_draw_text :: proc(pos: Vec2, format: string, args: ..any) {
rl.DrawTextEx(gs.debug.font, fmt.ctprintf(format, ..args), pos, 16, 0, rl.WHITE)
}
Cross-Section Visualisation
The most interesting addition is the cross-section visualisation in the physics system. This shows 2D representations of how a sphere intersects with oriented bounding boxes.
In the collision detection code, add visualisation for each cross-section:
// Calculate visualisation parameters
largest_obb_axis_size := max(obb.size.x, max(obb.size.y, obb.size.z))
viz_scale := f32(50)
viz_offset := Vec2{f32(gs.window_width) - largest_obb_axis_size * viz_scale - 20, 20}
// XY Cross-section (green)
viz_color := rl.Color{64, 255, 64, 80}
debug_draw_push_2d(Debug_Draw_Command_Rect{
rect = {
viz_offset.x,
viz_offset.y,
cross_section_xy.width * viz_scale,
cross_section_xy.height * viz_scale
},
color = viz_color,
})
debug_draw_push_2d(Debug_Draw_Command_Circle{
center = viz_offset + (local_sphere.xy + (obb.size.xy / 2)) * viz_scale,
radius = sphere_radius * viz_scale,
color = viz_color,
})
Repeat similar code for XZ (red) and ZY (blue) cross-sections, adjusting the viz_offset.y and colors accordingly.
Updating the Render Loop
Update the render function to use the new debug text system:
render :: proc() {
// ... existing 3D rendering code ...
rl.EndMode3D()
debug_draw_flush_2d()
debug_draw_text(Vec2{8, 8}, "Frame: %d, Frame Time: %f, FPS: %d, Session: %f",
gs.time.frame, gs.time.delta, gs.time.fps, gs.time.session)
debug_draw_text(Vec2{8, 26}, "Up: %v, Down: %v, Left: %v, Right: %v",
gs.input.up, gs.input.down, gs.input.left, gs.input.right)
rl.EndDrawing()
}
Understanding Cross-Section Visualisation
The cross-section visualisation shows three 2D views of how a sphere interacts with an oriented box:
- XY Cross-section (Green): Looking down the Z-axis
- XZ Cross-section (Red): Looking down the Y-axis
- ZY Cross-section (Blue): Looking down the X-axis
Each visualisation shows:
- A rectangle representing the box's cross-section in that plane
- A circle representing the sphere's cross-section in that plane
This is incredibly useful for debugging collision detection, as you can see exactly how the shapes intersect in each dimension.
Key Benefits
This debug drawing system provides:
- Visual Debugging: See collision shapes in real-time
- Cross-section Analysis: Understand 3D collisions through 2D projections
The system is designed to be non-intrusive - debug commands are queued during physics calculations and flushed during rendering, keeping the separation of concerns clean.
Next Steps
With this debug drawing system in place, we can:
- Add more debug shape types as needed
- Implement toggleable debug modes
- Add colour coding for different collision states
The cross-section visualisation is particularly powerful for understanding complex 3D collision scenarios and will be useful as we implement the collision response code in the next lessons.