Skip to main content

The scene language

@scenography/core is the what: a small, serializable data model with zero three.js. An editor, a database, or a backend can depend on it without ever touching WebGL.

defineScene

defineScene(scene) validates a definition and returns it unchanged. It throws on structural errors so authoring mistakes surface early. The returned value is plain JSON-serializable data.

import { defineScene } from '@scenography/core';

const scene = defineScene({
walls: [...],
artworks: [...],
camera: { start: { position: [0, 170, 0] } },
});

Use validateScene(scene) if you want the list of issues without throwing (an editor's live feedback), and cloneScene(scene) for a JSON round-trip clone.

Walls

A wall is a top-down path: a polyline footprint the engine extrudes into a continuous run of walls. No need to declare each side separately.

interface WallStructure {
id: string;
path: [x: number, z: number][]; // top-down footprint
height?: number; // default 300 (≈3 m)
thickness?: number; // default 30
closed?: boolean; // connect the last vertex back to the first
edges?: { segment: number; name: string }[]; // name segments to hang art on
color?: string; // default white
}

Each edge names a segment of the path (segment runs from path[segment] to path[segment + 1], wrapping when closed). Artworks reference these names.

Corners are simple butt joints for now and the wall follows the path at its center. Chamfered/rounded corners and inside/outside offset are on the roadmap.

Artworks

A framed image, placed via a location. Today a piece either hangs on a named wall edge or floats freely.

interface Artwork {
id: string;
location: ArtworkLocation;
src: string;
size: [width: number, height: number];
title?: string;
technique?: string;
}

type ArtworkLocation =
| {
type: 'wall';
edge: string; // a NamedEdge name
position: number | `${number}%`; // along the edge: units, or a fraction
height?: number | `${number}%`; // world Y, a fraction of wall height, or (omitted) eye level
side?: 'right' | 'left'; // which face; 'right' = right-hand side along the edge
}
| {
type: 'floating';
point: [x: number, y: number, z: number];
yaw?: number; // radians
};

side defaults to 'right' — the right-hand side when walking the edge from its start vertex to its end. Set it explicitly when a wall's winding makes the visible face ambiguous (e.g. an S-shaped path).

Camera

Where the visitor starts, and how far they may roam.

interface Camera {
start: {
position: [x: number, y: number, z: number];
yaw?: number; // radians, default 0
};
bounds?: number; // square half-extent that clamps XZ movement
}

Richer camera paths and tours are a planned addition — and, true to the core principle, they will be data (waypoints, easings, durations), with the motion itself living in the engine.