Learn Simulant
Everything you need to know to build games with Simulant
Debug Drawing
Source: simulant/nodes/debug.h
Every Stage exposes a debug property of type Debug* that lets you render temporary lines, rays, and points directly in the scene. This is useful for visualizing physics shapes, raycasts, bounding volumes, AI paths, and any other runtime geometry without creating permanent mesh assets.
Basic Usage
#include <simulant/nodes/debug.h>
// Draw a red line from the origin to (1, 1, 1)
stage->debug->draw_line(
Vec3(0, 0, 0), // start
Vec3(1, 1, 1), // end
Color::red(), // color
Seconds(), // duration (0 = one frame)
false // depth test
);
// Draw a ray (start + direction)
stage->debug->draw_ray(
Vec3(0, 0, 0), // origin
Vec3(0, 1, 0), // direction
Color::white(), // default color
Seconds(), // default: one frame
false // no depth test
);
// Draw a blue point
stage->debug->draw_point(
Vec3(5, 3, 2), // position
Color::blue(), // color
Seconds(1.0f), // lasts 1 second
false // visible through geometry
);
All three drawing functions share the same parameter pattern:
| Parameter | Type | Default | Description |
|---|---|---|---|
start / position |
Vec3 |
(required) | World-space start point |
end / dir |
Vec3 |
(required) | World-space end point (line) or direction vector (ray) |
color |
Color |
Color::white() |
Line / point color |
duration |
Seconds |
Seconds() (one frame) |
How long the element persists |
depth_test |
bool |
false |
Whether the element is occluded by scene geometry |
Duration and Lifecycle
The duration parameter controls how long a debug element persists on screen:
Seconds()(default) -- the element is drawn for one frame only. Call every frame to keep it visible.Seconds(1.0f)-- the element persists for 1 second automatically, then fades out.Seconds(5.0f)-- persists for 5 seconds.
This is useful for drawing one-shot debug visuals (e.g., collision impact points) without managing their lifecycle manually.
// One-frame: must be called every update
stage->debug->draw_line(a, b, Color::green());
// Persists for 2 seconds, no further calls needed
stage->debug->draw_point(impact_point, Color::red(), Seconds(2.0f));
Depth Testing
By default, debug elements are drawn without depth testing so they are always visible on top of scene geometry. Pass true for the depth_test parameter to make them respect the depth buffer:
// Always visible (default)
stage->debug->draw_line(start, end, Color::yellow());
// Occluded by walls, floors, etc.
stage->debug->draw_line(start, end, Color::yellow(), Seconds(), true);
Configuration
Adjust the visual size of lines and points globally:
// Adjust visual size
stage->debug->set_point_size(0.05f);
stage->debug->set_line_width(0.02f);
float ps = stage->debug->point_size();
float lw = stage->debug->line_width();
These settings affect all subsequently drawn elements. Points are rendered as small axis-aligned quads (since many platforms do not support native point sprite rendering).
Practical Examples
Visualize a Velocity Vector
stage->debug->draw_ray(
entity->translation(),
entity->velocity().normalized() * 2.0f,
Color::green()
);
Draw a Bounding Box Wireframe
The Debug class does not have a built-in draw_box function, but you can easily draw an AABB wireframe by connecting its corners with draw_line:
void draw_aabb(Debug* debug, const AABB& aabb, const Color& color,
Seconds duration = Seconds(), bool depth_test = false) {
auto corners = aabb.corners();
// Bottom face
debug->draw_line(corners[0], corners[1], color, duration, depth_test);
debug->draw_line(corners[1], corners[2], color, duration, depth_test);
debug->draw_line(corners[2], corners[3], color, duration, depth_test);
debug->draw_line(corners[3], corners[0], color, duration, depth_test);
// Top face
debug->draw_line(corners[4], corners[5], color, duration, depth_test);
debug->draw_line(corners[5], corners[6], color, duration, depth_test);
debug->draw_line(corners[6], corners[7], color, duration, depth_test);
debug->draw_line(corners[7], corners[4], color, duration, depth_test);
// Vertical edges
debug->draw_line(corners[0], corners[4], color, duration, depth_test);
debug->draw_line(corners[1], corners[5], color, duration, depth_test);
debug->draw_line(corners[2], corners[6], color, duration, depth_test);
debug->draw_line(corners[3], corners[7], color, duration, depth_test);
}
Mark a Collision Point
stage->debug->draw_point(collision_point, Color::red(), Seconds(0.5f));
Visualize a Raycast
auto result = physics->ray_cast(origin, direction, 100.0f);
// Draw the ray
stage->debug->draw_ray(origin, direction * 100.0f, Color::cyan());
// Mark the hit point if one was found
if (result) {
stage->debug->draw_point(result->impact_point, Color::red(), Seconds(1.0f));
}
Draw a Skeleton Hierarchy
// Assuming you have access to joint absolute positions
for (auto& joint : skeleton->joints()) {
if (joint->parent()) {
debug->draw_line(
joint->parent()->absolute_translation(),
joint->absolute_translation(),
Color::yellow()
);
}
}
Colors
Simulant provides the Color struct with several static factory methods for common colors:
#include <simulant/color.h>
Color::white();
Color::black();
Color::red();
Color::green();
Color::blue();
Color::yellow();
Color::purple();
Color::turquoise();
Color::gray();
Color::none(); // fully transparent
// Custom color (RGBA, 0.0 - 1.0 range)
Color custom(1.0f, 0.5f, 0.0f, 1.0f);
// From 8-bit bytes (0 - 255 range)
Color from_bytes = Color::from_bytes(255, 128, 0, 255);
Performance Considerations
- Debug drawing is intentionally lightweight. Elements are batched into a single mesh each frame and rendered with a built-in unlit material.
- Debug elements are drawn at the absolute foreground render priority, so they appear on top of all scene geometry (unless
depth_testistrue). - Depth writing is always disabled for debug rendering, so debug elements never occlude other geometry.
- Because elements with a non-zero
durationare stored internally and updated each frame, avoid creating a large number of long-lived elements. Use one-frame duration and redraw explicitly when you need dense, persistent visuals. - Calling
draw_rayis a thin wrapper arounddraw_line-- it simply draws fromstarttostart + dir.
Physics Debug Integration
The engine's built-in physics service (PhysicsService) uses the Debug node to render collision shapes when a debug overlay is active. The physics DebugDraw implementation calls draw_line and draw_point under the hood with per-frame duration:
// Inside the physics service update (simplified)
if (debug_) {
DebugDraw draw(debug_);
for (b3Body* b = scene_->GetBodyList(); b; b = b->GetNext()) {
for (b3Fixture* f = b->GetFixtureList(); f; f = f->GetNext()) {
f->Draw(&draw, b3Color(1, 1, 1, 1));
}
}
}
This means that simply connecting a Debug node to your physics service will automatically render body shapes, joints, and contact points every frame without any additional code.