102 lines
3.7 KiB
JavaScript
102 lines
3.7 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { after, before, test } from "node:test";
|
|
import { createServer } from "vite";
|
|
|
|
let server, createRecordedRerunCameraJournal, initialEye;
|
|
before(async () => {
|
|
server = await createServer({ appType: "custom", logLevel: "silent",
|
|
server: { middlewareMode: true } });
|
|
const module = await server.ssrLoadModule(
|
|
"/src/components/rerun/recordedRerunCameraJournal.ts");
|
|
createRecordedRerunCameraJournal = module.createRecordedRerunCameraJournal;
|
|
initialEye = module.RECORDED_RERUN_ORBITAL_EYE;
|
|
});
|
|
after(async () => { await server?.close(); });
|
|
|
|
class FakeEvent {
|
|
constructor(type, init = {}) { this.type = type; Object.assign(this, init); }
|
|
}
|
|
|
|
class FakeTarget {
|
|
listeners = new Map();
|
|
emitted = [];
|
|
addEventListener(type, listener) {
|
|
const listeners = this.listeners.get(type) ?? [];
|
|
listeners.push(listener); this.listeners.set(type, listeners);
|
|
}
|
|
removeEventListener(type, listener) {
|
|
this.listeners.set(type, (this.listeners.get(type) ?? []).filter(item => item !== listener));
|
|
}
|
|
dispatchEvent(event) {
|
|
this.emitted.push(event);
|
|
for (const listener of this.listeners.get(event.type) ?? []) listener(event);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
const radius = (eye) => Math.hypot(
|
|
eye.position[0] - eye.lookTarget[0],
|
|
eye.position[1] - eye.lookTarget[1],
|
|
eye.position[2] - eye.lookTarget[2],
|
|
);
|
|
|
|
test("recorded orbital eye tracks only 3D viewport navigation", () => {
|
|
const scope = new FakeTarget();
|
|
const timers = [];
|
|
Object.assign(scope, {
|
|
WheelEvent: FakeEvent,
|
|
requestAnimationFrame(callback) { callback(); return 1; },
|
|
setTimeout(callback) { timers.push(callback); return timers.length; },
|
|
});
|
|
const canvas = new FakeTarget();
|
|
Object.assign(canvas, {
|
|
clientHeight: 200,
|
|
isConnected: true,
|
|
getBoundingClientRect: () => ({
|
|
left: 100, right: 500, top: 50, bottom: 250, width: 400, height: 200,
|
|
}),
|
|
});
|
|
const journal = createRecordedRerunCameraJournal(canvas, scope);
|
|
journal.configure(initialEye, 0.46);
|
|
const pointer = (type, x, y, buttons) => new FakeEvent(type, {
|
|
clientX: x, clientY: y, button: 0, buttons, pointerId: 7, pointerType: "mouse",
|
|
altKey: false, ctrlKey: false, metaKey: false, shiftKey: false,
|
|
});
|
|
|
|
canvas.dispatchEvent(pointer("pointerdown", 200, 100, 1));
|
|
scope.dispatchEvent(pointer("pointermove", 240, 120, 1));
|
|
scope.dispatchEvent(pointer("pointerup", 240, 120, 0));
|
|
assert.deepEqual(journal.current(), initialEye);
|
|
|
|
canvas.dispatchEvent(pointer("pointerdown", 380, 100, 1));
|
|
scope.dispatchEvent(pointer("pointermove", 420, 120, 1));
|
|
scope.dispatchEvent(pointer("pointerup", 420, 120, 0));
|
|
const rotated = journal.current();
|
|
assert.notDeepEqual(rotated.position, initialEye.position);
|
|
assert.ok(Math.abs(radius(rotated) - radius(initialEye)) < 1e-9);
|
|
|
|
journal.setMaxOrbitalRadius(40);
|
|
scope.dispatchEvent(new FakeEvent("wheel", {
|
|
clientX: 380, clientY: 130, deltaX: 0, deltaY: 2_000, deltaMode: 0,
|
|
altKey: false, ctrlKey: false, metaKey: false, shiftKey: false,
|
|
}));
|
|
assert.ok(Math.abs(radius(journal.current()) - 40) < 1e-9);
|
|
|
|
journal.configure(rotated, 0.46);
|
|
|
|
scope.dispatchEvent(new FakeEvent("wheel", {
|
|
clientX: 0, clientY: 0, deltaX: 0, deltaY: -20, deltaMode: 0,
|
|
altKey: false, ctrlKey: false, metaKey: false, shiftKey: false,
|
|
}));
|
|
const zoomed = journal.current();
|
|
assert.ok(Math.abs(radius(zoomed) - radius(rotated) * Math.exp(-20 / 200)) < 1e-9);
|
|
|
|
const snapshot = journal.current();
|
|
journal.setSpatialViewportStart(0.8);
|
|
scope.dispatchEvent(new FakeEvent("wheel", {
|
|
clientX: 380, clientY: 130, deltaX: 0, deltaY: -20, deltaMode: 0,
|
|
}));
|
|
assert.deepEqual(journal.current(), snapshot);
|
|
journal.dispose();
|
|
});
|