Skip to content

BlockRegistry ​

Maps block ids to resolved definitions. A document's registry is VoxelDocument.blocks.

ts
const registry = new BlockRegistry();
registry.register({ id: 1, name: "Stone", shapeId: "cube" });
registry.get(1)?.name; // "Stone"

API ​

ts
class BlockRegistry implements Iterable<ResolvedBlockDefinition> {
  readonly size: number;
  readonly nextId: number;
  readonly version: number;

  constructor(definitions?: BlockDefinition[]);
  register(definition: BlockDefinition): this;
  registerMany(
    definitions: Iterable<BlockDefinition>,
    options?: { skipExisting?: boolean; }
  ): this;
  unregister(id: number): boolean;
  clear(): void;
  moveBlockToIndex(id: number, toIndex: number): boolean;
  findIndex(id: number): number;
  get(id: number): ResolvedBlockDefinition | undefined;
  copyProperties(id: number): BlockProperties | undefined;
  has(id: number): boolean;
  getAll(): IterableIterator<ResolvedBlockDefinition>;
}

Edit a document's blocks with defineBlock(), removeBlock() and moveBlock() on VoxelDocument, which emit block commands. The registry's own methods emit nothing.

Writing ​

register() resolves the definition and replaces any block with the same id, keeping its position. It throws for AIR_BLOCK_ID and for invalid surface settings.

registerMany() registers each definition. With skipExisting: true, a block already registered under that id is kept.

unregister() returns whether the id was registered. clear() removes every block. Ids are never reused: nextId stays one above the highest id ever registered.

version increases on every registration, including one that only changes properties.

Reading ​

get() returns the stored definition, not a copy. Treat it as read-only.

copyProperties() returns a copy of the block's custom properties, {} for a block without any, and undefined for an unknown id.

Ordering ​

Blocks keep registration order, and iteration, getAll() and BlocksetDocument.toJSON() follow it.

moveBlockToIndex() moves a block to toIndex, clamped to the list. It returns false for an unknown id or a move that changes nothing. findIndex() returns -1 for an unknown id. Order has no effect on rendering.

Creating blocks from a blockset ​

ts
type TileGridSource = {
  cols: number;
  rows: number;
  id?: string;
  slot?: number;
};

interface BlocksFromTileGridOptions {
  limit?: number;
  map?: (blockId: number, col: number, row: number) => BlockOverrides;
}

type BlockOverrides = Partial<Pick<
  ResolvedBlockDefinition,
  | "name"
  | "shapeId"
  | "collidable"
  | "alphaMode"
  | "side"
  | "alphaCutoff"
  | "materialGroup"
  | "blendGroup"
  | "cullCoveredFaces"
  | "properties"
>>;

function blocksFromTileGrid(
  source: TileGridSource,
  options?: BlocksFromTileGridOptions
): IterableIterator<ResolvedBlockDefinition>;

Yields one cube block per tile, in row-major order, with local ids starting at 1. limit is the highest local id generated, 255 by default. With an id, the tile references name that blockset; with a slot, block ids are composed into it. map receives the local id and returns overrides. Generated blocks are not collidable unless map says so.

ts
document.blocks.registerMany(
  blocksFromTileGrid({ id: "terrain", cols: 8, rows: 4 }, {
    limit: 32,
    map: () => ({ collidable: true })
  })
);