feat: evolve portfolio into an authored midnight grand tour
This commit is contained in:
@@ -15,6 +15,8 @@ import {
|
||||
HORIZON_FOG_END,
|
||||
HORIZON_FOG_START,
|
||||
} from '@/scene/world/metrics';
|
||||
import { CHAPTER_COMPOSITIONS } from '@/scene/world/composition';
|
||||
import { createCoachworkEnvironment } from '@/scene/world/coachwork-light';
|
||||
import { createTravelDistance } from '@/scene/world/travel-distance';
|
||||
import {
|
||||
createMutableChapterPalette,
|
||||
@@ -27,9 +29,9 @@ declare const __PORTFOLIO_TEST_BUILD__: boolean;
|
||||
const TEST_BUILD = typeof __PORTFOLIO_TEST_BUILD__ !== 'undefined' && __PORTFOLIO_TEST_BUILD__;
|
||||
|
||||
export function createChaseCamera(aspect = 1) {
|
||||
const camera = new THREE.PerspectiveCamera(66, aspect, 0.1, 1_200);
|
||||
camera.position.set(0, 8.6, 22);
|
||||
camera.lookAt(0, 4.8, -128);
|
||||
const camera = new THREE.PerspectiveCamera(56, aspect, 0.1, 1_200);
|
||||
camera.position.set(-2.6, 6.4, 21);
|
||||
camera.lookAt(0, 3.9, -90);
|
||||
return camera;
|
||||
}
|
||||
|
||||
@@ -70,6 +72,8 @@ export async function createProceduralSceneGeneration(
|
||||
|
||||
const scene = new THREE.Scene();
|
||||
scene.name = 'world-root';
|
||||
scene.environment = createCoachworkEnvironment(context.resources, renderer);
|
||||
scene.environmentIntensity = 0.85;
|
||||
const currentPalette = createMutableChapterPalette('deep-night');
|
||||
const targetPalette = createMutableChapterPalette('deep-night');
|
||||
const background = currentPalette.sky.clone();
|
||||
@@ -121,8 +125,10 @@ export async function createProceduralSceneGeneration(
|
||||
y: camera.position.y,
|
||||
z: camera.position.z,
|
||||
targetX: 0,
|
||||
targetY: 4.8,
|
||||
targetZ: -128,
|
||||
targetY: 3.9,
|
||||
targetZ: -90,
|
||||
shift: 0.2,
|
||||
verticalShift: 0,
|
||||
fov: camera.fov,
|
||||
roll: 0,
|
||||
};
|
||||
@@ -145,8 +151,10 @@ export async function createProceduralSceneGeneration(
|
||||
camera.updateProjectionMatrix();
|
||||
if (lastNow === null) {
|
||||
const wideComposition = camera.aspect > 1.18;
|
||||
cameraState.x = wideComposition ? -1.35 : 0;
|
||||
cameraState.targetX = wideComposition ? 1.6 : 0;
|
||||
cameraState.x = wideComposition ? -2.6 : 0;
|
||||
cameraState.targetX = 0;
|
||||
cameraState.shift = wideComposition ? 0.2 : 0;
|
||||
cameraState.verticalShift = wideComposition ? 0 : 0.16;
|
||||
camera.position.set(cameraState.x, cameraState.y, cameraState.z);
|
||||
camera.lookAt(
|
||||
cameraState.targetX,
|
||||
@@ -241,18 +249,22 @@ export async function createProceduralSceneGeneration(
|
||||
ambient.color.copy(currentPalette.haze).lerp(currentPalette.terrainHigh, 0.26);
|
||||
ambient.groundColor.copy(currentPalette.fog);
|
||||
ambient.intensity = 1.05 + currentPalette.warmth * 0.18;
|
||||
carKeyLight.color.copy(currentPalette.terrainHigh).lerp(currentPalette.haze, 0.22);
|
||||
carKeyLight.intensity = 21 + response.bloomEnergy * 3.6;
|
||||
carFillLight.color.copy(currentPalette.haze).lerp(currentPalette.sun, 0.18);
|
||||
carKeyLight.color.setHex(0xb9e3ef);
|
||||
carKeyLight.intensity = 32 + response.bloomEnergy * 3.6;
|
||||
carFillLight.color.setHex(0x809ec1);
|
||||
carFillLight.intensity = 12.5 + currentPalette.warmth * 2.8;
|
||||
rearLight.intensity = 4.2 + response.suspensionEnergy * 1.4;
|
||||
const wideComposition = camera.aspect > 1.18;
|
||||
const cameraX = wideComposition ? -1.35 : 0;
|
||||
const targetX = wideComposition ? 1.6 : 0;
|
||||
const composition = CHAPTER_COMPOSITIONS[snapshot.activeSectionId];
|
||||
// The document stays native. Only the rendered lens eases to the chapter's safe zone.
|
||||
const cameraX = wideComposition ? composition.x : 0;
|
||||
const targetX = 0;
|
||||
const targetShift = wideComposition ? composition.shift : 0;
|
||||
const targetVerticalShift = wideComposition ? 0 : (snapshot.activeSectionId === 'start' ? 0.16 : 0.07);
|
||||
const roadVibration = Math.sin(driveDistance * 0.105) * (
|
||||
0.012 + response.roadEnergy * 0.024
|
||||
);
|
||||
const cameraMix = 1 - Math.exp(-deltaSeconds / 0.2);
|
||||
const cameraMix = 1 - Math.exp(-deltaSeconds / 0.85);
|
||||
cameraState.x = THREE.MathUtils.lerp(
|
||||
cameraState.x,
|
||||
cameraX - response.directionBias * 0.16,
|
||||
@@ -260,12 +272,12 @@ export async function createProceduralSceneGeneration(
|
||||
);
|
||||
cameraState.y = THREE.MathUtils.lerp(
|
||||
cameraState.y,
|
||||
8.6 + roadVibration,
|
||||
(wideComposition ? composition.y : 6.5) + roadVibration,
|
||||
cameraMix,
|
||||
);
|
||||
cameraState.z = THREE.MathUtils.lerp(
|
||||
cameraState.z,
|
||||
22 - response.cameraEnergy * 1.65,
|
||||
(wideComposition ? composition.z : 29) - response.cameraEnergy * 1.2,
|
||||
cameraMix,
|
||||
);
|
||||
cameraState.targetX = THREE.MathUtils.lerp(
|
||||
@@ -275,17 +287,17 @@ export async function createProceduralSceneGeneration(
|
||||
);
|
||||
cameraState.targetY = THREE.MathUtils.lerp(
|
||||
cameraState.targetY,
|
||||
4.8 + response.cameraEnergy * 0.58,
|
||||
composition.targetY + response.cameraEnergy * 0.32,
|
||||
cameraMix,
|
||||
);
|
||||
cameraState.targetZ = THREE.MathUtils.lerp(
|
||||
cameraState.targetZ,
|
||||
-128 - response.cameraEnergy * 24,
|
||||
composition.targetZ - response.cameraEnergy * 16,
|
||||
cameraMix,
|
||||
);
|
||||
cameraState.fov = THREE.MathUtils.lerp(
|
||||
cameraState.fov,
|
||||
66 + response.cameraEnergy * 13,
|
||||
(wideComposition ? composition.fov : 62) + response.cameraEnergy * 7,
|
||||
cameraMix,
|
||||
);
|
||||
cameraState.roll = THREE.MathUtils.lerp(
|
||||
@@ -293,6 +305,9 @@ export async function createProceduralSceneGeneration(
|
||||
-response.directionBias * 0.003 + roadVibration * 0.018,
|
||||
cameraMix,
|
||||
);
|
||||
cameraState.shift = THREE.MathUtils.lerp(cameraState.shift, targetShift, cameraMix);
|
||||
cameraState.verticalShift = THREE.MathUtils.lerp(cameraState.verticalShift, targetVerticalShift, cameraMix);
|
||||
camera.setViewOffset(width, height, -width * cameraState.shift, -height * cameraState.verticalShift, width, height);
|
||||
camera.position.set(cameraState.x, cameraState.y, cameraState.z);
|
||||
camera.lookAt(cameraState.targetX, cameraState.targetY, cameraState.targetZ);
|
||||
camera.rotation.z += cameraState.roll;
|
||||
@@ -327,6 +342,11 @@ export async function createProceduralSceneGeneration(
|
||||
dispose() {
|
||||
if (disposed) return;
|
||||
disposed = true;
|
||||
// Quality changes reuse the canvas context. Leave texture upload flags at
|
||||
// defaults before the next renderer creates its empty array textures.
|
||||
const gl = renderer.getContext();
|
||||
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
|
||||
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
|
||||
scene.clear();
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user