import { content } from "@deadhead/content"; import { describe, expect, it } from "vitest"; import { DEFAULT_CAMERA, pan, toScreen, zoomAt } from "../src/map/camera"; import { locationMapPoint, radialScale, toMapPoint } from "../src/map/projection"; describe("radial scale", () => { it("is monotonic: farther in reality is farther on screen", () => { let previous = radialScale(0); for (let km = 0.5; km <= 1200; km += 0.5) { const next = radialScale(km); expect(next).toBeGreaterThan(previous); previous = next; } }); it("is compressed: Jupiter is much less than 5× Earth's distance on screen", () => { expect(radialScale(780) / radialScale(150)).toBeLessThan(2.5); expect(radialScale(1000)).toBe(1); }); it("keeps a position's direction from the Sun", () => { const p = toMapPoint({ x: 300, y: 400 }); expect(Math.atan2(-p.y, p.x)).toBeCloseTo(Math.atan2(400, 300), 10); expect(Math.hypot(p.x, p.y)).toBeCloseTo(radialScale(500), 10); }); }); describe("map fits a phone", () => { // A 375 px wide phone, minus header and tab bar. const viewport = { width: 375, height: 690 }; const start = Date.parse("2100-01-01T00:00:00Z"); it("shows all five locations on screen at default zoom, across 12 years", () => { for (let month = 0; month < 12 * 12; month++) { const gameTime = start + month * 30 * 86_400_000; for (const location of content.locations) { const p = toScreen(locationMapPoint(location, gameTime), viewport, DEFAULT_CAMERA); expect(p.x).toBeGreaterThanOrEqual(0); expect(p.x).toBeLessThanOrEqual(viewport.width); expect(p.y).toBeGreaterThanOrEqual(0); expect(p.y).toBeLessThanOrEqual(viewport.height); } } }); it("draws the Moon apart from Earth", () => { const earth = locationMapPoint(content.location("earth")!, start); const moon = locationMapPoint(content.location("moon")!, start); const a = toScreen(earth, viewport, DEFAULT_CAMERA); const b = toScreen(moon, viewport, DEFAULT_CAMERA); expect(Math.hypot(a.x - b.x, a.y - b.y)).toBeGreaterThan(5); }); }); describe("camera", () => { const viewport = { width: 400, height: 600 }; it("keeps the map point under the fingers fixed while zooming", () => { const anchor = { x: 300, y: 150 }; const camera = pan(DEFAULT_CAMERA, 20, -40); const point = { x: 0.37, y: -0.21 }; const before = toScreen(point, viewport, camera); const zoomed = zoomAt(camera, 3, anchor, viewport); const after = toScreen(point, viewport, zoomed); // Distance from the anchor scales by the zoom factor; the anchor itself stays put. expect(after.x - anchor.x).toBeCloseTo((before.x - anchor.x) * 3, 6); expect(after.y - anchor.y).toBeCloseTo((before.y - anchor.y) * 3, 6); }); it("clamps zoom", () => { expect(zoomAt(DEFAULT_CAMERA, 1000, { x: 0, y: 0 }, viewport).zoom).toBe(40); expect(zoomAt(DEFAULT_CAMERA, 0.0001, { x: 0, y: 0 }, viewport).zoom).toBe(0.5); }); });