editor: readable names in inspector, status hover, asset browser and outliner filter, waypoint buttons and long inspector labels wrap, library keeps no collision, autosave no longer hides fresher messages, old menu play-test path removed, task runner always finishes (no stuck loading), play-test starts bound to their level
This commit is contained in:
1 parent
4a58910fba
commit
22a61246fc
26 files changed
+804
-271
No files matched your search
@@ -88,7 +88,7 @@ Invisible collision volumes (Invisible wall/block, colliders) are drawn transluc
|
||||
"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
|
||||
File chooser, then a dialog: rotatable preview (shown as in game) 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).
|
||||
@@ -102,12 +102,14 @@ and keeps the placement.
|
||||
→ `<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.
|
||||
are skipped. Tiles with **No collision** store `"noCollision": true` (the model keeps its collision, so
|
||||
the inserted tile shares/dedups the model and the flag can be switched off again); insert sets the
|
||||
flag (`GeometryChecksTest.libraryKeepsNoCollision`). Brush-compiled tiles have no such flag.
|
||||
* **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
|
||||
* **File → Export selection as OBJ…**: OBJ (game space = file 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
|
||||
@@ -115,7 +117,7 @@ and keeps the placement.
|
||||
library tiles, left the re-imported OBJ as a mesh brush).
|
||||
|
||||
### glTF export (File → Export level as glTF…, `editor.tools.gltf`)
|
||||
* `GltfExport.collect(scene, Options(objects, gameView), skip)` runs a private `SceneRenderer` on the
|
||||
* `GltfExport.collect(scene, Options(objects), skip)` runs a private `SceneRenderer` on the
|
||||
scene and takes exactly what the viewport draws: tile instances (scene tiles + compiled brushes and
|
||||
meshes), optionally object models; `excluded` tiles and (option) hidden nodes are skipped.
|
||||
Vertices are baked to world (export) space, deduplicated per primitive; one primitive per
|
||||
@@ -127,9 +129,9 @@ and keeps the placement.
|
||||
with cut-out pixels, `BLEND` for alpha/additive classes (glTF has no additive), `doubleSided`,
|
||||
`KHR_materials_unlit` (optional extension). u16 indices up to 65535 vertices, else u32.
|
||||
* `.glb` (JSON + BIN chunk) or `.gltf` (one file, buffer as base64 data URI).
|
||||
* Axes: export space (y up, z = −game z) like the OBJ export and the mesh importer, so a round trip
|
||||
through **Import mesh** is exact. **Game orientation** mirrors z (and winding) for viewers – see
|
||||
the open handedness question below.
|
||||
* Axes: game space (right-handed, y up, z = −export z, winding reversed) like the OBJ export and
|
||||
the mesh importer, so viewers show the level as the game does and a round trip through **Import
|
||||
mesh** is exact (see "Handedness of files"; the former "Game orientation" option is gone).
|
||||
* Verified: `GltfExportTest` (structural validation as the Khronos validator would: GLB header and
|
||||
chunk padding, view/accessor bounds, equal attribute counts, exact POSITION min/max, unit normals,
|
||||
UV range, index range, PNGs decode; .gltf data URI; round trip through `MeshImport` with the same
|
||||
@@ -141,6 +143,47 @@ and keeps the placement.
|
||||
(26 403 triangles, 16 materials, 15 textured, `img/editor/geo_gltf_export.jpg`).
|
||||
Village: 36 087 triangles with objects, 2.9 MB .glb; without objects 2.1 MB.
|
||||
|
||||
### Handedness of files (fix 2026-11-07)
|
||||
* **Problem (verified)**: the importer and both exports used export space as file space. The game
|
||||
(and the editor view) shows export space left-handed, Blender and glTF are right-handed, so
|
||||
everything crossing a file was mirrored: an "F" modelled in Blender (red front, blue +x end, as
|
||||
OBJ with Blender's default *Forward −Z, Up Y* and as .glb) showed as "ꟻ" on its red side in game,
|
||||
and the old glTF export of that level rendered in Blender (same camera) was the game picture
|
||||
flipped left-right (`img/editor/handed_before.jpg`: Blender front view | game | Blender render
|
||||
of the exported level). Editor round trips were exact (mirrored twice).
|
||||
* **Convention now**: file space = **game space** (right-handed, y up; 1 unit = 1 cell): file
|
||||
(x, y, z) = game (x, y, z) = export (x, y, −z); z-up files: game (x, z, −y). The conversion
|
||||
mirrors, so triangle corners 1 and 2 swap (`MeshImport.Builder.tri`; exports write (0, 2, 1)).
|
||||
Same in `MeshLibrary.exportObj` (also `_collision.obj`), `GltfExport` (positions, normals,
|
||||
winding; option "Game orientation" removed – it is now always so) and the Python tools
|
||||
(`geomlib.to_file/file_order`; `geom_export`/`geom_import --export-space` = old convention,
|
||||
`geom_demo` writes file space, `vorschau/render_level.py` reads it).
|
||||
* **Saved projects don't flip**: projects store the source file and re-parse it on load, so
|
||||
`MeshAsset` got a property **`handedness`** ("right" | "left", Inspector "Handedness"). The field
|
||||
default is "left" (old reading) – projects saved before have no such key and load as before;
|
||||
`MeshImport.importFile` sets "right" on every new import; Re-import keeps the old asset's value.
|
||||
Switching an old asset to "right" un-mirrors it. No format-version bump (older editors keep the
|
||||
unknown key and show the mesh mirrored).
|
||||
* Import dialog preview: screen x is now −x (game space is right-handed), so it shows the model
|
||||
as the game does (before it showed the mirror image of the game); it starts at yaw 215°, pitch
|
||||
−25°, i.e. from above at the model's front (before it looked from below – positive pitch tilted
|
||||
the view up). Drag left/right is inverted to keep the orbit direction. `PreviewShot` (test
|
||||
sources) paints it to a PNG. The manual's export screenshot had its "Game orientation" row
|
||||
painted out.
|
||||
* Python round trip unchanged: `geom_export` → `geom_import` of the village gives a WAD
|
||||
byte-identical to the old tools (also `geom_demo` → `geom_import`); `--export-space` writes the
|
||||
old files byte-identically.
|
||||
* Tests: `MeshHandednessTest` (asymmetric triangle: y-up and z-up OBJ and glTF keep coordinates
|
||||
and face +z; mirroring glTF node; "left" stays mirrored; new imports are "right"; a project whose
|
||||
JSON lacks the key loads as "left"), `ObjExportRoundTripTest` (box brush → OBJ (+collision) →
|
||||
Import mesh: same size, game z, every face outward), `GltfExportTest` (game-space bounds, box
|
||||
round trip at game z 10..13 with outward faces), `MeshImportTest` updated.
|
||||
* **Verified after the fix** (apptest :23, `HandednessDemo`: TestLevel 7, four F meshes at game
|
||||
z 13 – OBJ and .glb as imported, then both turned 180° – llama at (16, 0, 4) facing +z): the
|
||||
unturned Fs now show their grey back reading "ꟻ" (correct from behind), the turned ones their red
|
||||
front reading "F"; the new glTF export of the level rendered in Blender from the game camera
|
||||
matches the game picture (`img/editor/handed_after.jpg`).
|
||||
|
||||
### 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
|
||||
@@ -203,10 +246,6 @@ between neighbouring floors at the same height; brush compiler warnings.
|
||||
* 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: whole level only (no "selection" variant), no collision export (the OBJ export has it),
|
||||
no texture animations, additive surfaces become plain alpha blending. **Handedness**: the editor
|
||||
view is left-handed like the game (editor-render.md), but the mesh importer and the OBJ/glTF
|
||||
exports treat file coordinates as export space – so a right-handed tool (Blender) shows exported
|
||||
levels mirrored relative to the game, and an asymmetric model imported from Blender probably
|
||||
appears mirrored in game. Not verified in game; "Game orientation" in the glTF export is the
|
||||
workaround for viewing.
|
||||
no texture animations, additive surfaces become plain alpha blending. (Handedness: fixed, see
|
||||
"Handedness of files" above.)
|
||||
* `TextureImport` packs imported textures at one size per insert (largest texture, ≤ 256).
|
||||
+15
-1
@@ -33,6 +33,16 @@ Ctrl+Z/Y/Shift+Z, Ctrl+X/C/V, Ctrl+A, Ctrl+B build, F5 play, F1 help. Single key
|
||||
text field has the focus; while the GL canvas has the focus a `KeyEventDispatcher` maps keys to
|
||||
actions (AWT canvases do not get Swing key bindings).
|
||||
|
||||
* Status messages: `ctx.status(msg)` shows a message for ~6 s; the autosave note uses
|
||||
`backgroundStatus`, which waits until a fresher user message has expired (it no longer replaces e.g.
|
||||
the glTF export result).
|
||||
* Inspector property labels have a fixed 118 px column; longer labels wrap onto more lines
|
||||
(`InspectorPanel.fitLabel`) instead of being cut ("Exclude from build").
|
||||
* Readable names everywhere (`img/editor/ui_readable_names.jpg`: outliner filter "coin 3", inspector
|
||||
"Coin 3", status bar hover "Coin 3"/"Tile 41, 66 · Model 87", asset tab "Model 0"); inspector button
|
||||
rows wrap without clipping in a narrow dock (`ui_waypoint_buttons_narrow.jpg`); long property labels
|
||||
wrap (`ui_inspector_label_wrap.jpg`).
|
||||
|
||||
## Architecture
|
||||
|
||||
| Class | Role |
|
||||
@@ -44,7 +54,9 @@ actions (AWT canvases do not get Swing key bindings).
|
||||
| `Document` | `Project` + `History` + file + autosave state; `dirty()` |
|
||||
| `Selection` | ordered node set, primary = last, listeners get the source (avoid echo loops), auto-pruned |
|
||||
| `ProjectController` | new/open/save/save as/recent, autosave + recovery, thumbnail, import/export |
|
||||
| `PlayTest` | check, background build, play-test |
|
||||
| `PlayTest` | check, background build, install/mod-selection/save-slot helpers for the play-test plugin (the old menu-based `play()` is gone; `level.play` is a disabled placeholder that `PlaytestEditorPlugin` replaces) |
|
||||
| `TaskRunner` | `runTask`: one background task on its own daemon thread; progress and completion are posted with `invokeLater` from that thread, so the completion always runs after the last progress update, also when the task or the done callback throws (replaced `SwingWorker`, whose `done()` goes through a Swing timer) |
|
||||
| `NodeNames` | readable names shared by outliner, inspector header, status-bar hover, reference buttons and the Tile models asset tab ("Floor 2", "Coin 3", "Waypoint 4 (chain 2)", "Tile 3, 4 · Model 12", "Model 12"); numbers follow the level order |
|
||||
| `view.SceneRenderer` | scene model → `RenderScene`, live, stable pick ids |
|
||||
| `view.Thumbnails` | software rasteriser for asset thumbnails |
|
||||
| `tools.SelectTool`, `TransformSupport`, `Transformers`, `EditOps` | selection/transform tools and edit operations |
|
||||
@@ -113,6 +125,8 @@ same id again replaces (panels, tools, actions, asset tabs, layers).
|
||||
| `LevelTemplate` | `fill(Scene)` adds nodes directly (no undo) before the editor shows the new level |
|
||||
| `LevelCheck` | adds `Problem.error/warning/info(msg, node)`; errors block Build/Play |
|
||||
| `Icons` | vector icons by name; `Icons.register(name, painter)` for your own (16×16 Java2D); `Icons.swatch(colour, w, h)` |
|
||||
| `NodeNames` | `NodeNames.name(node)` (one node) or `NodeNames.of(scene)` (index for many: `nameOf`, `objectName`, `waypointName`, `chainStarts`) – use it instead of `Node.label()` in UI texts; tooltips may show `node.id()` |
|
||||
| `WrapLayout` | public `FlowLayout` that grows to more rows in a narrow container (inspector button rows: `ParamsSection.compact` switches a FlowLayout bar to it) |
|
||||
| `Widgets`, `util.SwingListeners` | `Widgets.button(text, icon, tip, run)`, `Widgets.listCell(c, list, selected)`, `Widgets.wrappingLabel(html)` (help text that wraps at the dock width – use it instead of `<body style='width:…'>`); `onTextChange`, `onMenuOpen` |
|
||||
| `ErrorDialog` | `ctx.error(title, t)` uses it: plain sentence (`ErrorDialog.plain(t)`, no exception class names) + collapsible **Details** (stack trace) |
|
||||
| Game state | `ctx.setGameRunning(what, stop)` (default no-op): while a play-test game runs the status bar shows "Game running: what" and the toolbar Play button becomes **Stop game** (runs `stop`); `null` resets. `PlaytestRunner` sets it on launch and clears it when its watcher sees the game exit |
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 111 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 121 KiB |
Reference in new issue
Block a user