Published
Edited
Apr 13, 2020
4 forks
29 stars
Insert cell
Insert cell
Insert cell
viewof gl = {
const canvas = document.createElement("canvas");
canvas.width = W;
canvas.height = H;
const gl = canvas.value = canvas.getContext("webgl");
if (!gl) throw new Error("WebGL unsupported");
if (gl.getParameter(gl.MAX_TEXTURE_SIZE) < W) throw new Error("big textures unsupported");
canvas.style.imageRendering = "pixelated";
return canvas;
}
Insert cell
mouse = Generators.observe(notify => {
let down;
const moved = (event) => {
if (!down) return;
notify([event.layerX, event.layerY]);
gl.useProgram(timestep);
gl.uniform2f(u_mouse, event.layerX, H - event.layerY);
};
const downed = event => {
down = true;
moved(event);
};
const upped = () => {
down = false;
notify(null);
gl.useProgram(timestep);
gl.uniform2f(u_mouse, NaN, NaN);
};
viewof gl.addEventListener("mousedown", downed);
addEventListener("mousemove", moved);
addEventListener("mouseup", upped);
upped();
return () => {
viewof gl.removeEventListener("mousedown", downed);
removeEventListener("mousemove", moved);
removeEventListener("mouseup", upped);
};
})
Insert cell
timestep = newProgram(timestepShader)
Insert cell
render = newProgram(renderShader)
Insert cell
u_mouse = gl.getUniformLocation(timestep, "u_mouse")
Insert cell
{
replay;
let t1 = newTexture(initialState()), b1 = newFramebuffer(t1);
let t2 = newTexture(), b2 = newFramebuffer(t2);
while (true) {
gl.useProgram(timestep);
gl.bindTexture(gl.TEXTURE_2D, t1);
gl.bindFramebuffer(gl.FRAMEBUFFER, b2);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
gl.useProgram(render);
gl.bindTexture(gl.TEXTURE_2D, t2);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
[t1, b1, t2, b2] = [t2, b2, t1, b1];
yield;
}
}
Insert cell
function initialState() {
const array = new Uint8Array(4 * W * H);
for (let y = 0; y < H; ++y) {
for (let x = 0; x < W; ++x) {
array[W * y + x << 2] = Math.random() > 0.5 ? 255 : 0;
}
}
return array;
}
Insert cell
W = 1024
Insert cell
H = 512
Insert cell
timestepShader = newShader(gl.FRAGMENT_SHADER, `
precision mediump float;
uniform sampler2D u_image;
uniform vec2 u_size;
uniform vec2 u_mouse;

void main() {
vec2 position = gl_FragCoord.xy;
if (distance(u_mouse, position) < 12.0) {
gl_FragColor = vec4(1, 0, 0, 0);
} else {
int s = int(texture2D(u_image, position / u_size).x), n = -s;
for (int dx = -1; dx <= 1; ++dx) {
for (int dy = -1; dy <= 1; ++dy) {
n += int(texture2D(u_image, (position + vec2(dx, dy)) / u_size).x);
}
}
gl_FragColor = vec4(s == 1 ? 2 <= n && n <= 3 : n == 3, 0, 0, 0);
}
}`)
Insert cell
renderShader = newShader(gl.FRAGMENT_SHADER, `
precision mediump float;
uniform sampler2D u_image;
uniform vec2 u_size;

void main() {
gl_FragColor = vec4(0, 0, 0, texture2D(u_image, gl_FragCoord.xy / u_size).x);
}`)
Insert cell
vertexShader = newShader(gl.VERTEX_SHADER, `
attribute vec2 a_position;

void main() {
gl_Position = vec4(a_position, 0, 1);
}`)
Insert cell
function newTexture(source) {
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, W, H, 0, gl.RGBA, gl.UNSIGNED_BYTE, source);
return texture;
}
Insert cell
function newFramebuffer(texture) {
const framebuffer = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
return framebuffer;
}
Insert cell
function newShader(type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(shader));
return shader;
}
Insert cell
function newProgram(fragmentShader) {
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) throw new Error("cannot link program");
gl.useProgram(program);
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW);
const a_position = gl.getAttribLocation(program, "a_position");
gl.enableVertexAttribArray(a_position);
gl.vertexAttribPointer(a_position, 2, gl.FLOAT, false, 0, 0);
gl.uniform2f(gl.getUniformLocation(program, "u_size"), W, H);
return program;
}
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more