7.5 KiB
Level editor – texture tools (editor.tools.textures)
Plugin TexturesEditorPlugin (phase 3). User guide: editor-guide.md#textures.
What it adds
| Item | Where | |
|---|---|---|
| Texture manager | panel textures (bottom dock, Ctrl+Shift+T; Window button = own 1300×760 frame) |
pages, atlas, table, details, animation editor |
| Import texture… | File menu (replaces the built-in file.importTexture), panel toolbar |
ImportDialog |
| Replace texture pixels… | Edit menu, panel toolbar | image → the selected texture's rectangle |
| Paint texture | tool texpaint, key M (own toolbar group) |
PaintTool |
| Texture eyedropper | Edit menu, key I; Shift+click in the paint tool | |
| Checks | Build/Check | > 127 pages = error, entry on a missing page = error |
TexState holds the window's current texture (shared by panel, tool, eyedropper; selecting a
TextureEntry anywhere, e.g. in the outliner, makes it current).
Texture manager (TextureManagerPanel)
- Pages: thumbnail, index, blend class of the page format (
Blend.ofPage). Context menu: export page PNG, replace page from PNG (scaled to 256×256, reduced with the page's class). - Atlas: the page (nearest-neighbour, fit to the view until the wheel zooms) with all
rectangles: white = used, red = unused, cyan = animated, orange = current with 8 handles.
Drag inside the current rectangle = move, handles = resize (one undo step per drag:
TextureLibrary.setRectwith a merge key). Click selects the smallest rectangle under the cursor; double-click activates the paint tool; tooltips show size/usage. - Table: #, preview, name (editable), page, rectangle, blend, faces (tile-model faces + brushes), models, animation ("frame" = used as a frame). Filters: all / on this page / used / unused / animated / imported; search by name or number.
- Texture tab: name, X/Y/W/H spinners, blend class (sets the entry flags), raw flags, usage line, Duplicate (new entry with the same rectangle, e.g. for frames), Select users (tiles whose models use it).
- Animation tab (
AnimEditor): create/remove, type flip-book / scroll up / scroll down, frame delay (≈ fps at 30 game frames/s), back and forth, frame list (+ − ◀ ▶), scroll band / speed / top row, live preview (AnimSpec.frameAt/offsetAt).
Usage counts (TextureLibrary.usage): faces of TRAK tile models (faces[i+4]), brush string
parameters "#n", animation targets and frames. Object (STPC) models are not counted – their
textures are in the same table, so "unused" means not used by tiles/brushes/animations.
Import (ImportDialog, ColorReduction, TextureLibrary.importItems)
- Size = longest side (16…256 or original), aspect kept; rectangles need not be powers of two.
- Blend classes (
Blend), derived from the flag pairs of all original level WADs (entry flags ↔ page flags & 0x7f, counts over 33 WADs):
| Class | entry flags | page flags | originals |
|---|---|---|---|
| Opaque | 0x0104 | 0x81 | 16 551 entries |
| Cut-out | 0x0406 | 0x84 | 1 774 |
| Semi-transparent (mode 1) | 0x1406 | 0x87 | 528 |
| Additive (mode 2) | 0x2406 | 0x85 | 649 |
| Darken (mode 3) | 0x3406 | 0x82 | 574 |
Others seen: 0x1104/class 6 (186), 0x2104/class 3 (38), 0x4406/class 5 (469), object-model entries 0x8cc6/0xbbc6/0x9cc6/0xb8c6 (bit 15). Entry bits 12–14 = blend mode, 0x400 = has transparent pixels.
- Colour reduction to ABGR1555: alpha < cut → 0x0000; with "keep half-transparent" alpha < 192 → STP 0 (black becomes 0x0421, never 0); optional colour key (magenta / top-left pixel) and 4×4 ordered dithering; opaque class ignores alpha. Preview = the game's 15-bit result.
- Packing: with "pack into free space" the importer searches pages of the same format
class for a free block (
FreeMap, summed-area table over a 256×256 occupancy mask built from all rectangles plus scroll bands), 8-pixel grid, largest images first; otherwise / if full it appends pages (≤ 127). Optional 1-pixel border (edge pixels repeated) against filtering seams. One undoableBatch:SetProp pixelsof changed existing pages (the project stores only the changed pixel runs),AddNodepages,AddNodeentries (always appended – faces reference the index). - Village test: two 32×32 imports land in free space of existing pages, a 200×100 semi-transparent
one gets a new 0x87 page; the WAD builds and re-imports (
TextureLibraryTest).
Paint tool (PaintTool)
- Hover shows the face under the cursor (white outline) and model/face/texture/flags in the
status bar (plus "model shared by n tiles"). Face picking:
ctx.pickNode→ tile, then a Möller–Trumbore ray test against that tile model's triangles (tile transform, export space). - Click = assign the current texture; scope Face / Same texture in model / Whole model. Shift+click or the eyedropper button / I = pick. The clicked face stays selected (orange).
- Rotate cycles the face's corners (v0,v1,v2 → v1,v2,v0; winding and plane unchanged),
Mirror toggles 0x20 (mirror U), Other half cycles 0x800/0x10 (
Geometry.faceUvs: no 0x800 = corners (a,v1)(b,v1)(a,v0); 0x800 = (a,v1)(b,v1)(b,v0); 0x810 = (b,v0)(a,v0)(a,v1)). - Unique: if the tile's model is shared, the edit first adds a copy of the model and points the tile at it (one undo step).
- Brushes: the clicked side's normal (from three depth samples) picks the parameter
top(n.y > 0.7) /bottom/side, else the first texture parameter (tex,post,trunk…); the value becomes"#n". Rotate incrementstexRot(0–3) of the last painted brush.
Texture animations (decoded, AnimSpec)
Loader FUN_00406e40 (16-byte runtime records at 0x581138, count 0x581134), update FUN_0040c6e0, called once per rendered frame from FUN_0054b590 while the game is not paused. The runtime texture record (20 bytes at 0x581154 + tex·0x14) holds the page and the UVs as floats (+0xc / +0x10 = v0 / v1, divided by the page height at 0x58114c + page·0x20 + 8).
| Type | File payload | Behaviour |
|---|---|---|
| 8 flip-book | delay, n, pingPong, n × u16 texture | every delay+1 frames the texture's runtime record is replaced by the next frame's (loop or back and forth). 214 in the levels (torches, water, flags), e.g. 03 08 00 d0 03 … |
| 1 / 2 scroll | band−1, speed, v0 | V range = v0+off … v0+off+band, off −= / += speed per frame, wraps in the band. 7× type 1, 2× type 2 (rivers, lava: 1f 02 01) |
| 10 / 11 | band−1, speed, v0, n, n × u16 | scroll as 2 / 1, each wrap switches to the next / previous frame. Unused |
| 7 / 9 | u16 palette, u16 delay, u8 first, u8 last | palette cycling of 8-bit pages. Unused (no palettes) |
The editor viewport does not animate textures; the Animation tab previews them.
Known limits
- Texture entries are never deleted (indices are referenced by faces and object models); unused ones can be reused by moving their rectangle.
- Object (STPC) model faces cannot be painted.
- Painting a shared original tile model changes every tile using it unless Unique is on.
- The page format of an existing page is not changed by the importer; importing into a page of another class is avoided.
Files: src/main/java/com/eliasmueller/llamamod/editor/tools/textures/*, tests
TextureLibraryTest (colour reduction, anim round trip of all village animations, packing,
build + undo), tour steps TexTourSteps (with playtest.EdTour).


