UNPKG

pathgl

Version:

A webgl renderer for data visualization, motion graphics and explorable explanations.

144 lines (128 loc) 4.16 kB
var simulation_vs = [ 'precision mediump float;' , 'attribute vec2 pos;' , ' void main() {' , ' gl_Position = vec4( pos.xy, 1.0 , 1.0);' , ' }' ].join('\n') var forceShader = [ , 'precision mediump float;' , 'uniform sampler2D texture;' , 'uniform vec2 resolution;' , 'uniform vec2 mouse;' , 'uniform vec2 dimensions;' , 'vec4 texelAtOffet( vec2 offset ) { ' + 'return texture2D(texture, (gl_FragCoord.xy + offset) / dimensions); }' , 'void main() {' , 'vec3 TARGET = vec3(mouse / resolution, 0.01 );' , 'int slot = int( mod( gl_FragCoord.x, 2.0 ) );' , 'if ( slot == 0 ) { ' , 'vec4 dataA = texelAtOffet( vec2( 0, 0 ) );' , 'vec4 dataB = texelAtOffet( vec2( 1, 0 ) );' , 'vec3 pos = dataA.xyz;' , 'vec3 vel = dataB.xyz;' , 'float phase = dataA.w;' , 'if ( phase > 0.0 ) {' , 'pos += vel * 0.005;' , 'if ( length( TARGET - pos ) < 0.035 ) phase = 0.0;' , ' else phase += 0.1;' , '} else {' , ' pos = vec3(-1);' , '}' , ' gl_FragColor = vec4( pos, phase );' , '} else if ( slot == 1 ) { ' , 'vec4 dataA = texelAtOffet( vec2( -1, 0 ) );' , 'vec4 dataB = texelAtOffet( vec2( 0, 0 ) );' , 'vec3 pos = dataA.xyz;' , 'vec3 vel = dataB.xyz;' , 'float phase = dataA.w;' , 'if ( phase > 0.0 ) {' , 'vec3 delta = normalize( TARGET - pos );' , 'vel += delta * 0.1;' , ' vel *= 0.991;' , '} else {' , ' vel = vec3(0);' , '}' , ' gl_FragColor = vec4( vel, 1.0 );' , ' }' , '}' ].join('\n') var since = Date.now() pathgl.sim.force = function (size) { var particleData = new Float32Array(2 * 4 * size) var width = Math.sqrt(size) * 2 var height = Math.sqrt(size) var elapsed = 0, cooldown = 16 var rate = 500 var particleIndex = 0 var gl, texture var mousemove = mousemove.bind(this) return pathgl.texture(forceShader, { step: step , data: particleData , width: width , height: height , size: size , start: start , emit: mousemove }) function step () { } function start () { var now = Date.now() - since , origin = [ -1.0 + Math.sin(now * 0.001) * 2.0 , -0.2 + Math.cos(now * 0.004) * 0.5 , Math.sin(now * 0.015) * -0.05 ] pathgl.uniform('dimensions', [width, height]) emit(gl = this.gl, texture = this.texture, 40000, origin) } function mousemove() { var count = rate * Math.random() , origin = svgToClipSpace(d3.mouse(d3.select('canvas').node())).concat(0) emit(gl, texture, count, origin) } function emit(gl, tex, count, origin, velocities) { velocities = velocities || { x:0, y:0, z:0 } gl.activeTexture( gl.TEXTURE0 + tex.unit) gl.bindTexture(gl.TEXTURE_2D, tex) var x = ~~(( particleIndex * 2) % width) var y = ~~(particleIndex / height) var chunks = [{ x: x, y: y, size: count * 2 }] function split( chunk ) { var boundary = chunk.x + chunk.size; if (boundary > width) { var delta = boundary - width chunk.size -= delta; chunk = { x: 0, y: ( chunk.y + 1 ) % height, size: delta } chunks.push(chunk) split(chunk) } } split(chunks[0]) var i, j, n, m, chunk, data, force = 1.0; for (i = 0, n = chunks.length; i < n; i++) { chunk = chunks[i] data = [] for (j = 0, m = chunk.size; j < m; j++) { data.push( origin[0], origin[1], 0, Math.random() * 10, velocities.x + force * random(-1.0, 1.0), velocities.y + force * random(-1.0, 1.0), velocities.z + force * random(-1.0, 1.0), 0 ) } gl.texSubImage2D(gl.TEXTURE_2D, 0, chunk.x, chunk.y, chunk.size, 1, gl.RGBA, gl.FLOAT, new Float32Array(data)) } particleIndex += count particleIndex %= size } } function random (min, max) { return Math.random() * ( max - min ); } function svgToClipSpace(pos) { return [2 * (pos[0] / 960) - 1, 1 - (pos[1] / 500 * 2)] }