Basic animation timeline
This commit is contained in:
parent
a9f7e2c470
commit
9564299a26
57
src/main.ts
57
src/main.ts
@ -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
62
src/utils/sequence.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user