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; } }