155 lines
3.9 KiB
TypeScript
155 lines
3.9 KiB
TypeScript
import { Renderer, UiState } from './engine/renderer'
|
|
import { Input } from './ui/input'
|
|
import { DomOverlay } from './ui/overlay'
|
|
import { Router, Route } from './router'
|
|
import { PAGES, PROJECTS_SCENE, RESUME_SCENE } from './data/pages'
|
|
|
|
const DPR_CAP = 2
|
|
|
|
export class App {
|
|
private canvas: HTMLCanvasElement
|
|
private renderer = new Renderer()
|
|
private input!: Input
|
|
private overlay!: DomOverlay
|
|
private router = new Router()
|
|
|
|
private dpr = 1
|
|
private res = new Float32Array(2)
|
|
private mouse = new Float32Array(2)
|
|
|
|
private scene = 0
|
|
private prevScene = -1
|
|
private startTime = 0
|
|
private rafId = 0
|
|
private running = false
|
|
private stemMultipliers = new Float32Array(32)
|
|
|
|
constructor(canvas: HTMLCanvasElement) {
|
|
this.canvas = canvas
|
|
}
|
|
|
|
async start(): Promise<boolean> {
|
|
this.input = new Input()
|
|
|
|
// Build SceneContent[] from PAGES for the overlay
|
|
const scenes = PAGES.map((p) => ({
|
|
title: p.title,
|
|
subtitle: p.subtitle,
|
|
body: p.body,
|
|
}))
|
|
|
|
this.overlay = new DomOverlay(scenes, {
|
|
onNavClick: (s) => {
|
|
this.router.navigate(Router.sceneToBasePath(s))
|
|
},
|
|
onButtonClick: () => {
|
|
const next = (this.scene + 1) % 4
|
|
this.router.navigate(Router.sceneToBasePath(next))
|
|
},
|
|
onNavigate: (path) => {
|
|
this.router.navigate(path)
|
|
},
|
|
})
|
|
|
|
this.router.onChange((route) => this.applyRoute(route))
|
|
const initial = this.router.start()
|
|
this.applyRoute(initial)
|
|
this.router.replace(Router.sceneToBasePath(this.scene))
|
|
|
|
this.resize()
|
|
const ok = await this.renderer.init(this.canvas)
|
|
if (!ok) return false
|
|
this.startTime = performance.now()
|
|
this.running = true
|
|
this.loop()
|
|
return true
|
|
}
|
|
|
|
stop(): void {
|
|
this.running = false
|
|
cancelAnimationFrame(this.rafId)
|
|
}
|
|
|
|
// ---- routing ----
|
|
|
|
private applyRoute(route: Route): void {
|
|
this.scene = route.scene
|
|
this.overlay.setScene(this.scene)
|
|
|
|
if (route.scene === PROJECTS_SCENE && 'view' in route) {
|
|
if (route.view === 'category' && 'catId' in route && route.catId) {
|
|
this.overlay.showCategory(route.catId)
|
|
} else if (
|
|
route.view === 'detail' &&
|
|
'catId' in route && route.catId &&
|
|
'projectId' in route && route.projectId
|
|
) {
|
|
this.overlay.showProjectDetail(route.projectId, route.catId)
|
|
} else {
|
|
this.overlay.showCategoryGrid()
|
|
}
|
|
} else if (route.scene === RESUME_SCENE) {
|
|
this.overlay.showResume()
|
|
}
|
|
}
|
|
|
|
// ---- rendering ----
|
|
|
|
private resize(): void {
|
|
this.dpr = Math.min(window.devicePixelRatio || 1, DPR_CAP)
|
|
const w = Math.floor(window.innerWidth * this.dpr)
|
|
const h = Math.floor(window.innerHeight * this.dpr)
|
|
if (this.canvas.width !== w || this.canvas.height !== h) {
|
|
this.canvas.width = w
|
|
this.canvas.height = h
|
|
this.res[0] = w
|
|
this.res[1] = h
|
|
if (this.renderer.ready) this.renderer.resize()
|
|
}
|
|
}
|
|
|
|
private computeStemMultipliers(time: number): void {
|
|
const sm = this.stemMultipliers
|
|
const colorBase = [0.0, 0.6, 1.2, 2.4]
|
|
const speedBase = [0.0, 0.2, 0.1, 0.15]
|
|
const scene = this.scene
|
|
|
|
sm[0] = speedBase[scene]
|
|
sm[2] = colorBase[scene] * 0.2
|
|
sm[3] = 0.3 + 0.3 * Math.sin(time * 0.5)
|
|
|
|
sm[4] = colorBase[scene] * 0.4
|
|
sm[8] = 0.1
|
|
}
|
|
|
|
private loop = (): void => {
|
|
if (!this.running) return
|
|
this.rafId = requestAnimationFrame(this.loop)
|
|
|
|
const now = performance.now()
|
|
const time = (now - this.startTime) / 1000
|
|
|
|
this.resize()
|
|
|
|
const mx = this.input.x * this.dpr
|
|
const my = this.res[1] - this.input.y * this.dpr
|
|
this.mouse[0] = mx
|
|
this.mouse[1] = my
|
|
|
|
if (this.scene !== this.prevScene) {
|
|
this.prevScene = this.scene
|
|
}
|
|
|
|
this.computeStemMultipliers(time)
|
|
|
|
const state: UiState = {
|
|
resolution: this.res,
|
|
time,
|
|
scene: this.scene,
|
|
mouse: this.mouse,
|
|
stemMultipliers: this.stemMultipliers,
|
|
}
|
|
this.renderer.render(state)
|
|
}
|
|
}
|