Skip to content

3D renderer

Argument of render_3d. Positions are vec3 values in world coordinates — camera offset is handled for you.

Function What it does
camera() Camera position as vec3
camera_rotation() Camera yaw, pitch in degrees
camera_forward() View direction as a unit vec3
line(from, to, color[, width[, through_walls]]) Line between two world points
box(min, max, color[, width[, through_walls]]) Box outline
filled_box(min, max, color[, through_walls]) Solid translucent box (all six faces)
quad(a, b, c, d, color[, through_walls]) Filled quad
triangle(a, b, c, color[, through_walls]) Filled triangle
point(pos, color[, size[, through_walls]]) Point sprite (size in pixels, default 4)
text(str, pos[, color[, scale[, through_walls[, background]]]]) Camera-facing billboard text; returns width
tracer(pos, color[, width[, through_walls]]) Line from crosshair (screen center) to a world point
begin(mode[, width[, through_walls]]) / vertex(pos[, color]) / finish() Arbitrary mesh
begin_layer(layer) / uv(u, v) Mesh through a custom gfx pipeline
push() / pop() Matrix transform scope — coordinates become local
origin(pos) Moves local origin to a world position
translate(offset) / rotate(deg[, axis]) / scale(sx[, sy, sz]) Local transforms (rotate axis defaults to Y)
rotate_camera() Turns the local frame to face the camera (billboard)

through_walls = true makes the shape visible through blocks.

module:event("render_3d", function(render)
    for _, p in ipairs(world:players()) do
        if not p:is_self() then
            local pos = p:position()
            local half = p:width() / 2
            render.box(pos + vec3(-half, 0, -half),
                       pos + vec3(half, p:height(), half),
                       0xFF4FF2A6, 2)
            render.text(p:name(), pos + vec3(0, p:height() + 0.4, 0), 0xFFFFFFFF, 1, true)
        end
    end
end)

Meshes

Modes: "lines", "line_strip", "quads", "triangles", "triangle_strip", "triangle_fan", "points". ("quads" and "triangle_fan" are 3D-only — not on the 2D canvas.) Vertex color sticks until changed; width is line width / point size:

render.begin("quads", nil, true)   -- mode, default width, through_walls
render.vertex(pos, 0x604FF2A6)
render.vertex(pos + vec3(1, 0, 0))
render.vertex(pos + vec3(1, 0, 1))
render.vertex(pos + vec3(0, 0, 1))
render.finish()

For a custom GLSL shader instead of built-in shading, use begin_layer(layer) — see Custom pipelines.

Transforms

Inside push/pop, all draw coordinates are local to the transform. origin places the local origin at a world position (usual first call after push). rotate_camera faces the camera with the exact orientation quaternion — no trigonometry:

module:event("render_3d", function(render)
    for _, p in ipairs(world:players()) do
        if not p:is_self() then
            render.push()
            render.origin(p:position() + vec3(0, p:height() / 2, 0))
            render.rotate_camera()
            render.begin("quads")
            render.vertex(vec3(-0.6, 0.6, 0), 0x904FF2A6)
            render.vertex(vec3(0.6, 0.6, 0))
            render.vertex(vec3(0.6, -0.6, 0))
            render.vertex(vec3(-0.6, -0.6, 0))
            render.finish()
            render.pop()
        end
    end
end)

rotate(degrees[, axis]) spins around a vec3 axis (Y by default). scale and translate work in the local frame. Transforms nest; anything left unbalanced is popped automatically after the callback.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close