Log inGet started
▣
module · drop-in viewer
asset⌬ modulemoduleprimary: init.luau·originates fromworld 07158574-5…

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 appends packed vertex bytes to a caller-supplied accumulator and returns how many vert…

byzero-proxy @ DESKTOP-DB3UJOJ·posted 2mo ago
What it does

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 appends packed vertex bytes to a caller-supplied accumulator 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")

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 })

local vtxBytes = debugDraw.buildVertexBytes(vtxParts)
local vtx = substrate.createBuffer({
    name = "my.vtx", type = "f32", len = math.ceil(#vtxBytes / 4),
    kind = "gpu", usage = { "vertex" },
})
vtx:writeBytes(vtxBytes)

-- One static sequential index buffer, grown only when the vertex count does.
local idx = substrate.createBuffer({
    name = "my.idx", type = "f32", len = verts, kind = "gpu", usage = { "index" },
})
idx:writeBytes(debugDraw.buildSequentialIndexBytes(verts))

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.

API

Each builder appends line-soup vertices to vtxParts and returns the vertex count it added:

  • appendLine(vtxParts, a, b, color?) — one segment (2 verts).
  • appendBox(vtxParts, min, max, color?) — axis-aligned box, 12 edges (24 verts).
  • appendOrientedBox(vtxParts, center, rotation, halfExtents, color?) — a posed box.
  • appendWireSphere(vtxParts, center, radius, color?) — three orthogonal rings.
  • appendWireCapsule(vtxParts, center, rotation, radius, halfHeight, color?) — two rings + connectors.
  • appendCross(vtxParts, center, size, color?) — a 3-axis marker (6 verts).
  • appendOctahedron(vtxParts, center, size, color?) — a diamond marker (12 edges).
  • appendBillboard(vtxParts, center, color?) — one camera-facing quad (6 triangle-soup verts) at center; every corner stores center and its uv corner, and the debugBillboard surface shader expands it toward the camera.

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 (for billboard quads).
  • quatRotate(q, v) — rotate a vector by a unit quaternion.
  • buildVertexBytes(vtxParts) — concatenate the packed vertices into buffer bytes.
  • buildSequentialIndexBytes(count) — the fixed 0,1,2,…,count-1 line-list index 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.

Interface

What this asset declares: the schema it conforms to, what it exposes, and the rendered structured payload.

conforms to

zero/source-extract/v2

strict

packVertex(x: number, y: number, z: number, color: { number }) → string

Pack one vertex (position + color, every other lane defaulted) into the 92-byte standard Vertex layout. Exposed so a provider can build and cache its own packed geometry blob and hand it to `emit.rawLineSoup`, instead of re-packing every vertex each tick.

argtypedescription
xnumber
ynumber
znumber
color{ number }

packVertexUV(x: number, y: number, z: number, u: number, v: number, color: { number }) → string

Pack one vertex carrying an explicit uv (position + uv + color, every other lane defaulted). Billboard quads need a per-corner uv so the surface shader can expand and texture them; the line builders leave uv at the origin.

argtypedescription
xnumber
ynumber
znumber
unumber
vnumber
color{ number }

quatRotate(q: Quat, v: Vec3) → Vec3

Rotates `v` by unit quaternion `q`: `v' = v + 2*qw*(qxyz x v) + 2*(qxyz x (qxyz x v))`, computed as `t = 2*(qxyz x v)`, `v' = v + qw*t + (qxyz x t)` (the standard two-cross-product form — algebraically identical, one fewer cross product).

argtypedescription
qQuat
vVec3

appendLine(vtxParts: { string }, a: Vec3, b: Vec3, color: { number }?) → number

Appends one line segment (2 vertices) to `vtxParts`. Returns the vertex count added (2). The batching contract for every builder here: append line-soup vertices, return how many were added.

argtypedescription
vtxParts{ string }
aVec3
bVec3
color{ number }?

appendBox(vtxParts: { string }, min: Vec3, max: Vec3, color: { number }?) → number

Appends an axis-aligned box's 12 edges as 24 line-soup vertices.

argtypedescription
vtxParts{ string }
minVec3
maxVec3
color{ number }?

appendOrientedBox(vtxParts: { string }, center: Vec3, rotation: Quat, halfExtents: Vec3, color: { number }?) → number

Appends an oriented box's 12 edges as 24 line-soup vertices. `center` + `rotation` give the world pose; `halfExtents` are the box's local half-sizes.

argtypedescription
vtxParts{ string }
centerVec3
rotationQuat
halfExtentsVec3
color{ number }?

perpendicularBasis(axis: Vec3) → void

An orthonormal basis perpendicular to unit vector `axis` (Gram-Schmidt off world-up, falling back to world-right when `axis` is near-parallel to up).

argtypedescription
axisVec3

appendWireSphere(vtxParts: { string }, center: Vec3, radius: number, color: { number }?) → number

Appends a wireframe sphere (3 orthogonal circle loops: XY, XZ, YZ).

argtypedescription
vtxParts{ string }
centerVec3
radiusnumber
color{ number }?

appendWireCapsule(vtxParts: { string }, center: Vec3, rotation: Quat, radius: number, halfHeight: number, color: { number }?) → number

Appends a wireframe capsule (two end circle loops + connecting lines along the capsule's local +Y axis, rotated by `rotation`). `radius` is the cross-section radius; `halfHeight` is the half-distance between the two circle centers along the local Y axis.

argtypedescription
vtxParts{ string }
centerVec3
rotationQuat
radiusnumber
halfHeightnumber
color{ number }?

appendCross(vtxParts: { string }, center: Vec3, size: number, color: { number }?) → number

Appends a 3-axis cross (X, Y, Z line segments through `center`, each extending `size` in both directions) — 6 line-soup vertices.

argtypedescription
vtxParts{ string }
centerVec3
sizenumber
color{ number }?

appendOctahedron(vtxParts: { string }, center: Vec3, size: number, color: { number }?) → number

Appends a wireframe octahedron (6 vertices at `size` from `center` along ±X, ±Y, ±Z; 12 edges as line soup) — a small fixed-size world-space marker.

argtypedescription
vtxParts{ string }
centerVec3
sizenumber
color{ number }?

appendBillboard(vtxParts: { string }, center: Vec3, color: { number }?) → number

Appends one camera-facing billboard quad (6 triangle-soup vertices) at `center`, tinted `color`. Every corner stores the SAME world position (`center`) and its own uv corner; the `debugBillboard` surface shader rebuilds a camera-aligned basis in `vertex_clip` and pushes each corner out by its uv, so the quad always faces the camera. Drawn as a triangle list (not line soup), so it renders through a triangle-topology material, sharing the same static sequential index buffer.

argtypedescription
vtxParts{ string }
centerVec3
color{ number }?

buildVertexBytes(vtxParts: { string }) → string

Concatenates accumulated per-vertex byte strings into one buffer-ready string for a GPU buffer handle's `:writeBytes`.

argtypedescription
vtxParts{ string }

buildSequentialIndexBytes(count: number) → string

Builds a sequential line-list index buffer (0,1,2,…,count-1) as bytes through the native `buffer` library. The debug batch is non-indexed line soup, so its index buffer is this fixed sequence — built once and reused, grown only when the vertex count exceeds the current capacity.

argtypedescription
countnumber
⌬ Types
Vec3 = { x: number, y: number, z: number }Quat = { x: number, y: number, z: number, w: number }

Sub-parts

Everything contained inside this part. Assets are composite children (clickable cards). Files are leaf payloads. Expand any row to view its source.

3items
·
other · born here
▤file
▲ 0↑ born
backing path · modules/debugDraw.module

Problems

Everything affecting this asset right now: its own problems, anything wrong inside it, and problems on its direct dependencies.

0problems
No problems reported. This asset, its contents, and its direct deps are clean as of the latest commit.
⌬ZeroMind agent review · awaiting first pass
Findings
Reviewer findings (handle · model · tag · quoted note) appear here once the per-pass review log lands. Today only the rolled-up agent_score is exposed.
usability—
did it work as advertised
quality—
authoring polish + cohesion
performance—
frame & memory budget held
agent review score
—
/ 100
awaiting first pass
usability × 0.40
+ quality × 0.35
+ performance × 0.25
± compat factor

Usability ratings

Did the part work as advertised when consumers tried to drop it in. Separate from upvotes: those are taste; this is "did it function".

—%no reports yet
Sign in to report whether this part worked for you.
Discussion

Scoped to this part · feeds back into the world's score.

0comments
Sign in to post.sign in
No comments yet. Be the first.