polish: scene nits — live-lens yard, square stacks, deck-clear billboard, two-layer lamps, flare reach, phone traffic and exposure
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import * as THREE from 'three';
|
||||
import { SceneResourceBag } from '@/scene/resources';
|
||||
import { createYard, railYaw } from '@/scene/world/yard';
|
||||
import { STACK_TIERS, STACK_ZS, YARD_STOPS, yardStopsPassed } from '@/journey/yard-progress';
|
||||
@@ -21,7 +22,7 @@ const nodes = (group: unknown): Node[] => {
|
||||
const instancedMeshes = (group: unknown) => nodes(group).filter((child) => child.isInstancedMesh === true);
|
||||
|
||||
describe('night freight yard', () => {
|
||||
it('draws the stacks as one InstancedMesh of 4 + 3 + 2 + 1 containers', () => {
|
||||
it('draws the stacks as one InstancedMesh of 3 + 3 + 2 + 1 containers', () => {
|
||||
const yard = createYard(new SceneResourceBag(), true);
|
||||
const stacks = instancedMeshes(yard.group).filter((mesh) => mesh.name === 'yard-stacks');
|
||||
expect(stacks).toHaveLength(1);
|
||||
@@ -97,6 +98,51 @@ describe('night freight yard', () => {
|
||||
expect(yawAt(Number.NaN)).toBeCloseTo(railYaw(), 6);
|
||||
});
|
||||
|
||||
it('aims the rails from the live lens, so the yard holds its screen x while the camera eases', () => {
|
||||
const yard = createYard(new SceneResourceBag(), true);
|
||||
const group = yard.group as unknown as InstanceType<typeof THREE.Group>;
|
||||
const camera = new THREE.PerspectiveCamera(61, 1440 / 900, 0.1, 2000);
|
||||
const railScreenX = (s: number) => {
|
||||
group.updateMatrixWorld(true);
|
||||
return new THREE.Vector3(0, 5, s).applyMatrix4(group.matrixWorld).project(camera).x;
|
||||
};
|
||||
// Mid-ease lenses: position, aim, fov and view offset all off their settled values.
|
||||
const lenses = [
|
||||
{ x: 6, z: 40, fov: 48, shift: 0.1 },
|
||||
{ x: 0, z: 30, fov: 55, shift: -0.02 },
|
||||
{ x: -3.2, z: 24, fov: 61, shift: -0.12 },
|
||||
];
|
||||
for (const lens of lenses) {
|
||||
camera.fov = lens.fov;
|
||||
camera.setViewOffset(1440, 900, -1440 * lens.shift, 0, 1440, 900);
|
||||
camera.position.set(lens.x, 30, lens.z);
|
||||
camera.lookAt(0, 18, -220);
|
||||
yard.update('additional-work', 0.5, 0, 1 / 60, null, camera.aspect, camera);
|
||||
for (const s of [-150, -110, -70]) expect(railScreenX(s)).toBeCloseTo(-0.86, 1);
|
||||
}
|
||||
});
|
||||
|
||||
it('keeps every container square to the road at any rail yaw', () => {
|
||||
const yard = createYard(new SceneResourceBag(), true);
|
||||
const stacks = instancedMeshes(yard.group).find((mesh) => mesh.name === 'yard-stacks') as unknown as InstanceType<typeof THREE.InstancedMesh>;
|
||||
const group = yard.group as unknown as InstanceType<typeof THREE.Group>;
|
||||
const local = new THREE.Matrix4();
|
||||
const world = new THREE.Matrix4();
|
||||
const axis = new THREE.Vector3();
|
||||
for (const aspect of [1920 / 1080, 1440 / 900, 1024 / 700]) {
|
||||
yard.update('additional-work', 0.5, 0, 1 / 60, null, aspect);
|
||||
group.updateMatrixWorld(true);
|
||||
for (let i = 0; i < stacks.count; i += 1) {
|
||||
stacks.getMatrixAt(i, local);
|
||||
world.multiplyMatrices(group.matrixWorld, local);
|
||||
// The box's long axis (local z) runs down the road: no more than the
|
||||
// bottom tier's hair of slop (0.75 degrees) off world z.
|
||||
axis.set(0, 0, 1).transformDirection(world);
|
||||
expect(Math.abs(Math.atan2(axis.x, axis.z))).toBeLessThan(0.013);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('frees every geometry and material on dispose', () => {
|
||||
const resources = new SceneResourceBag();
|
||||
const yard = createYard(resources, true);
|
||||
|
||||
Reference in New Issue
Block a user