PageSourceSearch

https://aspireyouth.com/wp-content/themes/levelup/assets/js/fx/seriously.js?ver=2.0.11

js aspireyouth.com collected 2026-10-02 00:15:55 UTC 156,293 bytes, 6,776 lines download raw bytes

1/*jslint devel: true, bitwise: true, browser: true, white: true, nomen: true, plusplus: true, maxerr: 50, indent: 4, todo: true */
2/*global Float32Array, Uint8Array, Uint16Array, WebGLTexture, HTMLInputElement, HTMLSelectElement, HTMLElement, WebGLFramebuffer, HTMLCanvasElement, WebGLRenderingContext, define, module, exports */
3(function (root, factory) {
4	'use strict';
5	if (typeof define === 'function' && define.amd) {
6		// AMD. Register as an anonymous module.
7		define('seriously', function () {
8			var Seriously = factory(root);
9			if (!root.Seriously) {
10				root.Seriously = Seriously;
11			}
12			return Seriously;
13		});
14	} else if (typeof exports === 'object') {
15		// Node. Does not work with strict CommonJS, but
16		// only CommonJS-like enviroments that support module.exports,
17		// like Node.
18		module.exports = factory(root);
19	} else if (typeof root.Seriously !== 'function') {
20		// Browser globals
21		root.Seriously = factory(root);
22	}
23}(window, function (window) {
24	'use strict';
25
26	var document = window.document,
27		console = window.console,
28
29	/*
30		Global-ish look-up variables
31	*/
32
33	testContext,
34	colorCtx,
35	incompatibility,
36	seriousEffects = {},
37	seriousTransforms = {},
38	seriousSources = {},
39	seriousTargets = {},
40	timeouts = [],
41	allEffectsByHook = {},
42	allTransformsByHook = {},
43	allSourcesByHook = {
44		canvas: [],
45		image: [],
46		video: []
47	},
48	allTargetsByHook = {},
49	allTargets = window.WeakMap && new WeakMap(),
50	identity,
51	maxSeriouslyId = 0,
52	nop = function () {},
53	noVideoTextureSupport,
54
55	/*
56		Global reference variables
57	*/
58
59	// http://www.w3.org/TR/css3-color/#svg-color
60	colorNames = {
61		transparent: [0, 0, 0, 0],
62		black: [0, 0, 0, 1],
63		red: [1, 0, 0, 1],
64		green: [0, 128 / 255, 0, 1],
65		blue: [0, 0, 1, 1],
66		white: [1, 1, 1, 1],
67		silver: [192 / 255, 192 / 255, 192 / 255, 1],
68		gray: [128 / 255, 128 / 255, 128 / 255, 1],
69		maroon: [128 / 255, 0, 0, 1],
70		purple: [128 / 255, 0, 128 / 255, 1],
71		fuchsia: [1, 0, 1, 1],
72		lime: [0, 1, 0, 1],
73		olive: [128 / 255, 128 / 255, 0, 1],
74		yellow: [1, 1, 0, 1],
75		navy: [0, 0, 128 / 255, 1],
76		teal: [0, 128 / 255, 128 / 255, 1],
77		aqua: [0, 1, 1, 1],
78		orange: [1, 165 / 255, 0, 1]
79	},
80
81	colorRegex = /^(rgb|hsl)a?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(,\s*(\d+(\.\d*)?)\s*)?\)/i,
82	hexColorRegex = /^#(([0-9a-fA-F]{3,8}))/,
83
84	vectorFields = ['x', 'y', 'z', 'w'],
85	colorFields = ['r', 'g', 'b', 'a'],
86
87	outputRenderOptions = {
88		srcRGB: 0x0302, //SRC_ALPHA
89		dstRGB: 0x0303, //ONE_MINUS_SRC_ALPHA
90		srcAlpha: 0x01, //ONE
91		dstAlpha: 0x0303 //ONE_MINUS_SRC_ALPHA
92	},
93
94	shaderDebugConstants = [
95		'MAX_COMBINED_TEXTURE_IMAGE_UNITS',
96		'MAX_FRAGMENT_UNIFORM_VECTORS',
97		'MAX_TEXTURE_IMAGE_UNITS',
98		'MAX_VARYING_VECTORS',
99		'MAX_VERTEX_ATTRIBS',
100		'MAX_VERTEX_TEXTURE_IMAGE_UNITS',
101		'MAX_VERTEX_UNIFORM_VECTORS'
102	],
103
104	shaderNameRegex = /^[\t ]*#define[\t ]+SHADER_NAME\s+([^$\n\r]+)/i,
105
106	baseVertexShader,
107	baseFragmentShader,
108
109	/*
110		utility functions
111	*/
112
113	/*
114	mat4 matrix functions borrowed from gl-matrix by toji
115	https://github.com/toji/gl-matrix
116	License: https://github.com/toji/gl-matrix/blob/master/LICENSE.md
117	*/
118	mat4 = {
119		/*
120		 * mat4.frustum
121		 * Generates a frustum matrix with the given bounds
122		 *
123		 * Params:
124		 * left, right - scalar, left and right bounds of the frustum
125		 * bottom, top - scalar, bottom and top bounds of the frustum
126		 * near, far - scalar, near and far bounds of the frustum
127		 * dest - Optional, mat4 frustum matrix will be written into
128		 *
129		 * Returns:
130		 * dest if specified, a new mat4 otherwise
131		 */
132		frustum: function (left, right, bottom, top, near, far, dest) {
133			if(!dest) { dest = mat4.create(); }
134			var rl = (right - left),
135				tb = (top - bottom),
136				fn = (far - near);
137			dest[0] = (near*2) / rl;
138			dest[1] = 0;
139			dest[2] = 0;
140			dest[3] = 0;
141			dest[4] = 0;
142			dest[5] = (near*2) / tb;
143			dest[6] = 0;
144			dest[7] = 0;
145			dest[8] = (right + left) / rl;
146			dest[9] = (top + bottom) / tb;
147			dest[10] = -(far + near) / fn;
148			dest[11] = -1;
149			dest[12] = 0;
150			dest[13] = 0;
151			dest[14] = -(far*near*2) / fn;
152			dest[15] = 0;
153			return dest;
154		},
155
156		perspective: function (fovy, aspect, near, far, dest) {
157			var top = near*Math.tan(fovy*Math.PI / 360.0),
158				right = top*aspect;
159			return mat4.frustum(-right, right, -top, top, near, far, dest);
160		},
161		multiply: function (dest, mat, mat2) {
162			// Cache the matrix values (makes for huge speed increases!)
163			var a00 = mat[0], a01 = mat[1], a02 = mat[2], a03 = mat[3],
164				a10 = mat[4], a11 = mat[5], a12 = mat[6], a13 = mat[7],
165				a20 = mat[8], a21 = mat[9], a22 = mat[10], a23 = mat[11],
166				a30 = mat[12], a31 = mat[13], a32 = mat[14], a33 = mat[15],
167
168			// Cache only the current line of the second matrix
169			b0 = mat2[0], b1 = mat2[1], b2 = mat2[2], b3 = mat2[3];
170			dest[0] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
171			dest[1] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
172			dest[2] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
173			dest[3] = b0*a03 + b1*a13 + b2*a23 + b3*a33;
174
175			b0 = mat2[4];
176			b1 = mat2[5];
177			b2 = mat2[6];
178			b3 = mat2[7];
179			dest[4] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
180			dest[5] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
181			dest[6] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
182			dest[7] = b0*a03 + b1*a13 + b2*a23 + b3*a33;
183
184			b0 = mat2[8];
185			b1 = mat2[9];
186			b2 = mat2[10];
187			b3 = mat2[11];
188			dest[8] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
189			dest[9] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
190			dest[10] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
191			dest[11] = b0*a03 + b1*a13 + b2*a23 + b3*a33;
192
193			b0 = mat2[12];
194			b1 = mat2[13];
195			b2 = mat2[14];
196			b3 = mat2[15];
197			dest[12] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
198			dest[13] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
199			dest[14] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
200			dest[15] = b0*a03 + b1*a13 + b2*a23 + b3*a33;
201
202			return dest;
203		},
204		identity: function (dest) {
205			dest[0] = 1;
206			dest[1] = 0;
207			dest[2] = 0;
208			dest[3] = 0;
209			dest[4] = 0;
210			dest[5] = 1;
211			dest[6] = 0;
212			dest[7] = 0;
213			dest[8] = 0;
214			dest[9] = 0;
215			dest[10] = 1;
216			dest[11] = 0;
217			dest[12] = 0;
218			dest[13] = 0;
219			dest[14] = 0;
220			dest[15] = 1;
221			return dest;
222		},
223		copy: function (out, a) {
224			out[0] = a[0];
225			out[1] = a[1];
226			out[2] = a[2];
227			out[3] = a[3];
228			out[4] = a[4];
229			out[5] = a[5];
230			out[6] = a[6];
231			out[7] = a[7];
232			out[8] = a[8];
233			out[9] = a[9];
234			out[10] = a[10];
235			out[11] = a[11];
236			out[12] = a[12];
237			out[13] = a[13];
238			out[14] = a[14];
239			out[15] = a[15];
240			return out;
241		}
242	},
243
244	requestAnimationFrame = (function (){
245		var lastTime = 0;
246		return  window.requestAnimationFrame ||
247				window.webkitRequestAnimationFrame ||
248				window.mozRequestAnimationFrame ||
249				window.oRequestAnimationFrame ||
250				window.msRequestAnimationFrame ||
251				function (callback) {
252					var currTime, timeToCall, id;
253
254					function timeoutCallback() {
255						callback(currTime + timeToCall);
256					}
257
258					currTime = new Date().getTime();
259					timeToCall = Math.max(0, 16 - (currTime - lastTime));
260					id = window.setTimeout(timeoutCallback, timeToCall);
261					lastTime = currTime + timeToCall;
262					return id;
263				};
264	}()),
265
266	cancelAnimFrame = (function (){
267		return  window.cancelAnimationFrame ||
268				window.webkitCancelAnimationFrame ||
269				window.mozCancelAnimationFrame ||
270				window.oCancelAnimationFrame ||
271				window.msCancelAnimationFrame ||
272				function (id) {
273					window.cancelTimeout(id);
274				};
275	}()),
276
277	reservedEffectProperties = [
278		'alias',
279		'destroy',
280		'effect',
281		'id',
282		'initialize',
283		'inputs',
284		'isDestroyed',
285		'isReady',
286		'matte',
287		'off',
288		'on',
289		'readPixels',
290		'render',
291		'title',
292		'update'
293	],
294
295	reservedTransformProperties = [
296		'alias',
297		'destroy',
298		'id',
299		'inputs',
300		'isDestroyed',
301		'isReady',
302		'off',
303		'on',
304		'source',
305		'title',
306		'update'
307	],
308
309	reservedNames = [
310		'aliases',
311		'defaults',
312		'destroy',
313		'effect',
314		'go',
315		'id',
316		'incompatible',
317		'isDestroyed',
318		'isEffect',
319		'isNode',
320		'isSource',
321		'isTarget',
322		'isTransform',
323		'removeAlias',
324		'render',
325		'source',
326		'stop',
327		'target',
328		'transform'
329	];
330
331	function getElement(input, tags) {
332		var element,
333			tag;
334
335		if (typeof input === 'string') {
336			//element = document.getElementById(input) || document.getElementsByTagName(input)[0];
337			element = document.querySelector(input);
338		} else if (!input) {
339			return false;
340		}
341
342		if (input.tagName) {
343			element = input;
344		}
345
346		if (!element) {
347			return input;
348		}
349
350		tag = element.tagName.toLowerCase();
351		if (tags && tags.indexOf(tag) < 0) {
352			return input;
353		}
354
355		return element;
356	}
357
358	function extend(dest, src) {
359		var property,
360			descriptor;
361
362		//todo: are we sure this is safe?
363		if (dest.prototype && src.prototype && dest.prototype !== src.prototype) {
364			extend(dest.prototype, src.prototype);
365		}
366
367		for (property in src) {
368			if (src.hasOwnProperty(property)) {
369				descriptor = Object.getOwnPropertyDescriptor(src, property);
370
371				if (descriptor.get || descriptor.set) {
372					Object.defineProperty(dest, property, {
373						configurable: true,
374						enumerable: true,
375						get: descriptor.get,
376						set: descriptor.set
377					});
378				} else {
379					dest[property] = src[property];
380				}
381			}
382		}
383
384		return dest;
385	}
386
387	function consoleMethod(name) {
388		var method;
389		if (!console) {
390			return nop;
391		}
392
393		if (typeof console[name] === 'function') {
394			method = console[name];
395		} else if (typeof console.log === 'function') {
396			method = console.log;
397		} else {
398			return nop;
399		}
400
401		if (method.bind) {
402			return method.bind(console);
403		}
404
405		return function () {
406			method.apply(console, arguments);
407		};
408	}
409
410	/*
411	Like instanceof, but it will work on elements that come from different windows (e.g. iframes)
412
413	We do not use this for constructors defined in this script.
414	*/
415	function isInstance(obj, proto) {
416		if (!proto) {
417			proto = 'HTMLElement';
418		}
419
420		if (obj instanceof window[proto]) {
421			return true;
422		}
423
424		if (!obj || typeof obj !== 'object') {
425			return false;
426		}
427
428		while (obj) {
429			obj = Object.getPrototypeOf(obj);
430			if (obj && obj.constructor.name === proto) {
431				return true;
432			}
433		}
434
435		return false;
436	}
437
438	//http://www.w3.org/TR/css3-color/#hsl-color
439	function hslToRgb(h, s, l, a, out) {
440		function hueToRgb(m1, m2, h) {
441			h = h % 1;
442			if (h < 0) {
443				h += 1;
444			}
445			if (h < 1 / 6) {
446				return m1 + (m2 - m1) * h * 6;
447			}
448			if (h < 1 / 2) {
449				return m2;
450			}
451			if (h < 2 / 3) {
452				return m1 + (m2 - m1) * (2/3 - h) * 6;
453			}
454			return m1;
455		}
456
457		var m1, m2;
458		if (l < 0.5) {
459			m2 = l * (s + 1);
460		} else {
461			m2 = l + s - l * s;
462		}
463		m1 = l * 2 - m2;
464
465		if (!out) {
466			out = [];
467		}
468
469		out[0] = hueToRgb(m1, m2, h + 1/3);
470		out[1] = hueToRgb(m1, m2, h);
471		out[2] = hueToRgb(m1, m2, h - 1/3);
472		out[3] = a;
473
474		return out;
475	}
476
477	function colorArrayToHex(color) {
478		var i,
479			val,
480			hex,
481			s = '#',
482			len = color[3] < 1 ? 4 : 3;
483
484		for (i = 0; i < len; i++) {
485			val = Math.min(255, Math.round(color[i] * 255 || 0));
486			hex = val.toString(16);
487			if (val < 16) {
488				hex = '0' + hex;
489			}
490			s += hex;
491		}
492		return s;
493	}
494
495	function isArrayLike(obj) {
496		return Array.isArray(obj) ||
497			(obj && obj.BYTES_PER_ELEMENT && 'length' in obj);
498	}
499
500	/*
501	faster than setTimeout(fn, 0);
502	http://dbaron.org/log/20100309-faster-timeouts
503	*/
504	function setTimeoutZero(fn) {
505		/*
506		Workaround for postMessage bug in Firefox if the page is loaded from the file system
507		https://bugzilla.mozilla.org/show_bug.cgi?id=740576
508		Should run fine, but maybe a few milliseconds slower per frame.
509		*/
510		function timeoutFunction() {
511			if (timeouts.length) {
512				(timeouts.shift())();
513			}
514		}
515
516		if (typeof fn !== 'function') {
517			throw new Error('setTimeoutZero argument is not a function');
518		}
519
520		timeouts.push(fn);
521		if (window.location.protocol === 'file:') {
522			setTimeout(timeoutFunction, 0);
523			return;
524		}
525
526		window.postMessage('seriously-timeout-message', window.location);
527	}
528
529	window.addEventListener('message', function (event) {
530		if (event.source === window && event.data === 'seriously-timeout-message') {
531			event.stopPropagation();
532			if (timeouts.length > 0) {
533				var fn = timeouts.shift();
534				fn();
535			}
536		}
537	}, true);
538
539	function getWebGlContext(canvas, options) {
540		var context;
541		try {
542			if (window.WebGLDebugUtils && options && options.debugContext) {
543				context = window.WebGLDebugUtils.makeDebugContext(canvas.getContext('webgl', options));
544			} else {
545				context = canvas.getContext('webgl', options);
546			}
547		} catch (expError) {
548		}
549
550		if (!context) {
551			try {
552				context = canvas.getContext('experimental-webgl', options);
553			} catch (error) {
554			}
555		}
556		return context;
557	}
558
559	function getTestContext() {
560		var canvas;
561
562		if (testContext && testContext.getError() === testContext.CONTEXT_LOST_WEBGL) {
563			/*
564			Test context was lost already, and the webglcontextlost event maybe hasn't fired yet
565			so try making a new context
566			*/
567			testContext = undefined;
568		}
569
570		if (testContext || !window.WebGLRenderingContext || incompatibility) {
571			return testContext;
572		}
573
574		canvas = document.createElement('canvas');
575		testContext = getWebGlContext(canvas);
576
577		if (testContext) {
578			canvas.addEventListener('webglcontextlost', function contextLost(event) {
579				/*
580				If/When context is lost, just clear testContext and create
581				a new one the next time it's needed
582				*/
583				event.preventDefault();
584				if (testContext && testContext.canvas === this) {
585					testContext = undefined;
586					canvas.removeEventListener('webglcontextlost', contextLost, false);
587				}
588			}, false);
589		} else {
590			Seriously.logger.warn('Unable to access WebGL.');
591		}
592
593		return testContext;
594	}
595
596	function checkSource(source) {
597		var element, canvas, ctx, texture;
598
599		//todo: don't need to create a new array every time we do this
600		element = getElement(source, ['img', 'canvas', 'video']);
601		if (!element) {
602			return false;
603		}
604
605		canvas = document.createElement('canvas');
606		if (!canvas) {
607			Seriously.logger.warn('Browser does not support canvas or Seriously.js');
608			return false;
609		}
610
611		if (element.naturalWidth === 0 && element.tagName === 'IMG') {
612			Seriously.logger.warn('Image not loaded');
613			return false;
614		}
615
616		if (element.readyState === 0 && element.videoWidth === 0 && element.tagName === 'VIDEO') {
617			Seriously.logger.warn('Video not loaded');
618			return false;
619		}
620
621		ctx = getTestContext();
622		if (ctx) {
623			texture = ctx.createTexture();
624			if (!texture) {
625				Seriously.logger.error('Test WebGL context has been lost');
626			}
627
628			ctx.bindTexture(ctx.TEXTURE_2D, texture);
629
630			try {
631				ctx.texImage2D(ctx.TEXTURE_2D, 0, ctx.RGBA, ctx.RGBA, ctx.UNSIGNED_BYTE, element);
632			} catch (textureError) {
633				if (textureError.code === window.DOMException.SECURITY_ERR) {
634					Seriously.logger.log('Unable to access cross-domain image');
635				} else {
636					Seriously.logger.error('Error storing image to texture: ' + textureError.message);
637				}
638				ctx.deleteTexture(texture);
639				return false;
640			}
641			ctx.deleteTexture(texture);
642		} else {
643			ctx = canvas.getContext('2d');
644			try {
645				ctx.drawImage(element, 0, 0);
646				ctx.getImageData(0, 0, 1, 1);
647			} catch (drawImageError) {
648				if (drawImageError.code === window.DOMException.SECURITY_ERR) {
649					Seriously.logger.log('Unable to access cross-domain image');
650				} else {
651					Seriously.logger.error('Error drawing image to canvas: ' + drawImageError.message);
652				}
653				return false;
654			}
655		}
656
657		// This method will return a false positive for resources that aren't
658		// actually images or haven't loaded yet
659
660		return true;
661	}
662
663	function validateInputSpecs(plugin) {
664		var input,
665			options,
666			name;
667
668		function normalizeEnumOption(option, i) {
669			var key,
670				name;
671
672			if (isArrayLike(option)) {
673				key = option[0];
674				name = option[1] || key;
675			} else {
676				key = option;
677			}
678
679			if (typeof key === 'string') {
680				key = key.toLowerCase();
681			} else if (typeof key === 'number') {
682				key = String(key);
683			} else if (!key) {
684				key = '';
685			}
686
687			options[key] = name;
688
689			if (!i) {
690				input.firstValue = key;
691			}
692		}
693
694		function passThrough(value) {
695			return value;
696		}
697
698		for (name in plugin.inputs) {
699			if (plugin.inputs.hasOwnProperty(name)) {
700				if (plugin.reserved.indexOf(name) >= 0 || Object.prototype[name]) {
701					throw new Error('Reserved input name: ' + name);
702				}
703
704				input = plugin.inputs[name];
705				input.name = name;
706
707				if (isNaN(input.min)) {
708					input.min = -Infinity;
709				}
710
711				if (isNaN(input.max)) {
712					input.max = Infinity;
713				}
714
715				if (isNaN(input.minCount)) {
716					input.minCount = -Infinity;
717				}
718
719				if (isNaN(input.maxCount)) {
720					input.maxCount = Infinity;
721				}
722
723				if (isNaN(input.step)) {
724					input.step = 0;
725				}
726
727				if (isNaN(input.mod)) {
728					input.mod = 0;
729				}
730
731				if (input.type === 'enum') {
732					/*
733					Normalize options to make validation easy
734					- all items will have both a key and a name
735					- all keys will be lowercase strings
736					*/
737					if (input.options && isArrayLike(input.options) && input.options.length) {
738						options = {};
739						input.options.forEach(normalizeEnumOption);
740						input.options = options;
741					}
742				}
743
744				if (input.type === 'vector') {
745					if (input.dimensions < 2) {
746						input.dimensions = 2;
747					} else if (input.dimensions > 4) {
748						input.dimensions = 4;
749					} else if (!input.dimensions || isNaN(input.dimensions)) {
750						input.dimensions = 4;
751					} else {
752						input.dimensions = Math.round(input.dimensions);
753					}
754				} else {
755					input.dimensions = 1;
756				}
757
758				input.shaderDirty = !!input.shaderDirty;
759
760				if (typeof input.validate !== 'function') {
761					input.validate = Seriously.inputValidators[input.type] || passThrough;
762				}
763
764				if (!plugin.defaultImageInput && input.type === 'image') {
765					plugin.defaultImageInput = name;
766				}
767			}
768		}
769	}
770
771	/*
772		helper Classes
773	*/
774
775	function FrameBuffer(gl, width, height, options) {
776		var frameBuffer,
777			renderBuffer,
778			tex,
779			status,
780			useFloat = options === true ? options : (options && options.useFloat);
781
782		useFloat = false;//useFloat && !!gl.getExtension('OES_texture_float'); //useFloat is not ready!
783		if (useFloat) {
784			this.type = gl.FLOAT;
785		} else {
786			this.type = gl.UNSIGNED_BYTE;
787		}
788
789		frameBuffer = gl.createFramebuffer();
790		gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
791
792		if (options && options.texture) {
793			this.texture = options.texture;
794			gl.bindTexture(gl.TEXTURE_2D, this.texture);
795			this.ownTexture = false;
796		} else {
797			this.texture = gl.createTexture();
798			gl.bindTexture(gl.TEXTURE_2D, this.texture);
799			gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
800			gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
801			gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
802			gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
803			gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
804			this.ownTexture = true;
805		}
806
807		try {
808			if (this.type === gl.FLOAT) {
809				tex = new Float32Array(width * height * 4);
810				gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.FLOAT, tex);
811			} else {
812				gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
813				this.type = gl.UNSIGNED_BYTE;
814			}
815		} catch (e) {
816			// Null rejected
817			this.type = gl.UNSIGNED_BYTE;
818			tex = new Uint8Array(width * height * 4);
819			gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, tex);
820		}
821
822		renderBuffer = gl.createRenderbuffer();
823		gl.bindRenderbuffer(gl.RENDERBUFFER, renderBuffer);
824		gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, width, height);
825		gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, renderBuffer);
826
827		gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture, 0);
828
829		status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
830
831		if (status === gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT) {
832			throw new Error('Incomplete framebuffer: FRAMEBUFFER_INCOMPLETE_ATTACHMENT');
833		}
834
835		if (status === gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT) {
836			throw new Error('Incomplete framebuffer: FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT');
837		}
838
839		if (status === gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS) {
840			throw new Error('Incomplete framebuffer: FRAMEBUFFER_INCOMPLETE_DIMENSIONS');
841		}
842
843		if (status === gl.FRAMEBUFFER_UNSUPPORTED) {
844			throw new Error('Incomplete framebuffer: FRAMEBUFFER_UNSUPPORTED');
845		}
846
847		if (status !== gl.FRAMEBUFFER_COMPLETE) {
848			throw new Error('Incomplete framebuffer: ' + status);
849		}
850
851		//clean up
852		gl.bindTexture(gl.TEXTURE_2D, null);
853		gl.bindRenderbuffer(gl.RENDERBUFFER, null);
854		gl.bindFramebuffer(gl.FRAMEBUFFER, null);
855
856		this.gl = gl;
857		this.frameBuffer = frameBuffer;
858		this.renderBuffer = renderBuffer;
859		this.width = width;
860		this.height = height;
861	}
862
863	FrameBuffer.prototype.resize = function (width, height) {
864		var gl = this.gl;
865
866		if (this.width === width && this.height === height) {
867			return;
868		}
869
870		this.width = width;
871		this.height = height;
872
873		if (!gl) {
874			return;
875		}
876
877		gl.bindTexture(gl.TEXTURE_2D, this.texture);
878		gl.bindFramebuffer(gl.FRAMEBUFFER, this.frameBuffer);
879		gl.bindRenderbuffer(gl.RENDERBUFFER, this.renderBuffer);
880
881		//todo: handle float
882		gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
883		gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, width, height);
884		gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture, 0);
885
886		gl.bindTexture(gl.TEXTURE_2D, null);
887		gl.bindRenderbuffer(gl.RENDERBUFFER, null);
888		gl.bindFramebuffer(gl.FRAMEBUFFER, null);
889	};
890
891	FrameBuffer.prototype.destroy = function () {
892		var gl = this.gl;
893
894		if (gl) {
895			gl.deleteFramebuffer(this.frameBuffer);
896			gl.deleteRenderbuffer(this.renderBuffer);
897			if (this.ownTexture) {
898				gl.deleteTexture(this.texture);
899			}
900		}
901
902		delete this.frameBuffer;
903		delete this.renderBuffer;
904		delete this.texture;
905		delete this.gl;
906	};
907
908	/* ShaderProgram - utility class for building and accessing WebGL shaders */
909
910	function ShaderProgram(gl, vertexShaderSource, fragmentShaderSource) {
911		var program, vertexShader, fragmentShader,
912			programError = '',
913			shaderError,
914			i, l,
915			shaderNameRegexMatch,
916			obj;
917
918		function compileShader(source, fragment) {
919			var shader, j;
920			if (fragment) {
921				shader = gl.createShader(gl.FRAGMENT_SHADER);
922			} else {
923				shader = gl.createShader(gl.VERTEX_SHADER);
924			}
925
926			gl.shaderSource(shader, source);
927			gl.compileShader(shader);
928
929			if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
930				source = source.split(/[\n\r]/);
931				for (j = 0; j < source.length; j++) {
932					source[j] = (j + 1) + ':\t' + source[j];
933				}
934				source.unshift('Error compiling ' + (fragment ? 'fragment' : 'vertex') + ' shader:');
935				Seriously.logger.error(source.join('\n'));
936				throw new Error('Shader error: ' + gl.getShaderInfoLog(shader));
937			}
938
939			return shader;
940		}
941
942		function makeShaderSetter(info, loc) {
943			if (info.type === gl.SAMPLER_2D) {
944				return function (value) {
945					info.glTexture = gl['TEXTURE' + value];
946					gl.uniform1i(loc, value);
947				};
948			}
949
950			if (info.type === gl.BOOL|| info.type === gl.INT) {
951				if (info.size > 1) {
952					return function (value) {
953						gl.uniform1iv(loc, value);
954					};
955				}
956
957				return function (value) {
958					gl.uniform1i(loc, value);
959				};
960			}
961
962			if (info.type === gl.FLOAT) {
963				if (info.size > 1) {
964					return function (value) {
965						gl.uniform1fv(loc, value);
966					};
967				}
968
969				return function (value) {
970					gl.uniform1f(loc, value);
971				};
972			}
973
974			if (info.type === gl.FLOAT_VEC2) {
975				return function (obj) {
976					gl.uniform2f(loc, obj[0], obj[1]);
977				};
978			}
979
980			if (info.type === gl.FLOAT_VEC3) {
981				return function (obj) {
982					gl.uniform3f(loc, obj[0], obj[1], obj[2]);
983				};
984			}
985
986			if (info.type === gl.FLOAT_VEC4) {
987				return function (obj) {
988					gl.uniform4f(loc, obj[0], obj[1], obj[2], obj[3]);
989				};
990			}
991
992			if (info.type === gl.FLOAT_MAT3) {
993				return function (mat3) {
994					gl.uniformMatrix3fv(loc, false, mat3);
995				};
996			}
997
998			if (info.type === gl.FLOAT_MAT4) {
999				return function (mat4) {
1000					gl.uniformMatrix4fv(loc, false, mat4);
1001				};
1002			}
1003
1004			throw new Error('Unknown shader uniform type: ' + info.type);
1005		}
1006
1007		function makeShaderGetter(loc) {
1008			return function () {
1009				return gl.getUniform(program, loc);
1010			};
1011		}
1012
1013		vertexShader = compileShader(vertexShaderSource);
1014		fragmentShader = compileShader(fragmentShaderSource, true);
1015
1016		program = gl.createProgram();
1017		gl.attachShader(program, vertexShader);
1018		shaderError = gl.getShaderInfoLog(vertexShader);
1019		if (shaderError) {
1020			programError += 'Vertex shader error: ' + shaderError + '\n';
1021		}
1022		gl.attachShader(program, fragmentShader);
1023		shaderError = gl.getShaderInfoLog(fragmentShader);
1024		if (shaderError) {
1025			programError += 'Fragment shader error: ' + shaderError + '\n';
1026		}
1027		gl.linkProgram(program);
1028
1029		if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
1030			programError += gl.getProgramInfoLog(program);
1031			gl.deleteProgram(program);
1032			gl.deleteShader(vertexShader);
1033			gl.deleteShader(fragmentShader);
1034
1035			shaderNameRegexMatch = shaderNameRegex.exec(vertexShaderSource) ||
1036				shaderNameRegex.exec(fragmentShaderSource);
1037
1038			if (shaderNameRegexMatch) {
1039				programError = 'Shader = ' + shaderNameRegexMatch[1] + '\n' + programError;
1040			}
1041
1042			shaderDebugConstants.forEach(function (c) {
1043				programError += '\n' + c + ': ' + gl.getParameter(gl[c]);
1044			});
1045
1046			throw new Error('Could not initialize shader:\n' + programError);
1047		}
1048
1049		gl.useProgram(program);
1050
1051		this.uniforms = {};
1052
1053		l = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);
1054		for (i = 0; i < l; ++i) {
1055			obj = {
1056				info: gl.getActiveUniform(program, i)
1057			};
1058
1059			obj.name = obj.info.name.replace(/\[0\]$/, '');
1060			obj.loc = gl.getUniformLocation(program, obj.name);
1061			obj.set = makeShaderSetter(obj.info, obj.loc);
1062			obj.get = makeShaderGetter(obj.loc);
1063			this.uniforms[obj.name] = obj;
1064
1065			if (!this[obj.name]) {
1066				//for convenience
1067				this[obj.name] = obj;
1068			}
1069		}
1070
1071		this.attributes = {};
1072		this.location = {};
1073		l = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES);
1074		for (i = 0; i < l; ++i) {
1075			obj = {
1076				info: gl.getActiveAttrib(program, i)
1077			};
1078
1079			obj.name = obj.info.name;
1080			obj.location = gl.getAttribLocation(program, obj.name);
1081			this.attributes[obj.name] = obj;
1082			this.location[obj.name] = obj.location;
1083		}
1084
1085		this.gl = gl;
1086		this.program = program;
1087
1088		this.destroy = function () {
1089			var key;
1090
1091			if (gl) {
1092				gl.deleteProgram(program);
1093				gl.deleteShader(vertexShader);
1094				gl.deleteShader(fragmentShader);
1095			}
1096
1097			for (key in this) {
1098				if (this.hasOwnProperty(key)) {
1099					delete this[key];
1100				}
1101			}
1102
1103			program = null;
1104			vertexShader = null;
1105			fragmentShader = null;
1106		};
1107	}
1108
1109	ShaderProgram.prototype.use = function () {
1110		this.gl.useProgram(this.program);
1111	};
1112
1113	/*
1114		main class: Seriously
1115	*/
1116
1117	function Seriously(options) {
1118
1119		//if called without 'new', make a new object and return that
1120		if (window === this || !(this instanceof Seriously) || this.id !== undefined) {
1121			return new Seriously(options);
1122		}
1123
1124		//initialize object, private properties
1125		var id = ++maxSeriouslyId,
1126			seriously = this,
1127			nodes = [],
1128			nodesById = {},
1129			nodeId = 0,
1130			sources = [],
1131			targets = [],
1132			transforms = [],
1133			effects = [],
1134			aliases = {},
1135			preCallbacks = [],
1136			postCallbacks = [],
1137			defaultInputs = {},
1138			glCanvas,
1139			gl,
1140			primaryTarget,
1141			rectangleModel,
1142			commonShaders = {},
1143			baseShader,
1144			Node, SourceNode, EffectNode, TransformNode, TargetNode,
1145			Effect, Source, Transform, Target,
1146			auto = false,
1147			isDestroyed = false,
1148			rafId;
1149
1150		function makeGlModel(shape, gl) {
1151			var vertex, index, texCoord;
1152
1153			if (!gl) {
1154				return false;
1155			}
1156
1157			vertex = gl.createBuffer();
1158			gl.bindBuffer(gl.ARRAY_BUFFER, vertex);
1159			gl.bufferData(gl.ARRAY_BUFFER, shape.vertices, gl.STATIC_DRAW);
1160			vertex.size = 3;
1161
1162			index = gl.createBuffer();
1163			gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, index);
1164			gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, shape.indices, gl.STATIC_DRAW);
1165
1166			texCoord = gl.createBuffer();
1167			gl.bindBuffer(gl.ARRAY_BUFFER, texCoord);
1168			gl.bufferData(gl.ARRAY_BUFFER, shape.coords, gl.STATIC_DRAW);
1169			texCoord.size = 2;
1170
1171			return {
1172				vertex: vertex,
1173				index: index,
1174				texCoord: texCoord,
1175				length: shape.indices.length,
1176				mode: shape.mode || gl.TRIANGLES
1177			};
1178		}
1179
1180		function buildRectangleModel(gl) {
1181			var shape = {};
1182
1183			shape.vertices = new Float32Array([
1184				-1, -1, 0,
1185				1, -1, 0,
1186				1, 1, 0,
1187				-1, 1, 0
1188			]);
1189
1190			shape.indices = new Uint16Array([
1191				0, 1, 2,
1192				0, 2, 3	// Front face
1193			]);
1194
1195			shape.coords = new Float32Array([
1196				0, 0,
1197				1, 0,
1198				1, 1,
1199				0, 1
1200			]);
1201
1202			return makeGlModel(shape, gl);
1203		}
1204
1205		function attachContext(context) {
1206			var i, node;
1207
1208			if (gl) {
1209				return;
1210			}
1211
1212			context.canvas.addEventListener('webglcontextlost', destroyContext, false);
1213			context.canvas.addEventListener('webglcontextrestored', restoreContext, false);
1214
1215			if (context.isContextLost()) {
1216				Seriously.logger.warn('Unable to attach lost WebGL context. Will try again when context is restored.');
1217				return;
1218			}
1219
1220			gl = context;
1221			glCanvas = context.canvas;
1222
1223			rectangleModel = buildRectangleModel(gl);
1224
1225			baseShader = new ShaderProgram(
1226				gl,
1227				'#define SHADER_NAME seriously.base\n' + baseVertexShader, '#define SHADER_NAME seriously.base\n' + baseFragmentShader
1228			);
1229
1230			for (i = 0; i < effects.length; i++) {
1231				node = effects[i];
1232				node.gl = gl;
1233				node.initialize();
1234				node.buildShader();
1235			}
1236
1237			for (i = 0; i < sources.length; i++) {
1238				node = sources[i];
1239				node.initialize();
1240			}
1241
1242			for (i = 0; i < targets.length; i++) {
1243				node = targets[i];
1244
1245				if (!node.model) {
1246					node.model = rectangleModel;
1247					node.shader = baseShader;
1248				}
1249
1250				//todo: initialize frame buffer if not main canvas
1251			}
1252		}
1253
1254		function restoreContext() {
1255			var context,
1256				target,
1257				i,
1258				node;
1259
1260			if (primaryTarget && !gl) {
1261				target = primaryTarget.target;
1262
1263				//todo: if too many webglcontextlost events fired in too short a time, abort
1264				//todo: consider allowing "manual" control of restoring context
1265
1266				if (isInstance(target, 'WebGLFramebuffer')) {
1267					Seriously.logger.error('Unable to restore target built on WebGLFramebuffer');
1268					return;
1269				}
1270
1271				context = getWebGlContext(target, {
1272					alpha: true,
1273					premultipliedAlpha: true,
1274					preserveDrawingBuffer: true,
1275					stencil: true,
1276					debugContext: primaryTarget.debugContext
1277				});
1278
1279				if (context) {
1280					if (context.isContextLost()) {
1281						Seriously.logger.error('Unable to restore WebGL Context');
1282						return;
1283					}
1284
1285					attachContext(context);
1286
1287					if (primaryTarget.renderToTexture) {
1288						primaryTarget.frameBuffer = new FrameBuffer(gl, primaryTarget.width, primaryTarget.height, false);
1289					} else {
1290						primaryTarget.frameBuffer = {
1291							frameBuffer: null
1292						};
1293					}
1294
1295					/*
1296					Set all nodes dirty. In most cases, it should only be necessary
1297					to set sources dirty, but we want to make sure unattached nodes are covered
1298
1299					This should get renderDaemon running again if necessary.
1300					*/
1301					for (i = 0; i < nodes.length; i++) {
1302						node = nodes[i];
1303						node.setDirty();
1304						node.emit('webglcontextrestored');
1305					}
1306
1307					Seriously.logger.log('WebGL context restored');
1308				}
1309			}
1310		}
1311
1312		function destroyContext(event) {
1313			// either webglcontextlost or primary target node has been destroyed
1314			var i, node;
1315
1316			/*
1317			todo: once multiple shared webgl resources are supported,
1318			see if we can switch context to another existing one and
1319			rebuild immediately
1320			*/
1321
1322			if (event) {
1323				Seriously.logger.warn('WebGL context lost');
1324				/*
1325				todo: if too many webglcontextlost events fired in too short a time,
1326				don't preventDefault
1327				*/
1328				event.preventDefault();
1329			}
1330
1331			//don't draw anymore until context is restored
1332			if (rafId) {
1333				cancelAnimFrame(rafId);
1334				rafId = 0;
1335			}
1336
1337			if (glCanvas) {
1338				glCanvas.removeEventListener('webglcontextlost', destroyContext, false);
1339			}
1340
1341			for (i = 0; i < effects.length; i++) {
1342				node = effects[i];
1343				node.gl = null;
1344				node.initialized = false;
1345				node.baseShader = null;
1346				node.model = null;
1347				node.frameBuffer = null;
1348				node.texture = null;
1349				if (node.shader && node.shader.destroy) {
1350					node.shader.destroy();
1351					if (node.effect.commonShader) {
1352						delete commonShaders[node.hook];
1353					}
1354				}
1355				node.shaderDirty = true;
1356				node.shader = null;
1357				if (node.effect.lostContext) {
1358					node.effect.lostContext.call(node);
1359				}
1360
1361				/*
1362				todo: do we need to set nodes to uready?
1363				if so, make sure nodes never get set to ready unless gl exists
1364				and make sure to set ready again when context is restored
1365				*/
1366
1367				if (event) {
1368					node.emit('webglcontextlost');
1369				}
1370			}
1371
1372			for (i = 0; i < sources.length; i++) {
1373				node = sources[i];
1374				//node.setUnready();
1375				node.texture = null;
1376				node.initialized = false;
1377				node.allowRefresh = false;
1378				if (event) {
1379					node.emit('webglcontextlost');
1380				}
1381			}
1382
1383			for (i = 0; i < transforms.length; i++) {
1384				node = transforms[i];
1385				node.frameBuffer = null;
1386				node.texture = null;
1387				if (event) {
1388					node.emit('webglcontextlost');
1389				}
1390			}
1391
1392			for (i = 0; i < targets.length; i++) {
1393				node = targets[i];
1394				node.model = false;
1395				node.frameBuffer = null;
1396				//texture?
1397				if (event) {
1398					node.emit('webglcontextlost');
1399				}
1400			}
1401
1402			if (baseShader && baseShader.destroy) {
1403				baseShader.destroy();
1404			}
1405
1406			//clean up rectangleModel
1407			if (gl) {
1408				gl.deleteBuffer(rectangleModel.vertex);
1409				gl.deleteBuffer(rectangleModel.texCoord);
1410				gl.deleteBuffer(rectangleModel.index);
1411			}
1412
1413			if (rectangleModel) {
1414				delete rectangleModel.vertex;
1415				delete rectangleModel.texCoord;
1416				delete rectangleModel.index;
1417			}
1418
1419			rectangleModel = null;
1420			baseShader = null;
1421			gl = null;
1422			glCanvas = null;
1423		}
1424
1425		/*
1426		runs on every frame, as long as there are media sources (img, video, canvas, etc.) to check,
1427		dirty target nodes or pre/post callbacks to run. any sources that are updated are set to dirty,
1428		forcing all dependent nodes to render
1429		*/
1430		function renderDaemon(now) {
1431			var i, node,
1432				keepRunning = false;
1433
1434			rafId = 0;
1435
1436			if (preCallbacks.length) {
1437				keepRunning = true;
1438				for (i = 0; i < preCallbacks.length; i++) {
1439					preCallbacks[i].call(seriously, now);
1440				}
1441			}
1442
1443			if (sources && sources.length) {
1444				keepRunning = true;
1445				for (i = 0; i < sources.length; i++) {
1446					node = sources[i];
1447
1448					if (node.dirty ||
1449							node.checkDirty && node.checkDirty()) {
1450						node.dirty = false;
1451						node.setDirty();
1452					}
1453				}
1454			}
1455
1456			for (i = 0; i < targets.length; i++) {
1457				node = targets[i];
1458				if (node.auto && node.dirty) {
1459					node.render();
1460				}
1461			}
1462
1463			if (postCallbacks.length) {
1464				keepRunning = true;
1465				for (i = 0; i < postCallbacks.length; i++) {
1466					postCallbacks[i].call(seriously);
1467				}
1468			}
1469
1470			//rafId may have been set again by a callback or in target.setDirty()
1471			if (keepRunning && !rafId) {
1472				rafId = requestAnimationFrame(renderDaemon);
1473			}
1474		}
1475
1476		function draw(shader, model, uniforms, frameBuffer, node, options) {
1477			var numTextures = 0,
1478				name, value, shaderUniform,
1479				width, height,
1480				nodeGl = (node && node.gl) || gl,
1481				srcRGB, srcAlpha,
1482				dstRGB, dstAlpha;
1483
1484			if (!nodeGl) {
1485				return;
1486			}
1487
1488			if (node) {
1489				width = options && options.width || node.width || nodeGl.canvas.width;
1490				height = options && options.height || node.height || nodeGl.canvas.height;
1491			} else {
1492				width = options && options.width || nodeGl.canvas.width;
1493				height = options && options.height || nodeGl.canvas.height;
1494			}
1495
1496			shader.use();
1497
1498			nodeGl.viewport(0, 0, width, height);
1499
1500			nodeGl.bindFramebuffer(nodeGl.FRAMEBUFFER, frameBuffer);
1501
1502			/* todo: do this all only once at the beginning, since we only have one model? */
1503			nodeGl.enableVertexAttribArray(shader.location.position);
1504			nodeGl.enableVertexAttribArray(shader.location.texCoord);
1505
1506			if (model.texCoord) {
1507				nodeGl.bindBuffer(nodeGl.ARRAY_BUFFER, model.texCoord);
1508				nodeGl.vertexAttribPointer(shader.location.texCoord, model.texCoord.size, nodeGl.FLOAT, false, 0, 0);
1509			}
1510
1511			nodeGl.bindBuffer(nodeGl.ARRAY_BUFFER, model.vertex);
1512			nodeGl.vertexAttribPointer(shader.location.position, model.vertex.size, nodeGl.FLOAT, false, 0, 0);
1513
1514			nodeGl.bindBuffer(nodeGl.ELEMENT_ARRAY_BUFFER, model.index);
1515
1516			//default for depth is disable
1517			if (options && options.depth) {
1518				gl.enable(gl.DEPTH_TEST);
1519			} else {
1520				gl.disable(gl.DEPTH_TEST);
1521			}
1522
1523			//default for blend is enabled
1524			if (!options) {
1525				gl.enable(gl.BLEND);
1526				gl.blendFunc(
1527					gl.ONE,
1528					gl.ZERO
1529				);
1530				gl.blendEquation(gl.FUNC_ADD);
1531			} else if (options.blend === undefined || options.blend) {
1532				gl.enable(gl.BLEND);
1533
1534				srcRGB = options.srcRGB === undefined ? gl.ONE : options.srcRGB;
1535				dstRGB = options.dstRGB || gl.ZERO;
1536				srcAlpha = options.srcAlpha === undefined ? srcRGB : options.srcAlpha;
1537				dstAlpha = options.dstAlpha === undefined ? dstRGB : options.dstAlpha;
1538
1539				gl.blendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha);
1540				gl.blendEquation(options.blendEquation || gl.FUNC_ADD);
1541			} else {
1542				gl.disable(gl.BLEND);
1543			}
1544
1545			/* set uniforms to current values */
1546			for (name in uniforms) {
1547				if (uniforms.hasOwnProperty(name)) {
1548					value = uniforms[name];
1549					shaderUniform = shader.uniforms[name];
1550					if (shaderUniform) {
1551						if (isInstance(value, 'WebGLTexture')) {
1552							nodeGl.activeTexture(nodeGl.TEXTURE0 + numTextures);
1553							nodeGl.bindTexture(nodeGl.TEXTURE_2D, value);
1554							shaderUniform.set(numTextures);
1555							numTextures++;
1556						} else if (value instanceof SourceNode ||
1557								value instanceof EffectNode ||
1558								value instanceof TransformNode) {
1559							if (value.texture) {
1560								nodeGl.activeTexture(nodeGl.TEXTURE0 + numTextures);
1561								nodeGl.bindTexture(nodeGl.TEXTURE_2D, value.texture);
1562								shaderUniform.set(numTextures);
1563								numTextures++;
1564							}
1565						} else if(value !== undefined && value !== null) {
1566							shaderUniform.set(value);
1567						}
1568					}
1569				}
1570			}
1571
1572			//default for clear is true
1573			if (!options || options.clear === undefined || options.clear) {
1574				nodeGl.clearColor(0.0, 0.0, 0.0, 0.0);
1575				nodeGl.clear(nodeGl.COLOR_BUFFER_BIT | nodeGl.DEPTH_BUFFER_BIT);
1576			}
1577
1578			// draw!
1579			nodeGl.drawElements(model.mode, model.length, nodeGl.UNSIGNED_SHORT, 0);
1580
1581			//to protect other 3D libraries that may not remember to turn their depth tests on
1582			gl.enable(gl.DEPTH_TEST);
1583		}
1584
1585		function findInputNode(hook, source, options) {
1586			var node, i;
1587
1588			if (typeof hook !== 'string' || !source && source !== 0) {
1589				if (!options || typeof options !== 'object') {
1590					options = source;
1591				}
1592				source = hook;
1593			}
1594
1595			if (typeof hook !== 'string' || !seriousSources[hook]) {
1596				hook = null;
1597			}
1598
1599			if (source instanceof SourceNode ||
1600					source instanceof EffectNode ||
1601					source instanceof TransformNode) {
1602				node = source;
1603			} else if (source instanceof Effect ||
1604					source instanceof Source ||
1605					source instanceof Transform) {
1606				node = nodesById[source.id];
1607
1608				if (!node) {
1609					throw new Error('Cannot connect a foreign node');
1610				}
1611			} else {
1612				if (typeof source === 'string' && isNaN(source)) {
1613					source = getElement(source, ['canvas', 'img', 'video']);
1614				}
1615
1616				for (i = 0; i < sources.length; i++) {
1617					node = sources[i];
1618					if ((!hook || hook === node.hook) && node.compare && node.compare(source, options)) {
1619						return node;
1620					}
1621				}
1622
1623				node = new SourceNode(hook, source, options);
1624			}
1625
1626			return node;
1627		}
1628
1629		//trace back all sources to make sure we're not making a cyclical connection
1630		function traceSources(node, original) {
1631			var i,
1632				source,
1633				nodeSources;
1634
1635			if (!(node instanceof EffectNode) && !(node instanceof TransformNode)) {
1636				return false;
1637			}
1638
1639			if (node === original) {
1640				return true;
1641			}
1642
1643			nodeSources = node.sources;
1644
1645			for (i in nodeSources) {
1646				if (nodeSources.hasOwnProperty(i)) {
1647					source = nodeSources[i];
1648
1649					if (source === original || traceSources(source, original)) {
1650						return true;
1651					}
1652				}
1653			}
1654
1655			return false;
1656		}
1657
1658		Node = function () {
1659			this.ready = false;
1660			this.width = 1;
1661			this.height = 1;
1662
1663			this.gl = gl;
1664
1665			this.uniforms = {
1666				resolution: [this.width, this.height],
1667				transform: null
1668			};
1669
1670			this.dirty = true;
1671			this.isDestroyed = false;
1672
1673			this.seriously = seriously;
1674
1675			this.listeners = {};
1676
1677			this.id = nodeId;
1678			nodeId++;
1679		};
1680
1681		Node.prototype.setReady = function () {
1682			var i;
1683
1684			if (!this.ready) {
1685				this.ready = true;
1686				this.emit('ready');
1687				if (this.targets) {
1688					for (i = 0; i < this.targets.length; i++) {
1689						this.targets[i].setReady();
1690					}
1691				}
1692			}
1693		};
1694
1695		Node.prototype.setUnready = function () {
1696			var i;
1697
1698			if (this.ready) {
1699				this.ready = false;
1700				this.emit('unready');
1701				if (this.targets) {
1702					for (i = 0; i < this.targets.length; i++) {
1703						this.targets[i].setUnready();
1704					}
1705				}
1706			}
1707		};
1708
1709		Node.prototype.setDirty = function () {
1710			//loop through all targets calling setDirty (depth-first)
1711			var i;
1712
1713			if (!this.dirty) {
1714				this.emit('dirty');
1715				this.dirty = true;
1716				if (this.targets) {
1717					for (i = 0; i < this.targets.length; i++) {
1718						this.targets[i].setDirty();
1719					}
1720				}
1721			}
1722		};
1723
1724		Node.prototype.initFrameBuffer = function (useFloat) {
1725			if (gl) {
1726				this.frameBuffer = new FrameBuffer(gl, this.width, this.height, useFloat);
1727			}
1728		};
1729
1730		Node.prototype.readPixels = function (x, y, width, height, dest) {
1731			var nodeGl = this.gl || gl;
1732
1733			if (!gl) {
1734				//todo: is this the best approach?
1735				throw new Error('Cannot read pixels until a canvas is connected');
1736			}
1737
1738			//todo: check on x, y, width, height
1739
1740			if (!this.frameBuffer) {
1741				this.initFrameBuffer();
1742				this.setDirty();
1743			}
1744
1745			//todo: should we render here?
1746			this.render();
1747
1748			//todo: figure out formats and types
1749			if (dest === undefined) {
1750				dest = new Uint8Array(width * height * 4);
1751			} else if (!(isInstance(dest, 'Uint8Array'))) {
1752				throw new Error('Incompatible array type');
1753			}
1754
1755			nodeGl.bindFramebuffer(gl.FRAMEBUFFER, this.frameBuffer.frameBuffer);
1756			nodeGl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, dest);
1757
1758			return dest;
1759		};
1760
1761		Node.prototype.resize = function () {
1762			var width,
1763				height;
1764
1765			if (this.source) {
1766				width = this.source.width;
1767				height = this.source.height;
1768			} else if (this.sources && this.sources.source) {
1769				width = this.sources.source.width;
1770				height = this.sources.source.height;
1771			} else if (this.inputs && this.inputs.width) {
1772				width = this.inputs.width;
1773				height = this.inputs.height || width;
1774			} else if (this.inputs && this.inputs.height) {
1775				width = height = this.inputs.height;
1776			} else {
1777				//this node will be responsible for calculating its own size
1778				width = 1;
1779				height = 1;
1780			}
1781
1782			width = Math.floor(width);
1783			height = Math.floor(height);
1784
1785			if (this.width !== width || this.height !== height) {
1786				this.width = width;
1787				this.height = height;
1788
1789				this.emit('resize');
1790				this.setDirty();
1791			}
1792
1793			if (this.uniforms && this.uniforms.resolution) {
1794				this.uniforms.resolution[0] = width;
1795				this.uniforms.resolution[1] = height;
1796			}
1797
1798			if (this.frameBuffer && this.frameBuffer.resize) {
1799				this.frameBuffer.resize(width, height);
1800			}
1801		};
1802
1803		Node.prototype.on = function (eventName, callback) {
1804			var listeners,
1805				index = -1;
1806
1807			if (!eventName || typeof callback !== 'function') {
1808				return;
1809			}
1810
1811			listeners = this.listeners[eventName];
1812			if (listeners) {
1813				index = listeners.indexOf(callback);
1814			} else {
1815				listeners = this.listeners[eventName] = [];
1816			}
1817
1818			if (index < 0) {
1819				listeners.push(callback);
1820			}
1821		};
1822
1823		Node.prototype.off = function (eventName, callback) {
1824			var listeners,
1825				index = -1;
1826
1827			if (!eventName || typeof callback !== 'function') {
1828				return;
1829			}
1830
1831			listeners = this.listeners[eventName];
1832			if (listeners) {
1833				index = listeners.indexOf(callback);
1834				if (index >= 0) {
1835					listeners.splice(index, 1);
1836				}
1837			}
1838		};
1839
1840		Node.prototype.emit = function (eventName) {
1841			var i,
1842				listeners = this.listeners[eventName];
1843
1844			if (listeners && listeners.length) {
1845				for (i = 0; i < listeners.length; i++) {
1846					setTimeoutZero(listeners[i]);
1847				}
1848			}
1849		};
1850
1851		Node.prototype.destroy = function () {
1852			var i,
1853				key;
1854
1855			delete this.gl;
1856			delete this.seriously;
1857
1858			//remove all listeners
1859			for (key in this.listeners) {
1860				if (this.listeners.hasOwnProperty(key)) {
1861					delete this.listeners[key];
1862				}
1863			}
1864
1865			//clear out uniforms
1866			for (i in this.uniforms) {
1867				if (this.uniforms.hasOwnProperty(i)) {
1868					delete this.uniforms[i];
1869				}
1870			}
1871
1872			//clear out list of targets and disconnect each
1873			if (this.targets) {
1874				delete this.targets;
1875			}
1876
1877			//clear out frameBuffer
1878			if (this.frameBuffer && this.frameBuffer.destroy) {
1879				this.frameBuffer.destroy();
1880				delete this.frameBuffer;
1881			}
1882
1883			//remove from main nodes index
1884			i = nodes.indexOf(this);
1885			if (i >= 0) {
1886				nodes.splice(i, 1);
1887			}
1888			delete nodesById[this.id];
1889
1890			this.isDestroyed = true;
1891		};
1892
1893		Effect = function (effectNode) {
1894			var name, me = effectNode;
1895
1896			function setInput(inputName, input) {
1897				var lookup, value, effectInput, i;
1898
1899				effectInput = me.effect.inputs[inputName];
1900
1901				lookup = me.inputElements[inputName];
1902
1903				if (typeof input === 'string' && isNaN(input)) {
1904					if (effectInput.type === 'enum') {
1905						if (!effectInput.options.hasOwnProperty(input)) {
1906							input = getElement(input, ['select']);
1907						}
1908					} else if (effectInput.type === 'number' || effectInput.type === 'boolean') {
1909						input = getElement(input, ['input', 'select']);
1910					} else if (effectInput.type === 'image') {
1911						input = getElement(input, ['canvas', 'img', 'video']);
1912					}
1913					//todo: color? date/time?
1914				}
1915
1916				if (isInstance(input, 'HTMLInputElement') || isInstance(input, 'HTMLSelectElement')) {
1917					value = input.value;
1918
1919					if (lookup && lookup.element !== input) {
1920						lookup.element.removeEventListener('change', lookup.listener, true);
1921						lookup.element.removeEventListener('input', lookup.listener, true);
1922						delete me.inputElements[inputName];
1923						lookup = null;
1924					}
1925
1926					if (!lookup) {
1927						lookup = {
1928							element: input,
1929							listener: (function (name, element) {
1930								return function () {
1931									var oldValue, newValue;
1932
1933									if (input.type === 'checkbox') {
1934										//special case for check box
1935										oldValue = input.checked;
1936									} else {
1937										oldValue = element.value;
1938									}
1939									newValue = me.setInput(name, oldValue);
1940
1941									//special case for color type
1942									if (effectInput.type === 'color') {
1943										newValue = colorArrayToHex(newValue).substr(0, 7);
1944									}
1945
1946									//if input validator changes our value, update HTML Element
1947									//todo: make this optional...somehow
1948									if (newValue !== oldValue) {
1949										element.value = newValue;
1950									}
1951								};
1952							}(inputName, input))
1953						};
1954
1955						me.inputElements[inputName] = lookup;
1956						if (input.type === 'range') {
1957							input.addEventListener('input', lookup.listener, true);
1958							input.addEventListener('change', lookup.listener, true);
1959						} else {
1960							input.addEventListener('change', lookup.listener, true);
1961						}
1962					}
1963
1964					if (lookup && input.type === 'checkbox') {
1965						value = input.checked;
1966					}
1967				} else {
1968					if (lookup) {
1969						lookup.element.removeEventListener('change', lookup.listener, true);
1970						lookup.element.removeEventListener('input', lookup.listener, true);
1971						delete me.inputElements[inputName];
1972					}
1973					value = input;
1974				}
1975
1976				me.setInput(inputName, value);
1977				return me.inputs[inputName];
1978			}
1979
1980			function makeImageSetter(inputName) {
1981				return function (value) {
1982					var val = setInput(inputName, value);
1983					return val && val.pub;
1984				};
1985			}
1986
1987			function makeImageGetter(inputName) {
1988				return function () {
1989					var val = me.inputs[inputName];
1990					return val && val.pub;
1991				};
1992			}
1993
1994			function makeSetter(inputName) {
1995				return function (value) {
1996					return setInput(inputName, value);
1997				};
1998			}
1999
2000			function makeGetter(inputName) {
2001				return function () {
2002					return me.inputs[inputName];
2003				};
2004			}
2005
2006			//priveleged publicly accessible methods/setters/getters
2007			//todo: provide alternate set/get methods
2008			for (name in me.effect.inputs) {
2009				if (me.effect.inputs.hasOwnProperty(name)) {
2010					if (this[name] === undefined) {
2011						if (me.effect.inputs[name].type === 'image') {
2012							Object.defineProperty(this, name, {
2013								configurable: true,
2014								enumerable: true,
2015								get: makeImageGetter(name),
2016								set: makeImageSetter(name)
2017							});
2018						} else {
2019							Object.defineProperty(this, name, {
2020								configurable: true,
2021								enumerable: true,
2022								get: makeGetter(name),
2023								set: makeSetter(name)
2024							});
2025						}
2026					} else {
2027						//todo: this is temporary. get rid of it.
2028						throw new Error('Cannot overwrite Seriously.' + name);
2029					}
2030				}
2031			}
2032
2033			Object.defineProperties(this, {
2034				effect: {
2035					enumerable: true,
2036					configurable: true,
2037					get: function () {
2038						return me.hook;
2039					}
2040				},
2041				title: {
2042					enumerable: true,
2043					configurable: true,
2044					get: function () {
2045						return me.effect.title || me.hook;
2046					}
2047				},
2048				width: {
2049					enumerable: true,
2050					configurable: true,
2051					get: function () {
2052						return me.width;
2053					}
2054				},
2055				height: {
2056					enumerable: true,
2057					configurable: true,
2058					get: function () {
2059						return me.height;
2060					}
2061				},
2062				id: {
2063					enumerable: true,
2064					configurable: true,
2065					get: function () {
2066						return me.id;
2067					}
2068				}
2069			});
2070
2071			this.render = function () {
2072				me.render();
2073				return this;
2074			};
2075
2076			this.readPixels = function (x, y, width, height, dest) {
2077				return me.readPixels(x, y, width, height, dest);
2078			};
2079
2080			this.on = function (eventName, callback) {
2081				me.on(eventName, callback);
2082			};
2083
2084			this.off = function (eventName, callback) {
2085				me.off(eventName, callback);
2086			};
2087
2088			this.inputs = function (name) {
2089				var result,
2090					input,
2091					inputs,
2092					i,
2093					key;
2094
2095				inputs = me.effect.inputs;
2096
2097				if (name) {
2098					input = inputs[name];
2099					if (!input) {
2100						return null;
2101					}
2102
2103					result = {
2104						type: input.type,
2105						defaultValue: input.defaultValue,
2106						title: input.title || name
2107					};
2108
2109					if (input.type === 'number') {
2110						result.min = input.min;
2111						result.max = input.max;
2112						result.step = input.step;
2113						result.mod = input.mod;
2114					} else if (input.type === 'enum') {
2115						//make a copy
2116						result.options = extend({}, input.options);
2117					} else if (input.type === 'vector') {
2118						result.dimensions = input.dimensions;
2119					}
2120
2121					if (input.description) {
2122						result.description = input.description;
2123					}
2124
2125					return result;
2126				}
2127
2128				result = {};
2129				for (key in inputs) {
2130					if (inputs.hasOwnProperty(key)) {
2131						result[key] = this.inputs(key);
2132					}
2133				}
2134				return result;
2135			};
2136
2137			this.alias = function (inputName, aliasName) {
2138				me.alias(inputName, aliasName);
2139				return this;
2140			};
2141
2142			this.matte = function (polygons) {
2143				me.matte(polygons);
2144			};
2145
2146			this.destroy = function () {
2147				var i,
2148					descriptor;
2149
2150				me.destroy();
2151
2152				for (i in this) {
2153					if (this.hasOwnProperty(i) && i !== 'isDestroyed' && i !== 'id') {
2154						descriptor = Object.getOwnPropertyDescriptor(this, i);
2155						if (descriptor.get || descriptor.set ||
2156								typeof this[i] !== 'function') {
2157							delete this[i];
2158						} else {
2159							this[i] = nop;
2160						}
2161					}
2162				}
2163			};
2164
2165			this.isDestroyed = function () {
2166				return me.isDestroyed;
2167			};
2168
2169			this.isReady = function () {
2170				return me.ready;
2171			};
2172		};
2173
2174		EffectNode = function (hook, options) {
2175			var key, name, input,
2176				defaultValue,
2177				defaults,
2178				defaultSources = {};
2179
2180			Node.call(this, options);
2181			this.gl = gl;
2182
2183			this.effectRef = seriousEffects[hook];
2184			this.sources = {};
2185			this.targets = [];
2186			this.inputElements = {};
2187			this.dirty = true;
2188			this.shaderDirty = true;
2189			this.hook = hook;
2190			this.options = options;
2191			this.transform = null;
2192
2193			this.effect = extend({}, this.effectRef);
2194			if (this.effectRef.definition) {
2195				/*
2196				todo: copy over inputs object separately in case some are specified
2197				in advance and some are specified in definition function
2198				*/
2199				extend(this.effect, this.effectRef.definition.call(this, options));
2200			}
2201			validateInputSpecs(this.effect);
2202
2203			this.uniforms.transform = identity;
2204			this.inputs = {};
2205			defaults = defaultInputs[hook];
2206			for (name in this.effect.inputs) {
2207				if (this.effect.inputs.hasOwnProperty(name)) {
2208					input = this.effect.inputs[name];
2209
2210					if (input.defaultValue === undefined || input.defaultValue === null) {
2211						if (input.type === 'number') {
2212							input.defaultValue = Math.min(Math.max(0, input.min), input.max);
2213						} else if (input.type === 'color') {
2214							input.defaultValue = [0, 0, 0, 0];
2215						} else if (input.type === 'boolean') {
2216							input.defaultValue = false;
2217						} else if (input.type === 'string') {
2218							input.defaultValue = '';
2219						} else if (input.type === 'enum') {
2220							input.defaultValue = input.firstValue;
2221						}
2222					}
2223
2224					defaultValue = input.validate.call(this, input.defaultValue, input);
2225					if (defaults && defaults[name] !== undefined) {
2226						defaultValue = input.validate.call(this, defaults[name], input, input.defaultValue, defaultValue);
2227						defaults[name] = defaultValue;
2228						if (input.type === 'image') {
2229							defaultSources[name] = defaultValue;
2230						}
2231					}
2232
2233					this.inputs[name] = defaultValue;
2234					if (input.uniform) {
2235						this.uniforms[input.uniform] = input.defaultValue;
2236					}
2237				}
2238			}
2239
2240			if (gl) {
2241				this.initialize();
2242				if (this.effect.commonShader) {
2243					/*
2244					this effect is unlikely to need to be modified again
2245					by changing parameters, so build it now to avoid jank later
2246					*/
2247					this.buildShader();
2248				}
2249			}
2250
2251			this.updateReady();
2252			this.inPlace = this.effect.inPlace;
2253
2254			this.pub = new Effect(this);
2255
2256			nodes.push(this);
2257			nodesById[this.id] = this;
2258			effects.push(this);
2259
2260			allEffectsByHook[hook].push(this);
2261
2262			for (name in defaultSources) {
2263				if (defaultSources.hasOwnProperty(name)) {
2264					this.setInput(name, defaultSources[name]);
2265				}
2266			}
2267		};
2268
2269		EffectNode.prototype = Object.create(Node.prototype);
2270		EffectNode.prototype.constructor = EffectNode;
2271
2272		EffectNode.prototype.initialize = function () {
2273			if (!this.initialized) {
2274				var that = this;
2275
2276				this.baseShader = baseShader;
2277
2278				if (this.shape) {
2279					this.model = makeGlModel(this.shape, this.gl);
2280				} else {
2281					this.model = rectangleModel;
2282				}
2283
2284				if (typeof this.effect.initialize === 'function') {
2285					this.effect.initialize.call(this, function () {
2286						that.initFrameBuffer(true);
2287					}, gl);
2288				} else {
2289					this.initFrameBuffer(true);
2290				}
2291
2292				if (this.frameBuffer) {
2293					this.texture = this.frameBuffer.texture;
2294				}
2295
2296				this.initialized = true;
2297			}
2298		};
2299
2300		EffectNode.prototype.resize = function () {
2301			var i;
2302
2303			Node.prototype.resize.call(this);
2304
2305			if (this.effect.resize) {
2306				this.effect.resize.call(this);
2307			}
2308
2309			for (i = 0; i < this.targets.length; i++) {
2310				this.targets[i].resize();
2311			}
2312		};
2313
2314		EffectNode.prototype.updateReady = function () {
2315			var i,
2316				input,
2317				key,
2318				effect,
2319				ready = true,
2320				method;
2321
2322			effect = this.effect;
2323			for (key in effect.inputs) {
2324				if (effect.inputs.hasOwnProperty(key)) {
2325					input = this.effect.inputs[key];
2326					if (input.type === 'image' &&
2327							(!this.sources[key] || !this.sources[key].ready) &&
2328							(!effect.requires || effect.requires.call(this, key, this.inputs))
2329							) {
2330						ready = false;
2331						break;
2332					}
2333				}
2334			}
2335
2336			if (this.ready !== ready) {
2337				this.ready = ready;
2338				this.emit(ready ? 'ready' : 'unready');
2339				method = ready ? 'setReady' : 'setUnready';
2340
2341				if (this.targets) {
2342					for (i = 0; i < this.targets.length; i++) {
2343						this.targets[i][method]();
2344					}
2345				}
2346			}
2347		};
2348
2349		EffectNode.prototype.setReady = EffectNode.prototype.updateReady;
2350
2351		EffectNode.prototype.setUnready = EffectNode.prototype.updateReady;
2352
2353		EffectNode.prototype.addTarget = function (target) {
2354			var i;
2355			for (i = 0; i < this.targets.length; i++) {
2356				if (this.targets[i] === target) {
2357					return;
2358				}
2359			}
2360
2361			this.targets.push(target);
2362		};
2363
2364		EffectNode.prototype.removeTarget = function (target) {
2365			var i = this.targets && this.targets.indexOf(target);
2366			if (i >= 0) {
2367				this.targets.splice(i, 1);
2368			}
2369		};
2370
2371		EffectNode.prototype.removeSource = function (source) {
2372			var i, pub = source && source.pub;
2373
2374			for (i in this.inputs) {
2375				if (this.inputs.hasOwnProperty(i) &&
2376					(this.inputs[i] === source || this.inputs[i] === pub)) {
2377					this.inputs[i] = null;
2378				}
2379			}
2380
2381			for (i in this.sources) {
2382				if (this.sources.hasOwnProperty(i) &&
2383					(this.sources[i] === source || this.sources[i] === pub)) {
2384					this.sources[i] = null;
2385				}
2386			}
2387		};
2388
2389		EffectNode.prototype.buildShader = function () {
2390			var shader,
2391				effect = this.effect,
2392				me = this;
2393
2394			function addShaderName(shaderSrc) {
2395				if (shaderNameRegex.test(shaderSrc)) {
2396					return shaderSrc;
2397				}
2398
2399				return '#define SHADER_NAME seriously.' + me.hook + '\n' +
2400					shaderSrc;
2401			}
2402
2403			if (this.shaderDirty) {
2404				if (effect.commonShader && commonShaders[this.hook]) {
2405					if (!this.shader) {
2406						commonShaders[this.hook].count++;
2407					}
2408					this.shader = commonShaders[this.hook].shader;
2409				} else if (effect.shader) {
2410					if (this.shader && !effect.commonShader) {
2411						this.shader.destroy();
2412					}
2413					shader = effect.shader.call(this, this.inputs, {
2414						vertex: baseVertexShader,
2415						fragment: baseFragmentShader
2416					}, Seriously.util);
2417
2418					if (shader instanceof ShaderProgram) {
2419						this.shader = shader;
2420					} else if (shader && shader.vertex && shader.fragment) {
2421						this.shader = new ShaderProgram(
2422							gl,
2423							addShaderName(shader.vertex),
2424							addShaderName(shader.fragment)
2425						);
2426					} else {
2427						this.shader = baseShader;
2428					}
2429
2430					if (effect.commonShader) {
2431						commonShaders[this.hook] = {
2432							count: 1,
2433							shader: this.shader
2434						};
2435					}
2436				} else {
2437					this.shader = baseShader;
2438				}
2439
2440				this.shaderDirty = false;
2441			}
2442		};
2443
2444		EffectNode.prototype.render = function () {
2445			var key,
2446				frameBuffer,
2447				effect = this.effect,
2448				that = this,
2449				inPlace;
2450
2451			function drawFn(shader, model, uniforms, frameBuffer, node, options) {
2452				draw(shader, model, uniforms, frameBuffer, node || that, options);
2453			}
2454
2455			if (!gl) {
2456				return;
2457			}
2458
2459			if (!this.initialized) {
2460				this.initialize();
2461			}
2462
2463			if (this.shaderDirty) {
2464				this.buildShader();
2465			}
2466
2467			if (this.dirty && this.ready) {
2468				for (key in this.sources) {
2469					if (this.sources.hasOwnProperty(key) &&
2470						(!effect.requires || effect.requires.call(this, key, this.inputs))) {
2471
2472						//todo: set source texture in case it changes?
2473						//sourcetexture = this.sources[i].render() || this.sources[i].texture
2474
2475						inPlace = typeof this.inPlace === 'function' ? this.inPlace(key) : this.inPlace;
2476						this.sources[key].render(!inPlace);
2477					}
2478				}
2479
2480				if (this.frameBuffer) {
2481					frameBuffer = this.frameBuffer.frameBuffer;
2482				}
2483
2484				if (typeof effect.draw === 'function') {
2485					effect.draw.call(this, this.shader, this.model, this.uniforms, frameBuffer, drawFn);
2486					this.emit('render');
2487				} else if (frameBuffer) {
2488					draw(this.shader, this.model, this.uniforms, frameBuffer, this);
2489					this.emit('render');
2490				}
2491
2492				this.dirty = false;
2493			}
2494
2495			return this.texture;
2496		};
2497
2498		EffectNode.prototype.setInput = function (name, value) {
2499			var input, uniform,
2500				sourceKeys,
2501				source,
2502				me = this,
2503				defaultValue;
2504
2505			function disconnectSource() {
2506				var previousSource = me.sources[name],
2507					key;
2508
2509				/*
2510				remove this node from targets of previously connected source node,
2511				but only if the source node is not being used as another input
2512				*/
2513				if (previousSource) {
2514					for (key in me.sources) {
2515						if (key !== name &&
2516								me.sources.hasOwnProperty(key) &&
2517								me.sources[key] === previousSource) {
2518							return;
2519						}
2520					}
2521					previousSource.removeTarget(me);
2522				}
2523			}
2524
2525			if (this.effect.inputs.hasOwnProperty(name)) {
2526				input = this.effect.inputs[name];
2527				if (input.type === 'image') {
2528					//&& !(value instanceof Effect) && !(value instanceof Source)) {
2529
2530					if (value) {
2531						value = findInputNode(value);
2532
2533						if (value !== this.sources[name]) {
2534							disconnectSource();
2535
2536							if (traceSources(value, this)) {
2537								throw new Error('Attempt to make cyclical connection.');
2538							}
2539
2540							this.sources[name] = value;
2541							value.addTarget(this);
2542						}
2543					} else {
2544						delete this.sources[name];
2545						value = false;
2546					}
2547
2548					uniform = this.sources[name];
2549
2550					sourceKeys = Object.keys(this.sources);
2551					if (this.inPlace === true && sourceKeys.length === 1) {
2552						source = this.sources[sourceKeys[0]];
2553						this.uniforms.transform = source && source.cumulativeMatrix || identity;
2554					} else {
2555						this.uniforms.transform = identity;
2556					}
2557				} else {
2558					if (defaultInputs[this.hook] && defaultInputs[this.hook][name] !== undefined) {
2559						defaultValue = defaultInputs[this.hook][name];
2560					} else {
2561						defaultValue = input.defaultValue;
2562					}
2563					value = input.validate.call(this, value, input, defaultValue, this.inputs[name]);
2564					uniform = value;
2565				}
2566
2567				if (this.inputs[name] === value && input.type !== 'color' && input.type !== 'vector') {
2568					return value;
2569				}
2570
2571				this.inputs[name] = value;
2572
2573				if (input.uniform) {
2574					this.uniforms[input.uniform] = uniform;
2575				}
2576
2577				if (input.type === 'image') {
2578					this.resize();
2579					this.updateReady();
2580				} else if (input.updateSources) {
2581					this.updateReady();
2582				}
2583
2584				if (input.shaderDirty) {
2585					this.shaderDirty = true;
2586				}
2587
2588				this.setDirty();
2589
2590				if (input.update) {
2591					input.update.call(this, value);
2592				}
2593
2594				return value;
2595			}
2596		};
2597
2598		EffectNode.prototype.alias = function (inputName, aliasName) {
2599			var that = this;
2600
2601			if (reservedNames.indexOf(aliasName) >= 0) {
2602				throw new Error('\'' + aliasName + '\' is a reserved name and cannot be used as an alias.');
2603			}
2604
2605			if (this.effect.inputs.hasOwnProperty(inputName)) {
2606				if (!aliasName) {
2607					aliasName = inputName;
2608				}
2609
2610				seriously.removeAlias(aliasName);
2611
2612				aliases[aliasName] = {
2613					node: this,
2614					input: inputName
2615				};
2616
2617				Object.defineProperty(seriously, aliasName, {
2618					configurable: true,
2619					enumerable: true,
2620					get: function () {
2621						return that.inputs[inputName];
2622					},
2623					set: function (value) {
2624						return that.setInput(inputName, value);
2625					}
2626				});
2627			}
2628
2629			return this;
2630		};
2631
2632		/*
2633		matte function to be assigned as a method to EffectNode and TargetNode
2634		*/
2635		EffectNode.prototype.matte = function (poly) {
2636			var polys,
2637				polygons = [],
2638				polygon,
2639				vertices = [],
2640				i, j, v,
2641				vert, prev,
2642				//triangles = [],
2643				shape = {};
2644
2645			//detect whether it's multiple polygons or what
2646			function makePolygonsArray(poly) {
2647				if (!poly || !poly.length || !Array.isArray(poly)) {
2648					return [];
2649				}
2650
2651				if (!Array.isArray(poly[0])) {
2652					return [poly];
2653				}
2654
2655				if (Array.isArray(poly[0]) && !isNaN(poly[0][0])) {
2656					return [poly];
2657				}
2658
2659				return poly;
2660			}
2661
2662			function linesIntersect(a1, a2, b1, b2) {
2663				var ua_t, ub_t, u_b, ua, ub;
2664				ua_t = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x);
2665				ub_t = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x);
2666				u_b = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y);
2667				if (u_b) {
2668					ua = ua_t / u_b;
2669					ub = ub_t / u_b;
2670					if (ua > 0 && ua <= 1 && ub > 0 && ub <= 1) {
2671						return {
2672							x: a1.x + ua * (a2.x - a1.x),
2673							y: a1.y + ua * (a2.y - a1.y)
2674						};
2675					}
2676				}
2677				return false;
2678			}
2679
2680			function makeSimple(poly) {
2681				/*
2682				this uses a slow, naive approach to detecting line intersections.
2683				Use Bentley-Ottmann Algorithm
2684				see: http://softsurfer.com/Archive/algorithm_0108/algorithm_0108.htm#Bentley-Ottmann Algorithm
2685				see: https://github.com/tokumine/sweepline
2686				*/
2687				var i, j,
2688					edge1, edge2,
2689					intersect,
2690					intersections = [],
2691					newPoly,
2692					head, point,
2693					newPolygons,
2694					point1, point2;
2695
2696				if (poly.simple) {
2697					return;
2698				}
2699
2700				for (i = 0; i < poly.edges.length; i++) {
2701					edge1 = poly.edges[i];
2702					for (j = i + 1; j < poly.edges.length; j++) {
2703						edge2 = poly.edges[j];
2704						intersect = linesIntersect(edge1[0], edge1[1], edge2[0], edge2[1]);
2705						if (intersect) {
2706							intersect.edge1 = edge1;
2707							intersect.edge2 = edge2;
2708							intersections.push(intersect);
2709						}
2710					}
2711				}
2712
2713				if (intersections.length) {
2714					newPolygons = [];
2715
2716					for (i = 0; i < intersections.length; i++) {
2717						intersect = intersections[i];
2718						edge1 = intersect.edge1;
2719						edge2 = intersect.edge2;
2720
2721						//make new points
2722						//todo: set ids for points
2723						point1 = {
2724							x: intersect.x,
2725							y: intersect.y,
2726							prev: edge1[0],
2727							next: edge2[1],
2728							id: vertices.length
2729						};
2730						poly.vertices.push(point1);
2731						vertices.push(point1);
2732
2733						point2 = {
2734							x: intersect.x,
2735							y: intersect.y,
2736							prev: edge2[0],
2737							next: edge1[1],
2738							id: vertices.length
2739						};
2740						poly.vertices.push(point2);
2741						vertices.push(point1);
2742
2743						//modify old points
2744						point1.prev.next = point1;
2745						point1.next.prev = point1;
2746						point2.prev.next = point2;
2747						point2.next.prev = point2;
2748
2749						//don't bother modifying the old edges. we're just gonna throw them out
2750					}
2751
2752					//make new polygons
2753					do {
2754						newPoly = {
2755							edges: [],
2756							vertices: [],
2757							simple: true
2758						};
2759						newPolygons.push(newPoly);
2760						point = poly.vertices[0];
2761						head = point;
2762						//while (point.next !== head && poly.vertices.length) {
2763						do {
2764							i = poly.vertices.indexOf(point);
2765							poly.vertices.splice(i, 1);
2766							newPoly.edges.push([point, point.next]);
2767							newPoly.vertices.push(point);
2768							point = point.next;
2769						} while (point !== head);
2770					} while (poly.vertices.length);
2771
2772					//remove original polygon from list
2773					i = polygons.indexOf(poly);
2774					polygons.splice(i, 1);
2775
2776					//add new polygons to list
2777					for (i = 0; i < newPolygons.length; i++) {
2778						polygons.push(newPolygons[i]);
2779					}
2780				} else {
2781					poly.simple = true;
2782				}
2783			}
2784
2785			function clockWise(poly) {
2786				var p, q, n = poly.vertices.length,
2787					pv, qv, sum = 0;
2788				for (p = n - 1, q = 0; q < n; p = q, q++) {
2789					pv = poly.vertices[p];
2790					qv = poly.vertices[q];
2791					//sum += (next.x - v.x) * (next.y + v.y);
2792					//sum += (v.next.x + v.x) * (v.next.y - v.y);
2793					sum += pv.x * qv.y - qv.x * pv.y;
2794				}
2795				return sum > 0;
2796			}
2797
2798			function triangulate(poly) {
2799				var v, points = poly.vertices,
2800					n, V = [], indices = [],
2801					nv, count, m, u, w,
2802
2803					//todo: give these variables much better names
2804					a, b, c, s, t;
2805
2806				function pointInTriangle(a, b, c, p) {
2807					var ax, ay, bx, by, cx, cy, apx, apy, bpx, bpy, cpx, cpy,
2808						cXap, bXcp, aXbp;
2809
2810					ax = c.x - b.x;
2811					ay = c.y - b.y;
2812					bx = a.x - c.x;
2813					by = a.y - c.y;
2814					cx = b.x - a.x;
2815					cy = b.y - a.y;
2816					apx = p.x - a.x;
2817					apy = p.y - a.y;
2818					bpx = p.x - b.x;
2819					bpy = p.y - b.y;
2820					cpx = p.x - c.x;
2821					cpy = p.y - c.y;
2822
2823					aXbp = ax * bpy - ay * bpx;
2824					cXap = cx * apy - cy * apx;
2825					bXcp = bx * cpy - by * cpx;
2826
2827					return aXbp >= 0 && bXcp >=0 && cXap >=0;
2828				}
2829
2830				function snip(u, v, w, n, V) {
2831					var p, a, b, c, point;
2832					a = points[V[u]];
2833					b = points[V[v]];
2834					c = points[V[w]];
2835					if (0 > (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x)) {
2836						return false;
2837					}
2838					for (p = 0; p < n; p++) {
2839						if (!(p === u || p === v || p === w)) {
2840							point = points[V[p]];
2841							if (pointInTriangle(a, b, c, point)) {
2842								return false;
2843							}
2844						}
2845					}
2846					return true;
2847				}
2848
2849				//copy points
2850				//for (v = 0; v < poly.vertices.length; v++) {
2851				//	points.push(poly.vertices[v]);
2852				//}
2853				n = points.length;
2854
2855				if (poly.clockWise) {
2856					for (v = 0; v < n; v++) {
2857						V[v] = v;
2858					}
2859				} else {
2860					for (v = 0; v < n; v++) {
2861						V[v] = (n - 1) - v;
2862					}
2863				}
2864
2865				nv = n;
2866				count = 2 * nv;
2867				m = 0;
2868				v = nv - 1;
2869				while (nv > 2) {
2870					if ((count--) <= 0) {
2871						return indices;
2872					}
2873
2874					u = v;
2875					if (nv <= u) {
2876						u = 0;
2877					}
2878
2879					v = u + 1;
2880					if (nv <= v) {
2881						v = 0;
2882					}
2883
2884					w = v + 1;
2885					if (nv < w) {
2886						w = 0;
2887					}
2888
2889					if (snip(u, v, w, nv, V)) {
2890						a = V[u];
2891						b = V[v];
2892						c = V[w];
2893						if (poly.clockWise) {
2894							indices.push(points[a]);
2895							indices.push(points[b]);
2896							indices.push(points[c]);
2897						} else {
2898							indices.push(points[c]);
2899							indices.push(points[b]);
2900							indices.push(points[a]);
2901						}
2902						m++;
2903						for (s = v, t = v + 1; t < nv; s++, t++) {
2904							V[s] = V[t];
2905						}
2906						nv--;
2907						count = 2 * nv;
2908					}
2909				}
2910
2911				polygon.indices = indices;
2912			}
2913
2914			polys = makePolygonsArray(poly);
2915
2916			for (i = 0; i < polys.length; i++) {
2917				poly = polys[i];
2918				prev = null;
2919				polygon = {
2920					vertices: [],
2921					edges: []
2922				};
2923
2924				for (j = 0; j < poly.length; j++) {
2925					v = poly[j];
2926					if (typeof v ==='object' && !isNaN(v.x) && !isNaN(v.y)) {
2927						vert = {
2928							x: v.x,
2929							y: v.y,
2930							id: vertices.length
2931						};
2932					} else if (v.length >= 2 && !isNaN(v[0]) && !isNaN(v[1])) {
2933						vert = {
2934							x: v[0],
2935							y: v[1],
2936							id: vertices.length
2937						};
2938					}
2939					if (vert) {
2940						if (prev) {
2941							prev.next = vert;
2942							vert.prev = prev;
2943							vert.next = polygon.vertices[0];
2944							polygon.vertices[0].prev = vert;
2945						} else {
2946							polygon.head = vert;
2947							vert.next = vert;
2948							vert.prev = vert;
2949						}
2950						vertices.push(vert);
2951						polygon.vertices.push(vert);
2952						prev = vert;
2953					}
2954				}
2955
2956				if (polygon.vertices.length > 2) {
2957					if (polygon.vertices.length === 3) {
2958						polygon.simple = true;
2959					}
2960
2961					polygons.push(polygon);
2962
2963					//save edges
2964					for (j = 0; j < polygon.vertices.length; j++) {
2965						vert = polygon.vertices[j];
2966						polygon.edges.push([
2967							vert, vert.next
2968						]);
2969					}
2970				}
2971			}
2972
2973			for (i = polygons.length - 1; i >= 0; i--) {
2974				polygon = polygons[i];
2975				makeSimple(polygon);
2976			}
2977
2978			for (i = 0; i < polygons.length; i++) {
2979				polygon = polygons[i];
2980				polygon.clockWise = clockWise(polygon);
2981				triangulate(polygon);
2982			}
2983
2984			//build shape
2985			shape.vertices = [];
2986			shape.coords = [];
2987			for (i = 0; i < vertices.length; i++) {
2988				v = vertices[i];
2989				shape.vertices.push(v.x * 2 - 1);
2990				shape.vertices.push(v.y * -2 + 1);
2991				shape.vertices.push(-1);
2992
2993				shape.coords.push(v.x);
2994				shape.coords.push(v.y * -1 + 1);
2995			}
2996			shape.vertices = new Float32Array(shape.vertices);
2997			shape.coords = new Float32Array(shape.coords);
2998
2999			shape.indices = [];
3000			for (i = 0; i < polygons.length; i++) {
3001				polygon = polygons[i];
3002				for (j = 0; j < polygon.indices.length; j++) {
3003					v = polygon.indices[j];
3004					shape.indices.push(v.id);
3005					//shape.indices.push(v[1].id);
3006					//shape.indices.push(v[2].id);
3007				}
3008			}
3009			shape.indices = new Uint16Array(shape.indices);
3010
3011			this.shape = shape;
3012			if (this.gl) {
3013				makeGlModel(shape, this.gl);
3014			}
3015		};
3016
3017		EffectNode.prototype.destroy = function () {
3018			var i, key, item, hook = this.hook;
3019
3020			//let effect destroy itself
3021			if (this.effect.destroy && typeof this.effect.destroy === 'function') {
3022				this.effect.destroy.call(this);
3023			}
3024			delete this.effect;
3025
3026			//shader
3027			if (commonShaders[hook]) {
3028				commonShaders[hook].count--;
3029				if (!commonShaders[hook].count) {
3030					delete commonShaders[hook];
3031				}
3032			}
3033			if (this.shader && this.shader.destroy && this.shader !== baseShader && !commonShaders[hook]) {
3034				this.shader.destroy();
3035			}
3036			delete this.shader;
3037
3038			//stop watching any input elements
3039			for (key in this.inputElements) {
3040				if (this.inputElements.hasOwnProperty(key)) {
3041					item = this.inputElements[key];
3042					item.element.removeEventListener('change', item.listener, true);
3043					item.element.removeEventListener('input', item.listener, true);
3044				}
3045			}
3046
3047			//sources
3048			for (key in this.sources) {
3049				if (this.sources.hasOwnProperty(key)) {
3050					item = this.sources[key];
3051					if (item && item.removeTarget) {
3052						item.removeTarget(this);
3053					}
3054					delete this.sources[key];
3055				}
3056			}
3057
3058			//targets
3059			while (this.targets.length) {
3060				item = this.targets.pop();
3061				if (item && item.removeSource) {
3062					item.removeSource(this);
3063				}
3064			}
3065
3066			for (key in this) {
3067				if (this.hasOwnProperty(key) && key !== 'id') {
3068					delete this[key];
3069				}
3070			}
3071
3072			//remove any aliases
3073			for (key in aliases) {
3074				if (aliases.hasOwnProperty(key)) {
3075					item = aliases[key];
3076					if (item.node === this) {
3077						seriously.removeAlias(key);
3078					}
3079				}
3080			}
3081
3082			//remove self from master list of effects
3083			i = effects.indexOf(this);
3084			if (i >= 0) {
3085				effects.splice(i, 1);
3086			}
3087
3088			i = allEffectsByHook[hook].indexOf(this);
3089			if (i >= 0) {
3090				allEffectsByHook[hook].splice(i, 1);
3091			}
3092
3093			Node.prototype.destroy.call(this);
3094		};
3095
3096		Source = function (sourceNode) {
3097			var me = sourceNode;
3098
3099			//priveleged accessor methods
3100			Object.defineProperties(this, {
3101				original: {
3102					enumerable: true,
3103					configurable: true,
3104					get: function () {
3105						return me.source;
3106					}
3107				},
3108				id: {
3109					enumerable: true,
3110					configurable: true,
3111					get: function () {
3112						return me.id;
3113					}
3114				},
3115				width: {
3116					enumerable: true,
3117					configurable: true,
3118					get: function () {
3119						return me.width;
3120					}
3121				},
3122				height: {
3123					enumerable: true,
3124					configurable: true,
3125					get: function () {
3126						return me.height;
3127					}
3128				}
3129			});
3130
3131			this.render = function () {
3132				me.render();
3133			};
3134
3135			this.update = function () {
3136				me.setDirty();
3137			};
3138
3139			this.readPixels = function (x, y, width, height, dest) {
3140				return me.readPixels(x, y, width, height, dest);
3141			};
3142
3143			this.on = function (eventName, callback) {
3144				me.on(eventName, callback);
3145			};
3146
3147			this.off = function (eventName, callback) {
3148				me.off(eventName, callback);
3149			};
3150
3151			this.destroy = function () {
3152				var i,
3153					descriptor;
3154
3155				me.destroy();
3156
3157				for (i in this) {
3158					if (this.hasOwnProperty(i) && i !== 'isDestroyed' && i !== 'id') {
3159						descriptor = Object.getOwnPropertyDescriptor(this, i);
3160						if (descriptor.get || descriptor.set ||
3161								typeof this[i] !== 'function') {
3162							delete this[i];
3163						} else {
3164							this[i] = nop;
3165						}
3166					}
3167				}
3168			};
3169
3170			this.isDestroyed = function () {
3171				return me.isDestroyed;
3172			};
3173
3174			this.isReady = function () {
3175				return me.ready;
3176			};
3177		};
3178
3179		/*
3180			possible sources: img, video, canvas (2d or 3d), texture, ImageData, array, typed array
3181		*/
3182		SourceNode = function (hook, source, options) {
3183			var opts = options || {},
3184				flip = opts.flip === undefined ? true : opts.flip,
3185				width = opts.width,
3186				height = opts.height,
3187				deferTexture = false,
3188				that = this,
3189				matchedType = false,
3190				key,
3191				plugin;
3192
3193			function sourcePlugin(hook, source, options, force) {
3194				var p = seriousSources[hook];
3195				if (p.definition) {
3196					p = p.definition.call(that, source, options, force);
3197					if (p) {
3198						p = extend(extend({}, seriousSources[hook]), p);
3199					} else {
3200						return null;
3201					}
3202				}
3203				return p;
3204			}
3205
3206			function compareSource(source) {
3207				return that.source === source;
3208			}
3209
3210			Node.call(this);
3211
3212			if (hook && typeof hook !== 'string' || !source && source !== 0) {
3213				if (!options || typeof options !== 'object') {
3214					options = source;
3215				}
3216				source = hook;
3217			}
3218
3219			if (typeof source === 'string' && isNaN(source)) {
3220				source = getElement(source, ['canvas', 'img', 'video']);
3221			}
3222
3223			// forced source type?
3224			if (typeof hook === 'string' && seriousSources[hook]) {
3225				plugin = sourcePlugin(hook, source, options, true);
3226				if (plugin) {
3227					this.hook = hook;
3228					matchedType = true;
3229					deferTexture = plugin.deferTexture;
3230					this.plugin = plugin;
3231					this.compare = plugin.compare;
3232					this.checkDirty = plugin.checkDirty;
3233					if (plugin.source) {
3234						source = plugin.source;
3235					}
3236				}
3237			}
3238
3239			//todo: could probably stand to re-work and re-indent this whole block now that we have plugins
3240			if (!plugin && isInstance(source)) {
3241				if (source.tagName === 'CANVAS') {
3242					this.width = source.width;
3243					this.height = source.height;
3244
3245					this.render = this.renderImageCanvas;
3246					matchedType = true;
3247					this.hook = 'canvas';
3248					this.compare = compareSource;
3249				} else if (source.tagName === 'IMG') {
3250					this.width = source.naturalWidth || 1;
3251					this.height = source.naturalHeight || 1;
3252
3253					if (!source.complete || !source.naturalWidth) {
3254						deferTexture = true;
3255					}
3256
3257					source.addEventListener('load', function () {
3258						if (!that.isDestroyed) {
3259							if (that.width !== source.naturalWidth || that.height !== source.naturalHeight) {
3260								that.width = source.naturalWidth;
3261								that.height = source.naturalHeight;
3262								that.resize();
3263							}
3264
3265							that.setDirty();
3266							that.setReady();
3267						}
3268					}, true);
3269
3270					this.render = this.renderImageCanvas;
3271					matchedType = true;
3272					this.hook = 'image';
3273					this.compare = compareSource;
3274				}
3275			} else if (!plugin && isInstance(source, 'WebGLTexture')) {
3276				if (gl && !gl.isTexture(source)) {
3277					throw new Error('Not a valid WebGL texture.');
3278				}
3279
3280				//different defaults
3281				if (!isNaN(width)) {
3282					if (isNaN(height)) {
3283						height = width;
3284					}
3285				} else if (!isNaN(height)) {
3286					width = height;
3287				}/* else {
3288					//todo: guess based on dimensions of target canvas
3289					//throw new Error('Must specify width and height when using a WebGL texture as a source');
3290				}*/
3291
3292				this.width = width;
3293				this.height = height;
3294
3295				if (opts.flip === undefined) {
3296					flip = false;
3297				}
3298				matchedType = true;
3299
3300				this.texture = source;
3301				this.initialized = true;
3302				this.hook = 'texture';
3303				this.compare = compareSource;
3304
3305				//todo: if WebGLTexture source is from a different context render it and copy it over
3306				this.render = function () {};
3307			}
3308
3309			if (!matchedType && !plugin) {
3310				for (key in seriousSources) {
3311					if (seriousSources.hasOwnProperty(key) && seriousSources[key]) {
3312						plugin = sourcePlugin(key, source, options, false);
3313						if (plugin) {
3314							this.hook = key;
3315							matchedType = true;
3316							deferTexture = plugin.deferTexture;
3317							this.plugin = plugin;
3318							this.compare = plugin.compare;
3319							this.checkDirty = plugin.checkDirty;
3320							if (plugin.source) {
3321								source = plugin.source;
3322							}
3323
3324							break;
3325						}
3326					}
3327				}
3328			}
3329
3330			if (!matchedType) {
3331				throw new Error('Unknown source type');
3332			}
3333
3334			this.source = source;
3335			if (this.flip === undefined) {
3336				this.flip = flip;
3337			}
3338
3339			this.targets = [];
3340
3341			if (!deferTexture) {
3342				that.setReady();
3343			}
3344
3345			this.pub = new Source(this);
3346
3347			nodes.push(this);
3348			nodesById[this.id] = this;
3349			sources.push(this);
3350			allSourcesByHook[this.hook].push(this);
3351
3352			if (sources.length && !rafId) {
3353				renderDaemon();
3354			}
3355		};
3356
3357		SourceNode.prototype = Object.create(Node.prototype);
3358		SourceNode.prototype.constructor = SourceNode;
3359
3360		SourceNode.prototype.initialize = function () {
3361			var texture;
3362
3363			if (!gl || this.texture || !this.ready) {
3364				return;
3365			}
3366
3367			texture = gl.createTexture();
3368			gl.bindTexture(gl.TEXTURE_2D, texture);
3369			gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
3370			gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
3371			gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
3372			gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
3373			gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
3374			gl.bindTexture(gl.TEXTURE_2D, null);
3375
3376			this.texture = texture;
3377			this.initialized = true;
3378			this.allowRefresh = true;
3379			this.setDirty();
3380		};
3381
3382		SourceNode.prototype.initFrameBuffer = function (useFloat) {
3383			if (gl) {
3384				this.frameBuffer = new FrameBuffer(gl, this.width, this.height, {
3385					texture: this.texture,
3386					useFloat: useFloat
3387				});
3388			}
3389		};
3390
3391		SourceNode.prototype.addTarget = function (target) {
3392			var i;
3393			for (i = 0; i < this.targets.length; i++) {
3394				if (this.targets[i] === target) {
3395					return;
3396				}
3397			}
3398
3399			this.targets.push(target);
3400		};
3401
3402		SourceNode.prototype.removeTarget = function (target) {
3403			var i = this.targets && this.targets.indexOf(target);
3404			if (i >= 0) {
3405				this.targets.splice(i, 1);
3406			}
3407		};
3408
3409		SourceNode.prototype.resize = function () {
3410			var i,
3411				target;
3412
3413			this.uniforms.resolution[0] = this.width;
3414			this.uniforms.resolution[1] = this.height;
3415
3416			if (this.framebuffer) {
3417				this.framebuffer.resize(this.width, this.height);
3418			}
3419
3420			this.emit('resize');
3421			this.setDirty();
3422
3423			if (this.targets) {
3424				for (i = 0; i < this.targets.length; i++) {
3425					target = this.targets[i];
3426					target.resize();
3427					if (target.setTransformDirty) {
3428						target.setTransformDirty();
3429					}
3430				}
3431			}
3432		};
3433
3434		SourceNode.prototype.setReady = function () {
3435			var i;
3436			if (!this.ready) {
3437				this.ready = true;
3438				this.resize();
3439				this.initialize();
3440
3441				this.emit('ready');
3442				if (this.targets) {
3443					for (i = 0; i < this.targets.length; i++) {
3444						this.targets[i].setReady();
3445					}
3446				}
3447
3448			}
3449		};
3450
3451		SourceNode.prototype.render = function () {
3452			var media = this.source;
3453
3454			if (!gl || !media && media !== 0 || !this.ready) {
3455				return;
3456			}
3457
3458			if (!this.initialized) {
3459				this.initialize();
3460			}
3461
3462			if (!this.allowRefresh) {
3463				return;
3464			}
3465
3466			if (this.plugin && this.plugin.render &&
3467					(this.dirty || this.checkDirty && this.checkDirty()) &&
3468					this.plugin.render.call(this, gl, draw, rectangleModel, baseShader)) {
3469
3470				this.dirty = false;
3471				this.emit('render');
3472			}
3473		};
3474
3475		SourceNode.prototype.renderImageCanvas = function () {
3476			var media = this.source;
3477
3478			if (!gl || !media || !this.ready) {
3479				return;
3480			}
3481
3482			if (!this.initialized) {
3483				this.initialize();
3484			}
3485
3486			if (!this.allowRefresh) {
3487				return;
3488			}
3489
3490			if (this.dirty) {
3491				gl.bindTexture(gl.TEXTURE_2D, this.texture);
3492				gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, this.flip);
3493				gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
3494				try {
3495					gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, media);
3496
3497					this.dirty = false;
3498					this.emit('render');
3499					return true;
3500				} catch (securityError) {
3501					if (securityError.code === window.DOMException.SECURITY_ERR) {
3502						this.allowRefresh = false;
3503						Seriously.logger.error('Unable to access cross-domain image');
3504					}
3505				}
3506
3507				return false;
3508			}
3509		};
3510
3511		SourceNode.prototype.destroy = function () {
3512			var i, key, item;
3513
3514			if (this.plugin && this.plugin.destroy) {
3515				this.plugin.destroy.call(this);
3516			}
3517
3518			if (gl && this.texture) {
3519				gl.deleteTexture(this.texture);
3520			}
3521
3522			//targets
3523			while (this.targets.length) {
3524				item = this.targets.pop();
3525				if (item && item.removeSource) {
3526					item.removeSource(this);
3527				}
3528			}
3529
3530			//remove self from master list of sources
3531			i = sources.indexOf(this);
3532			if (i >= 0) {
3533				sources.splice(i, 1);
3534			}
3535
3536			i = allSourcesByHook[this.hook].indexOf(this);
3537			if (i >= 0) {
3538				allSourcesByHook[this.hook].splice(i, 1);
3539			}
3540
3541			for (key in this) {
3542				if (this.hasOwnProperty(key) && key !== 'id') {
3543					delete this[key];
3544				}
3545			}
3546
3547			Node.prototype.destroy.call(this);
3548		};
3549
3550		//todo: implement render for array and typed array
3551
3552		Target = function (targetNode) {
3553			var me = targetNode;
3554
3555			//priveleged accessor methods
3556			Object.defineProperties(this, {
3557				source: {
3558					enumerable: true,
3559					configurable: true,
3560					get: function () {
3561						if (me.source) {
3562							return me.source.pub;
3563						}
3564					},
3565					set: function (value) {
3566						me.setSource(value);
3567					}
3568				},
3569				original: {
3570					enumerable: true,
3571					configurable: true,
3572					get: function () {
3573						return me.target;
3574					}
3575				},
3576				width: {
3577					enumerable: true,
3578					configurable: true,
3579					get: function () {
3580						return me.width;
3581					},
3582					set: function (value) {
3583						if (!isNaN(value) && value >0 && me.width !== value) {
3584							me.width = value;
3585							me.resize();
3586							me.setTransformDirty();
3587						}
3588					}
3589				},
3590				height: {
3591					enumerable: true,
3592					configurable: true,
3593					get: function () {
3594						return me.height;
3595					},
3596					set: function (value) {
3597						if (!isNaN(value) && value >0 && me.height !== value) {
3598							me.height = value;
3599							me.resize();
3600							me.setTransformDirty();
3601						}
3602					}
3603				},
3604				id: {
3605					enumerable: true,
3606					configurable: true,
3607					get: function () {
3608						return me.id;
3609					}
3610				}
3611			});
3612
3613			this.render = function () {
3614				me.render();
3615			};
3616
3617			this.readPixels = function (x, y, width, height, dest) {
3618				return me.readPixels(x, y, width, height, dest);
3619			};
3620
3621			this.on = function (eventName, callback) {
3622				me.on(eventName, callback);
3623			};
3624
3625			this.off = function (eventName, callback) {
3626				me.off(eventName, callback);
3627			};
3628
3629			this.go = function (options) {
3630				me.go(options);
3631			};
3632
3633			this.stop = function () {
3634				me.stop();
3635			};
3636
3637			this.getTexture = function () {
3638				return me.frameBuffer.texture;
3639			};
3640
3641			this.destroy = function () {
3642				var i,
3643					descriptor;
3644
3645				me.destroy();
3646
3647				for (i in this) {
3648					if (this.hasOwnProperty(i) && i !== 'isDestroyed' && i !== 'id') {
3649						descriptor = Object.getOwnPropertyDescriptor(this, i);
3650						if (descriptor.get || descriptor.set ||
3651								typeof this[i] !== 'function') {
3652							delete this[i];
3653						} else {
3654							this[i] = nop;
3655						}
3656					}
3657				}
3658			};
3659
3660			this.inputs = function (name) {
3661				return {
3662					source: {
3663						type: 'image'
3664					}
3665				};
3666			};
3667
3668			this.isDestroyed = function () {
3669				return me.isDestroyed;
3670			};
3671
3672			this.isReady = function () {
3673				return me.ready;
3674			};
3675		};
3676
3677		/*
3678			possible targets: canvas (2d or 3d), gl render buffer (must be same canvas)
3679		*/
3680		TargetNode = function (hook, target, options) {
3681			var opts,
3682				flip,
3683				width,
3684				height,
3685				that = this,
3686				matchedType = false,
3687				i, element, elements, context,
3688				debugContext,
3689				frameBuffer,
3690				targetList,
3691				triedWebGl = false,
3692				key;
3693
3694			function targetPlugin(hook, target, options, force) {
3695				var plugin = seriousTargets[hook];
3696				if (plugin.definition) {
3697					plugin = plugin.definition.call(that, target, options, force);
3698					if (!plugin) {
3699						return null;
3700					}
3701					plugin = extend(extend({}, seriousTargets[hook]), plugin);
3702					that.hook = key;
3703					matchedType = true;
3704					that.plugin = plugin;
3705					that.compare = plugin.compare;
3706					if (plugin.target) {
3707						target = plugin.target;
3708					}
3709					if (plugin.gl && !that.gl) {
3710						that.gl = plugin.gl;
3711						if (!gl) {
3712							attachContext(plugin.gl);
3713						}
3714					}
3715					if (that.gl === gl) {
3716						that.model = rectangleModel;
3717						that.shader = baseShader;
3718					}
3719				}
3720				return plugin;
3721			}
3722
3723			function compareTarget(target) {
3724				return that.target === target;
3725			}
3726
3727			Node.call(this);
3728
3729			if (hook && typeof hook !== 'string' || !target && target !== 0) {
3730				if (!options || typeof options !== 'object') {
3731					options = target;
3732				}
3733				target = hook;
3734			}
3735
3736			opts = options || {};
3737			flip = opts.flip === undefined ? true : opts.flip;
3738			width = parseInt(opts.width, 10);
3739			height = parseInt(opts.height, 10);
3740			debugContext = opts.debugContext;
3741
3742			// forced target type?
3743			if (typeof hook === 'string' && seriousTargets[hook]) {
3744				targetPlugin(hook, target, opts, true);
3745			}
3746
3747			this.renderToTexture = opts.renderToTexture;
3748
3749			if (isInstance(target, 'WebGLFramebuffer')) {
3750				frameBuffer = target;
3751
3752				if (isInstance(opts, 'HTMLCanvasElement')) {
3753					target = opts;
3754				} else if (isInstance(opts, 'WebGLRenderingContext')) {
3755					target = opts.canvas;
3756				} else if (isInstance(opts.canvas, 'HTMLCanvasElement')) {
3757					target = opts.canvas;
3758				} else if (isInstance(opts.context, 'WebGLRenderingContext')) {
3759					target = opts.context.canvas;
3760				} else {
3761					//todo: search all canvases for matching contexts?
3762					throw new Error('Must provide a canvas with WebGLFramebuffer target');
3763				}
3764			}
3765
3766			if (isInstance(target, 'HTMLCanvasElement')) {
3767				width = target.width;
3768				height = target.height;
3769
3770				//try to get a webgl context.
3771				if (!gl || gl.canvas !== target && opts.allowSecondaryWebGL) {
3772					triedWebGl = true;
3773					context = getWebGlContext(target, {
3774						alpha: true,
3775						premultipliedAlpha: true,
3776						preserveDrawingBuffer: true,
3777						stencil: true,
3778						debugContext: debugContext
3779					});
3780				}
3781
3782				if (!context) {
3783					if (!opts.allowSecondaryWebGL && gl && gl.canvas !== target) {
3784						throw new Error('Only one WebGL target canvas allowed. Set allowSecondaryWebGL option to create secondary context.');
3785					}
3786
3787					this.render = nop;
3788					Seriously.logger.log('Unable to create WebGL context.');
3789					//throw new Error('Unable to create WebGL context.');
3790				} else if (!gl || gl === context) {
3791					//this is our main WebGL canvas
3792					if (!primaryTarget) {
3793						primaryTarget = this;
3794					}
3795					if (!gl) {
3796						attachContext(context);
3797					}
3798					this.render = this.renderWebGL;
3799
3800					/*
3801					Don't remember what this is for. Maybe we should remove it
3802					*/
3803					if (opts.renderToTexture) {
3804						if (gl) {
3805							this.frameBuffer = new FrameBuffer(gl, width, height, false);
3806						}
3807					} else {
3808						this.frameBuffer = {
3809							frameBuffer: frameBuffer || null
3810						};
3811					}
3812				} else {
3813					//set up alternative drawing method using ArrayBufferView
3814					this.gl = context;
3815
3816					//this.pixels = new Uint8Array(width * height * 4);
3817					//todo: probably need another framebuffer for renderToTexture?
3818					//todo: handle lost context on secondary webgl
3819					this.frameBuffer = {
3820						frameBuffer: frameBuffer || null
3821					};
3822					this.shader = new ShaderProgram(this.gl, baseVertexShader, baseFragmentShader);
3823					this.model = buildRectangleModel.call(this, this.gl);
3824					this.pixels = null;
3825
3826					this.texture = this.gl.createTexture();
3827					this.gl.bindTexture(gl.TEXTURE_2D, this.texture);
3828					this.gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
3829					this.gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
3830					this.gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
3831					this.gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
3832
3833					this.render = this.renderSecondaryWebGL;
3834				}
3835
3836				matchedType = true;
3837			}
3838
3839			if (!matchedType) {
3840				for (key in seriousTargets) {
3841					if (seriousTargets.hasOwnProperty(key) && seriousTargets[key]) {
3842						if (targetPlugin(key, target, opts, false)) {
3843							break;
3844						}
3845					}
3846				}
3847			}
3848
3849			if (!matchedType) {
3850				throw new Error('Unknown target type');
3851			}
3852
3853			if (allTargets) {
3854				targetList = allTargets.get(target);
3855				if (targetList) {
3856					Seriously.logger.warn(
3857						'Target already in use by another instance',
3858						target,
3859						Object.keys(targetList).map(function (key) {
3860							return targetList[key];
3861						})
3862					);
3863				} else {
3864					targetList = {};
3865					allTargets.set(target, targetList);
3866				}
3867				targetList[seriously.id] = seriously;
3868			}
3869
3870			this.target = target;
3871			this.transform = null;
3872			this.transformDirty = true;
3873			this.flip = flip;
3874			if (width) {
3875				this.width = width;
3876			}
3877			if (height) {
3878				this.height = height;
3879			}
3880
3881			this.uniforms.resolution[0] = this.width;
3882			this.uniforms.resolution[1] = this.height;
3883
3884			if (opts.auto !== undefined) {
3885				this.auto = opts.auto;
3886			} else {
3887				this.auto = auto;
3888			}
3889			this.frames = 0;
3890
3891			this.pub = new Target(this);
3892
3893			nodes.push(this);
3894			nodesById[this.id] = this;
3895			targets.push(this);
3896		};
3897
3898		TargetNode.prototype = Object.create(Node.prototype);
3899		TargetNode.prototype.constructor = TargetNode;
3900
3901		TargetNode.prototype.setSource = function (source) {
3902			var newSource;
3903
3904			//todo: what if source is null/undefined/false
3905
3906			newSource = findInputNode(source);
3907
3908			//todo: check for cycles
3909
3910			if (newSource !== this.source) {
3911				if (this.source) {
3912					this.source.removeTarget(this);
3913				}
3914				this.source = newSource;
3915				newSource.addTarget(this);
3916
3917				if (newSource) {
3918					this.resize();
3919					if (newSource.ready) {
3920						this.setReady();
3921					} else {
3922						this.setUnready();
3923					}
3924				}
3925
3926				this.setDirty();
3927			}
3928		};
3929
3930		TargetNode.prototype.setDirty = function () {
3931			this.dirty = true;
3932
3933			if (this.auto && !rafId) {
3934				rafId = requestAnimationFrame(renderDaemon);
3935			}
3936		};
3937
3938		TargetNode.prototype.resize = function () {
3939			//if target is a canvas, reset size to canvas size
3940			if (isInstance(this.target, 'HTMLCanvasElement')) {
3941				if (this.width !== this.target.width || this.height !== this.target.height) {
3942					this.target.width = this.width;
3943					this.target.height = this.height;
3944					this.uniforms.resolution[0] = this.width;
3945					this.uniforms.resolution[1] = this.height;
3946					this.emit('resize');
3947					this.setTransformDirty();
3948				}
3949			} else if (this.plugin && this.plugin.resize) {
3950				this.plugin.resize.call(this);
3951			}
3952
3953			if (this.source &&
3954				(this.source.width !== this.width || this.source.height !== this.height)) {
3955				if (!this.transform) {
3956					this.transform = new Float32Array(16);
3957				}
3958			}
3959		};
3960
3961		TargetNode.prototype.setTransformDirty = function () {
3962			this.transformDirty = true;
3963			this.setDirty();
3964		};
3965
3966		TargetNode.prototype.go = function () {
3967			this.auto = true;
3968			this.setDirty();
3969		};
3970
3971		TargetNode.prototype.stop = function () {
3972			this.auto = false;
3973		};
3974
3975		TargetNode.prototype.render = function () {
3976			if (gl && this.plugin && this.plugin.render) {
3977				this.plugin.render.call(this, draw, baseShader, rectangleModel);
3978			}
3979		};
3980
3981		TargetNode.prototype.renderWebGL = function () {
3982			var matrix, x, y;
3983
3984			this.resize();
3985
3986			if (gl && this.dirty && this.ready) {
3987				if (!this.source) {
3988					return;
3989				}
3990
3991				this.source.render();
3992
3993				this.uniforms.source = this.source.texture;
3994
3995				if (this.source.width === this.width && this.source.height === this.height) {
3996					this.uniforms.transform = this.source.cumulativeMatrix || identity;
3997				} else if (this.transformDirty) {
3998					matrix = this.transform;
3999					mat4.copy(matrix, this.source.cumulativeMatrix || identity);
4000					x = this.source.width / this.width;
4001					y = this.source.height / this.height;
4002					matrix[0] *= x;
4003					matrix[1] *= x;
4004					matrix[2] *= x;
4005					matrix[3] *= x;
4006					matrix[4] *= y;
4007					matrix[5] *= y;
4008					matrix[6] *= y;
4009					matrix[7] *= y;
4010					this.uniforms.transform = matrix;
4011					this.transformDirty = false;
4012				}
4013
4014				draw(baseShader, rectangleModel, this.uniforms, this.frameBuffer.frameBuffer, this, outputRenderOptions);
4015
4016				this.emit('render');
4017				this.dirty = false;
4018			}
4019		};
4020
4021		TargetNode.prototype.renderSecondaryWebGL = function () {
4022			var sourceWidth,
4023				sourceHeight,
4024				matrix,
4025				x,
4026				y;
4027
4028			if (this.dirty && this.ready && this.source) {
4029				this.emit('render');
4030				this.source.render(true);
4031
4032				sourceWidth = this.source.width;
4033				sourceHeight = this.source.height;
4034
4035				if (!this.pixels || this.pixels.length !== sourceWidth * sourceHeight * 4) {
4036					this.pixels = new Uint8Array(sourceWidth * sourceHeight * 4);
4037				}
4038
4039				this.source.readPixels(0, 0, sourceWidth, sourceHeight, this.pixels);
4040
4041				this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, sourceWidth, sourceHeight, 0, this.gl.RGBA, this.gl.UNSIGNED_BYTE, this.pixels);
4042
4043				if (sourceWidth === this.width && sourceHeight === this.height) {
4044					this.uniforms.transform = identity;
4045				} else if (this.transformDirty) {
4046					matrix = this.transform;
4047					mat4.copy(matrix, identity);
4048					x = this.source.width / this.width;
4049					y = this.source.height / this.height;
4050					matrix[0] *= x;
4051					matrix[1] *= x;
4052					matrix[2] *= x;
4053					matrix[3] *= x;
4054					matrix[4] *= y;
4055					matrix[5] *= y;
4056					matrix[6] *= y;
4057					matrix[7] *= y;
4058					this.uniforms.transform = matrix;
4059					this.transformDirty = false;
4060				}
4061
4062				this.uniforms.source = this.texture;
4063				draw(this.shader, this.model, this.uniforms, null, this, outputRenderOptions);
4064
4065				this.dirty = false;
4066			}
4067		};
4068
4069		TargetNode.prototype.removeSource = function (source) {
4070			if (this.source === source || this.source === source.pub) {
4071				this.source = null;
4072			}
4073		};
4074
4075		TargetNode.prototype.destroy = function () {
4076			var i,
4077				targetList;
4078
4079			//source
4080			if (this.source && this.source.removeTarget) {
4081				this.source.removeTarget(this);
4082			}
4083
4084			if (allTargets) {
4085				targetList = allTargets.get(this.target);
4086				delete targetList[seriously.id];
4087				if (!Object.keys(targetList).length) {
4088					allTargets.delete(this.target);
4089				}
4090			}
4091
4092			if (this.plugin && this.plugin.destroy) {
4093				this.plugin.destroy.call(this);
4094			}
4095
4096			delete this.source;
4097			delete this.target;
4098			delete this.pub;
4099			delete this.uniforms;
4100			delete this.pixels;
4101			delete this.auto;
4102
4103			//remove self from master list of targets
4104			i = targets.indexOf(this);
4105			if (i >= 0) {
4106				targets.splice(i, 1);
4107			}
4108
4109			Node.prototype.destroy.call(this);
4110
4111			//clear out context so we can start over
4112			if (this === primaryTarget) {
4113				glCanvas.removeEventListener('webglcontextrestored', restoreContext, false);
4114				destroyContext();
4115				primaryTarget = null;
4116			}
4117		};
4118
4119		Transform = function (transformNode) {
4120			var me = transformNode,
4121				self = this,
4122				key;
4123
4124			function setInput(inputName, def, input) {
4125				var inputKey, lookup, value;
4126
4127				lookup = me.inputElements[inputName];
4128
4129				//todo: there is some duplicate code with Effect here. Consolidate.
4130				if (typeof input === 'string' && isNaN(input)) {
4131					if (def.type === 'enum') {
4132						if (!def.options.hasOwnProperty(input)) {
4133							input = getElement(input, ['select']);
4134						}
4135					} else if (def.type === 'number' || def.type === 'boolean') {
4136						input = getElement(input, ['input', 'select']);
4137					} else if (def.type === 'image') {
4138						input = getElement(input, ['canvas', 'img', 'video']);
4139					}
4140				}
4141
4142				if (isInstance(input, 'HTMLInputElement') || isInstance(input, 'HTMLSelectElement')) {
4143					value = input.value;
4144
4145					if (lookup && lookup.element !== input) {
4146						lookup.element.removeEventListener('change', lookup.listener, true);
4147						lookup.element.removeEventListener('input', lookup.listener, true);
4148						delete me.inputElements[inputName];
4149						lookup = null;
4150					}
4151
4152					if (!lookup) {
4153						lookup = {
4154							element: input,
4155							listener: (function (element) {
4156								return function () {
4157									var oldValue, newValue;
4158
4159									if (input.type === 'checkbox') {
4160										//special case for check box
4161										oldValue = input.checked;
4162									} else {
4163										oldValue = element.value;
4164									}
4165									newValue = me.setInput(inputName, oldValue);
4166
4167									//special case for color type
4168									if (input.type === 'color') {
4169										newValue = colorArrayToHex(newValue);
4170									}
4171
4172									//if input validator changes our value, update HTML Element
4173									//todo: make this optional...somehow
4174									if (newValue !== oldValue) {
4175										element.value = newValue;
4176									}
4177								};
4178							}(input))
4179						};
4180
4181						me.inputElements[inputName] = lookup;
4182						if (input.type === 'range') {
4183							input.addEventListener('input', lookup.listener, true);
4184							input.addEventListener('change', lookup.listener, true);
4185						} else {
4186							input.addEventListener('change', lookup.listener, true);
4187						}
4188					}
4189
4190					if (lookup && input.type === 'checkbox') {
4191						value = input.checked;
4192					}
4193				} else {
4194					if (lookup) {
4195						lookup.element.removeEventListener('change', lookup.listener, true);
4196						lookup.element.removeEventListener('input', lookup.listener, true);
4197						delete me.inputElements[inputName];
4198					}
4199					value = input;
4200				}
4201
4202				me.setInput(inputName, value);
4203			}
4204
4205			function setProperty(name, def) {
4206				// todo: validate value passed to 'set'
4207				Object.defineProperty(self, name, {
4208					configurable: true,
4209					enumerable: true,
4210					get: function () {
4211						return def.get.call(me);
4212					},
4213					set: function (val) {
4214						setInput(name, def, val);
4215					}
4216				});
4217			}
4218
4219			function makeMethod(method) {
4220				return function () {
4221					if (method.apply(me, arguments)) {
4222						me.setTransformDirty();
4223					}
4224				};
4225			}
4226
4227			//priveleged accessor methods
4228			Object.defineProperties(this, {
4229				transform: {
4230					enumerable: true,
4231					configurable: true,
4232					get: function () {
4233						return me.hook;
4234					}
4235				},
4236				title: {
4237					enumerable: true,
4238					configurable: true,
4239					get: function () {
4240						return me.plugin.title || me.hook;
4241					}
4242				},
4243				width: {
4244					enumerable: true,
4245					configurable: true,
4246					get: function () {
4247						return me.width;
4248					}
4249				},
4250				height: {
4251					enumerable: true,
4252					configurable: true,
4253					get: function () {
4254						return me.height;
4255					}
4256				},
4257				id: {
4258					enumerable: true,
4259					configurable: true,
4260					get: function () {
4261						return me.id;
4262					}
4263				},
4264				source: {
4265					enumerable: true,
4266					configurable: true,
4267					get: function () {
4268						return me.source && me.source.pub;
4269					},
4270					set: function (source) {
4271						me.setSource(source);
4272					}
4273				}
4274			});
4275
4276			// attach methods
4277			for (key in me.methods) {
4278				if (me.methods.hasOwnProperty(key)) {
4279					this[key] = makeMethod(me.methods[key]);
4280				}
4281			}
4282
4283			for (key in me.inputs) {
4284				if (me.inputs.hasOwnProperty(key)) {
4285					setProperty(key, me.inputs[key]);
4286				}
4287			}
4288
4289			this.update = function () {
4290				me.setDirty();
4291			};
4292
4293			this.inputs = function (name) {
4294				var result,
4295					input,
4296					inputs,
4297					i,
4298					key;
4299
4300				inputs = me.plugin.inputs;
4301
4302				/*
4303				Only reports setter/getter inputs, not methods
4304				*/
4305
4306				if (name) {
4307					input = inputs[name];
4308					if (!input || input.method) {
4309						return null;
4310					}
4311
4312					result = {
4313						type: input.type,
4314						defaultValue: input.defaultValue,
4315						title: input.title || name
4316					};
4317
4318					if (input.type === 'number') {
4319						result.min = input.min;
4320						result.max = input.max;
4321						result.step = input.step;
4322						result.mod = input.mod;
4323					} else if (input.type === 'enum') {
4324						//make a copy
4325						result.options = extend({}, input.options);
4326					} else if (input.type === 'vector') {
4327						result.dimensions = input.dimensions;
4328					}
4329
4330					if (input.description) {
4331						result.description = input.description;
4332					}
4333
4334					return result;
4335				}
4336
4337				result = {};
4338				for (key in inputs) {
4339					if (inputs.hasOwnProperty(key) && !inputs[key].method) {
4340						result[key] = this.inputs(key);
4341					}
4342				}
4343				return result;
4344			};
4345
4346			this.alias = function (inputName, aliasName) {
4347				me.alias(inputName, aliasName);
4348				return this;
4349			};
4350
4351			this.on = function (eventName, callback) {
4352				me.on(eventName, callback);
4353			};
4354
4355			this.off = function (eventName, callback) {
4356				me.off(eventName, callback);
4357			};
4358
4359			this.destroy = function () {
4360				var i,
4361					descriptor;
4362
4363				me.destroy();
4364
4365				for (i in this) {
4366					if (this.hasOwnProperty(i) && i !== 'isDestroyed' && i !== 'id') {
4367						//todo: probably can simplify this if the only setter/getter is id
4368						descriptor = Object.getOwnPropertyDescriptor(this, i);
4369						if (descriptor.get || descriptor.set ||
4370								typeof this[i] !== 'function') {
4371							delete this[i];
4372						} else {
4373							this[i] = nop;
4374						}
4375					}
4376				}
4377			};
4378
4379			this.isDestroyed = function () {
4380				return me.isDestroyed;
4381			};
4382
4383			this.isReady = function () {
4384				return me.ready;
4385			};
4386		};
4387
4388		TransformNode = function (hook, options) {
4389			var key,
4390				input,
4391				initialValue,
4392				defaultValue,
4393				defaults;
4394
4395			this.matrix = new Float32Array(16);
4396			this.cumulativeMatrix = new Float32Array(16);
4397
4398			this.ready = false;
4399			this.width = 1;
4400			this.height = 1;
4401
4402			this.seriously = seriously;
4403
4404			this.transformRef = seriousTransforms[hook];
4405			this.hook = hook;
4406			this.id = nodeId;
4407			nodeId++;
4408
4409			this.options = options;
4410			this.sources = null;
4411			this.targets = [];
4412			this.inputElements = {};
4413			this.inputs = {};
4414			this.methods = {};
4415			this.listeners = {};
4416
4417			this.texture = null;
4418			this.frameBuffer = null;
4419			this.uniforms = null;
4420
4421			this.dirty = true;
4422			this.transformDirty = true;
4423			this.renderDirty = false;
4424			this.isDestroyed = false;
4425			this.transformed = false;
4426
4427			this.plugin = extend({}, this.transformRef);
4428			if (this.transformRef.definition) {
4429				extend(this.plugin, this.transformRef.definition.call(this, options));
4430			}
4431
4432			// set up inputs and methods
4433			for (key in this.plugin.inputs) {
4434				if (this.plugin.inputs.hasOwnProperty(key)) {
4435					input = this.plugin.inputs[key];
4436
4437					if (input.method && typeof input.method === 'function') {
4438						this.methods[key] = input.method;
4439					} else if (typeof input.set === 'function' && typeof input.get === 'function') {
4440						this.inputs[key] = input;
4441					}
4442				}
4443			}
4444			validateInputSpecs(this.plugin);
4445
4446			// set default value for all inputs (no defaults for methods)
4447			defaults = defaultInputs[hook];
4448			for (key in this.plugin.inputs) {
4449				if (this.plugin.inputs.hasOwnProperty(key)) {
4450					input = this.plugin.inputs[key];
4451
4452					if (typeof input.set === 'function' && typeof input.get === 'function' &&
4453							typeof input.method !== 'function') {
4454
4455						initialValue = input.get.call(this);
4456						defaultValue = input.defaultValue === undefined ? initialValue : input.defaultValue;
4457						defaultValue = input.validate.call(this, defaultValue, input, initialValue);
4458						if (defaults && defaults[key] !== undefined) {
4459							defaultValue = input.validate.call(this, defaults[key], input, input.defaultValue, defaultValue);
4460							defaults[key] = defaultValue;
4461						}
4462						if (defaultValue !== initialValue) {
4463							input.set.call(this, defaultValue);
4464						}
4465					}
4466				}
4467			}
4468
4469			nodes.push(this);
4470			nodesById[this.id] = this;
4471
4472			this.pub = new Transform(this);
4473
4474			transforms.push(this);
4475
4476			allTransformsByHook[hook].push(this);
4477		};
4478
4479		TransformNode.prototype = Object.create(Node.prototype);
4480		TransformNode.prototype.constructor = TransformNode;
4481
4482		TransformNode.prototype.setDirty = function () {
4483			this.renderDirty = true;
4484			Node.prototype.setDirty.call(this);
4485		};
4486
4487		TransformNode.prototype.setTransformDirty = function () {
4488			var i,
4489				target;
4490			this.transformDirty = true;
4491			this.dirty = true;
4492			this.renderDirty = true;
4493			for (i = 0; i < this.targets.length; i++) {
4494				target = this.targets[i];
4495				if (target.setTransformDirty) {
4496					target.setTransformDirty();
4497				} else {
4498					target.setDirty();
4499				}
4500			}
4501		};
4502
4503		TransformNode.prototype.resize = function () {
4504			var i;
4505
4506			Node.prototype.resize.call(this);
4507
4508			if (this.plugin.resize) {
4509				this.plugin.resize.call(this);
4510			}
4511
4512			for (i = 0; i < this.targets.length; i++) {
4513				this.targets[i].resize();
4514			}
4515
4516			this.setTransformDirty();
4517		};
4518
4519		TransformNode.prototype.setSource = function (source) {
4520			var newSource;
4521
4522			//todo: what if source is null/undefined/false
4523
4524			newSource = findInputNode(source);
4525
4526			if (newSource === this.source) {
4527				return;
4528			}
4529
4530			if (traceSources(newSource, this)) {
4531				throw new Error('Attempt to make cyclical connection.');
4532			}
4533
4534			if (this.source) {
4535				this.source.removeTarget(this);
4536			}
4537			this.source = newSource;
4538			newSource.addTarget(this);
4539
4540			if (newSource && newSource.ready) {
4541				this.setReady();
4542			} else {
4543				this.setUnready();
4544			}
4545			this.resize();
4546		};
4547
4548		TransformNode.prototype.addTarget = function (target) {
4549			var i;
4550			for (i = 0; i < this.targets.length; i++) {
4551				if (this.targets[i] === target) {
4552					return;
4553				}
4554			}
4555
4556			this.targets.push(target);
4557		};
4558
4559		TransformNode.prototype.removeTarget = function (target) {
4560			var i = this.targets && this.targets.indexOf(target);
4561			if (i >= 0) {
4562				this.targets.splice(i, 1);
4563			}
4564
4565			if (this.targets && this.targets.length) {
4566				this.resize();
4567			}
4568		};
4569
4570		TransformNode.prototype.setInput = function (name, value) {
4571			var input,
4572				defaultValue,
4573				previous;
4574
4575			if (this.plugin.inputs.hasOwnProperty(name)) {
4576				input = this.plugin.inputs[name];
4577
4578				if (defaultInputs[this.hook] && defaultInputs[this.hook][name] !== undefined) {
4579					defaultValue = defaultInputs[this.hook][name];
4580				} else {
4581					defaultValue = input.defaultValue;
4582				}
4583
4584				previous = input.get.call(this);
4585				if (defaultValue === undefined) {
4586					defaultValue = previous;
4587				}
4588				value = input.validate.call(this, value, input, defaultValue, previous);
4589
4590				if (input.set.call(this, value)) {
4591					this.setTransformDirty();
4592				}
4593
4594				return input.get.call(this);
4595			}
4596		};
4597
4598		TransformNode.prototype.alias = function (inputName, aliasName) {
4599			var me = this,
4600				input,
4601				def;
4602
4603			if (reservedNames.indexOf(aliasName) >= 0) {
4604				throw new Error('\'' + aliasName + '\' is a reserved name and cannot be used as an alias.');
4605			}
4606
4607			if (this.plugin.inputs.hasOwnProperty(inputName)) {
4608				if (!aliasName) {
4609					aliasName = inputName;
4610				}
4611
4612				seriously.removeAlias(aliasName);
4613
4614				input = this.inputs[inputName];
4615				if (input) {
4616					def = me.inputs[inputName];
4617					Object.defineProperty(seriously, aliasName, {
4618						configurable: true,
4619						enumerable: true,
4620						get: function () {
4621							return def.get.call(me);
4622						},
4623						set: function (val) {
4624							if (def.set.call(me, val)) {
4625								me.setTransformDirty();
4626							}
4627						}
4628					});
4629				} else {
4630					input = this.methods[inputName];
4631					if (input) {
4632						def = input;
4633						seriously[aliasName] = function () {
4634							if (def.apply(me, arguments)) {
4635								me.setTransformDirty();
4636							}
4637						};
4638					}
4639				}
4640
4641				if (input) {
4642					aliases[aliasName] = {
4643						node: this,
4644						input: inputName
4645					};
4646				}
4647			}
4648
4649			return this;
4650		};
4651
4652		TransformNode.prototype.render = function (renderTransform) {
4653			if (!this.source) {
4654				if (this.transformDirty) {
4655					mat4.copy(this.cumulativeMatrix, this.matrix);
4656					this.transformDirty = false;
4657				}
4658				this.texture = null;
4659				this.dirty = false;
4660
4661				return;
4662			}
4663
4664			this.source.render();
4665
4666			if (this.transformDirty) {
4667				if (this.transformed) {
4668					//use this.matrix
4669					if (this.source.cumulativeMatrix) {
4670						mat4.multiply(this.cumulativeMatrix, this.matrix, this.source.cumulativeMatrix);
4671					} else {
4672						mat4.copy(this.cumulativeMatrix, this.matrix);
4673					}
4674				} else {
4675					//copy source.cumulativeMatrix
4676					mat4.copy(this.cumulativeMatrix, this.source.cumulativeMatrix || identity);
4677				}
4678
4679				this.transformDirty = false;
4680			}
4681
4682			if (renderTransform && gl) {
4683				if (this.renderDirty) {
4684					if (!this.frameBuffer) {
4685						this.uniforms = {
4686							resolution: [this.width, this.height]
4687						};
4688						this.frameBuffer = new FrameBuffer(gl, this.width, this.height);
4689					}
4690
4691					this.uniforms.source = this.source.texture;
4692					this.uniforms.transform = this.cumulativeMatrix || identity;
4693					draw(baseShader, rectangleModel, this.uniforms, this.frameBuffer.frameBuffer, this);
4694
4695					this.renderDirty = false;
4696				}
4697				this.texture = this.frameBuffer.texture;
4698			} else if (this.source) {
4699				this.texture = this.source.texture;
4700			} else {
4701				this.texture = null;
4702			}
4703
4704			this.dirty = false;
4705
4706			return this.texture;
4707		};
4708
4709		TransformNode.prototype.readPixels = function (x, y, width, height, dest) {
4710			var nodeGl = this.gl || gl;
4711
4712			if (!gl) {
4713				//todo: is this the best approach?
4714				throw new Error('Cannot read pixels until a canvas is connected');
4715			}
4716
4717			//todo: check on x, y, width, height
4718			this.render(true);
4719
4720			if (dest === undefined) {
4721				dest = new Uint8Array(width * height * 4);
4722			} else if (!(isInstance(dest, 'Uint8Array'))) {
4723				throw new Error('Incompatible array type');
4724			}
4725
4726			nodeGl.bindFramebuffer(gl.FRAMEBUFFER, this.frameBuffer.frameBuffer);
4727			nodeGl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, dest);
4728
4729			return dest;
4730		};
4731
4732		TransformNode.prototype.destroy = function () {
4733			var i, key, item, hook = this.hook;
4734
4735			//let effect destroy itself
4736			if (this.plugin.destroy && typeof this.plugin.destroy === 'function') {
4737				this.plugin.destroy.call(this);
4738			}
4739			delete this.effect;
4740
4741			if (this.frameBuffer) {
4742				this.frameBuffer.destroy();
4743				delete this.frameBuffer;
4744				delete this.texture;
4745			}
4746
4747			//stop watching any input elements
4748			for (i in this.inputElements) {
4749				if (this.inputElements.hasOwnProperty(i)) {
4750					item = this.inputElements[i];
4751					item.element.removeEventListener('change', item.listener, true);
4752					item.element.removeEventListener('input', item.listener, true);
4753				}
4754			}
4755
4756			//sources
4757			if (this.source) {
4758				this.source.removeTarget(this);
4759			}
4760
4761			//targets
4762			while (this.targets.length) {
4763				item = this.targets.pop();
4764				if (item && item.removeSource) {
4765					item.removeSource(this);
4766				}
4767			}
4768
4769			for (key in this) {
4770				if (this.hasOwnProperty(key) && key !== 'id') {
4771					delete this[key];
4772				}
4773			}
4774
4775			//remove any aliases
4776			for (key in aliases) {
4777				if (aliases.hasOwnProperty(key)) {
4778					item = aliases[key];
4779					if (item.node === this) {
4780						seriously.removeAlias(key);
4781					}
4782				}
4783			}
4784
4785			//remove self from master list of effects
4786			i = transforms.indexOf(this);
4787			if (i >= 0) {
4788				transforms.splice(i, 1);
4789			}
4790
4791			i = allTransformsByHook[hook].indexOf(this);
4792			if (i >= 0) {
4793				allTransformsByHook[hook].splice(i, 1);
4794			}
4795
4796			Node.prototype.destroy.call(this);
4797		};
4798
4799		TransformNode.prototype.setReady = Node.prototype.setReady;
4800		TransformNode.prototype.setUnready = Node.prototype.setUnready;
4801		TransformNode.prototype.on = Node.prototype.on;
4802		TransformNode.prototype.off = Node.prototype.off;
4803		TransformNode.prototype.emit = Node.prototype.emit;
4804
4805		/*
4806		Initialize Seriously object based on options
4807		*/
4808
4809		if (isInstance(options, 'HTMLCanvasElement')) {
4810			options = {
4811				canvas: options
4812			};
4813		} else {
4814			options = options || {};
4815		}
4816
4817		if (options.canvas) {
4818		}
4819
4820		/*
4821		priveleged methods
4822		*/
4823		this.effect = function (hook, options) {
4824			if (!seriousEffects[hook]) {
4825				throw new Error('Unknown effect: ' + hook);
4826			}
4827
4828			var effectNode = new EffectNode(hook, options);
4829			return effectNode.pub;
4830		};
4831
4832		this.source = function (hook, source, options) {
4833			var sourceNode = findInputNode(hook, source, options);
4834			return sourceNode.pub;
4835		};
4836
4837		this.transform = function (hook, opts) {
4838			var transformNode;
4839
4840			if (typeof hook !== 'string') {
4841				opts = hook;
4842				hook = false;
4843			}
4844
4845			if (hook) {
4846				if (!seriousTransforms[hook]) {
4847					throw new Error('Unknown transform: ' + hook);
4848				}
4849			} else {
4850				hook = options && options.defaultTransform || '2d';
4851				if (!seriousTransforms[hook]) {
4852					throw new Error('No transform specified');
4853				}
4854			}
4855
4856			transformNode = new TransformNode(hook, opts);
4857			return transformNode.pub;
4858		};
4859
4860		this.target = function (hook, target, options) {
4861			var targetNode,
4862				element,
4863				i;
4864
4865			if (hook && typeof hook === 'string' && !seriousTargets[hook]) {
4866				element = document.querySelector(hook);
4867			}
4868
4869			if (typeof hook !== 'string' || !target && target !== 0 || element) {
4870				if (!options || typeof options !== 'object') {
4871					options = target;
4872				}
4873				target = element || hook;
4874				hook = null;
4875			}
4876
4877			if (typeof target === 'string' && isNaN(target)) {
4878				target = document.querySelector(target);
4879			}
4880
4881			for (i = 0; i < targets.length; i++) {
4882				targetNode = targets[i];
4883				if ((!hook || hook === targetNode.hook) &&
4884						(targetNode.target === target || targetNode.compare && targetNode.compare(target, options))) {
4885
4886					return targetNode.pub;
4887				}
4888			}
4889
4890			targetNode = new TargetNode(hook, target, options);
4891
4892			return targetNode.pub;
4893		};
4894
4895		this.aliases = function () {
4896			return Object.keys(aliases);
4897		};
4898
4899		this.removeAlias = function (name) {
4900			if (aliases[name]) {
4901				delete this[name];
4902				delete aliases[name];
4903			}
4904		};
4905
4906		this.defaults = function (hook, options) {
4907			var key;
4908
4909			if (!hook) {
4910				if (hook === null) {
4911					for (key in defaultInputs) {
4912						if (defaultInputs.hasOwnProperty(key)) {
4913							delete defaultInputs[key];
4914						}
4915					}
4916				}
4917				return;
4918			}
4919
4920			if (typeof hook === 'object') {
4921				for (key in hook) {
4922					if (hook.hasOwnProperty(key)) {
4923						this.defaults(key, hook[key]);
4924					}
4925				}
4926
4927				return;
4928			}
4929
4930			if (options === null) {
4931				delete defaultInputs[hook];
4932			} else if (typeof options === 'object') {
4933				defaultInputs[hook] = extend({}, options);
4934			}
4935		};
4936
4937		this.go = function (pre, post) {
4938			var i;
4939
4940			if (typeof pre === 'function' && preCallbacks.indexOf(pre) < 0) {
4941				preCallbacks.push(pre);
4942			}
4943
4944			if (typeof post === 'function' && postCallbacks.indexOf(post) < 0) {
4945				postCallbacks.push(post);
4946			}
4947
4948			auto = true;
4949			for (i = 0; i < targets.length; i++) {
4950				targets[i].go();
4951			}
4952
4953			if (!rafId && (preCallbacks.length || postCallbacks.length)) {
4954				renderDaemon();
4955			}
4956		};
4957
4958		this.stop = function () {
4959			preCallbacks.length = 0;
4960			postCallbacks.length = 0;
4961			cancelAnimFrame(rafId);
4962			rafId = 0;
4963		};
4964
4965		this.render = function () {
4966			var i;
4967			for (i = 0; i < targets.length; i++) {
4968				targets[i].render(options);
4969			}
4970		};
4971
4972		this.destroy = function () {
4973			var i,
4974				node,
4975				descriptor;
4976
4977			while (nodes.length) {
4978				node = nodes[0];
4979				node.pub.destroy();
4980			}
4981
4982			for (i in this) {
4983				if (this.hasOwnProperty(i) && i !== 'isDestroyed' && i !== 'id') {
4984					descriptor = Object.getOwnPropertyDescriptor(this, i);
4985					if (descriptor.get || descriptor.set ||
4986							typeof this[i] !== 'function') {
4987						delete this[i];
4988					} else {
4989						this[i] = nop;
4990					}
4991				}
4992			}
4993
4994			seriously = null;
4995
4996			//todo: do we really need to allocate new arrays here?
4997			sources = [];
4998			targets = [];
4999			effects = [];
5000			nodes = [];
5001
5002			preCallbacks.length = 0;
5003			postCallbacks.length = 0;
5004			cancelAnimFrame(rafId);
5005			rafId = 0;
5006
5007			isDestroyed = true;
5008		};
5009
5010		this.isDestroyed = function () {
5011			return isDestroyed;
5012		};
5013
5014		this.incompatible = function (hook) {
5015			var key,
5016				plugin,
5017				failure = false;
5018
5019			failure = Seriously.incompatible(hook);
5020
5021			if (failure) {
5022				return failure;
5023			}
5024
5025			if (!hook) {
5026				for (key in allEffectsByHook) {
5027					if (allEffectsByHook.hasOwnProperty(key) && allEffectsByHook[key].length) {
5028						plugin = seriousEffects[key];
5029						if (plugin && typeof plugin.compatible === 'function' &&
5030								!plugin.compatible.call(this)) {
5031							return 'plugin-' + key;
5032						}
5033					}
5034				}
5035
5036				for (key in allSourcesByHook) {
5037					if (allSourcesByHook.hasOwnProperty(key) && allSourcesByHook[key].length) {
5038						plugin = seriousSources[key];
5039						if (plugin && typeof plugin.compatible === 'function' &&
5040								!plugin.compatible.call(this)) {
5041							return 'source-' + key;
5042						}
5043					}
5044				}
5045			}
5046
5047			return false;
5048		};
5049
5050		/*
5051		Informational utility methods
5052		*/
5053
5054		this.isNode = function (candidate) {
5055			var node;
5056			if (candidate) {
5057				node = nodesById[candidate.id];
5058				if (node && !node.isDestroyed) {
5059					return true;
5060				}
5061			}
5062			return false;
5063		};
5064
5065		this.isSource = function (candidate) {
5066			return this.isNode(candidate) && candidate instanceof Source;
5067		};
5068
5069		this.isEffect = function (candidate) {
5070			return this.isNode(candidate) && candidate instanceof Effect;
5071		};
5072
5073		this.isTransform = function (candidate) {
5074			return this.isNode(candidate) && candidate instanceof Transform;
5075		};
5076
5077		this.isTarget = function (candidate) {
5078			return this.isNode(candidate) && candidate instanceof Target;
5079		};
5080
5081		Object.defineProperties(this, {
5082			id: {
5083				enumerable: true,
5084				configurable: true,
5085				get: function () {
5086					return id;
5087				}
5088			}
5089		});
5090
5091		//todo: load, save, find
5092
5093		this.defaults(options.defaults);
5094	}
5095
5096	Seriously.incompatible = function (hook) {
5097		var canvas, gl, plugin;
5098
5099		if (incompatibility === undefined) {
5100			canvas = document.createElement('canvas');
5101			if (!canvas || !canvas.getContext) {
5102				incompatibility = 'canvas';
5103			} else if (!window.WebGLRenderingContext) {
5104				incompatibility = 'webgl';
5105			} else {
5106				gl = getTestContext();
5107				if (!gl) {
5108					incompatibility = 'context';
5109				}
5110			}
5111		}
5112
5113		if (incompatibility) {
5114			return incompatibility;
5115		}
5116
5117		if (hook) {
5118			plugin = seriousEffects[hook];
5119			if (plugin && typeof plugin.compatible === 'function' &&
5120				!plugin.compatible(gl)) {
5121
5122				return 'plugin-' + hook;
5123			}
5124
5125			plugin = seriousSources[hook];
5126			if (plugin && typeof plugin.compatible === 'function' &&
5127				!plugin.compatible(gl)) {
5128
5129				return 'source-' + hook;
5130			}
5131		}
5132
5133		return false;
5134	};
5135
5136	Seriously.plugin = function (hook, definition, meta) {
5137		var effect;
5138
5139		if (seriousEffects[hook]) {
5140			Seriously.logger.warn('Effect [' + hook + '] already loaded');
5141			return;
5142		}
5143
5144		if (meta === undefined && typeof definition === 'object') {
5145			meta = definition;
5146		}
5147
5148		if (!meta) {
5149			return;
5150		}
5151
5152		effect = extend({}, meta);
5153
5154		if (typeof definition === 'function') {
5155			effect.definition = definition;
5156		}
5157
5158		effect.reserved = reservedEffectProperties;
5159
5160		if (effect.inputs) {
5161			validateInputSpecs(effect);
5162		}
5163
5164		if (!effect.title) {
5165			effect.title = hook;
5166		}
5167
5168		/*
5169		if (typeof effect.requires !== 'function') {
5170			effect.requires = false;
5171		}
5172		*/
5173
5174		seriousEffects[hook] = effect;
5175		allEffectsByHook[hook] = [];
5176
5177		return effect;
5178	};
5179
5180	Seriously.removePlugin = function (hook) {
5181		var all, effect, plugin;
5182
5183		if (!hook) {
5184			return this;
5185		}
5186
5187		plugin = seriousEffects[hook];
5188
5189		if (!plugin) {
5190			return this;
5191		}
5192
5193		all = allEffectsByHook[hook];
5194		if (all) {
5195			while (all.length) {
5196				effect = all.shift();
5197				effect.destroy();
5198			}
5199			delete allEffectsByHook[hook];
5200		}
5201
5202		delete seriousEffects[hook];
5203
5204		return this;
5205	};
5206
5207	Seriously.source = function (hook, definition, meta) {
5208		var source;
5209
5210		if (seriousSources[hook]) {
5211			Seriously.logger.warn('Source [' + hook + '] already loaded');
5212			return;
5213		}
5214
5215		if (meta === undefined && typeof definition === 'object') {
5216			meta = definition;
5217		}
5218
5219		if (!meta && !definition) {
5220			return;
5221		}
5222
5223		source = extend({}, meta);
5224
5225		if (typeof definition === 'function') {
5226			source.definition = definition;
5227		}
5228
5229		if (!source.title) {
5230			source.title = hook;
5231		}
5232
5233
5234		seriousSources[hook] = source;
5235		allSourcesByHook[hook] = [];
5236
5237		return source;
5238	};
5239
5240	Seriously.removeSource = function (hook) {
5241		var all, source, plugin;
5242
5243		if (!hook) {
5244			return this;
5245		}
5246
5247		plugin = seriousSources[hook];
5248
5249		if (!plugin) {
5250			return this;
5251		}
5252
5253		all = allSourcesByHook[hook];
5254		if (all) {
5255			while (all.length) {
5256				source = all.shift();
5257				source.destroy();
5258			}
5259			delete allSourcesByHook[hook];
5260		}
5261
5262		delete seriousSources[hook];
5263
5264		return this;
5265	};
5266
5267	Seriously.transform = function (hook, definition, meta) {
5268		var transform;
5269
5270		if (seriousTransforms[hook]) {
5271			Seriously.logger.warn('Transform [' + hook + '] already loaded');
5272			return;
5273		}
5274
5275		if (meta === undefined && typeof definition === 'object') {
5276			meta = definition;
5277		}
5278
5279		if (!meta && !definition) {
5280			return;
5281		}
5282
5283		transform = extend({}, meta);
5284
5285		if (typeof definition === 'function') {
5286			transform.definition = definition;
5287		}
5288
5289		transform.reserved = reservedTransformProperties;
5290
5291		//todo: validate method definitions
5292		if (transform.inputs) {
5293			validateInputSpecs(transform);
5294		}
5295
5296		if (!transform.title) {
5297			transform.title = hook;
5298		}
5299
5300		seriousTransforms[hook] = transform;
5301		allTransformsByHook[hook] = [];
5302
5303		return transform;
5304	};
5305
5306	Seriously.removeTransform = function (hook) {
5307		var all, transform, plugin;
5308
5309		if (!hook) {
5310			return this;
5311		}
5312
5313		plugin = seriousTransforms[hook];
5314
5315		if (!plugin) {
5316			return this;
5317		}
5318
5319		all = allTransformsByHook[hook];
5320		if (all) {
5321			while (all.length) {
5322				transform = all.shift();
5323				transform.destroy();
5324			}
5325			delete allTransformsByHook[hook];
5326		}
5327
5328		delete seriousTransforms[hook];
5329
5330		return this;
5331	};
5332
5333	Seriously.target = function (hook, definition, meta) {
5334		var target;
5335
5336		if (seriousTargets[hook]) {
5337			Seriously.logger.warn('Target [' + hook + '] already loaded');
5338			return;
5339		}
5340
5341		if (meta === undefined && typeof definition === 'object') {
5342			meta = definition;
5343		}
5344
5345		if (!meta && !definition) {
5346			return;
5347		}
5348
5349		target = extend({}, meta);
5350
5351		if (typeof definition === 'function') {
5352			target.definition = definition;
5353		}
5354
5355		if (!target.title) {
5356			target.title = hook;
5357		}
5358
5359
5360		seriousTargets[hook] = target;
5361		allTargetsByHook[hook] = [];
5362
5363		return target;
5364	};
5365
5366	Seriously.removeTarget = function (hook) {
5367		var all, target, plugin;
5368
5369		if (!hook) {
5370			return this;
5371		}
5372
5373		plugin = seriousTargets[hook];
5374
5375		if (!plugin) {
5376			return this;
5377		}
5378
5379		all = allTargetsByHook[hook];
5380		if (all) {
5381			while (all.length) {
5382				target = all.shift();
5383				target.destroy();
5384			}
5385			delete allTargetsByHook[hook];
5386		}
5387
5388		delete seriousTargets[hook];
5389
5390		return this;
5391	};
5392
5393	//todo: validators should not allocate new objects/arrays if input is valid
5394	Seriously.inputValidators = {
5395		color: function (value, input, defaultValue, oldValue) {
5396			var s,
5397				a,
5398				match,
5399				i;
5400
5401			a = oldValue || [];
5402
5403			if (typeof value === 'string') {
5404				//todo: support percentages, decimals
5405				match = colorRegex.exec(value);
5406				if (match && match.length) {
5407					if (match.length < 3) {
5408						a[0] = a[1] = a[2] = a[3] = 0;
5409						return a;
5410					}
5411
5412					a[3] = 1;
5413					for (i = 0; i < 3; i++) {
5414						a[i] = parseFloat(match[i + 2]) / 255;
5415					}
5416					if (!isNaN(match[6])) {
5417						a[3] = parseFloat(match[6]);
5418					}
5419					if (match[1].toLowerCase() === 'hsl') {
5420						return hslToRgb(a[0], a[1], a[2], a[3], a);
5421					}
5422
5423					return a;
5424				}
5425
5426				match = hexColorRegex.exec(value);
5427				if (match && match.length) {
5428					s = match[1];
5429					if (s.length === 3) {
5430						a[0] = parseInt(s[0], 16) / 15;
5431						a[1] = parseInt(s[1], 16) / 15;
5432						a[2] = parseInt(s[2], 16) / 15;
5433						a[3] = 1;
5434					} else if (s.length === 4) {
5435						a[0] = parseInt(s[0], 16) / 15;
5436						a[1] = parseInt(s[1], 16) / 15;
5437						a[2] = parseInt(s[2], 16) / 15;
5438						a[3] = parseInt(s[3], 16) / 15;
5439					} else if (s.length === 6) {
5440						a[0] = parseInt(s.substr(0, 2), 16) / 255;
5441						a[1] = parseInt(s.substr(2, 2), 16) / 255;
5442						a[2] = parseInt(s.substr(4, 2), 16) / 255;
5443						a[3] = 1;
5444					} else if (s.length === 8) {
5445						a[0] = parseInt(s.substr(0, 2), 16) / 255;
5446						a[1] = parseInt(s.substr(2, 2), 16) / 255;
5447						a[2] = parseInt(s.substr(4, 2), 16) / 255;
5448						a[3] = parseInt(s.substr(6, 2), 16) / 255;
5449					} else {
5450						a[0] = a[1] = a[2] = a[3] = 0;
5451					}
5452					return a;
5453				}
5454
5455				match = colorNames[value.toLowerCase()];
5456				if (match) {
5457					for (i = 0; i < 4; i++) {
5458						a[i] = match[i];
5459					}
5460					return a;
5461				}
5462
5463				if (!colorCtx) {
5464					colorCtx = document.createElement('canvas').getContext('2d');
5465				}
5466				colorCtx.fillStyle = value;
5467				s = colorCtx.fillStyle;
5468				if (s && s !== '#000000') {
5469					return Seriously.inputValidators.color(s, input, defaultValue, oldValue);
5470				}
5471
5472				a[0] = a[1] = a[2] = a[3] = 0;
5473				return a;
5474			}
5475
5476			if (isArrayLike(value)) {
5477				a = value;
5478				if (a.length < 3) {
5479					a[0] = a[1] = a[2] = a[3] = 0;
5480					return a;
5481				}
5482				for (i = 0; i < 3; i++) {
5483					if (isNaN(a[i])) {
5484						a[0] = a[1] = a[2] = a[3] = 0;
5485						return a;
5486					}
5487				}
5488				if (a.length < 4) {
5489					a.push(1);
5490				}
5491				return a;
5492			}
5493
5494			if (typeof value === 'number') {
5495				a[0] = a[1] = a[2] = value;
5496				a[3] = 1;
5497				return a;
5498			}
5499
5500			if (typeof value === 'object') {
5501				for (i = 0; i < 4; i++) {
5502					s = colorFields[i];
5503					if (value[s] === null || isNaN(value[s])) {
5504						a[i] = i === 3 ? 1 : 0;
5505					} else {
5506						a[i] = value[s];
5507					}
5508				}
5509				return a;
5510			}
5511
5512			a[0] = a[1] = a[2] = a[3] = 0;
5513			return a;
5514		},
5515		number: function (value, input, defaultValue) {
5516			value = parseFloat(value);
5517
5518			if (isNaN(value)) {
5519				return defaultValue || 0;
5520			}
5521
5522			if (input.mod) {
5523				value = value - input.mod * Math.floor(value / input.mod);
5524			}
5525
5526			if (value < input.min) {
5527				return input.min;
5528			}
5529
5530			if (value > input.max) {
5531				return input.max;
5532			}
5533
5534			if (input.step) {
5535				return Math.round(value / input.step) * input.step;
5536			}
5537
5538			return value;
5539		},
5540		'enum': function (value, input, defaultValue) {
5541			var options = input.options || [],
5542				i,
5543				opt;
5544
5545			if (typeof value === 'string') {
5546				value = value.toLowerCase();
5547			} else if (typeof value === 'number') {
5548				value = value.toString();
5549			} else if (!value) {
5550				value = '';
5551			}
5552
5553			if (options.hasOwnProperty(value)) {
5554				return value;
5555			}
5556
5557			return defaultValue || '';
5558		},
5559		vector: function (value, input, defaultValue, oldValue) {
5560			var a, i, s, n = input.dimensions || 4;
5561
5562			a = oldValue || [];
5563			if (isArrayLike(value)) {
5564				for (i = 0; i < n; i++) {
5565					a[i] = value[i] || 0;
5566				}
5567				return a;
5568			}
5569
5570			if (typeof value === 'object') {
5571				for (i = 0; i < n; i++) {
5572					s = vectorFields[i];
5573					if (value[s] === undefined) {
5574						s = colorFields[i];
5575					}
5576					a[i] = value[s] || 0;
5577				}
5578				return a;
5579			}
5580
5581			value = parseFloat(value) || 0;
5582			for (i = 0; i < n; i++) {
5583				a[i] = value;
5584			}
5585
5586			return a;
5587		},
5588		'boolean': function (value) {
5589			if (!value) {
5590				return false;
5591			}
5592
5593			if (value && value.toLowerCase && value.toLowerCase() === 'false') {
5594				return false;
5595			}
5596
5597			return true;
5598		},
5599		'string': function (value) {
5600			if (typeof value === 'string') {
5601				return value;
5602			}
5603
5604			if (value !== 0 && !value) {
5605				return '';
5606			}
5607
5608			if (value.toString) {
5609				return value.toString();
5610			}
5611
5612			return String(value);
5613		}
5614		//todo: date/time
5615	};
5616
5617	Seriously.prototype.effects = Seriously.effects = function () {
5618		var name,
5619			effect,
5620			manifest,
5621			effects = {},
5622			input,
5623			i;
5624
5625		for (name in seriousEffects) {
5626			if (seriousEffects.hasOwnProperty(name)) {
5627				effect = seriousEffects[name];
5628				manifest = {
5629					title: effect.title || name,
5630					description: effect.description || '',
5631					inputs: {}
5632				};
5633
5634				for (i in effect.inputs) {
5635					if (effect.inputs.hasOwnProperty(i)) {
5636						input = effect.inputs[i];
5637						manifest.inputs[i] = {
5638							type: input.type,
5639							defaultValue: input.defaultValue,
5640							step: input.step,
5641							min: input.min,
5642							max: input.max,
5643							mod: input.mod,
5644							minCount: input.minCount,
5645							maxCount: input.maxCount,
5646							dimensions: input.dimensions,
5647							title: input.title || i,
5648							description: input.description || '',
5649							options: input.options || []
5650						};
5651					}
5652				}
5653
5654				effects[name] = manifest;
5655			}
5656		}
5657
5658		return effects;
5659	};
5660
5661	if (window.Float32Array) {
5662		identity = new Float32Array([
5663			1, 0, 0, 0,
5664			0, 1, 0, 0,
5665			0, 0, 1, 0,
5666			0, 0, 0, 1
5667		]);
5668	}
5669
5670	//check for plugins loaded out of order
5671	if (window.Seriously) {
5672		if (typeof window.Seriously === 'object') {
5673			(function () {
5674				var i;
5675				for (i in window.Seriously) {
5676					if (window.Seriously.hasOwnProperty(i) &&
5677						i !== 'plugin' &&
5678						typeof window.Seriously[i] === 'object') {
5679
5680						Seriously.plugin(i, window.Seriously[i]);
5681					}
5682				}
5683			}());
5684		}
5685	}
5686
5687	/*
5688
5689	*/
5690	Seriously.logger = {
5691		log: consoleMethod('log'),
5692		info: consoleMethod('info'),
5693		warn: consoleMethod('warn'),
5694		error: consoleMethod('error')
5695	};
5696
5697	Seriously.util = {
5698		mat4: mat4,
5699		checkSource: checkSource,
5700		hslToRgb: hslToRgb,
5701		colors: colorNames,
5702		setTimeoutZero: setTimeoutZero,
5703		ShaderProgram: ShaderProgram,
5704		FrameBuffer: FrameBuffer,
5705		requestAnimationFrame: requestAnimationFrame,
5706		shader: {
5707			makeNoise: 'float makeNoise(float u, float v, float timer) {\n' +
5708						'	float x = u * v * mod(timer * 1000.0, 100.0);\n' +
5709						'	x = mod(x, 13.0) * mod(x, 127.0);\n' +
5710						'	float dx = mod(x, 0.01);\n' +
5711						'	return clamp(0.1 + dx * 100.0, 0.0, 1.0);\n' +
5712						'}\n',
5713			random: '#ifndef RANDOM\n' +
5714				'#define RANDOM\n' +
5715				'float random(vec2 n) {\n' +
5716				'	return 0.5 + 0.5 * fract(sin(dot(n.xy, vec2(12.9898, 78.233)))* 43758.5453);\n' +
5717				'}\n' +
5718				'#endif\n'
5719		}
5720	};
5721
5722	Seriously.source('video', function (video, options, force) {
5723		var me = this,
5724			key,
5725			opts,
5726
5727			canvas,
5728			ctx2d,
5729
5730			destroyed = false,
5731			deferTexture = false,
5732
5733			isSeeking = false,
5734			lastRenderTime = 0;
5735
5736		function initializeVideo() {
5737			video.removeEventListener('loadedmetadata', initializeVideo, true);
5738
5739			if (destroyed) {
5740				return;
5741			}
5742
5743			if (video.videoWidth) {
5744				if (me.width !== video.videoWidth || me.height !== video.videoHeight) {
5745					me.width = video.videoWidth;
5746					me.height = video.videoHeight;
5747					me.resize();
5748				}
5749
5750				if (deferTexture) {
5751					me.setReady();
5752				}
5753			} else {
5754				//Workaround for Firefox bug https://bugzilla.mozilla.org/show_bug.cgi?id=926753
5755				deferTexture = true;
5756				setTimeout(initializeVideo, 50);
5757			}
5758		}
5759
5760		function seeking() {
5761			// IE doesn't report .seeking properly so make our own
5762			isSeeking = true;
5763		}
5764
5765		function seeked() {
5766			isSeeking = false;
5767			me.setDirty();
5768		}
5769
5770		if (isInstance(video, 'HTMLVideoElement')) {
5771			if (video.readyState) {
5772				initializeVideo();
5773			} else {
5774				deferTexture = true;
5775				video.addEventListener('loadedmetadata', initializeVideo, true);
5776			}
5777
5778			video.addEventListener('seeking', seeking, false);
5779			video.addEventListener('seeked', seeked, false);
5780
5781			return {
5782				deferTexture: deferTexture,
5783				source: video,
5784				render: function renderVideo(gl) {
5785					var source,
5786						error;
5787
5788					lastRenderTime = video.currentTime;
5789
5790					if (!video.videoHeight || !video.videoWidth) {
5791						return false;
5792					}
5793
5794					if (noVideoTextureSupport) {
5795						if (!ctx2d) {
5796							ctx2d = document.createElement('canvas').getContext('2d');
5797							canvas = ctx2d.canvas;
5798							canvas.width = me.width;
5799							canvas.height = me.height;
5800						}
5801						source = canvas;
5802						ctx2d.drawImage(video, 0, 0, me.width, me.height);
5803					} else {
5804						source = video;
5805					}
5806
5807					gl.bindTexture(gl.TEXTURE_2D, me.texture);
5808					gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, me.flip);
5809					gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
5810					try {
5811						gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source);
5812
5813						//workaround for lack of video texture support in IE
5814						if (noVideoTextureSupport === undefined) {
5815							error = gl.getError();
5816							if (error === gl.INVALID_VALUE) {
5817								noVideoTextureSupport = true;
5818								return renderVideo(gl);
5819							}
5820							noVideoTextureSupport = false;
5821						}
5822						return true;
5823					} catch (securityError) {
5824						if (securityError.code === window.DOMException.SECURITY_ERR) {
5825							me.allowRefresh = false;
5826							Seriously.logger.error('Unable to access cross-domain image');
5827						} else {
5828							Seriously.logger.error('Error rendering video source', securityError);
5829						}
5830					}
5831					return false;
5832				},
5833				checkDirty: function () {
5834					return !isSeeking && video.currentTime !== lastRenderTime;
5835				},
5836				compare: function (source) {
5837					return me.source === source;
5838				},
5839				destroy: function () {
5840					destroyed = true;
5841					video.removeEventListener('seeking', seeking, false);
5842					video.removeEventListener('seeked', seeked, false);
5843					video.removeEventListener('loadedmetadata', initializeVideo, true);
5844				}
5845			};
5846		}
5847	}, {
5848		title: 'Video'
5849	});
5850
5851	/*
5852	Default transform - 2D
5853	Affine transforms
5854	- translate
5855	- rotate (degrees)
5856	- scale
5857	- skew
5858
5859	todo: move this to a different file when we have a build tool
5860	*/
5861	Seriously.transform('2d', function (options) {
5862		var me = this,
5863			degrees = !(options && options.radians),
5864
5865			centerX = 0,
5866			centerY = 0,
5867			scaleX = 1,
5868			scaleY = 1,
5869			translateX = 0,
5870			translateY = 0,
5871			rotation = 0,
5872			skewX = 0,
5873			skewY = 0;
5874
5875		//todo: skew order
5876		//todo: invert?
5877
5878		function recompute() {
5879			var matrix = me.matrix,
5880				angle,
5881				s, c,
5882				m00,
5883				m01,
5884				m02,
5885				m03,
5886				m10,
5887				m11,
5888				m12,
5889				m13;
5890
5891			function translate(x, y) {
5892				matrix[12] = matrix[0] * x + matrix[4] * y + matrix[12];
5893				matrix[13] = matrix[1] * x + matrix[5] * y + matrix[13];
5894				matrix[14] = matrix[2] * x + matrix[6] * y + matrix[14];
5895				matrix[15] = matrix[3] * x + matrix[7] * y + matrix[15];
5896			}
5897
5898			if (!translateX &&
5899					!translateY &&
5900					!rotation &&
5901					!skewX &&
5902					!skewY &&
5903					scaleX === 1 &&
5904					scaleY === 1
5905					) {
5906				me.transformed = false;
5907				return;
5908			}
5909
5910			//calculate transformation matrix
5911			mat4.identity(matrix);
5912
5913			translate(translateX + centerX, translateY + centerY);
5914
5915			//skew
5916			if (skewX) {
5917				matrix[4] = skewX / me.width;
5918			}
5919			if (skewY) {
5920				matrix[1] = skewY / me.height;
5921			}
5922
5923			if (rotation) {
5924				m00 = matrix[0];
5925				m01 = matrix[1];
5926				m02 = matrix[2];
5927				m03 = matrix[3];
5928				m10 = matrix[4];
5929				m11 = matrix[5];
5930				m12 = matrix[6];
5931				m13 = matrix[7];
5932
5933				//rotate
5934				angle = -(degrees ? rotation * Math.PI / 180 : rotation);
5935				//...rotate
5936				s = Math.sin(angle);
5937				c = Math.cos(angle);
5938				matrix[0] = m00 * c + m10 * s;
5939				matrix[1] = m01 * c + m11 * s;
5940				matrix[2] = m02 * c + m12 * s;
5941				matrix[3] = m03 * c + m13 * s;
5942				matrix[4] = m10 * c - m00 * s;
5943				matrix[5] = m11 * c - m01 * s;
5944				matrix[6] = m12 * c - m02 * s;
5945				matrix[7] = m13 * c - m03 * s;
5946			}
5947
5948			//scale
5949			if (scaleX !== 1) {
5950				matrix[0] *= scaleX;
5951				matrix[1] *= scaleX;
5952				matrix[2] *= scaleX;
5953				matrix[3] *= scaleX;
5954			}
5955			if (scaleY !== 1) {
5956				matrix[4] *= scaleY;
5957				matrix[5] *= scaleY;
5958				matrix[6] *= scaleY;
5959				matrix[7] *= scaleY;
5960			}
5961
5962			translate(-centerX, -centerY);
5963
5964			me.transformed = true;
5965		}
5966
5967		return {
5968			inputs: {
5969				reset: {
5970					method: function () {
5971						centerX = 0;
5972						centerY = 0;
5973						scaleX = 1;
5974						scaleY = 1;
5975						translateX = 0;
5976						translateY = 0;
5977						rotation = 0;
5978						skewX = 0;
5979						skewY = 0;
5980
5981						if (me.transformed) {
5982							me.transformed = false;
5983							return true;
5984						}
5985
5986						return false;
5987					}
5988				},
5989				translate: {
5990					method: function (x, y) {
5991						if (isNaN(x)) {
5992							x = translateX;
5993						}
5994
5995						if (isNaN(y)) {
5996							y = translateY;
5997						}
5998
5999						if (x === translateX && y === translateY) {
6000							return false;
6001						}
6002
6003						translateX = x;
6004						translateY = y;
6005
6006						recompute();
6007						return true;
6008					},
6009					type: [
6010						'number',
6011						'number'
6012					]
6013				},
6014				translateX: {
6015					get: function () {
6016						return translateX;
6017					},
6018					set: function (x) {
6019						if (x === translateX) {
6020							return false;
6021						}
6022
6023						translateX = x;
6024
6025						recompute();
6026						return true;
6027					},
6028					type: 'number'
6029				},
6030				translateY: {
6031					get: function () {
6032						return translateY;
6033					},
6034					set: function (y) {
6035						if (y === translateY) {
6036							return false;
6037						}
6038
6039						translateY = y;
6040
6041						recompute();
6042						return true;
6043					},
6044					type: 'number'
6045				},
6046				rotation: {
6047					get: function () {
6048						return rotation;
6049					},
6050					set: function (angle) {
6051						if (angle === rotation) {
6052							return false;
6053						}
6054
6055						//todo: fmod 360deg or Math.PI * 2 radians
6056						rotation = parseFloat(angle);
6057
6058						recompute();
6059						return true;
6060					},
6061					type: 'number'
6062				},
6063				center: {
6064					method: function (x, y) {
6065						if (isNaN(x)) {
6066							x = centerX;
6067						}
6068
6069						if (isNaN(y)) {
6070							y = centerY;
6071						}
6072
6073						if (x === centerX && y === centerY) {
6074							return false;
6075						}
6076
6077						centerX = x;
6078						centerY = y;
6079
6080						recompute();
6081						return true;
6082					},
6083					type: [
6084						'number',
6085						'number'
6086					]
6087				},
6088				centerX: {
6089					get: function () {
6090						return centerX;
6091					},
6092					set: function (x) {
6093						if (x === centerX) {
6094							return false;
6095						}
6096
6097						centerX = x;
6098
6099						recompute();
6100						return true;
6101					},
6102					type: 'number'
6103				},
6104				centerY: {
6105					get: function () {
6106						return centerY;
6107					},
6108					set: function (y) {
6109						if (y === centerY) {
6110							return false;
6111						}
6112
6113						centerY = y;
6114
6115						recompute();
6116						return true;
6117					},
6118					type: 'number'
6119				},
6120				skew: {
6121					method: function (x, y) {
6122						if (isNaN(x)) {
6123							x = skewX;
6124						}
6125
6126						if (isNaN(y)) {
6127							y = skewY;
6128						}
6129
6130						if (x === skewX && y === skewY) {
6131							return false;
6132						}
6133
6134						skewX = x;
6135						skewY = y;
6136
6137						recompute();
6138						return true;
6139					},
6140					type: [
6141						'number',
6142						'number'
6143					]
6144				},
6145				skewX: {
6146					get: function () {
6147						return skewX;
6148					},
6149					set: function (x) {
6150						if (x === skewX) {
6151							return false;
6152						}
6153
6154						skewX = x;
6155
6156						recompute();
6157						return true;
6158					},
6159					type: 'number'
6160				},
6161				skewY: {
6162					get: function () {
6163						return skewY;
6164					},
6165					set: function (y) {
6166						if (y === skewY) {
6167							return false;
6168						}
6169
6170						skewY = y;
6171
6172						recompute();
6173						return true;
6174					},
6175					type: 'number'
6176				},
6177				scale: {
6178					method: function (x, y) {
6179						var newX, newY;
6180
6181						if (isNaN(x)) {
6182							newX = scaleX;
6183						} else {
6184							newX = x;
6185						}
6186
6187						/*
6188						if only one value is specified, set both x and y to the same scale
6189						*/
6190						if (isNaN(y)) {
6191							if (isNaN(x)) {
6192								return false;
6193							}
6194
6195							newY = newX;
6196						} else {
6197							newY = y;
6198						}
6199
6200						if (newX === scaleX && newY === scaleY) {
6201							return false;
6202						}
6203
6204						scaleX = newX;
6205						scaleY = newY;
6206
6207						recompute();
6208						return true;
6209					},
6210					type: [
6211						'number',
6212						'number'
6213					]
6214				},
6215				scaleX: {
6216					get: function () {
6217						return scaleX;
6218					},
6219					set: function (x) {
6220						if (x === scaleX) {
6221							return false;
6222						}
6223
6224						scaleX = x;
6225
6226						recompute();
6227						return true;
6228					},
6229					type: 'number'
6230				},
6231				scaleY: {
6232					get: function () {
6233						return scaleY;
6234					},
6235					set: function (y) {
6236						if (y === scaleY) {
6237							return false;
6238						}
6239
6240						scaleY = y;
6241
6242						recompute();
6243						return true;
6244					},
6245					type: 'number'
6246				}
6247			}
6248		};
6249	}, {
6250		title: '2D Transform',
6251		description: 'Translate, Rotate, Scale, Skew'
6252	});
6253
6254	/*
6255	todo: move this to a different file when we have a build tool
6256	*/
6257	Seriously.transform('flip', function () {
6258		var me = this,
6259			horizontal = true;
6260
6261		function recompute() {
6262			var matrix = me.matrix;
6263
6264			//calculate transformation matrix
6265			//mat4.identity(matrix);
6266
6267			//scale
6268			if (horizontal) {
6269				matrix[0] = -1;
6270				matrix[5] = 1;
6271			} else {
6272				matrix[0] = 1;
6273				matrix[5] = -1;
6274			}
6275		}
6276
6277		mat4.identity(me.matrix);
6278		recompute();
6279
6280		me.transformDirty = true;
6281
6282		me.transformed = true;
6283
6284		return {
6285			inputs: {
6286				direction: {
6287					get: function () {
6288						return horizontal ? 'horizontal' : 'vertical';
6289					},
6290					set: function (d) {
6291						var horiz;
6292						if (d === 'vertical') {
6293							horiz = false;
6294						} else {
6295							horiz = true;
6296						}
6297
6298						if (horiz === horizontal) {
6299							return false;
6300						}
6301
6302						horizontal = horiz;
6303						recompute();
6304						return true;
6305					},
6306					type: 'string'
6307				}
6308			}
6309		};
6310	}, {
6311		title: 'Flip',
6312		description: 'Flip Horizontal/Vertical'
6313	});
6314
6315	/*
6316	Reformat
6317	todo: move this to a different file when we have a build tool
6318	*/
6319	Seriously.transform('reformat', function () {
6320		var me = this,
6321			forceWidth,
6322			forceHeight,
6323			mode = 'contain';
6324
6325		function recompute() {
6326			var matrix = me.matrix,
6327				width = forceWidth || me.width,
6328				height = forceHeight || me.height,
6329				scaleX,
6330				scaleY,
6331				source = me.source,
6332				sourceWidth = source && source.width || 1,
6333				sourceHeight = source && source.height || 1,
6334				aspectIn,
6335				aspectOut;
6336
6337			if (mode === 'distort' || width === sourceWidth && height === sourceHeight) {
6338				me.transformed = false;
6339				return;
6340			}
6341
6342			aspectIn = sourceWidth / sourceHeight;
6343
6344			aspectOut = width / height;
6345
6346			if (mode === 'none') {
6347				scaleX = sourceWidth / width;
6348				scaleY = sourceHeight / height;
6349			} else if (mode === 'width' || mode === 'contain' && aspectOut <= aspectIn) {
6350				scaleX = 1;
6351				scaleY = aspectOut / aspectIn;
6352			} else if (mode === 'height' || mode === 'contain' && aspectOut > aspectIn) {
6353				scaleX = aspectIn / aspectOut;
6354				scaleY = 1;
6355			} else {
6356				//mode === 'cover'
6357				if (aspectOut > aspectIn) {
6358					scaleX = 1;
6359					scaleY = aspectOut / aspectIn;
6360				} else {
6361					scaleX = aspectIn / aspectOut;
6362					scaleY = 1;
6363				}
6364			}
6365
6366			if (scaleX === 1 && scaleY === 1) {
6367				me.transformed = false;
6368				return;
6369			}
6370
6371			//calculate transformation matrix
6372			mat4.identity(matrix);
6373
6374			//scale
6375			if (scaleX !== 1) {
6376				matrix[0] *= scaleX;
6377				matrix[1] *= scaleX;
6378				matrix[2] *= scaleX;
6379				matrix[3] *= scaleX;
6380			}
6381			if (scaleY !== 1) {
6382				matrix[4] *= scaleY;
6383				matrix[5] *= scaleY;
6384				matrix[6] *= scaleY;
6385				matrix[7] *= scaleY;
6386			}
6387			me.transformed = true;
6388		}
6389
6390		function getWidth() {
6391			return forceWidth || me.source && me.source.width || 1;
6392		}
6393
6394		function getHeight() {
6395			return forceHeight || me.source && me.source.height || 1;
6396		}
6397
6398		this.resize = function () {
6399			var width = getWidth(),
6400				height = getHeight(),
6401				i;
6402
6403			if (this.width !== width || this.height !== height) {
6404				this.width = width;
6405				this.height = height;
6406
6407				if (this.uniforms && this.uniforms.resolution) {
6408					this.uniforms.resolution[0] = width;
6409					this.uniforms.resolution[1] = height;
6410				}
6411
6412				if (this.frameBuffer && this.frameBuffer.resize) {
6413					this.frameBuffer.resize(width, height);
6414				}
6415
6416				for (i = 0; i < this.targets.length; i++) {
6417					this.targets[i].resize();
6418				}
6419			}
6420
6421			this.setTransformDirty();
6422
6423			recompute();
6424		};
6425
6426		return {
6427			inputs: {
6428				width: {
6429					get: getWidth,
6430					set: function (x) {
6431						x = Math.floor(x);
6432						if (x === forceWidth) {
6433							return false;
6434						}
6435
6436						forceWidth = x;
6437
6438						this.resize();
6439
6440						//don't need to run setTransformDirty again
6441						return false;
6442					},
6443					type: 'number'
6444				},
6445				height: {
6446					get: getHeight,
6447					set: function (y) {
6448						y = Math.floor(y);
6449						if (y === forceHeight) {
6450							return false;
6451						}
6452
6453						forceHeight = y;
6454
6455						this.resize();
6456
6457						//don't need to run setTransformDirty again
6458						return false;
6459					},
6460					type: 'number'
6461				},
6462				mode: {
6463					get: function () {
6464						return mode;
6465					},
6466					set: function (m) {
6467						if (m === mode) {
6468							return false;
6469						}
6470
6471						mode = m;
6472
6473						recompute();
6474						return true;
6475					},
6476					type: 'enum',
6477					options: [
6478						'cover',
6479						'contain',
6480						'distort',
6481						'width',
6482						'height',
6483						'none'
6484					]
6485				}
6486			}
6487		};
6488	}, {
6489		title: 'Reformat',
6490		description: 'Change output dimensions'
6491	});
6492
6493	/*
6494	todo: additional transform node types
6495	- perspective
6496	- matrix
6497	*/
6498
6499	baseVertexShader = [
6500		'precision mediump float;',
6501
6502		'attribute vec4 position;',
6503		'attribute vec2 texCoord;',
6504
6505		'uniform vec2 resolution;',
6506		'uniform mat4 transform;',
6507
6508		'varying vec2 vTexCoord;',
6509
6510		'void main(void) {',
6511		// first convert to screen space
6512		'	vec4 screenPosition = vec4(position.xy * resolution / 2.0, position.z, position.w);',
6513		'	screenPosition = transform * screenPosition;',
6514
6515		// convert back to OpenGL coords
6516		'	gl_Position.xy = screenPosition.xy * 2.0 / resolution;',
6517		'	gl_Position.z = screenPosition.z * 2.0 / (resolution.x / resolution.y);',
6518		'	gl_Position.w = screenPosition.w;',
6519		'	vTexCoord = texCoord;',
6520		'}\n'
6521	].join('\n');
6522
6523	baseFragmentShader = [
6524		'precision mediump float;',
6525
6526		'varying vec2 vTexCoord;',
6527
6528		'uniform sampler2D source;',
6529
6530		'void main(void) {',
6531		/*
6532		'	if (any(lessThan(vTexCoord, vec2(0.0))) || any(greaterThanEqual(vTexCoord, vec2(1.0)))) {',
6533		'		gl_FragColor = vec4(0.0);',
6534		'	} else {',
6535		*/
6536		'		gl_FragColor = texture2D(source, vTexCoord);',
6537		//'	}',
6538		'}'
6539	].join('\n');
6540
6541	/*
6542	 * simplex noise shaders
6543	 * https://github.com/ashima/webgl-noise
6544	 * Copyright (C) 2011 by Ashima Arts (Simplex noise)
6545	 * Copyright (C) 2011 by Stefan Gustavson (Classic noise)
6546	 */
6547
6548	Seriously.util.shader.noiseHelpers = '#ifndef NOISE_HELPERS\n' +
6549		'#define NOISE_HELPERS\n' +
6550		'vec2 mod289(vec2 x) {\n' +
6551		'	return x - floor(x * (1.0 / 289.0)) * 289.0;\n' +
6552		'}\n' +
6553		'vec3 mod289(vec3 x) {\n' +
6554		'	return x - floor(x * (1.0 / 289.0)) * 289.0;\n' +
6555		'}\n' +
6556		'vec4 mod289(vec4 x) {\n' +
6557		'	return x - floor(x * (1.0 / 289.0)) * 289.0;\n' +
6558		'}\n' +
6559		'vec3 permute(vec3 x) {\n' +
6560		'	return mod289(((x*34.0)+1.0)*x);\n' +
6561		'}\n' +
6562		'vec4 permute(vec4 x) {\n' +
6563		'	return mod289(((x*34.0)+1.0)*x);\n' +
6564		'}\n' +
6565		'vec4 taylorInvSqrt(vec4 r) {\n' +
6566		'	return 1.79284291400159 - 0.85373472095314 * r;\n' +
6567		'}\n' +
6568		'float taylorInvSqrt(float r) {\n' +
6569		'	return 1.79284291400159 - 0.85373472095314 * r;\n' +
6570		'}\n' +
6571		'#endif\n';
6572
6573	Seriously.util.shader.snoise2d = '#ifndef NOISE2D\n' +
6574		'#define NOISE2D\n' +
6575		'float snoise(vec2 v) {\n' +
6576		'	const vec4 C = vec4(0.211324865405187, // (3.0-sqrt(3.0))/6.0\n' +
6577		'		0.366025403784439, // 0.5*(sqrt(3.0)-1.0)\n' +
6578		'		-0.577350269189626, // -1.0 + 2.0 * C.x\n' +
6579		'		0.024390243902439); // 1.0 / 41.0\n' +
6580		'	vec2 i = floor(v + dot(v, C.yy));\n' +
6581		'	vec2 x0 = v - i + dot(i, C.xx);\n' +
6582		'	vec2 i1;\n' +
6583		'	//i1.x = step(x0.y, x0.x); // x0.x > x0.y ? 1.0 : 0.0\n' +
6584		'	//i1.y = 1.0 - i1.x;\n' +
6585		'	i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);\n' +
6586		'	// x0 = x0 - 0.0 + 0.0 * C.xx ;\n' +
6587		'	// x1 = x0 - i1 + 1.0 * C.xx ;\n' +
6588		'	// x2 = x0 - 1.0 + 2.0 * C.xx ;\n' +
6589		'	vec4 x12 = x0.xyxy + C.xxzz;\n' +
6590		'	x12.xy -= i1;\n' +
6591		'	i = mod289(i); // Avoid truncation effects in permutation\n' +
6592		'	vec3 p = permute(permute(i.y + vec3(0.0, i1.y, 1.0)) + i.x + vec3(0.0, i1.x, 1.0));\n' +
6593		'	vec3 m = max(0.5 - vec3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), 0.0);\n' +
6594		'	m = m*m ;\n' +
6595		'	m = m*m ;\n' +
6596		'	vec3 x = 2.0 * fract(p * C.www) - 1.0;\n' +
6597		'	vec3 h = abs(x) - 0.5;\n' +
6598		'	vec3 ox = floor(x + 0.5);\n' +
6599		'	vec3 a0 = x - ox;\n' +
6600		'	m *= 1.79284291400159 - 0.85373472095314 * (a0*a0 + h*h);\n' +
6601		'	vec3 g;\n' +
6602		'	g.x = a0.x * x0.x + h.x * x0.y;\n' +
6603		'	g.yz = a0.yz * x12.xz + h.yz * x12.yw;\n' +
6604		'	return 130.0 * dot(m, g);\n' +
6605		'}\n' +
6606		'#endif\n';
6607
6608	Seriously.util.shader.snoise3d = '#ifndef NOISE3D\n' +
6609		'#define NOISE3D\n' +
6610		'float snoise(vec3 v) {\n' +
6611		'	const vec2 C = vec2(1.0/6.0, 1.0/3.0) ;\n' +
6612		'	const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);\n' +
6613
6614		// First corner
6615		'	vec3 i = floor(v + dot(v, C.yyy));\n' +
6616		'	vec3 x0 = v - i + dot(i, C.xxx) ;\n' +
6617
6618		// Other corners
6619		'	vec3 g = step(x0.yzx, x0.xyz);\n' +
6620		'	vec3 l = 1.0 - g;\n' +
6621		'	vec3 i1 = min(g.xyz, l.zxy);\n' +
6622		'	vec3 i2 = max(g.xyz, l.zxy);\n' +
6623
6624		'	// x0 = x0 - 0.0 + 0.0 * C.xxx;\n' +
6625		'	// x1 = x0 - i1 + 1.0 * C.xxx;\n' +
6626		'	// x2 = x0 - i2 + 2.0 * C.xxx;\n' +
6627		'	// x3 = x0 - 1.0 + 3.0 * C.xxx;\n' +
6628		'	vec3 x1 = x0 - i1 + C.xxx;\n' +
6629		'	vec3 x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y\n' +
6630		'	vec3 x3 = x0 - D.yyy; // -1.0+3.0*C.x = -0.5 = -D.y\n' +
6631
6632		// Permutations
6633		'	i = mod289(i);\n' +
6634		'	vec4 p = permute(permute(permute(\n' +
6635		'						i.z + vec4(0.0, i1.z, i2.z, 1.0))\n' +
6636		'						+ i.y + vec4(0.0, i1.y, i2.y, 1.0))\n' +
6637		'						+ i.x + vec4(0.0, i1.x, i2.x, 1.0));\n' +
6638
6639		// Gradients: 7x7 points over a square, mapped onto an octahedron.
6640		// The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294)
6641		'	float n_ = 0.142857142857; // 1.0/7.0\n' +
6642		'	vec3 ns = n_ * D.wyz - D.xzx;\n' +
6643
6644		'	vec4 j = p - 49.0 * floor(p * ns.z * ns.z); // mod(p, 7 * 7)\n' +
6645
6646		'	vec4 x_ = floor(j * ns.z);\n' +
6647		'	vec4 y_ = floor(j - 7.0 * x_); // mod(j, N)\n' +
6648
6649		'	vec4 x = x_ * ns.x + ns.yyyy;\n' +
6650		'	vec4 y = y_ * ns.x + ns.yyyy;\n' +
6651		'	vec4 h = 1.0 - abs(x) - abs(y);\n' +
6652
6653		'	vec4 b0 = vec4(x.xy, y.xy);\n' +
6654		'	vec4 b1 = vec4(x.zw, y.zw);\n' +
6655
6656		'	//vec4 s0 = vec4(lessThan(b0, 0.0)) * 2.0 - 1.0;\n' +
6657		'	//vec4 s1 = vec4(lessThan(b1, 0.0)) * 2.0 - 1.0;\n' +
6658		'	vec4 s0 = floor(b0) * 2.0 + 1.0;\n' +
6659		'	vec4 s1 = floor(b1) * 2.0 + 1.0;\n' +
6660		'	vec4 sh = -step(h, vec4(0.0));\n' +
6661
6662		'	vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy ;\n' +
6663		'	vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww ;\n' +
6664
6665		'	vec3 p0 = vec3(a0.xy, h.x);\n' +
6666		'	vec3 p1 = vec3(a0.zw, h.y);\n' +
6667		'	vec3 p2 = vec3(a1.xy, h.z);\n' +
6668		'	vec3 p3 = vec3(a1.zw, h.w);\n' +
6669
6670		//Normalise gradients
6671		'	vec4 norm = taylorInvSqrt(vec4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3)));\n' +
6672		'	p0 *= norm.x;\n' +
6673		'	p1 *= norm.y;\n' +
6674		'	p2 *= norm.z;\n' +
6675		'	p3 *= norm.w;\n' +
6676
6677		// Mix final noise value
6678		'	vec4 m = max(0.6 - vec4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0);\n' +
6679		'	m = m * m;\n' +
6680		'	return 42.0 * dot(m*m, vec4(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3)));\n' +
6681		'}\n' +
6682		'#endif\n';
6683
6684	Seriously.util.shader.snoise4d = '#ifndef NOISE4D\n' +
6685		'#define NOISE4D\n' +
6686		'vec4 grad4(float j, vec4 ip)\n' +
6687		'	{\n' +
6688		'	const vec4 ones = vec4(1.0, 1.0, 1.0, -1.0);\n' +
6689		'	vec4 p, s;\n' +
6690		'\n' +
6691		'	p.xyz = floor(fract (vec3(j) * ip.xyz) * 7.0) * ip.z - 1.0;\n' +
6692		'	p.w = 1.5 - dot(abs(p.xyz), ones.xyz);\n' +
6693		'	s = vec4(lessThan(p, vec4(0.0)));\n' +
6694		'	p.xyz = p.xyz + (s.xyz*2.0 - 1.0) * s.www;\n' +
6695		'\n' +
6696		'	return p;\n' +
6697		'	}\n' +
6698		'\n' +
6699		// (sqrt(5) - 1)/4 = F4, used once below\n
6700		'#define F4 0.309016994374947451\n' +
6701		'\n' +
6702		'float snoise(vec4 v)\n' +
6703		'	{\n' +
6704		'	const vec4 C = vec4(0.138196601125011, // (5 - sqrt(5))/20 G4\n' +
6705		'						0.276393202250021, // 2 * G4\n' +
6706		'						0.414589803375032, // 3 * G4\n' +
6707		'						-0.447213595499958); // -1 + 4 * G4\n' +
6708		'\n' +
6709		// First corner
6710		'	vec4 i = floor(v + dot(v, vec4(F4)));\n' +
6711		'	vec4 x0 = v - i + dot(i, C.xxxx);\n' +
6712		'\n' +
6713		// Other corners
6714		'\n' +
6715		// Rank sorting originally contributed by Bill Licea-Kane, AMD (formerly ATI)
6716		'	vec4 i0;\n' +
6717		'	vec3 isX = step(x0.yzw, x0.xxx);\n' +
6718		'	vec3 isYZ = step(x0.zww, x0.yyz);\n' +
6719		// i0.x = dot(isX, vec3(1.0));
6720		'	i0.x = isX.x + isX.y + isX.z;\n' +
6721		'	i0.yzw = 1.0 - isX;\n' +
6722		// i0.y += dot(isYZ.xy, vec2(1.0));
6723		'	i0.y += isYZ.x + isYZ.y;\n' +
6724		'	i0.zw += 1.0 - isYZ.xy;\n' +
6725		'	i0.z += isYZ.z;\n' +
6726		'	i0.w += 1.0 - isYZ.z;\n' +
6727		'\n' +
6728			// i0 now contains the unique values 0, 1, 2, 3 in each channel
6729		'	vec4 i3 = clamp(i0, 0.0, 1.0);\n' +
6730		'	vec4 i2 = clamp(i0 - 1.0, 0.0, 1.0);\n' +
6731		'	vec4 i1 = clamp(i0 - 2.0, 0.0, 1.0);\n' +
6732		'\n' +
6733		'	vec4 x1 = x0 - i1 + C.xxxx;\n' +
6734		'	vec4 x2 = x0 - i2 + C.yyyy;\n' +
6735		'	vec4 x3 = x0 - i3 + C.zzzz;\n' +
6736		'	vec4 x4 = x0 + C.wwww;\n' +
6737		'\n' +
6738		// Permutations
6739		'	i = mod289(i);\n' +
6740		'	float j0 = permute(permute(permute(permute(i.w) + i.z) + i.y) + i.x);\n' +
6741		'	vec4 j1 = permute(permute(permute(permute (\n' +
6742		'					i.w + vec4(i1.w, i2.w, i3.w, 1.0))\n' +
6743		'					+ i.z + vec4(i1.z, i2.z, i3.z, 1.0))\n' +
6744		'					+ i.y + vec4(i1.y, i2.y, i3.y, 1.0))\n' +
6745		'					+ i.x + vec4(i1.x, i2.x, i3.x, 1.0));\n' +
6746		'\n' +
6747		// Gradients: 7x7x6 points over a cube, mapped onto a 4-cross polytope
6748		// 7*7*6 = 294, which is close to the ring size 17*17 = 289.
6749		'	vec4 ip = vec4(1.0/294.0, 1.0/49.0, 1.0/7.0, 0.0) ;\n' +
6750		'\n' +
6751		'	vec4 p0 = grad4(j0, ip);\n' +
6752		'	vec4 p1 = grad4(j1.x, ip);\n' +
6753		'	vec4 p2 = grad4(j1.y, ip);\n' +
6754		'	vec4 p3 = grad4(j1.z, ip);\n' +
6755		'	vec4 p4 = grad4(j1.w, ip);\n' +
6756		'\n' +
6757		// Normalise gradients
6758		'	vec4 norm = taylorInvSqrt(vec4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3)));\n' +
6759		'	p0 *= norm.x;\n' +
6760		'	p1 *= norm.y;\n' +
6761		'	p2 *= norm.z;\n' +
6762		'	p3 *= norm.w;\n' +
6763		'	p4 *= taylorInvSqrt(dot(p4, p4));\n' +
6764		'\n' +
6765		// Mix contributions from the five corners
6766		'	vec3 m0 = max(0.6 - vec3(dot(x0, x0), dot(x1, x1), dot(x2, x2)), 0.0);\n' +
6767		'	vec2 m1 = max(0.6 - vec2(dot(x3, x3), dot(x4, x4)), 0.0);\n' +
6768		'	m0 = m0 * m0;\n' +
6769		'	m1 = m1 * m1;\n' +
6770		'	return 49.0 * (dot(m0*m0, vec3(dot(p0, x0), dot(p1, x1), dot(p2, x2)))\n' +
6771		'							+ dot(m1*m1, vec2(dot(p3, x3), dot(p4, x4)))) ;\n' +
6772		'}\n' +
6773		'#endif\n';
6774
6775	return Seriously;
6776}));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.