This system is described in the paper From Labyrinthine Patterns to Spiral Turbulence, Aric Hagberg and Ehud Meron, 1994. See Figure 3.
Last active
August 29, 2015 14:03
-
-
Save robinhouston/f2592c6b2e779b26f60d to your computer and use it in GitHub Desktop.
FitzHugh-Nagomo maze generator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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