132 lines
12 KiB
Markdown
132 lines
12 KiB
Markdown
# Level editor – environment, lighting & visibility tools
|
||
|
||
Plugin `editor.tools.environment.EnvironmentEditorPlugin` (phase 3, `notes/editor-ui.md`).
|
||
Code: `src/main/java/com/eliasmueller/llamamod/editor/tools/environment/`, tests
|
||
`src/test/.../editor/tools/environment/` (`EnvironmentToolsTest` needs `-Dllamamod.cd`; `EnvTour`
|
||
= scripted screenshots, `EnvScenarios` = the in-game test projects).
|
||
|
||

|
||
|
||
## What the user gets
|
||
|
||
| Where | What |
|
||
|---|---|
|
||
| **Environment** panel (left dock) – *Zones* tab | Live fog preview (on/off, zone: camera / start / selected, detail Min/Medium/Max, status "showing … fog a → b cells"), list of all zones (start zone first; fog colour swatch, kind, trigger → target, fog end, music), + Teleport (starts the Zone tool), + Environment zone, delete, frame |
|
||
| Inspector section "Environment zone" | trigger cell x/z/y + height test, *move the player here* (flag 4), target x/y/z, facing (°), step forward, fog mode, fog ramp bar, draw distance at Min/Medium/Max, the four fog values, fog/sky colour and ambient colour pickers (live preview while picking, one undo step), music dropdown (track names), sky model (STPC offsets used in the level) and sky yaw, *Make start zone*, *Duplicate*. Raw fields stay in the generic properties below |
|
||
| **Zones** tool | no zone selected: click a cell → new teleport zone (environment copied from the start zone); then click/drag = target, Shift+drag = facing (22.5° steps), `[` `]` turn; *Trigger cell* mode moves the trigger. Viewport: target ring + facing arrow (all zones), selected zone: filled trigger cell, box and an arc to the target; the target is a pickable marker |
|
||
| *Lights* tab | *Preview lights on objects* (engine lighting of all object models), list of lights (swatch, type, position, radius), + Point (Lights tool), + Directional, delete |
|
||
| Inspector section "Light" | type (directional / point / negative point), colour picker + intensity (0–2, colour value 128 = 1.0), directional: azimuth/elevation, point: position, full radius, falloff radius, flicker |
|
||
| **Lights** tool | click = point light 1 cell above the surface (colour preset: torch, fire, lamp, magic, moon, glow), drag = radius. Gizmos: directional = arrows above the level centre, point = radius rings, selected = spheres for both radii |
|
||
| *Lighting* tab | bake: presets Day / Dusk / Night / Cave / Flat or the level's own sun, sun azimuth/elevation/strength/colour, sky + ground colour, AO amount/radius, shadow light; scope all / selected tiles; options: add LGHT point lights to the tiles, also set the level lighting for brushes, set the start zone's fog + ambient to the preset. One undo step. Display: textured / vertex colours only; button for the paint tool |
|
||
| **Paint vertex colours** tool (V) | modes paint / lighten / darken / smooth, colour + brightness (1 = texture colour, 2 = max), radius, strength, falloff smooth/linear/constant, selection only; Ctrl+click = eyedropper; one undo step per stroke (`PropMapCommand` merges by stroke) |
|
||
| **Visibility** panel (right dock) | 32 × 32 mask matrix (row = camera region, column = drawn region; click/drag toggles, *symmetric* option, unused regions dimmed, row label picks a region), preview *off / follow the camera / from region n* (hides the tiles and objects the game would not draw), All visible, Self only, Make symmetric, Auto-generate… (one region / blocks / connected areas + distance) with a triangle-cost warning, statistics (triangles drawn per camera region, max/mean) |
|
||
| **Paint regions** tool | region (colour swatches) + brush 1…17 cells, Ctrl+click picks; switches the region layer on |
|
||
| Level → *Level settings…* (replaces the built-in action) | name, author, description, TestLevel chapter (+ chapters used by other projects), base level, grid width/height (grows east/south, region grid copied), death plane height (KILL_PLANE / SEA brushes and material-30 no-floor tiles move together, or add one), WFPC flags as check boxes (layout flags read-only, unknown set bits listed raw), strat pool |
|
||
| Level → Bake lighting / Environment zones / Visibility regions; View → Fog preview, Light objects, Vertex colours only (Shift+V) | shortcuts to the above |
|
||
| Problems (Check/Build) | fog end < 3 or > 30 cells at detail Max, fog start ≥ end, no / several start zones, trigger outside the grid, teleport target outside the grid (error), teleport without exit floor or door object in the trigger cell (info), player start in a region that does not draw itself (error), regions never drawn from anywhere, region drawing > 30k triangles (info), tiles with flat grey / black vertex colours, point lights without radius, black lights, ignored light types |
|
||
|
||
## Engine facts used (groove.exe, see also mod-fog.md / mod-mirror.md)
|
||
|
||
* **Zone records** (MAP block 4, 90 bytes; `EnvZone`): the start zone has flag 8 (`+0x20`) and
|
||
target (1,1,1) raw – exactly one per original level, trigger at (3, y, 0) outside the
|
||
playfield. Teleports: strat opcode **0x100** (`FUN_0054df30`, table 0x578ff4 + op·4) looks up the
|
||
zone of the calling object's cell (`FUN_0054d560`: same cell x/z, smallest cellY ≥ player cell
|
||
y (+1 with angle bit 0, −1 with bit 1)), then `FUN_0054de00`: unless flag 4, position = target,
|
||
yaw = (angle & 0xff0) << 12; then `FUN_0041e1c0` switches fog, colours, music; if angle bit 1
|
||
is clear the player is pushed `(short)+0x38` along the facing. In the originals the trigger
|
||
cells hold exit floors (collision material 16) or door objects (RubyIdolDoor, PressurePadDoor):
|
||
house doors in the village. Flags 0x400/0x800 are read by strat opcode 0x81 (unknown use).
|
||
* **Fog mode** (`+0x34` → DAT_005865cc, `FUN_00438180`): 0 none, 1 darkness with a white point
|
||
light on the player with radius fog start…end (no original uses it), 2 table fog, 3 vertex fog.
|
||
Fog fades to the fog colour (`+0x50`, also the backdrop clear colour); the far clip = fog end.
|
||
`+0x4c` = ambient colour of object lighting (`FUN_0041bcf0`, byte/255).
|
||
* **Music** ids: table at 0x578960 (20 bytes per entry, name pointer + 8) → `music/<NAME>.ASF`
|
||
(`FUN_00546b70`); ids 1–39 (VILLAGE … ENDGAME), 0 stops. `+0x56` u16 → `FUN_005460d0` (low
|
||
nibble, bits 8–14; probably ambience, kept raw).
|
||
* **Lights** (LGHT, `FUN_0042c180`): positions and directions are **game space** (z = grid row,
|
||
the engine negates z when loading) – the editor previously treated them as export space (fixed
|
||
in `SceneRenderer`, `Transformers.LIGHT`, `Edits.translate`, see "shared changes"). Colour factor
|
||
2c/255; type 4 = **negative** light −(1 + 2c/255)/2. Point light: full up to `a`, linear falloff
|
||
to 0 at `b` (`FUN_0041b8a0` / `FUN_0041ba10`); byte c = 1 → random ×1.5/×0.5 every frame
|
||
(DAT_005865d4 = random & 3) = flicker. Lights only light **object models** (strats, per vertex:
|
||
ambient + all directional + nearest point lights, max 6, `FUN_0041bdb0`); tiles use baked
|
||
vertex colours. 97 directional and 1729 point lights in the 31 levels, no type 4.
|
||
* **Regions**: camera cell → mask (`FUN_0041e020`), mask 0 = everything, camera outside the grid
|
||
= everything. Tiles (`FUN_0042bf40`) and strats (flag 0x40000 unless strat flag 0x800) in cells
|
||
whose region bit is not set are skipped. The second table value + the top tile's y goes to
|
||
DAT_00586424 (strat opcode 0x78; unknown use).
|
||
* **WFPC** bits: 0x10 / 0x100 / 0x10000 / 0x100000 / 0x200000 / 0x4000000 / 0x10000000 change the
|
||
data layout (read-only in the dialog); 0x800 renderer fog flag (`FUN_00424b90`), 0x80000 read by
|
||
strat opcode 0x8e, 0x8000000 collision materials 17/18 ignored are editable; others raw.
|
||
|
||
## How the previews work
|
||
|
||
* **Fog** (`EnvSession.updateFog`): zone = selected zone, else the start zone or the zone whose
|
||
*area* is nearest (teleport targets / player start; the game switches zones only by teleports),
|
||
fog start/end for the chosen detail exactly as `FUN_0041a480` (incl. the 1.5× loader rule).
|
||
Renderer addition `Viewport.setFogOverride(FogOverride)`: fog ramp to the zone colour, geometry
|
||
beyond the end discarded, clear colour = fog colour; distances are measured from the orbit
|
||
pivot (the game camera follows the llama a few cells behind), so an orbiting camera shows the
|
||
"visible bubble" around the point it looks at. When the camera is far from its pivot
|
||
(overview) the preview pauses ("Zoom in to see the fog of …").
|
||
* **Shading** `Viewport.setShading(SHADE_VERTEX_COLORS)`: texture rgb replaced by white (cut-out
|
||
alpha kept).
|
||
* **Object lighting**: per-vertex colours of every OBJECT render instance from the engine
|
||
formula (`ObjectLighting`, `EnvMath.shade`), re-applied after scene changes.
|
||
* **Region preview**: hides tiles/objects through `ctx.setHidden` and restores exactly the nodes
|
||
it hid (user-hidden nodes stay hidden).
|
||
|
||
## Bake (`VertexBake`)
|
||
|
||
Same shading model as `levelbuild.Lighting` (sky/ground hemisphere by normal, sun Lambert × shadow
|
||
ray, AO with 24 Fibonacci rays, `amb·ao + sun·f·(0.6 + 0.4·ao)`), but traced against the real
|
||
triangles of all tiles (opaque faces; uniform 1-cell xz grid with a 2D DDA, per-cell max height
|
||
skips upward rays). Optional point lights (engine falloff, shadowed, ×0.5). Writes frame 0, keeps
|
||
byte 3 (frame count) and other frames. Village (1358 tiles, 26k triangles): **0.7 s**. Brushes
|
||
are not tiles in the scene – their light comes from the level's Lighting settings, which the bake
|
||
can set (the brush preview recompiles).
|
||
|
||
## Shared changes (minimal, backwards compatible)
|
||
|
||
* `editor.render.Viewport`: `FogOverride` record, `setFogOverride/fogOverride`, `setShading/shading`,
|
||
`SHADE_*`; `GlRenderer.FrameParams.fogOverride/shading`; shader uniforms `uFogMode`, `uFogOrigin`,
|
||
`uFarClip`, `uShade` (default path unchanged: 40–160 cells, 0.85).
|
||
* Light z sign (stored z = game z): `ui.view.SceneRenderer` (marker position, direction line),
|
||
`ui.tools.Transformers.LIGHT`, `model.cmd.Edits.translate`; `model.Light` javadoc corrected.
|
||
|
||
## Verified in game (apptest, display :8, Wine 6, Village 1 edits built with `EnvScenarios`)
|
||
|
||
See `img/editor/env_game_*.jpg`.
|
||
|
||
* **New teleport zone (2026-11-07):** Village 1 edit with zones made by `ZoneTool.create` in the
|
||
2×3 cells east of the player start (all their floor polygons painted material 16, zone cellY 8,
|
||
target 40.5/7.25/62.5, red fog 2–7 cells). Walking east from the original start: black fade, the
|
||
llama stands at the target next to the checkpoint post, red fog active
|
||
(`img/editor/env_game_teleport.jpg`). Teleports fire only while the llama *moves* onto the
|
||
exit floor (player code checks a speed property > 0): spawning on a house-door pad
|
||
(43.5/48.45) does nothing.
|
||
* **Start positions around 34–35 / 58 crash the game while loading** (page fault 0x405527 in
|
||
FUN_004054c0, floor-normal lookup), both with boot-to-level's start override and with the Llama
|
||
moved in the project (`EnvScenarios` "village-teleport"); the original start is fine. Not
|
||
investigated further.
|
||
|
||
## Fog preview while zooming
|
||
|
||
Beyond max(10, 1.6 × fog end) cells from the orbit pivot the preview fades out over another half
|
||
of that distance (fog radius grows ×(1 + 30u²), amount × (1 − u)); the overview (no override)
|
||
starts when the fade is complete and ends again at 90 % of it. The override is recomputed on every
|
||
camera change (the zone lookup still runs 120 ms after the camera stops), so zooming no longer
|
||
pops between "fogged" and "whole level".
|
||
|
||
## Known limits
|
||
|
||
* Zone "areas" for the fog preview are a heuristic (nearest teleport target); the game's real
|
||
current zone depends on which door the player used last.
|
||
* The bake is not identical to the original Argonaut lighting (their vertex colours were
|
||
hand-tuned/radiosity); it reproduces levelbuild's look.
|
||
* The object lighting preview ignores the flicker and the second light set (fog mode 1); objects
|
||
whose model is rebuilt by the shell get their lit colours back after the next scene change.
|
||
* Grid resizing only grows/shrinks at the east/far end; WFPC layout flags cannot be changed
|
||
(would need converting the MAP blocks).
|
||
* Region preview uses the editor's hidden flags (visible in the outliner while active).
|