From a611b6c72da1562e1b941608b6d237d1af8b70e6 Mon Sep 17 00:00:00 2001 From: Keanu Taufan Date: Sat, 21 Sep 2024 22:19:22 +0700 Subject: [PATCH] Refactor animator + allow loop --- src/main.ts | 10 +++--- src/utils/sequence.ts | 76 ++++++++++++++++++++++++------------------- 2 files changed, 47 insertions(+), 39 deletions(-) diff --git a/src/main.ts b/src/main.ts index 1a71d9a..ecb6e0b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -73,11 +73,11 @@ function render() { ]); const timeline = new Timeline({ - "positionK": pTrackK, - "positionE": pTrackE, - "positionA": pTrackA, + "position0": pTrackK, + "position1": pTrackE, + "position2": pTrackA, "backgroundColor": bgTrack - }, 4); + }, 10, true); const fragmentColorUniform = gl.getUniformLocation(program, "uColor"); const canvasSizeUniform = gl.getUniformLocation(program, "uCanvasSize"); @@ -109,7 +109,7 @@ function render() { gl.uniform2f(canvasSizeUniform, canvas.width, canvas.height); shapes.forEach((shape, index) => { - const positionKey = `position${"KEA"[index]}`; + const positionKey = `position${index}`; const [x, y] = interpolatedValues[positionKey] as number[]; gl.uniform3f(fragmentColorUniform, shape.color[0], shape.color[1], shape.color[2]); diff --git a/src/utils/sequence.ts b/src/utils/sequence.ts index 165bbe3..dd2c74b 100644 --- a/src/utils/sequence.ts +++ b/src/utils/sequence.ts @@ -1,62 +1,70 @@ -export type InterpolatableValue = number[] | number; +export type InterpolatableValue = number | number[]; -export interface Keyframe { +interface Keyframe { time: number; value: InterpolatableValue; } export class Track { - keyframes: Keyframe[]; + private readonly 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; - } + const { keyframes } = this; + const lastIndex = keyframes.length - 1; - let prevIndex = 0; - for (let i = 1; i < this.keyframes.length; i++) { - if (time < this.keyframes[i].time) { - break; - } - prevIndex = i; + if (time <= keyframes[0].time) return keyframes[0].value; + if (time >= keyframes[lastIndex].time) return keyframes[lastIndex].value; + + const nextIndex = keyframes.findIndex(kf => kf.time > time); + const prevIndex = nextIndex - 1; + + const [prevKf, nextKf] = [keyframes[prevIndex], keyframes[nextIndex]]; + const t = (time - prevKf.time) / (nextKf.time - prevKf.time); + + return this.interpolateValues(prevKf.value, nextKf.value, t); + } + + private interpolateValues(start: InterpolatableValue, end: InterpolatableValue, t: number): InterpolatableValue { + if (Array.isArray(start) && Array.isArray(end)) { + return start.map((v, i) => v + t * (end[i] - v)); } - - 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); + if (typeof start === 'number' && typeof end === 'number') { + return start + t * (end - start); } + throw new Error('Mismatched value types in keyframes'); } } export class Timeline { - tracks: { [key: string]: Track }; - duration: number; + private readonly tracks: Record; + private readonly duration: number; + private loop: boolean; - constructor(tracks: { [key: string]: Track }, duration: number) { + constructor(tracks: Record, duration: number, loop: boolean = false) { this.tracks = tracks; this.duration = duration; + this.loop = loop; } - update(time: number): { [key: string]: InterpolatableValue } { - const result: { [key: string]: InterpolatableValue } = {}; + setLoop(loop: boolean): void { + this.loop = loop; + } - for (const key in this.tracks) { - result[key] = this.tracks[key].interpolate(Math.min(time, this.duration)); + update(time: number): Record { + let adjustedTime = time; + + if (this.loop) { + adjustedTime = time % this.duration; + } else { + adjustedTime = Math.min(time, this.duration); } - return result; + return Object.fromEntries( + Object.entries(this.tracks).map(([key, track]) => [key, track.interpolate(adjustedTime)]) + ); } } \ No newline at end of file