Skip to content

Instantly share code, notes, and snippets.

@dancingfrog
Forked from pbeshai/.block
Last active January 5, 2019 15:11
Show Gist options
  • Save dancingfrog/e8dcb9fbc7977de1e1dee8dc028373c0 to your computer and use it in GitHub Desktop.
Save dancingfrog/e8dcb9fbc7977de1e1dee8dc028373c0 to your computer and use it in GitHub Desktop.
Animate 100,000 points with regl - I
license: mit
height: 720
border: no
var regl=createREGL({onDone:function (err, regl) {console.log('Initialization of regl complete\n', regl);main(err, regl);}});function main(t,n){function o(t){var n=d3.randomNormal(0,.05);t.forEach(function(t,o){t.x=(n()+Math.cos(o))*(l/2.5)+l/2,t.y=(n()+Math.sin(o))*(c/2.5)+c/2,t.color=[0,Math.random(),0]})}function i(t){var n=d3.randomNormal(0,.15);t.forEach(function(t){t.x=n()*l+l/2,t.y=n()*c+c/2,t.color=[0,.5*t.color[1],.9]})}function e(t){var o=n({frag:"\n\t\t\t// set the precision of floating point numbers\n\t\t precision highp float;\n\n\t\t // this value is populated by the vertex shader\n\t\t\tvarying vec3 fragColor;\n\n\t\t\tvoid main() {\n\t\t\t\t// gl_FragColor is a special variable that holds the color of a pixel\n\t\t\t\tgl_FragColor = vec4(fragColor, 1);\n\t\t\t}\n\t\t\t",vert:"\n\t\t\t// per vertex attributes\n\t\t\tattribute vec2 positionStart;\n\t\t\tattribute vec2 positionEnd;\n\t\t\tattribute vec3 colorStart;\n\t\t\tattribute vec3 colorEnd;\n\n\t\t\t// variables to send to the fragment shader\n\t\t\tvarying vec3 fragColor;\n\n\t\t\t// values that are the same for all vertices\n\t\t\tuniform float pointWidth;\n\t\t\tuniform float stageWidth;\n\t\t\tuniform float stageHeight;\n\t\t\tuniform float elapsed;\n\t\t\tuniform float duration;\n\n\t\t\t// helper function to transform from pixel space to normalized device coordinates (NDC)\n\t\t\t// in NDC (0,0) is the middle, (-1, -1) is the top left and (1, 1) is the bottom right.\n\t\t\tvec2 normalizeCoords(vec2 position) {\n\t\t\t\t// read in the positions into x and y vars\n\t float x = position[0];\n\t float y = position[1];\n\n\t\t\t\treturn vec2(\n\t\t 2.0 * ((x / stageWidth) - 0.5),\n\t\t // invert y since we think [0,0] is bottom left in pixel space\n\t\t -(2.0 * ((y / stageHeight) - 0.5)));\n\t\t\t}\n\n\t\t\t// helper function to handle cubic easing (copied from d3 for consistency)\n\t\t\t// note there are pre-made easing functions available via glslify.\n\t\t\tfloat easeCubicInOut(float t) {\n\t\t\t\tt *= 2.0;\n t = (t <= 1.0 ? t * t * t : (t -= 2.0) * t * t + 2.0) / 2.0;\n\n if (t > 1.0) {\n t = 1.0;\n }\n\n return t;\n\t\t\t}\n\n\t\t\tvoid main() {\n\t\t\t\t// update the size of a point based on the prop pointWidth\n\t\t\t\tgl_PointSize = pointWidth;\n\n\t\t\t\t// number between 0 and 1 indicating how far through the animation this\n\t\t\t\t// vertex is.\n\t\t\t\tfloat t;\n\n\t // drawing without animation, so show end state immediately\n\t if (duration == 0.0) {\n\t t = 1.0;\n\n\t // otherwise we are animating, so use cubic easing\n\t } else {\n\t // t = easeCubicInOut(elapsed / duration);\n\t t = min(1.0, elapsed / duration);\n\t }\n\n\t // interpolate position\n\t vec2 position = mix(positionStart, positionEnd, t);\n\n\t // interpolate and send color to the fragment shader\n\t fragColor = mix(colorStart, colorEnd, t);\n\n\t\t\t\t// scale to normalized device coordinates\n\t\t\t\t// gl_Position is a special variable that holds the position of a vertex\n\t gl_Position = vec4(normalizeCoords(position), 0.0, 1.0);\n\t\t\t}\n\t\t\t",attributes:{positionStart:t.map(function(t){return[t.sx,t.sy]}),positionEnd:t.map(function(t){return[t.tx,t.ty]}),colorStart:t.map(function(t){return t.colorStart}),colorEnd:t.map(function(t){return t.colorEnd})},uniforms:{pointWidth:n.prop("pointWidth"),stageWidth:n.prop("stageWidth"),stageHeight:n.prop("stageHeight"),duration:n.prop("duration"),elapsed:function(t,n){var o=t.time,i=n.startTime;return void 0===i&&(i=0),1e3*(o-i)}},count:t.length,primitive:"points"});return o}function a(t,o){console.log("animating with new layout"),o.forEach(function(t){t.sx=t.tx,t.sy=t.ty,t.colorStart=t.colorEnd}),t(o),o.forEach(function(t,n){t.tx=t.x,t.ty=t.y,t.colorEnd=t.color});var i=e(o),r=null,p=n.frame(function(t){var e=t.time;null===r&&(r=e),n.clear({color:[0,0,0,1],depth:1}),i({pointWidth:s,stageWidth:l,stageHeight:c,duration:d,startTime:r}),e-r>d/1e3&&(console.log("done animating, moving to next layout"),p.cancel(),u=(u+1)%h.length,a(h[u],o))})}var r=1e5,s=4,l=window.innerWidth,c=window.innerHeight,d=1500,h=[o,i],u=0,p=d3.range(r).map(function(t){return{tx:l/2,ty:c/2,colorEnd:[0,0,0]}});a(h[u],p)}
//# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInNjcmlwdC5qcyJdLCJuYW1lcyI6WyJtYWluIiwiZXJyIiwicmVnbCIsImdyZWVuQ2lyY2xlTGF5b3V0IiwicG9pbnRzIiwiY29uc3QiLCJybmciLCJkMyIsInJhbmRvbU5vcm1hbCIsImZvckVhY2giLCJkIiwiaSIsIngiLCJNYXRoIiwiY29zIiwid2lkdGgiLCJ5Iiwic2luIiwiaGVpZ2h0IiwiY29sb3IiLCJyYW5kb20iLCJibHVlTm9ybWFsTGF5b3V0IiwiY3JlYXRlRHJhd1BvaW50cyIsImRyYXdQb2ludHMiLCJmcmFnIiwidmVydCIsImF0dHJpYnV0ZXMiLCJwb3NpdGlvblN0YXJ0IiwibWFwIiwic3giLCJzeSIsInBvc2l0aW9uRW5kIiwidHgiLCJ0eSIsImNvbG9yU3RhcnQiLCJjb2xvckVuZCIsInVuaWZvcm1zIiwicG9pbnRXaWR0aCIsInByb3AiLCJzdGFnZVdpZHRoIiwic3RhZ2VIZWlnaHQiLCJkdXJhdGlvbiIsImVsYXBzZWQiLCJyZWYiLCJyZWYkMSIsInRpbWUiLCJzdGFydFRpbWUiLCJjb3VudCIsImxlbmd0aCIsInByaW1pdGl2ZSIsImFuaW1hdGUiLCJsYXlvdXQiLCJjb25zb2xlIiwibG9nIiwiZnJhbWVMb29wIiwiZnJhbWUiLCJjbGVhciIsImRlcHRoIiwiY2FuY2VsIiwiY3VycmVudExheW91dCIsImxheW91dHMiLCJudW1Qb2ludHMiLCJ3aW5kb3ciLCJpbm5lcldpZHRoIiwiaW5uZXJIZWlnaHQiLCJyYW5nZSIsIm9uRG9uZSJdLCJtYXBwaW5ncyI6IkFBQUEsUUFBU0EsTUFBS0MsRUFBS0MsR0FVbEIsUUFBU0MsR0FBa0JDLEdBQzFCQyxHQUFNQyxHQUFRQyxHQUFDQyxhQUFjLEVBQUUsSUFDL0JKLEdBQU9LLFFBQVEsU0FBQUMsRUFBQUMsR0FDZEQsRUFBRUUsR0FBS04sSUFBUU8sS0FBS0MsSUFBSUgsS0FBT0ksRUFBUSxLQUFRQSxFQUFRLEVBQ3ZETCxFQUFFTSxHQUFLVixJQUFRTyxLQUFLSSxJQUFJTixLQUFPTyxFQUFTLEtBQVFBLEVBQVMsRUFDekRSLEVBQUVTLE9BQVMsRUFBR04sS0FBS08sU0FBVSxLQUsvQixRQUFTQyxHQUFpQmpCLEdBQ3pCQyxHQUFNQyxHQUFRQyxHQUFDQyxhQUFjLEVBQUUsSUFDL0JKLEdBQU9LLFFBQVEsU0FBQUMsR0FDZEEsRUFBRUUsRUFBS04sSUFBUVMsRUFBVUEsRUFBUSxFQUNqQ0wsRUFBRU0sRUFBS1YsSUFBUVksRUFBV0EsRUFBUyxFQUNuQ1IsRUFBRVMsT0FBUyxFQUFnQixHQUFiVCxFQUFFUyxNQUFNLEdBQVUsTUFTbEMsUUFBU0csR0FBaUJsQixHQUN6QkMsR0FBTWtCLEdBQWFyQixHQUNsQnNCLEtBQU0sa1ZBK0ZOQyxLQUFBLHExRUFUQUMsWUE4RUFDLGNBQVd2QixFQUFBd0IsSUFBQSxTQUFBbEIsR0FBQSxPQUFBQSxFQUFBbUIsR0FBQW5CLEVBQUFvQixNQUNWQyxZQUFBM0IsRUFBQXdCLElBQVUsU0FBQWxCLEdBQUEsT0FBQUEsRUFBQXNCLEdBQUF0QixFQUFBdUIsTUFDVkMsV0FBWTlCLEVBQUt3QixJQUFBLFNBQUFsQixHQUFBLE1BQUFBLEdBQUF3QixhQUNqQkMsU0FBQS9CLEVBQWF3QixJQUFNLFNBQUFsQixHQUFBLE1BQUFBLEdBQUF5QixZQXhFcEJDLFVBOEVBQyxXQUFXbkMsRUFBQW9DLEtBQVksY0F0RXRCQyxXQUFZckMsRUFBS29DLEtBQUssY0FDdEJFLFlBQWF0QyxFQUFLb0MsS0FBSyxlQUVwQkcsU0FBVXZDLEVBQUtvQyxLQUFLLFlBK0V0QkksUUFBQSxTQUFBQyxFQUFBQyxNQUFBQyxHQUFBRixFQUFBRSx5Q0FBQSxHQUFBLEtBQUFBLEVBQUFDLEtBdkVGQyxNQUFPM0MsRUFBTzRDLE9BOEVmQyxVQUFVLFVBeEVWLE9BQU8xQixHQUlSLFFBQVMyQixHQUFRQyxFQUFRL0MsR0FDeEJnRCxRQUFRQyxJQUFJLDZCQUVaakQsRUFBT0ssUUFBUSxTQUFBQyxHQTRFZkEsRUFBQW1CLEdBQVFuQixFQUFBc0IsR0FDUHRCLEVBQUFvQixHQUFBcEIsRUFBQXVCLEdBMUVBdkIsRUFBRXdCLFdBQWF4QixFQUFFeUIsV0FJbEJnQixFQUFPL0MsR0FHUEEsRUFBT0ssUUFBUSxTQUFDQyxFQUFHQyxHQUNsQkQsRUFBRXNCLEdBQUt0QixFQUFFRSxFQUNURixFQUFFdUIsR0FBS3ZCLEVBQUVNLEVBQ1ROLEVBQUV5QixTQUFXekIsRUFBRVMsT0FJaEJkLElBQU1rQixHQUFhRCxFQUFpQmxCLEdBR2hDMEMsRUFBWSxLQUNWUSxFQUFZcEQsRUFBS3FELE1BQU0sU0FBQ1osTUFBRUUsR0FBSUYsRUFBQUUsSUFHakIsUUFBZEMsSUFDSEEsRUFBWUQsR0FJYjNDLEVBQUtzRCxPQUVKckMsT0FBUSxFQUFHLEVBQUcsRUFBRyxHQUNqQnNDLE1BQU8sSUFLUmxDLEdBQ0NjLFdBQUFBLEVBQ0FFLFdBQVl4QixFQUNaeUIsWUFBYXRCLEVBQ2J1QixTQUFBQSxFQUNBSyxVQUFBQSxJQUlHRCxFQUFPQyxFQUFhTCxFQUFXLE1BQ2xDVyxRQUFRQyxJQUFJLHlDQUdaQyxFQUFVSSxTQUdWQyxHQUFpQkEsRUFBZ0IsR0FBS0MsRUFBUVosT0FHOUNFLEVBQVFVLEVBQVFELEdBQWdCdkQsTUExTm5DQyxHQUFNd0QsR0FBWSxJQUNaeEIsRUFBZSxFQUNmdEIsRUFBUStDLE9BQU9DLFdBQ2Y3QyxFQUFTNEMsT0FBT0UsWUFHaEJ2QixFQUFXLEtBdUJYbUIsR0FBV3pELEVBQW1Ca0IsR0FDaENzQyxFQUFnQixFQW1NZHZELEVBQVNHLEdBQUcwRCxNQUFNSixHQUFXakMsSUFBSSxTQUFBakIsR0FBRSxPQUN4Q3FCLEdBQUlqQixFQUFRLEVBQ1prQixHQUFJZixFQUFTLEVBQ2JpQixVQUFXLEVBQUcsRUFBRyxLQUlsQmUsR0FBUVUsRUFBUUQsR0FBZ0J2RCxHQUlqQ0YsTUFFRWdFLE9BQVFsRSIsImZpbGUiOiJzY3JpcHQuanMiLCJzb3VyY2VzQ29udGVudCI6WyJmdW5jdGlvbiBtYWluKGVyciwgcmVnbCkge1xuXHRjb25zdCBudW1Qb2ludHMgPSAxMDAwMDA7XG5cdGNvbnN0IHBvaW50V2lkdGggPSA0O1xuXHRjb25zdCB3aWR0aCA9IHdpbmRvdy5pbm5lcldpZHRoO1xuXHRjb25zdCBoZWlnaHQgPSB3aW5kb3cuaW5uZXJIZWlnaHQ7XG5cblx0Ly8gZHVyYXRpb24gb2YgdGhlIGFuaW1hdGlvbiBpZ25vcmluZyBkZWxheXNcblx0Y29uc3QgZHVyYXRpb24gPSAxNTAwOyAvLyAxNTAwbXMgPSAxLjVzXG5cblx0Ly8gaGVscGVyIHRvIGxheW91dCBwb2ludHMgaW4gYSBncmVlbiBmdXp6eSBjaXJjbGVcblx0ZnVuY3Rpb24gZ3JlZW5DaXJjbGVMYXlvdXQocG9pbnRzKSB7XG5cdFx0Y29uc3Qgcm5nID0gZDMucmFuZG9tTm9ybWFsKDAsIDAuMDUpO1xuXHRcdHBvaW50cy5mb3JFYWNoKChkLCBpKSA9PiB7XG5cdFx0XHRkLnggPSAocm5nKCkgKyBNYXRoLmNvcyhpKSkgKiAod2lkdGggLyAyLjUpICsgKHdpZHRoIC8gMik7XG5cdFx0XHRkLnkgPSAocm5nKCkgKyBNYXRoLnNpbihpKSkgKiAoaGVpZ2h0IC8gMi41KSArIChoZWlnaHQgLyAyKTtcblx0XHRcdGQuY29sb3IgPSBbMCwgTWF0aC5yYW5kb20oKSwgMF07IC8vIHJhbmRvbSBhbW91bnQgb2YgZ3JlZW5cblx0XHR9KTtcblx0fVxuXG5cdC8vIGhlbHBlciB0byBsYXlvdXQgcG9pbnRzIGluIGEgbm9ybWFsbHkgZGlzdHJpYnV0ZWQgYXJlYSwgY29sb3JlZCBibHVlXG5cdGZ1bmN0aW9uIGJsdWVOb3JtYWxMYXlvdXQocG9pbnRzKSB7XG5cdFx0Y29uc3Qgcm5nID0gZDMucmFuZG9tTm9ybWFsKDAsIDAuMTUpO1xuXHRcdHBvaW50cy5mb3JFYWNoKGQgPT4ge1xuXHRcdFx0ZC54ID0gKHJuZygpICogd2lkdGgpICsgKHdpZHRoIC8gMik7XG5cdFx0XHRkLnkgPSAocm5nKCkgKiBoZWlnaHQpICsgKGhlaWdodCAvIDIpO1xuXHRcdFx0ZC5jb2xvciA9IFswLCBkLmNvbG9yWzFdICogMC41LCAwLjldOyAvLyBzb21lIHByZXZpb3VzIGdyZWVuIGFuZCAwLjkgYmx1ZVxuXHRcdH0pO1xuXHR9XG5cblx0Ly8gc2V0IHRoZSBvcmRlciBvZiB0aGUgbGF5b3V0cyBhbmQgc29tZSBpbml0aWFsIGFuaW1hdGlvbiBzdGF0ZVxuXHRjb25zdCBsYXlvdXRzID0gW2dyZWVuQ2lyY2xlTGF5b3V0LCBibHVlTm9ybWFsTGF5b3V0XTtcblx0bGV0IGN1cnJlbnRMYXlvdXQgPSAwOyAvLyBzdGFydCB3aXRoIGdyZWVuIGNpcmNsZSBsYXlvdXRcblxuXHQvLyBmdW5jdGlvbiB0byBjb21waWxlIGEgZHJhdyBwb2ludHMgcmVnbCBmdW5jXG5cdGZ1bmN0aW9uIGNyZWF0ZURyYXdQb2ludHMocG9pbnRzKSB7XG5cdFx0Y29uc3QgZHJhd1BvaW50cyA9IHJlZ2woe1xuXHRcdFx0ZnJhZzogYFxuXHRcdFx0Ly8gc2V0IHRoZSBwcmVjaXNpb24gb2YgZmxvYXRpbmcgcG9pbnQgbnVtYmVyc1xuXHRcdCAgcHJlY2lzaW9uIGhpZ2hwIGZsb2F0O1xuXG5cdFx0ICAvLyB0aGlzIHZhbHVlIGlzIHBvcHVsYXRlZCBieSB0aGUgdmVydGV4IHNoYWRlclxuXHRcdFx0dmFyeWluZyB2ZWMzIGZyYWdDb2xvcjtcblxuXHRcdFx0dm9pZCBtYWluKCkge1xuXHRcdFx0XHQvLyBnbF9GcmFnQ29sb3IgaXMgYSBzcGVjaWFsIHZhcmlhYmxlIHRoYXQgaG9sZHMgdGhlIGNvbG9yIG9mIGEgcGl4ZWxcblx0XHRcdFx0Z2xfRnJhZ0NvbG9yID0gdmVjNChmcmFnQ29sb3IsIDEpO1xuXHRcdFx0fVxuXHRcdFx0YCxcblxuXHRcdFx0dmVydDogYFxuXHRcdFx0Ly8gcGVyIHZlcnRleCBhdHRyaWJ1dGVzXG5cdFx0XHRhdHRyaWJ1dGUgdmVjMiBwb3NpdGlvblN0YXJ0O1xuXHRcdFx0YXR0cmlidXRlIHZlYzIgcG9zaXRpb25FbmQ7XG5cdFx0XHRhdHRyaWJ1dGUgdmVjMyBjb2xvclN0YXJ0O1xuXHRcdFx0YXR0cmlidXRlIHZlYzMgY29sb3JFbmQ7XG5cblx0XHRcdC8vIHZhcmlhYmxlcyB0byBzZW5kIHRvIHRoZSBmcmFnbWVudCBzaGFkZXJcblx0XHRcdHZhcnlpbmcgdmVjMyBmcmFnQ29sb3I7XG5cblx0XHRcdC8vIHZhbHVlcyB0aGF0IGFyZSB0aGUgc2FtZSBmb3IgYWxsIHZlcnRpY2VzXG5cdFx0XHR1bmlmb3JtIGZsb2F0IHBvaW50V2lkdGg7XG5cdFx0XHR1bmlmb3JtIGZsb2F0IHN0YWdlV2lkdGg7XG5cdFx0XHR1bmlmb3JtIGZsb2F0IHN0YWdlSGVpZ2h0O1xuXHRcdFx0dW5pZm9ybSBmbG9hdCBlbGFwc2VkO1xuXHRcdFx0dW5pZm9ybSBmbG9hdCBkdXJhdGlvbjtcblxuXHRcdFx0Ly8gaGVscGVyIGZ1bmN0aW9uIHRvIHRyYW5zZm9ybSBmcm9tIHBpeGVsIHNwYWNlIHRvIG5vcm1hbGl6ZWQgZGV2aWNlIGNvb3JkaW5hdGVzIChOREMpXG5cdFx0XHQvLyBpbiBOREMgKDAsMCkgaXMgdGhlIG1pZGRsZSwgKC0xLCAtMSkgaXMgdGhlIHRvcCBsZWZ0IGFuZCAoMSwgMSkgaXMgdGhlIGJvdHRvbSByaWdodC5cblx0XHRcdHZlYzIgbm9ybWFsaXplQ29vcmRzKHZlYzIgcG9zaXRpb24pIHtcblx0XHRcdFx0Ly8gcmVhZCBpbiB0aGUgcG9zaXRpb25zIGludG8geCBhbmQgeSB2YXJzXG5cdCAgICAgIGZsb2F0IHggPSBwb3NpdGlvblswXTtcblx0ICAgICAgZmxvYXQgeSA9IHBvc2l0aW9uWzFdO1xuXG5cdFx0XHRcdHJldHVybiB2ZWMyKFxuXHRcdCAgICAgIDIuMCAqICgoeCAvIHN0YWdlV2lkdGgpIC0gMC41KSxcblx0XHQgICAgICAvLyBpbnZlcnQgeSBzaW5jZSB3ZSB0aGluayBbMCwwXSBpcyBib3R0b20gbGVmdCBpbiBwaXhlbCBzcGFjZVxuXHRcdCAgICAgIC0oMi4wICogKCh5IC8gc3RhZ2VIZWlnaHQpIC0gMC41KSkpO1xuXHRcdFx0fVxuXG5cdFx0XHQvLyBoZWxwZXIgZnVuY3Rpb24gdG8gaGFuZGxlIGN1YmljIGVhc2luZyAoY29waWVkIGZyb20gZDMgZm9yIGNvbnNpc3RlbmN5KVxuXHRcdFx0Ly8gbm90ZSB0aGVyZSBhcmUgcHJlLW1hZGUgZWFzaW5nIGZ1bmN0aW9ucyBhdmFpbGFibGUgdmlhIGdsc2xpZnkuXG5cdFx0XHRmbG9hdCBlYXNlQ3ViaWNJbk91dChmbG9hdCB0KSB7XG5cdFx0XHRcdHQgKj0gMi4wO1xuICAgICAgICB0ID0gKHQgPD0gMS4wID8gdCAqIHQgKiB0IDogKHQgLT0gMi4wKSAqIHQgKiB0ICsgMi4wKSAvIDIuMDtcblxuICAgICAgICBpZiAodCA+IDEuMCkge1xuICAgICAgICAgIHQgPSAxLjA7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gdDtcblx0XHRcdH1cblxuXHRcdFx0dm9pZCBtYWluKCkge1xuXHRcdFx0XHQvLyB1cGRhdGUgdGhlIHNpemUgb2YgYSBwb2ludCBiYXNlZCBvbiB0aGUgcHJvcCBwb2ludFdpZHRoXG5cdFx0XHRcdGdsX1BvaW50U2l6ZSA9IHBvaW50V2lkdGg7XG5cblx0XHRcdFx0Ly8gbnVtYmVyIGJldHdlZW4gMCBhbmQgMSBpbmRpY2F0aW5nIGhvdyBmYXIgdGhyb3VnaCB0aGUgYW5pbWF0aW9uIHRoaXNcblx0XHRcdFx0Ly8gdmVydGV4IGlzLlxuXHRcdFx0XHRmbG9hdCB0O1xuXG5cdCAgICAgIC8vIGRyYXdpbmcgd2l0aG91dCBhbmltYXRpb24sIHNvIHNob3cgZW5kIHN0YXRlIGltbWVkaWF0ZWx5XG5cdCAgICAgIGlmIChkdXJhdGlvbiA9PSAwLjApIHtcblx0ICAgICAgICB0ID0gMS4wO1xuXG5cdCAgICAgIC8vIG90aGVyd2lzZSB3ZSBhcmUgYW5pbWF0aW5nLCBzbyB1c2UgY3ViaWMgZWFzaW5nXG5cdCAgICAgIH0gZWxzZSB7XG5cdCAgICAgICAgLy8gdCA9IGVhc2VDdWJpY0luT3V0KGVsYXBzZWQgLyBkdXJhdGlvbik7XG5cdCAgICAgICAgdCA9IG1pbigxLjAsIGVsYXBzZWQgLyBkdXJhdGlvbik7XG5cdCAgICAgIH1cblxuXHQgICAgICAvLyBpbnRlcnBvbGF0ZSBwb3NpdGlvblxuXHQgICAgICB2ZWMyIHBvc2l0aW9uID0gbWl4KHBvc2l0aW9uU3RhcnQsIHBvc2l0aW9uRW5kLCB0KTtcblxuXHQgICAgICAvLyBpbnRlcnBvbGF0ZSBhbmQgc2VuZCBjb2xvciB0byB0aGUgZnJhZ21lbnQgc2hhZGVyXG5cdCAgICAgIGZyYWdDb2xvciA9IG1peChjb2xvclN0YXJ0LCBjb2xvckVuZCwgdCk7XG5cblx0XHRcdFx0Ly8gc2NhbGUgdG8gbm9ybWFsaXplZCBkZXZpY2UgY29vcmRpbmF0ZXNcblx0XHRcdFx0Ly8gZ2xfUG9zaXRpb24gaXMgYSBzcGVjaWFsIHZhcmlhYmxlIHRoYXQgaG9sZHMgdGhlIHBvc2l0aW9uIG9mIGEgdmVydGV4XG5cdCAgICAgIGdsX1Bvc2l0aW9uID0gdmVjNChub3JtYWxpemVDb29yZHMocG9zaXRpb24pLCAwLjAsIDEuMCk7XG5cdFx0XHR9XG5cdFx0XHRgLFxuXG5cdFx0XHRhdHRyaWJ1dGVzOiB7XG5cdFx0XHRcdC8vIGVhY2ggb2YgdGhlc2UgZ2V0cyBtYXBwZWQgdG8gYSBzaW5nbGUgZW50cnkgZm9yIGVhY2ggb2YgdGhlIHBvaW50cy5cblx0XHRcdFx0Ly8gdGhpcyBtZWFucyB0aGUgdmVydGV4IHNoYWRlciB3aWxsIHJlY2VpdmUganVzdCB0aGUgcmVsZXZhbnQgdmFsdWUgZm9yIGEgZ2l2ZW4gcG9pbnQuXG5cdFx0XHRcdHBvc2l0aW9uU3RhcnQ6IHBvaW50cy5tYXAoZCA9PiBbZC5zeCwgZC5zeV0pLFxuXHRcdFx0XHRwb3NpdGlvbkVuZDogcG9pbnRzLm1hcChkID0+IFtkLnR4LCBkLnR5XSksXG5cdFx0XHRcdGNvbG9yU3RhcnQ6IHBvaW50cy5tYXAoZCA9PiBkLmNvbG9yU3RhcnQpLFxuXHRcdFx0XHRjb2xvckVuZDogcG9pbnRzLm1hcChkID0+IGQuY29sb3JFbmQpLFxuXHRcdFx0fSxcblxuXHRcdFx0dW5pZm9ybXM6IHtcblx0XHRcdFx0Ly8gYnkgdXNpbmcgYHJlZ2wucHJvcGAgdG8gcGFzcyB0aGVzZSBpbiwgd2UgY2FuIHNwZWNpZnkgdGhlbSBhcyBhcmd1bWVudHNcblx0XHRcdFx0Ly8gdG8gb3VyIGRyYXdQb2ludHMgZnVuY3Rpb25cblx0XHRcdFx0cG9pbnRXaWR0aDogcmVnbC5wcm9wKCdwb2ludFdpZHRoJyksXG5cblx0XHRcdFx0Ly8gcmVnbCBhY3R1YWxseSBwcm92aWRlcyB0aGVzZSBhcyB2aWV3cG9ydFdpZHRoIGFuZCB2aWV3cG9ydEhlaWdodCBidXQgSVxuXHRcdFx0XHQvLyBhbSB1c2luZyB0aGVzZSBvdXRzaWRlIGFuZCBJIHdhbnQgdG8gZW5zdXJlIHRoZXkgYXJlIHRoZSBzYW1lIG51bWJlcnMsXG5cdFx0XHRcdC8vIHNvIEkgYW0gZXhwbGljaXRseSBwYXNzaW5nIHRoZW0gaW4uXG5cdFx0XHRcdHN0YWdlV2lkdGg6IHJlZ2wucHJvcCgnc3RhZ2VXaWR0aCcpLFxuXHRcdFx0XHRzdGFnZUhlaWdodDogcmVnbC5wcm9wKCdzdGFnZUhlaWdodCcpLFxuXG5cdCAgICAgIGR1cmF0aW9uOiByZWdsLnByb3AoJ2R1cmF0aW9uJyksXG5cdFx0XHRcdC8vIHRpbWUgaW4gbWlsbGlzZWNvbmRzIHNpbmNlIHRoZSBwcm9wIHN0YXJ0VGltZSAoaS5lLiB0aW1lIGVsYXBzZWQpXG5cdFx0XHRcdC8vIG5vdGUgdGhhdCBgdGltZWAgaXMgcGFzc2VkIGJ5IHJlZ2wgd2hlcmVhcyBgc3RhcnRUaW1lYCBpcyBhIHByb3AgcGFzc2VkXG5cdFx0XHRcdC8vIHRvIHRoZSBkcmF3UG9pbnRzIGZ1bmN0aW9uLlxuXHQgICAgICBlbGFwc2VkOiAoeyB0aW1lIH0sIHsgc3RhcnRUaW1lID0gMCB9KSA9PiAodGltZSAtIHN0YXJ0VGltZSkgKiAxMDAwLFxuXHRcdFx0fSxcblxuXHRcdFx0Ly8gc3BlY2lmeSB0aGUgbnVtYmVyIG9mIHBvaW50cyB0byBkcmF3XG5cdFx0XHRjb3VudDogcG9pbnRzLmxlbmd0aCxcblxuXHRcdFx0Ly8gc3BlY2lmeSB0aGF0IGVhY2ggdmVydGV4IGlzIGEgcG9pbnQgKG5vdCBwYXJ0IG9mIGEgbWVzaClcblx0XHRcdHByaW1pdGl2ZTogJ3BvaW50cycsXG5cdFx0fSk7XG5cblx0XHRyZXR1cm4gZHJhd1BvaW50cztcblx0fVxuXG5cdC8vIGZ1bmN0aW9uIHRvIHN0YXJ0IHRoZSBhbmltYXRpb24gbG9vcCAobm90ZTogdGltZSBpcyBpbiBzZWNvbmRzKVxuXHRmdW5jdGlvbiBhbmltYXRlKGxheW91dCwgcG9pbnRzKSB7XG5cdFx0Y29uc29sZS5sb2coJ2FuaW1hdGluZyB3aXRoIG5ldyBsYXlvdXQnKTtcblx0XHQvLyBtYWtlIHByZXZpb3VzIGVuZCB0aGUgbmV3IGJlZ2lubmluZ1xuXHRcdHBvaW50cy5mb3JFYWNoKGQgPT4ge1xuXHRcdFx0ZC5zeCA9IGQudHg7XG5cdFx0XHRkLnN5ID0gZC50eTtcblx0XHRcdGQuY29sb3JTdGFydCA9IGQuY29sb3JFbmQ7XG5cdFx0fSk7XG5cblx0XHQvLyBsYXlvdXQgcG9pbnRzXG5cdFx0bGF5b3V0KHBvaW50cyk7XG5cblx0XHQvLyBjb3B5IGxheW91dCB4IHkgdG8gZW5kIHBvc2l0aW9uc1xuXHRcdHBvaW50cy5mb3JFYWNoKChkLCBpKSA9PiB7XG5cdFx0XHRkLnR4ID0gZC54O1xuXHRcdFx0ZC50eSA9IGQueTtcblx0XHRcdGQuY29sb3JFbmQgPSBkLmNvbG9yO1xuXHRcdH0pO1xuXG5cdFx0Ly8gY3JlYXRlIHRoZSByZWdsIGZ1bmN0aW9uIHdpdGggdGhlIG5ldyBzdGFydCBhbmQgZW5kIHBvaW50c1xuXHRcdGNvbnN0IGRyYXdQb2ludHMgPSBjcmVhdGVEcmF3UG9pbnRzKHBvaW50cyk7XG5cblx0XHQvLyBzdGFydCBhbiBhbmltYXRpb24gbG9vcFxuXHRcdGxldCBzdGFydFRpbWUgPSBudWxsOyAvLyBpbiBzZWNvbmRzXG5cdFx0Y29uc3QgZnJhbWVMb29wID0gcmVnbC5mcmFtZSgoeyB0aW1lIH0pID0+IHtcblx0XHRcdC8vIGtlZXAgdHJhY2sgb2Ygc3RhcnQgdGltZSBzbyB3ZSBjYW4gZ2V0IHRpbWUgZWxhcHNlZFxuXHRcdFx0Ly8gdGhpcyBpcyBpbXBvcnRhbnQgc2luY2UgdGltZSBkb2Vzbid0IHJlc2V0IHdoZW4gc3RhcnRpbmcgbmV3IGFuaW1hdGlvbnNcblx0XHRcdGlmIChzdGFydFRpbWUgPT09IG51bGwpIHtcblx0XHRcdFx0c3RhcnRUaW1lID0gdGltZTtcblx0XHRcdH1cblxuXHRcdFx0Ly8gY2xlYXIgdGhlIGJ1ZmZlclxuXHRcdFx0cmVnbC5jbGVhcih7XG5cdFx0XHRcdC8vIGJhY2tncm91bmQgY29sb3IgKGJsYWNrKVxuXHRcdFx0XHRjb2xvcjogWzAsIDAsIDAsIDFdLFxuXHRcdFx0XHRkZXB0aDogMSxcblx0XHRcdH0pO1xuXG5cdFx0XHQvLyBkcmF3IHRoZSBwb2ludHMgdXNpbmcgb3VyIGNyZWF0ZWQgcmVnbCBmdW5jXG5cdFx0XHQvLyBub3RlIHRoYXQgdGhlIGFyZ3VtZW50cyBhcmUgYXZhaWxhYmxlIHZpYSBgcmVnbC5wcm9wYC5cblx0XHRcdGRyYXdQb2ludHMoe1xuXHRcdFx0XHRwb2ludFdpZHRoLFxuXHRcdFx0XHRzdGFnZVdpZHRoOiB3aWR0aCxcblx0XHRcdFx0c3RhZ2VIZWlnaHQ6IGhlaWdodCxcblx0XHRcdFx0ZHVyYXRpb24sXG5cdFx0XHRcdHN0YXJ0VGltZSxcblx0XHRcdH0pO1xuXG5cdFx0XHQvLyBpZiB3ZSBoYXZlIGV4Y2VlZGVkIHRoZSBtYXhpbXVtIGR1cmF0aW9uLCBtb3ZlIG9uIHRvIHRoZSBuZXh0IGFuaW1hdGlvblxuXHRcdFx0aWYgKHRpbWUgLSBzdGFydFRpbWUgPiAoZHVyYXRpb24gLyAxMDAwKSkge1xuXHRcdFx0XHRjb25zb2xlLmxvZygnZG9uZSBhbmltYXRpbmcsIG1vdmluZyB0byBuZXh0IGxheW91dCcpO1xuXG5cdFx0XHRcdC8vIGNhbmNlbCB0aGlzIGxvb3AsIHdlIGFyZSBnb2luZyB0byBzdGFydCBhbm90aGVyXG5cdFx0XHRcdGZyYW1lTG9vcC5jYW5jZWwoKTtcblxuXHRcdFx0XHQvLyBpbmNyZW1lbnQgdG8gdXNlIG5leHQgbGF5b3V0IGZ1bmN0aW9uXG5cdFx0XHRcdGN1cnJlbnRMYXlvdXQgPSAoY3VycmVudExheW91dCArIDEpICUgbGF5b3V0cy5sZW5ndGg7XG5cblx0XHRcdFx0Ly8gc3RhcnQgYSBuZXcgYW5pbWF0aW9uIGxvb3Agd2l0aCBuZXh0IGxheW91dCBmdW5jdGlvblxuXHRcdFx0XHRhbmltYXRlKGxheW91dHNbY3VycmVudExheW91dF0sIHBvaW50cyk7XG5cdFx0XHR9XG5cdFx0fSk7XG5cdH1cblxuXHQvLyBjcmVhdGUgaW5pdGlhbCBzZXQgb2YgcG9pbnRzXG5cdC8vIGluaXRpYWxpemUgd2l0aCBhbGwgdGhlIHBvaW50cyBpbiB0aGUgbWlkZGxlIG9mIHRoZSBzY3JlZW4gYW5kIGJsYWNrXG5cdGNvbnN0IHBvaW50cyA9IGQzLnJhbmdlKG51bVBvaW50cykubWFwKGkgPT4gKHtcblx0XHR0eDogd2lkdGggLyAyLFxuXHRcdHR5OiBoZWlnaHQgLyAyLFxuXHRcdGNvbG9yRW5kOiBbMCwgMCwgMF0sXG5cdH0pKTtcblxuXHQvLyBzdGFydCB0aGUgaW5pdGlhbCBhbmltYXRpb25cblx0YW5pbWF0ZShsYXlvdXRzW2N1cnJlbnRMYXlvdXRdLCBwb2ludHMpO1xufVxuXG4vLyBpbml0aWFsaXplIHJlZ2xcbnJlZ2woe1xuICAvLyBjYWxsYmFjayB3aGVuIHJlZ2wgaXMgaW5pdGlhbGl6ZWRcbiAgb25Eb25lOiBtYWluXG59KTtcbiJdfQ==
<!DOCTYPE html>
<title>Animate 100,000 points with regl - I</title>
<body>
<script src="https://npmcdn.com/regl/dist/regl.min.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="dist.js"></script>
</body>
var regl=createREGL({onDone:function (err, regl) {console.log('Initialization of regl complete\n', regl);main(err, regl);}});
function main(err, regl) {
const numPoints = 100000;
const pointWidth = 4;
const width = window.innerWidth;
const height = window.innerHeight;
// duration of the animation ignoring delays
const duration = 1500; // 1500ms = 1.5s
// helper to layout points in a green fuzzy circle
function greenCircleLayout(points) {
const rng = d3.randomNormal(0, 0.05);
points.forEach((d, i) => {
d.x = (rng() + Math.cos(i)) * (width / 2.5) + (width / 2);
d.y = (rng() + Math.sin(i)) * (height / 2.5) + (height / 2);
d.color = [0, Math.random(), 0]; // random amount of green
});
}
// helper to layout points in a normally distributed area, colored blue
function blueNormalLayout(points) {
const rng = d3.randomNormal(0, 0.15);
points.forEach(d => {
d.x = (rng() * width) + (width / 2);
d.y = (rng() * height) + (height / 2);
d.color = [0, d.color[1] * 0.5, 0.9]; // some previous green and 0.9 blue
});
}
// set the order of the layouts and some initial animation state
const layouts = [greenCircleLayout, blueNormalLayout];
let currentLayout = 0; // start with green circle layout
// function to compile a draw points regl func
function createDrawPoints(points) {
const drawPoints = regl({
frag: `
// set the precision of floating point numbers
precision highp float;
// this value is populated by the vertex shader
varying vec3 fragColor;
void main() {
// gl_FragColor is a special variable that holds the color of a pixel
gl_FragColor = vec4(fragColor, 1);
}
`,
vert: `
// per vertex attributes
attribute vec2 positionStart;
attribute vec2 positionEnd;
attribute vec3 colorStart;
attribute vec3 colorEnd;
// variables to send to the fragment shader
varying vec3 fragColor;
// values that are the same for all vertices
uniform float pointWidth;
uniform float stageWidth;
uniform float stageHeight;
uniform float elapsed;
uniform float duration;
// helper function to transform from pixel space to normalized device coordinates (NDC)
// in NDC (0,0) is the middle, (-1, 1) is the top left and (1, -1) is the bottom right.
vec2 normalizeCoords(vec2 position) {
// read in the positions into x and y vars
float x = position[0];
float y = position[1];
return vec2(
2.0 * ((x / stageWidth) - 0.5),
// invert y since we think [0,0] is bottom left in pixel space
-(2.0 * ((y / stageHeight) - 0.5)));
}
// helper function to handle cubic easing (copied from d3 for consistency)
// note there are pre-made easing functions available via glslify.
float easeCubicInOut(float t) {
t *= 2.0;
t = (t <= 1.0 ? t * t * t : (t -= 2.0) * t * t + 2.0) / 2.0;
if (t > 1.0) {
t = 1.0;
}
return t;
}
void main() {
// update the size of a point based on the prop pointWidth
gl_PointSize = pointWidth;
// number between 0 and 1 indicating how far through the animation this
// vertex is.
float t;
// drawing without animation, so show end state immediately
if (duration == 0.0) {
t = 1.0;
// otherwise we are animating, so use cubic easing
} else {
t = easeCubicInOut(elapsed / duration);
}
// interpolate position
vec2 position = mix(positionStart, positionEnd, t);
// interpolate and send color to the fragment shader
fragColor = mix(colorStart, colorEnd, t);
// scale to normalized device coordinates
// gl_Position is a special variable that holds the position of a vertex
gl_Position = vec4(normalizeCoords(position), 0.0, 1.0);
}
`,
attributes: {
// each of these gets mapped to a single entry for each of the points.
// this means the vertex shader will receive just the relevant value for a given point.
positionStart: points.map(d => [d.sx, d.sy]),
positionEnd: points.map(d => [d.tx, d.ty]),
colorStart: points.map(d => d.colorStart),
colorEnd: points.map(d => d.colorEnd),
},
uniforms: {
// by using `regl.prop` to pass these in, we can specify them as arguments
// to our drawPoints function
pointWidth: regl.prop('pointWidth'),
// regl actually provides these as viewportWidth and viewportHeight but I
// am using these outside and I want to ensure they are the same numbers,
// so I am explicitly passing them in.
stageWidth: regl.prop('stageWidth'),
stageHeight: regl.prop('stageHeight'),
duration: regl.prop('duration'),
// time in milliseconds since the prop startTime (i.e. time elapsed)
// note that `time` is passed by regl whereas `startTime` is a prop passed
// to the drawPoints function.
elapsed: ({ time }, { startTime = 0 }) => (time - startTime) * 1000,
},
// specify the number of points to draw
count: points.length,
// specify that each vertex is a point (not part of a mesh)
primitive: 'points',
});
return drawPoints;
}
// function to start the animation loop (note: time is in seconds)
function animate(layout, points) {
console.log('animating with new layout');
// make previous end the new beginning
points.forEach(d => {
d.sx = d.tx;
d.sy = d.ty;
d.colorStart = d.colorEnd;
});
// layout points
layout(points);
// copy layout x y to end positions
points.forEach((d, i) => {
d.tx = d.x;
d.ty = d.y;
d.colorEnd = d.color;
});
// create the regl function with the new start and end points
const drawPoints = createDrawPoints(points);
// start an animation loop
let startTime = null; // in seconds
const frameLoop = regl.frame(({ time }) => {
// keep track of start time so we can get time elapsed
// this is important since time doesn't reset when starting new animations
if (startTime === null) {
startTime = time;
}
// clear the buffer
regl.clear({
// background color (black)
color: [0, 0, 0, 1],
depth: 1,
});
// draw the points using our created regl func
// note that the arguments are available via `regl.prop`.
drawPoints({
pointWidth,
stageWidth: width,
stageHeight: height,
duration,
startTime,
});
// if we have exceeded the maximum duration, move on to the next animation
if (time - startTime > (duration / 1000)) {
console.log('done animating, moving to next layout');
// cancel this loop, we are going to start another
frameLoop.cancel();
// increment to use next layout function
currentLayout = (currentLayout + 1) % layouts.length;
// start a new animation loop with next layout function
animate(layouts[currentLayout], points);
}
});
}
// create initial set of points
// initialize with all the points in the middle of the screen and black
const points = d3.range(numPoints).map(i => ({
tx: width / 2,
ty: height / 2,
colorEnd: [0, 0, 0],
}));
// start the initial animation
animate(layouts[currentLayout], points);
}
// initialize regl
regl({
// callback when regl is initialized
onDone: main
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment