import { Vec2 } from "./common.js"; var vertexShaderSource = `#version 300 es in vec2 a_position; uniform vec2 u_resolution; void main() { vec2 clipSpace = (a_position / u_resolution) * 2.0 - 1.0; gl_Position = vec4(clipSpace.xy, 0.0, 1.0); } `; var fragmentShaderSource = ` #version 300 es precision highp float; out vec4 outColor; uniform vec4 u_color; void main() { outColor = u_color; } `; function createShader(ctx: WebGL2RenderingContext, type: GLenum, source: string): WebGLShader { var shader = ctx.createShader(type); if (!shader) { throw new Error("Couldn't create shader: " + type); } ctx.shaderSource(shader, source); ctx.compileShader(shader); var success = ctx.getShaderParameter(shader, ctx.COMPILE_STATUS); if (!success) { console.log(ctx.getShaderInfoLog(shader)); ctx.deleteShader(shader); throw new Error("Couldn't compile shader: " + type); } return shader; } function createProgram(ctx: WebGL2RenderingContext, vertexShaderSource: string | null, fragmentShaderSource: string | null): WebGLProgram { var program = ctx.createProgram(); if (vertexShaderSource !== null) { const vs = createShader(ctx, ctx.VERTEX_SHADER, vertexShaderSource); ctx.attachShader(program, vs); } if (fragmentShaderSource !== null) { const fs = createShader(ctx, ctx.FRAGMENT_SHADER, fragmentShaderSource); ctx.attachShader(program, fs); } ctx.linkProgram(program); var success = ctx.getProgramParameter(program, ctx.LINK_STATUS); if (!success) { console.log(ctx.getProgramInfoLog(program)); ctx.deleteProgram(program); throw new Error("Failed to create program!"); } return program; } function initializeContext(canvasId: string): WebGL2RenderingContext | null { const canvas = document.getElementById(canvasId) as HTMLCanvasElement; const ctx = canvas.getContext("webgl2", {antialias: false}); return ctx; } function drawTriangle(gfx: Graphics, positions: [Vec2, Vec2, Vec2]) { const positionLoc = gfx.attribs.get("a_position"); if (positionLoc === undefined) throw new Error("`drawTriangle` requires attribute `a_position` to be defined"); const points: Array = [ positions[0].x, positions[0].y, positions[1].x, positions[1].y, positions[2].x, positions[2].y, ] gfx.ctx.bindBuffer(gfx.ctx.ARRAY_BUFFER, gfx.buffer); gfx.ctx.enableVertexAttribArray(positionLoc); gfx.ctx.vertexAttribPointer(positionLoc, 2, gfx.ctx.FLOAT, false, 0, 0); gfx.ctx.bufferData(gfx.ctx.ARRAY_BUFFER, new Float32Array(points), gfx.ctx.STATIC_DRAW); gfx.ctx.bindVertexArray(gfx.vao); gfx.ctx.drawArrays(gfx.ctx.TRIANGLES, 0, 3); gfx.ctx.disableVertexAttribArray(positionLoc); } function drawTriangleExts(gfx: Graphics, position: Vec2, exts: Vec2) { const positionLoc = gfx.attribs.get("a_position"); if (positionLoc === undefined) throw new Error("`drawTriangle` requires attribute `a_position` to be defined"); const points: Array = [ position.x, position.y, position.x + exts.x, position.y, position.x, position.y + exts.y, ] gfx.ctx.bindBuffer(gfx.ctx.ARRAY_BUFFER, gfx.buffer); gfx.ctx.enableVertexAttribArray(positionLoc); gfx.ctx.vertexAttribPointer(positionLoc, 2, gfx.ctx.FLOAT, false, 0, 0); gfx.ctx.bufferData(gfx.ctx.ARRAY_BUFFER, new Float32Array(points), gfx.ctx.STATIC_DRAW); gfx.ctx.bindVertexArray(gfx.vao); gfx.ctx.drawArrays(gfx.ctx.TRIANGLES, 0, 3); gfx.ctx.disableVertexAttribArray(positionLoc); } function drawRectangle(gfx: Graphics, position: Vec2, exts: Vec2) { const positionLoc = gfx.attribs.get("a_position"); if (positionLoc === undefined) throw new Error("`drawRectange` requires attribute `a_position` to be defined"); const points: Array = [ position.x, position.y, position.x + exts.x, position.y, position.x, position.y + exts.y, position.x + exts.x, position.y + exts.y, position.x + exts.x, position.y, position.x, position.y + exts.y, ] gfx.ctx.bindBuffer(gfx.ctx.ARRAY_BUFFER, gfx.buffer); gfx.ctx.enableVertexAttribArray(positionLoc); gfx.ctx.vertexAttribPointer(positionLoc, 2, gfx.ctx.FLOAT, false, 0, 0); gfx.ctx.bufferData(gfx.ctx.ARRAY_BUFFER, new Float32Array(points), gfx.ctx.STATIC_DRAW); gfx.ctx.bindVertexArray(gfx.vao); gfx.ctx.drawArrays(gfx.ctx.TRIANGLES, 0, 6); gfx.ctx.disableVertexAttribArray(positionLoc); } function drawCircle(gfx: Graphics, position: Vec2, radius: number) { const positionLoc = gfx.attribs.get("a_position"); if (positionLoc === undefined) throw new Error("`drawCircle` requires attribute `a_position` to be defined"); const points: Array = new Array(); const precision = 40; const angle = 2.0*Math.PI/precision; let a = 0; for (let i = 0; i < precision; ++i) { var vec = Vec2.angle(a); vec.mult(radius); a += angle; points.push(vec); } for (let i = 0; i < points.length; i++) { const current = points[i]; const next = points[(i + 1) % points.length]; let center = position; drawTriangle(gfx, [center, center.addNew(current), center.addNew(next)]); } } function drawLine(gfx: Graphics, A: Vec2, B: Vec2) { const positionLoc = gfx.attribs.get("a_position"); if (positionLoc === undefined) throw new Error("`drawTriangle` requires attribute `a_position` to be defined"); let points: Array = [ A.x, A.y, B.x, B.y, ]; gfx.ctx.bindBuffer(gfx.ctx.ARRAY_BUFFER, gfx.buffer); gfx.ctx.enableVertexAttribArray(positionLoc); gfx.ctx.vertexAttribPointer(positionLoc, 2, gfx.ctx.FLOAT, false, 0, 0); gfx.ctx.bufferData(gfx.ctx.ARRAY_BUFFER, new Float32Array(points), gfx.ctx.STATIC_DRAW); gfx.ctx.bindVertexArray(gfx.vao); gfx.ctx.drawArrays(gfx.ctx.LINES, 0, 2); gfx.ctx.disableVertexAttribArray(positionLoc); } class Graphics { ctx: WebGL2RenderingContext; program: WebGLProgram; buffer: WebGLBuffer; vao: WebGLVertexArrayObject; attribs: Map = new Map(); uniforms: Map = new Map(); constructor(ctx: WebGL2RenderingContext) { this.ctx = ctx; this.program = createProgram(ctx, vertexShaderSource, fragmentShaderSource) this.buffer = ctx.createBuffer(); this.vao = ctx.createVertexArray(); ctx.bindVertexArray(this.vao); ctx.viewport(0, 0, ctx.canvas.width, ctx.canvas.height); ctx.useProgram(this.program); } clear(r: number, g: number, b: number, a: number) { this.ctx.clearColor(r, g, b, a); this.ctx.clear(this.ctx.COLOR_BUFFER_BIT); } createAttribute(name: string) { const loc = this.ctx.getAttribLocation(this.program, name); this.attribs.set(name, loc); } createUniform(name: string) { const loc = this.ctx.getUniformLocation(this.program, name); if (loc === null) throw new Error("Couldn't get location for uniform: " + name); this.uniforms.set(name, loc); } getUniform(name: string): WebGLUniformLocation { const loc = this.uniforms.get(name); if (loc === undefined) throw new Error("Tried to get uninitialized uniform: " + name); return loc; } } function fullscreenCanvas(id: string) { const canvas = document.getElementById(id) as HTMLCanvasElement; canvas.width = window.innerWidth; canvas.height = window.innerHeight; } function draw() { const canvasId = "game"; fullscreenCanvas(canvasId); const ctx = initializeContext(canvasId); if (ctx === null) return; const gfx = new Graphics(ctx); gfx.createAttribute("a_position"); gfx.createUniform("u_resolution"); gfx.createUniform("u_color"); const position: Vec2 = new Vec2(ctx.canvas.width / 2 - 100, ctx.canvas.height / 2 - 100); gfx.clear(0, 0, 0, 0); ctx.uniform2f(gfx.getUniform("u_resolution"), ctx.canvas.width, ctx.canvas.height); ctx.uniform4f(gfx.getUniform("u_color"), Math.random(), Math.random(), Math.random(), 1); drawRectangle(gfx, position, new Vec2(200, 200)) drawTriangle(gfx, [new Vec2(100, 100), new Vec2(200, 100), new Vec2(100, 200)]); drawTriangleExts(gfx, new Vec2(200, 200), new Vec2(-100, -100)); drawCircle(gfx, new Vec2(400, 200), 100); drawLine(gfx, new Vec2(100, 600), new Vec2(600, 600)); } (() => { draw(); window.onresize = draw; })();