
# 3D renderer

The argument of `render_3d`. Positions are [vec3](vec3.md) values in world coordinates; the
camera offset is handled for you. `through_walls = true` makes a shape visible through blocks.

```lua
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)
```

| Function | Draws |
|----------|-------|
| `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 its width |
| `tracer(pos, color[, width[, through_walls]])` | Line from the crosshair to a world point |

| Function | Returns |
|----------|---------|
| `camera()` | Camera position as [vec3](vec3.md) |
| `camera_rotation()` | Camera `yaw, pitch` in degrees |
| `camera_forward()` | View direction as a unit [vec3](vec3.md) |

## Meshes

`begin(mode[, width[, through_walls]])`, `vertex(pos[, color])` and `finish()` build an
arbitrary mesh. Modes are `"lines"`, `"line_strip"`, `"quads"`, `"triangles"`,
`"triangle_strip"`, `"triangle_fan"` and `"points"`; `"quads"` and `"triangle_fan"` exist only
here, not on the 2D canvas. A vertex color holds until you change it, and `width` is the line
width or point size.

```lua
render.begin("quads", nil, true)
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 your own GLSL instead of the built-in shading, open the mesh with `begin_layer(layer)` and
set texture coordinates with `uv(u, v)`. See [Custom pipelines](gfx.md).

## Transforms

| Function | Does |
|----------|------|
| `push()`, `pop()` | Opens and closes a transform scope, inside which coordinates are local |
| `origin(pos)` | Moves the local origin to a world position |
| `translate(offset)` | Shifts the local frame |
| `rotate(deg[, axis])` | Rotates around a [vec3](vec3.md) axis, Y by default |
| `scale(sx[, sy, sz])` | Scales the local frame |
| `rotate_camera()` | Turns the local frame to face the camera, using the orientation quaternion, so no trigonometry |

`origin` is the usual first call after `push`. Transforms nest, and anything left unbalanced
is popped after the callback.

```lua
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)
```
