84 lines
2.9 KiB
TypeScript
84 lines
2.9 KiB
TypeScript
/** Render-layer contract tests that do not require a WebGL context. */
|
|
|
|
import assert from "node:assert/strict";
|
|
import { describe, it } from "node:test";
|
|
import * as THREE from "three";
|
|
import type { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
|
|
import CALIFORNIA_TRANSPORT from "../transport/california.ts";
|
|
import { createRoadTrafficLayer } from "../engine/roadTraffic.ts";
|
|
import type { World } from "../engine/world.ts";
|
|
|
|
function fixture() {
|
|
const world = {
|
|
project(lat: number, lng: number): [number, number] {
|
|
return [(lng + 121) * 20, -(lat - 36) * 20];
|
|
},
|
|
groundAt(): number {
|
|
return 0;
|
|
},
|
|
} as unknown as World;
|
|
const camera = new THREE.PerspectiveCamera(42, 16 / 9, 0.1, 2_000);
|
|
const controls = {
|
|
target: new THREE.Vector3(),
|
|
enabled: true,
|
|
} as unknown as OrbitControls;
|
|
const layer = createRoadTrafficLayer(world, camera, controls, {
|
|
pack: CALIFORNIA_TRANSPORT,
|
|
routeId: "la-sf-us-101",
|
|
count: 8,
|
|
seed: 115,
|
|
});
|
|
return { layer, camera, controls };
|
|
}
|
|
|
|
describe("road traffic render layer", () => {
|
|
it("draws one articulated hero and instanced background parts", () => {
|
|
const { layer } = fixture();
|
|
const hero = layer.group.getObjectByName("model-x-hero");
|
|
assert.ok(hero);
|
|
assert.equal(hero.scale.x, 0.18);
|
|
assert.ok(layer.group.children.some((child) => child instanceof THREE.InstancedMesh));
|
|
assert.equal(layer.hero()?.routeId, "la-sf-us-101");
|
|
assert.ok(Math.abs(hero.position.y - 0.155) < 1e-9);
|
|
layer.dispose();
|
|
});
|
|
|
|
it("switches routes and owns the orbit-control handoff while following", () => {
|
|
const { layer, camera, controls } = fixture();
|
|
layer.setRoute("la-sf-i-5");
|
|
assert.equal(layer.routeId(), "la-sf-i-5");
|
|
assert.equal(layer.hero()?.routeId, "la-sf-i-5");
|
|
|
|
layer.setFollowing(true);
|
|
assert.equal(layer.following(), true);
|
|
assert.equal(controls.enabled, false);
|
|
layer.tick(0.1);
|
|
assert.ok(camera.position.toArray().every(Number.isFinite));
|
|
assert.ok(controls.target.toArray().every(Number.isFinite));
|
|
|
|
layer.setCameraMode("driver");
|
|
assert.equal(layer.cameraMode(), "driver");
|
|
layer.tick(1 / 60);
|
|
assert.ok(camera.position.y > layer.group.getObjectByName("model-x-hero")!.position.y);
|
|
|
|
layer.setFollowing(false);
|
|
assert.equal(controls.enabled, true);
|
|
layer.dispose();
|
|
});
|
|
|
|
it("hands manual input to the deterministic hero and resumes assistance", () => {
|
|
const { layer } = fixture();
|
|
for (let index = 0; index < 20; index += 1) {
|
|
layer.setVehicleActions({ throttle: 1, steering: 0.8 });
|
|
layer.tick(1 / 30);
|
|
}
|
|
assert.equal(layer.hero().mode, "manual");
|
|
assert.ok(layer.hero().lateralOffsetM > 0);
|
|
|
|
layer.setVehicleActions({ modeRequest: "assisted" });
|
|
layer.tick(1 / 30);
|
|
assert.equal(layer.hero().mode, "assisted");
|
|
layer.dispose();
|
|
});
|
|
});
|