Usage
What you need for most integrations. The generated API reference covers the rest.
Install
three.js is a peer dependency. V2 is published under the alpha tag while the API settles.
npm install minerender@alpha three
# or
yarn add minerender@alpha three
Without a build step, load the bundle. It includes three.js and exposes everything as window.MineRender.
<script src="https://unpkg.com/minerender@alpha/dist/bundle.js"></script>
<script>
const renderer = new MineRender.Renderer({ controls: { enabled: true } });
renderer.appendTo(document.getElementById("render"));
renderer.start();
</script>
Your first render
Create a renderer, attach it to an element that has a size, start the loop, then add content to its scene. Every add method is asynchronous because assets load on demand.
import { AssetKey, BlockStates, Renderer } from "minerender";
const renderer = new Renderer({
camera: { position: [40, 30, 50], lookingAt: [0, 0, 0] },
controls: { enabled: true } // built-in orbit controls
});
renderer.appendTo(document.getElementById("render")!);
renderer.start();
// A player skin
await renderer.scene.addSkin("https://example.com/skin.png");
// A block, from its blockstate
const state = await BlockStates.get(AssetKey.parse("blockstates", "grass_block"));
const block = await renderer.scene.addBlock(state!);
block.setPosition(block.getPosition().set(32, 0, 0)); // 1 block = 16 units
// When the view goes away
renderer.dispose();
Renderer options
All options are optional and deep-merged over these defaults.
| Option | Default | Description |
camera.type | "perspective" | Or "orthographic". |
camera.position | [50, 50, 50] | Initial camera position as a Vector3 or [x, y, z]. |
camera.lookingAt | [0, 0, 0] | Camera target, and the orbit centre when controls are enabled. |
camera.near, camera.far | 1, 5000 | Clipping planes in scene units. |
camera.perspective.fov | 50 | Field of view in degrees. |
render.fpsLimit | 60 | Maximum frames per second. Zero or negative removes the cap. |
render.antialias | true | WebGL antialiasing. |
render.shade | true | Minecraft-style per-face shading on models. |
render.autoResize | true | Resize the canvas when the window resizes. |
render.renderAlways | false | Render every frame instead of only when the scene is dirty. |
render.stats | false | Show a stats.js overlay. |
composer.enabled | true | Route output through a post-processing composer. Disable for the fastest path. |
controls.enabled | false | Create renderer-owned orbit controls, available as renderer.controls. |
debug.grid, debug.axes | false | Add grid and axes helpers. |
Scene and objects
renderer.scene is a three.js Scene with asset-aware add methods.
scene.addSkin(textureUrl?, options?) // SkinObject
scene.addBlock(blockState, options?) // BlockObject
scene.addModel(model, options?) // ModelObject | InstanceReference
scene.addEntity(entityModel, options?) // EntityObject | InstanceReference
scene.stats // object and instance counts
Scene objects extend Object3D. Beyond the usual three.js properties they offer setPosition, setRotation, setScale, getGroupByName, getMeshByName, toggleGroupVisibility, removeFromScene and disposeAndRemoveAllChildren.
Every add method accepts the same base options, plus type-specific ones.
{
mergeMeshes: true, // one geometry per model
instanceMeshes: true, // share an InstancedMesh per model
maxInstanceCount: 50, // slots per instanced model
wireframe: false, // debug outlines
// models and blocks
displayPosition: DisplayPosition.GUI, // apply a display transform
tints: { 0: 0x79c05a }, // sRGB color per tint index
// skins
slim: undefined, // true, false, or undefined to detect
legacy: undefined, // 64×32 layout; undefined to detect
// entities
flip: true // vanilla models point down the Y axis
}
With instanceMeshes on, repeated models return an InstanceReference that only carries transforms. Pass instanceMeshes: false to get a standalone object you can remove on its own.
Redraws
Frames are drawn only when something changed. The built-in controls and the scene's add methods mark the scene dirty for you. Anything you change by hand needs a nudge.
skin.getGroupByName("head")!.rotation.y = 0.5;
renderer.dirty = true;
// Other event sources can request redraws too
renderer.registerEventDispatcher(myControls, "change");
Assets and versions
Assets are addressed by AssetKey and loaded through an ordered list of sources. The default source is a CDN with vanilla assets for the version in AssetLoader.ROOT.
import { AssetKey, AssetLoader, Caching, HostedAssetSource, Models } from "minerender";
// Keys
AssetKey.parse("blockstates", "minecraft:oak_log");
new AssetKey("minecraft", "diamond_sword", "models", "item", "assets");
// Another game version for everything that follows
AssetLoader.setVersion("1.20.4");
// Sources added later are searched first
AssetLoader.addSource("pack", new HostedAssetSource("https://cdn.example/pack"));
// In-memory caches
Caching.clear();
await Models.clearCache();
Resource-pack ZIPs work in the browser through BrowserArchiveProxy and ArchiveAssetSource; see the example. Persistent caches (IndexedDB in browsers, a directory in Node) are keyed by asset root, so versions never collide.
Lifecycle
stop() pauses the loop and start() resumes it. dispose() is final: it releases the WebGL context, the controls and owned helpers. Shared assets stay cached.
renderer.stop();
renderer.start();
renderer.dispose();
// In Node, or before unloading a page: end caches, queues and timers
import { shutdown } from "minerender";
shutdown();
To grab the current frame, render once and read the canvas.
renderer.renderer.render(renderer.scene, renderer.camera);
const dataUrl = renderer.toImage();
Node
The node export condition installs a platform provider backed by node-canvas and node-persist.
Asset loading, model merging, atlas generation and structure parsing work in Node. The Node entry does not create a WebGL context, so drawing to an image there needs one from a package such as headless-gl.
import { AssetKey, AssetLoader, AssetParser, Models, StructureParser, shutdown } from "minerender";
const sword = await Models.getMerged(new AssetKey("minecraft", "diamond_sword", "models", "item", "assets"));
console.log(sword?.elements?.length, "elements");
const key = new AssetKey("minecraft", "igloo/top", "structure", undefined, "data", ".nbt");
const structure = await StructureParser.parse((await AssetLoader.get(key, AssetParser.NBT))!);
console.log(structure.size, structure.blocks.length, "blocks");
shutdown(); // let the process exit
Performance
- One renderer per visible area. Browsers limit WebGL contexts. This page keeps at most two alive and swaps the rest for snapshots.
- Dispose what scrolls away and recreate it on demand. Assets stay cached, so the second load is quick.
- Leave instancing on for repeated models, and set
maxInstanceCount above the number of copies you expect.
- Disable the composer when you do not need post-processing.
- Lower
fpsLimit on busy pages. The dirty flag already skips idle frames.