Basic animation timeline

This commit is contained in:
Keanu Taufan 2024-09-21 14:38:54 +07:00
parent a9f7e2c470
commit 9564299a26
No known key found for this signature in database
GPG Key ID: 1952D665A3A51BE0
2 changed files with 111 additions and 8 deletions

View File

@ -10,6 +10,7 @@ import { createBasicVao } from "./utils/vao";
import Shape2D from "./geometry/Shape2D"; import Shape2D from "./geometry/Shape2D";
import outline from "./data/letter_outline.json"; import outline from "./data/letter_outline.json";
import { Timeline, Track } from "./utils/sequence";
function render() { function render() {
const canvas = document.querySelector<HTMLCanvasElement>("#canvas"); const canvas = document.querySelector<HTMLCanvasElement>("#canvas");
@ -45,11 +46,39 @@ function render() {
const aVao = createBasicVao(gl, aBuffer, vertexPositionAttribLocation); const aVao = createBasicVao(gl, aBuffer, vertexPositionAttribLocation);
const shapes: Shape2D[] = [ 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(0, 0), 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(0, 0), 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], 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 fragmentColorUniform = gl.getUniformLocation(program, "uColor");
const canvasSizeUniform = gl.getUniformLocation(program, "uCanvasSize"); const canvasSizeUniform = gl.getUniformLocation(program, "uCanvasSize");
const shapeLocationUniform = gl.getUniformLocation(program, "uLocation"); const shapeLocationUniform = gl.getUniformLocation(program, "uLocation");
@ -63,23 +92,35 @@ function render() {
`); `);
} }
let startTime = performance.now();
const frame = () => { 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.width = canvas.clientWidth;
canvas.height = canvas.clientHeight; 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.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.viewport(0, 0, canvas.width, canvas.height); gl.viewport(0, 0, canvas.width, canvas.height);
gl.uniform2f(canvasSizeUniform, 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.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.uniform1f(shapeScaleUniform, shape.scale);
gl.bindVertexArray(shape.vao); gl.bindVertexArray(shape.vao);
gl.drawArrays(shape.renderType, 0, shape.verticesCount); gl.drawArrays(shape.renderType, 0, shape.verticesCount);
}); });
requestAnimationFrame(frame)
} }
requestAnimationFrame(frame); requestAnimationFrame(frame);

62
src/utils/sequence.ts Normal file
View File

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