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
+22
View File
@@ -0,0 +1,22 @@
CREATE TABLE IF NOT EXISTS "chunk_sections" (
"server_id" uuid NOT NULL REFERENCES "servers"("id") ON DELETE CASCADE,
"dimension" integer NOT NULL,
"x" integer NOT NULL,
"z" integer NOT NULL,
"section_y" integer NOT NULL,
"blocks" text NOT NULL,
"updated_at" timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY ("server_id", "dimension", "x", "z", "section_y")
);
CREATE TABLE IF NOT EXISTS "mesh_pointers" (
"server_id" uuid NOT NULL REFERENCES "servers"("id") ON DELETE CASCADE,
"dimension" integer NOT NULL,
"x" integer NOT NULL,
"z" integer NOT NULL,
"section_y" integer NOT NULL,
"storage_key" text NOT NULL,
"content_hash" text NOT NULL,
"rendered_at" timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY ("server_id", "dimension", "x", "z", "section_y")
);