import { canvas2d, h } from "./lib/html"; import { Cancel } from "./lib/source"; import { tick } from "./lib/tick"; type Mass = { x: number; y: number; vx: number; vy: number; radius: number; mass: number; color: string; }; export function OrbitDemo() { const [canvas, cx] = canvas2d({ width: 512, height: 512, }); cx.scale(1 / 2, 1 / 2); cx.translate(512, 512); const G = 8000; const PHYSICS_TICKS = 30; const masses: Mass[] = []; const tickSource = tick(PHYSICS_TICKS); let cancel: Cancel; const begin = () => { // reset / init cancel?.(); masses.length = 0; masses.push( { x: 0, y: 0, vx: -1, vy: -2, radius: 10, mass: 1000, color: "yellow" }, { x: -150, y: 150, vx: 130, vy: 130, radius: 9, mass: 10, color: "red" }, { x: -450, y: 0, vx: 0, vy: 120, radius: 4, mass: 5, color: "#0f8" } ); // subscribe to source, stash cancellation func cancel = tickSource((tick) => { switch (tick[0]) { case "physics": // apply velocities masses.forEach((mass) => { mass.x += mass.vx * (1 / PHYSICS_TICKS); mass.y += mass.vy * (1 / PHYSICS_TICKS); }); // apply accelerations masses.forEach((mass) => { masses.forEach((other) => { if (mass != other) { const dx = other.x - mass.x; const dy = other.y - mass.y; const rSquared = dx ** 2 + dy ** 2; const f = (G * other.mass) / rSquared; const d = Math.sqrt(rSquared); const fx = (f * dx) / d; const fy = (f * dy) / d; mass.vx += fx / PHYSICS_TICKS; mass.vy += fy / PHYSICS_TICKS; } }); }); break; case "render": const [, dt] = tick; cx.fillStyle = "black"; cx.fillRect(-512, -512, 1024, 1024); masses.forEach(({ x, y, vx, vy, radius, color }) => { cx.fillStyle = color; cx.beginPath(); cx.arc(x + vx * dt, y + vy * dt, radius, 0, Math.PI * 2); cx.fill(); }); break; } }); }; const end = () => cancel?.(); const start = h("button", { onclick: begin }, "Begin / Reset"); const stop = h("button", { onclick: end }, "Cease"); return [h("h1", {}, "Orbit Simulation"), h("p", {}, start, stop), canvas]; } Object.assign(globalThis, { OrbitDemo });