feat: weave speed-reactive terrain and ground roadside signs
Anchor desktop signs beside the road and feather the compact reading edge with measured chapter landings. Add a continuous cyan-to-coral terrain grid and a bounded rear wake that builds with existing drive speed. Preserve approved content, media, soundtrack, and canonical journey contracts.
This commit is contained in:
@@ -6,6 +6,8 @@ import { renderQualityFor } from '@/scene/render-quality';
|
||||
import { createHorizonAir } from '@/scene/world/horizon-air';
|
||||
import { createExpressway } from '@/scene/world/expressway';
|
||||
import { createSignStage } from '@/scene/world/sign-stage';
|
||||
import { usesRoadsideSign } from '@/scene/world/sign-layout';
|
||||
import { createSpeedWake } from '@/scene/world/speed-wake';
|
||||
import { createRoad } from '@/scene/world/road';
|
||||
import { createTerrain } from '@/scene/world/terrain';
|
||||
import { createSun } from '@/scene/world/sun';
|
||||
@@ -111,6 +113,7 @@ export async function createProceduralSceneGeneration(
|
||||
|
||||
const road = createRoad(context.resources, quality);
|
||||
const terrain = createTerrain(context.resources, quality);
|
||||
const speedWake = createSpeedWake(context.resources);
|
||||
const expressway = createExpressway(context.resources, quality);
|
||||
const signStage = createSignStage(context.resources);
|
||||
const sun = createSun(context.resources);
|
||||
@@ -132,6 +135,7 @@ export async function createProceduralSceneGeneration(
|
||||
sun.group,
|
||||
horizonAir.group,
|
||||
car.group,
|
||||
speedWake.group,
|
||||
sign.group,
|
||||
);
|
||||
|
||||
@@ -158,6 +162,8 @@ export async function createProceduralSceneGeneration(
|
||||
let disposed = false;
|
||||
let renderedFrames = 0;
|
||||
let atmosphericTime = 0;
|
||||
let signCameraWeight = 0;
|
||||
let overdrive = 0;
|
||||
let firstFrameSuppressionRecorded = false;
|
||||
|
||||
const resize = (): void => {
|
||||
@@ -240,6 +246,17 @@ export async function createProceduralSceneGeneration(
|
||||
lastNow = now;
|
||||
dynamics.update(snapshot.acceleration, snapshot.direction, deltaSeconds);
|
||||
const response = dynamics.snapshot();
|
||||
// Read the existing speed contract; visual charge has its own slow release
|
||||
// and cannot change the soundtrack or the canonical drive dynamics.
|
||||
const chargeTarget = THREE.MathUtils.smoothstep(
|
||||
snapshot.driveSpeed,
|
||||
0.68,
|
||||
0.9,
|
||||
);
|
||||
overdrive +=
|
||||
(chargeTarget - overdrive) *
|
||||
(1 -
|
||||
Math.exp(-deltaSeconds / (chargeTarget > overdrive ? 0.28 : 1.05)));
|
||||
writeChapterPalette(targetPalette, snapshot.chapter, snapshot.chapter, 0);
|
||||
dampPalette(currentPalette, targetPalette, deltaSeconds);
|
||||
const driveDistance = travelDistance.advance(
|
||||
@@ -271,6 +288,7 @@ export async function createProceduralSceneGeneration(
|
||||
snapshot.chapter,
|
||||
snapshot.routeProgress,
|
||||
currentPalette,
|
||||
overdrive,
|
||||
);
|
||||
atmosphericTime += deltaSeconds;
|
||||
const elapsedSeconds = atmosphericTime;
|
||||
@@ -287,6 +305,12 @@ export async function createProceduralSceneGeneration(
|
||||
elapsedSeconds,
|
||||
driveDistance,
|
||||
);
|
||||
speedWake.update(
|
||||
overdrive,
|
||||
elapsedSeconds,
|
||||
car.group.position,
|
||||
width < 1100 || height < 620 ? 2.32 : 2.78,
|
||||
);
|
||||
sign.update(snapshot.sign, now);
|
||||
atmosphere.update(
|
||||
driveDistance,
|
||||
@@ -302,7 +326,7 @@ export async function createProceduralSceneGeneration(
|
||||
ambient.groundColor.copy(currentPalette.fog);
|
||||
ambient.intensity = 1.05 + currentPalette.warmth * 0.18;
|
||||
carKeyLight.color.setHex(0xb9e3ef);
|
||||
carKeyLight.intensity = 32 + response.bloomEnergy * 3.6;
|
||||
carKeyLight.intensity = 32 + response.bloomEnergy * 3.6 + overdrive * 8;
|
||||
carFillLight.color.setHex(0x809ec1);
|
||||
carFillLight.intensity = 12.5 + currentPalette.warmth * 2.8;
|
||||
rearLight.intensity = 4.2 + response.suspensionEnergy * 1.4;
|
||||
@@ -346,11 +370,23 @@ export async function createProceduralSceneGeneration(
|
||||
// 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 signTarget =
|
||||
usesRoadsideSign(width, height) && snapshot.sign !== null
|
||||
? snapshot.sign.phase === 'depart'
|
||||
? 1 - snapshot.sign.progress
|
||||
: 1
|
||||
: 0;
|
||||
signCameraWeight +=
|
||||
(signTarget - signCameraWeight) * (1 - Math.exp(-deltaSeconds / 0.48));
|
||||
const targetShift =
|
||||
height < 560 && width >= 768
|
||||
? 0.22
|
||||
: wideComposition
|
||||
? composition.shift
|
||||
? THREE.MathUtils.lerp(
|
||||
composition.shift,
|
||||
composition.shift * 0.28,
|
||||
signCameraWeight,
|
||||
)
|
||||
: 0;
|
||||
const targetVerticalShift = wideComposition
|
||||
? 0
|
||||
@@ -434,7 +470,14 @@ export async function createProceduralSceneGeneration(
|
||||
camera.updateProjectionMatrix();
|
||||
}
|
||||
const stage = signStage.update(width, height);
|
||||
sign.compose(camera, width, height, stage.navigationBottom, now);
|
||||
sign.compose(
|
||||
camera,
|
||||
width,
|
||||
height,
|
||||
stage.navigationBottom,
|
||||
now,
|
||||
car.group.position,
|
||||
);
|
||||
if (TEST_BUILD && snapshot.sign !== null) {
|
||||
const presentation = sign.inspect(camera, width, height);
|
||||
if (presentation !== null) {
|
||||
|
||||
Reference in New Issue
Block a user