Phase 12: texture atlas + UV-mapped 3D mesh textures
Fixes a real gap left over from Phase 11: mesh.rs's 3D mesher was still using the hand-picked flat palette instead of texture-averaged colors. Adds worker/src/atlas.rs to pack downloaded block textures into a single PNG atlas + UV rect map, threads tile-relative UV and atlas-rect buffers through the mesh binary format (v2, hard break — meshes are a regenerable render cache), serves the atlas from MinIO via two new api routes, and adds a custom Babylon shader that falls back to flat vertex colors per-fragment for untextured quads. glTF export intentionally stays vertex-color-only (documented reasoning in gltf-export.js) since standard glTF materials can't express that same per-fragment fallback. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015tKdPZt78zbPUZMXWzKEKt
This commit is contained in:
@@ -90,7 +90,7 @@ to thread a server-scoped palette through `main.rs`'s batch loop instead of one
|
||||
already has every loaded mod's texture assets, just unused server-side. The `forge-1_12_2` mod
|
||||
leaf (Enigmatica 2, this project's primary target) extracts them once at startup
|
||||
(`BlockAssetExtractor`, best-effort: guesses each block's texture by its registry-name path
|
||||
segment, not a real blockstate/model JSON resolution — that's Phase 12's job) and ships two new
|
||||
segment, not a real blockstate/model JSON resolution — that's Phase 13's job, see below) and ships two new
|
||||
WS messages after connecting: `block_registry` (numeric id -> registry name, needed since a
|
||||
numeric `blockId` alone is meaningless without the mod list that assigned it) and `block_textures`
|
||||
(the extracted PNGs, batched). The api stores both (`api/src/textures.ts`: registry rows in a new
|
||||
@@ -103,6 +103,66 @@ implement extraction yet either — `BackendConnection#sendBlockRegistry`/`#send
|
||||
on the shared interface (so any leaf can adopt them later with no protocol change), but only the
|
||||
1.12.2 leaf calls them so far, matching this phase's Enigmatica-2-focused verification target.
|
||||
|
||||
### Texture atlas + UV-mapped 3D meshes (Phase 12)
|
||||
|
||||
Phase 11 only fed texture-averaged colors into the 2D tile path — `worker/src/mesh.rs`'s 3D
|
||||
section mesher still called the plain hand-picked `palette::color_for`, so the 3D viewer's "now
|
||||
accurate" claim in that phase's writeup wasn't actually true yet. Fixed first: `mesh.rs` now calls
|
||||
`color_for_textured` too, same as the 2D path.
|
||||
|
||||
On top of that, `worker/src/atlas.rs` packs every block texture the worker already downloads (see
|
||||
Phase 11 above) into a single RGBA PNG atlas (one native `16x16` tile per texture, deterministically
|
||||
laid out in a square-ish grid) plus a `texture name -> normalized [u0,v0,u1,v1]` rect map, cached
|
||||
to disk like the palette. It does its own jar download rather than sharing Phase 11's — a small,
|
||||
one-time, cached duplicate fetch, accepted to keep the two build paths independent. Unlike the
|
||||
palette's post-hoc `texturepacks/` overlay, the atlas always rebuilds (and caches under its own
|
||||
`-<pack>` suffixed filename) when `TEXTURE_PACK` is set, since there's no cheap way to patch one
|
||||
tile back out of an already-packed image.
|
||||
|
||||
`mesh.rs` now emits two new per-vertex buffers alongside the existing position/normal/color ones:
|
||||
tile-relative `uv` (unbounded — a merged quad spanning N blocks has that UV coordinate run 0..N,
|
||||
not 0..1) and `atlasRect` (the same 4 floats repeated for all 4 vertices of a quad; `[0,0,0,0]`
|
||||
sentinel when the block has no atlas entry — the frontend falls back to the flat vertex color for
|
||||
that quad). This is a hard break in the section-mesh binary wire format (v2) — safe to do without
|
||||
any migration, since rendered meshes are a fully regenerable cache (MinIO + a Postgres pointer
|
||||
row), not a durable artifact; an old-format blob just gets silently overwritten the next time that
|
||||
section's dirty-chunk job runs.
|
||||
|
||||
The atlas PNG + UV-map JSON are uploaded once at worker startup to fixed, version-agnostic MinIO
|
||||
keys (`atlas/current.png`, `atlas/current.json` — matches the worker-wide-only scope already
|
||||
established for the palette/texturepack in Phase 11) and served by `api` at `GET /api/atlas.png`
|
||||
/ `GET /api/atlas.json` (404 until a worker with `ACCEPT_MINECRAFT_EULA=true` has built one).
|
||||
|
||||
The live Babylon 3D viewer (`frontend/src/public/js/mesh.js`) uses a custom unlit `ShaderMaterial`
|
||||
(nearest-neighbor sampling, mipmaps disabled — bilinear/mip blending would bleed a tile's edge
|
||||
pixels into its atlas neighbor) whose fragment shader `fract()`s the tile-relative UV to repeat a
|
||||
single atlas tile across a merged quad, and falls back to the plain vertex color per-fragment when
|
||||
`atlasRect` is the `[0,0,0,0]` sentinel — this per-fragment branch is what makes the live viewer
|
||||
strictly more capable than the exported glTF here (see below), and is what actually makes greedy
|
||||
meshing (which merges many blocks into one quad) compatible with per-block texture tiling at all.
|
||||
Falls back entirely to the pre-Phase-12 flat-color `StandardMaterial` if no atlas was ever
|
||||
uploaded (fetch 404/error). **Not yet empirically verified against a real running worker + browser**
|
||||
(no headless-GL environment available in this dev setup) — in particular `invertY`'s row-order
|
||||
assumption against `atlas.rs`'s top-down PNG rows is unconfirmed, worth checking on first live
|
||||
test, same "flagged, not yet live-tested" caveat this project already carries for the Xaero
|
||||
waypoint format (Phase 4).
|
||||
|
||||
The client-side region-export mesher (`voxel-mesh.js`) gained the identical UV/atlasRect output
|
||||
(ported by hand from `mesh.rs`, same pattern as `block-colors.js` mirroring `palette.rs` — see the
|
||||
new `block-textures.js` mirroring `block_names.rs`), but **`gltf-export.js` deliberately does not
|
||||
embed the atlas texture into exported glTFs** — still vertex-color-only, unchanged from before this
|
||||
phase. Reason: standard glTF materials only support one fixed formula (`baseColorTexture *
|
||||
baseColorFactor * COLOR_0`, no branching), so the live viewer's per-fragment vertex-color fallback
|
||||
for untextured quads isn't expressible in a way that works in arbitrary external viewers (Blender,
|
||||
generic glTF web viewers) — properly supporting it needs either a reserved always-white atlas tile
|
||||
baked into `atlas.rs` or splitting merged geometry into per-material primitives, both real scope,
|
||||
deliberately deferred rather than shipping some faces textured and others visibly wrong.
|
||||
|
||||
**Still not done** (see the plan's phase list): real non-cube block/blockentity geometry via
|
||||
blockstate/model JSON parsing (`BlockAssetExtractor`'s texture matching is still a filename
|
||||
convention guess, not a real model resolution) — renumbered to **Phase 13** once the atlas/UV
|
||||
scope above turned out to be its own full phase; Thaumcraft remains the named stress test for it.
|
||||
|
||||
## Running
|
||||
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user