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.