Published
Edited
Sep 26, 2021
3 forks
18 stars
Insert cell
Insert cell
Insert cell
canvas = {
const height = 720;
const canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
canvas.style = `width: ${width}px; height: ${height}px`;
return canvas;
}
Insert cell
{
const t1 = newTexture(seed);
const t2 = newTexture();
const fb1 = newFramebuffer(t1);
const fb2 = newFramebuffer(t2);
for (let i = 0; true; ++i) {
gl.useProgram(timestepProgram);
gl.viewport(0, 0, W, H);
for (let j = 0; j < 32; ++j) {
gl.bindTexture(gl.TEXTURE_2D, t1);
gl.bindFramebuffer(gl.FRAMEBUFFER, fb2);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
gl.bindTexture(gl.TEXTURE_2D, t2);
gl.bindFramebuffer(gl.FRAMEBUFFER, fb1);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}
gl.useProgram(renderProgram);
gl.viewport(0, 0, canvas.width, canvas.height);
gl.bindTexture(gl.TEXTURE_2D, t2);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
yield i;
}
}
Insert cell
Insert cell
W = 512
Insert cell
H = 512
Insert cell
seed = {
restart;
const a = new Float32Array(4 * W * H);

for (let y = 0; y < H; ++y) {
for (let x = 0; x < W; ++x) {
const i = W * y + x << 2;
a[i + 0] = -0.3;
}
}

function dot(cx, cy, r = 5) {
const r2 = r ** 2;
for (let y = cy - r; y < cy + r; ++y) {
for (let x = cx - r; x < cx + r; ++x) {
if ((x - cx) ** 2 + (y - cy) ** 2 < r2) {
const i = W * y + x << 2;
a[i + 0] = 0.6;
}
}
}
}

for (let i = 0; i < 2; ++i) {
dot(W * Math.random(), H * Math.random());
}

return a;
}
Insert cell
function newTexture(source) {
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, W, H, 0, gl.RGBA, gl.FLOAT, source);
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);
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
Insert cell
timestepShader = newShader(gl.FRAGMENT_SHADER, `
precision mediump float;
uniform sampler2D u_image;
uniform vec2 u_size;

// Parameters from Ready: Patterns > FitzHugh-Nagomo > ising_regime.vti
const float A0 = -0.01;
const float A1 = 2.0;
const float EPSILON = 0.1;
const float DELTA = 7.0;
const float TIMESTEP = 0.03;
const float SCALE = 1.0;

void main() {
vec2 position = gl_FragCoord.xy;

vec2 value = texture2D(u_image, position / u_size).xy;

vec2 laplacian = - 4.0 * value
+ texture2D(u_image, (position + vec2(0.0, 1.0)) / u_size).xy
+ texture2D(u_image, (position + vec2(1.0, 0.0)) / u_size).xy
+ texture2D(u_image, (position + vec2(0.0, -1.0)) / u_size).xy
+ texture2D(u_image, (position + vec2(-1.0, 0.0)) / u_size).xy;

vec2 delta = vec2(
value.x - value.x * value.x * value.x - value.y + laplacian.x * SCALE,
EPSILON * (value.x - A1 * value.y - A0) + DELTA * laplacian.y * SCALE
);

gl_FragColor = vec4(value + delta * TIMESTEP, 0.0, 0.0);
}`)
Insert cell
renderShader = newShader(gl.FRAGMENT_SHADER, `
precision mediump float;
uniform sampler2D u_image;
uniform vec2 u_size;

void main() {
float c = texture2D(u_image, gl_FragCoord.xy / u_size).y;
float v = smoothstep(-0.10, -0.05, c);
gl_FragColor = vec4(0.0, 0.0, 0.0, v);
}`)
Insert cell
vertexShader = newShader(gl.VERTEX_SHADER, `
attribute vec2 a_position;

void main() {
gl_Position = vec4(a_position, 0.0, 1.0);
}`)
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("cannot compile shader");
return shader;
}
Insert cell
Insert cell
renderProgram = newProgram(vertexShader, renderShader)
Insert cell
timestepProgram = newProgram(vertexShader, timestepShader)
Insert cell
function newProgram(vertexShader, 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");
const a_position = gl.getAttribLocation(program, "a_position");
const u_size = gl.getUniformLocation(program, "u_size");
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);
gl.enableVertexAttribArray(a_position);
gl.vertexAttribPointer(a_position, 2, gl.FLOAT, false, 0, 0);
gl.uniform2f(u_size, W, H);
return program;
}
Insert cell
gl = {
const gl = canvas.getContext("webgl");
if (!gl) throw new Error("WebGL unsupported");
if (!gl.getExtension("OES_texture_float")) throw new Error("OES_texture_float unsupported");
if (gl.getParameter(gl.MAX_TEXTURE_SIZE) < W) throw new Error("big textures unsupported");
return gl;
}
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more