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.