Skip to content

Instantly share code, notes, and snippets.

@pbeshai
Last active August 11, 2019 10:57
Show Gist options
  • Save pbeshai/98c08d22c922688acff852d35b70e4d2 to your computer and use it in GitHub Desktop.
Save pbeshai/98c08d22c922688acff852d35b70e4d2 to your computer and use it in GitHub Desktop.
Particles Flowing with WebGL and regl - II
license: mit
height: 720
border: no

Particles Flowing with WebGL and regl - II

Example of having particles flow with WebGL and regl. The main idea is to use update state with shaders by writing to framebuffers. In this example, the particles are affected by a directional "flow" field (possibly similar to what may be used for a wind map).

Big thanks to Mikola Lysenko, the creator of regl, for explaining the concept of writing to framebuffers from shaders to me after OpenVis Conf 2017!

Note: it appears that currently framebuffers aren't supported by iOS :( If you have any ideas on how to accomplish this on a way that works on phones, I'd love to hear it!

This block was developed with blockup.

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())});
//# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInNjcmlwdC5qcyJdLCJuYW1lcyI6WyJjcmVhdGVJbml0aWFsUGFydGljbGVCdWZmZXIiLCJpbml0aWFsUGFydGljbGVTdGF0ZSIsImNvbnN0IiwiaW5pdGlhbFRleHR1cmUiLCJyZWdsIiwidGV4dHVyZSIsImRhdGEiLCJzaGFwZSIsInNxcnROdW1QYXJ0aWNsZXMiLCJ0eXBlIiwiZnJhbWVidWZmZXIiLCJjb2xvciIsImRlcHRoIiwic3RlbmNpbCIsImN5Y2xlUGFydGljbGVTdGF0ZXMiLCJ0bXAiLCJwcmV2UGFydGljbGVTdGF0ZSIsImN1cnJQYXJ0aWNsZVN0YXRlIiwibmV4dFBhcnRpY2xlU3RhdGUiLCJub3JtYWxpemUiLCJ2ZWN0b3IiLCJtYWduaXR1ZGUiLCJNYXRoIiwic3FydCIsInBvdyIsIm1ha2VHcmlkTWVzaCIsIm51bUNvbHMiLCJudW1Sb3dzIiwiY29sU2NhbGUiLCJkMyIsInNjYWxlTGluZWFyIiwiZG9tYWluIiwicmFuZ2UiLCJyb3dTY2FsZSIsInZlcnRpY2VzIiwibnVtRmxvd0RhdGEiLCJtYXAiLCJpIiwiY29sIiwicm93IiwiZmxvb3IiLCJpbmRleEF0Q29sUm93IiwiZmFjZXMiLCJmb3JFYWNoIiwidmVydGV4IiwidG9wTGVmdFRyaWFuZ2xlIiwicHVzaCIsImJvdHRvbUxlZnRUcmlhbmdsZSIsInBvc2l0aW9ucyIsImNlbGxzIiwiZ2VuZXJhdGVGbG93RGF0YSIsImZsb3dEYXRhIiwicmFuZG9tIiwid2lkdGgiLCJ3aW5kb3ciLCJpbm5lcldpZHRoIiwiaGVpZ2h0IiwiaW5uZXJIZWlnaHQiLCJwb2ludFdpZHRoIiwiYW5pbWF0aW9uVGlja0xpbWl0IiwiY29uc29sZSIsImxvZyIsInRpY2tzUGVyRmxvdyIsIm51bVBhcnRpY2xlcyIsImxldCIsImNyZWF0ZVJFR0wiLCJleHRlbnNpb25zIiwiRmxvYXQzMkFycmF5IiwicGFydGljbGVUZXh0dXJlSW5kZXgiLCJqIiwic3FydEZsb3dEYXRhTGVuZ3RoIiwiZ3JpZE1lc2giLCJ1cHNjYWxlQW1vdW50IiwiZmxvd0J1ZmZlciIsImdlbmVyYXRlRmxvd0J1ZmZlciIsInZlcnQiLCJmcmFnIiwiYXR0cmlidXRlcyIsInBvc2l0aW9uIiwiZWxlbWVudHMiLCJkcmF3Rmxvd0J1ZmZlciIsInVuaWZvcm1zIiwiY291bnQiLCJ1cGRhdGVQYXJ0aWNsZXMiLCJ0aWNrIiwicmVmIiwiZHJhd1BhcnRpY2xlcyIsInByaW1pdGl2ZSIsImVuYWJsZSIsIm1hc2siLCJmcmFtZUxvb3AiLCJmcmFtZSIsImNsZWFyIiwiY2FuY2VsIl0sIm1hcHBpbmdzIjoiQUFrQ0EsUUFBU0EsNkJBQTRCQyxHQUVwQ0MsR0FBTUMsR0FBaUJDLEtBQUtDLFNBQzFCQyxLQUFNTCxFQUNOTSxPQUFRQyxpQkFBa0JBLGlCQUFrQixHQUM1Q0MsS0FBTSxTQUlSLE9BQU9MLE1BQUtNLGFBQ1hDLE1BQU9SLEVBQ1BTLE9BQU8sRUFDUEMsU0FBUyxJQVVYLFFBQVNDLHVCQUNSWixHQUFNYSxHQUFNQyxpQkFDWkEsbUJBQW9CQyxrQkFDcEJBLGtCQUFvQkMsa0JBQ3BCQSxrQkFBb0JILEVBWXJCLFFBQVNJLFdBQVVDLEdBQ2xCbEIsR0FBTW1CLEdBQVlDLEtBQUtDLEtBQUtELEtBQUtFLElBQUlKLEVBQVMsR0FBSSxHQUFHRSxLQUFLRSxJQUFJSixFQUFTLEdBQUksR0FHM0UsT0FGQUEsR0FBTyxJQUFNQyxFQUNiRCxFQUFPLElBQU1DLEVBQ05ELEVBU1IsUUFBU0ssY0FBYUMsRUFBU0MsR0FHN0J6QixHQUFNMEIsR0FBYUMsR0FBQ0MsY0FBY0MsUUFBUyxFQUFFTCxFQUFZLElBQUVNLFFBQVMsRUFBSSxJQUNsRUMsRUFBYUosR0FBQ0MsY0FBY0MsUUFBUyxFQUFFSixFQUFZLElBQUVLLE9BQVEsR0FBSSxJQVFqRUUsRUFBYUwsR0FBQ0csTUFBTUcsYUFBYUMsSUFBSSxTQUFBQyxHQUN6Q25DLEdBQU1vQyxHQUFPRCxFQUFHWCxFQUNWYSxFQUFNakIsS0FBS2tCLE1BQU9ILEVBQUdYLEVBRTNCLFFBQVFFLEVBQVNVLEdBQU1M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TSxZQUFhLFdBQUcsTUFBR1Esb0JBR3BCeUQsS0FzTkMsNmVBb0IwQ0MsS0FBQSxraEVBaEozQ0MsWUFFR2pDLFNBQ0UsRUFBSSxFQUNKLEVBQUcsRUFDSCxHQUFHLElBS1BxQyxVQUVFaEUsa0JBQW1CLFdBQUcsTUFBR0Esb0JBQ3pCRCxrQkFBbUIsV0FBRyxNQUFHQSxvQkFDekJ5RCxXQUFBQSxXQUdBVyxLQUFNLFNBQUNDLE1BQUVELEdBQUlDLEVBQUFELFdBQU9BLEtBSXRCRixNQUFPLElBSUhJLGNBQWdCbEYsTUFDckJ1RSxLQUFNLDgyRUE2RUxDLEtBQU0sZ1RBYVBDLFlBR0NULHFCQUFBQSxzQkFHRGEsVUFFQ2hFLGtCQUFtQixXQUFHLE1BQUdBLG9CQUN6QkQsa0JBQW1CLFdBQUcsTUFBR0Esb0JBQ3pCMEMsV0FBQUEsV0FDQWUsV0FBQUEsWUFJRFMsTUFBT25CLGFBR1B3QixVQUFXLFNBR1gzRSxPQUNFNEUsUUFBUSxFQUNSQyxNQUFNLEtBS0hDLFVBQVl0RixLQUFLdUYsTUFBTSxTQUFDTixNQUFFRCxHQUFJQyxFQUFBRCxJQUVuQ2hGLE1BQUt3RixPQUVKakYsT0FBUSxFQUFHLEVBQUcsRUFBRyxHQUNqQkMsTUFBTyxFQUNQQyxRQUFTLElBSUcsSUFBVHVFLEdBQWNBLEVBQU90QixlQUFpQixJQUN6Q1osbUJBQ0F3QixzQkFPRFksZ0JBR0FILGtCQUdBckUsc0JBR0lzRSxJQUFTekIscUJBQ1pDLFFBQVFDLElBQUksWUFBWXVCLEVBQUksOEJBRzVCTSxVQUFVRyIsImZpbGUiOiJzY3JpcHQuanMiLCJzb3VyY2VzQ29udGVudCI6WyJjb25zdCB3aWR0aCA9IHdpbmRvdy5pbm5lcldpZHRoO1xuY29uc3QgaGVpZ2h0ID0gd2luZG93LmlubmVySGVpZ2h0O1xuY29uc3QgcG9pbnRXaWR0aCA9IDM7XG5cbmNvbnN0IGFuaW1hdGlvblRpY2tMaW1pdCA9IC0xOyAvLyAtMSBkaXNhYmxlc1xuaWYgKGFuaW1hdGlvblRpY2tMaW1pdCA+PSAwKSB7XG4gIGNvbnNvbGUubG9nKGBMaW1pdGluZyB0byAke2FuaW1hdGlvblRpY2tMaW1pdH0gdGlja3NgKTtcbn1cblxuY29uc3QgdGlja3NQZXJGbG93ID0gMTMwO1xuY29uc29sZS5sb2coYENoYW5naW5nIGZsb3cgYnVmZmVyIGV2ZXJ5ICR7dGlja3NQZXJGbG93fSB0aWNrc2ApO1xuXG5jb25zdCBzcXJ0TnVtUGFydGljbGVzID0gMjU2O1xuY29uc3QgbnVtUGFydGljbGVzID0gc3FydE51bVBhcnRpY2xlcyAqIHNxcnROdW1QYXJ0aWNsZXM7XG5jb25zb2xlLmxvZyhgVXNpbmcgJHtudW1QYXJ0aWNsZXN9IHBhcnRpY2xlc2ApO1xuXG4vLyBpbml0aWFsaXplIHJlZ2xcbmNvbnN0IHJlZ2wgPSBjcmVhdGVSRUdMKHtcblx0Ly8gbmVlZCB0aGlzIHRvIHVzZSB0aGUgdGV4dHVyZXMgYXMgc3RhdGVzXG4gIGV4dGVuc2lvbnM6ICdPRVNfdGV4dHVyZV9mbG9hdCcsXG59KTtcblxuXG4vLyBpbml0aWFsIHBhcnRpY2xlcyBzdGF0ZSBhbmQgdGV4dHVyZSBmb3IgYnVmZmVyXG4vLyBtdWx0aXBseSBieSA0IGZvciBSIEcgQiBBXG5jb25zdCBpbml0aWFsUGFydGljbGVTdGF0ZSA9IG5ldyBGbG9hdDMyQXJyYXkobnVtUGFydGljbGVzICogNCk7XG5mb3IgKGxldCBpID0gMDsgaSA8IG51bVBhcnRpY2xlczsgKytpKSB7XG5cdC8vIHN0b3JlIHggdGhlbiB5IHRoZW4gdGljayBsaWZlc3BhbiBhbmQgMSBlbXB0eSBzcG90XG5cdGluaXRpYWxQYXJ0aWNsZVN0YXRlW2kgKiA0XSA9IDIgKiBNYXRoLnJhbmRvbSgpIC0gMTsgLy8geCBwb3NpdGlvblxuXHRpbml0aWFsUGFydGljbGVTdGF0ZVtpICogNCArIDFdID0gMiAqIE1hdGgucmFuZG9tKCkgLSAxOyAvLyB5IHBvc2l0aW9uXG5cdGluaXRpYWxQYXJ0aWNsZVN0YXRlW2kgKiA0ICsgMl0gPSA1MCArIDMwMCAqIE1hdGgucmFuZG9tKCk7IC8vIHRpY2sgbGlmZXNwYW4gcG9zaXRpb25cbn1cblxuLy8gY3JlYXRlIGEgcmVnbCBmcmFtZWJ1ZmZlciBob2xkaW5nIHRoZSBpbml0aWFsIHBhcnRpY2xlIHN0YXRlXG5mdW5jdGlvbiBjcmVhdGVJbml0aWFsUGFydGljbGVCdWZmZXIoaW5pdGlhbFBhcnRpY2xlU3RhdGUpIHtcblx0Ly8gY3JlYXRlIGEgdGV4dHVyZSB3aGVyZSBSIGhvbGRzIHBhcnRpY2xlIFggYW5kIEcgaG9sZHMgcGFydGljbGUgWSBwb3NpdGlvblxuXHRjb25zdCBpbml0aWFsVGV4dHVyZSA9IHJlZ2wudGV4dHVyZSh7XG5cdCAgZGF0YTogaW5pdGlhbFBhcnRpY2xlU3RhdGUsXG5cdCAgc2hhcGU6IFtzcXJ0TnVtUGFydGljbGVzLCBzcXJ0TnVtUGFydGljbGVzLCA0XSxcblx0ICB0eXBlOiAnZmxvYXQnXG5cdH0pO1xuXG5cdC8vIGNyZWF0ZSBhIGZyYW1lIGJ1ZmZlciB1c2luZyB0aGUgc3RhdGUgYXMgdGhlIGNvbG9yZWQgdGV4dHVyZVxuXHRyZXR1cm4gcmVnbC5mcmFtZWJ1ZmZlcih7XG5cdFx0Y29sb3I6IGluaXRpYWxUZXh0dXJlLFxuXHRcdGRlcHRoOiBmYWxzZSxcblx0XHRzdGVuY2lsOiBmYWxzZSxcblx0fSk7XG59XG5cbi8vIGluaXRpYWxpemUgcGFydGljbGUgc3RhdGVzXG5sZXQgcHJldlBhcnRpY2xlU3RhdGUgPSBjcmVhdGVJbml0aWFsUGFydGljbGVCdWZmZXIoaW5pdGlhbFBhcnRpY2xlU3RhdGUpO1xubGV0IGN1cnJQYXJ0aWNsZVN0YXRlID0gY3JlYXRlSW5pdGlhbFBhcnRpY2xlQnVmZmVyKGluaXRpYWxQYXJ0aWNsZVN0YXRlKTtcbmxldCBuZXh0UGFydGljbGVTdGF0ZSA9IGNyZWF0ZUluaXRpYWxQYXJ0aWNsZUJ1ZmZlcihpbml0aWFsUGFydGljbGVTdGF0ZSk7XG5cbi8vIGN5Y2xlIHdoaWNoIGJ1ZmZlciBpcyBiZWluZyBwb2ludGVkIHRvIGJ5IHRoZSBzdGF0ZSB2YXJpYWJsZXNcbmZ1bmN0aW9uIGN5Y2xlUGFydGljbGVTdGF0ZXMoKSB7XG5cdGNvbnN0IHRtcCA9IHByZXZQYXJ0aWNsZVN0YXRlO1xuXHRwcmV2UGFydGljbGVTdGF0ZSA9IGN1cnJQYXJ0aWNsZVN0YXRlO1xuXHRjdXJyUGFydGljbGVTdGF0ZSA9IG5leHRQYXJ0aWNsZVN0YXRlO1xuXHRuZXh0UGFydGljbGVTdGF0ZSA9IHRtcDtcbn1cblxuLy8gY3JlYXRlIGFycmF5IG9mIGluZGljZXMgaW50byB0aGUgcGFydGljbGUgdGV4dHVyZSBmb3IgZWFjaCBwYXJ0aWNsZVxuY29uc3QgcGFydGljbGVUZXh0dXJlSW5kZXggPSBbXTtcbmZvciAobGV0IGkgPSAwOyBpIDwgc3FydE51bVBhcnRpY2xlczsgaSsrKSB7XG5cdGZvciAobGV0IGogPSAwOyBqIDwgc3FydE51bVBhcnRpY2xlczsgaisrKSB7XG5cdFx0cGFydGljbGVUZXh0dXJlSW5kZXgucHVzaChpIC8gc3FydE51bVBhcnRpY2xlcywgaiAvIHNxcnROdW1QYXJ0aWNsZXMpO1xuXHR9XG59XG5cbi8vIGhlbHBlciB0byBub3JtYWxpemUgYSB2ZWN0b3Igb2YgbGVuZ3RoIDIgc28gaXQgaGFzIG1hZ25pdHVkZSAxIChtdXRhdGVzKVxuZnVuY3Rpb24gbm9ybWFsaXplKHZlY3Rvcikge1xuXHRjb25zdCBtYWduaXR1ZGUgPSBNYXRoLnNxcnQoTWF0aC5wb3codmVjdG9yWzBdLCAyKSArIE1hdGgucG93KHZlY3RvclsxXSwgMikpO1xuXHR2ZWN0b3JbMF0gLz0gbWFnbml0dWRlO1xuXHR2ZWN0b3JbMV0gLz0gbWFnbml0dWRlO1xuXHRyZXR1cm4gdmVjdG9yO1xufVxuXG4vLyBjcmVhdGUgdGhlIGZsb3cgbWFwXG5jb25zdCBzcXJ0Rmxvd0RhdGFMZW5ndGggPSA0O1xuY29uc3QgbnVtRmxvd0RhdGEgPSBzcXJ0Rmxvd0RhdGFMZW5ndGggKiBzcXJ0Rmxvd0RhdGFMZW5ndGg7XG5jb25zdCBmbG93RGF0YSA9IFtdO1xuXG4vLyBnZW5lcmF0ZSBhIG1lc2ggZm9yIGEgZ3JpZFxuZnVuY3Rpb24gbWFrZUdyaWRNZXNoKG51bUNvbHMsIG51bVJvd3MpIHtcblxuICAvLyBoZWxwZXIgc2NhbGVzIHRvIG1hcCB0byBub3JtYWxpemVkIGRldmljZSBjb29yZGluYXRlcyBmcm9tIGNvbHVtbnMgZm9yIHNpbXBsaWNpdHlcbiAgY29uc3QgY29sU2NhbGUgPSBkMy5zY2FsZUxpbmVhcigpLmRvbWFpbihbMCwgbnVtQ29scyAtIDFdKS5yYW5nZShbLTEsIDFdKTtcbiAgY29uc3Qgcm93U2NhbGUgPSBkMy5zY2FsZUxpbmVhcigpLmRvbWFpbihbMCwgbnVtUm93cyAtIDFdKS5yYW5nZShbMSwgLTFdKTtcblxuICAvLyBhdCB0aGlzIHBvaW50LCB3ZSBhcmUgZ29pbmcgdG8gY3JlYXRlIGEgZ3JpZCBtZXNoIHNvIHdlIGNhbiBpbnRlcnBvbGF0ZVxuICAvLyBiZXR3ZWVuIHRoZSB2YWx1ZXMgb2Ygb3VyIGZsb3cgc28gdGhleSBzbW9vdGhseSBtZXJnZSBpbnRvIG9uZSBhbm90aGVyLlxuICAvLyBpZiB5b3UgdW5jb21tZW50IGRyYXdGbG93QnVmZmVyKCkgaW4gdGhlIHJlZ2wuZnJhbWUgY29kZSB3YXkgYmVsb3dcbiAgLy8geW91IGNhbiBzZWUgd2hhdCB0aGUgZmxvdyBidWZmZXIgbG9va3MgbGlrZS5cblxuICAvLyBjcmVhdGUgdmVydGljZXMgZm9yIGVhY2ggZmxvdyBkYXRhIHBvaW50XG4gIGNvbnN0IHZlcnRpY2VzID0gZDMucmFuZ2UobnVtRmxvd0RhdGEpLm1hcCgoaSkgPT4ge1xuICAgIGNvbnN0IGNvbCA9IGkgJSBudW1Db2xzO1xuICAgIGNvbnN0IHJvdyA9IE1hdGguZmxvb3IoaSAvIG51bUNvbHMpO1xuXG4gICAgcmV0dXJuIFtjb2xTY2FsZShjb2wpLCByb3dTY2FsZShyb3cpXTtcbiAgfSk7XG5cbiAgLy8gaGVscGVyIHRvIGZpbmQgYW4gaW5kZXggaW4gdGhlIGZsYXQgYXJyYXkgYmFzZWQgb24gcm93IGFuIGRvY2x1bW5cbiAgY29uc3QgaW5kZXhBdENvbFJvdyA9IChjb2wsIHJvdykgPT4gY29sICsgKHJvdyAqIG51bUNvbHMpO1xuXG4gIC8vIGNyZWF0ZSB0aGUgZmFjZXMgZm9yIHRoZSBtZXNoICh0d28gdHJpYW5nbGVzIGZvcm0gYSBncmlkIGNlbGwpXG4gIGNvbnN0IGZhY2VzID0gW107XG4gIHZlcnRpY2VzLmZvckVhY2goKHZlcnRleCwgaSkgPT4ge1xuICAgIGNvbnN0IGNvbCA9IGkgJSBudW1Db2xzO1xuICAgIGNvbnN0IHJvdyA9IE1hdGguZmxvb3IoaSAvIG51bUNvbHMpO1xuXG4gICAgaWYgKGNvbCArIDEgPCBudW1Db2xzICYmIHJvdyArIDEgPCBudW1Sb3dzKSB7XG4gICAgICBjb25zdCB0b3BMZWZ0VHJpYW5nbGUgPSBbaSwgaSArIDEsIGluZGV4QXRDb2xSb3coY29sLCByb3cgKyAxKV07XG4gICAgICBmYWNlcy5wdXNoKHRvcExlZnRUcmlhbmdsZSk7XG4gICAgfVxuXG4gICAgaWYgKGNvbCArIDEgPCBudW1Db2xzICYmIHJvdyAtIDEgPj0gMCkge1xuICAgICAgY29uc3QgYm90dG9tTGVmdFRyaWFuZ2xlID0gW2ksIGkgKyAxLCBpbmRleEF0Q29sUm93KGNvbCArIDEsIHJvdyAtIDEpXTtcbiAgICAgIGZhY2VzLnB1c2goYm90dG9tTGVmdFRyaWFuZ2xlKTtcbiAgICB9XG4gIH0pO1xuXG4gIHJldHVybiB7IHBvc2l0aW9uczogdmVydGljZXMsIGNlbGxzOiBmYWNlcyB9O1xufVxuY29uc3QgZ3JpZE1lc2ggPSBtYWtlR3JpZE1lc2goc3FydEZsb3dEYXRhTGVuZ3RoLCBzcXJ0Rmxvd0RhdGFMZW5ndGgpO1xuXG4vLyBnZW5lcmF0ZSBhIG5ldyBmbG93IG1hcCBieSB1cGRhdGluZyB2YWx1ZXMgaW4gZmxvd0RhdGFcbmZ1bmN0aW9uIGdlbmVyYXRlRmxvd0RhdGEoKSB7XG5cdGQzLnJhbmdlKG51bUZsb3dEYXRhKS5mb3JFYWNoKChpKSA9PiB7XG5cdCAgZmxvd0RhdGFbaV0gPSBub3JtYWxpemUoW1xuICAgICAgTWF0aC5yYW5kb20oKSAqIDIgLSAxLCAvLyBjb2x1bW5cbiAgICAgIE1hdGgucmFuZG9tKCkgKiAyIC0gMSwgLy8gcm93XG4gICAgICAzICogTWF0aC5yYW5kb20oKSwgLy8gbWFnbml0dWRlXG4gICAgXSk7XG5cdH0pO1xufVxuXG4vLyBnZW5lcmF0ZSB0aGUgaW5pdGlhbCBmbG93IG1lc2hcbmdlbmVyYXRlRmxvd0RhdGEoKTtcblxuLy8gaG93IGhpZ2ggcmVzIHdlIHdhbnQgdGhlIHNtb290aGVkIGZsb3cgbWFwIHRvIGxvb2tcbi8vIHR1cm4gb24gZHJhd0Zsb3dCdWZmZXIoKSBpbiByZWdsLmZyYW1lIHRvIHNlZSB0aGUgZGlmZmVyZW5jZVxuY29uc3QgdXBzY2FsZUFtb3VudCA9IHNxcnRGbG93RGF0YUxlbmd0aCAqIDE2O1xuXG5jb25zdCBmbG93QnVmZmVyID0gcmVnbC5mcmFtZWJ1ZmZlcih7XG5cdGNvbG9yOiByZWdsLnRleHR1cmUoe1xuICAgIC8vIGluaXRpYWxpemUgdG8gZW1wdHkgdmFsdWVzXG5cdFx0ZGF0YTogbmV3IEZsb2F0MzJBcnJheSh1cHNjYWxlQW1vdW50ICogdXBzY2FsZUFtb3VudCAqIDQpLFxuXHRcdHNoYXBlOiBbdXBzY2FsZUFtb3VudCwgdXBzY2FsZUFtb3VudCwgNF0sXG5cdFx0dHlwZTogJ2Zsb2F0Jyxcblx0fSksXG5cdGRlcHRoOiBmYWxzZSxcblx0c3RlbmNpbDogZmFsc2UsXG59KTtcblxuXG4vLyByZWdsIGNvbW1hbmQgdG8gcG9wdWxhdGUgdGhlIGZsb3dCdWZmZXIgd2l0aCBhY3R1YWwgdmFsdWVzIGJhc2VkIG9uIG91ciBkYXRhXG4vLyB1c2VzIHRoZSBncmlkIG1lc2ggdG8gaW50ZXJwb2xhdGUgdGhlIGZsb3cgdmFsdWVzIGF0IGVhY2ggcG9pbnQgaW4gdGhlIGdyaWRcbmNvbnN0IGdlbmVyYXRlRmxvd0J1ZmZlciA9IHJlZ2woe1xuXHRmcmFtZWJ1ZmZlcjogZmxvd0J1ZmZlcixcbiAgdmVydDogYFxuICBwcmVjaXNpb24gbWVkaXVtcCBmbG9hdDtcblxuICBhdHRyaWJ1dGUgdmVjMiBwb3NpdGlvbjtcbiAgYXR0cmlidXRlIHZlYzMgZmxvd0RhdGE7XG5cbiAgdmFyeWluZyB2ZWMzIGZsb3c7XG5cbiAgdm9pZCBtYWluKCkge1xuICAgIGZsb3cgPSBmbG93RGF0YTtcbiAgICBnbF9Qb3NpdGlvbiA9IHZlYzQocG9zaXRpb24sIDAsIDEpO1xuICB9YCxcblxuICBmcmFnOiBgXG4gIHByZWNpc2lvbiBtZWRpdW1wIGZsb2F0O1xuXG4gIHZhcnlpbmcgdmVjMyBmbG93O1xuXG4gIHZvaWQgbWFpbigpIHtcbiAgICBnbF9GcmFnQ29sb3IgPSB2ZWM0KGZsb3csIDEpO1xuICB9YCxcblxuICAvLyB0aGlzIGNvbnZlcnRzIHRoZSB2ZXJ0aWNlcyBvZiB0aGUgbWVzaCBpbnRvIHRoZSBwb3NpdGlvbiBhdHRyaWJ1dGVcbiAgYXR0cmlidXRlczoge1xuICAgIHBvc2l0aW9uOiBncmlkTWVzaC5wb3NpdGlvbnMsXG4gICAgZmxvd0RhdGE6ICgpID0+IGZsb3dEYXRhLFxuICB9LFxuXG4gIGVsZW1lbnRzOiBncmlkTWVzaC5jZWxscyxcbn0pXG5cblxuY29uc3QgZHJhd0Zsb3dCdWZmZXIgPSByZWdsKHtcblx0dmVydDogYFxuXHQvLyBzZXQgdGhlIHByZWNpc2lvbiBvZiBmbG9hdGluZyBwb2ludCBudW1iZXJzXG4gIHByZWNpc2lvbiBtZWRpdW1wIGZsb2F0O1xuXG4gIC8vIHZlcnRleCBvZiB0aGUgdHJpYW5nbGVcbiAgYXR0cmlidXRlIHZlYzIgdmVydGV4O1xuXG4gIC8vIGluZGV4IGludG8gdGhlIHRleHR1cmUgc3RhdGVcbiAgdmFyeWluZyB2ZWMyIGZsb3dJbmRleDtcblxuICB2b2lkIG1haW4oKSB7XG4gIFx0Ly8gbWFwIGJvdHRvbSBsZWZ0IC0xLC0xIChub3JtYWxpemVkIGRldmljZSBjb29yZHMpIHRvIDAsMCAocGFydGljbGUgdGV4dHVyZSBpbmRleClcbiAgXHQvLyBhbmQgMSwxIChuZGMpIHRvIDEsMSAodGV4dHVyZSlcbiAgXHRmbG93SW5kZXggPSAwLjUgKiAoMS4wICsgdmVydGV4KTtcblxuICBcdGdsX1Bvc2l0aW9uID0gdmVjNCh2ZXJ0ZXgsIDAsIDEpO1xuICB9XG5cdGAsXG5cbiAgZnJhZzogYFxuICAvLyBzZXQgdGhlIHByZWNpc2lvbiBvZiBmbG9hdGluZyBwb2ludCBudW1iZXJzXG4gIHByZWNpc2lvbiBtZWRpdW1wIGZsb2F0O1xuXG4gIHVuaWZvcm0gc2FtcGxlcjJEIGZsb3dCdWZmZXI7XG5cbiAgLy8gaW5kZXggaW50byB0aGUgdGV4dHVyZSBzdGF0ZVxuICB2YXJ5aW5nIHZlYzIgZmxvd0luZGV4O1xuXG4gIHZvaWQgbWFpbigpIHtcbiAgICB2ZWMzIGZsb3cgPSB0ZXh0dXJlMkQoZmxvd0J1ZmZlciwgZmxvd0luZGV4KS54eXo7XG4gICAgZ2xfRnJhZ0NvbG9yID0gdmVjNChmbG93LCAxKTtcbiAgfVxuICBgLFxuXG5cdGF0dHJpYnV0ZXM6IHtcblx0XHQvLyBhIHRyaWFuZ2xlIGJpZyBlbm91Z2ggdG8gZmlsbCB0aGUgc2NyZWVuXG4gICAgdmVydGV4OiBbXG4gICAgICAtNCwgMCxcbiAgICAgIDQsIDQsXG4gICAgICA0LCAtNFxuICAgIF1cbiAgfSxcblxuICAvLyBwYXNzIGluIHByZXZpb3VzIHN0YXRlcyB0byB3b3JrIGZyb21cbiAgdW5pZm9ybXM6IHtcbiAgICBmbG93QnVmZmVyLFxuICB9LFxuXG4gIC8vIGl0J3MgYSB0cmlhbmdsZSAtIDMgdmVydGljZXNcbiAgY291bnQ6IDMsXG59KTtcblxuLy8gcmVnbCBjb21tYW5kIHRoYXQgdXBkYXRlcyBwYXJ0aWNsZXMgc3RhdGUgYmFzZWQgb24gcHJldmlvdXMgdHdvXG5jb25zdCB1cGRhdGVQYXJ0aWNsZXMgPSByZWdsKHtcblx0Ly8gd3JpdGUgdG8gYSBmcmFtZWJ1ZmZlciBpbnN0ZWFkIG9mIHRvIHRoZSBzY3JlZW5cbiAgZnJhbWVidWZmZXI6ICgpID0+IG5leHRQYXJ0aWNsZVN0YXRlLFxuICAvLyBeXl5eXiBpbXBvcnRhbnQgc3R1ZmYuICAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cblxuXHR2ZXJ0OiBgXG5cdC8vIHNldCB0aGUgcHJlY2lzaW9uIG9mIGZsb2F0aW5nIHBvaW50IG51bWJlcnNcbiAgcHJlY2lzaW9uIG1lZGl1bXAgZmxvYXQ7XG5cbiAgLy8gdmVydGV4IG9mIHRoZSB0cmlhbmdsZVxuICBhdHRyaWJ1dGUgdmVjMiB2ZXJ0ZXg7XG5cbiAgLy8gaW5kZXggaW50byB0aGUgdGV4dHVyZSBzdGF0ZVxuICB2YXJ5aW5nIHZlYzIgcGFydGljbGVUZXh0dXJlSW5kZXg7XG5cdHVuaWZvcm0gc2FtcGxlcjJEIGZsb3dCdWZmZXI7XG5cbiAgdm9pZCBtYWluKCkge1xuICBcdC8vIG1hcCBib3R0b20gbGVmdCAtMSwtMSAobm9ybWFsaXplZCBkZXZpY2UgY29vcmRzKSB0byAwLDAgKHBhcnRpY2xlIHRleHR1cmUgaW5kZXgpXG4gIFx0Ly8gYW5kIDEsMSAobmRjKSB0byAxLDEgKHRleHR1cmUpXG4gIFx0cGFydGljbGVUZXh0dXJlSW5kZXggPSAwLjUgKiAoMS4wICsgdmVydGV4KTtcblxuICBcdGdsX1Bvc2l0aW9uID0gdmVjNCh2ZXJ0ZXgsIDAsIDEpO1xuICB9XG5cdGAsXG5cbiAgLy8gc2luY2Ugd2UgYXJlIHdyaXRpbmcgdG8gYSBmcmFtZWJ1ZmZlciwgYWxsIG91ciBkYXRhIG5lZWRzIHRvIGJlIGVuY29kZWRcbiAgLy8gYXMgdGhlIHJnYiB2YWx1ZSwgaGVuY2Ugd2h5IHRoZSBmcmFnIHNoYWRlciBpcyB3aGVyZSBhbGwgdGhlIHdvcmsgaGFwcGVucy5cbiAgZnJhZzogYFxuICAvLyBzZXQgdGhlIHByZWNpc2lvbiBvZiBmbG9hdGluZyBwb2ludCBudW1iZXJzXG4gIHByZWNpc2lvbiBtZWRpdW1wIGZsb2F0O1xuXG4gIC8vIHN0YXRlcyB0byByZWFkIGZyb20gdG8gZ2V0IHZlbG9jaXR5XG4gIHVuaWZvcm0gc2FtcGxlcjJEIGN1cnJQYXJ0aWNsZVN0YXRlO1xuICB1bmlmb3JtIHNhbXBsZXIyRCBwcmV2UGFydGljbGVTdGF0ZTtcblxuICB1bmlmb3JtIHNhbXBsZXIyRCBmbG93QnVmZmVyO1xuICB1bmlmb3JtIGZsb2F0IHRpY2s7XG5cbiAgLy8gaW5kZXggaW50byB0aGUgdGV4dHVyZSBzdGF0ZVxuICB2YXJ5aW5nIHZlYzIgcGFydGljbGVUZXh0dXJlSW5kZXg7XG5cbiAgLy8gc2VlbWluZ2x5IHN0YW5kYXJkIDEtbGluZXIgcmFuZG9tIGZ1bmN0aW9uXG4gIC8vIGh0dHA6Ly9zdGFja292ZXJmbG93LmNvbS9xdWVzdGlvbnMvNDIwMDIyNC9yYW5kb20tbm9pc2UtZnVuY3Rpb25zLWZvci1nbHNsXG4gIGZsb2F0IHJhbmQodmVjMiBjbyl7XG4gICAgcmV0dXJuIGZyYWN0KHNpbihkb3QoY28ueHksIHZlYzIoMTIuOTg5OCwgNzguMjMzKSkpICogNDM3NTguNTQ1Myk7XG4gIH1cblxuICB2b2lkIG1haW4oKSB7XG4gICAgdmVjMyBwYXJ0aWNsZSA9IHRleHR1cmUyRChjdXJyUGFydGljbGVTdGF0ZSwgcGFydGljbGVUZXh0dXJlSW5kZXgpLnh5ejtcbiAgICB2ZWMyIGN1cnJQb3NpdGlvbiA9IHBhcnRpY2xlLnh5O1xuICAgIGZsb2F0IHRpY2tMaWZlc3BhbiA9IHBhcnRpY2xlWzJdO1xuXG4gICAgdmVjMyBwcmV2UGFydGljbGUgPSB0ZXh0dXJlMkQocHJldlBhcnRpY2xlU3RhdGUsIHBhcnRpY2xlVGV4dHVyZUluZGV4KS54eXo7XG4gICAgdmVjMiBwcmV2UG9zaXRpb24gPSBwcmV2UGFydGljbGUueHk7XG4gICAgZmxvYXQgcHJldlRpY2tMaWZlc3BhbiA9IHByZXZQYXJ0aWNsZVsyXTtcblxuICAgIHZlYzIgcG9zaXRpb247XG5cbiAgICAvLyByZXNwYXduXG4gICAgaWYgKHRpY2tMaWZlc3BhbiA8PSAwLjApIHtcbiAgICAgIHRpY2tMaWZlc3BhbiA9IDEwMC4wICogcmFuZCh0aWNrICogcGFydGljbGVUZXh0dXJlSW5kZXgpICsgMS4wO1xuICAgICAgcG9zaXRpb24gPSAyLjAgKiB2ZWMyKHJhbmQodGljayAqIHBvc2l0aW9uKSwgcmFuZCh0aWNrICogdGlja0xpZmVzcGFuICogcG9zaXRpb24pKSAtIDEuMDtcblxuICAgIC8vIHVwZGF0ZSBjdXJyZW50IHBvc2l0aW9uXG4gICAgfSBlbHNlIHtcblxuICAgICAgLy8gZmluZCBmbG93IGJhc2VkIG9uIGN1cnJlbnQgcG9zaXRpb25cbiAgICAgIHZlYzIgZmxvd0luZGV4ID0gMC41ICogKGN1cnJQb3NpdGlvbiArIDEuMCk7XG4gICAgICB2ZWMzIGZsb3cgPSB0ZXh0dXJlMkQoZmxvd0J1ZmZlciwgZmxvd0luZGV4KS54eXo7XG4gICAgICBmbG9hdCBmbG93TWFnbml0dWRlID0gZmxvd1syXTtcblxuICAgICAgdmVjMiB2ZWxvY2l0eTtcblxuICAgICAgLy8gdXNlIHZlbG9jaXR5IHVubGVzcyBqdXN0IHJlc3Bhd25lZFxuICAgICAgaWYgKHRpY2tMaWZlc3BhbiAhPSBwcmV2VGlja0xpZmVzcGFuIC0gMS4wKSB7XG4gICAgICAgIHZlbG9jaXR5ID0gdmVjMigwLjApO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgdmVsb2NpdHkgPSBjdXJyUG9zaXRpb24gLSBwcmV2UG9zaXRpb247XG4gICAgICB9XG5cbiAgICAgIHZlYzIgcmFuZG9tID0gMC41IC0gdmVjMihyYW5kKGN1cnJQb3NpdGlvbiksIHJhbmQoMTAuMCAqIGN1cnJQb3NpdGlvbikpO1xuICAgICAgLy8gcmFuZG9tID0gdmVjMigwLjAsIDAuMCk7XG5cbiAgICAgIHBvc2l0aW9uID0gY3VyclBvc2l0aW9uICtcbiAgICAgICAgKDAuOTYgKiB2ZWxvY2l0eSkgK1xuICAgICAgICAoMC4wMDEgKiByYW5kb20pICtcbiAgICAgICAgKGZsb3cueHkgKiAoZmxvd01hZ25pdHVkZSAqIDAuMDAyKSk7XG4gICAgfVxuXG4gICAgLy8gd2Ugc3RvcmUgdGhlIG5ldyBwb3NpdGlvbiBhcyB0aGUgY29sb3IgaW4gdGhpcyBmcmFtZSBidWZmZXJcbiAgICAvLyByZWR1Y2UgdGhlIHRpY2sgbGlmZXNwYW4gYnkgMVxuICAgIGdsX0ZyYWdDb2xvciA9IHZlYzQocG9zaXRpb24sIHRpY2tMaWZlc3BhbiAtIDEuMCwgMSk7XG4gIH1cbiAgYCxcblxuXHRhdHRyaWJ1dGVzOiB7XG5cdFx0Ly8gYSB0cmlhbmdsZSBiaWcgZW5vdWdoIHRvIGZpbGwgdGhlIHNjcmVlblxuICAgIHZlcnRleDogW1xuICAgICAgLTQsIDAsXG4gICAgICA0LCA0LFxuICAgICAgNCwgLTRcbiAgICBdXG4gIH0sXG5cbiAgLy8gcGFzcyBpbiBwcmV2aW91cyBzdGF0ZXMgdG8gd29yayBmcm9tXG4gIHVuaWZvcm1zOiB7XG4gIFx0Ly8gbXVzdCB1c2UgYSBmdW5jdGlvbiBzbyBpdCBnZXRzIHVwZGF0ZWQgZWFjaCBjYWxsXG4gICAgY3VyclBhcnRpY2xlU3RhdGU6ICgpID0+IGN1cnJQYXJ0aWNsZVN0YXRlLFxuICAgIHByZXZQYXJ0aWNsZVN0YXRlOiAoKSA9PiBwcmV2UGFydGljbGVTdGF0ZSxcbiAgICBmbG93QnVmZmVyLFxuXG4gICAgLy8gaW5jbHVkZSB0aWNrIGZvciBpbXByb3ZpbmcgcmFuZG9tc1xuICAgIHRpY2s6ICh7IHRpY2sgfSkgPT4gdGljayxcbiAgfSxcblxuICAvLyBpdCdzIGEgdHJpYW5nbGUgLSAzIHZlcnRpY2VzXG4gIGNvdW50OiAzLFxufSk7XG5cbi8vIHJlZ2wgY29tbWFuZCB0aGF0IGRyYXdzIHBhcnRpY2xlcyBhdCB0aGVpciBjdXJyZW50IHN0YXRlXG5jb25zdCBkcmF3UGFydGljbGVzID0gcmVnbCh7XG5cdHZlcnQ6IGBcblx0Ly8gc2V0IHRoZSBwcmVjaXNpb24gb2YgZmxvYXRpbmcgcG9pbnQgbnVtYmVyc1xuICBwcmVjaXNpb24gbWVkaXVtcCBmbG9hdDtcblxuXHRhdHRyaWJ1dGUgdmVjMiBwYXJ0aWNsZVRleHR1cmVJbmRleDtcblx0dW5pZm9ybSBzYW1wbGVyMkQgY3VyclBhcnRpY2xlU3RhdGU7XG5cdHVuaWZvcm0gc2FtcGxlcjJEIHByZXZQYXJ0aWNsZVN0YXRlO1xuXHR1bmlmb3JtIHNhbXBsZXIyRCBmbG93QnVmZmVyO1xuXG4gIC8vIHZhcmlhYmxlcyB0byBzZW5kIHRvIHRoZSBmcmFnbWVudCBzaGFkZXJcbiAgdmFyeWluZyB2ZWMzIGZyYWdDb2xvcjtcblxuICAvLyB2YWx1ZXMgdGhhdCBhcmUgdGhlIHNhbWUgZm9yIGFsbCB2ZXJ0aWNlc1xuICB1bmlmb3JtIGZsb2F0IHBvaW50V2lkdGg7XG5cbiAgLy8gZ2V0IGNvbG9yIGJhc2VkIG9uIHBhcnRpY2xlIHNwZWVkXG4gIHZlYzMgZ2V0Q29sb3IodmVjMyBjdXJyUGFydGljbGUsIHZlYzMgcHJldlBhcnRpY2xlKSB7XG4gIFx0dmVjMiBjdXJyUG9zaXRpb24gPSBjdXJyUGFydGljbGUueHk7XG5cdFx0ZmxvYXQgdGlja0xpZmVzcGFuID0gY3VyclBhcnRpY2xlWzJdO1xuXHRcdHZlYzIgcHJldlBvc2l0aW9uID0gcHJldlBhcnRpY2xlLnh5O1xuXHRcdGZsb2F0IHByZXZUaWNrTGlmZXNwYW4gPSBwcmV2UGFydGljbGVbMl07XG5cblx0XHR2ZWMyIHZlbG9jaXR5O1xuXG4gIFx0Ly8gdXNlIHZlbG9jaXR5IHVubGVzcyBqdXN0IHJlc3Bhd25lZFxuXHRcdGlmICh0aWNrTGlmZXNwYW4gIT0gcHJldlRpY2tMaWZlc3BhbiAtIDEuMCkge1xuXHRcdFx0dmVsb2NpdHkgPSB2ZWMyKDAuMCk7XG5cdFx0fSBlbHNlIHtcblx0XHRcdHZlbG9jaXR5ID0gY3VyclBvc2l0aW9uIC0gcHJldlBvc2l0aW9uO1xuXHRcdH1cblxuICAgIC8vIGNvbG9yIGJhc2VkIG9uIHRoZSBzcGVlZCAoZmFzdGVyIHBhcnRpY2xlcyBhcmUgYnJpZ2h0ZXIpXG5cdFx0ZmxvYXQgc3BlZWQgPSBzcXJ0KHZlbG9jaXR5WzBdICogdmVsb2NpdHlbMF0gKyB2ZWxvY2l0eVsxXSAqIHZlbG9jaXR5WzFdKTtcblxuICAgIC8vIGNvbG9yIHNjYWxlIGdvaW5nIGZyb20gY29sb3IwIC0+IGNvbG9yMSAtPiBjb2xvcjIgLT4gY29sb3IzXG4gICAgdmVjMyBjb2xvcjAgPSB2ZWMzKDAuMCwgMC4wLCAwLjIpO1xuICAgIHZlYzMgY29sb3IxID0gdmVjMygwLjAsIDAuMCwgMC4zNSk7XG4gICAgdmVjMyBjb2xvcjIgPSB2ZWMzKDAuOCwgMC4zLCAwLjQpO1xuICAgIHZlYzMgY29sb3IzID0gdmVjMygxLjAsIDAuOSwgMC42KTtcblxuICAgIGZsb2F0IGJyZWFrMCA9IDAuMDtcbiAgICBmbG9hdCBicmVhazEgPSAwLjAwMTtcbiAgICBmbG9hdCBicmVhazIgPSAwLjAyNztcbiAgICBmbG9hdCBicmVhazMgPSAwLjA0O1xuXG4gICAgaWYgKHNwZWVkIDwgYnJlYWsxKSB7XG4gICAgICBmbG9hdCB0ID0gKHNwZWVkIC0gYnJlYWswKSAvIGJyZWFrMTtcbiAgICAgIHJldHVybiBtaXgoY29sb3IwLCBjb2xvcjEsIHQpO1xuICAgIH0gZWxzZSBpZiAoc3BlZWQgPCBicmVhazIpIHtcbiAgICAgIGZsb2F0IHQgPSAoc3BlZWQgLSBicmVhazEpIC8gYnJlYWsyO1xuICAgICAgLy8gZmxvYXQgdCA9IChzcGVlZCAtIDAuMDAxKSAvIDAuMDM7XG4gICAgICByZXR1cm4gbWl4KGNvbG9yMSwgY29sb3IyLCB0KTtcbiAgICB9IGVsc2UgIHtcbiAgICAgIGZsb2F0IHQgPSAoc3BlZWQgLSBicmVhazIpIC8gYnJlYWszO1xuICAgICAgcmV0dXJuIG1peChjb2xvcjIsIGNvbG9yMywgbWluKDEuMCwgdCkpO1xuICAgIH1cbiAgfVxuXG5cdHZvaWQgbWFpbigpIHtcblx0XHQvLyByZWFkIGluIHBvc2l0aW9uIGZyb20gdGhlIHN0YXRlIHRleHR1cmVcblx0XHR2ZWMzIGN1cnJQYXJ0aWNsZSA9IHRleHR1cmUyRChjdXJyUGFydGljbGVTdGF0ZSwgcGFydGljbGVUZXh0dXJlSW5kZXgpLnh5ejtcblx0XHR2ZWMyIGN1cnJQb3NpdGlvbiA9IGN1cnJQYXJ0aWNsZS54eTtcblxuXHRcdHZlYzMgcHJldlBhcnRpY2xlID0gdGV4dHVyZTJEKHByZXZQYXJ0aWNsZVN0YXRlLCBwYXJ0aWNsZVRleHR1cmVJbmRleCkueHl6O1xuXG5cdFx0Ly8gY29weSBjb2xvciBvdmVyIHRvIGZyYWdtZW50IHNoYWRlclxuXHRcdGZyYWdDb2xvciA9IGdldENvbG9yKGN1cnJQYXJ0aWNsZSwgcHJldlBhcnRpY2xlKTtcblxuXHRcdC8vIHNjYWxlIHRvIG5vcm1hbGl6ZWQgZGV2aWNlIGNvb3JkaW5hdGVzXG5cdFx0Ly8gZ2xfUG9zaXRpb24gaXMgYSBzcGVjaWFsIHZhcmlhYmxlIHRoYXQgaG9sZHMgdGhlIHBvc2l0aW9uIG9mIGEgdmVydGV4XG4gICAgZ2xfUG9zaXRpb24gPSB2ZWM0KGN1cnJQb3NpdGlvbiwgMC4wLCAxLjApO1xuXG5cdFx0Ly8gdXBkYXRlIHRoZSBzaXplIG9mIGEgcGFydGljbGVzIGJhc2VkIG9uIHRoZSBwcm9wIHBvaW50V2lkdGhcblx0XHRnbF9Qb2ludFNpemUgPSBwb2ludFdpZHRoO1xuXHR9XG5cdGAsXG5cbiAgZnJhZzogYFxuICAvLyBzZXQgdGhlIHByZWNpc2lvbiBvZiBmbG9hdGluZyBwb2ludCBudW1iZXJzXG4gIHByZWNpc2lvbiBtZWRpdW1wIGZsb2F0O1xuXG4gIC8vIHRoaXMgdmFsdWUgaXMgcG9wdWxhdGVkIGJ5IHRoZSB2ZXJ0ZXggc2hhZGVyXG4gIHZhcnlpbmcgdmVjMyBmcmFnQ29sb3I7XG5cbiAgdm9pZCBtYWluKCkge1xuICAgIC8vIGdsX0ZyYWdDb2xvciBpcyBhIHNwZWNpYWwgdmFyaWFibGUgdGhhdCBob2xkcyB0aGUgY29sb3Igb2YgYSBwaXhlbFxuICAgIGdsX0ZyYWdDb2xvciA9IHZlYzQoZnJhZ0NvbG9yLCAxKTtcbiAgfVxuICBgLFxuXG5cdGF0dHJpYnV0ZXM6IHtcblx0XHQvLyBlYWNoIG9mIHRoZXNlIGdldHMgbWFwcGVkIHRvIGEgc2luZ2xlIGVudHJ5IGZvciBlYWNoIG9mIHRoZSBwb2ludHMuXG5cdFx0Ly8gdGhpcyBtZWFucyB0aGUgdmVydGV4IHNoYWRlciB3aWxsIHJlY2VpdmUganVzdCB0aGUgcmVsZXZhbnQgdmFsdWUgZm9yIGEgZ2l2ZW4gcG9pbnQuXG5cdFx0cGFydGljbGVUZXh0dXJlSW5kZXgsXG5cdH0sXG5cblx0dW5pZm9ybXM6IHtcblx0XHQvLyBpbXBvcnRhbnQgdG8gdXNlIGEgZnVuY3Rpb24gaGVyZSBzbyBpdCBnZXRzIHRoZSBuZXcgYnVmZmVyIGVhY2ggcmVuZGVyXG5cdFx0Y3VyclBhcnRpY2xlU3RhdGU6ICgpID0+IGN1cnJQYXJ0aWNsZVN0YXRlLFxuXHRcdHByZXZQYXJ0aWNsZVN0YXRlOiAoKSA9PiBwcmV2UGFydGljbGVTdGF0ZSxcblx0XHRwb2ludFdpZHRoLFxuXHRcdGZsb3dCdWZmZXIsXG5cdH0sXG5cblx0Ly8gc3BlY2lmeSB0aGUgbnVtYmVyIG9mIHBvaW50cyB0byBkcmF3XG5cdGNvdW50OiBudW1QYXJ0aWNsZXMsXG5cblx0Ly8gc3BlY2lmeSB0aGF0IGVhY2ggdmVydGV4IGlzIGEgcG9pbnQgKG5vdCBwYXJ0IG9mIGEgbWVzaClcblx0cHJpbWl0aXZlOiAncG9pbnRzJyxcblxuXHQvLyB3ZSBkb24ndCBjYXJlIGFib3V0IGRlcHRoIGNvbXB1dGF0aW9uc1xuXHRkZXB0aDoge1xuXHQgIGVuYWJsZTogZmFsc2UsXG5cdCAgbWFzazogZmFsc2UsXG5cdH0sXG59KTtcblxuLy8gc3RhcnQgdGhlIGFuaW1hdGlvbiBsb29wXG5jb25zdCBmcmFtZUxvb3AgPSByZWdsLmZyYW1lKCh7IHRpY2sgfSkgPT4ge1xuXHQvLyBjbGVhciB0aGUgYnVmZmVyXG5cdHJlZ2wuY2xlYXIoe1xuXHRcdC8vIGJhY2tncm91bmQgY29sb3IgKGJsYWNrKVxuXHRcdGNvbG9yOiBbMCwgMCwgMCwgMV0sXG5cdFx0ZGVwdGg6IDEsXG5cdFx0c3RlbmNpbDogMCxcblx0fSk7XG5cbiAgLy8gZ2VuZXJhdGUgbmV3IGZsb3cgZXZlcnkgdGlja3NQZXJGbG93IHRpY2tzXG5cdGlmICh0aWNrID09PSAxIHx8IHRpY2sgJSB0aWNrc1BlckZsb3cgPT09IDApIHtcblx0XHRnZW5lcmF0ZUZsb3dEYXRhKCk7XG5cdFx0Z2VuZXJhdGVGbG93QnVmZmVyKCk7XG5cdH1cblxuICAvLyB1bmNvbW1lbnQgYmVsb3cgdG8gc2VlIHRoZSBmbG93IGJ1ZmZlclxuXHQvLyBkcmF3Rmxvd0J1ZmZlcigpO1xuXG5cdC8vIGRyYXcgdGhlIHBvaW50cyB1c2luZyBvdXIgY3JlYXRlZCByZWdsIGZ1bmNcblx0ZHJhd1BhcnRpY2xlcygpO1xuXG5cdC8vIHVwZGF0ZSBwb3NpdGlvbiBvZiBwYXJ0aWNsZXMgaW4gc3RhdGUgYnVmZmVyc1xuXHR1cGRhdGVQYXJ0aWNsZXMoKTtcblxuXHQvLyB1cGRhdGUgcG9pbnRlcnMgZm9yIG5leHQsIGN1cnJlbnQsIGFuZCBwcmV2aW91cyBwYXJ0aWNsZSBzdGF0ZXNcblx0Y3ljbGVQYXJ0aWNsZVN0YXRlcygpO1xuXG5cdC8vIHNpbXBsZSB3YXkgb2Ygc3RvcHBpbmcgdGhlIGFuaW1hdGlvbiBhZnRlciBhIGZldyB0aWNrc1xuXHRpZiAodGljayA9PT0gYW5pbWF0aW9uVGlja0xpbWl0KSB7XG5cdFx0Y29uc29sZS5sb2coYEhpdCB0aWNrICR7dGlja30sIGNhbmNlbGluZyBhbmltYXRpb24gbG9vcGApO1xuXG5cdFx0Ly8gY2FuY2VsIHRoaXMgbG9vcFxuXHRcdGZyYW1lTG9vcC5jYW5jZWwoKTtcblx0fVxufSk7XG4iXX0=
<!DOCTYPE html>
<title>Particles Flowing with WebGL and regl - II</title>
<body>
<script src="https://npmcdn.com/[email protected]/dist/regl.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="dist.js"></script>
</body>
View raw

(Sorry about that, but we can’t show files that are this big right now.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment