Uzay

Camera

3D viewpoint and projection

What is Camera3D?

Camera3D defines the viewpoint from which a scene is rendered. Unlike most 3D libraries where cameras are separate from scene items, in Uzay cameras are items that live in the scene, which means they can be reactive too.

Every View requires a camera. Without one, you can't render the scene.

Basic Usage

const camera = scene.create("camera3d", {
  position: vec3(10, 10, 10),
  lookAt: vec3(0, 0, 0),
});

// Use this camera for the view
const view = new View3D(scene, camera.id, containerElement);

Camera Position and Target

The camera's view is defined by two vectors:

  • position: Where the camera is located in 3D space
  • lookAt: The point the camera is aimed at
// Looking at the origin from the front-right
scene.create("camera3d", {
  position: vec3(10, 5, 10),
  lookAt: vec3(0, 0, 0),
});

// Top-down view
scene.create("camera3d", {
  position: vec3(0, 20, 0),
  lookAt: vec3(0, 0, 0),
});

// Front view
scene.create("camera3d", {
  position: vec3(0, 0, 15),
  lookAt: vec3(0, 0, 0),
});

Zoom

The zoom property scales the view:

// Default zoom
scene.create("camera3d", {
  position: vec3(10, 10, 10),
  lookAt: vec3(0, 0, 0),
  zoom: 1,
});

// Zoomed in (objects appear larger)
scene.create("camera3d", {
  position: vec3(10, 10, 10),
  lookAt: vec3(0, 0, 0),
  zoom: 2,
});

// Zoomed out (objects appear smaller)
scene.create("camera3d", {
  position: vec3(10, 10, 10),
  lookAt: vec3(0, 0, 0),
  zoom: 0.5,
});

Field of View

The fov property controls the vertical field of view in degrees:

// Wide angle (more visible, more distortion)
scene.create("camera3d", {
  position: vec3(10, 10, 10),
  lookAt: vec3(0, 0, 0),
  fov: 90,
});

// Narrow angle (telephoto look)
scene.create("camera3d", {
  position: vec3(10, 10, 10),
  lookAt: vec3(0, 0, 0),
  fov: 30,
});

Projection Mode (Incomplete)

Camera3D includes a projection field for future support of multiple projection modes.

  • "perspective" is fully supported.
  • "orthogonal" is not implemented yet in View3D.

If you set projection: "orthogonal", Uzay currently logs a warning and falls back to perspective rendering.

Clipping Planes

Objects closer than near or farther than far are not rendered:

scene.create("camera3d", {
  position: vec3(10, 10, 10),
  lookAt: vec3(0, 0, 0),
  near: 0.1,   // Don't render objects closer than 0.1 units
  far: 1000,   // Don't render objects farther than 1000 units
});

Adjust these if you see objects disappearing unexpectedly.

Multiple Cameras

You can create multiple cameras and switch between them:

const frontCamera = scene.create("camera3d", {
  position: vec3(0, 0, 20),
  lookAt: vec3(0, 0, 0),
});

const topCamera = scene.create("camera3d", {
  position: vec3(0, 20, 0),
  lookAt: vec3(0, 0, 0),
});

const sideCamera = scene.create("camera3d", {
  position: vec3(20, 0, 0),
  lookAt: vec3(0, 0, 0),
});

// Start with front camera
const view = new View3D(scene, frontCamera.id, container);

// Switch to top view
view.changeActiveCam(topCamera.id);

Examples

Since cameras are scene items, their properties can be atoms. Camera sync is bidirectional: atom changes update the Three.js camera, and orbit control interactions update the atoms.

const cameraY = scene.atom(10);

const camera = scene.create("camera3d", {
  position: scene.atom((get) => vec3(10, get(cameraY), 10)),
  lookAt: vec3(0, 0, 0),
});

// Camera smoothly moves up
function animate() {
  cameraY.set(cameraY.get() + 0.01);
  requestAnimationFrame(animate);
}
animate();

Camera Following an Item

const targetPos = scene.atom(vec3(0, 0, 0));

// A point that moves
const point = scene.create("point3d", {
  coords: targetPos,
  color: "gold",
});

// Camera that follows the point
const camera = scene.create("camera3d", {
  position: scene.atom((get) => {
    const target = get(targetPos);
    return Vec3.add(target, vec3(5, 5, 5)); // Offset from target
  }),
  lookAt: targetPos,
});

Bidirectional Sync with UI

When you pass writable atoms for position or lookAt, orbit control interactions (rotating, panning) write back to those atoms. This lets you build UI that stays in sync with user interactions:

const camX = scene.atom(10);
const camY = scene.atom(10);
const camZ = scene.atom(10);

const cameraPosAtom = scene.atom(
  (get) => vec3(get(camX), get(camY), get(camZ)),
  (_get, set, next: Vec3) => {
    set(camX, next.x);
    set(camY, next.y);
    set(camZ, next.z);
  },
);

const camera = scene.create("camera3d", {
  position: cameraPosAtom,
  lookAt: vec3(0, 0, 0),
});

// Subscribe to see orbit changes reflected in individual atoms
camX.sub(() => console.log("Camera X:", camX.get()));

If position or lookAt is a read-only derived atom, writeback is skipped gracefully.

Orbit Controls

Views come with built-in orbit controls that let users interact with the camera:

  • Left-click + drag: Rotate around the lookAt point
  • Right-click + drag: Pan the camera
  • Scroll: Zoom in/out

Each of these can be independently enabled or disabled via camera properties:

// Rotation only, no panning or zooming
scene.create("camera3d", {
  position: vec3(10, 10, 10),
  lookAt: vec3(0, 0, 0),
  enableOrbit: true,
  enablePan: false,
  enableZoom: false,
});

Since these are atoms, you can toggle them reactively:

const canOrbit = scene.atom(true);

const camera = scene.create("camera3d", {
  position: vec3(10, 10, 10),
  lookAt: vec3(0, 0, 0),
  enableOrbit: canOrbit,
});

// Disable orbiting later
canOrbit.set(false);

API Reference

Options

PropertyTypeDefaultDescription
positionVec3{ x: 10, y: 10, z: 10 }Camera location
lookAtVec3{ x: 0, y: 0, z: 0 }Point the camera aims at
projection"perspective" | "orthogonal""perspective"Projection mode ("orthogonal" currently warns and falls back to perspective)
enableOrbitbooleantrueEnable rotation (left-click drag)
enablePanbooleantrueEnable panning (right-click drag)
enableZoombooleantrueEnable zooming (scroll)
fovnumber60Vertical field of view (degrees)
zoomnumber1Zoom level
nearnumber0.1Near clipping plane
farnumber1000Far clipping plane

Returned Item

FieldTypeDescription
idstringUnique identifier (pass to View3D)
positionBoundAtom<Vec3>Position atom
lookAtBoundAtom<Vec3>Target atom
projectionBoundAtom<"perspective" | "orthogonal">Projection atom
enableOrbitBoundAtom<boolean>Orbit toggle atom
enablePanBoundAtom<boolean>Pan toggle atom
enableZoomBoundAtom<boolean>Zoom toggle atom
fovBoundAtom<number>FOV atom
zoomBoundAtom<number>Zoom atom
nearBoundAtom<number>Near plane atom
farBoundAtom<number>Far plane atom

On this page