# Text3D
Renders text in 3D world space. The quad auto-sizes to fit the text content and never clips. Positioning works like TextMeshPro: `offsetX/Y/Z` is a local offset from the entity, `pivotX/pivotY` is the anchor within the quad, and `billboard` makes it face the camera. With `billboard = false` the text reads from the entity's own forward, the local -Z that `transform.forward` reports and that `entity:lookAt` aims.
Sizing — three fields, two jobs:
- `worldHeight` — the quad's height in **world units** (default `1.0`). This is the one field that resizes a label, and it holds that height under every transform between the label and the world: a label hung off a shrunken detail box on a model comes out the size it asked for, and so does one whose own entity carries a `localScale`. Width follows the rasterised text's aspect. Measure the result with `getWorldSize()`, which reports the extent the quad is drawn at.
- `fontSize` — the **raster resolution** in texels (default `32`). Higher values sharpen the texture and change wrapping against `maxWidth`; the world-space size stays `worldHeight`.
- `scale` — rasterisation scale multiplier applied at raster time. Resolution only, like `fontSize`.
`maxWidth` is the width the text wraps at, in pixels of the raster `fontSize` states; `0` keeps it on one line. A label already drawn is laid out again when the field is written, so a caller re-wraps one label to line after line rather than making a label per line.
`alphaCutoff` decides whether the letters are a surface. At `0` (the default) the text is pure alpha blending: it draws over what is behind it and leaves the depth buffer alone, so a screen-space effect that reads scene depth — volumetric fog, screen-space shadows — integrates the whole distance behind the letters and the text sits inside it. Above `0`, coverage at or over the threshold is drawn opaque and written to depth, so those effects stop at the glyph shape instead. `0.5` reads well for most fonts; higher thins the letters, lower keeps more of the antialiased edge.
Public fields: `content`, `fontSize`, `color`, `alignment`, `richText`, `maxWidth`, `outline`, `outlineColor`, `background`, `scale`, `worldHeight`, `alphaCutoff`, `shadowX/Y`, `fontFamily`, `weight`, `slant`, `offsetX/Y/Z`, `pivotX/Y`, `billboard`. `weight` and `slant` are strings (`"regular"` / `"bold"`, `"normal"` / `"italic"`).
Methods: `setText(content)`, `setStyle(options)`, `getText()`, `getSize()`, `getWorldSize()`, `refresh()`.
```luau
entity(id).component.add("Text3D", { content = "Hello World" })
entity(id).component.add("Text3D", { content = "HP: 100", worldHeight = 0.5, fontSize = 96, color = "red", offsetY = 2.0, pivotY = 0 })
-- A title that keeps its letters crisp through volumetric fog.
entity(id).component.add("Text3D", { content = "RAISING", worldHeight = 3.0, alphaCutoff = 0.5 })
```
A label that is not showing, or came out in a face you did not ask for, reads
back out of the text system: `text.observe()` lists every live text object with
the entity that owns it and the texture its raster is in, and `text.face(h)`
names the font face the shaper actually used against the `fontFamily` that was
requested. `topics/text` walks both.