Lesson 3

Automate chapters

Turn camera movement into destination buttons: enter a solar orbit, then insert around the Pleiades and Orion Nebula using RA, Dec, and distance values.

Learning goal

Lesson 2 called individual transitions directly. This lesson gives those ideas chapter semantics: named functions, catalogue-style sky targets, orbit cameras, and direct navigation actions.

The buttons only choose chapters. Each chapter owns its target, orbit camera, duration, and orbital plane.

Live code

Chapters from the Sun to Orion

Use the buttons to enter a 1 pc solar orbit, fly to the Pleiades, or insert around the Orion Nebula from RA, Dec, and distance targets.

Ready.
Console
 

Things to try

  • Change the Pleiades or Orion distancePc values to move the target closer or farther away.
  • Change their radiusPc values to tighten or widen the inserts.
  • Change ORION_TILTED_ORBIT_NORMAL to see the arrival plane change.
  • Add another chapter key with its own target and orbit settings.
  • Adjust each chapter's durationSecs.

What changed in the code

The chapter table is ordinary app code. The cluster targets are written as RA, Dec, and parsec distance; SkyKit resolves them into 3D navigation targets when goTo() invokes the navigation actions. lockAt keeps the camera on the destination while orbitalInsert builds the smooth route into the final orbit. The Sun stays as a parsec vector because a zero-distance target has no meaningful RA or Dec.

Chapter table
import { createRaDecLookAt } from '@found-in-space/skykit';

const SUN_PC = { x: 0, y: 0, z: 0 };
const ICRS_NORTH = { x: 0, y: 0, z: 1 };
const PLEIADES = skyTarget('03h 40m', '+22d 00m', 125);
const ORION_NEBULA = skyTarget('05h 15m', '-05d 23m', 355);
const ORION_TILTED_ORBIT_NORMAL = { x: -0.35, y: 0.45, z: 0.82 };

const chapters = {
  solarOrbit: {
    label: '1 pc Sun orbit',
    center: SUN_PC,
    radiusPc: 1,
    angularSpeedRadPerSec: 0.22,
    normal: ICRS_NORTH,
    durationSecs: 4,
  },
  pleiadesOrbit: {
    label: 'Wide Pleiades orbit',
    center: PLEIADES,
    radiusPc: 50,
    angularSpeedRadPerSec: 0.25,
    normal: ICRS_NORTH,
    durationSecs: 8,
  },
  orionNebulaOrbit: {
    label: 'Tilted Orion Nebula orbit',
    center: ORION_NEBULA,
    radiusPc: 150,
    angularSpeedRadPerSec: 0.25,
    normal: ORION_TILTED_ORBIT_NORMAL,
    durationSecs: 10,
  },
};

function skyTarget(ra, dec, distancePc) {
  const target = createRaDecLookAt(ra, dec, { distancePc });
  if (!target) throw new Error(`Could not parse sky target ${ra}, ${dec}`);
  return target;
}
Chapter automation
async function goTo(id) {
  const chapter = chapters[id];
  if (!chapter) return;

  await browser.viewer.actions.invoke(SKYKIT_ACTIONS.navigation.cancel);
  await browser.viewer.actions.invoke(SKYKIT_ACTIONS.navigation.lockAt, {
    ...chapter.center,
    up: chapter.normal,
    recenterSpeed: 0.08,
  });
  await browser.viewer.actions.invoke(SKYKIT_ACTIONS.navigation.orbitalInsert, {
    center: chapter.center,
    radius: chapter.radiusPc,
    angularSpeedRadPerSec: chapter.angularSpeedRadPerSec,
    normal: chapter.normal,
    durationSecs: chapter.durationSecs,
    sampleStepSecs: 1 / 30,
  });
}
Button wiring
document.querySelector('#sun-orbit').addEventListener('click', () => {
  goTo('solarOrbit');
});

document.querySelector('#pleiades-orbit').addEventListener('click', () => {
  goTo('pleiadesOrbit');
});

document.querySelector('#orion-orbit').addEventListener('click', () => {
  goTo('orionNebulaOrbit');
});

Next step

Move the same pattern into a local app.

The quickstart embed is useful for learning, but the same browser handle exists when you install SkyKit in Vite and import it from npm.