diff --git a/src/main.ts b/src/main.ts index 149c231..1a71d9a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -10,6 +10,7 @@ import { createBasicVao } from "./utils/vao"; import Shape2D from "./geometry/Shape2D"; import outline from "./data/letter_outline.json"; +import { Timeline, Track } from "./utils/sequence"; function render() { const canvas = document.querySelector("#canvas"); @@ -45,11 +46,39 @@ function render() { const aVao = createBasicVao(gl, aBuffer, vertexPositionAttribLocation); const shapes: Shape2D[] = [ - new Shape2D(new Vector2D(300, canvas.height/2), 200, [0.22745, 0.35294, 0.25098], kVerts.length / 2, kVao, gl.LINES), - new Shape2D(new Vector2D(600, canvas.height/2), 200, [0.22745, 0.35294, 0.25098], eVerts.length / 2, eVao, gl.LINES), - new Shape2D(new Vector2D(900, canvas.height/2), 200, [0.22745, 0.35294, 0.25098], aVerts.length / 2, aVao, gl.LINES), + new Shape2D(new Vector2D(0, 0), 200, [0.22745, 0.35294, 0.25098], kVerts.length / 2, kVao, gl.LINES), + new Shape2D(new Vector2D(0, 0), 200, [0.22745, 0.35294, 0.25098], eVerts.length / 2, eVao, gl.LINES), + new Shape2D(new Vector2D(0, 0), 200, [0.22745, 0.35294, 0.25098], aVerts.length / 2, aVao, gl.LINES), ] + const pTrackK = new Track([ + { time: 0, value: [300, -200] }, + { time: 1, value: [300, 300] }, + ]); + + const pTrackE = new Track([ + { time: 1, value: [600, 900] }, + { time: 2, value: [600, 300] }, + ]); + + const pTrackA = new Track([ + { time: 2, value: [900, -200] }, + { time: 3, value: [900, 300] }, + ]); + + const bgTrack = new Track([ + { time: 0, value: [0.1, 0.1, 0.1, 1.0] }, + { time: 3, value: [0.1, 0.1, 0.1, 1.0] }, + { time: 4, value: [0.2, 0.9, 0.2, 1.0] }, + ]); + + const timeline = new Timeline({ + "positionK": pTrackK, + "positionE": pTrackE, + "positionA": pTrackA, + "backgroundColor": bgTrack + }, 4); + const fragmentColorUniform = gl.getUniformLocation(program, "uColor"); const canvasSizeUniform = gl.getUniformLocation(program, "uCanvasSize"); const shapeLocationUniform = gl.getUniformLocation(program, "uLocation"); @@ -63,23 +92,35 @@ function render() { `); } - + let startTime = performance.now(); + const frame = () => { + const currentTime = (performance.now() - startTime) / 1000; + const interpolatedValues = timeline.update(currentTime); + + const [r, g, b, a] = interpolatedValues.backgroundColor as number[]; + canvas.width = canvas.clientWidth; canvas.height = canvas.clientHeight; - gl.clearColor(0.1, 0.1, 0.1, 1.0); + gl.clearColor(r, g, b, a); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + gl.viewport(0, 0, canvas.width, canvas.height); gl.uniform2f(canvasSizeUniform, canvas.width, canvas.height); - shapes.forEach((shape) => { + shapes.forEach((shape, index) => { + const positionKey = `position${"KEA"[index]}`; + const [x, y] = interpolatedValues[positionKey] as number[]; + gl.uniform3f(fragmentColorUniform, shape.color[0], shape.color[1], shape.color[2]); - gl.uniform2f(shapeLocationUniform, shape.position.x, shape.position.y); + gl.uniform2f(shapeLocationUniform, x, y); gl.uniform1f(shapeScaleUniform, shape.scale); - + gl.bindVertexArray(shape.vao); gl.drawArrays(shape.renderType, 0, shape.verticesCount); }); + + requestAnimationFrame(frame) } requestAnimationFrame(frame); diff --git a/src/utils/sequence.ts b/src/utils/sequence.ts new file mode 100644 index 0000000..165bbe3 --- /dev/null +++ b/src/utils/sequence.ts @@ -0,0 +1,62 @@ +export type InterpolatableValue = number[] | number; + +export interface Keyframe { + time: number; + value: InterpolatableValue; +} + +export class Track { + keyframes: Keyframe[]; + constructor(keyframes: Keyframe[]) { + this.keyframes = keyframes.sort((a, b) => a.time - b.time); + } + + interpolate(time: number): InterpolatableValue { + if (time <= this.keyframes[0].time) { + return this.keyframes[0].value; + } + + if (time >= this.keyframes[this.keyframes.length - 1].time) { + return this.keyframes[this.keyframes.length - 1].value; + } + + let prevIndex = 0; + for (let i = 1; i < this.keyframes.length; i++) { + if (time < this.keyframes[i].time) { + break; + } + prevIndex = i; + } + + const prev = this.keyframes[prevIndex]; + const next = this.keyframes[prevIndex + 1]; + + const t = (time - prev.time) / (next.time - prev.time); + + if (Array.isArray(prev.value)) { + return prev.value.map((v, i) => v + t * ((next.value as number[])[i] - v)); + } else { + return prev.value + t * ((next.value as number) - prev.value); + } + } +} + +export class Timeline { + tracks: { [key: string]: Track }; + duration: number; + + constructor(tracks: { [key: string]: Track }, duration: number) { + this.tracks = tracks; + this.duration = duration; + } + + update(time: number): { [key: string]: InterpolatableValue } { + const result: { [key: string]: InterpolatableValue } = {}; + + for (const key in this.tracks) { + result[key] = this.tracks[key].interpolate(Math.min(time, this.duration)); + } + + return result; + } +} \ No newline at end of file