// Generated by CoffeeScript 1.8.0 (function() { var Framebuffer, Heights, Node, Shader, Texture, WebGLHeatmap, fragmentShaderBlit, nukeVendorPrefix, textureFloatShims, vertexShaderBlit, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; nukeVendorPrefix = function() { var getExtension, getSupportedExtensions, vendorRe, vendors; if (window.WebGLRenderingContext != null) { vendors = ['WEBKIT', 'MOZ', 'MS', 'O']; vendorRe = /^WEBKIT_(.*)|MOZ_(.*)|MS_(.*)|O_(.*)/; getExtension = WebGLRenderingContext.prototype.getExtension; WebGLRenderingContext.prototype.getExtension = function(name) { var extobj, match, vendor, _i, _len; match = name.match(vendorRe); if (match !== null) { name = match[1]; } extobj = getExtension.call(this, name); if (extobj === null) { for (_i = 0, _len = vendors.length; _i < _len; _i++) { vendor = vendors[_i]; extobj = getExtension.call(this, vendor + '_' + name); if (extobj !== null) { return extobj; } } return null; } else { return extobj; } }; getSupportedExtensions = WebGLRenderingContext.prototype.getSupportedExtensions; return WebGLRenderingContext.prototype.getSupportedExtensions = function() { var extension, match, result, supported, _i, _len; supported = getSupportedExtensions.call(this); result = []; for (_i = 0, _len = supported.length; _i < _len; _i++) { extension = supported[_i]; match = extension.match(vendorRe); if (match !== null) { extension = match[1]; } if (__indexOf.call(result, extension) < 0) { result.push(extension); } } return result; }; } }; textureFloatShims = function() { var checkColorBuffer, checkFloatLinear, checkSupport, checkTexture, createSourceCanvas, getExtension, getSupportedExtensions, name, shimExtensions, shimLookup, unshimExtensions, unshimLookup, _i, _len; createSourceCanvas = function() { var canvas, ctx, imageData; canvas = document.createElement('canvas'); canvas.width = 2; canvas.height = 2; ctx = canvas.getContext('2d'); imageData = ctx.getImageData(0, 0, 2, 2); imageData.data.set(new Uint8ClampedArray([0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255])); ctx.putImageData(imageData, 0, 0); return canvas; }; createSourceCanvas(); checkFloatLinear = function(gl, sourceType) { var buffer, cleanup, fragmentShader, framebuffer, positionLoc, program, readBuffer, result, source, sourceCanvas, sourceLoc, target, vertexShader, vertices; program = gl.createProgram(); vertexShader = gl.createShader(gl.VERTEX_SHADER); gl.attachShader(program, vertexShader); gl.shaderSource(vertexShader, 'attribute vec2 position;\nvoid main(){\n gl_Position = vec4(position, 0.0, 1.0);\n}'); gl.compileShader(vertexShader); if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) { throw gl.getShaderInfoLog(vertexShader); } fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); gl.attachShader(program, fragmentShader); gl.shaderSource(fragmentShader, 'uniform sampler2D source;\nvoid main(){\n gl_FragColor = texture2D(source, vec2(1.0, 1.0));\n}'); gl.compileShader(fragmentShader); if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) { throw gl.getShaderInfoLog(fragmentShader); } gl.linkProgram(program); if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { throw gl.getProgramInfoLog(program); } gl.useProgram(program); cleanup = function() { gl.deleteShader(fragmentShader); gl.deleteShader(vertexShader); gl.deleteProgram(program); gl.deleteBuffer(buffer); gl.deleteTexture(source); gl.deleteTexture(target); gl.deleteFramebuffer(framebuffer); gl.bindBuffer(gl.ARRAY_BUFFER, null); gl.useProgram(null); gl.bindTexture(gl.TEXTURE_2D, null); return gl.bindFramebuffer(gl.FRAMEBUFFER, null); }; target = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, target); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 2, 2, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); framebuffer = gl.createFramebuffer(); gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, target, 0); sourceCanvas = createSourceCanvas(); source = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, source); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, sourceType, sourceCanvas); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); vertices = new Float32Array([1, 1, -1, 1, -1, -1, 1, 1, -1, -1, 1, -1]); buffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buffer); gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW); positionLoc = gl.getAttribLocation(program, 'position'); sourceLoc = gl.getUniformLocation(program, 'source'); gl.enableVertexAttribArray(positionLoc); gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0); gl.uniform1i(sourceLoc, 0); gl.drawArrays(gl.TRIANGLES, 0, 6); readBuffer = new Uint8Array(4 * 4); gl.readPixels(0, 0, 2, 2, gl.RGBA, gl.UNSIGNED_BYTE, readBuffer); result = Math.abs(readBuffer[0] - 127) < 10; cleanup(); return result; }; checkTexture = function(gl, targetType) { var target; target = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, target); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 2, 2, 0, gl.RGBA, targetType, null); if (gl.getError() === 0) { gl.deleteTexture(target); return true; } else { gl.deleteTexture(target); return false; } }; checkColorBuffer = function(gl, targetType) { var check, framebuffer, target; target = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, target); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 2, 2, 0, gl.RGBA, targetType, null); framebuffer = gl.createFramebuffer(); gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, target, 0); check = gl.checkFramebufferStatus(gl.FRAMEBUFFER); gl.deleteTexture(target); gl.deleteFramebuffer(framebuffer); gl.bindTexture(gl.TEXTURE_2D, null); gl.bindFramebuffer(gl.FRAMEBUFFER, null); if (check === gl.FRAMEBUFFER_COMPLETE) { return true; } else { return false; } }; shimExtensions = []; shimLookup = {}; unshimExtensions = []; checkSupport = function() { var canvas, extobj, gl, halfFloatExt, halfFloatTexturing, singleFloatExt, singleFloatTexturing; canvas = document.createElement('canvas'); gl = null; try { gl = canvas.getContext('experimental-webgl'); if (gl === null) { gl = canvas.getContext('webgl'); } } catch (_error) {} if (gl != null) { singleFloatExt = gl.getExtension('OES_texture_float'); if (singleFloatExt === null) { if (checkTexture(gl, gl.FLOAT)) { singleFloatTexturing = true; shimExtensions.push('OES_texture_float'); shimLookup.OES_texture_float = { shim: true }; } else { singleFloatTexturing = false; unshimExtensions.push('OES_texture_float'); } } else { if (checkTexture(gl, gl.FLOAT)) { singleFloatTexturing = true; shimExtensions.push('OES_texture_float'); } else { singleFloatTexturing = false; unshimExtensions.push('OES_texture_float'); } } if (singleFloatTexturing) { extobj = gl.getExtension('WEBGL_color_buffer_float'); if (extobj === null) { if (checkColorBuffer(gl, gl.FLOAT)) { shimExtensions.push('WEBGL_color_buffer_float'); shimLookup.WEBGL_color_buffer_float = { shim: true, RGBA32F_EXT: 0x8814, RGB32F_EXT: 0x8815, FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE_EXT: 0x8211, UNSIGNED_NORMALIZED_EXT: 0x8C17 }; } else { unshimExtensions.push('WEBGL_color_buffer_float'); } } else { if (checkColorBuffer(gl, gl.FLOAT)) { shimExtensions.push('WEBGL_color_buffer_float'); } else { unshimExtensions.push('WEBGL_color_buffer_float'); } } extobj = gl.getExtension('OES_texture_float_linear'); if (extobj === null) { if (checkFloatLinear(gl, gl.FLOAT)) { shimExtensions.push('OES_texture_float_linear'); shimLookup.OES_texture_float_linear = { shim: true }; } else { unshimExtensions.push('OES_texture_float_linear'); } } else { if (checkFloatLinear(gl, gl.FLOAT)) { shimExtensions.push('OES_texture_float_linear'); } else { unshimExtensions.push('OES_texture_float_linear'); } } } halfFloatExt = gl.getExtension('OES_texture_half_float'); if (halfFloatExt === null) { if (checkTexture(gl, 0x8D61)) { halfFloatTexturing = true; shimExtensions.push('OES_texture_half_float'); halfFloatExt = shimLookup.OES_texture_half_float = { HALF_FLOAT_OES: 0x8D61, shim: true }; } else { halfFloatTexturing = false; unshimExtensions.push('OES_texture_half_float'); } } else { if (checkTexture(gl, halfFloatExt.HALF_FLOAT_OES)) { halfFloatTexturing = true; shimExtensions.push('OES_texture_half_float'); } else { halfFloatTexturing = false; unshimExtensions.push('OES_texture_half_float'); } } if (halfFloatTexturing) { extobj = gl.getExtension('EXT_color_buffer_half_float'); if (extobj === null) { if (checkColorBuffer(gl, halfFloatExt.HALF_FLOAT_OES)) { shimExtensions.push('EXT_color_buffer_half_float'); shimLookup.EXT_color_buffer_half_float = { shim: true, RGBA16F_EXT: 0x881A, RGB16F_EXT: 0x881B, FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE_EXT: 0x8211, UNSIGNED_NORMALIZED_EXT: 0x8C17 }; } else { unshimExtensions.push('EXT_color_buffer_half_float'); } } else { if (checkColorBuffer(gl, halfFloatExt.HALF_FLOAT_OES)) { shimExtensions.push('EXT_color_buffer_half_float'); } else { unshimExtensions.push('EXT_color_buffer_half_float'); } } extobj = gl.getExtension('OES_texture_half_float_linear'); if (extobj === null) { if (checkFloatLinear(gl, halfFloatExt.HALF_FLOAT_OES)) { shimExtensions.push('OES_texture_half_float_linear'); return shimLookup.OES_texture_half_float_linear = { shim: true }; } else { return unshimExtensions.push('OES_texture_half_float_linear'); } } else { if (checkFloatLinear(gl, halfFloatExt.HALF_FLOAT_OES)) { return shimExtensions.push('OES_texture_half_float_linear'); } else { return unshimExtensions.push('OES_texture_half_float_linear'); } } } } }; if (window.WebGLRenderingContext != null) { checkSupport(); unshimLookup = {}; for (_i = 0, _len = unshimExtensions.length; _i < _len; _i++) { name = unshimExtensions[_i]; unshimLookup[name] = true; } getExtension = WebGLRenderingContext.prototype.getExtension; WebGLRenderingContext.prototype.getExtension = function(name) { var extobj; extobj = shimLookup[name]; if (extobj === void 0) { if (unshimLookup[name]) { return null; } else { return getExtension.call(this, name); } } else { return extobj; } }; getSupportedExtensions = WebGLRenderingContext.prototype.getSupportedExtensions; WebGLRenderingContext.prototype.getSupportedExtensions = function() { var extension, result, supported, _j, _k, _len1, _len2; supported = getSupportedExtensions.call(this); result = []; for (_j = 0, _len1 = supported.length; _j < _len1; _j++) { extension = supported[_j]; if (unshimLookup[extension] === void 0) { result.push(extension); } } for (_k = 0, _len2 = shimExtensions.length; _k < _len2; _k++) { extension = shimExtensions[_k]; if (__indexOf.call(result, extension) < 0) { result.push(extension); } } return result; }; return WebGLRenderingContext.prototype.getFloatExtension = function(spec) { var candidate, candidates, half, halfFramebuffer, halfLinear, halfTexture, i, importance, preference, result, single, singleFramebuffer, singleLinear, singleTexture, use, _j, _k, _l, _len1, _len2, _len3, _len4, _m, _ref, _ref1, _ref2; if (spec.prefer == null) { spec.prefer = ['half']; } if (spec.require == null) { spec.require = []; } if (spec.throws == null) { spec.throws = true; } singleTexture = this.getExtension('OES_texture_float'); halfTexture = this.getExtension('OES_texture_half_float'); singleFramebuffer = this.getExtension('WEBGL_color_buffer_float'); halfFramebuffer = this.getExtension('EXT_color_buffer_half_float'); singleLinear = this.getExtension('OES_texture_float_linear'); halfLinear = this.getExtension('OES_texture_half_float_linear'); single = { texture: singleTexture !== null, filterable: singleLinear !== null, renderable: singleFramebuffer !== null, score: 0, precision: 'single', half: false, single: true, type: this.FLOAT }; half = { texture: halfTexture !== null, filterable: halfLinear !== null, renderable: halfFramebuffer !== null, score: 0, precision: 'half', half: true, single: false, type: (_ref = halfTexture != null ? halfTexture.HALF_FLOAT_OES : void 0) != null ? _ref : null }; candidates = []; if (single.texture) { candidates.push(single); } if (half.texture) { candidates.push(half); } result = []; for (_j = 0, _len1 = candidates.length; _j < _len1; _j++) { candidate = candidates[_j]; use = true; _ref1 = spec.require; for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) { name = _ref1[_k]; if (candidate[name] === false) { use = false; } } if (use) { result.push(candidate); } } for (_l = 0, _len3 = result.length; _l < _len3; _l++) { candidate = result[_l]; _ref2 = spec.prefer; for (i = _m = 0, _len4 = _ref2.length; _m < _len4; i = ++_m) { preference = _ref2[i]; importance = Math.pow(2, spec.prefer.length - i - 1); if (candidate[preference]) { candidate.score += importance; } } } result.sort(function(a, b) { if (a.score === b.score) { return 0; } else if (a.score < b.score) { return 1; } else if (a.score > b.score) { return -1; } }); if (result.length === 0) { if (spec.throws) { throw 'No floating point texture support that is ' + spec.require.join(', '); } else { return null; } } else { result = result[0]; return { filterable: result.filterable, renderable: result.renderable, type: result.type, precision: result.precision }; } }; } }; nukeVendorPrefix(); textureFloatShims(); Shader = (function() { function Shader(gl, _arg) { var fragment, vertex; this.gl = gl; vertex = _arg.vertex, fragment = _arg.fragment; this.program = this.gl.createProgram(); this.vs = this.gl.createShader(this.gl.VERTEX_SHADER); this.fs = this.gl.createShader(this.gl.FRAGMENT_SHADER); this.gl.attachShader(this.program, this.vs); this.gl.attachShader(this.program, this.fs); this.compileShader(this.vs, vertex); this.compileShader(this.fs, fragment); this.link(); this.value_cache = {}; this.uniform_cache = {}; this.attribCache = {}; } Shader.prototype.attribLocation = function(name) { var location; location = this.attribCache[name]; if (location === void 0) { location = this.attribCache[name] = this.gl.getAttribLocation(this.program, name); } return location; }; Shader.prototype.compileShader = function(shader, source) { this.gl.shaderSource(shader, source); this.gl.compileShader(shader); if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) { throw "Shader Compile Error: " + (this.gl.getShaderInfoLog(shader)); } }; Shader.prototype.link = function() { this.gl.linkProgram(this.program); if (!this.gl.getProgramParameter(this.program, this.gl.LINK_STATUS)) { throw "Shader Link Error: " + (this.gl.getProgramInfoLog(this.program)); } }; Shader.prototype.use = function() { this.gl.useProgram(this.program); return this; }; Shader.prototype.uniformLoc = function(name) { var location; location = this.uniform_cache[name]; if (location === void 0) { location = this.uniform_cache[name] = this.gl.getUniformLocation(this.program, name); } return location; }; Shader.prototype.int = function(name, value) { var cached, loc; cached = this.value_cache[name]; if (cached !== value) { this.value_cache[name] = value; loc = this.uniformLoc(name); if (loc) { this.gl.uniform1i(loc, value); } } return this; }; Shader.prototype.vec2 = function(name, a, b) { var loc; loc = this.uniformLoc(name); if (loc) { this.gl.uniform2f(loc, a, b); } return this; }; Shader.prototype.float = function(name, value) { var cached, loc; cached = this.value_cache[name]; if (cached !== value) { this.value_cache[name] = value; loc = this.uniformLoc(name); if (loc) { this.gl.uniform1f(loc, value); } } return this; }; return Shader; })(); Framebuffer = (function() { function Framebuffer(gl) { this.gl = gl; this.buffer = this.gl.createFramebuffer(); } Framebuffer.prototype.destroy = function() { return this.gl.deleteFRamebuffer(this.buffer); }; Framebuffer.prototype.bind = function() { this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.buffer); return this; }; Framebuffer.prototype.unbind = function() { this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null); return this; }; Framebuffer.prototype.check = function() { var result; result = this.gl.checkFramebufferStatus(this.gl.FRAMEBUFFER); switch (result) { case this.gl.FRAMEBUFFER_UNSUPPORTED: throw 'Framebuffer is unsupported'; break; case this.gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT: throw 'Framebuffer incomplete attachment'; break; case this.gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS: throw 'Framebuffer incomplete dimensions'; break; case this.gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: throw 'Framebuffer incomplete missing attachment'; } return this; }; Framebuffer.prototype.color = function(texture) { this.gl.framebufferTexture2D(this.gl.FRAMEBUFFER, this.gl.COLOR_ATTACHMENT0, texture.target, texture.handle, 0); this.check(); return this; }; Framebuffer.prototype.depth = function(buffer) { this.gl.framebufferRenderbuffer(this.gl.FRAMEBUFFER, this.gl.DEPTH_ATTACHMENT, this.gl.RENDERBUFFER, buffer.id); this.check(); return this; }; Framebuffer.prototype.destroy = function() { return this.gl.deleteFramebuffer(this.buffer); }; return Framebuffer; })(); Texture = (function() { function Texture(gl, params) { var _ref, _ref1; this.gl = gl; if (params == null) { params = {}; } this.channels = this.gl[((_ref = params.channels) != null ? _ref : 'rgba').toUpperCase()]; if (typeof params.type === 'number') { this.type = params.type; } else { this.type = this.gl[((_ref1 = params.type) != null ? _ref1 : 'unsigned_byte').toUpperCase()]; } switch (this.channels) { case this.gl.RGBA: this.chancount = 4; break; case this.gl.RGB: this.chancount = 3; break; case this.gl.LUMINANCE_ALPHA: this.chancount = 2; break; default: this.chancount = 1; } this.target = this.gl.TEXTURE_2D; this.handle = this.gl.createTexture(); } Texture.prototype.destroy = function() { return this.gl.deleteTexture(this.handle); }; Texture.prototype.bind = function(unit) { if (unit == null) { unit = 0; } if (unit > 15) { throw 'Texture unit too large: ' + unit; } this.gl.activeTexture(this.gl.TEXTURE0 + unit); this.gl.bindTexture(this.target, this.handle); return this; }; Texture.prototype.setSize = function(width, height) { this.width = width; this.height = height; this.gl.texImage2D(this.target, 0, this.channels, this.width, this.height, 0, this.channels, this.type, null); return this; }; Texture.prototype.upload = function(data) { this.width = data.width; this.height = data.height; this.gl.texImage2D(this.target, 0, this.channels, this.channels, this.type, data); return this; }; Texture.prototype.linear = function() { this.gl.texParameteri(this.target, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR); this.gl.texParameteri(this.target, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR); return this; }; Texture.prototype.nearest = function() { this.gl.texParameteri(this.target, this.gl.TEXTURE_MAG_FILTER, this.gl.NEAREST); this.gl.texParameteri(this.target, this.gl.TEXTURE_MIN_FILTER, this.gl.NEAREST); return this; }; Texture.prototype.clampToEdge = function() { this.gl.texParameteri(this.target, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE); this.gl.texParameteri(this.target, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE); return this; }; Texture.prototype.repeat = function() { this.gl.texParameteri(this.target, this.gl.TEXTURE_WRAP_S, this.gl.REPEAT); this.gl.texParameteri(this.target, this.gl.TEXTURE_WRAP_T, this.gl.REPEAT); return this; }; return Texture; })(); Node = (function() { function Node(gl, width, height) { var floatExt; this.gl = gl; this.width = width; this.height = height; floatExt = this.gl.getFloatExtension({ require: ['renderable'] }); this.texture = new Texture(this.gl, { type: floatExt.type }).bind(0).setSize(this.width, this.height).nearest().clampToEdge(); this.fbo = new Framebuffer(this.gl).bind().color(this.texture).unbind(); } Node.prototype.use = function() { return this.fbo.bind(); }; Node.prototype.bind = function(unit) { return this.texture.bind(unit); }; Node.prototype.end = function() { return this.fbo.unbind(); }; Node.prototype.resize = function(width, height) { this.width = width; this.height = height; return this.texture.bind(0).setSize(this.width, this.height); }; return Node; })(); vertexShaderBlit = 'attribute vec4 position;\nvarying vec2 texcoord;\nvoid main(){\n texcoord = position.xy*0.5+0.5;\n gl_Position = position;\n}'; fragmentShaderBlit = '#ifdef GL_FRAGMENT_PRECISION_HIGH\n precision highp int;\n precision highp float;\n#else\n precision mediump int;\n precision mediump float;\n#endif\nuniform sampler2D source;\nvarying vec2 texcoord;'; Heights = (function() { function Heights(heatmap, gl, width, height) { var i, _i, _ref; this.heatmap = heatmap; this.gl = gl; this.width = width; this.height = height; this.shader = new Shader(this.gl, { vertex: 'attribute vec4 position, intensity;\nvarying vec2 off, dim;\nvarying float vIntensity;\nuniform vec2 viewport;\n\nvoid main(){\n dim = abs(position.zw);\n off = position.zw;\n vec2 pos = position.xy + position.zw;\n vIntensity = intensity.x;\n gl_Position = vec4((pos/viewport)*2.0-1.0, 0.0, 1.0);\n}', fragment: '#ifdef GL_FRAGMENT_PRECISION_HIGH\n precision highp int;\n precision highp float;\n#else\n precision mediump int;\n precision mediump float;\n#endif\nvarying vec2 off, dim;\nvarying float vIntensity;\nvoid main(){\n float falloff = (1.0 - smoothstep(0.0, 1.0, length(off/dim)));\n float intensity = falloff*vIntensity;\n gl_FragColor = vec4(intensity);\n}' }); this.clampShader = new Shader(this.gl, { vertex: vertexShaderBlit, fragment: fragmentShaderBlit + 'uniform float low, high;\nvoid main(){\n gl_FragColor = vec4(clamp(texture2D(source, texcoord).rgb, low, high), 1.0);\n}' }); this.multiplyShader = new Shader(this.gl, { vertex: vertexShaderBlit, fragment: fragmentShaderBlit + 'uniform float value;\nvoid main(){\n gl_FragColor = vec4(texture2D(source, texcoord).rgb*value, 1.0);\n}' }); this.blurShader = new Shader(this.gl, { vertex: vertexShaderBlit, fragment: fragmentShaderBlit + 'uniform vec2 viewport;\nvoid main(){\n vec4 result = vec4(0.0);\n for(int x=-1; x<=1; x++){\n for(int y=-1; y<=1; y++){\n vec2 off = vec2(x,y)/viewport;\n //float factor = 1.0 - smoothstep(0.0, 1.5, length(off));\n float factor = 1.0;\n result += vec4(texture2D(source, texcoord+off).rgb*factor, factor);\n }\n }\n gl_FragColor = vec4(result.rgb/result.w, 1.0);\n}' }); this.nodeBack = new Node(this.gl, this.width, this.height); this.nodeFront = new Node(this.gl, this.width, this.height); this.vertexBuffer = this.gl.createBuffer(); this.vertexSize = 8; this.maxPointCount = 1024 * 10; this.vertexBufferData = new Float32Array(this.maxPointCount * this.vertexSize * 6); this.vertexBufferViews = []; for (i = _i = 0, _ref = this.maxPointCount; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { this.vertexBufferViews.push(new Float32Array(this.vertexBufferData.buffer, 0, i * this.vertexSize * 6)); } this.bufferIndex = 0; this.pointCount = 0; } Heights.prototype.resize = function(width, height) { this.width = width; this.height = height; this.nodeBack.resize(this.width, this.height); return this.nodeFront.resize(this.width, this.height); }; Heights.prototype.update = function() { var intensityLoc, positionLoc; if (this.pointCount > 0) { this.gl.enable(this.gl.BLEND); this.nodeFront.use(); this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vertexBuffer); this.gl.bufferData(this.gl.ARRAY_BUFFER, this.vertexBufferViews[this.pointCount], this.gl.STREAM_DRAW); positionLoc = this.shader.attribLocation('position'); intensityLoc = this.shader.attribLocation('intensity'); this.gl.enableVertexAttribArray(1); this.gl.vertexAttribPointer(positionLoc, 4, this.gl.FLOAT, false, 8 * 4, 0 * 4); this.gl.vertexAttribPointer(intensityLoc, 4, this.gl.FLOAT, false, 8 * 4, 4 * 4); this.shader.use().vec2('viewport', this.width, this.height); this.gl.drawArrays(this.gl.TRIANGLES, 0, this.pointCount * 6); this.gl.disableVertexAttribArray(1); this.pointCount = 0; this.bufferIndex = 0; this.nodeFront.end(); return this.gl.disable(this.gl.BLEND); } }; Heights.prototype.clear = function() { this.nodeFront.use(); this.gl.clearColor(0, 0, 0, 1); this.gl.clear(this.gl.COLOR_BUFFER_BIT); return this.nodeFront.end(); }; Heights.prototype.clamp = function(min, max) { this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.heatmap.quad); this.gl.vertexAttribPointer(0, 4, this.gl.FLOAT, false, 0, 0); this.nodeFront.bind(0); this.nodeBack.use(); this.clampShader.use().int('source', 0).float('low', min).float('high', max); this.gl.drawArrays(this.gl.TRIANGLES, 0, 6); this.nodeBack.end(); return this.swap(); }; Heights.prototype.multiply = function(value) { this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.heatmap.quad); this.gl.vertexAttribPointer(0, 4, this.gl.FLOAT, false, 0, 0); this.nodeFront.bind(0); this.nodeBack.use(); this.multiplyShader.use().int('source', 0).float('value', value); this.gl.drawArrays(this.gl.TRIANGLES, 0, 6); this.nodeBack.end(); return this.swap(); }; Heights.prototype.blur = function() { this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.heatmap.quad); this.gl.vertexAttribPointer(0, 4, this.gl.FLOAT, false, 0, 0); this.nodeFront.bind(0); this.nodeBack.use(); this.blurShader.use().int('source', 0).vec2('viewport', this.width, this.height); this.gl.drawArrays(this.gl.TRIANGLES, 0, 6); this.nodeBack.end(); return this.swap(); }; Heights.prototype.swap = function() { var tmp; tmp = this.nodeFront; this.nodeFront = this.nodeBack; return this.nodeBack = tmp; }; Heights.prototype.addVertex = function(x, y, xs, ys, intensity) { this.vertexBufferData[this.bufferIndex++] = x; this.vertexBufferData[this.bufferIndex++] = y; this.vertexBufferData[this.bufferIndex++] = xs; this.vertexBufferData[this.bufferIndex++] = ys; this.vertexBufferData[this.bufferIndex++] = intensity; this.vertexBufferData[this.bufferIndex++] = intensity; this.vertexBufferData[this.bufferIndex++] = intensity; return this.vertexBufferData[this.bufferIndex++] = intensity; }; Heights.prototype.addPoint = function(x, y, size, intensity) { var s; if (size == null) { size = 50; } if (intensity == null) { intensity = 0.2; } if (this.pointCount >= this.maxPointCount - 1) { this.update(); } y = this.height - y; s = size / 2; this.addVertex(x, y, -s, -s, intensity); this.addVertex(x, y, +s, -s, intensity); this.addVertex(x, y, -s, +s, intensity); this.addVertex(x, y, -s, +s, intensity); this.addVertex(x, y, +s, -s, intensity); this.addVertex(x, y, +s, +s, intensity); return this.pointCount += 1; }; return Heights; })(); WebGLHeatmap = (function() { function WebGLHeatmap(_arg) { var alphaEnd, alphaRange, alphaStart, error, getColorFun, gradientTexture, image, intensityToAlpha, output, quad, textureGradient, _ref, _ref1; _ref = _arg != null ? _arg : {}, this.canvas = _ref.canvas, this.width = _ref.width, this.height = _ref.height, intensityToAlpha = _ref.intensityToAlpha, gradientTexture = _ref.gradientTexture, alphaRange = _ref.alphaRange; if (!this.canvas) { this.canvas = document.createElement('canvas'); } try { this.gl = this.canvas.getContext('experimental-webgl', { depth: false, antialias: false }); if (this.gl === null) { this.gl = this.canvas.getContext('webgl', { depth: false, antialias: false }); if (this.gl === null) { throw 'WebGL not supported'; } } } catch (_error) { error = _error; throw 'WebGL not supported'; } if (window.WebGLDebugUtils != null) { console.log('debugging mode'); this.gl = WebGLDebugUtils.makeDebugContext(this.gl, function(err, funcName, args) { throw WebGLDebugUtils.glEnumToString(err) + " was caused by call to: " + funcName; }); } this.gl.enableVertexAttribArray(0); this.gl.blendFunc(this.gl.ONE, this.gl.ONE); if (gradientTexture) { textureGradient = this.gradientTexture = new Texture(this.gl, { channels: 'rgba' }).bind(0).setSize(2, 2).nearest().clampToEdge(); if (typeof gradientTexture === 'string') { image = new Image(); image.onload = function() { return textureGradient.bind().upload(image); }; image.src = gradientTexture; } else { if (gradientTexture.width > 0 && gradientTexture.height > 0) { textureGradient.upload(gradientTexture); } else { gradientTexture.onload = function() { return textureGradient.upload(gradientTexture); }; } } getColorFun = 'uniform sampler2D gradientTexture;\nvec3 getColor(float intensity){\n return texture2D(gradientTexture, vec2(intensity, 0.0)).rgb;\n}'; } else { textureGradient = null; getColorFun = 'vec3 getColor(float intensity){\n vec3 blue = vec3(0.0, 0.0, 1.0);\n vec3 cyan = vec3(0.0, 1.0, 1.0);\n vec3 green = vec3(0.0, 1.0, 0.0);\n vec3 yellow = vec3(1.0, 1.0, 0.0);\n vec3 red = vec3(1.0, 0.0, 0.0);\n\n vec3 color = (\n fade(-0.25, 0.25, intensity)*blue +\n fade(0.0, 0.5, intensity)*cyan +\n fade(0.25, 0.75, intensity)*green +\n fade(0.5, 1.0, intensity)*yellow +\n smoothstep(0.75, 1.0, intensity)*red\n );\n return color;\n}'; } if (intensityToAlpha == null) { intensityToAlpha = true; } if (intensityToAlpha) { _ref1 = alphaRange != null ? alphaRange : [0, 1], alphaStart = _ref1[0], alphaEnd = _ref1[1]; output = "vec4 alphaFun(vec3 color, float intensity){\n float alpha = smoothstep(" + (alphaStart.toFixed(8)) + ", " + (alphaEnd.toFixed(8)) + ", intensity);\n return vec4(color*alpha, alpha);\n}"; } else { output = 'vec4 alphaFun(vec3 color, float intensity){\n return vec4(color, 1.0);\n}'; } this.shader = new Shader(this.gl, { vertex: vertexShaderBlit, fragment: fragmentShaderBlit + ("float linstep(float low, float high, float value){\n return clamp((value-low)/(high-low), 0.0, 1.0);\n}\n\nfloat fade(float low, float high, float value){\n float mid = (low+high)*0.5;\n float range = (high-low)*0.5;\n float x = 1.0 - clamp(abs(mid-value)/range, 0.0, 1.0);\n return smoothstep(0.0, 1.0, x);\n}\n\n" + getColorFun + "\n" + output + "\n\nvoid main(){\n float intensity = smoothstep(0.0, 1.0, texture2D(source, texcoord).r);\n vec3 color = getColor(intensity);\n gl_FragColor = alphaFun(color, intensity);\n}") }); if (this.width == null) { this.width = this.canvas.offsetWidth || 2; } if (this.height == null) { this.height = this.canvas.offsetHeight || 2; } this.canvas.width = this.width; this.canvas.height = this.height; this.gl.viewport(0, 0, this.width, this.height); this.quad = this.gl.createBuffer(); this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.quad); quad = new Float32Array([-1, -1, 0, 1, 1, -1, 0, 1, -1, 1, 0, 1, -1, 1, 0, 1, 1, -1, 0, 1, 1, 1, 0, 1]); this.gl.bufferData(this.gl.ARRAY_BUFFER, quad, this.gl.STATIC_DRAW); this.gl.bindBuffer(this.gl.ARRAY_BUFFER, null); this.heights = new Heights(this, this.gl, this.width, this.height); } WebGLHeatmap.prototype.adjustSize = function() { var canvasHeight, canvasWidth; canvasWidth = this.canvas.offsetWidth || 2; canvasHeight = this.canvas.offsetHeight || 2; if (this.width !== canvasWidth || this.height !== canvasHeight) { this.gl.viewport(0, 0, canvasWidth, canvasHeight); this.canvas.width = canvasWidth; this.canvas.height = canvasHeight; this.width = canvasWidth; this.height = canvasHeight; return this.heights.resize(this.width, this.height); } }; WebGLHeatmap.prototype.display = function() { this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.quad); this.gl.vertexAttribPointer(0, 4, this.gl.FLOAT, false, 0, 0); this.heights.nodeFront.bind(0); if (this.gradientTexture) { this.gradientTexture.bind(1); } this.shader.use().int('source', 0).int('gradientTexture', 1); return this.gl.drawArrays(this.gl.TRIANGLES, 0, 6); }; WebGLHeatmap.prototype.update = function() { return this.heights.update(); }; WebGLHeatmap.prototype.clear = function() { return this.heights.clear(); }; WebGLHeatmap.prototype.clamp = function(min, max) { if (min == null) { min = 0; } if (max == null) { max = 1; } return this.heights.clamp(min, max); }; WebGLHeatmap.prototype.multiply = function(value) { if (value == null) { value = 0.95; } return this.heights.multiply(value); }; WebGLHeatmap.prototype.blur = function() { return this.heights.blur(); }; WebGLHeatmap.prototype.addPoint = function(x, y, size, intensity) { return this.heights.addPoint(x, y, size, intensity); }; WebGLHeatmap.prototype.addPoints = function(items) { var item, _i, _len, _results; _results = []; for (_i = 0, _len = items.length; _i < _len; _i++) { item = items[_i]; _results.push(this.addPoint(item.x, item.y, item.size, item.intensity)); } return _results; }; return WebGLHeatmap; })(); window.createWebGLHeatmap = function(params) { return new WebGLHeatmap(params); }; }).call(this);