Compare commits
No commits in common. "28db2c53f514053086aebd6a2be1e002f8d08345" and "929421ba6d63bf0f489bed56b10413ebbc0da3ca" have entirely different histories.
28db2c53f5
...
929421ba6d
4
.gitignore
vendored
4
.gitignore
vendored
@ -22,7 +22,3 @@ dist-ssr
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
# Specific to this project
|
||||
scripts/in/*
|
||||
scripts/out/*
|
||||
@ -11,6 +11,5 @@
|
||||
"devDependencies": {
|
||||
"typescript": "^5.5.3",
|
||||
"vite": "^5.4.1"
|
||||
},
|
||||
"packageManager": "pnpm@9.9.0+sha512.60c18acd138bff695d339be6ad13f7e936eea6745660d4cc4a776d5247c540d0edee1a563695c183a66eb917ef88f2b4feb1fc25f32a7adcadc7aaf3438e99c1"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,27 +0,0 @@
|
||||
import sys
|
||||
|
||||
def conv_obj(obj):
|
||||
vertex_positions = []
|
||||
triangle_vertices = []
|
||||
lines = obj.splitlines()
|
||||
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
if line.startswith("v "):
|
||||
parts = line.split()
|
||||
|
||||
# or do parts[2] too if 3D, adjust with your axis
|
||||
vertex_positions.append([float(parts[1]), float(parts[3])])
|
||||
elif line.startswith("f "):
|
||||
parts = line.split()
|
||||
v1 = vertex_positions[int(parts[1].split("/")[0]) - 1]
|
||||
v2 = vertex_positions[int(parts[2].split("/")[0]) - 1]
|
||||
v3 = vertex_positions[int(parts[3].split("/")[0]) - 1]
|
||||
triangle_vertices.extend(v1 + v2 + v3)
|
||||
|
||||
return triangle_vertices
|
||||
|
||||
if __name__ == "__main__":
|
||||
obj_data = sys.stdin.read()
|
||||
result = conv_obj(obj_data)
|
||||
print(result)
|
||||
@ -1,11 +0,0 @@
|
||||
{
|
||||
"k": {
|
||||
"vertices": [-1.0, -1.0, -1.0, 1.0, -1.0, 0.0, 0.05, 1.0, -1.0, 0.0, 0.05, -1.0]
|
||||
},
|
||||
"e": {
|
||||
"vertices": [-1.0, -1.0, -1.0, 1.0, 0.0, -1.0, -1.0, -1.0, 0.0, 0.0, -1.0, 0.0, -1.0, 1.0, 0.0, 1.0]
|
||||
},
|
||||
"a": {
|
||||
"vertices": [-0.8, -1.0, 0.0, 1.0, 0.0, 1.0, 0.8, -1.0, -0.4, 0.0, 0.4, 0.0]
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@ -1,12 +0,0 @@
|
||||
import Vector2D from "./Vector2D";
|
||||
|
||||
export default class Shape2D {
|
||||
constructor(
|
||||
public position: Vector2D,
|
||||
public scale: number,
|
||||
public color: [number, number, number],
|
||||
public verticesCount: number,
|
||||
public vao: WebGLVertexArrayObject,
|
||||
public renderType: GLenum,
|
||||
) {}
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
export default class Vector2D {
|
||||
constructor(
|
||||
public x: number,
|
||||
public y: number,
|
||||
) {}
|
||||
|
||||
add(other: Vector2D) {
|
||||
this.x += other.x;
|
||||
this.y += other.y;
|
||||
}
|
||||
}
|
||||
154
src/main.ts
154
src/main.ts
@ -1,168 +1,18 @@
|
||||
import { showError } from "./debug";
|
||||
import "./style.css";
|
||||
|
||||
import vsSource from "./shaders/basic.vert?raw";
|
||||
import fsSource from "./shaders/basic.frag?raw";
|
||||
import { loadBuffer, loadProgram, loadShader } from "./utils/shader";
|
||||
|
||||
import Vector2D from "./geometry/Vector2D";
|
||||
import { createBasicVao } from "./utils/vao";
|
||||
import Shape2D from "./geometry/Shape2D";
|
||||
|
||||
import outline from "./data/letter_outline.json";
|
||||
import solid from "./data/letter_triangle.json";
|
||||
|
||||
import { Timeline, Track } from "./utils/sequence";
|
||||
|
||||
function render() {
|
||||
const canvas = document.querySelector<HTMLCanvasElement>("#canvas");
|
||||
if (!canvas) {
|
||||
throw new Error("Canvas does not exist");
|
||||
}
|
||||
|
||||
const gl = canvas.getContext("webgl2");
|
||||
const gl = canvas.getContext("webgl");
|
||||
if (!gl) {
|
||||
throw new Error("This browser does not support WebGL 2");
|
||||
throw new Error("This browser does not support WebGL");
|
||||
}
|
||||
|
||||
const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource);
|
||||
const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource);
|
||||
const program = loadProgram(gl, vertexShader, fragmentShader);
|
||||
gl.useProgram(program);
|
||||
|
||||
const vertexPositionAttribLocation = gl.getAttribLocation(program, "vertexPosition");
|
||||
if (vertexPositionAttribLocation < 0) {
|
||||
throw new Error("Failed to get attrib location for vertexPosition");
|
||||
}
|
||||
|
||||
const kVerts = new Float32Array(outline.k.vertices);
|
||||
const kBuffer = loadBuffer(gl, kVerts);
|
||||
const kVao = createBasicVao(gl, kBuffer, vertexPositionAttribLocation);
|
||||
|
||||
const eVerts = new Float32Array(outline.e.vertices);
|
||||
const eBuffer = loadBuffer(gl, eVerts);
|
||||
const eVao = createBasicVao(gl, eBuffer, vertexPositionAttribLocation);
|
||||
|
||||
const aVerts = new Float32Array(outline.a.vertices);
|
||||
const aBuffer = loadBuffer(gl, aVerts);
|
||||
const aVao = createBasicVao(gl, aBuffer, vertexPositionAttribLocation);
|
||||
|
||||
const KVerts = new Float32Array(solid.k.vertices);
|
||||
const KBuffer = loadBuffer(gl, KVerts);
|
||||
const KVao = createBasicVao(gl, KBuffer, vertexPositionAttribLocation);
|
||||
|
||||
const EVerts = new Float32Array(solid.e.vertices);
|
||||
const EBuffer = loadBuffer(gl, EVerts);
|
||||
const EVao = createBasicVao(gl, EBuffer, vertexPositionAttribLocation);
|
||||
|
||||
const AVerts = new Float32Array(solid.a.vertices);
|
||||
const ABuffer = loadBuffer(gl, AVerts);
|
||||
const AVao = createBasicVao(gl, ABuffer, vertexPositionAttribLocation);
|
||||
|
||||
const shapes: Shape2D[] = [
|
||||
new Shape2D(new Vector2D(0, 0), 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], eVerts.length / 2, eVao, gl.LINES),
|
||||
new Shape2D(new Vector2D(0, 0), 200, [0.22745, 0.35294, 0.25098], aVerts.length / 2, aVao, gl.LINES),
|
||||
new Shape2D(new Vector2D(0, 0), 800, [0.98823, 0.30980, 0.21960], KVerts.length / 2, KVao, gl.TRIANGLES),
|
||||
new Shape2D(new Vector2D(0, 0), 800, [0.98823, 0.30980, 0.21960], EVerts.length / 2, EVao, gl.TRIANGLES),
|
||||
new Shape2D(new Vector2D(0, 0), 800, [0.98823, 0.30980, 0.21960], AVerts.length / 2, AVao, gl.TRIANGLES),
|
||||
]
|
||||
|
||||
const pTrackK = new Track([
|
||||
{ time: 0.0, value: [300, -200] },
|
||||
{ time: 0.5, value: [300, 300] },
|
||||
]);
|
||||
|
||||
const pTrackE = new Track([
|
||||
{ time: 0.5, value: [625, 900] },
|
||||
{ time: 1.0, value: [625, 300] },
|
||||
]);
|
||||
|
||||
const pTrackA = new Track([
|
||||
{ time: 1.0, value: [900, -200] },
|
||||
{ time: 1.5, value: [900, 300] },
|
||||
]);
|
||||
|
||||
const qTrackK = new Track([
|
||||
{ time: 4.5, value: [100, 610] },
|
||||
{ time: 5.0, value: [100, 75] },
|
||||
]);
|
||||
|
||||
const qTrackE = new Track([
|
||||
{ time: 5.0, value: [450, -500] },
|
||||
{ time: 5.5, value: [450, 75] },
|
||||
]);
|
||||
|
||||
const qTrackA = new Track([
|
||||
{ time: 5.5, value: [800, 610] },
|
||||
{ time: 6.0, value: [800, 75] },
|
||||
]);
|
||||
|
||||
const bgTrack = new Track([
|
||||
{ time: 0, value: [0.0, 0.0, 0.0] },
|
||||
{ time: 3, value: [0.0, 0.0, 0.0] },
|
||||
{ time: 4, value: [0.22745, 0.35294, 0.25098] },
|
||||
]);
|
||||
|
||||
const timeline = new Timeline({
|
||||
"position0": pTrackK,
|
||||
"position1": pTrackE,
|
||||
"position2": pTrackA,
|
||||
"position3": qTrackK,
|
||||
"position4": qTrackE,
|
||||
"position5": qTrackA,
|
||||
"backgroundColor": bgTrack
|
||||
}, 8, true);
|
||||
|
||||
const fragmentColorUniform = gl.getUniformLocation(program, "uColor");
|
||||
const canvasSizeUniform = gl.getUniformLocation(program, "uCanvasSize");
|
||||
const shapeLocationUniform = gl.getUniformLocation(program, "uLocation");
|
||||
const shapeScaleUniform = gl.getUniformLocation(program, "uScale");
|
||||
if (!(fragmentColorUniform && canvasSizeUniform && shapeLocationUniform && shapeScaleUniform)) {
|
||||
throw new Error(`Some uniform not found:
|
||||
${fragmentColorUniform ? "" : "uColor"};
|
||||
${canvasSizeUniform ? "" : "uCanvasSize"};
|
||||
${shapeLocationUniform ? "" : "uLocation"};
|
||||
${shapeScaleUniform ? "" : "uScale"};
|
||||
`);
|
||||
}
|
||||
|
||||
let startTime = performance.now();
|
||||
|
||||
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.height = canvas.clientHeight;
|
||||
|
||||
gl.clearColor(r, g, b, a);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
||||
|
||||
gl.viewport(0, 0, canvas.width, canvas.height);
|
||||
gl.uniform2f(canvasSizeUniform, canvas.width, canvas.height);
|
||||
|
||||
shapes.forEach((shape, index) => {
|
||||
const positionKey = `position${index}`;
|
||||
const [x, y] = interpolatedValues[positionKey] as number[];
|
||||
|
||||
gl.uniform3f(fragmentColorUniform, shape.color[0], shape.color[1], shape.color[2]);
|
||||
gl.uniform2f(shapeLocationUniform, x, y);
|
||||
gl.uniform1f(shapeScaleUniform, shape.scale);
|
||||
|
||||
gl.bindVertexArray(shape.vao);
|
||||
gl.drawArrays(shape.renderType, 0, shape.verticesCount);
|
||||
});
|
||||
|
||||
requestAnimationFrame(frame)
|
||||
}
|
||||
|
||||
requestAnimationFrame(frame);
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
render();
|
||||
} catch (err) {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
export function loadShader(gl: WebGL2RenderingContext, type: GLenum, source: string) {
|
||||
export function loadShader(gl: WebGLRenderingContext, type: GLenum, source: string) {
|
||||
const shader = gl.createShader(type);
|
||||
if (!shader) {
|
||||
throw new Error("WebGL fails to create shader for given type");
|
||||
@ -11,12 +11,10 @@ export function loadShader(gl: WebGL2RenderingContext, type: GLenum, source: str
|
||||
const compileError = gl.getShaderInfoLog(shader);
|
||||
throw new Error("Error when compiling shader: " + compileError);
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
|
||||
export function loadProgram(gl: WebGL2RenderingContext, vertexShader: WebGLShader, fragmentShader: WebGLShader) {
|
||||
export function loadProgram(gl: WebGLRenderingContext, vertexShader: WebGLShader, fragmentShader: WebGLShader) {
|
||||
const program = gl.createProgram();
|
||||
if (!program) {
|
||||
throw new Error("WebGL fails to create program");
|
||||
@ -30,19 +28,4 @@ export function loadProgram(gl: WebGL2RenderingContext, vertexShader: WebGLShade
|
||||
const linkError = gl.getProgramInfoLog(program);
|
||||
throw new Error("Error when linking program: " + linkError);
|
||||
}
|
||||
|
||||
return program;
|
||||
}
|
||||
|
||||
|
||||
export function loadBuffer(gl: WebGL2RenderingContext, vertices: Float32Array) {
|
||||
const buffer = gl.createBuffer();
|
||||
if (!buffer) {
|
||||
throw new Error("WebGL fails to create buffer");
|
||||
}
|
||||
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
@ -1,10 +0,0 @@
|
||||
#version 300 es
|
||||
precision mediump float;
|
||||
|
||||
uniform vec3 uColor;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
void main() {
|
||||
fragColor = vec4(uColor, 1.0);
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
#version 300 es
|
||||
precision mediump float;
|
||||
|
||||
uniform vec2 uCanvasSize;
|
||||
uniform vec2 uLocation;
|
||||
uniform float uScale;
|
||||
|
||||
in vec2 vertexPosition;
|
||||
|
||||
void main() {
|
||||
vec2 finalVertexPosition = vertexPosition * uScale + uLocation;
|
||||
vec2 clipPosition = (finalVertexPosition / uCanvasSize) * 2.0 - 1.0;
|
||||
|
||||
gl_Position = vec4(clipPosition, 0.0, 1.0);
|
||||
}
|
||||
@ -1,70 +0,0 @@
|
||||
export type InterpolatableValue = number | number[];
|
||||
|
||||
interface Keyframe {
|
||||
time: number;
|
||||
value: InterpolatableValue;
|
||||
}
|
||||
|
||||
export class Track {
|
||||
private readonly keyframes: Keyframe[];
|
||||
|
||||
constructor(keyframes: Keyframe[]) {
|
||||
this.keyframes = keyframes.sort((a, b) => a.time - b.time);
|
||||
}
|
||||
|
||||
interpolate(time: number): InterpolatableValue {
|
||||
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 (typeof start === 'number' && typeof end === 'number') {
|
||||
return start + t * (end - start);
|
||||
}
|
||||
throw new Error('Mismatched value types in keyframes');
|
||||
}
|
||||
}
|
||||
|
||||
export class Timeline {
|
||||
private readonly tracks: Record<string, Track>;
|
||||
private readonly duration: number;
|
||||
private loop: boolean;
|
||||
|
||||
constructor(tracks: Record<string, Track>, duration: number, loop: boolean = false) {
|
||||
this.tracks = tracks;
|
||||
this.duration = duration;
|
||||
this.loop = loop;
|
||||
}
|
||||
|
||||
setLoop(loop: boolean): void {
|
||||
this.loop = loop;
|
||||
}
|
||||
|
||||
update(time: number): Record<string, InterpolatableValue> {
|
||||
let adjustedTime = time;
|
||||
|
||||
if (this.loop) {
|
||||
adjustedTime = time % this.duration;
|
||||
} else {
|
||||
adjustedTime = Math.min(time, this.duration);
|
||||
}
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries(this.tracks).map(([key, track]) => [key, track.interpolate(adjustedTime)])
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
export function createBasicVao(gl: WebGL2RenderingContext, buffer: WebGLBuffer, attrib: number) {
|
||||
const vao = gl.createVertexArray();
|
||||
if (!vao) {
|
||||
throw new Error("Failed to create VAO");
|
||||
}
|
||||
|
||||
gl.bindVertexArray(vao);
|
||||
gl.enableVertexAttribArray(attrib);
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
||||
gl.vertexAttribPointer(attrib, 2, gl.FLOAT, false, 0, 0);
|
||||
|
||||
gl.bindVertexArray(null);
|
||||
|
||||
return vao;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user