Skip to content

Instantly share code, notes, and snippets.

@robinhouston
Last active August 29, 2015 14:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save robinhouston/f2592c6b2e779b26f60d to your computer and use it in GitHub Desktop.
Save robinhouston/f2592c6b2e779b26f60d to your computer and use it in GitHub Desktop.
FitzHugh-Nagomo maze generator
<!DOCTYPE html>
<meta charset="utf-8">
<title>FitzHugh-Nagomo maze using WebGL</title>
<style>
body { font-family: sans-serif; }
canvas { display: block; margin: auto; width: 1024px; height: 512px; border: 10px solid #000; }
#fail { color: red; white-space: pre-wrap; max-width: 600px; }
</style>
<script type="x-webgl/x-vertex-shader" id="vertex-shader">
attribute vec2 a_position;
void main() {
gl_Position = vec4(a_position, 0, 1);
}
</script>
<script type="x-webgl/x-fragment-shader" id="timestep-shader">
precision mediump float;
uniform sampler2D u_image;
uniform vec2 u_size;
const float // Parameters from Ready: Patterns > FitzHugh-Nagomo > ising_regime.vti
A0 = -0.01,
A1 = 2.0,
EPSILON = 0.1,
DELTA = 7.0;
const float TIMESTEP = 0.03,
SCALE = 1.0;
void main() {
vec2 p = gl_FragCoord.xy,
n = p + vec2(0.0, 1.0),
e = p + vec2(1.0, 0.0),
s = p + vec2(0.0, -1.0),
w = p + vec2(-1.0, 0.0);
vec2 val = texture2D(u_image, p / u_size).xy,
laplacian = texture2D(u_image, n / u_size).xy
+ texture2D(u_image, s / u_size).xy
+ texture2D(u_image, e / u_size).xy
+ texture2D(u_image, w / u_size).xy
- 4.0 * val;
vec2 delta = vec2(val.x - val.x*val.x*val.x - val.y + laplacian.x * SCALE,
EPSILON*(val.x - A1*val.y - A0) + DELTA*laplacian.y * SCALE);
gl_FragColor = vec4(val + delta * TIMESTEP, 0, 0);
}
</script>
<script type="x-webgl/x-fragment-shader" id="render-shader">
precision mediump float;
uniform sampler2D u_image;
uniform vec2 u_size;
const float COLOR_MIN = -0.10, COLOR_MAX = -0.05;
void main() {
float c = texture2D(u_image, gl_FragCoord.xy / u_size).y,
v = max(0.0, min(1.0, (c - COLOR_MIN) / (COLOR_MAX - COLOR_MIN)));
gl_FragColor = vec4(v, v, v, 1);
}
</script>
<body>
<script>
var W = 512, H = 256;
function init() {
if (window.parent && window.parent != window) {
var iframe = window.parent.document.querySelector("iframe");
iframe.style.width = "1044px";
iframe.style.height = "532px";
}
var canvas = document.createElement("canvas");
canvas.id = "canvas";
canvas.width = W;
canvas.height = H;
document.body.appendChild(canvas);
var gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
checkCompatibility(gl);
var vertex_shader = createShader(gl, gl.VERTEX_SHADER, "vertex-shader"),
timestep_shader = createShader(gl, gl.FRAGMENT_SHADER, "timestep-shader"),
render_shader = createShader(gl, gl.FRAGMENT_SHADER, "render-shader");
var timestep_prog = createAndLinkProgram(gl, vertex_shader, timestep_shader),
render_prog = createAndLinkProgram(gl, vertex_shader, render_shader);
gl.useProgram(render_prog);
loadVertexData(gl, render_prog);
gl.uniform2f(gl.getUniformLocation(render_prog, "u_size"), W, H);
gl.useProgram(timestep_prog);
loadVertexData(gl, timestep_prog);
gl.uniform2f(gl.getUniformLocation(timestep_prog, "u_size"), W, H);
var initial_state = getInitialState();
var t1 = newTexture(gl, initial_state),
t2 = newTexture(gl, null),
fb1 = newFramebuffer(gl, t1),
fb2 = newFramebuffer(gl, t2);
// Check the hardware can render to a float framebuffer
// (https://developer.mozilla.org/en-US/docs/Web/WebGL/WebGL_best_practices)
gl.useProgram(timestep_prog);
gl.bindFramebuffer(gl.FRAMEBUFFER, fb1);
var fb_status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
if (fb_status != gl.FRAMEBUFFER_COMPLETE) {
fail("Cannot render to framebuffer: " + fb_status);
}
function step() {
gl.useProgram(timestep_prog);
for (var i=0; i<80; i++) {
gl.bindTexture(gl.TEXTURE_2D, [t1, t2][i % 2]);
gl.bindFramebuffer(gl.FRAMEBUFFER, [fb2, fb1][i % 2]);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}
gl.useProgram(render_prog);
gl.bindTexture(gl.TEXTURE_2D, t1);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}
var af;
function frame() {
step();
af = requestAnimationFrame(frame);
}
af = requestAnimationFrame(frame);
}
function getInitialState() {
var a = new Float32Array(4 * W * H);
for (var y=0; y<H; y++) {
for (var x=0; x<W; x++) {
var i = W*y + x,
rx = (x - W/2),
ry = (y - H/2),
dd = Math.sqrt(rx*rx + ry*ry),
in_stripe = dd > 50 && dd < 55 && ry < 20;
if (in_stripe) {
a[4*i + 0] = 0.99;
} else {
a[4*i + 0] = -0.7;
}
a[4*i + 1] = -0.3;
}
}
return a;
}
// Create, initialise, and bind a new texture
function newTexture(gl, initial_state) {
var 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.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, W, H, 0, gl.RGBA, gl.FLOAT, initial_state);
return texture;
}
function newFramebuffer(gl, texture) {
var fb = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
return fb;
}
function loadVertexData(gl, prog) {
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);
var a_position = gl.getAttribLocation(prog, "a_position");
gl.enableVertexAttribArray(a_position);
gl.vertexAttribPointer(a_position, 2, gl.FLOAT, false, 0, 0);
}
function createAndLinkProgram(gl, vertex_shader, fragment_shader) {
var prog = gl.createProgram();
gl.attachShader(prog, vertex_shader);
gl.attachShader(prog, fragment_shader);
gl.linkProgram(prog);
if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {
fail("Failed to link program: " + gl.getProgramInfoLog(prog));
}
return prog;
}
function createShader(gl, shader_type, shader_code_id) {
var shader = gl.createShader(shader_type);
gl.shaderSource(shader, document.getElementById(shader_code_id).text);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
var err = gl.getShaderInfoLog(shader);
fail("Failed to compile shader: " + err);
}
return shader
}
function checkCompatibility(gl) {
if (!gl) fail("WebGL is not supported");
var float_texture_ext = gl.getExtension("OES_texture_float");
if (!float_texture_ext) fail("Your browser does not support the WebGL extension OES_texture_float");
window.float_texture_ext = float_texture_ext; // Hold onto it
var max_texture_size = gl.getParameter(gl.MAX_TEXTURE_SIZE);
if (max_texture_size < 512) fail("Your browser only supports "+max_texture_size+"×"+max_texture_size+" WebGL textures");
}
function fail(message) {
var fail = document.createElement("p");
fail.id = "fail";
fail.appendChild(document.createTextNode(message));
document.body.removeChild(document.getElementById("canvas"));
document.body.appendChild(fail);
throw message;
}
init();
</script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment