Refactor animator + allow loop

This commit is contained in:
Keanu Taufan 2024-09-21 22:19:22 +07:00
parent 9564299a26
commit a611b6c72d
No known key found for this signature in database
GPG Key ID: 1952D665A3A51BE0
2 changed files with 47 additions and 39 deletions

View File

@ -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]);

View File

@ -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;
const { keyframes } = this;
const lastIndex = keyframes.length - 1;
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));
}
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);
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<string, Track>;
private readonly duration: number;
private loop: boolean;
constructor(tracks: { [key: string]: Track }, duration: number) {
constructor(tracks: Record<string, Track>, 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<string, InterpolatableValue> {
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)])
);
}
}