var lbViscosity = 0.0002;
var ENTROPY_CUTOFF = 0.001;
const PARTICLE_COUNT = Math.pow(1600, 2);
const PARTICLE_COUNT_SQRT = Math.sqrt(PARTICLE_COUNT);
const PARTICLE_DATA_SLOTS = 2;
const PARTICLE_DATA_WIDTH = PARTICLE_COUNT_SQRT * PARTICLE_DATA_SLOTS;
const PARTICLE_DATA_HEIGHT = PARTICLE_COUNT_SQRT;
const PARTICLE_EMIT_RATE = 1000;
name: 'ReactionDiffusion',
let physicsOutputTexture;
const createShader = (source, type) => {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
throw gl.getShaderInfoLog(shader);
const createProgram = (vSource, fSource) => {
const vs = createShader(vSource, gl.VERTEX_SHADER);
const fs = createShader(fSource, gl.FRAGMENT_SHADER);
const program = gl.createProgram();
gl.attachShader(program, vs);
gl.attachShader(program, fs);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
throw gl.getProgramInfoLog(program);
const createImageTexture = (image) => {
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
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.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
const imgupdate = () => {
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.generateMipmap(gl.TEXTURE_2D);
image.naturalWidth > 0 ? imgupdate() : image.onload = imgupdate;
const updateImageTexture = (texture,image) => {
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.generateMipmap(gl.TEXTURE_2D);
const createDataTexture = (width, height, data) => {
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
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.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA32F, width, height, 0, gl.RGBA, gl.FLOAT, data);
const createFramebuffer = () => {
const buffer = gl.createFramebuffer();
const random = (min, max) => {
if (typeof min !== 'number') min = 1;
if (typeof max !== 'number') max = min, min = 0;
return min + Math.random() * (max - min);
const createPhysicsProgram = () => {
const program = createProgram(physicsVS, physicsFS);
program.vertexPosition = gl.getAttribLocation(program, 'vertexPosition');
program.physicsData = gl.getUniformLocation(program, 'physicsData');
program.inf_copy = gl.getUniformLocation(program, 'inf_copy');
program.barrier = gl.getUniformLocation(program, 'barrier');
program.bounds = gl.getUniformLocation(program, 'bounds');
program.TM = gl.getUniformLocation(program, 'TM');
program.UM = gl.getUniformLocation(program, 'UM');
program.default_u = gl.getUniformLocation(program, 'default_u');
program.lbIterPerFrame = gl.getUniformLocation(program,"lbIterPerFrame");
gl.enableVertexAttribArray(program.vertexPosition);
const createRenderProgram = () => {
const program = createProgram(renderVS, renderFS);
program.dataLocation = gl.getAttribLocation(program, 'dataLocation');
program.physicsData = gl.getUniformLocation(program, 'physicsData');
gl.enableVertexAttribArray(program.dataLocation);
const createDebugProgram = () => {
const program = createProgram(debugVS, debugFS);
program.vertexPosition = gl.getAttribLocation(program, 'vertexPosition');
program.texture = gl.getUniformLocation(program, 'texture');
gl.enableVertexAttribArray(program.vertexPosition);
const createCopyProgram = () => {
const program = createProgram(copyVS, copyFS);
program.vertexPosition = gl.getAttribLocation(program, 'vertexPosition');
program.texture = gl.getUniformLocation(program, 'texture');
gl.enableVertexAttribArray(program.vertexPosition);
const createPhysicsDataTexture = () => {
const size = 4 * PARTICLE_COUNT * PARTICLE_DATA_SLOTS;
const data = new Float32Array(size);
return createDataTexture(PARTICLE_DATA_WIDTH, PARTICLE_DATA_HEIGHT, data);
const createVideoTexture = () => {
return createImageTexture(cam.elt);
const createDataLocationBuffer = () => {
const data = new Float32Array(PARTICLE_COUNT * 2);
const step = 1 / PARTICLE_COUNT_SQRT;
for (let u, v, i = 0; i < PARTICLE_COUNT; i++) {
data[u] = step * Math.floor(i % PARTICLE_COUNT_SQRT);
data[v] = step * Math.floor(i / PARTICLE_COUNT_SQRT);
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
const createViewportQuadBuffer = () => {
const data = new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]);
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
const oldemitParticles = (count, origin, velocities = [0, 0, 0]) => {
gl.bindTexture(gl.TEXTURE_2D, physicsInputTexture);
const x = Math.floor((emitIndex * PARTICLE_DATA_SLOTS) % PARTICLE_DATA_WIDTH);
const y = Math.floor(emitIndex / PARTICLE_DATA_HEIGHT);
const chunks = [[x, y, count * PARTICLE_DATA_SLOTS]];
const split = (chunk) => {
const boundary = chunk[0] + chunk[2];
if (boundary > PARTICLE_DATA_WIDTH) {
const delta = boundary - PARTICLE_DATA_WIDTH;
chunk = [0, (chunk[1] + 1) % PARTICLE_DATA_HEIGHT, delta];
let i, j, n, m, chunk, data, force = 1.0;
for (i = 0, n = chunks.length; i < n; i++) {
for (j = 0, m = chunk[2]; j < m; j++) {
origin[0] + random(-0.02, 0.02),
origin[1] + random(-0.02, 0.02),
velocities[0] + force * random(-1, 1),
velocities[1] + force * random(-1, 1),
gl.TEXTURE_2D, 0, chunk[0], chunk[1], chunk[2], 1,
gl.RGBA, gl.FLOAT, new Float32Array(data)
emitIndex %= PARTICLE_COUNT;
const emitParticles = (count, origin, velocities = [0, 0, 0]) => {
gl.bindTexture(gl.TEXTURE_2D, physicsInputTexture);
const x = Math.floor((emitIndex * PARTICLE_DATA_SLOTS) % PARTICLE_DATA_WIDTH);
const y = Math.floor(emitIndex / PARTICLE_DATA_HEIGHT);
const chunks = [[x, y, count * PARTICLE_DATA_SLOTS]];
const split = (chunk) => {
const boundary = chunk[0] + chunk[2];
if (boundary > PARTICLE_DATA_WIDTH) {
const delta = boundary - PARTICLE_DATA_WIDTH;
chunk = [0, (chunk[1] + 1) % PARTICLE_DATA_HEIGHT, delta];
let i, j, n, m, chunk, data, force = 1.0;
for (i = 0, n = chunks.length; i < n; i++) {
for (j = 0, m = chunk[2]; j < m; j++) {
origin[0] + random(-0.5, 0.5),
origin[1] + random(-0.5, 0.5),
gl.TEXTURE_2D, 0, chunk[0], chunk[1], chunk[2], 1,
gl.RGBA, gl.FLOAT, new Float32Array(data)
emitIndex %= PARTICLE_COUNT;
if (typeof Leap !== 'undefined') {
const fingers = frame.pointables;
for (let i = 0, n = fingers.length; i < n; i++) {
const { tipPosition, tipVelocity } = fingers[i];
const count = random(110, 200);
(tipPosition.y / 200) - 1
(tipPosition.z / 400) * -1
emitParticles(count, position, velocity);
const particle_init = () => {
const particle_setup = () => {
physicsInputTexture = createPhysicsDataTexture();
physicsOutputTexture = createPhysicsDataTexture();
dataLocationBuffer = createDataLocationBuffer();
viewportQuadBuffer = createViewportQuadBuffer();
physicsProgram = createPhysicsProgram();
renderProgram = createRenderProgram();
debugProgram = createDebugProgram();
copyProgram = createCopyProgram();
frameBuffer = createFramebuffer();
video.src = createVideoTexture();
gl.useProgram(physicsProgram);
gl.viewport(0, 0, PARTICLE_DATA_WIDTH, PARTICLE_DATA_HEIGHT);
gl.bindBuffer(gl.ARRAY_BUFFER, viewportQuadBuffer);
gl.vertexAttribPointer(physicsProgram.vertexPosition, 2, gl.FLOAT, gl.FALSE, 0, 0);
gl.uniform2f(physicsProgram.bounds, PARTICLE_DATA_WIDTH, PARTICLE_DATA_HEIGHT);
gl.uniform2f(physicsProgram.TM, 1.0/gr_w, 1.0/gr_h);
gl.uniform2f(physicsProgram.UM, 1.0/comp_w, 1.0/comp_h);
gl.uniform1f(physicsProgram.lbIterPerFrame, lbIterPerFrame);
gl.uniform2f(physicsProgram.default_u, default_ux,default_uy);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, physicsInputTexture);
gl.uniform1i(physicsProgram.physicsData, 0);
gl.activeTexture(gl.TEXTURE0+1);
gl.bindTexture(gl.TEXTURE_2D, inf_copy.src);
gl.uniform1i(physicsProgram.inf_copy, 1);
gl.activeTexture(gl.TEXTURE0+2);
gl.bindTexture(gl.TEXTURE_2D, barrier.src);
gl.uniform1i(physicsProgram.barrier, 2);
gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, physicsOutputTexture, 0);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.useProgram(copyProgram);
gl.viewport(0, 0, PARTICLE_DATA_WIDTH, PARTICLE_DATA_HEIGHT);
gl.bindBuffer(gl.ARRAY_BUFFER, viewportQuadBuffer);
gl.vertexAttribPointer(copyProgram.vertexPosition, 2, gl.FLOAT, gl.FALSE, 0, 0);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, physicsOutputTexture);
gl.uniform1i(copyProgram.physicsData, 0);
gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, physicsInputTexture, 0);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.useProgram(debugProgram);
gl.bindBuffer(gl.ARRAY_BUFFER, viewportQuadBuffer);
gl.vertexAttribPointer(physicsProgram.vertexPosition, 2, gl.FLOAT, gl.FALSE, 0, 0);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, physicsOutputTexture);
gl.uniform1i(debugProgram.texture, 0);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
gl.useProgram(renderProgram);
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
gl.bindBuffer(gl.ARRAY_BUFFER, dataLocationBuffer);
gl.vertexAttribPointer(renderProgram.dataLocation, 2, gl.FLOAT, gl.FALSE, 0, 0);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, physicsOutputTexture);
gl.uniform1i(renderProgram.physicsData, 0);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
gl.drawArrays(gl.POINTS, 0, PARTICLE_COUNT);
millis += now - clock || 0;
random()*2-1,random()*2-1,0
const touch = (event) => {
if (millis - lastEmit < 20) return;
const touches = event.changedTouches || [event];
const limit = PARTICLE_EMIT_RATE / touches.length;
for (let i = 0; i < touches.length; i++) {
const touch = touches[i];
const x = (touch.clientX / width) * 2 - 1;
const y = (touch.clientY / height) * -2 + 1;
emitParticles(limit, [x, y, 0]);
scale = window.devicePixelRatio || 1;
width = window.innerWidth;
height = window.innerHeight;
gl.canvas.width = width * scale;
gl.canvas.height = height * scale;
gl.canvas.style.width = width + 'px';
gl.canvas.style.height = height + 'px';
createCanvas(windowWidth, windowHeight, WEBGL);
gui.add(rdDef, 'da', 0, 1).listen();
gui.add(rdDef, 'db', 0, 1).listen();
gui.add(rdDef, 'feed', 0.01, 0.09).listen();
gui.add(rdDef, 'kill', 0.01, 0.09).listen();
gui.add(rdDef, 'dt', 0, 1);
gui.add(rdDef, 'iter', 1, 50);
gui.add(rdDef, 'preset0');
gui.add(rdDef, 'preset1');
gui.add(rdDef, 'preset2');
gui.add(rdDef, 'preset3');
gui.add(rdDef, 'preset4');
gui.add(rdDef, 'preset5');
gui.add(rdDef, 'preset6');
gui.add(rdDef, 'preset7');
var VERSION = gl.getVersion();
console.log("WebGL Version: " + VERSION);
gl.newExt(gl.getSupportedExtensions(), true);
fbo = gl.newFramebuffer();
filter: [gl.LINEAR, gl.LINEAR]
filter: [gl.NEAREST, gl.NEAREST]
filter: [gl.NEAREST, gl.NEAREST]
filter: [gl.LINEAR, gl.LINEAR]
filter: [gl.LINEAR, gl.LINEAR]
comp_w = ceil(gr_w / gr_to_comp);
comp_h = ceil(gr_h / gr_to_comp);
tex.src = gl.newTexture(comp_w * 3, comp_h * 3, def_tex);
tex.dst = gl.newTexture(comp_w * 3, comp_h * 3, def_tex);
dye.src = gl.newTexture(gr_w, gr_h, def_gr);
dye.dst = gl.newTexture(gr_w, gr_h, def_gr);
barrier.src = gl.newTexture(gr_w, gr_h, def_inf_copy);
barrier.dst = gl.newTexture(gr_w, gr_h, def_inf_copy);
info.src = gl.newTexture(comp_w, comp_h, def_info);
info.dst = gl.newTexture(comp_w, comp_h, def_info);
inf_copy.src = gl.newTexture(comp_w, comp_h, def_inf_copy);
inf_copy.dst = gl.newTexture(comp_w, comp_h, def_inf_copy);
var shaderlist = ['init', 'init_dye', 'init_barrier', 'stream', 'limiter', 'collide',
'forcing', 'relax', 'display', 'info', 'inf_copy', 'advect_dye', 'video','barrier'
shaderlist.forEach((fs_basename) => {
console.log('shader: ' + fs_basename);
let fs_shader = shader_common + shaderfiles["webgl" + VERSION + ".fs_" + fs_basename];
shaderprog[fs_basename] = new Shader(gl, {
cam = createCapture(VIDEO);
cam.size(video_w, video_h);
video.src = createVideoTexture();
function windowResized() {
var tex_w = ceil(w * SCREEN_SCALE);
var tex_h = ceil(h * SCREEN_SCALE);
tex.src.resize(tex_w, tex_h);
tex.dst.resize(tex_w, tex_h);
function randomizeColors() {
var num = pallette.length / 3;
for (var i = 1; i < num - 1; i++) {
function LBstep(textr, shader, xuniforms, xblend) {
gl.clearColor(1.0, 0.0, 0.0, 0.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.disable(gl.DEPTH_TEST);
shader.uniformF("mousex", mouseX / ceil(windowWidth));
shader.uniformF("mousey", mouseY / ceil(windowHeight));
shader.uniformF("timer", Date.now() / 1000.0 - timeStarted);
shader.uniformF("default_u", [default_ux, default_uy]);
shader.uniformF("lbViscosity", lbViscosity);
shader.uniformF("lbIterPerFrame", lbIterPerFrame);
shader.uniformF("TM", [1.0 / gr_w, 1.0 / gr_h]);
shader.uniformF("LM", [1.0 / (comp_w * 3), 1.0 / (comp_h * 3)]);
shader.uniformF("UM", [1.0 / comp_w, 1.0 / comp_h]);
shader.uniformT("tex", tex.src);
shader.uniformT("dye", dye.src);
shader.uniformT("barrier", barrier.src);
shader.uniformT("info", info.src);
shader.uniformT("inf_copy", inf_copy.src);
shader.uniformT("video", video.src);
var lasttime = Date.now();
updateImageTexture(video.src,cam.elt);
translate(-width / 2, -height / 2, 0);
LBstep(0, shaderprog.display);
if (fct % 50 == 0) console.log(fct + " FPS=" + int(100000. / (dn - lasttime)) / 100.);
console.log("about to initialize.");
LBstep(barrier, shaderprog.init_barrier);
LBstep(info, shaderprog.init);
LBstep(inf_copy, shaderprog.init);
LBstep(tex, shaderprog.relax);
LBstep(dye, shaderprog.init_dye);
timeStarted = Date.now() / 1000.0;
console.log("done initializing.");
function updateTexture(gl, texture, videop) {
const internalFormat = gl.RGBA;
const srcFormat = gl.RGBA;
const srcType = gl.UNSIGNED_BYTE;
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, level, video_w, video_h, 0, internalFormat,
srcFormat, srcType, videop);
for (var i = 0; i < lbIterPerFrame; i++) {
LBstep(tex, shaderprog.stream);
LBstep(info, shaderprog.info);
LBstep(info, shaderprog.forcing);
LBstep(tex, shaderprog.collide);
LBstep(info, shaderprog.info);
LBstep(tex, shaderprog.limiter);
LBstep(info, shaderprog.info);
LBstep(inf_copy, shaderprog.inf_copy);
LBstep(barrier, shaderprog.barrier);
LBstep(dye, shaderprog.advect_dye);
var loadJS = function(filename) {
var script = document.createElement("script");
script.setAttribute("type", "text/javascript");
script.setAttribute("src", filename);
document.getElementsByTagName("head")[0].appendChild(script);
loadJS("https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.6.5/dat.gui.min.js");
document.oncontextmenu = function() {
document.onmousedown = function() {