feat: build procedural lifecycle-safe drive scene
This commit is contained in:
79
src/scene/world/sign.ts
Normal file
79
src/scene/world/sign.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import * as THREE from 'three';
|
||||
import type { SectionId } from '@/content/types';
|
||||
import type { SignSnapshot } from '@/journey/types';
|
||||
import type { SceneResourceBag } from '@/scene/resources';
|
||||
|
||||
const routeLabels: Readonly<Record<SectionId, string>> = {
|
||||
start: '00 / START',
|
||||
profile: '01 / PROFILE',
|
||||
experience: '02 / EXPERIENCE',
|
||||
infrastructure: '03 / INFRASTRUCTURE',
|
||||
'selected-builds': '04 / SELECTED BUILDS',
|
||||
'additional-work': '05 / ADDITIONAL WORK',
|
||||
toolkit: '06 / TOOLKIT',
|
||||
contact: '07 / DESTINATION',
|
||||
};
|
||||
|
||||
export function routeLabelFor(id: SectionId): string {
|
||||
return routeLabels[id];
|
||||
}
|
||||
|
||||
export function createSign(resources: SceneResourceBag) {
|
||||
const group = new THREE.Group();
|
||||
group.visible = false;
|
||||
const geometry = resources.own('geometry', new THREE.PlaneGeometry(8.4, 2.2));
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 768;
|
||||
canvas.height = 192;
|
||||
const context = canvas.getContext('2d');
|
||||
const texture = resources.own('texture', new THREE.CanvasTexture(canvas));
|
||||
texture.colorSpace = THREE.SRGBColorSpace;
|
||||
const material = resources.own('material', new THREE.MeshBasicMaterial({
|
||||
color: 0xffffff,
|
||||
transparent: true,
|
||||
opacity: 0.96,
|
||||
side: THREE.DoubleSide,
|
||||
map: texture,
|
||||
}));
|
||||
const panel = new THREE.Mesh(geometry, material);
|
||||
group.add(panel);
|
||||
|
||||
const thrusterGeometry = resources.own('geometry', new THREE.ConeGeometry(0.18, 0.72, 8));
|
||||
const thrusterMaterial = resources.own('material', new THREE.MeshBasicMaterial({ color: 0x44e8f5 }));
|
||||
for (const x of [-3.5, 3.5]) {
|
||||
const thruster = new THREE.Mesh(thrusterGeometry, thrusterMaterial);
|
||||
thruster.rotation.x = Math.PI / 2;
|
||||
thruster.position.set(x, -1.22, 0.08);
|
||||
group.add(thruster);
|
||||
}
|
||||
|
||||
let target: SectionId | null = null;
|
||||
const updateTexture = (id: SectionId): void => {
|
||||
if (target === id) return;
|
||||
target = id;
|
||||
if (!context) return;
|
||||
context.fillStyle = '#07111b';
|
||||
context.fillRect(0, 0, canvas.width, canvas.height);
|
||||
context.strokeStyle = '#35ddeb';
|
||||
context.lineWidth = 7;
|
||||
context.strokeRect(8, 8, canvas.width - 16, canvas.height - 16);
|
||||
context.fillStyle = '#e8fbff';
|
||||
context.font = '600 46px ui-monospace, monospace';
|
||||
context.textAlign = 'center';
|
||||
context.textBaseline = 'middle';
|
||||
context.fillText(routeLabelFor(id), canvas.width / 2, canvas.height / 2);
|
||||
texture.needsUpdate = true;
|
||||
};
|
||||
|
||||
return {
|
||||
group,
|
||||
update(sign: SignSnapshot | null) {
|
||||
group.visible = sign !== null;
|
||||
if (!sign) return;
|
||||
updateTexture(sign.targetId);
|
||||
const phaseOffset = sign.phase === 'approach' ? -26 : sign.phase === 'stabilized' ? -5 : 8;
|
||||
group.position.set(7.2, 4.1, phaseOffset + sign.progress * 8);
|
||||
group.rotation.y = -0.12;
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user