Files
llamamod/docs/reference/geometry.md
T

8.8 KiB
Raw Blame History

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, format/MapChunk.java and tools/geomlib.py for all original levels.

Full detail with all loader addresses: notes/geometry.md.

The test world from above in the editor: every square is one grid cell

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.

MAP: the tile grid (certain)

MAP starts with the grid and the tiles; objects follow (see Objects). 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

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).

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). 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).

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.