feat: mount 3d app shell with camera rig

Add busPath curve helpers (pointAt/tangentAt), CameraRig as the
sole consumer of useRide.frame(), and App.tsx wiring the R3F
Canvas at low dpr for chunky pixels. Replace the boot.ts stub
with the real React root mount and append fixed-canvas CSS.
This commit is contained in:
L'électron rare
2026-07-09 11:24:39 +02:00
parent ce40fa93c1
commit cc133d5734
5 changed files with 76 additions and 1 deletions
+23
View File
@@ -0,0 +1,23 @@
import { Canvas } from '@react-three/fiber'
import { CameraRig } from './scene/CameraRig'
import { useRideInput } from './ride/useRideInput'
export default function App() {
useRideInput()
return (
<div id="demo">
<Canvas
dpr={0.6}
gl={{ antialias: false }}
camera={{ fov: 55, position: [0, 6.5, -11], near: 0.1, far: 900 }}
>
<color attach="background" args={['#05010d']} />
<ambientLight intensity={0.55} />
<directionalLight position={[60, 90, 30]} intensity={1.3} />
<CameraRig />
{/* World (Task 6), Bus (Task 7), Sky (Task 8),
Passengers (Task 7), Stops (Task 9) mount here */}
</Canvas>
</div>
)
}
+5 -1
View File
@@ -1,3 +1,7 @@
import { createRoot } from 'react-dom/client'
import { createElement } from 'react'
import App from './App'
export function boot(): void { export function boot(): void {
console.log('demo boot placeholder — replaced by Task 5') createRoot(document.getElementById('root')!).render(createElement(App))
} }
+23
View File
@@ -0,0 +1,23 @@
import { useFrame } from '@react-three/fiber'
import { Vector3 } from 'three'
import { pointAt, tangentAt } from './path'
import { useRide } from '../ride/store'
const eye = new Vector3()
const look = new Vector3()
export function CameraRig() {
useFrame((state, dt) => {
useRide.getState().frame(Math.min(dt, 0.1))
const t = useRide.getState().t
const p = pointAt(t)
const tan = tangentAt(t)
eye.copy(p).addScaledVector(tan, -11)
eye.y += 6.5
eye.x += Math.sin(state.clock.elapsedTime * 0.4) * 0.6 // gentle sway
look.copy(p).addScaledVector(tan, 6)
state.camera.position.lerp(eye, Math.min(1, dt * 4))
state.camera.lookAt(look)
})
return null
}
+22
View File
@@ -0,0 +1,22 @@
import { CatmullRomCurve3, Vector3 } from 'three'
const POINTS: Array<[number, number, number]> = [
[0, 0, 0], [18, 0, -6], [34, 0, 4], [52, 0, -2],
[70, 0, -14], [88, 0, -6], [104, 0, 6], [122, 0, 0],
[140, 0, -10], [158, 0, -2], [176, 0, 8], [196, 0, 0],
]
export const busPath = new CatmullRomCurve3(
POINTS.map((p) => new Vector3(...p)),
false,
'catmullrom',
0.3,
)
export function pointAt(t: number): Vector3 {
return busPath.getPointAt(Math.min(1, Math.max(0, t)))
}
export function tangentAt(t: number): Vector3 {
return busPath.getTangentAt(Math.min(1, Math.max(0, t)))
}
+3
View File
@@ -30,3 +30,6 @@ body.demo-on #fallback {
clip-path: inset(50%); clip-path: inset(50%);
white-space: nowrap; white-space: nowrap;
} }
#demo { position: fixed; inset: 0; }
#demo canvas { image-rendering: pixelated; width: 100%; height: 100%; }