initial commit: llamamod 0.2.0 – reverse-engineered level formats (wad, map, trak, text, stpc), python tools and notes, java mod manager (iso install, mod pipeline with exe patching, save editor, wine/windows launcher) with mods (test world, debug wireframe, mirror, randomizer, fog, cheats, intro skip, practice, free camera, boot to level, level projects) and the 3d level editor (geometry, objects, cross-world import, environment, textures, play-test, manual)
This commit is contained in:
commit
b36a366240
826 files changed
+105304
No files matched your search
@@ -0,0 +1,164 @@
|
||||
# Level editor – geometry tools (`editor.tools.geometry`)
|
||||
|
||||
Plugin `GeometryEditorPlugin` (phase 3, discovered automatically). Everything the owner asked for
|
||||
under "geometry": draw primitives, resize/turn/scale/mirror them, align and repeat, paint tiles,
|
||||
paint collision materials, import meshes, reuse geometry across levels, export to Blender, and
|
||||
geometry checks. Screenshots: `img/editor/geo_*.jpg`.
|
||||
|
||||

|
||||
|
||||
## User guide
|
||||
|
||||
### Brush tool (B) – draw primitives
|
||||
Toolbar button / **Geometry → Brush tool**. The options bar has one button per primitive, the
|
||||
Inspector shows the same palette with names plus the options of the chosen primitive (textures with
|
||||
a swatch picker, thickness, steps, segments, collision, solid sides, material).
|
||||
|
||||
| Primitive | How to draw | Brush |
|
||||
|---|---|---|
|
||||
| Block, Platform, Ramp, Stairs, Round (cylinder), Floor, Water, Death plane, Invisible block | drag the footprint, then move the mouse up/down for the height and click (Enter = height field, Esc cancels) | BOX, PLATFORM, RAMP, STAIRS, CYLINDER (new), FLOOR, SEA, KILL_PLANE, BOX invisible |
|
||||
| Wall, Invisible wall | drag along the wall – any direction, snapped to the angle step; then the height | BOX (turned by `rotation` if diagonal) |
|
||||
| Pillar, Round | click = default size, drag = size | PILLAR, CYLINDER |
|
||||
| Fence | click the corners, double-click / Enter finishes | FENCE |
|
||||
| Tree | click | TREE |
|
||||
|
||||
* Drawing happens on the surface under the cursor (top of other geometry, water, the grid) or on a
|
||||
**work plane** at a fixed height. Footprints snap to the move step (Ctrl inverts snapping).
|
||||
* Ramps and stairs rise in the main drag direction. Stairs start at the floor in front (`y0`), the
|
||||
walkable invisible slope makes them climbable (the engine has no step-up logic).
|
||||
* **Handles**: a selected brush shows cubes on its faces (orange = sides, green = top/bottom). Drag
|
||||
one to resize; the opposite face stays where it is – also for turned brushes.
|
||||
* `[` / `]` turn the selection by 90°, Page Up / Page Down raise / lower it by the snap step.
|
||||
* Status bar shows the size while drawing/resizing.
|
||||
|
||||
### Rotate / scale / arrange
|
||||
* **Rotate (E)**: the green ring turns everything about the vertical axis (as before). The red and
|
||||
blue rings now turn **brushes freely in 3D** (`rotation` x·y·z Euler angles about the brush's
|
||||
centre; meshes about their origin). Tiles and objects only turn about the vertical axis in the
|
||||
engine – the status bar says so and they are left alone.
|
||||
* **Scale (R)** on brushes changes the primitive's own sizes in its frame (width/depth/height,
|
||||
thickness, radius, crown …; meshes stretch per axis) – the bottom centre stays.
|
||||
* Quarter turns and mirrors of unrotated brushes change their parameters (texture stays aligned to
|
||||
the grid; ramp/stairs directions, sides, per-face settings follow).
|
||||
* **Geometry menu**: Turn left/right 90° (Ctrl+[ / Ctrl+]), Mirror X / Z (about the selection
|
||||
centre; tiles and objects are moved and turned – tile models themselves cannot be mirrored),
|
||||
**Array duplicate…** (Ctrl+Shift+D: n copies with offset and turn per copy, optionally circular
|
||||
about the selection centre), **Align ▸** west/centre/east, bottom/middle/top, north/centre/south to
|
||||
the last selected item, distribute along X / Z, **Grid & snapping…** (any move/angle/scale step,
|
||||
grid on/off), **Death plane height…** (moves death-plane brushes and the original level's
|
||||
material-30 tiles, or adds a plane over the whole grid), **Re-import mesh…**, **Save selection to
|
||||
library…**.
|
||||
* Inspector section **Brush shape** (one brush): size, rotation X/Y/Z fields, turn/mirror buttons,
|
||||
*Visible* (off = invisible collision volume) and a face table: collision on/off and material per
|
||||
face (top, bottom, north, east, south, west). Section **Tile**: turn/raise buttons, "use palette
|
||||
model", tile swap (see below) and the honest note that tiles only turn about y and cannot scale.
|
||||
|
||||
### Tiles tool (T) – original-style levels
|
||||
Modes **Paint / Pick / Erase / Raise-lower / Swap model**, palette of the level's tile models with
|
||||
thumbnails in the Inspector (filter, "only used models"). Paint puts the palette model into the cell
|
||||
under the cursor (on the surface height, or at a fixed height), rotation 0/90/180/270 (`[` `]`),
|
||||
height Page Up/Down. Painting onto a tile at the same height swaps its model instead of stacking
|
||||
(keeps tile indices that strat scripts use). Shift+click = pick (model, height, rotation). New tiles
|
||||
copy the vertex colours of a tile with the same model. **Tile swaps** (doors/bridges, WFPC 0x10000):
|
||||
the Tile section shows the swap entry, sets the alternative model from the palette, removes or adds
|
||||
a swap; layer "Tile swap alternatives" draws the alternative model in cyan.
|
||||
|
||||
### Collision tool (K)
|
||||
Turns the collision layer on. Hovering highlights the polygon (or, on brushes, the whole face) under
|
||||
the cursor and names its material; click/drag **paints** the material from the palette (Inspector
|
||||
list with the known materials of `debugviz.md`: 0 normal, **16 exit floor**, **30 no floor /
|
||||
death**, 17/18 selective, specials 1…255 "meaning unknown", "Other…" for any number). Shift+click
|
||||
picks a material. **Toggle collision** mode switches a brush face's collision off/on. Tile models
|
||||
are painted per polygon; shared models: change the model (all tiles) or give the tile its own copy.
|
||||
Invisible collision volumes (Invisible wall/block, colliders) are drawn translucent red (layer
|
||||
"Invisible collision volumes").
|
||||
|
||||
### Mesh import (File → Import mesh)
|
||||
File chooser, then a dialog: rotatable preview with a 1-cell grid and a llama-sized marker, source
|
||||
info, up axis, unit scale and "fit largest side to n cells", turn, mirror, stand on the ground,
|
||||
texture size and UV precision, double-sided, lit, collision **from the mesh / simplified invisible
|
||||
box / none** + material, warnings (densest cell vs. the 8000-vertex limit, larger than the grid).
|
||||
Placement: click in the viewport (a box follows the cursor) or at the view centre.
|
||||
**Geometry → Re-import mesh…** reloads the selected mesh brush's file (remembered) or another file
|
||||
and keeps the placement.
|
||||
|
||||
### Library (cross-level reuse) and OBJ export
|
||||
* **Save selection to library…** (Geometry menu or Assets → **Library** tab): selected tiles
|
||||
(original TRAK models with collision and vertex colours), brushes and meshes (compiled to tiles)
|
||||
→ `<data>/library/<name>.llamamesh`: a zip with `library.json` (models: vertices, faces, planes,
|
||||
three collision groups; tiles: offsets from the anchor cell, rotation, vertex colours) and one PNG
|
||||
per used texture (pixels embedded – owner decision: game data may be embedded). Death-plane tiles
|
||||
are skipped.
|
||||
* **Insert**: drag an entry from the Library tab into the viewport (anchor = cell under the drop
|
||||
point) or double-click / Insert (view centre). Textures with identical pixels in the target level
|
||||
are reused (same base: nothing new), the others are packed onto new texture pages (blend mode
|
||||
kept); identical tile models are reused. Works on any base level.
|
||||
* **File → Export selection as OBJ…**: OBJ (export space = the importer's convention) + MTL + one
|
||||
PNG per texture, UVs per triangle (the engine's texture-rectangle halves), vertex colours as
|
||||
`v x y z r g b` (0x80 = 1.0), optionally `…_collision.obj` (polygons grouped by material). The
|
||||
dialog notes that the textures are game data (personal use). The importer reads it back
|
||||
(round trip verified: same shape and textures, `img/editor/geo_library_insert.jpg` – right the
|
||||
library tiles, left the re-imported OBJ as a mesh brush).
|
||||
|
||||
### Checks (Level → Check, Build)
|
||||
Tiles outside the grid; tile models (also compiled brush/mesh tiles) over **8000 vertices**;
|
||||
**overlapping** identical tiles; **no floor collision under the player start** (cell-based like the
|
||||
engine) or start far above the floor; **no exit floor (16)** in the LevelDoor's cell; **gaps** < 0.2
|
||||
between neighbouring floors at the same height; brush compiler warnings.
|
||||
|
||||
## Design / implementation
|
||||
|
||||
* **Brush math** (`BrushMath`, pure, unit-tested): footprint → parameters (`Primitive.params`),
|
||||
handles + `drag` (opposite face kept in world space via `keepWorld`), `rotate` (JOML matrices,
|
||||
`getEulerAnglesXYZ`; quarter turns parametric), `scale`, `mirror` (local mirror + M·R·M), diff-based
|
||||
commands (`Edits.set` with merge keys → one undo step per gesture). `BrushTransformer` replaces the
|
||||
built-in brush transformer (position = bottom centre of the pivot column, yaw = rotation[1]).
|
||||
`RotateTool3D` wraps the built-in Rotate tool (same id) and handles the non-vertical rings.
|
||||
* **Generic brush parameters** (all kinds except SEA, KILL_PLANE, COLLIDER): `rotation` {x, y, z}°,
|
||||
`visible`, `collFaces` ("tbnesw"), `faceMats` int[6] (−1 = brush material); MESH also `stretch`.
|
||||
Applied by `BrushCompiler.postProcess` on the quads a brush added (`LevelBuilder.editQuads`/
|
||||
`editSolids`): face class from the quad's normal in the brush frame, then rotation about
|
||||
`Brush.pivot()`; default values leave the build byte-identical (golden/test-world tests pass).
|
||||
* `CollisionIndex`: world polygons of scene tiles + compiled brush tiles (cached per scene,
|
||||
invalidated by scene events) for picking, highlighting, checks.
|
||||
* `MeshLibrary` (save/load/insert/OBJ), `LibraryTab` (asset tab + drag & drop kind `geolib`).
|
||||
|
||||
### Shared-file changes (backwards compatible)
|
||||
| File | Change |
|
||||
|---|---|
|
||||
| `levelbuild/LevelBuilder` | `QuadEdit`, `quadCount/solidCount`, `editQuads/editSolids` (editor post-processing), `cylinder(...)` primitive |
|
||||
| `editor/model/BrushKind` | kind `CYLINDER`; generic params (above); `FACES`, `transformable()`; INT_ARRAY params; STAIRS `y0` label "Start height (floor in front)" |
|
||||
| `editor/model/Brush` | int[] defaults cloned; `bounds()` = rotated `localBounds()` (now includes platform/pillar/cylinder/tree/fence/mesh sizes), `pivot()`, `rotation()`, `hasRotation()`, `rotationMatrix()`, `rotate()` |
|
||||
| `editor/io/BrushCompiler` | CYLINDER replay, `postProcess`, `faceClass/frontNormal`, mesh `stretch` (+ mirrored winding) |
|
||||
| `editor/io/MeshImport` | bug fix: the source folder is normalised, otherwise a path with `..` silently dropped the MTL and textures (meshes rendered untextured after saving) |
|
||||
|
||||
## Verification
|
||||
* Unit tests `BrushMathTest` (rotation matrix = JOML, Euler round trip, primitives from footprints,
|
||||
diagonal wall end points, quarter turns = free 90° turn, mirror involution incl. rotated shapes,
|
||||
handles keep the opposite face also when turned, scaling, transformer; compiled per-face material
|
||||
16 / sides without collision / invisible = collision only / 45° walls / tilted top / cylinder) and
|
||||
`GeometryChecksTest` (gap, player-start floor, exit floor; library save→load→insert into the same
|
||||
level (textures reused) and into a world-4 base (textures imported, builds)).
|
||||
* UI on Xvfb: `geo_draw` (ramp preview), `geo_handles`, `geo_rotate3d` (blue ring turned a ramp
|
||||
60°, inspector updates live), `geo_collision` (hover highlight, palette), `geo_tiles` (palette +
|
||||
cell preview, village), `geo_import` (dialog), `geo_placed`, `geo_menu` (Geometry ▸ Align),
|
||||
`geo_library`, `geo_build_ok`.
|
||||
* **In game** (apptest, Wine; display :21 because :5 was taken by another agent's session): test
|
||||
level "Geometry Test" (TestLevel chapter 4, built with the same code paths, `GeoDemo`): start
|
||||
plaza → ramp → platform → stairs → round platform → **imported OBJ bridge** → goal block whose top
|
||||
face is painted **16** with a LevelDoor → walked through in one run, the door fired, level summary
|
||||
shown (`geo_game_course.jpg`). **Death floor** (FLOOR material 30): stepping on it = death and
|
||||
respawn (`geo_game_death.jpg`). **Invisible wall** at the plaza edge stops the llama
|
||||
(`geo_game_invisible_wall.jpg`). **Library**: a village piece (9 tiles, 44 textures) saved and
|
||||
inserted into new levels on the village base (textures reused) and on the world-4 base t1l4m001
|
||||
(textures imported, 3 new pages): both build and load in game (`geo_game_library.jpg`); also built
|
||||
on the river base t1l3m001 (no Llama placeable there, so not played).
|
||||
|
||||
## Known limits
|
||||
* Collision materials of brushes are per face class, not per individual polygon; tile models per
|
||||
polygon. Collision of tile-model polygons cannot be switched off (paint 30 or hide the tile).
|
||||
* Water and death planes are cell based: resize/move/90° only. Gaps check only unrotated
|
||||
box/platform/floor tops.
|
||||
* Library insert keeps tiles cell-aligned (anchor = whole cells); objects are not part of the library
|
||||
(see the objects agent's prefabs / object catalogue). glTF export not done (OBJ only).
|
||||
* `TextureImport` packs imported textures at one size per insert (largest texture, ≤ 256).
|
||||
Reference in new issue
Block a user