3D Scenes and Cameras
3D shares the same timeline / Player / serialization machinery as 2D (design.md §10). Scene3D mirrors Scene2D: register immutable objects, accumulate seekable storyboard; difference is transforms include quaternion rotation, and the camera is part of the timeline.
Creating a 3D scene
import { axes3D, createProgram, surface3D, xyz } from "@intermact/core";
const program = createProgram(async (ctx) => {
const scene = ctx.createScene3D({ background: "#05070f" });
const camera = ctx.createCamera3D(scene, { position: xyz(6, 4, 6), target: [0, 0, 0], fov: 45 });
ctx.mount(scene, camera);
scene.register(axes3D({ size: 2.5 }));
const surf = scene.register(
surface3D({ fn: (u, v) => [(u - 0.5) * 4, Math.sin(u * 6) * Math.cos(v * 6), (v - 0.5) * 4] }),
);
await scene.play(surf.create({ duration: 1.5 }));
});3D object factories
| Factory | Geometry channel |
|---|---|
polyline3D / curve3D | Polyline / parametric curve (line) |
meshObject / surface3D | Triangle mesh / parametric surface (mesh) |
pointCloud3D | Point cloud (points, scalar coloring optional) |
axes3D | 3D coordinate axes |
isosurface(field, opts) | Scalar field isosurface (marching-tetrahedra, watertight unambiguous) |
Each factory tags Geometry3DTrait capability, so Create reveal, grouping, etc. dispatch by trait, not concrete type (design.md §4.2). Create reveals line geometry by arc length, mesh/points by build order.
Camera as timeline object
Registering a camera returns RegisteredCamera3D; moveTo / lookAt / orbit / dollyTo append quaternion look-at tweens, seekable:
scene.marker("start");
await scene.play(camera.orbit(Math.PI, { duration: 3 }));
await scene.play(camera.dollyTo(3, { duration: 1.5 }));
await scene.play(camera.lookAt([1.5, 1, 0], { duration: 1.2 }));Camera can participate in parent hierarchy as transform node (camera.follow / setParent), moving with target object.
Grouping and hierarchy
Scene3D.group3D(children, transform) parents multiple registered objects under a transform-only empty node for collective translate/rotate — Player composes world transforms along hierarchy at snapshot (design.md §9.3).
const a = scene.register(meshA, { position: xyz(1.2, 0, 0) });
const b = scene.register(meshB, { position: xyz(-1.2, 0, 0) });
const group = scene.group3D([a, b]);
await scene.play(group.orientTo({ x: 0.4, y: Math.PI * 1.2, z: 0 }, { duration: 4 }));Coordinates and axes
Scene3D.coordinate (CoordinateTransform3D) maps data ↔ world on scene domain; scene.getAxes(props) returns RegisteredAxes3D with handle.c2p/p2c, revealable via standard animation APIs.
Nested sub-scenes
render(scene, camera) wraps an independent (animatable) sub-scene as a registrable rendered-scene object (design.md §10.2). SceneView/IntermactCanvas composites it to an offscreen render target, then frames it like any object.
Related examples
3d/surface-plot—surface3Dfunction surface +axes3D3d/training-trajectory—polyline3D/pointCloud3Dtrajectory3d/isosurface—isosurfacescalar field (marching-cubes)3d/camera-moves— camera as timeline object3d/grouping—group3Daggregation +polyline3Dring3d/nested-scene-panel—render(scene, camera)nested picture-in-picture
Full list in example index.