VoxelRenderer
ActorComponent that renders a layered voxel world as chunked Three.js meshes. Each chunk is rebuilt only when its content changes, keeping GPU work proportional to edits rather than world size.
// Pre-load tilesets before constructing VoxelRenderer (no async in lifecycle).
const loader = new TilesetLoader();
await loader.fromTileDefinition({
id: "default",
src: "tileset.png",
tileSize: 16
});
const vr = actor.addComponentAndGet(VoxelRenderer, {
tilesetLoader: loader,
layers: ["Ground"],
blocks: [
{
id: 1,
name: "Grass",
shapeId: "cube",
collidable: true,
faceTextures: {},
defaultTexture: {
col: 0,
row: 0
}
}
]
});
vr.setVoxel("Ground", {
position: { x: 0, y: 0, z: 0 },
blockId: 1
});
vr.setVoxel("Ground", {
position: { x: 1, y: 0, z: 0 },
blockId: 1,
rotation: VoxelRotation.CW90,
flipX: false,
flipZ: false
});
vr.setVoxel("Ground", {
position: { x: 2, y: 0, z: 0 },
blockId: 1,
flipY: true
});
const entry = vr.getVoxel({
x: 0, y: 0, z: 0
});
// Move an entire layer in world space
// e.g. snap a prefab layer to a new grid position
vr.setLayerOffset("Ground", {
x: 8, y: 0, z: 0
});
// Shift a layer incrementally
vr.translateLayer("Ground", {
x: 0, y: 1, z: 0
});VoxelRendererOptions
type MaterialCustomizerFn = (
material: THREE.MeshLambertMaterial | THREE.MeshStandardMaterial,
tilesetId: string
) => void;
interface VoxelRendererOptions {
/**
* @default 16
*/
chunkSize?: number;
/**
* Enables collision shapes when provided.
* disabled by default to avoid forcing Rapier
* as a dependency for users who don't need physics.
*/
rapier?: {
/** Rapier3D module (static API) */
api: RapierAPI;
/** Rapier3D world instance */
world: RapierWorld;
};
/**
* @default "lambert"
* The type of material to use for rendering chunks. "standard" supports
* roughness and metalness maps but is more expensive to render; "lambert"
* is faster but only supports a simple diffuse map.
*/
material?: "lambert" | "standard";
/**
* Optional callback to customize each material after it is created.
* Called with the material instance and the tileset ID it corresponds to
*/
materialCustomizer?: MaterialCustomizerFn;
/**
* Optional list of layer names to create on initialization.
*/
layers?: string[];
/**
* Optional initial block definitions to register.
* Block ID 0 is reserved for air
*/
blocks?: BlockDefinition[];
/**
* Optional block shapes to register in addition to the default
* shapes provided by BlockShapeRegistry.createDefault().
*/
shapes?: BlockShape[];
/**
* Alpha value below which fragments are discarded (cutout transparency).
* Set to 0 to disable alpha testing entirely (useful when your tileset tiles
* have no transparency, or during debugging to confirm geometry is present).
* @default 0.1
*/
alphaTest?: number;
/**
* Optional logger instance for debug output.
* Uses the engine's default logger if not provided.
*/
logger?: Systems.Logger;
/**
* Optional callback that is called whenever a layer is
* - added
* - removed
* - updated.
* Useful for synchronizing external systems with changes to the voxel world.
*/
onLayerUpdated?: VoxelLayerHookListener;
/**
* Optional pre-loaded tileset collection. All tilesets in the loader are
* registered synchronously during construction. Use `TilesetLoader.fromTileDefinition()`
* or `TilesetLoader.fromWorld()` to populate it before constructing `VoxelRenderer`.
*/
tilesetLoader?: TilesetLoader;
}VoxelRenderer
Extends ActorComponent.
Properties
class VoxelRenderer {
readonly world: VoxelWorld;
readonly blockRegistry: BlockRegistry;
readonly shapeRegistry: BlockShapeRegistry;
readonly tilesetManager: TilesetManager;
readonly serializer: VoxelSerializer;
}Methods
getLayer(name: string): VoxelLayer
Find a layer or null if none is found with name.
addLayer(name: string, options?: VoxelLayerConfigurableOptions): VoxelLayer
Creates and returns a new named layer.
options is described by the following interface:
interface VoxelLayerConfigurableOptions {
visible?: boolean;
properties?: Record<string, any>;
}updateLayer(name: string, options?: Partial< VoxelLayerConfigurableOptions >): boolean
Update a layer that already exists. Return false if no layer is found with the given name and true when updated.
removeLayer(name: string): VoxelLayer
Remove and returns a boolean confirming layer deletion.
setLayerOffset(name: string, offset: VoxelCoord): void
Sets the world-space translation of a layer. All voxels in the layer shift to localPosition + offset. Triggers a full dirty-chunk pass so cross-layer face culling is re-evaluated on the next frame. No-op if the layer is not found.
translateLayer(name: string, delta: VoxelCoord): void
Adds delta to the layer's current offset. Equivalent to setLayerOffset with layer.offset + delta. No-op if the layer is not found.
moveLayer(name: string, direction: "up" | "down"): void
Swaps order with the neighbouring layer in the given direction.
getLayerCenter(name: string): Vector3 | null
Returns the world-space center of all voxels in the given layer
setVoxel(layerName: string, options: VoxelSetOptions): void
Places a voxel at a world-space position.
interface VoxelSetOptions {
position: THREE.Vector3Like;
blockId: number;
/** Y-axis rotation in 90° steps. Default: `VoxelRotation.None`. */
rotation?: VoxelRotation;
/** Mirror the block on the X axis. Default: `false`. */
flipX?: boolean;
/** Mirror the block on the Z axis. Default: `false`. */
flipZ?: boolean;
/** Mirror the block geometry around y = 0.5 (upside-down). */
flipY?: boolean;
}Y-axis rotation applied to a placed voxel, in 90° steps.
const VoxelRotation = {
None: 0, // 0°
CCW90: 1, // 90° counter-clockwise
Deg180: 2, // 180°
CW90: 3, // 90° clockwise
} as const;
type VoxelRotation = typeof VoxelRotation[keyof typeof VoxelRotation];removeVoxel(layerName: string, options: VoxelRemoveOptions): void
Removes the voxel at a world-space position.
interface VoxelRemoveOptions {
position: THREE.Vector3Like;
}setVoxelBulk(layerName: string, entries: VoxelSetOptions[]): void
Places multiple voxels in the specified layer in a single batch call.
voxelRenderer.setVoxelBulk("Ground", [
{ position: { x: 0, y: 0, z: 0 }, blockId: 1 },
{ position: { x: 1, y: 0, z: 0 }, blockId: 2, rotation: VoxelRotation.CW90 },
]);Each item in entries accepts the same fields as VoxelSetOptions.
removeVoxelBulk(layerName: string, entries: VoxelRemoveOptions[]): void
Removes multiple voxels from the specified layer in a single batch call.
voxelRenderer.removeVoxelBulk("Ground", [
{ position: { x: 0, y: 0, z: 0 } },
{ position: { x: 1, y: 0, z: 0 } },
]);getVoxel overloads
getVoxel(position: VoxelCoord): VoxelEntry | undefined
getVoxel(layerName: string, position: VoxelCoord): VoxelEntry | undefinedComposited read (first overload) or layer-specific read (second overload). Returns undefined for air.
getVoxelNeighbour overloads
getVoxelNeighbour(position: VoxelCoord, face: Face): VoxelEntry | undefined
getVoxelNeighbour(layerName: string, position: VoxelCoord, face: Face): VoxelEntry | undefinedReturns the voxel immediately adjacent to position in the given face direction. Composited (first overload) or restricted to a specific layer (second overload).
loadTileset(def: TilesetDefinition, texture: THREE.Texture<HTMLImageElement>): void
Registers an already-loaded texture for a tileset definition. The first registered tileset becomes the default for TileRef values with no explicit tilesetId. Prefer passing a TilesetLoader via VoxelRendererOptions.tilesetLoader for pre-loading; use this method only when adding a tileset after construction.
save(): VoxelWorldJSON
Serialises the full world state (layers, voxels, tileset metadata) to a plain JSON object.
load(data: VoxelWorldJSON): void
Clears the current world and restores state from a JSON snapshot. All tilesets referenced by the snapshot must have been pre-loaded via TilesetLoader before this call — if a tileset is missing, an error is thrown. Already-registered tilesets are skipped.
markAllChunksDirty(source?: string): void
Mark all the chunks as dirty and rebuild them in the next frame
Object Layer API
Object layers hold placed objects (spawn points, trigger zones, etc.) rather than voxel data. Each mutating method fires a VoxelLayerHookEvent so external systems stay in sync.
addObjectLayer(name: string, options?: { visible?: boolean; order?: number }): VoxelObjectLayerJSON
Creates a new object layer in the world and fires "object-layer-added". Returns the new layer descriptor.
removeObjectLayer(name: string): boolean
Removes an object layer from the world. Fires "object-layer-removed" on success. Returns false if not found.
getObjectLayer(name: string): VoxelObjectLayerJSON | undefined
Returns the layer descriptor for name, or undefined if it does not exist.
getObjectLayers(): readonly VoxelObjectLayerJSON[]
Returns a snapshot array of all object layers in insertion order.
updateObjectLayer(name: string, patch: { visible?: boolean }): boolean
Applies a partial patch to a named object layer and fires "object-layer-updated". Returns false if not found.
addObject(layerName: string, object: VoxelObjectJSON): boolean
Appends an object to the named layer and fires "object-added". Returns false if the layer does not exist.
removeObject(layerName: string, objectId: string): boolean
Removes the object with the given id from the layer and fires "object-removed". Returns false if the layer or object is not found.
updateObject(layerName: string, objectId: string, patch: Partial<VoxelObjectJSON>): boolean
Merges patch into the matching object and fires "object-updated". Returns false if the layer or object is not found.
Hooks
See Hooks for more information