Files
CodeNavigator/webtest/camera.test.mjs
T
Andy Dixon 9c7643c616 License under GPLv3
Add SPDX headers to the source, a License section to the README, the
OFL notice for the bundled fonts, and ship LICENSE in the Docker image.
2026-09-20 10:05:06 +01:00

37 lines
2.2 KiB
JavaScript

// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2026 Andy Dixon
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { perspective, lookAt, multiply, transform, invert, orbitPose, ORBIT_FOV } from '../web/camera.js';
// The landscape projection before the real camera, kept as the reference.
function legacyProject(camera, [x, y, z], width, height) {
let px = (x - camera.x) / 420, py = (y - camera.y) / 420, pz = z / 170;
const cy = Math.cos(camera.yaw), sy = Math.sin(camera.yaw), cp = Math.cos(camera.pitch), sp = Math.sin(camera.pitch);
[px, py] = [cy * px - sy * py, sy * px + cy * py]; [py, pz] = [cp * py + sp * pz, -sp * py + cp * pz];
const depth = camera.distance - py, aspect = width / height;
return { x: width / 2 + (px / (depth * aspect) * 2.7) * width / 2, y: height / 2 - ((pz - .35) / depth * 2.7) * height / 2, depth };
}
test('orbit camera reproduces the legacy landscape projection', () => {
const width = 1280, height = 800;
for (const camera of [{ x: 500, y: 340, yaw: -.12, pitch: .76, distance: 3.5 }, { x: 120, y: 600, yaw: 2.4, pitch: .2, distance: 1.1 }, { x: 800, y: 50, yaw: -1.3, pitch: 1.4, distance: 7 }]) {
const pose = orbitPose(camera);
const viewProj = multiply(perspective(ORBIT_FOV, width / height, 1, 100000), lookAt(pose.eye, pose.target, pose.up));
for (const point of [[500, 340, 0], [0, 0, 40], [1000, 680, 12], [430, 200, 80]]) {
const legacy = legacyProject(camera, point, width, height);
if (legacy.depth < .6) continue;
const [cx, cy, , cw] = transform(viewProj, point[0], point[1], point[2] * 420 / 170);
const x = (cx / cw + 1) / 2 * width, y = (1 - cy / cw) / 2 * height;
assert.ok(Math.abs(x - legacy.x) < .05 && Math.abs(y - legacy.y) < .05, `${JSON.stringify(camera)} ${point}: got ${x},${y} want ${legacy.x},${legacy.y}`);
assert.ok(Math.abs(cw / 420 - legacy.depth) < 1e-3, `depth ${cw / 420} vs ${legacy.depth}`);
}
}
});
test('invert undoes a view-projection', () => {
const m = multiply(perspective(1, 1.5, .1, 1000), lookAt([3, 4, 5], [0, 0, 0]));
const inv = invert(m), id = multiply(m, inv);
for (let i = 0; i < 16; i++) assert.ok(Math.abs(id[i] - (i % 5 === 0 ? 1 : 0)) < 1e-4, `${i}: ${id[i]}`);
});