Files
llamamod/docs/reference/geometry.md
T

156 lines
8.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Geometry and collision
The world is **not** one big mesh and there is no BSP. It is a **grid of tiles**, inherited from
Croc: TRAK holds a set of small models (the level's construction kit), and MAP places them on a
W × H grid, one cell = one unit. Every byte described here is reproduced exactly by
[`format/Trak.java`](../../src/main/java/com/eliasmueller/llamamod/format/Trak.java),
[`format/MapChunk.java`](../../src/main/java/com/eliasmueller/llamamod/format/MapChunk.java) and
[`tools/geomlib.py`](../../tools/geomlib.py) for all original levels.
Full detail with all loader addresses: [notes/geometry.md](../../notes/geometry.md).
![The test world from above in the editor: every square is one grid cell](../img/testworld-top.jpg)
## Coordinate spaces
| Space | Used by | Axes |
|---|---|---|
| **Game space** | strats, collision, MAP fixed-point tile positions, lights | x east, **y up**, z = grid row (positive); 20.12 fixed point (4096 = 1.0); right-handed as the game shows it |
| **Export space** | TRAK/STPC vertices, MAP float tile positions | same, but **z negated** (grid at z ∈ (−H, 0]); drawn by a Direct3D (left-handed) camera |
| **File space** | OBJ/glTF of every LlamaMod and kit tool | = game space (y up, right-handed): Blender shows a level the same way round as the game |
Converting export ↔ file space negates z and reverses the triangle corner order. Angles:
4096 = 360° in MAP records; the strat VM uses 0x1000000 = 360°. A tile is turned only about y
(0, 1024, 2048, 3072 in practice). Rotation 0 faces +z, 90° faces +x.
## TRAK: tile models (*certain*)
`u32 n`, then **all n headers** (0x84 bytes each), then the data of every model in order.
Typical levels have 100–650 models with up to 422 vertices / 798 faces each.
| Header field | Meaning |
|---|---|
| 0x00 | bounding-box centre (3 × f32) |
| 0x0c | 8 box corners (used for frustum culling) |
| 0x6c / 0x6e | vertex count / face count |
| 0x78 / 0x7a / 0x7c | collision polygon counts in three groups (the engine always uses the sum; meaning of the groups *unknown*) |
| pointers | 0 in the file, set on load |
Data per model:
* **Vertex**, 24 bytes: `f32 x, y, z, nx, ny, nz` (lighting comes from the MAP vertex colours, not
from the normals).
* **Face**, 28 bytes, triangles only: `u16 flags, u16 v0, v1, v2, u16 texture, u16 0, f32 plane
nx, ny, nz, d`.
* **Collision polygon**, 32 bytes: `u8 type, u8 material`, then 5 planes `{i8 nx, ny, nz, u8 0,
i16 d}`. Plane 0 is the surface, planes 1–4 the edges (normals point inwards). Type bit 0 =
quad, otherwise triangle. Normals are ×127, d is 20.12 fixed point – so a collision polygon
must lie within **±8 units of its tile's origin**.
### Face flags
| Bit | Meaning |
|---|---|
| 0x0008 | start of a new render batch (texture page or blend mode changes) – must be set on the first face of each batch, at the latest every 256 faces |
| 0x0010, 0x0020, 0x0800 | which corners of the texture rectangle the triangle uses (below) |
| 0x0400 | double-sided (no backface culling) |
| 0x4000 | ignore vertex colours |
| 0x0001, 0x0002 | special sorting/transparency modes (*likely*) |
| others | present in the data, not read by the PC renderer (*unknown*, probably PlayStation leftovers) |
### No free texture coordinates
The engine has **no per-vertex UVs**. Every triangle shows one half of a texture rectangle from the
TEXT table: with flag 0x800 the face is the first or (with 0x10) second half of a quad, without it
a triangle covering the lower-left half; 0x20 mirrors horizontally. 96 % of world faces are quad
halves. This is why the level builder and editor split surfaces into 1 × 1 pieces and why a narrow
wall shows a squashed texture. See [Textures](textures.md).
## MAP: the tile grid (*certain*)
MAP starts with the grid and the tiles; objects follow (see [Objects](objects.md)). The blocks that
concern geometry:
| Block | Format |
|---|---|
| header | `u32 nTiles, u32 W, u32 H` |
| tiles (floats) | nTiles × `{f32 x, y, z, u32 rotY, u32 model, u32 hasAlt}` in export space; x, z = cell centre |
| regions | 32 × `{u32 visibility mask, i32 value}`, then W·H × u32 region number per cell |
| tiles (fixed point) | nTiles × `{rx, ry, rz, x, y, z}` 20.12 in game space – used for drawing and collision; must match the float copy exactly |
| model index | nTiles × u32 (= the float block's `model`) |
| tile swaps (WFPC 0x10000) | one `{cellX, y, cellZ, modelA, modelB}` per tile with `hasAlt ≠ 0`: doors and bridges that change their model (strat opcodes 0x11a / 0x11b) |
| vertex colours | per tile: one u32 RGBA per vertex of its model (0x80 = 1.0, the game doubles and clamps); more sets for animated colours; then the same for the swap model |
Rules that crash the game or corrupt the level when broken:
* every tile must lie inside the grid (`0 ≤ x < W`, `0 ≤ row < H`) – outside, the cell list
writes into foreign memory;
* the vertex-colour block must have **exactly** one entry per vertex of the tile's model – else
everything after it (the objects) is read shifted;
* the float and fixed-point copies and the model index must agree;
* tile swaps: one entry per tile with `hasAlt ≠ 0`, in tile order.
The cell of a tile is `floor(x) + floor(−z) · W` (export-space floats).
## Collision (*certain*)
* Only **the tiles listed in the cell the player stands in** are tested (`FUN_004036d0` floors and
ceilings, `FUN_00403ad0` walls). A wall polygon that sticks into the neighbouring cell has no
effect there – builders enter a polygon into every cell it covers, as a collision-only tile if
needed.
* Collision is in game space, relative to the tile's fixed-point position.
* There is **no step-up logic**: a 0.12-unit edge with a wall polygon already blocks the llama.
Low plinths need walls without collision; stairs need an invisible slope.
### Collision materials
The material byte ends up in the player's ground state and is read by strats (`floorMaterial`).
| Material | Meaning |
|---|---|
| 0 | normal ground |
| **16** | **exit floor**: the LevelDoor ends the level only when the llama stands on material 16 in the door's cell; also used under the village house doors (zone teleports) |
| 17, 18 | ignored when WFPC flag 0x8000000 is set (*likely*: special surfaces) |
| **30** (0x1e) | death plane / water (*likely*): falling onto it empties the energy and the llama respawns at the last checkpoint or the start (Model 0 of many levels is such an invisible plane at y = −2.75) |
| others | special surfaces, meaning *unknown* – the debug view colours them |
![Debug view of Village 3: collision frames, invisible walls in red, objects as markers](../img/debug-view.jpg)
## Visibility regions (no BSP)
Drawing (`FUN_0042bf40`) walks the grid cells within the draw distance around the camera and draws
a cell's tiles **only if the cell's region bit is set in the mask of the camera's region**. Up to 32
regions; mask 0 or a camera outside the grid means "everything". New geometry must therefore get a
region that the surrounding regions can see, or it stays invisible – the builders put everything
into region 0 with mask `0xffffffff`. Objects in hidden cells are skipped too (unless strat flag
0x800). The editor's *Visibility regions* tool edits this
([manual chapter 13](../manual/13-visibility.md)).
## STPC: object models and code
STPC starts with the **object models** (same model format as TRAK, header 0x8c bytes, optionally
skinned with a bone table), then the **animation sets** (per-frame bone matrices and compressed
vertex deltas – the compression is only partly understood), then the **strat code units**
([Strats](../strats/README.md)). The engine never parses the code; MAP records and bytecode refer
into STPC by absolute byte offsets, so STPC can only be extended at the end or merged with
relocation ([notes/editor-cross-import.md](../../notes/editor-cross-import.md)).
## WFPC flags
WFPC is a u32 of feature flags. Some change the data layout and must stay as they are: 0x10,
0x100, 0x10000 (tile swaps), 0x100000, 0x200000, 0x4000000, 0x10000000. Others: 0x800 renderer
fog flag, 0x80000 read by a strat opcode, 0x8000000 makes collision materials 17/18 ignored.
## Writing new geometry – checklist
1. TRAK: correct counts, bounding box and face planes recomputed, texture index below the
number of texture entries, batch bit 0x8, ≤ 8000 vertices per model.
2. Collision polygons within ±8 units of the tile origin, entered into every cell they cover.
3. MAP: tiles inside the grid, float/fixed/model blocks consistent, vertex colours exactly per
vertex, tile-swap block matching `hasAlt`, a visible region.
4. Rewrite the WAD's total length.
The editor and both level builders do all of this; the details of the builder's approach
(splitting into tiles per cell and height band, merging identical tiles, baking sun, sky and
ambient occlusion into vertex colours) are in [notes/levelbau.md](../../notes/levelbau.md).