Builtin Library
README.md
debugDraw
Procedural geometry builders for GPU debug visualization: wireframe boxes,
spheres, capsules, crosses, octahedra, line segments, and camera-facing icon
billboards. Pure geometry: every builder writes packed vertices into a
caller-supplied Writer and returns how many vertices it added. Line geometry
is a non-indexed line soup (each line is two consecutive vertices) and
billboards are a triangle soup (each quad is six consecutive vertices), so
both draw through one static, grow-only sequential index buffer (0,1,2,…) that
never needs re-uploading per frame; the topology comes from the material.
Nothing here touches compute.* or renderer.*: callers own the buffers and
the Draw pass.
local debugDraw = require("@builtin::modules.debugDraw")
-- Keep one writer and reset it for each build: a steady rebuild allocates nothing.
local w = debugDraw.writer()
debugDraw.reset(w)
debugDraw.writeBox(w, -1, 0, -1, 1, 2, 1, 1, 0.8, 0, 1)
debugDraw.writeLine(w, 0, 0, 0, 0, 3, 0, 0, 1, 1, 1)
local vtx = substrate.createBuffer({
name = "my.vtx", type = "f32", len = buffer.len(w.buf) // 4,
kind = "gpu", usage = { "vertex" },
})
vtx:writeBytes(w.buf)
-- One static sequential index buffer, grown only when the vertex count does.
local idx = substrate.createBuffer({
name = "my.idx", type = "f32", len = w.count, kind = "gpu", usage = { "index" },
})
idx:writeBytes(debugDraw.buildSequentialIndexBytes(w.count))
The Draw pass draws w.count vertices; the bytes past them in w.buf are
storage the writer keeps for its next build.
Vertex layout
Every packed vertex matches the engine's standard Vertex layout (position,
normal, uv, joints, weights, node_index, tangent, color: 92 bytes,
little-endian) so the resulting buffer draws through the ordinary vertex
pipeline via a Draw pass. Debug geometry only needs position + color; every
other field is filled with a default.
Writer builders
A Writer is { buf: buffer, count: number }: buf grows by doubling and is
kept across builds, count is how many vertices the current build holds.
writer(capacity?): a new, empty writer with room forcapacityvertices.reset(w): emptywfor the next build, keeping its storage.
Each builder takes its points, rotation and colour as plain numbers (a
rotation is a unit quaternion qx, qy, qz, qw; a colour is r, g, b, a),
writes at the writer's end and returns the vertex count it added:
writeLine(w, ax, ay, az, bx, by, bz, r, g, b, a): one segment (2 verts).writeBox(w, minX, minY, minZ, maxX, maxY, maxZ, r, g, b, a): axis-aligned box, 12 edges (24 verts).writeOrientedBox(w, cx, cy, cz, qx, qy, qz, qw, hx, hy, hz, r, g, b, a): a posed box with half-extentshx, hy, hz.writeWireSphere(w, cx, cy, cz, radius, r, g, b, a): three orthogonal rings.writeWireCapsule(w, cx, cy, cz, qx, qy, qz, qw, radius, halfHeight, r, g, b, a): two rings + connectors.writeCross(w, cx, cy, cz, size, r, g, b, a): a 3-axis marker (6 verts).writeOctahedron(w, cx, cy, cz, size, r, g, b, a): a diamond marker (12 edges).writeBillboard(w, cx, cy, cz, r, g, b, a): one camera-facing quad (6 triangle-soup verts); every corner stores the centre and its uv corner, and thedebugBillboardsurface shader expands it toward the camera.writeWriter(dst, src): every vertexsrcholds, after the onesdstholds.writeRaw(w, bytes): pre-packed vertex bytes (wholeVERTEX_STRIDErecords).
Parts-table builders
The append* builders draw the same shapes through the writer builders and
append them to a { string } parts table, one packed string per vertex. They
take points as { x, y, z } tables, a rotation as an { x, y, z, w } table,
and a colour as an { r, g, b, a? } table (alpha 1 when left out, the default
colour when nil), and return the vertex count they added:
local vtxParts = {}
local verts = 0
verts += debugDraw.appendBox(vtxParts, bounds.min, bounds.max, { 1, 0.8, 0, 1 })
verts += debugDraw.appendLine(vtxParts, a, b, { 0, 1, 1, 1 })
vtx:writeBytes(debugDraw.buildVertexBytes(vtxParts))
appendLine(vtxParts, a, b, color?)appendBox(vtxParts, min, max, color?)appendOrientedBox(vtxParts, center, rotation, halfExtents, color?)appendWireSphere(vtxParts, center, radius, color?)appendWireCapsule(vtxParts, center, rotation, radius, halfHeight, color?)appendCross(vtxParts, center, size, color?)appendOctahedron(vtxParts, center, size, color?)appendBillboard(vtxParts, center, color?)buildVertexBytes(vtxParts): the parts joined into buffer-ready bytes.
Helpers
packVertex(x, y, z, color): pack one vertex; for building cached geometry blobs.packVertexUV(x, y, z, u, v, color): pack one vertex carrying an explicit uv (a billboard corner).quatRotate(q, v): rotate a{ x, y, z }vector by a unit quaternion table.rotate(qx, qy, qz, qw, vx, vy, vz):quatRotateon scalars.defaultColor(): the colour builders draw in when a caller states none.buildSequentialIndexBytes(count): the fixed0,1,2,…,count-1index buffer.VERTEX_STRIDE: 92, the byte stride of one packed vertex.
See @builtin::modules.debug_bounds for a caller and
@builtin::renderFeatures.debugViz for the Draw pass that renders the batch.