304 lines
8.3 KiB
TypeScript
304 lines
8.3 KiB
TypeScript
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 createUniform(ctx: WebGL2RenderingContext, program: WebGLProgram, name: string): WebGLUniformLocation | null {
|
|
const loc = ctx.getUniformLocation(program, name);
|
|
|
|
return loc;
|
|
}
|
|
|
|
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, 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<number> = [
|
|
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<number> = [
|
|
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 precision = 20;
|
|
|
|
const positionLoc = gfx.attribs.get("a_position");
|
|
|
|
if (positionLoc === undefined)
|
|
throw new Error("`drawCircle` requires attribute `a_position` to be defined");
|
|
|
|
const points: Array<number> = new Array<number>();
|
|
|
|
const angle = 2.0*Math.PI/precision;
|
|
var a = 0;
|
|
points.push(position.x);
|
|
points.push(position.y);
|
|
points.push(position.x);
|
|
points.push(position.y + radius);
|
|
for (let i = precision; i >= 0; --i) {
|
|
var vec = Vec2.angle(a);
|
|
a += angle;
|
|
}
|
|
|
|
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*precision);
|
|
gfx.ctx.disableVertexAttribArray(positionLoc);
|
|
}
|
|
|
|
class Graphics {
|
|
ctx: WebGL2RenderingContext;
|
|
program: WebGLProgram;
|
|
buffer: WebGLBuffer;
|
|
vao: WebGLVertexArrayObject;
|
|
attribs: Map<string, GLint> = new Map();
|
|
uniforms: Map<string, WebGLUniformLocation> = 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;
|
|
}
|
|
}
|
|
|
|
class Vec2 {
|
|
x;
|
|
y;
|
|
|
|
constructor(x: number, y: number) {
|
|
this.x = x;
|
|
this.y = y;
|
|
}
|
|
|
|
add(other: Vec2) {
|
|
this.x += other.x;
|
|
this.y += other.y;
|
|
}
|
|
|
|
sub(other: Vec2) {
|
|
this.x -= other.x;
|
|
this.y -= other.y;
|
|
}
|
|
|
|
mult(scalar: number) {
|
|
this.x *= scalar;
|
|
this.y *= scalar;
|
|
}
|
|
|
|
static angle(angle: number): Vec2 {
|
|
const eps = 1e-6;
|
|
let x = Math.cos(angle);
|
|
let y = Math.sin(angle);
|
|
|
|
if ((x > 0 && x < eps)
|
|
|| (x < 0 && x > -eps))
|
|
x = 0;
|
|
|
|
if ((y > 0 && y < eps)
|
|
|| (y < 0 && y > -eps))
|
|
y = 0;
|
|
|
|
return new Vec2(x, y);
|
|
}
|
|
}
|
|
|
|
class Vec3 {
|
|
x;
|
|
y;
|
|
z;
|
|
|
|
constructor(x: number, y: number, z: number) {
|
|
this.x = x;
|
|
this.y = y;
|
|
this.z = z;
|
|
}
|
|
|
|
add(other: Vec3) {
|
|
this.x += other.x;
|
|
this.y += other.y;
|
|
this.z += other.z;
|
|
}
|
|
|
|
sub(other: Vec3) {
|
|
this.x -= other.x;
|
|
this.y -= other.y;
|
|
this.z -= other.z;
|
|
}
|
|
}
|
|
|
|
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"), 0.2, 0.2, 0.2, 1);
|
|
drawRectangle(gfx, position, new Vec2(200, 200))
|
|
}
|
|
|
|
(() => {
|
|
draw();
|
|
window.onresize = draw;
|
|
})();
|