|
function createInitialParticleBuffer(e){var t=regl.texture({data:e,shape:[sqrtNumParticles,sqrtNumParticles,4],type:"float"});return regl.framebuffer({color:t,depth:!1,stencil:!1})}function cycleParticleStates(){var e=prevParticleState;prevParticleState=currParticleState,currParticleState=nextParticleState,nextParticleState=e}function normalize(e){var t=Math.sqrt(Math.pow(e[0],2)+Math.pow(e[1],2));return e[0]/=t,e[1]/=t,e}function makeGridMesh(e,t){var n=d3.scaleLinear().domain([0,e-1]).range([-1,1]),r=d3.scaleLinear().domain([0,t-1]).range([1,-1]),i=d3.range(numFlowData).map(function(t){var i=t%e,a=Math.floor(t/e);return[n(i),r(a)]}),a=function(t,n){return t+n*e},o=[];return i.forEach(function(n,r){var i=r%e,l=Math.floor(r/e);if(i+1<e&&l+1<t){var c=[r,r+1,a(i,l+1)];o.push(c)}if(i+1<e&&l-1>=0){var s=[r,r+1,a(i+1,l-1)];o.push(s)}}),{positions:i,cells:o}}function generateFlowData(){d3.range(numFlowData).forEach(function(e){flowData[e]=normalize([2*Math.random()-1,2*Math.random()-1,3*Math.random()])})}var width=window.innerWidth,height=window.innerHeight,pointWidth=3,animationTickLimit=-1;animationTickLimit>=0&&console.log("Limiting to "+animationTickLimit+" ticks");var ticksPerFlow=130;console.log("Changing flow buffer every "+ticksPerFlow+" ticks");var sqrtNumParticles=256,numParticles=sqrtNumParticles*sqrtNumParticles;console.log("Using "+numParticles+" particles");for(var regl=createREGL({extensions:"OES_texture_float"}),initialParticleState=new Float32Array(4*numParticles),i=0;i<numParticles;++i)initialParticleState[4*i]=2*Math.random()-1,initialParticleState[4*i+1]=2*Math.random()-1,initialParticleState[4*i+2]=50+300*Math.random();for(var prevParticleState=createInitialParticleBuffer(initialParticleState),currParticleState=createInitialParticleBuffer(initialParticleState),nextParticleState=createInitialParticleBuffer(initialParticleState),particleTextureIndex=[],i$1=0;i$1<sqrtNumParticles;i$1++)for(var j=0;j<sqrtNumParticles;j++)particleTextureIndex.push(i$1/sqrtNumParticles,j/sqrtNumParticles);var sqrtFlowDataLength=4,numFlowData=sqrtFlowDataLength*sqrtFlowDataLength,flowData=[],gridMesh=makeGridMesh(sqrtFlowDataLength,sqrtFlowDataLength);generateFlowData();var upscaleAmount=16*sqrtFlowDataLength,flowBuffer=regl.framebuffer({color:regl.texture({data:new Float32Array(upscaleAmount*upscaleAmount*4),shape:[upscaleAmount,upscaleAmount,4],type:"float"}),depth:!1,stencil:!1}),generateFlowBuffer=regl({framebuffer:flowBuffer,vert:"\n precision mediump float;\n\n attribute vec2 position;\n attribute vec3 flowData;\n\n varying vec3 flow;\n\n void main() {\n flow = flowData;\n gl_Position = vec4(position, 0, 1);\n }",frag:"\n precision mediump float;\n\n varying vec3 flow;\n\n void main() {\n gl_FragColor = vec4(flow, 1);\n }",attributes:{position:gridMesh.positions,flowData:function(){return flowData}},elements:gridMesh.cells}),drawFlowBuffer=regl({vert:"\n\t// set the precision of floating point numbers\n precision mediump float;\n\n // vertex of the triangle\n attribute vec2 vertex;\n\n // index into the texture state\n varying vec2 flowIndex;\n\n void main() {\n \t// map bottom left -1,-1 (normalized device coords) to 0,0 (particle texture index)\n \t// and 1,1 (ndc) to 1,1 (texture)\n \tflowIndex = 0.5 * (1.0 + vertex);\n\n \tgl_Position = vec4(vertex, 0, 1);\n }\n\t",frag:"\n // set the precision of floating point numbers\n precision mediump float;\n\n uniform sampler2D flowBuffer;\n\n // index into the texture state\n varying vec2 flowIndex;\n\n void main() {\n vec3 flow = texture2D(flowBuffer, flowIndex).xyz;\n gl_FragColor = vec4(flow, 1);\n }\n ",attributes:{vertex:[-4,0,4,4,4,-4]},uniforms:{flowBuffer:flowBuffer},count:3}),updateParticles=regl({framebuffer:function(){return nextParticleState},vert:"\n\t// set the precision of floating point numbers\n precision mediump float;\n\n // vertex of the triangle\n attribute vec2 vertex;\n\n // index into the texture state\n varying vec2 particleTextureIndex;\n\tuniform sampler2D flowBuffer;\n\n void main() {\n \t// map bottom left -1,-1 (normalized device coords) to 0,0 (particle texture index)\n \t// and 1,1 (ndc) to 1,1 (texture)\n \tparticleTextureIndex = 0.5 * (1.0 + vertex);\n\n \tgl_Position = vec4(vertex, 0, 1);\n }\n\t",frag:"\n // set the precision of floating point numbers\n precision mediump float;\n\n // states to read from to get velocity\n uniform sampler2D currParticleState;\n uniform sampler2D prevParticleState;\n\n uniform sampler2D flowBuffer;\n uniform float tick;\n\n // index into the texture state\n varying vec2 particleTextureIndex;\n\n // seemingly standard 1-liner random function\n // http://stackoverflow.com/questions/4200224/random-noise-functions-for-glsl\n float rand(vec2 co){\n return fract(sin(dot(co.xy, vec2(12.9898, 78.233))) * 43758.5453);\n }\n\n void main() {\n vec3 particle = texture2D(currParticleState, particleTextureIndex).xyz;\n vec2 currPosition = particle.xy;\n float tickLifespan = particle[2];\n\n vec3 prevParticle = texture2D(prevParticleState, particleTextureIndex).xyz;\n vec2 prevPosition = prevParticle.xy;\n float prevTickLifespan = prevParticle[2];\n\n vec2 position;\n\n // respawn\n if (tickLifespan <= 0.0) {\n tickLifespan = 100.0 * rand(tick * particleTextureIndex) + 1.0;\n position = 2.0 * vec2(rand(tick * position), rand(tick * tickLifespan * position)) - 1.0;\n\n // update current position\n } else {\n\n // find flow based on current position\n vec2 flowIndex = 0.5 * (currPosition + 1.0);\n vec3 flow = texture2D(flowBuffer, flowIndex).xyz;\n float flowMagnitude = flow[2];\n\n vec2 velocity;\n\n // use velocity unless just respawned\n if (tickLifespan != prevTickLifespan - 1.0) {\n velocity = vec2(0.0);\n } else {\n velocity = currPosition - prevPosition;\n }\n\n vec2 random = 0.5 - vec2(rand(currPosition), rand(10.0 * currPosition));\n // random = vec2(0.0, 0.0);\n\n position = currPosition +\n (0.96 * velocity) +\n (0.001 * random) +\n (flow.xy * (flowMagnitude * 0.002));\n }\n\n // we store the new position as the color in this frame buffer\n // reduce the tick lifespan by 1\n gl_FragColor = vec4(position, tickLifespan - 1.0, 1);\n }\n ",attributes:{vertex:[-4,0,4,4,4,-4]},uniforms:{currParticleState:function(){return currParticleState},prevParticleState:function(){return prevParticleState},flowBuffer:flowBuffer,tick:function(e){var t=e.tick;return t}},count:3}),drawParticles=regl({vert:"\n\t// set the precision of floating point numbers\n precision mediump float;\n\n\tattribute vec2 particleTextureIndex;\n\tuniform sampler2D currParticleState;\n\tuniform sampler2D prevParticleState;\n\tuniform sampler2D flowBuffer;\n\n // variables to send to the fragment shader\n varying vec3 fragColor;\n\n // values that are the same for all vertices\n uniform float pointWidth;\n\n // get color based on particle speed\n vec3 getColor(vec3 currParticle, vec3 prevParticle) {\n \tvec2 currPosition = currParticle.xy;\n\t\tfloat tickLifespan = currParticle[2];\n\t\tvec2 prevPosition = prevParticle.xy;\n\t\tfloat prevTickLifespan = prevParticle[2];\n\n\t\tvec2 velocity;\n\n \t// use velocity unless just respawned\n\t\tif (tickLifespan != prevTickLifespan - 1.0) {\n\t\t\tvelocity = vec2(0.0);\n\t\t} else {\n\t\t\tvelocity = currPosition - prevPosition;\n\t\t}\n\n // color based on the speed (faster particles are brighter)\n\t\tfloat speed = sqrt(velocity[0] * velocity[0] + velocity[1] * velocity[1]);\n\n // color scale going from color0 -> color1 -> color2 -> color3\n vec3 color0 = vec3(0.0, 0.0, 0.2);\n vec3 color1 = vec3(0.0, 0.0, 0.35);\n vec3 color2 = vec3(0.8, 0.3, 0.4);\n vec3 color3 = vec3(1.0, 0.9, 0.6);\n\n float break0 = 0.0;\n float break1 = 0.001;\n float break2 = 0.027;\n float break3 = 0.04;\n\n if (speed < break1) {\n float t = (speed - break0) / break1;\n return mix(color0, color1, t);\n } else if (speed < break2) {\n float t = (speed - break1) / break2;\n // float t = (speed - 0.001) / 0.03;\n return mix(color1, color2, t);\n } else {\n float t = (speed - break2) / break3;\n return mix(color2, color3, min(1.0, t));\n }\n }\n\n\tvoid main() {\n\t\t// read in position from the state texture\n\t\tvec3 currParticle = texture2D(currParticleState, particleTextureIndex).xyz;\n\t\tvec2 currPosition = currParticle.xy;\n\n\t\tvec3 prevParticle = texture2D(prevParticleState, particleTextureIndex).xyz;\n\n\t\t// copy color over to fragment shader\n\t\tfragColor = getColor(currParticle, prevParticle);\n\n\t\t// scale to normalized device coordinates\n\t\t// gl_Position is a special variable that holds the position of a vertex\n gl_Position = vec4(currPosition, 0.0, 1.0);\n\n\t\t// update the size of a particles based on the prop pointWidth\n\t\tgl_PointSize = pointWidth;\n\t}\n\t",frag:"\n // set the precision of floating point numbers\n precision mediump float;\n\n // this value is populated by the vertex shader\n varying vec3 fragColor;\n\n void main() {\n // gl_FragColor is a special variable that holds the color of a pixel\n gl_FragColor = vec4(fragColor, 1);\n }\n ",attributes:{particleTextureIndex:particleTextureIndex},uniforms:{currParticleState:function(){return currParticleState},prevParticleState:function(){return prevParticleState},pointWidth:pointWidth,flowBuffer:flowBuffer},count:numParticles,primitive:"points",depth:{enable:!1,mask:!1}}),frameLoop=regl.frame(function(e){var t=e.tick;regl.clear({color:[0,0,0,1],depth:1,stencil:0}),1!==t&&t%ticksPerFlow!==0||(generateFlowData(),generateFlowBuffer()),drawParticles(),updateParticles(),cycleParticleStates(),t===animationTickLimit&&(console.log("Hit tick "+t+", canceling animation loop"),frameLoop.cancel())}); |