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.