Merge branch 'polish/card-gaps' into redesign/overnight
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import * as THREE from 'three';
|
||||
import type { SceneResourceBag } from '@/scene/resources';
|
||||
import { CARD_GAP } from '@/journey/card-gaps';
|
||||
import { maintenanceStep, type MaintenanceStep } from '@/journey/maintenance-progress';
|
||||
|
||||
/**
|
||||
@@ -7,7 +8,8 @@ import { maintenanceStep, type MaintenanceStep } from '@/journey/maintenance-pro
|
||||
* window opens over the city: one district powers down lamp by lamp, amber
|
||||
* backup packets stream to an offsite silhouette on the far right, rain
|
||||
* starts on the windshield, then the service re-lights in the infrastructure
|
||||
* accent. Everything is a pure function of section-local scroll progress, so
|
||||
* accent at cutover, in the open road after the card (CARD_GAP), where the
|
||||
* skyline is in frame. Everything is a pure function of section-local scroll progress, so
|
||||
* scrolling back up replays it in reverse. Only the distant lightning keeps
|
||||
* its own (bounded) clock.
|
||||
*
|
||||
@@ -48,9 +50,9 @@ export function maintenanceTimeline(p: number): MaintenanceFrame {
|
||||
return {
|
||||
step: maintenanceStep(x),
|
||||
dim: smooth(0.1, 0.35, x),
|
||||
relight: smooth(0.35, 0.65, x),
|
||||
relight: smooth(CARD_GAP.from, CARD_GAP.peak, x),
|
||||
rain: MAINTENANCE_RAIN_PEAK * smooth(0.1, 0.3, x) * (1 - smooth(0.7, 0.9, x)),
|
||||
packetPhase: clamp01((x - 0.12) / 0.21) + clamp01((x - 0.67) / 0.21),
|
||||
packetPhase: clamp01((x - 0.12) / 0.21) + clamp01((x - (CARD_GAP.from - 0.21)) / 0.21),
|
||||
offsite: smooth(0.14, 0.36, x),
|
||||
storm: smooth(0, 0.06, x) * (1 - smooth(0.68, 0.92, x)),
|
||||
lightning: smooth(0, 0.03, x) * (1 - smooth(0.62, 0.7, x)),
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import * as THREE from 'three';
|
||||
import type { SectionId } from '@/content/types';
|
||||
import { SIGN_ACCENTS } from '@/journey/sign-routes';
|
||||
import { BEACON_HZ, readRelay, RELAY_STOPS, rungLit } from '@/journey/relay-progress';
|
||||
import { CARD_GAP } from '@/journey/card-gaps';
|
||||
import { BEACON_HZ, readRelay, RELAY_STOPS, rungLit, stationLive } from '@/journey/relay-progress';
|
||||
import type { SceneResourceBag } from '@/scene/resources';
|
||||
import { CHAPTER_COMPOSITIONS, RELAY_NUDGE } from './composition';
|
||||
|
||||
@@ -18,8 +19,9 @@ type ThreeInstancedMesh = InstanceType<typeof THREE.InstancedMesh>;
|
||||
* RELAY STATION, scene side. Four lattice transmitter masts, one per toolkit
|
||||
* group, stand on the far shoulders, alternating sides (first on the right).
|
||||
* As the chapter scrolls each comes online in turn: its lamp rungs ignite
|
||||
* bottom-up (one per technology), a red beacon blinks at 0.5 Hz and a thin
|
||||
* additive beam sweeps the sky every 6 s. Every beat is a pure function of
|
||||
* bottom-up (one per technology) and a red beacon blinks at 0.5 Hz. The
|
||||
* station goes live in the open road after the card (CARD_GAP): every
|
||||
* online mast's thin additive beam sweeps the sky every 6 s. Every beat is a pure function of
|
||||
* section-local progress, so scrolling back takes the masts offline again.
|
||||
* One InstancedMesh per part (frames, rungs, beacons, beams). Hovering a
|
||||
* technology flashes its rung. Lite (low tier, touch, data saver): all rungs
|
||||
@@ -133,7 +135,7 @@ const smooth = (from: number, to: number, value: number): number => {
|
||||
const halfWidthAt = (y: number): number => THREE.MathUtils.lerp(BASE_HALF, TOP_HALF, y / MAST_HEIGHT);
|
||||
|
||||
/** The chapter fades the station in and out at its edges. */
|
||||
export const relayPresence = (p: number): number => smooth(0, 0.05, p) * (1 - smooth(0.95, 1, p));
|
||||
export const relayPresence = (p: number): number => smooth(0, 0.05, p) * (1 - smooth(CARD_GAP.to, 1, p));
|
||||
|
||||
export interface RelayRung {
|
||||
/** World height of the rung's centre. */
|
||||
@@ -453,6 +455,7 @@ export function createRelayMasts(resources: SceneResourceBag, full: boolean, ton
|
||||
const dt = Math.max(0, deltaSeconds);
|
||||
const live = section === 'toolkit' ? p : null;
|
||||
const masts = relayLayout(live, readRelay().counts, full, aspect);
|
||||
const beams = stationLive(live);
|
||||
const changed = section !== lastSection;
|
||||
lastSection = section;
|
||||
|
||||
@@ -498,9 +501,9 @@ export function createRelayMasts(resources: SceneResourceBag, full: boolean, ton
|
||||
if (beamMesh !== null && beamState !== null) {
|
||||
const sweep = mast.side * 0.5 + 0.4 * Math.sin((2 * Math.PI * time) / SWEEP_SECONDS + m * 1.7);
|
||||
rotation.setFromAxisAngle(forward, sweep);
|
||||
const shown = drawn && mast.level > 0 ? mast.scale : 0;
|
||||
const shown = drawn && mast.level > 0 && beams > 0 ? mast.scale : 0;
|
||||
beamMesh.setMatrixAt(m, matrix.compose(position, rotation, scale.set(BEAM_WIDTH * shown, BEAM_LENGTH * shown, 1)));
|
||||
beamState.setXYZ(m, mast.level, 0, mast.presence);
|
||||
beamState.setXYZ(m, mast.level * beams, 0, mast.presence);
|
||||
rotation.identity();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as THREE from 'three';
|
||||
import type { SectionId } from '@/content/types';
|
||||
import { CARD_GAP } from '@/journey/card-gaps';
|
||||
import { SIGN_ACCENTS } from '@/journey/sign-routes';
|
||||
import {
|
||||
CRANE_FROM_Z,
|
||||
@@ -434,7 +435,7 @@ export function createYard(resources: SceneResourceBag, full: boolean): YardWorl
|
||||
const t = Math.min(1, Math.max(0, (v - from) / (to - from)));
|
||||
return t * t * (3 - 2 * t);
|
||||
};
|
||||
const presence = live === null ? 0 : edge(0, 0.05, live) * (1 - edge(0.95, 1, live));
|
||||
const presence = live === null ? 0 : edge(0, 0.05, live) * (1 - edge(CARD_GAP.to, 1, live));
|
||||
const z = craneZ(live ?? 0);
|
||||
const worldZ = group.position.z + z * Math.cos(yaw);
|
||||
const clear = deckZ === null || !Number.isFinite(deckZ) ? 1 : edge(DECK_CLEAR.from, DECK_CLEAR.to, Math.abs(deckZ - worldZ));
|
||||
@@ -461,7 +462,7 @@ export function createYard(resources: SceneResourceBag, full: boolean): YardWorl
|
||||
});
|
||||
stackState.needsUpdate = true;
|
||||
|
||||
warm = floodWarmup(warm, full && live !== null && live < 0.93, deltaSeconds);
|
||||
warm = floodWarmup(warm, full && live !== null && live < CARD_GAP.to, deltaSeconds);
|
||||
const light = floodLight(warm);
|
||||
flood.copy(ORANGE).lerp(HID, light.hue).multiplyScalar(full ? light.output * 0.5 : 0);
|
||||
pulse.value = full ? Math.min(1, Math.max(0, beat)) : 0;
|
||||
|
||||
Reference in New Issue
Block a user