Skip to content

Rendering

One namespace for both, but each call is only valid in its own pass. A 2D call from a renderWorld handler, or a 3D call from renderHud, throws with your file and line, because the alternative is drawing into whatever matrices happen to be loaded: an invisible artefact at best and the rest of the frame rendering wrong at worst.

Colours are 0xAARRGGBB. A string like '#FF5BD75B' works anywhere a colour does, and a six-digit value is given full alpha rather than being drawn invisibly.

2D, inside renderHud

render.text(s, x, y, argb)                  // returns the x it ended at
render.text(s, x, y, argb, shadow)
render.rect(x1, y1, x2, y2, argb)
render.outline(x1, y1, x2, y2, argb)
render.line(x1, y1, x2, y2, width, argb)
render.textWidth(s)
render.fontHeight()
render.width()   render.height()            // GUI units; also on the event

3D, inside renderWorld

render.box3d(x1, y1, z1, x2, y2, z2, argb, filled)
render.box(x, y, z, argb, filled)                 // one block
render.box3dEntity(playerOrEntity, argb, filled)  // interpolated for this frame
render.line3d(x1, y1, z1, x2, y2, z2, argb, width)
render.tracer(playerOrEntity, argb, width)
render.worldToScreen(x, y, z)                     // [x, y, depth] in GUI units, or null

worldToScreen answers null when the point is behind the camera, rather than a plausible pair of coordinates on the wrong side of the screen. Store the result and draw it in renderHud.

render.color(r, g, b, a) packs a colour; render.accent() is the live theme accent, so an overlay can follow the user's own colour, including its AccentMode animation.

function onRenderWorld(e) {
    world.players().forEach(function (p) {
        if (!p.self && p.threat > 70) render.box3dEntity(p, '#AAFF5B7A', false);
    });
}

Drawing your own shapes: gl

render covers boxes, lines and tracers. When you need a shape it does not have (a curve, a ring, a filled polygon), gl is an immediate-mode surface that keeps the familiar OpenGL names, so drawing code written against a conventional immediate-mode API ports almost directly. It is only valid inside a renderWorld handler, and every call outside one is a script error naming the method.

gl.begin(mode)          // "lines" "linestrip" "lineloop" "triangles" "trianglefan"
                        // "trianglestrip" "quads" "quadstrip" "points" "polygon"
gl.vertex3(x, y, z)     // CAMERA-RELATIVE: subtract gl.camera() from world coordinates
gl.end()
gl.color(r, g, b, a)    // 0..1 floats; 0..255 ints and a packed 0xAARRGGBB also work
gl.push() / gl.pop() / gl.translate(x, y, z) / gl.rotate(deg, x, y, z) / gl.scale(x, y, z)
gl.lineWidth(w)
gl.blend(on) / gl.cull(on) / gl.depth(on) / gl.depthMask(on) / gl.texture2d(on) / gl.lineSmooth(on)
gl.camera()             // {x, y, z} of the view position this frame
events.on('renderWorld', function () {
    var c = gl.camera(), p = player.pos();
    gl.push();
    gl.translate(p.x - c.x, p.y + 1.2 - c.y, p.z - c.z);
    gl.depthMask(false); gl.lineWidth(2);
    gl.begin('lineloop');
    gl.color(1, 0.4, 0.7, 0.9);
    for (var i = 0; i <= 24; i++) {
        var a = i / 24 * Math.PI * 2;
        gl.vertex3(Math.cos(a) * 0.6, 0, Math.sin(a) * 0.6);
    }
    gl.end();
    gl.depthMask(true);
    gl.pop();
});

fkdr tidies up after your drawing

After every script's 3D pass, fkdr puts the drawing state back as it was, closes a begin() you never ended and pops any matrix you pushed and forgot. A pop() with no matching push() is refused. The worst a runaway script can do is draw nonsense in its own frame.

The built-in CuteVisuals module is the worked example of this style of drawing: hearts and dots trailing the player, and a rainbow burst when a bed breaks.