deadhead-space-freight-tycoon/apps/web/test/map-projection.test.ts

73 lines
2.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 };
it("shows all five locations on screen at default zoom, across 12 years", () => {
for (let month = 0; month < 12 * 12; month++) {
for (const location of content.locations) {
const p = toScreen(locationMapPoint(location), 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")!);
const moon = locationMapPoint(content.location("moon")!);
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);
});
});