Back to Napplet Machines V8
SOURCE / PINNED RELEASE

Made of little things.

Napplet Machines V8

Release
a48ffab2ae1e…
Author-recorded commit
2a817e4d389e…
License
LICENSE
Author’s source reference
nostr://npub182jczunncwe0jn6frpqwq3e0qjws7yqqnc3auccqv9nte2dnd63scjm4rf/wss%3A%2F%2Fgit.napplet.soy%2F/n-b5572362d4a

Archive hash verified: ecd5161370161eeb…. The source-to-build association is the author’s claim; it has not been independently rebuilt.

src/camera.ts
import type { Body } from './physics.js';
export type Vector3 = [number, number, number];
const dot = (a: Vector3, b: Vector3) => a.reduce((sum, v, i) => sum + v * b[i], 0);
export function cameraAxes(azimuth: number, elevation: number) {
  const right: Vector3 = [Math.cos(azimuth), Math.sin(azimuth), 0];
  const up: Vector3 = [-Math.sin(azimuth) * Math.sin(elevation), Math.cos(azimuth) * Math.sin(elevation), Math.cos(elevation)];
  return { right, up };
}
// Fit screen-space extents, including elevated cars and the road ahead of each
// driver. Opposing headings get independent front padding rather than cancelling.
export function sharedCamera(bodies: Body[], ratio: number, right: Vector3, up: Vector3) {
  const points: Vector3[] = [];
  for (const b of bodies) {
    const speed = Math.hypot(b.vx, b.vy), look = Math.min(20, 7 + speed * 0.65);
    const dx = speed > 2 ? b.vx / speed : Math.cos(b.angle);
    const dy = speed > 2 ? b.vy / speed : Math.sin(b.angle);
    points.push([b.x, b.y, b.z], [b.x, b.y, b.z + 3], [b.x + dx * look, b.y + dy * look, b.z]);
  }
  const xs = points.map(p => dot(p, right)), ys = points.map(p => dot(p, up));
  const x = (Math.min(...xs) + Math.max(...xs)) / 2;
  const y = (Math.min(...ys) + Math.max(...ys)) / 2;
  const anchor: Vector3 = [0, 0, 0];
  for (const b of bodies) {
    anchor[0] += b.x / bodies.length; anchor[1] += b.y / bodies.length; anchor[2] += b.z / bodies.length;
  }
  const sx = x - dot(anchor, right), sy = y - dot(anchor, up);
  const target = anchor.map((v, i) => v + right[i] * sx + up[i] * sy) as Vector3;
  const requiredSpan = (center: Vector3) => Math.max(
    48 / Math.min(Math.max(ratio, 1), 1.8),
    ...points.flatMap(p => [
      (Math.abs(dot(p, right) - dot(center, right)) + 6) * 2 / (ratio * 0.82),
      (Math.abs(dot(p, up) - dot(center, up)) + 6) * 2 / 0.55,
    ]),
  );
  return { target, requiredSpan };
}