Phase 2: full-voxel chunk storage, greedy mesher, and Babylon 3D viewer

api: chunk_sections table (per 16x16x16 section, base64-encoded u16
blockStateId array) and mesh_pointers table, additive to Phase 1's
column-based chunk_columns/tile_pointers — 2D tile rendering keeps using
the cheap column path unchanged. New "sections" WS message (backfill on
chunk load + delta resend on flush, same "current state, not a diff"
philosophy as columns) reuses the existing dirty-chunk Redis event, so one
event now triggers the worker to re-render both the 2D tile and any 3D
meshes for that chunk. New mesh-serving routes.

worker: a from-scratch greedy mesher (per-axis 2D mask sweep + rectangle
merge — the standard voxel-meshing technique, reimplemented from its
public description, not copied from any codebase) producing a compact
custom binary vertex buffer per non-empty section. Verified with unit
tests, including one that specifically checks a uniform section collapses
to exactly 6 merged quads rather than one quad per voxel face (the
decisive signal that merging, not just per-voxel face emission, is
actually happening).

frontend: a barebones Babylon.js 3D viewer (/3d) that loads a fixed radius
of chunks, parses the mesh binary format, and renders each section as its
own mesh (no cross-section merging yet, no camera-based streaming yet —
both reasonable follow-ups once there's a reason to optimize).

End-to-end verified against live containers, including through the real
mod-side Java WS client (see MCMapper-Mod's matching commit): a known
half-solid section correctly round-trips to exactly 24 vertices / 36
indices at the mesh-serving endpoint, matching the "6 merged outer faces"
the unit tests predict.
This commit is contained in:
2026-08-08 16:19:03 +02:00
parent 7bed571ffa
commit 5ed4d32a56
15 changed files with 707 additions and 12 deletions
+1
View File
@@ -14,6 +14,7 @@ html(lang="en")
div#app.h-screen.flex.flex-col(x-data="mapmapper()" x-init="init()")
header.px-4.py-2.flex.items-center.gap-4.border-b.border-neutral-700
h1.text-lg.font-semibold MCMapper
a.text-sm.text-neutral-400.underline(href="/3d") 3D view
span.text-sm.text-neutral-400(x-show="!loading && server")
| Viewing:
span(x-text="server?.name")
+18
View File
@@ -0,0 +1,18 @@
doctype html
html(lang="en")
head
meta(charset="utf-8")
meta(name="viewport" content="width=device-width, initial-scale=1")
title MCMapper — 3D
link(rel="stylesheet" href="/css/tailwind.css")
script(src="https://cdn.babylonjs.com/babylon.js")
style.
html, body, #renderCanvas { height: 100%; margin: 0; touch-action: none; outline: none; }
body.bg-neutral-900.text-neutral-100
div.h-screen.flex.flex-col
header.px-4.py-2.flex.items-center.gap-4.border-b.border-neutral-700
h1.text-lg.font-semibold MCMapper — 3D
a.text-sm.text-neutral-400.underline(href="/") back to 2D map
span#status.text-sm.text-neutral-500 loading…
canvas#renderCanvas.flex-1
script(src="/js/mesh.js")