pathgl
Version:
A webgl renderer for data visualization, motion graphics and explorable explanations.
1,202 lines (1,012 loc) • 37.5 kB
JavaScript
! function() {
var pathgl = this.pathgl = {}
pathgl.sim = {}
pathgl.stop = function () {}
pathgl.context = function () { return gl }
var inited = 0
pathgl.texture = function (image, options, target) {
if (! inited) pathgl.init('canvas')
return new (image == null ? RenderTexture :
isShader(image) ? ShaderTexture :
DataTexture)(image, extend(options || {}, { src: image }), target)
}
pathgl.uniform = function (attr, value) {
return arguments.length == 1 ? uniforms[attr] : uniforms[attr] = value
}
pathgl.applyCSS = applyCSSRules
HTMLCanvasElement.prototype.appendChild = function (el) {
if (pathgl.init(this)) return this.appendChild(el)
}
var gl, program, programs
var textures = { null: [] }
var stopRendering = false
var tasks = []
var uniforms = {}
var start = Date.now()
pathgl.init = function (canvas) {
inited = 1
canvas = 'string' == typeof canvas ? document.querySelector(canvas) :
canvas instanceof d3.selection ? canvas.node() :
canvas
if (! canvas.getContext) return console.log(canvas, 'is not a valid canvas')
return !! init(canvas)
};
function noop () {}
function identity(x) { return x }
function push(d) { return this.push(d) }
function powerOfTwo(x) { return x && ! (x & (x - 1)) }
function nextSquare(n) { return Math.pow(Math.ceil(Math.sqrt(n)), 2) }
function each(obj, fn) { for (var key in obj) fn(obj[key], key, obj) }
function clamp (x, min, max) { return Math.min(Math.max(x, min), max) }
function Quad () { return [-1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0] }
function isVideoUrl(url) { return url.split('.').pop().join().match(/mp4|ogg|webm/) }
function uniq(ar) { return ar.filter(function (d, i) { return ar.indexOf(d) == i }) }
function flatten(list){ return list.reduce(function( p,n){ return p.concat(n) }, []) }
function svgToClipSpace(pos) { return [2 * (pos[0] / 960) - 1, 1 - (pos[1] / 500 * 2)] }
function range(a, b) { return Array(Math.abs(b - a)).join().split(',').map(function (d, i) { return i + a }) }
function hash(str) { return str.split("").reduce(function(a,b) { a = ((a << 5) - a) + b.charCodeAt(0); return a & a }, 0) }
function extend (a, b) {
if (arguments.length > 2) [].forEach.call(arguments, function (b) { extend(a, b) })
else for (var k in b) a[k] = b[k]
return a
}
function pointInPolygon(x, y, shape) {}
;function parseColor(v) {
var a = setStyle(v)
return + ( a[0] * 255 ) << 16 ^ ( a[1] * 255 ) << 8 ^ ( a[2] * 255 ) << 0
}
function hexColor( hex ) {
hex = Math.floor( hex )
return [ (hex >> 16 & 255 ) / 255
, (hex >> 8 & 255 ) / 255
, (hex & 255 ) / 255]
}
function parse_hsl(h, s, l) {
if ( s === 0 ) {
return [l, l, l]
} else {
var p = l <= 0.5 ? l * (1 + s) : l + s - (l * s)
var q = ( 2 * l ) - p
return [
hue2rgb(q, p, h + 1 / 3)
, hue2rgb(q, p, h)
, hue2rgb(q, p, h - 1 / 3)
]
}
}
function hue2rgb(p, q, t) {
if (t < 0) t += 1
if (t > 1) t -= 1
if (t < 1 / 6) return p + (q - p) * 6 * t
if (t < 1 / 2) return q
if (t < 2 / 3) return p + (q - p) * 6 * (2 / 3 - t)
return p
}
function setStyle(style) {
if (cssColors[style])
return hexColor(cssColors[style])
if (/^hsl/i.test(style)) {
var hsl = style.split(/,|\(/).map(parseFloat)
return parse_hsl(hsl[1] / 360 , hsl[2] / 100, hsl[3] / 100)
}
if (/^rgb\((\d+), ?(\d+), ?(\d+)\)$/i.test(style)) {
var color = /^rgb\((\d+), ?(\d+), ?(\d+)\)$/i.exec(style)
return [ Math.min(255, parseInt(color[1], 10)) / 255
, Math.min(255, parseInt(color[2], 10)) / 255
, Math.min(255, parseInt(color[3], 10)) / 255
]
}
if (/^rgb\((\d+)\%, ?(\d+)\%, ?(\d+)\%\)$/i.test(style)) {
var color = /^rgb\((\d+)\%, ?(\d+)\%, ?(\d+)\%\)$/i.exec(style)
return [ Math.min(100, parseInt(color[1], 10)) / 100
, Math.min(100, parseInt(color[2], 10)) / 100
, Math.min(100, parseInt(color[3], 10)) / 100
]
}
if (/^\#([0-9a-f]{6})$/i.test(style)) {
var color = /^\#([0-9a-f]{6})$/i.exec(style)
return hexColor( parseInt(color[1], 16))
}
if (/^\#([0-9a-f])([0-9a-f])([0-9a-f])$/i.test(style)) {
var color = /^\#([0-9a-f])([0-9a-f])([0-9a-f])$/i.exec(style)
return hexColor(parseInt(color[1] + color[1] + color[2] + color[2] + color[3] + color[3], 16))
}
return false
}
var cssColors = {
"aliceblue": 0xF0F8FF, "antiquewhite": 0xFAEBD7, "aqua": 0x00FFFF, "aquamarine": 0x7FFFD4, "azure": 0xF0FFFF
, "beige": 0xF5F5DC, "bisque": 0xFFE4C4, "black": 0x000000, "blanchedalmond": 0xFFEBCD, "blue": 0x0000FF, "blueviolet": 0x8A2BE2
, "brown": 0xA52A2A, "burlywood": 0xDEB887, "cadetblue": 0x5F9EA0, "chartreuse": 0x7FFF00, "chocolate": 0xD2691E, "coral": 0xFF7F50
, "cornflowerblue": 0x6495ED, "cornsilk": 0xFFF8DC, "crimson": 0xDC143C, "cyan": 0x00FFFF, "darkblue": 0x00008B, "darkcyan": 0x008B8B
, "darkgoldenrod": 0xB8860B, "darkgray": 0xA9A9A9, "darkgreen": 0x006400, "darkgrey": 0xA9A9A9, "darkkhaki": 0xBDB76B
, "darkmagenta": 0x8B008B, "darkolivegreen": 0x556B2F, "darkorange": 0xFF8C00, "darkorchid": 0x9932CC, "darkred": 0x8B0000
, "darksalmon": 0xE9967A, "darkseagreen": 0x8FBC8F, "darkslateblue": 0x483D8B, "darkslategray": 0x2F4F4F, "darkslategrey": 0x2F4F4F
, "darkturquoise": 0x00CED1, "darkviolet": 0x9400D3, "deeppink": 0xFF1493, "deepskyblue": 0x00BFFF, "dimgray": 0x696969
, "dimgrey": 0x696969, "dodgerblue": 0x1E90FF, "firebrick": 0xB22222, "floralwhite": 0xFFFAF0, "forestgreen": 0x228B22
, "fuchsia": 0xFF00FF, "gainsboro": 0xDCDCDC, "ghostwhite": 0xF8F8FF, "gold": 0xFFD700, "goldenrod": 0xDAA520
, "gray": 0x808080, "green": 0x008000, "greenyellow": 0xADFF2F, "grey": 0x808080, "honeydew": 0xF0FFF0, "hotpink": 0xFF69B4
, "indianred": 0xCD5C5C, "indigo": 0x4B0082, "ivory": 0xFFFFF0, "khaki": 0xF0E68C, "lavender": 0xE6E6FA, "lavenderblush": 0xFFF0F5
, "lawngreen": 0x7CFC00, "lemonchiffon": 0xFFFACD, "lightblue": 0xADD8E6, "lightcoral": 0xF08080
, "lightcyan": 0xE0FFFF, "lightgoldenrodyellow": 0xFAFAD2, "lightgray": 0xD3D3D3
, "lightgreen": 0x90EE90, "lightgrey": 0xD3D3D3, "lightpink": 0xFFB6C1, "lightsalmon": 0xFFA07A, "lightseagreen": 0x20B2AA
, "lightskyblue": 0x87CEFA, "lightslategray": 0x778899, "lightslategrey": 0x778899, "lightsteelblue": 0xB0C4DE
, "lightyellow": 0xFFFFE0, "lime": 0x00FF00, "limegreen": 0x32CD32, "mediumpurple": 0x9370DB
, "mediumseagreen": 0x3CB371, "mediumslateblue": 0x7B68EE, "mediumspringgreen": 0x00FA9A, "mediumturquoise": 0x48D1CC
, "mediumvioletred": 0xC71585, "midnightblue": 0x191970, "mintcream": 0xF5FFFA, "mistyrose": 0xFFE4E1, "moccasin": 0xFFE4B5
, "navajowhite": 0xFFDEAD,"navy": 0x000080, "oldlace": 0xFDF5E6, "olive": 0x808000, "olivedrab": 0x6B8E23
, "orange": 0xFFA500, "orangered": 0xFF4500, "orchid": 0xDA70D6, "palegoldenrod": 0xEEE8AA, "palegreen": 0x98FB98
, "paleturquoise": 0xAFEEEE, "palevioletred": 0xDB7093, "papayawhip": 0xFFEFD5
, "peachpuff": 0xFFDAB9, "peru": 0xCD853F, "pink": 0xFFC0CB, "plum": 0xDDA0DD, "powderblue": 0xB0E0E6, "purple": 0x800080
, "red": 0xFF0000, "rosybrown": 0xBC8F8F, "royalblue": 0x4169E1, "saddlebrown": 0x8B4513, "salmon": 0xFA8072
, "sandybrown": 0xF4A460, "seagreen": 0x2E8B57, "seashell": 0xFFF5EE, "sienna": 0xA0522D, "silver": 0xC0C0C0, "skyblue": 0x87CEEB
, "slateblue": 0x6A5ACD, "slategray": 0x708090, "slategrey": 0x708090, "snow": 0xFFFAFA
, "springgreen": 0x00FF7F, "steelblue": 0x4682B4, "tan": 0xD2B48C, "teal": 0x008080, "thistle": 0xD8BFD8
, "tomato": 0xFF6347, "turquoise": 0x40E0D0, "violet": 0xEE82EE, "wheat": 0xF5DEB3, "white": 0xFFFFFF, "whitesmoke": 0xF5F5F5
, "yellow": 0xFFFF00, "yellowgreen": 0x9ACD32
}
;pathgl.vertexShader = [
'precision mediump float;'
, 'uniform float clock;'
, 'uniform vec2 mouse;'
, 'uniform vec2 resolution;'
, 'uniform vec2 dates;'
, 'attribute vec4 pos;'
, 'attribute vec4 color;'
, 'attribute vec4 fugue;'
, 'varying float type;'
, 'varying vec4 v_stroke;'
, 'varying vec4 v_fill;'
, 'uniform sampler2D texture;'
, 'const mat4 modelViewMatrix = mat4(1.);'
, 'const mat4 projectionMatrix = mat4(1.);'
, 'vec4 unpack_color(float col) {'
, ' return vec4(mod(col / 256. / 256., 256.),'
, ' mod(col / 256. , 256.),'
, ' mod(col, 256.),'
, ' 200.)'
, ' / 256.;'
, '}'
, 'void main() {'
, ' float time = clock / 1000.;'
, ' float x = replace_x;'
, ' float y = replace_y;'
, ' float fill = color.x;'
, ' float stroke = color.x;'
, ' gl_Position = vec4(2. * (x / resolution.x) - 1., 1. - ((y / resolution.y) * 2.), 1., 1.);'
, ' type = fugue.x;'
, ' gl_PointSize = replace_r;'
, ' v_fill = unpack_color(fill);'
, ' v_stroke = replace_stroke;'
, '}'
].join('\n\n')
pathgl.fragmentShader = [
'precision mediump float;'
, 'uniform sampler2D texture;'
, 'uniform vec2 resolution;'
, 'uniform vec2 dates;'
, 'varying float type;'
, 'varying vec4 v_stroke;'
, 'varying vec4 v_fill;'
, 'void main() {'
, ' float dist = distance(gl_PointCoord, vec2(0.5));'
, ' if (type == 1. && dist > 0.5) discard;'
, ' gl_FragColor = (v_stroke.x < 0.) ? texture2D(texture, gl_PointCoord) : v_stroke;'
, '}'
].join('\n')
function createProgram(gl, vs, fs, attributes) {
var src = vs + '\n' + fs
program = gl.createProgram()
vs = compileShader(gl, gl.VERTEX_SHADER, vs)
fs = compileShader(gl, gl.FRAGMENT_SHADER, fs)
gl.attachShader(program, vs)
gl.attachShader(program, fs)
gl.deleteShader(vs)
gl.deleteShader(fs)
;(attributes || ['pos', 'color', 'fugue']).forEach(function (d, i){
gl.bindAttribLocation(program, i, d)
})
gl.linkProgram(program)
gl.useProgram(program)
if (! gl.getProgramParameter(program, gl.LINK_STATUS)) throw name + ': ' + gl.getProgramInfoLog(program)
var re = /uniform\s+(\S+)\s+(\S+)\s*;/g, match = null
while ((match = re.exec(src)) != null) bindUniform(match[2], match[1])
return program
function bindUniform(key, type) {
var loc = gl.getUniformLocation(program, key)
, method = 'uniform' + glslTypedef(type) + 'fv'
, keep
program[key] = function (data) {
if (keep == data || ! arguments.length) return
gl[method](loc, Array.isArray(data) ? data : [data])
keep = data
}
}
}
function build_vs(subst) {
var vertex = pathgl.vertexShader
each(subst || {}, function (v, k, o) {
if (k == 'cx') o['x'] = v
if (k == 'cy') o['y'] = v
})
var defaults = extend({
stroke: '(color.r < 0.) ? vec4(stroke) : unpack_color(stroke)'
, r: '(pos.z < 0.) ? 4. - texture2D(texture, abs(pos.xy)).z : (2. * pos.z)'
, x: '(pos.x < 0.) ? texture2D(texture, abs(pos.xy)).x * resolution.x : pos.x'
, y: '(pos.y < 0.) ? texture2D(texture, abs(pos.xy)).y * resolution.y : pos.y'
}, subst)
for(var attr in defaults)
vertex = vertex.replace('replace_'+attr, defaults[attr])
return vertex
}
function compileShader (gl, type, src) {
var shader = gl.createShader(type)
gl.shaderSource(shader, src)
gl.compileShader(shader)
if (! gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
var log = (gl.getShaderInfoLog(shader) || '')
, line = + log.split(':')[2]
return console.error((src || '').split('\n').slice(line-5, line + 5).join('\n'), log)
}
return shader
}
function glslTypedef(type) {
if (type.match('vec')) return type[type.length - 1]
return 1
};function init(c) {
if (! (gl = initContext(canvas = c)))
return !! console.log('webGL context could not be initialized')
if (! gl.getExtension('OES_texture_float'))
console.warn('does not support floating point textures')
program = createProgram(gl, build_vs(), pathgl.fragmentShader)
canvas.program = program
monkeyPatch(canvas)
bindEvents(canvas)
var main = RenderTarget(canvas)
tasks.push(main.update)
gl.clearColor(0, 0, 0, 0)
startDrawLoop()
return canvas
}
function flags(gl) {
gl.disable(gl.SCISSOR_TEST)
gl.stencilMask(1, 1, 1, 1)
gl.clear(gl.COLOR_BUFFER_BIT)
gl.colorMask(true, true, true, true)
gl.disable(gl.BLEND)
gl.enable(gl.CULL_FACE)
}
function bindEvents(canvas) {
setInterval(resizeCanvas, 100)
function resizeCanvas(v) {
pathgl.uniform('resolution', [canvas.width || 960, canvas.height || 500])
}
canvas.addEventListener('click', clicked)
canvas.addEventListener('mousemove', mousemoved)
canvas.addEventListener('touchmove', touchmoved)
canvas.addEventListener('touchstart', touchmoved)
}
function clicked () {}
function mousemoved(e) {
var rect = canvas.getBoundingClientRect()
pathgl.uniform('mouse', [ e.clientX - rect.left - canvas.clientLeft, e.clientY - rect.top - canvas.clientTop ])
}
function touchmoved(e) {
var rect = canvas.getBoundingClientRect()
e = e.touches[0]
pathgl.uniform('mouse', [ e.clientX - rect.left - canvas.clientLeft, e.clientY - rect.top - canvas.clientTop ])
}
function monkeyPatch(canvas) {
if(window.d3)
extend(window.d3.selection.prototype, {
vAttr: d3_vAttr
, shader: d3_shader
})
if (window.d3)
extend(window.d3.transition.prototype, {
vAttr: d3_vAttr
, shader: d3_shader
})
extend(canvas, appendable).gl = gl
}
var appendable = {
appendChild: appendChild
, querySelectorAll: querySelectorAll
, querySelector: function (s) { return this.querySelectorAll(s)[0] }
, removeChild: removeChild
, insertBefore: insertBefore
, __scene__: []
, __pos__: []
, __program__: void 0
}
function initContext(canvas) {
var gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl')
return gl && extend(gl, { viewportWidth: canvas.width, viewportHeight: canvas.height })
}
function d3_vAttr(attr, fn) {
//check if svg
this.each(function(d, i) {
this.colorBuffer[this.indices[0]] = parseColor(fn(d, i))
})
return this
}
function d3_shader(attr, name) {
this.node().mesh.mergeProgram(attr)
return this
}
var raf = window.requestAnimationFrame
|| window.webkitRequestAnimationFrame
|| window.mozRequestAnimationFrame
|| function(callback) { window.setTimeout(callback, 1000 / 60) }
function startDrawLoop() {
tasks.forEach(function (task) { task() })
raf(startDrawLoop)
}
;function parse (str, stroke) {
var buffer = [], lb = this.buffer, pb = this.posBuffer, indices = this.indices, count = 0
, pos = [0, 0], l = indices.length, i = 0
, origin = [0, 0]
str.match(/[a-z][^a-z]*/ig).forEach(function (segment, i, match) {
var points = segment.slice(1).trim().split(/,| /g), c = segment[0].toLowerCase(), j = 0
while(j < points.length) {
var x = points[j++], y = points[j++]
c == 'm' ? origin = pos = [x, y] :
c == 'l' ? buffer.push(pos[0], pos[1], x, y) && (pos = [x, y]) :
c == 'z' ? buffer.push(pos[0], pos[1], origin[0], origin[1]) && (pos = origin):
console.log('%d method is not supported malformed path:', c)
}
})
while(indices.length < buffer.length) indices.push(lb.count + i++)
if (indices.length > buffer.length) indices.length = buffer.length
indices.forEach(function (d, i) {
pb[3 * lb[d] + d % 3] = i < buffer.length && buffer[i]
})
lb.count += buffer.length - l
}
function applyCSSRules () {
if (! d3) return console.warn('this method depends on d3')
d3.selectAll('link[rel=styleSheet]').each(function () {
d3.text
})
var k = d3.selectAll('style')[0].map(function () { return this.sheet })
.reduce(function (acc, item) {
var itemRules = {}
each(item.cssRules, function (rules, i) {
var l = rules.length, cssom = {}
while(l--) {
var name = rules[rules[l]]
cssom[name] = rules[name]
}
itemRules[rules.selectorText] = cssom
})
return extend(acc, itemRules)
}, {})
each(k, function (styles, selector) {
d3.select(selector).attr(styles)
})
}
function matchesSelector(selector) {
if (isNode(selector)) return this == selector
if (isFinite(selector.length)) return !!~flatten(selector).indexOf(this)
for (var selectors = selector.split(','), tokens, dividedTokens; selector = selectors.pop(); tokens = selector.split(tokenizr).slice(0))
if (interpret.apply(this, q(tokens.pop())) && (!tokens.length || ancestorMatch(this, tokens, selector.match(dividers)))) return true
};//cpu intersection tests
//offscreen render color test
var pickings = {}
function addEvenLtistener (evt, listener, capture) {
//oaaa
(pickings[this.attr.cx] = (pickings[this.attr.cx] || {}))
[this.attr.cy] = this
this.mouseover = listener
};function Mesh (gl, options, attr) {
var attributes = {}
, count = options.count || 0
, attrList = options.attrList || ['pos', 'color', 'fugue']
, primitive = gl[options.primitive.toUpperCase()]
init()
return {
init : init
, free: free
, alloc: alloc
, draw: draw
, bind: bind
, attributes: attributes
, set: set
, addAttr: addAttr
, removeAttr: removeAttr
, boundingBox: boundingBox
}
function alloc() {
return count += options.primitive == 'points' ? 1
: options.primitive == 'lines' ? 2
: 3
}
function init() {
attrList.forEach(function (name, i) {
var buffer = gl.createBuffer()
var option = options[name] || {}
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
gl.bufferData(gl.ARRAY_BUFFER, 4 * 1e7, gl.STREAM_DRAW)
attributes[name] = {
array: new Float32Array(options[name] && options[name].array || 4e5)
, buffer: buffer
, size: option.size || 4
, changed: true
, loc: i
}
})
}
function free (index) {
var i, attr
for(attr in attributes) {
attr = attributes
i = attr.size
while(i--) attributes[index * attr.size + i] = 0
}
}
function bind (obj) {
obj.posBuffer = attributes.pos.array
obj.fBuffer = attributes.fugue.array
obj.colorBuffer = attributes.color.array
obj.mesh = this
}
function draw (offset) {
if (! count) return
for (var attr in attributes) {
attr = attributes[attr]
gl.bindBuffer(gl.ARRAY_BUFFER, attr.buffer)
gl.vertexAttribPointer(attr.loc, attr.size, gl.FLOAT, false, 0, 0)
gl.enableVertexAttribArray(attr.loc)
if (attr.changed)
gl.bufferSubData(gl.ARRAY_BUFFER, 0, attr.array)
}
gl.drawArrays(primitive, offset || 0, count)
}
function set () {}
function addAttr () {}
function removeAttr () {}
function boundingBox() {}
}
function RenderTarget(screen) {
var gl = screen.gl
, i = 0
, fbo = screen.fbo || null
, prog = screen.program
, types = screen.types = SVGProxy()
, meshes = buildBuffers(gl, screen.types)
var bound_textures = false
screen.mesh && meshes.push(screen.mesh)
meshes.forEach(function (d) { d.mergeProgram = mergeProgram })
initFbo.call(screen)
return screen.__renderTarget__ =
{ update: update, append: append }
function append(el) {
return (types[el.toLowerCase()] || console.log.bind(console, 'oops'))(el)
}
function mergeProgram(d) {
prog = createProgram(gl, build_vs(d), pathgl.fragmentShader)
}
function update () {
if (program != prog) gl.useProgram(program = prog)
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo)
bindTextures()
setUniforms()
beforeRender(gl)
pathgl.uniform('clock', new Date - start)
for(i = -1; ++i < meshes.length;) meshes[i].draw()
}
function setUniforms () {
for (var k in uniforms)
program[k] && program[k](uniforms[k])
}
function bindTextures () {
if (screen.texture) gl.bindTexture(gl.TEXTURE_2D, screen.texture)
// if ((textures[fbo] || []).length && bound_textures)
// gl.bindTexture(gl.TEXTURE_2D, textures[fbo][0].texture)
}
function beforeRender(gl) {
if (! fbo) gl.clear( gl.COLOR_BUFFER_BIT)
gl.viewport(0, 0, screen.width, screen.height)
}
}
function buildBuffers(gl, types) {
var pointMesh = new Mesh(gl, { primitive: 'points' })
pointMesh.bind(types.circle)
pointMesh.bind(types.rect)
var lineMesh = new Mesh(gl, { primitive: 'lines', pos: { size: 2 }})
lineMesh.bind(types.line)
return [pointMesh, lineMesh]
}
function initFbo(width, height) {
if (! this.fbo) return
gl.bindFramebuffer(gl.FRAMEBUFFER, this.fbo)
this.fbo.width = screen.width
this.fbo.height = screen.height
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture, null)
gl.bindFramebuffer(gl.FRAMEBUFFER, null)
}
;//regexes sourced from sizzle
function querySelectorAll(selector, r) {
return selector.replace(/^\s+|\s*([,\s\+\~>]|$)\s*/g, '$1').split(',')
.forEach(function (s) { query(s, this).forEach(push.bind(r = [])) }, this) || r
}
function query(selector, root) {
var symbols = selector.split(/[\s\>\+\~](?![\s\w\-\/\?\&\=\:\.\(\)\!,@#%<>\{\}\$\*\^'"]*\]|[\s\w\+\-]*\))/)
, dividedTokens = selector.match(/([\s\>\+\~])(?![\s\w\-\/\?\&\=\:\.\(\)\!,@#%<>\{\}\$\*\^'"]*\]|[\s\w\+\-]*\))/)
, last = chunk(symbols.pop()), right = [], left = [], item
byTagName.call(root, last[1] || '*').forEach(function (d) { if (item = checkRight.apply(d, last)) right.push(item) })
return symbols.length ? right.forEach(function (e) { if (leftCheck(e, symbols, dividedTokens)) left.push(e) }) || left : right
}
function leftCheck(doc, symbols, divided, cand) {
return cand = function recur(e, i, p) {
while (p = combinators[divided[i]](p, e))
if (checkRight.apply(p, chunk(symbols[i]))) {
if (i) if (cand = recur(p, i - 1, p)) return cand
else return p
}
}(doc, symbols.length - 1, doc)
}
function checkRight(_, tag, classId, attribute, attr, attrCmp, attrVal, _, pseudo, _, pseudoVal, m) {
return pseudo && pseudos[pseudo] && !pseudos[pseudo](this, pseudoVal)
|| tag && tag !== '*' && this.tag && this.tag.toLowerCase() !== tag
|| attribute && !checkAttr(attrCmp, this.attr[attr] || '', attrVal)
|| classId && (m = classId.match(/#([\w\-]+)/)) && m[1] !== this.attr.id
|| classId && (classId.match(/\.[\w\-]+/g) || []).some(matchClass.bind(this)) ? 0 : this
}
function checkAttr(cmp, actual, val) {
return actual.match(RegExp({ '=' : val
, '^=' : '^' + clean(val)
, '$=' : clean(val) + '$'
, '*=' : clean(val)
, '~=' : '(?:^|\\s+)' + clean(val) + '(?:\\s+|$)'
, '|=' : '^' + clean(val) + '(-|$)'
}[cmp] || 'adnan^'))
}
function chunk(query) { return query.match(chunker) }
function byId(id) { return querySelectorAll('[id="' + id + '"]')[0] }
function isNode(el) { return el && typeof el === 'object' }
function previous(n) { while (n = n.previousSibling()) if (n.top) return n }
function clean(s) { return s.replace(/([.*+?\^=!:${}()|\[\]\/\\])/, '\\$1') }
function matchClass(d) { return ! RegExp('(^|\\s+)' + d.slice(1) + '(\\s+|$)').test(this.attr.class) }
function byTagName(name) { return traverse(this, function (doc) { return name == '*' || doc.tagName == name }, []) }
function traverse(node, fn, val) {
return (node.__scene__ || node.children).forEach(function (node) { traverse(node, fn, val), fn(node) && val.push(node) }) || val }
var pseudos = {} //todo
var combinators = { ' ': function (d) { return d && d !== __scene__ && d.parent() }
, '>': function (d, maybe) { return d && d.parent() == maybe.parent() && d.parent() }
, '~': function (d) { return d && d.previousSibling() }
, '+': function (d, ct, p1, p2) { return ! d || ((p1 = previous(d)) && (p2 = previous(ct)) && p1 == p2 && p1) }
}
var chunker =
/^(\*|\w+)?(?:([\.\#]+[\w\-\.#]+)?)(\[([\w\-]+)(?:([\|\^\$\*\~]?\=)['"]?([ \w\-\/\?\&\=\:\.\(\)\!,@#%<>\{\}\$\*\^]+)["']?)?\])?(:([\w\-]+)(\(['"]?([^()]+)['"]?\))?)?/
;function SVGProxy () {
return types.reduce(function (a, type) {
a[type.name] = function x() {
var self = Object.create(type.prototype)
extend(self, x)
self.init(x.mesh.alloc() - 1)
self.attr = {}
return self
}
extend(type.prototype, baseProto, proto[type.name])
return a
}, {})
}
var proto = {
circle: { init: function (i) {
this.fBuffer[i * 4] = 1
this.indices = [i * 4]
}
, cx: function (v) {
this.posBuffer[this.indices[0] + 0] = v
}
, cy: function (v) {
this.posBuffer[this.indices[0] + 1] = v
}
, r: function (v) {
this.posBuffer[this.indices[0] + 2] = v
}
, cz: function (v) {
this.posBuffer[this.indices[0] + 3] = v
}
, fill: function (v) {
this.colorBuffer[this.indices[0]] = v < 0 ? v : parseColor(v)
}
, stroke: function (v) {
this.colorBuffer[this.indices[0]] = parseColor(v)
},
opacity: function () {}
, tagName: 'circle'
, schema: 'cx cy r cz'.split(' ')
}
, ellipse: { init: function () {
}, tagName: 'ellipse'
, cx: noop, cy: noop, rx: noop, ry: noop }
, rect: { init: function (i) {
this.fBuffer[i * 4] = 0
this.indices = [i * 4]
}, tagName: 'rect'
, fill: function (v) {
this.colorBuffer[this.indices[0]] = v < 0 ? v : parseColor(v)
}
, x: function (v){
this.posBuffer[this.indices[0] + 0] = v
}
, y: function (v) {
this.posBuffer[this.indices[0] + 1] = v
}
, width: function (v) {
this.posBuffer[this.indices[0] + 2] = v
}
, height: function (v) {
this.posBuffer[this.indices[0] + 3] = v
}
, rx: noop,
ry: noop
}
, image: { init: function () {
}, tagName: 'image'
, 'xlink:href': noop, height: noop, width: noop, x: noop, y: noop }
, line: { init: function (i) {
this.indices = [i * 2, i * 2 + 1]
}, tagName: 'line'
, x1: function (v) { this.posBuffer[this.indices[0] * 2] = v }
, y1: function (v) { this.posBuffer[this.indices[0] * 2 + 1] = v }
, x2: function (v) { this.posBuffer[this.indices[1] * 2] = v }
, y2: function (v) { this.posBuffer[this.indices[1] * 2 + 1] = v }
, stroke: function (v) {
var fill = parseColor(v)
this.indices.forEach(function (i) {
this.colorBuffer[i * 4] = parseInt(fill.toString().slice(1), 16)
}, this)
}
}
, path: { init: function () {
}, tagName: 'path'
, d: buildPath
, pathLength: noop
, stroke: function (v) {
var fill = parseColor(v)
this.indices.forEach(function (i) {
this.colorBuffer[i] = + parseInt(fill.toString().slice(1), 16)
}, this)
}
}
, polygon: { init: function () {
}, tagName: 'polygon'
, points: noop }
, polyline: { init: function (i) {
this.indices = [i * 2, i * 2 + 1]
}, tagName: 'polyline'
, points: noop }
, g: { init: function () {
}, tagName: 'g'
, appendChild: function (tag) { this.children.push(appendChild(tag)) }, ctr: function () { this.children = [] } }
, text: { init: function () {
}, tagName: 'text'
, x: noop, y: noop, dx: noop, dy: noop }
}
var baseProto = {
querySelectorAll: querySelectorAll
, children: Object.freeze([])
, querySelector: function (s) { return this.querySelectorAll(s)[0] }
, createElementNS: identity
, insertBefore: noop
, ownerDocument: { createElementNS: function (_, x) { debugger ;return x } }
, previousSibling: function () { canvas.scene[canvas.__scene__.indexOf(this) - 1] }
, nextSibling: function () { canvas.scene[canvas.__scene__.indexOf() + 1] }
, parent: function () { return __scene__ }
, gl: gl
, transform: function (d) {
}
, getAttribute: function (name) {
return this.attr[name]
}
, setAttribute: function (name, value) {
if (value.ctr == Texture) value = + value
this.attr[name] = value
this[name] && this[name](value)
}
, style: { setProperty: noop }
, removeAttribute: function (name) {
delete this.attr[name]
}
, textContent: noop
, removeEventListener: noop
, addEventListener: addEventListener
, ownerSVGElement: {
createSVGPoint: function () {}
}
}
var types = [
function circle () {}
, function rect() {}
, function ellipse() {}
, function line() {}
, function path() {}
, function polygon() {}
, function polyline() {}
, function image() {}
, function text() {}
, function g() {}
]
function buildPath (d) {
parse.call(this, d, this.stroke(this.attr.stroke))
this.stroke(this.attr.stroke)
}
function insertBefore(node, next) {
var scene = canvas.__scene__
, i = scene.indexOf(next)
reverseEach(scene.slice(i, scene.push(0)),
function (d, i) { scene[i] = scene[i - 1] })
scene[i] = node
}
function appendChild(el) {
return this.__scene__[this.__scene__.length] = this.__renderTarget__.append(el.tagName)
}
function removeChild(el) {
var i = this.__scene__.indexOf(el)
el = this.__scene__.splice(i, 1)[0]
el && el.mesh.free(i)
//el.buffer.changed = true
//el.buffer.count -= 1
}
var attrDefaults = {
rotation: [0, 1]
, translate: [0, 0]
, scale: [1, 1]
, fill: 0
, stroke: 0
, 'stroke-width': 2
, cx: 0
, cy: 0
, x: 0
, y: 0
, opacity: .999
}
;var Texture = {
init: initTexture
, update: function () { gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this.data) }
, z: function () {
var sq = Math.sqrt(this.size)
return function (d, i) { return -1.0 / sq * ~~ (i % sq) }
}
, x: function () {
var sq = Math.sqrt(this.size)
return function (d, i) { return -1.0 / sq * ~~ (i % sq) }
}
, y: function () {
var sq = Math.sqrt(this.size)
return function (d, i) { return -1.0 / sq * ~~ (i / sq) }
}
, forEach: function () {}
, load: function () {
var image = this.data
if (image.complete || image.readyState == 4) this.init()
else image.addEventListener && image.addEventListener('load', this.init)
return this
}
, repeat: function () {
this.task = this.update.bind(this)
tasks.push(this.task)
return this
}
, stop : function () {
this.task && tasks.splice(tasks.indexOf(this.task))
delete this.task
}
, appendChild: function (el) {
return this.__scene__[this.__scene__.length] = this.__renderTarget__.append(el.tagName || el)
}
, valueOf: function () {
return - 1
}
, copy: function () { return pathgl.texture(this.src) }
, pipe: pipeTexture
, querySelectorAll: querySelectorAll
, __scene__: []
, ownerDocument: { createElementNS: function (_, x) { return x } }
, unwrap: unwrap
}
extend(RenderTexture.prototype, appendable, Texture, {
update: function () {
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, this.width, this.height, 0, gl.RGBA, gl.FLOAT, this.data || null)
}
})
extend(DataTexture.prototype, Texture, {})
function RenderTexture(prog, options) {
extend(this, {
fbo: gl.createFramebuffer()
, program: prog || program
, gl: gl
, texture: gl.createTexture()
, width: 512
, height: 512
, mesh: Mesh(gl, { pos: { array: Quad(), size: 2 }
, attrList: ['pos']
, count: 4
, primitive: 'triangle_strip'
})
}, options)
this.texture.unit = 0
this.init()
this.__renderTarget__ = RenderTarget(this)
this.start()
this.update = function () {
this.step && this.step()
this.__renderTarget__.update()
}
}
function ShaderTexture (shader, options) {
var prog = createProgram(gl, simulation_vs, shader, ['pos'])
extend(options, {})
return new RenderTexture(prog, options)
}
function DataTexture (image, options, target) {
if ('string' == typeof image) image = parseImage(image)
extend(this, {
gl: gl
, data: image
, texture: gl.createTexture()
, width: image.width || 512
, height: image.height || 512
}, options).load()
}
function initTexture() {
gl.bindTexture(gl.TEXTURE_2D, this.texture)
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
this.update()
}
function parseImage (image) {
// string
// selector
// url
// object
// video / image / canvas
// imageData
// typedarray
// array / nodelist
var query = document.querySelector(image)
if (query) return query
return extend(isVideoUrl ? new Image : document.createElement('video'), { src: image })
}
function isShader(str) {
return str.length > 50
}
function pipeTexture() {
}
function unwrap() {
var uv = new Array(this.size), i = this.size || 0
while(i--) uv[i] = {x: this.x()(i, i), y: this.y(i, i)(i, i) }
return uv
};;var simulation_vs = [
'precision mediump float;'
, 'attribute vec2 pos;'
, ' void main() {'
, ' gl_Position = vec4( pos.xy, 1.0 , 1.0);'
, ' }'
].join('\n')
var forceShader = [
, 'precision mediump float;'
, 'uniform sampler2D texture;'
, 'uniform vec2 resolution;'
, 'uniform vec2 mouse;'
, 'uniform vec2 dimensions;'
, 'vec4 texelAtOffet( vec2 offset ) { '
+ 'return texture2D(texture, (gl_FragCoord.xy + offset) / dimensions); }'
, 'void main() {'
, 'vec3 TARGET = vec3(mouse / resolution, 0.01 );'
, 'int slot = int( mod( gl_FragCoord.x, 2.0 ) );'
, 'if ( slot == 0 ) { '
, 'vec4 dataA = texelAtOffet( vec2( 0, 0 ) );'
, 'vec4 dataB = texelAtOffet( vec2( 1, 0 ) );'
, 'vec3 pos = dataA.xyz;'
, 'vec3 vel = dataB.xyz;'
, 'float phase = dataA.w;'
, 'if ( phase > 0.0 ) {'
, 'pos += vel * 0.005;'
, 'if ( length( TARGET - pos ) < 0.035 ) phase = 0.0;'
, ' else phase += 0.1;'
, '} else {'
, ' pos = vec3(-1);'
, '}'
, ' gl_FragColor = vec4( pos, phase );'
, '} else if ( slot == 1 ) { '
, 'vec4 dataA = texelAtOffet( vec2( -1, 0 ) );'
, 'vec4 dataB = texelAtOffet( vec2( 0, 0 ) );'
, 'vec3 pos = dataA.xyz;'
, 'vec3 vel = dataB.xyz;'
, 'float phase = dataA.w;'
, 'if ( phase > 0.0 ) {'
, 'vec3 delta = normalize( TARGET - pos );'
, 'vel += delta * 0.1;'
, ' vel *= 0.991;'
, '} else {'
, ' vel = vec3(0);'
, '}'
, ' gl_FragColor = vec4( vel, 1.0 );'
, ' }'
, '}'
].join('\n')
var since = Date.now()
pathgl.sim.force = function (size) {
var particleData = new Float32Array(2 * 4 * size)
var width = Math.sqrt(size) * 2
var height = Math.sqrt(size)
var elapsed = 0, cooldown = 16
var rate = 500
var particleIndex = 0
var gl, texture
var mousemove = mousemove.bind(this)
return pathgl.texture(forceShader, {
step: step
, data: particleData
, width: width
, height: height
, size: size
, start: start
, emit: mousemove
})
function step () { }
function start () {
var now = Date.now() - since
, origin = [ -1.0 + Math.sin(now * 0.001) * 2.0
, -0.2 + Math.cos(now * 0.004) * 0.5
, Math.sin(now * 0.015) * -0.05
]
pathgl.uniform('dimensions', [width, height])
emit(gl = this.gl, texture = this.texture, 40000, origin)
}
function mousemove() {
var count = rate * Math.random()
, origin = svgToClipSpace(d3.mouse(d3.select('canvas').node())).concat(0)
emit(gl, texture, count, origin)
}
function emit(gl, tex, count, origin, velocities) {
velocities = velocities || { x:0, y:0, z:0 }
gl.activeTexture( gl.TEXTURE0 + tex.unit)
gl.bindTexture(gl.TEXTURE_2D, tex)
var x = ~~(( particleIndex * 2) % width)
var y = ~~(particleIndex / height)
var chunks = [{ x: x, y: y, size: count * 2 }]
function split( chunk ) {
var boundary = chunk.x + chunk.size;
if (boundary > width) {
var delta = boundary - width
chunk.size -= delta;
chunk = { x: 0, y: ( chunk.y + 1 ) % height, size: delta }
chunks.push(chunk)
split(chunk)
}
}
split(chunks[0])
var i, j, n, m, chunk, data, force = 1.0;
for (i = 0, n = chunks.length; i < n; i++) {
chunk = chunks[i]
data = []
for (j = 0, m = chunk.size; j < m; j++) {
data.push(
origin[0],
origin[1],
0,
Math.random() * 10,
velocities.x + force * random(-1.0, 1.0),
velocities.y + force * random(-1.0, 1.0),
velocities.z + force * random(-1.0, 1.0),
0
)
}
gl.texSubImage2D(gl.TEXTURE_2D, 0, chunk.x, chunk.y, chunk.size, 1,
gl.RGBA, gl.FLOAT, new Float32Array(data))
}
particleIndex += count
particleIndex %= size
}
}
function random (min, max) {
return Math.random() * ( max - min );
}
function svgToClipSpace(pos) { return [2 * (pos[0] / 960) - 1, 1 - (pos[1] / 500 * 2)] } }()