Initial commit: WebGPU portfolio — generative-art site with DOM overlay
This commit is contained in:
147
src/app.ts
Normal file
147
src/app.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
import { Renderer, UiState } from './engine/renderer'
|
||||
import { Input } from './ui/input'
|
||||
import { DomOverlay } from './ui/overlay'
|
||||
|
||||
const DPR_CAP = 2
|
||||
|
||||
interface SceneContent {
|
||||
title: string
|
||||
subtitle: string
|
||||
body: string
|
||||
}
|
||||
|
||||
const SCENES: SceneContent[] = [
|
||||
{
|
||||
title: 'Ahmed A. Mohamed',
|
||||
subtitle: 'Creative Developer & Graphics Engineer',
|
||||
body: 'Welcome to my personal website, there\'s a lot going on in my life that I will be sharing here randomly over time so take a look.'
|
||||
},
|
||||
{
|
||||
title: 'ABOUT',
|
||||
subtitle: 'Who I am',
|
||||
body: 'I build creative web experiences\nusing cutting-edge graphics technology.'
|
||||
},
|
||||
{
|
||||
title: 'PROJECTS',
|
||||
subtitle: 'Things I have built',
|
||||
body: 'A collection of work exploring\nWebGPU, shaders, and generative art.'
|
||||
},
|
||||
{
|
||||
title: 'CONTACT',
|
||||
subtitle: 'Get in touch',
|
||||
body: 'Reach me at hello@example.com\nor find me on GitHub.'
|
||||
},
|
||||
{
|
||||
title: 'RESUME',
|
||||
subtitle: 'Ahmed Mohamed',
|
||||
body: 'Graphics & Systems Engineer'
|
||||
}
|
||||
]
|
||||
|
||||
export class App {
|
||||
private canvas: HTMLCanvasElement
|
||||
private renderer = new Renderer()
|
||||
private input!: Input
|
||||
private overlay!: DomOverlay
|
||||
|
||||
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()
|
||||
this.overlay = new DomOverlay(SCENES, {
|
||||
onNavClick: (s) => { this.scene = s },
|
||||
onButtonClick: () => { this.scene = (this.scene + 1) % 5 }
|
||||
})
|
||||
this.overlay.setScene(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)
|
||||
}
|
||||
|
||||
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
|
||||
// scene-based color identity and speed
|
||||
const colorBase = [0.0, 0.6, 1.2, 1.8, 2.4]
|
||||
const speedBase = [0.0, 0.2, 0.1, 0.0, 0.15]
|
||||
const scene = this.scene
|
||||
|
||||
// [0] = (camera speed, 0, color offset, twist/flash)
|
||||
sm[0] = speedBase[scene]
|
||||
sm[2] = colorBase[scene] * 0.2
|
||||
sm[3] = 0.3 + 0.3 * Math.sin(time * 0.5)
|
||||
|
||||
// [1] = (color offset, 0, 0, 0)
|
||||
sm[4] = colorBase[scene] * 0.4
|
||||
|
||||
// [2] = (size scale, 0, 0, 0)
|
||||
sm[8] = 0.1// + 0.4 * Math.sin(time * 0.3 + scene * 3.0)
|
||||
|
||||
// rest of the array stays zero
|
||||
}
|
||||
|
||||
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.overlay.setScene(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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user