TAU/ DOCS

Capture Camera Views

Render fitted views, exact viewer cameras, and editable preset batches to lossless images.

@taucad/image fits a model or reproduces a viewer camera using serializable camera options.

Choose framing

Tau viewer, thumbnail, and capture callers use framing: 'bounds' so every surface composes around the same conservative scene AABB; reserve framing: 'fit' for renderer-owned composition from exact visible geometry. direction points from the subject centre toward the camera — the renderer accepts Cartesian direction only — and up controls the top of the image:

const fittedCamera = {
  framing: 'bounds',
  direction: [1, -1, 0.7],
  up: [0, 0, 1],
  margin: 0.1,
  projection: { kind: 'perspective', verticalFieldOfView: 45 },
} as const;

Use framing: 'fixed' when the image must preserve position, target, roll, and magnification. Position, target, orthographic span, and clipping use the GLB world unit:

const fixedCamera = {
  framing: 'fixed',
  position: [0.09, -0.065, 0.045],
  target: [0.005, 0, 0.008],
  up: [0.16, 0.11, 0.98],
  projection: {
    kind: 'perspective',
    verticalFieldOfView: 48,
    zoom: 1.25,
  },
  clipping: { near: 0.0001, far: 2 },
} as const;

Perspective field of view is vertical, in degrees. Zoom is a positive magnification: the effective field of view is 2 × atan(tan(verticalFieldOfView / 2) / zoom), and an orthographic projection uses verticalSpan / zoom as its visible span. A fixed camera requires up because position and target alone cannot preserve roll.

Omit clipping to derive planes from model bounds; set it only when the caller owns the scene depth range. Geometry at or behind the near plane is clipped, and line segments crossing it are trimmed before screen-space expansion.

Capture a live Three.js camera

Keep Three.js at the adapter boundary. readThreeCameraState copies world placement, roll, projection, zoom, clipping, and aspect from the active render frame into renderer-neutral @taucad/camera data; toNanorasterCamera preserves those values in the fixed-camera contract without applying another length scale:

import type { RuntimeDocument } from '@taucad/runtime/client';
import { readThreeCameraState } from '@taucad/three/camera';
import { toNanorasterCamera } from '@taucad/image/camera';

const tauWorld = { up: '+z', forward: '-y', unit: 'meter' } as const;

export const captureCamera = async (document: RuntimeDocument, frame: Parameters<typeof readThreeCameraState>[0]) => {
  const cameraState = readThreeCameraState(frame);

  const width = 2400;
  const height = Math.round(width / cameraState.aspect);
  return document.export('webp', {
    content: { includeEdges: true },
    options: {
      width,
      height,
      lineWidth: 3,
      quality: 1,
      background: '#242424',
      label: 'main.ts',
      axes: true,
      scaleBar: true,
      world: tauWorld,
      camera: toNanorasterCamera({ cameraState }),
    },
  });
};

Pass a request-scoped document to these capture functions; its caller owns close(). Three rules keep the camera faithful:

  • Use the same aspect for output and source camera; a different output aspect deliberately changes horizontal composition.
  • lineWidth: 3 is a flat 3 screen pixels at every output size — do not normalize it by image dimensions.
  • Copy camera state before awaiting geometry or network work, so the image represents the view at the moment of the capture request even if the viewer keeps moving.

WebP quality: 1 is lossless.

Render editable presets

Each batch view owns an editable camera:

import type { RuntimeDocument } from '@taucad/runtime/client';

const views = [
  {
    id: 'three-quarter',
    label: 'Three-quarter',
    camera: {
      framing: 'bounds',
      direction: [1, -1, 0.7],
      up: [0, 0, 1],
      margin: 0.1,
      projection: { kind: 'perspective', verticalFieldOfView: 45 },
    },
  },
  {
    id: 'plan',
    label: 'Plan',
    camera: {
      framing: 'fixed',
      position: [0, 0, 0.2],
      target: [0, 0, 0],
      up: [0, 1, 0],
      projection: { kind: 'orthographic', verticalSpan: 0.16, zoom: 1 },
    },
  },
] as const;

export const capturePresets = (document: RuntimeDocument) =>
  document.export('webp', {
    content: { includeEdges: true },
    options: {
      mode: 'batch',
      width: 1600,
      height: 1600,
      lineWidth: 3,
      quality: 1,
      background: '#242424',
      axes: true,
      scaleBar: true,
      views,
    },
  });

Bounds and fit framing share one magnification control, margin; fixed perspective uses field of view and zoom; fixed orthographic uses span and zoom. Separate cases avoid competing fit and zoom rules.

Send the request over RPC

Camera options are JSON data. An application RPC can serialize them; its receiver opens and closes the request-scoped document:

import { toNanorasterCamera } from '@taucad/image/camera';

export const serializeCapture = (
  views: readonly { id: string; camera: ReturnType<typeof toNanorasterCamera> }[],
): string =>
  JSON.stringify({
    id: crypto.randomUUID(),
    method: 'capture',
    params: {
      source: { path: 'main.ts' },
      to: 'webp',
      options: { mode: 'batch', width: 1600, height: 1600, lineWidth: 3, views },
    },
  });

Validate at the receiver before rendering. The image plugin schemas reject non-finite vectors, coincident position and target, collinear direction and up, invalid projection values, inverted clip planes, duplicate view IDs, and unknown fields.

On this page