let oldPosVel, newPosVel;
let gridWide, gridHigh, grid;
let collShdr, showGridShdr, updateShdr, drawShdr;
let s = min( windowWidth, windowHeight );
createCanvas(s, s, WEBGL);
collDist = 0.75 * sqrt(4.0/N);
gridWide = gridHigh = int( 4.0/collDist );
collShdr = createShader( vsCollision, fsCollision );
showGridShdr = createShader( vsUpdate, fsShowGrid );
updateShdr = createShader( vsUpdate, fsUpdate );
drawShdr = createShader( vsDraw, fsDraw );
fbHigh = ceil( N / fbWide );
let fbOptions = { format: FLOAT,
depth: false, antialias: false, density: 1,
width: fbWide, height: fbHigh };
oldPosVel = createFramebuffer( fbOptions );
newPosVel = createFramebuffer( fbOptions );
for( let i=0; i<N/2; i++ ) {
let x = random( -1, 1-collDist );
oldPosVel.pixels[8*i ] = x;
oldPosVel.pixels[8*i+1] = y;
oldPosVel.pixels[8*i+4] = x+collDist;
oldPosVel.pixels[8*i+5] = y;
let spd = 0.005 * random(0.5, 1.0);
oldPosVel.pixels[8*i+2] = spd * cos(ang);
oldPosVel.pixels[8*i+3] = spd * sin(ang);
oldPosVel.pixels[8*i+6] = spd * cos(ang);
oldPosVel.pixels[8*i+7] = spd * sin(ang);
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
oldPosVel.updatePixels();
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
fbOptions = { format: UNSIGNED_BYTE,
depth: false, antialias: false, density: 1,
width: gridWide, height: gridHigh };
grid = createFramebuffer( fbOptions );
function windowResized() {
let canvasSize = min( windowWidth, windowHeight );
resizeCanvas( canvasSize, canvasSize );
collShdr.setUniform( 'data', oldPosVel );
collShdr.setUniform( 'outRes', [gridWide, gridHigh] );
gl.drawArrays( gl.POINTS, 0, N );
collShdr.unbindTextures();
updateShdr.setUniform( 'grid', grid );
updateShdr.setUniform( 'dataOld', oldPosVel );
updateShdr.setUniform( 'collDist', collDist );
updateShdr.setUniform( 'time', t );
updateShdr.bindTextures();
gl.drawArrays( gl.TRIANGLES, 0, 3 );
updateShdr.unbindTextures();
updateShdr.unbindShader();
scale( min(height, width) / 2.0 );
gl.disable( gl.DEPTH_TEST );
gl.blendEquation( gl.MAX );
drawShdr.setUniform( 'dataOld', oldPosVel );
drawShdr.setUniform( 'dataNew', newPosVel );
drawShdr.setUniform( 'N', N );
gl.drawArrays( gl.LINES, 0, N );
drawShdr.unbindTextures();
let vsCollision = `#version 300 es
ivec2 res = textureSize( data, 0 );
ivec2 ij = ivec2( idx%res.x, idx/res.x );
vec2 pos = texelFetch( data, ij, 0 ).xy;
gl_Position = vec4( floor((pos+1.)*0.5*outRes+1.)/outRes*2.-1., 0., 1.);
vColor = vec4( (idx>>16)&0xff, (idx>>8)&0xff, idx&0xff, 255 ) / 255.0;
let fsCollision = `#version 300 es
let fsShowGrid = `#version 300 es
vec2 uv = gl_FragCoord.xy/outRes;
fragColor = texelFetch(grid, ivec2(uv*vec2(textureSize(grid,0))), 0);
let vsUpdate = `#version 300 es
gl_Position = vec4( 4*ivec2(gl_VertexID&1, gl_VertexID&2)-1, 0., 1. );
let fsUpdate = `#version 300 es
uniform sampler2D dataOld;
ivec2 dataRes = textureSize( dataOld, 0 );
int idx = int(gl_FragCoord.y) * dataRes.x + int(gl_FragCoord.x);
vec4 posVel = texelFetch( dataOld, ivec2(gl_FragCoord.xy), 0 );
// push off of nearby particles except for yourself and your pair
ivec2 gridRes = textureSize( grid, 0 );
ivec2 minij = max( ivec2(floor( ((pos-collDist)*0.5+0.5)*vec2(gridRes) )), 0 );
ivec2 maxij = min( ivec2(floor( ((pos+collDist)*0.5+0.5)*vec2(gridRes) )), gridRes-1 );
for( int j=minij.y; j<=maxij.y; j++ ) {
for( int i=minij.x; i<=maxij.x; i++ ) {
ivec4 other = ivec4( texelFetch( grid, ivec2(i,j), 0 ) * 255.5 );
int oidx = (other.r << 16) + (other.g << 8) + other.b;
vec4 otherPosVel = texelFetch( dataOld, ivec2( oidx%dataRes.x, oidx/dataRes.x), 0 );
vec2 v = pos - otherPosVel.xy;
if( d < collDist && d > 0.00001 ) {
vel += 0.01 * (1.0-d/collDist) * normalize(v);
// stay stuck to your pair
vec4 otherPosVel = texelFetch( dataOld, ivec2( oidx%dataRes.x, oidx/dataRes.x), 0 );
vec2 v = pos - otherPosVel.xy;
vel += 0.01 * (1.0-d/collDist) * normalize(v);
// a wiggling force to mix things up
float a = atan( pos.y, pos.x );
vel += 0.00015 * sin(TAU*2.*sin(0.25*time)*r + 3.*(a-0.2*time)) * vec2( sin(a), -cos(a));
if( spd > 0.0015 ) vel *= 0.0015 / spd;
// apply the new velocity
// bounce off the walls to stay on the collision grid
if( pos.x < -1. ) { pos.x = -2. - pos.x; vel.x *= -1.; }
if( pos.x > 1. ) { pos.x = 2. - pos.x; vel.x *= -1.; }
if( pos.y < -1. ) { pos.y = -2. - pos.y; vel.y *= -1.; }
if( pos.y > 1. ) { pos.y = 2. - pos.y; vel.y *= -1.; }
fragColor = vec4( pos, vel );
let vsDraw = `#version 300 es
uniform mat4 uModelViewMatrix;
uniform mat4 uProjectionMatrix;
uniform sampler2D dataOld;
uniform sampler2D dataNew;
vec3 hsb2rgb( in vec3 c ) {
vec3 rgb = clamp(abs(mod(c.x*6.0+vec3(0.0,4.0,2.0),
6.0)-3.0)-1.0, 0.0, 1.0 );
rgb = rgb*rgb*(3.0-2.0*rgb);
return c.z * mix( vec3(1.0), rgb, c.y);
ivec2 res = textureSize( dataOld, 0 );
ivec2 ij = ivec2( idx%res.x, idx/res.x );
//vec2 p0 = texelFetch( dataOld, ij, 0 ).xy;
vec2 p1 = texelFetch( dataNew, ij, 0 ).xy;
vec4 p = vec4( p1, 0., 1. ); //vec4( (((gl_VertexID & 1) > 0 ) ? p0 : p1), 0., 1. );
p = uModelViewMatrix * p;
gl_Position = uProjectionMatrix * p;
float u = float(idx)/float(N);
vec3 c = hsb2rgb( vec3( u, 0.6, 1. ) );
let fsDraw = `#version 300 es