feat: cinematic outro that mirrors the intro crane

On arrival the camera cranes back toward the sun on a long lens, letterbox
bars close in on every tier and a closing credit of real identity facts
fades into the upper bar. Leaving the destination reverses it.
This commit is contained in:
2026-10-10 22:25:25 +03:00
parent 0ae27168cd
commit 06b6e996a0
6 changed files with 90 additions and 1 deletions

View File

@@ -184,6 +184,9 @@ export async function createProceduralSceneGeneration(
let introProgress: number | null = null;
let introFrames = 0;
let introSlowFrames = 0;
// The outro mirrors the intro: on arrival the camera cranes back up toward
// the sun on a long lens. Leaving the destination reverses it, faster.
let outroProgress = 0;
let gantryState = '';
const resize = (): void => {
@@ -515,9 +518,18 @@ export async function createProceduralSceneGeneration(
if (introSlowFrames >= 3) introProgress = 1;
introProgress = Math.min(1, introProgress + deltaSeconds / INTRO_SECONDS);
}
outroProgress = THREE.MathUtils.clamp(
outroProgress
+ (snapshot.arrived ? deltaSeconds / OUTRO_SECONDS : -deltaSeconds / OUTRO_RETURN_SECONDS),
0,
1,
);
// Part of the way back, not all: the end shot holds the sun, the
// skyline and the road running into them, like a film's last frame.
const outro = easeInOutCubic(outroProgress) * OUTRO_REACH;
const crane = easeInOutCubic(
THREE.MathUtils.clamp((introProgress - 0.06) / 0.94, 0, 1),
);
) * (1 - outro);
const craneShift = cameraState.shift * crane;
camera.setViewOffset(
width,
@@ -597,6 +609,9 @@ export async function createProceduralSceneGeneration(
}
const INTRO_SECONDS = 4.2;
const OUTRO_SECONDS = 5.5;
const OUTRO_RETURN_SECONDS = 1.6;
const OUTRO_REACH = 0.62;
function easeInOutCubic(t: number): number {
return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;