Skip to content

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

ts
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

FactoryGeometry channel
polyline3D / curve3DPolyline / parametric curve (line)
meshObject / surface3DTriangle mesh / parametric surface (mesh)
pointCloud3DPoint cloud (points, scalar coloring optional)
axes3D3D 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:

ts
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).

ts
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.

  • 3d/surface-plotsurface3D function surface + axes3D
  • 3d/training-trajectorypolyline3D/pointCloud3D trajectory
  • 3d/isosurfaceisosurface scalar field (marching-cubes)
  • 3d/camera-moves — camera as timeline object
  • 3d/groupinggroup3D aggregation + polyline3D ring
  • 3d/nested-scene-panelrender(scene, camera) nested picture-in-picture

Full list in example index.

Intermact v1.0 — docs cover Phase-1 / Phase-2 / Phase-3 (all stages)