@thoughts-on-things/vantage-mc
v0.4.0
Published
Render a Minecraft (Java) world as a fast, beautiful 3D web map. The web frontend for the Vantage generator.
Maintainers
Readme
@thoughts-on-things/vantage-mc
The web frontend for Vantage — render a Minecraft (Java) world as
a fast, beautiful, navigable 3D map in the browser. The native Zig generator bakes
versioned binary tiles (.vtile / .vtexarr); this package streams and shades
them with three.js.
It ships in three layers so you can enter at whatever altitude you need:
| Import | What it is | Depends on |
| --- | --- | --- |
| @thoughts-on-things/vantage-mc/core | Zero-dependency, isomorphic decoder for the binary tile format | — |
| @thoughts-on-things/vantage-mc/three (also the default package export) | three.js meshes/materials + a framework-agnostic viewer engine | three |
| @thoughts-on-things/vantage-mc/react | Drop-in React components | three, react |
Install
npm install @thoughts-on-things/vantage-mc three
npm install react react-dom # if you use the React entry pointthree, react, and react-dom are peer dependencies — you bring your own.
The package is ESM-only: CJS consumers need "type": "module" or a dynamic
import().
Quick start (React)
The whole map in one component. Point it at the manifest the generator produced
(just render <save> writes manifest.json + tiles/ + the texture array to
web/public/). Tiles stream in around the camera as the user pans — world size
doesn't matter.
import { VantageViewer, BiomeLayer } from '@thoughts-on-things/vantage-mc/react';
export function Map() {
return (
<div style={{ width: '100%', height: '100vh' }}>
<VantageViewer world="/manifest.json">
<BiomeLayer legend hover />
</VantageViewer>
</div>
);
}<VantageViewer> fills its parent, frames the world at its spawn point, and
provides the engine state to children via context. <BiomeLayer> adds the
interactive biome legend (click to isolate, hover the map to identify, press
B to toggle) — aggregated live across whatever tiles are resident. Add
<SettingsPanel /> for a quality menu: low/med/high/ultra
presets plus view-distance, tile-budget, render-scale, and haze sliders, all
applied live (viewer.setStreaming re-plans in place, no reload). Or set the
ring up-front with streaming={{ viewDistance: 1024, maxTiles: 128 }}.
The camera also lives in the URL hash (#@x,y,z,dist,rot,tilt), so every
view is a shareable deep link: the hash follows the camera (debounced,
history.replaceState — no history spam), a link opens at its saved view, and
pasting a new hash into the address bar pans there live. Pass
urlState={false} if your page owns its hash (e.g. a router); the plain
engine keeps it opt-in (new VantageViewer(el, { urlState: true })).
The viewer renders on demand: a frame draws only when something changed —
camera motion, tiles streaming in, a setting — plus a gentle 10 fps tick while
animated textures (water, lava) are on screen, so an idle map costs ~0 GPU/CPU
instead of a render loop. Input still draws the same frame it arrives. Pass
renderOnDemand: false to the engine if you drive per-frame effects off its
scene, or call viewer.invalidate() after mutating it externally.
viewer.screenshot() returns the current view as a PNG data URL (<MapNav>
has a button for it, next to its fullscreen toggle).
Worlds baked with vantage render --caves full unlock the cave view: a
depth slice that cuts the world open at any Y — press C, click <MapNav>'s
layers button, or drag the <DepthSlider> gauge that appears on the left
edge. The cut peels open with an eased reveal, a warm rim outlines the
section, unexplored rock reads as a dark chunk-gridded floor, and the slice
depth rides the URL hash (#@x,y,z,dist,rot,tilt,sliceY) so cave finds are
shareable links. Engine API: viewer.setSlice(y | null),
viewer.toggleSlice(), viewer.slice, viewer.sliceRange,
viewer.hasCaves, and a 'slice' event.
Format-2 manifests carry a lowres LOD pyramid (.vlr colored
heightfields): the viewer keeps coarse rings resident far beyond the hires
disc — level 1 also underlays it as a loading placeholder — so the whole world
stays visible at any zoom, right out to a satellite view.
A single standalone tile (from vantage meshtex) still works:
<VantageViewer tile="/terrain.vtile" textures="/terrain.vtexarr" />Reach the engine for custom UI with the useVantage() hook or a ref:
import { useVantage } from '@thoughts-on-things/vantage-mc/react';
function Stats() {
const { info, biomes, hoveredBiome } = useVantage();
return <span>{info?.triangleCount.toLocaleString()} tris · {biomes.length} biomes</span>;
}As a backend — your own three.js scene
Skip the engine and drop Vantage meshes into a scene you control (your own camera, lighting, post-processing):
import { maybeInflate, parseTile, parseTextureArray } from '@thoughts-on-things/vantage-mc/core';
import { buildTerrain } from '@thoughts-on-things/vantage-mc/three';
const tile = parseTile(await maybeInflate(await (await fetch('/tiles/t.0.0.vtile')).arrayBuffer()));
const tex = parseTextureArray(await maybeInflate(await (await fetch('/terrain.vtexarr')).arrayBuffer()));
const { terrain, water, bounds, shader } = buildTerrain(tile, tex);
scene.add(terrain);
if (water) scene.add(water);
// `shader` exposes the live uniforms: biome mix, fog, light direction.
shader!.uniforms.uBiomeMix.value = 1; // recolour by biomeOr stream a whole world into your scene with TileManager (shared materials,
nearest-first fetch queue, distance-based unload, height/biome queries):
import { biomePalette, maybeInflate, parseManifest, parseTextureArray } from '@thoughts-on-things/vantage-mc/core';
import { createTerrainMaterial, createWaterMaterial, TileManager } from '@thoughts-on-things/vantage-mc/three';
const manifest = parseManifest(await (await fetch('/manifest.json')).json());
const tex = parseTextureArray(await maybeInflate(await (await fetch(manifest.textures)).arrayBuffer()));
const material = createTerrainMaterial(tex);
const tiles = new TileManager({
manifest,
baseUrl: location.origin + '/',
scene,
material,
waterMaterial: createWaterMaterial(material),
palette: biomePalette(manifest.biomes.length),
});
// each frame: keep the resident set centred on your camera
tiles.update(camera.position.x, camera.position.z);Or the batteries-included engine without React:
import { VantageViewer } from '@thoughts-on-things/vantage-mc/three';
const viewer = await VantageViewer.mount('#app', { world: '/manifest.json' });
viewer.setBiomeLayer(true);
viewer.on('hover', (biomeId) => { /* ... */ });
viewer.on('stats', ({ loaded, triangleCount }) => { /* streaming HUD */ });Just the format
@thoughts-on-things/vantage-mc/core has no dependencies and runs anywhere (browser, worker, Node) —
use it to inspect or transcode tiles without a renderer:
import { parseTile, summarizeBiomes } from '@thoughts-on-things/vantage-mc/core';
const tile = parseTile(buffer);
console.log(tile.magic, tile.vertexCount, tile.indexCount / 3, 'tris');
for (const b of summarizeBiomes(tile)) console.log(b.label, `${Math.round(b.fraction * 100)}%`);The decoder handles every tile version the generator emits (VTL1–VTL6) and
returns only the fields a given version carries (textured, hasBiome, fluid,
surface). Tiles and the texture array ship gzip-wrapped on disk (~8× smaller);
maybeInflate sniffs the magic and inflates via the platform's native
DecompressionStream — a no-op when the server already handled it.
API surface
@thoughts-on-things/vantage-mc/core — parseTile, parseTextureArray, parseManifest,
parseLowresTile, maybeInflate, isGzip, tileKey, summarizeBiomes,
biomePalette, stripNamespace, ByteReader; types DecodedTile,
MeshSection, SurfaceMap, DecodedTextureArray, WorldManifest,
ManifestTile, LowresTile, BiomeEntry, Rgb.
@thoughts-on-things/vantage-mc/three — buildTerrain, buildTileMeshes,
buildQuantizedTileMeshes, buildLowresMesh, TileManager,
createTerrainMaterial, createWaterMaterial, createLowresMaterial,
createSky, pickBiome, MapControls, VantageViewer (engine),
VANILLA_DISPLAY, DEFAULT_ORBIT_ANGLE; types TerrainObjects,
TileMeshes, TileManagerOptions, TileStats, VantageViewerOptions,
LoadOptions, StreamingSettings, LightSettings, DisplaySettings,
TileInfo, ViewMode. Re-exports all of core.
@thoughts-on-things/vantage-mc/react — <VantageViewer>, <BiomeLayer>, <SettingsPanel>,
<LightPanel>, <MapNav>, <DepthSlider>, <Reticle>, <Panel>, useVantage,
injectStyles, QUALITY_PRESETS; types VantageViewerProps,
BiomeLayerProps, SettingsPanelProps, VantageContextValue,
VantageStatus. Re-exports the engine and core types.
Develop
This directory is both the published package and its demo (the reference
consumer in demo/).
npm install
npm run dev # Vite dev server at http://127.0.0.1:8753 (serves public/)
npm run typecheck # tsc --noEmit
npm run build # tsup → dist/ (ESM + .d.ts for each entry)From the repo root the Justfile wraps the loop:
just render "<save>" bakes tiles into public/, then just serve runs the
dev server.
The generator and this package are decoupled by the versioned binary tile
contract (src/core/format.ts): the frontend only
needs the format, never the world — so a new Minecraft version "just works"
once the generator can read it.
