From c9136d5812b83acc993e18ea098f02e841915740 Mon Sep 17 00:00:00 2001 From: Stanisław Zagórowski Date: Mon, 16 Mar 2026 20:26:35 +0100 Subject: update glslcanvas to be served locally --- public/assets/GlslCanvas.js | 2064 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2064 insertions(+) create mode 100644 public/assets/GlslCanvas.js (limited to 'public/assets/GlslCanvas.js') diff --git a/public/assets/GlslCanvas.js b/public/assets/GlslCanvas.js new file mode 100644 index 0000000..ab29380 --- /dev/null +++ b/public/assets/GlslCanvas.js @@ -0,0 +1,2064 @@ +// MIT, from https://github.com/patriciogonzalezvivo/glslCanvas/blob/master/dist/GlslCanvas.js + +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global.GlslCanvas = factory()); +}(this, (function () { 'use strict'; + +var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; + +var win; + +if (typeof window !== "undefined") { + win = window; +} else if (typeof commonjsGlobal !== "undefined") { + win = commonjsGlobal; +} else if (typeof self !== "undefined"){ + win = self; +} else { + win = {}; +} + +var window_1 = win; + +var isFunction_1 = isFunction; + +var toString = Object.prototype.toString; + +function isFunction (fn) { + if (!fn) { + return false + } + var string = toString.call(fn); + return string === '[object Function]' || + (typeof fn === 'function' && string !== '[object RegExp]') || + (typeof window !== 'undefined' && + // IE8 and below + (fn === window.setTimeout || + fn === window.alert || + fn === window.confirm || + fn === window.prompt)) +} + +var trim = function(string) { + return string.replace(/^\s+|\s+$/g, ''); +}; +var isArray = function(arg) { + return Object.prototype.toString.call(arg) === '[object Array]'; + }; + +var parseHeaders = function (headers) { + if (!headers) + return {} + + var result = {}; + + var headersArr = trim(headers).split('\n'); + + for (var i = 0; i < headersArr.length; i++) { + var row = headersArr[i]; + var index = row.indexOf(':') + , key = trim(row.slice(0, index)).toLowerCase() + , value = trim(row.slice(index + 1)); + + if (typeof(result[key]) === 'undefined') { + result[key] = value; + } else if (isArray(result[key])) { + result[key].push(value); + } else { + result[key] = [ result[key], value ]; + } + } + + return result +}; + +var immutable = extend; + +var hasOwnProperty = Object.prototype.hasOwnProperty; + +function extend() { + var target = {}; + + for (var i = 0; i < arguments.length; i++) { + var source = arguments[i]; + + for (var key in source) { + if (hasOwnProperty.call(source, key)) { + target[key] = source[key]; + } + } + } + + return target +} + +"use strict"; + + + + + +var xhr = createXHR; +// Allow use of default import syntax in TypeScript +var default_1 = createXHR; +createXHR.XMLHttpRequest = window_1.XMLHttpRequest || noop; +createXHR.XDomainRequest = "withCredentials" in (new createXHR.XMLHttpRequest()) ? createXHR.XMLHttpRequest : window_1.XDomainRequest; + +forEachArray(["get", "put", "post", "patch", "head", "delete"], function(method) { + createXHR[method === "delete" ? "del" : method] = function(uri, options, callback) { + options = initParams(uri, options, callback); + options.method = method.toUpperCase(); + return _createXHR(options) + }; +}); + +function forEachArray(array, iterator) { + for (var i = 0; i < array.length; i++) { + iterator(array[i]); + } +} + +function isEmpty(obj){ + for(var i in obj){ + if(obj.hasOwnProperty(i)) return false + } + return true +} + +function initParams(uri, options, callback) { + var params = uri; + + if (isFunction_1(options)) { + callback = options; + if (typeof uri === "string") { + params = {uri:uri}; + } + } else { + params = immutable(options, {uri: uri}); + } + + params.callback = callback; + return params +} + +function createXHR(uri, options, callback) { + options = initParams(uri, options, callback); + return _createXHR(options) +} + +function _createXHR(options) { + if(typeof options.callback === "undefined"){ + throw new Error("callback argument missing") + } + + var called = false; + var callback = function cbOnce(err, response, body){ + if(!called){ + called = true; + options.callback(err, response, body); + } + }; + + function readystatechange() { + if (xhr.readyState === 4) { + setTimeout(loadFunc, 0); + } + } + + function getBody() { + // Chrome with requestType=blob throws errors arround when even testing access to responseText + var body = undefined; + + if (xhr.response) { + body = xhr.response; + } else { + body = xhr.responseText || getXml(xhr); + } + + if (isJson) { + try { + body = JSON.parse(body); + } catch (e) {} + } + + return body + } + + function errorFunc(evt) { + clearTimeout(timeoutTimer); + if(!(evt instanceof Error)){ + evt = new Error("" + (evt || "Unknown XMLHttpRequest Error") ); + } + evt.statusCode = 0; + return callback(evt, failureResponse) + } + + // will load the data & process the response in a special response object + function loadFunc() { + if (aborted) return + var status; + clearTimeout(timeoutTimer); + if(options.useXDR && xhr.status===undefined) { + //IE8 CORS GET successful response doesn't have a status field, but body is fine + status = 200; + } else { + status = (xhr.status === 1223 ? 204 : xhr.status); + } + var response = failureResponse; + var err = null; + + if (status !== 0){ + response = { + body: getBody(), + statusCode: status, + method: method, + headers: {}, + url: uri, + rawRequest: xhr + }; + if(xhr.getAllResponseHeaders){ //remember xhr can in fact be XDR for CORS in IE + response.headers = parseHeaders(xhr.getAllResponseHeaders()); + } + } else { + err = new Error("Internal XMLHttpRequest Error"); + } + return callback(err, response, response.body) + } + + var xhr = options.xhr || null; + + if (!xhr) { + if (options.cors || options.useXDR) { + xhr = new createXHR.XDomainRequest(); + }else{ + xhr = new createXHR.XMLHttpRequest(); + } + } + + var key; + var aborted; + var uri = xhr.url = options.uri || options.url; + var method = xhr.method = options.method || "GET"; + var body = options.body || options.data; + var headers = xhr.headers = options.headers || {}; + var sync = !!options.sync; + var isJson = false; + var timeoutTimer; + var failureResponse = { + body: undefined, + headers: {}, + statusCode: 0, + method: method, + url: uri, + rawRequest: xhr + }; + + if ("json" in options && options.json !== false) { + isJson = true; + headers["accept"] || headers["Accept"] || (headers["Accept"] = "application/json"); //Don't override existing accept header declared by user + if (method !== "GET" && method !== "HEAD") { + headers["content-type"] || headers["Content-Type"] || (headers["Content-Type"] = "application/json"); //Don't override existing accept header declared by user + body = JSON.stringify(options.json === true ? body : options.json); + } + } + + xhr.onreadystatechange = readystatechange; + xhr.onload = loadFunc; + xhr.onerror = errorFunc; + // IE9 must have onprogress be set to a unique function. + xhr.onprogress = function () { + // IE must die + }; + xhr.onabort = function(){ + aborted = true; + }; + xhr.ontimeout = errorFunc; + xhr.open(method, uri, !sync, options.username, options.password); + //has to be after open + if(!sync) { + xhr.withCredentials = !!options.withCredentials; + } + // Cannot set timeout with sync request + // not setting timeout on the xhr object, because of old webkits etc. not handling that correctly + // both npm's request and jquery 1.x use this kind of timeout, so this is being consistent + if (!sync && options.timeout > 0 ) { + timeoutTimer = setTimeout(function(){ + if (aborted) return + aborted = true;//IE9 may still call readystatechange + xhr.abort("timeout"); + var e = new Error("XMLHttpRequest timeout"); + e.code = "ETIMEDOUT"; + errorFunc(e); + }, options.timeout ); + } + + if (xhr.setRequestHeader) { + for(key in headers){ + if(headers.hasOwnProperty(key)){ + xhr.setRequestHeader(key, headers[key]); + } + } + } else if (options.headers && !isEmpty(options.headers)) { + throw new Error("Headers cannot be set on an XDomainRequest object") + } + + if ("responseType" in options) { + xhr.responseType = options.responseType; + } + + if ("beforeSend" in options && + typeof options.beforeSend === "function" + ) { + options.beforeSend(xhr); + } + + // Microsoft Edge browser sends "undefined" when send is called with undefined value. + // XMLHttpRequest spec says to pass null as body to indicate no body + // See https://github.com/naugtur/xhr/issues/100. + xhr.send(body || null); + + return xhr + + +} + +function getXml(xhr) { + // xhr.responseXML will throw Exception "InvalidStateError" or "DOMException" + // See https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseXML. + try { + if (xhr.responseType === "document") { + return xhr.responseXML + } + var firefoxBugTakenEffect = xhr.responseXML && xhr.responseXML.documentElement.nodeName === "parsererror"; + if (xhr.responseType === "" && !firefoxBugTakenEffect) { + return xhr.responseXML + } + } catch (e) {} + + return null +} + +function noop() {} + +xhr.default = default_1; + +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { + return typeof obj; +} : function (obj) { + return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; +}; + + + + + +var asyncGenerator = function () { + function AwaitValue(value) { + this.value = value; + } + + function AsyncGenerator(gen) { + var front, back; + + function send(key, arg) { + return new Promise(function (resolve, reject) { + var request = { + key: key, + arg: arg, + resolve: resolve, + reject: reject, + next: null + }; + + if (back) { + back = back.next = request; + } else { + front = back = request; + resume(key, arg); + } + }); + } + + function resume(key, arg) { + try { + var result = gen[key](arg); + var value = result.value; + + if (value instanceof AwaitValue) { + Promise.resolve(value.value).then(function (arg) { + resume("next", arg); + }, function (arg) { + resume("throw", arg); + }); + } else { + settle(result.done ? "return" : "normal", result.value); + } + } catch (err) { + settle("throw", err); + } + } + + function settle(type, value) { + switch (type) { + case "return": + front.resolve({ + value: value, + done: true + }); + break; + + case "throw": + front.reject(value); + break; + + default: + front.resolve({ + value: value, + done: false + }); + break; + } + + front = front.next; + + if (front) { + resume(front.key, front.arg); + } else { + back = null; + } + } + + this._invoke = send; + + if (typeof gen.return !== "function") { + this.return = undefined; + } + } + + if (typeof Symbol === "function" && Symbol.asyncIterator) { + AsyncGenerator.prototype[Symbol.asyncIterator] = function () { + return this; + }; + } + + AsyncGenerator.prototype.next = function (arg) { + return this._invoke("next", arg); + }; + + AsyncGenerator.prototype.throw = function (arg) { + return this._invoke("throw", arg); + }; + + AsyncGenerator.prototype.return = function (arg) { + return this._invoke("return", arg); + }; + + return { + wrap: function (fn) { + return function () { + return new AsyncGenerator(fn.apply(this, arguments)); + }; + }, + await: function (value) { + return new AwaitValue(value); + } + }; +}(); + + + + + +var classCallCheck = function (instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } +}; + +var createClass = function () { + function defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } + } + + return function (Constructor, protoProps, staticProps) { + if (protoProps) defineProperties(Constructor.prototype, protoProps); + if (staticProps) defineProperties(Constructor, staticProps); + return Constructor; + }; +}(); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +var toConsumableArray = function (arr) { + if (Array.isArray(arr)) { + for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; + + return arr2; + } else { + return Array.from(arr); + } +}; + +var lastError = ''; + +/** + * Creates the HTLM for a failure message + * @param {string} canvasContainerId id of container of th + * canvas. + * @return {string} The html. + */ +function makeFailHTML(msg) { + return '\n\n
\n
\n
' + msg + '
\n
\n
\n'; +} + +/** + * Message for getting a webgl browser + * @type {string} + */ +var GET_A_WEBGL_BROWSER = '\n\tThis page requires a browser that supports WebGL.
\n\tClick here to upgrade your browser.\n'; + +/** + * Message for need better hardware + * @type {string} + */ +var OTHER_PROBLEM = '\n\tIt does not appear your computer can support WebGL.
\n\tClick here for more information.\n'; + +/** + * Code to return in `onError` callback when the browser doesn't support webgl + * @type {number} + */ +var ERROR_BROWSER_SUPPORT = 1; + +/** + * Code to return in `onError` callback there's any other problem related to webgl + * @type {number} + */ +var ERROR_OTHER = 2; + +/** + * Creates a webgl context. If creation fails it will + * change the contents of the container of the + * tag to an error message with the correct links for WebGL, + * unless `onError` option is defined to a callback, + * which allows for custom error handling.. + * @param {Element} canvas. The canvas element to create a + * context from. + * @param {WebGLContextCreationAttributes} optAttribs Any + * creation attributes you want to pass in. + * @return {WebGLRenderingContext} The created context. + */ +function setupWebGL(canvas, optAttribs, onError) { + function showLink(str) { + var container = canvas.parentNode; + if (container) { + container.innerHTML = makeFailHTML(str); + } + } + + function handleError(errorCode, msg) { + if (typeof onError === 'function') { + onError(errorCode); + } else { + showLink(msg); + } + } + + if (!window.WebGLRenderingContext) { + handleError(ERROR_BROWSER_SUPPORT, GET_A_WEBGL_BROWSER); + return null; + } + + var context = create3DContext(canvas, optAttribs); + if (!context) { + handleError(ERROR_OTHER, OTHER_PROBLEM); + } else { + context.getExtension('OES_standard_derivatives'); + } + return context; +} + +/** + * Creates a webgl context. + * @param {!Canvas} canvas The canvas tag to get context + * from. If one is not passed in one will be created. + * @return {!WebGLContext} The created context. + */ +function create3DContext(canvas, optAttribs) { + var names = ['webgl', 'experimental-webgl']; + var context = null; + for (var ii = 0; ii < names.length; ++ii) { + try { + context = canvas.getContext(names[ii], optAttribs); + } catch (e) { + if (context) { + break; + } + } + } + return context; +} + +/* + * Create a Vertex of a specific type (gl.VERTEX_SHADER/) + */ +function createShader(main, source, type, offset) { + var gl = main.gl; + + var shader = gl.createShader(type); + gl.shaderSource(shader, source); + gl.compileShader(shader); + + var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS); + + if (!compiled) { + // Something went wrong during compilation; get the error + lastError = gl.getShaderInfoLog(shader); + console.error('*** Error compiling shader ' + shader + ':' + lastError); + main.trigger('error', { + shader: shader, + source: source, + type: type, + error: lastError, + offset: offset || 0 + }); + gl.deleteShader(shader); + return null; + } + + return shader; +} + +/** + * Loads a shader. + * @param {!WebGLContext} gl The WebGLContext to use. + * @param {string} shaderSource The shader source. + * @param {number} shaderType The type of shader. + * @param {function(string): void) opt_errorCallback callback for errors. + * @return {!WebGLShader} The created shader. + */ +function createProgram(main, shaders, optAttribs, optLocations) { + var gl = main.gl; + + var program = gl.createProgram(); + for (var ii = 0; ii < shaders.length; ++ii) { + gl.attachShader(program, shaders[ii]); + } + if (optAttribs) { + for (var _ii = 0; _ii < optAttribs.length; ++_ii) { + gl.bindAttribLocation(program, optLocations ? optLocations[_ii] : _ii, optAttribs[_ii]); + } + } + gl.linkProgram(program); + + // Check the link status + var linked = gl.getProgramParameter(program, gl.LINK_STATUS); + if (!linked) { + // something went wrong with the link + lastError = gl.getProgramInfoLog(program); + console.log('Error in program linking:' + lastError); + gl.deleteProgram(program); + return null; + } + return program; +} + +// By Brett Camber on +// https://github.com/tangrams/tangram/blob/master/src/gl/glsl.js +function parseUniforms(uniforms) { + var prefix = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; + + var parsed = []; + + for (var name in uniforms) { + var uniform = uniforms[name]; + var u = void 0; + + if (prefix) { + name = prefix + '.' + name; + } + + // Single float + if (typeof uniform === 'number') { + parsed.push({ + type: 'float', + method: '1f', + name: name, + value: uniform + }); + } + // Array: vector, array of floats, array of textures, or array of structs + else if (Array.isArray(uniform)) { + // Numeric values + if (typeof uniform[0] === 'number') { + // float vectors (vec2, vec3, vec4) + if (uniform.length === 1) { + parsed.push({ + type: 'float', + method: '1f', + name: name, + value: uniform + }); + } + // float vectors (vec2, vec3, vec4) + else if (uniform.length >= 2 && uniform.length <= 4) { + parsed.push({ + type: 'vec' + uniform.length, + method: uniform.length + 'fv', + name: name, + value: uniform + }); + } + // float array + else if (uniform.length > 4) { + parsed.push({ + type: 'float[]', + method: '1fv', + name: name + '[0]', + value: uniform + }); + } + // TODO: assume matrix for (typeof == Float32Array && length == 16)? + } + // Array of textures + else if (typeof uniform[0] === 'string') { + parsed.push({ + type: 'sampler2D', + method: '1i', + name: name, + value: uniform + }); + } + // Array of arrays - but only arrays of vectors are allowed in this case + else if (Array.isArray(uniform[0]) && typeof uniform[0][0] === 'number') { + // float vectors (vec2, vec3, vec4) + if (uniform[0].length >= 2 && uniform[0].length <= 4) { + // Set each vector in the array + for (u = 0; u < uniform.length; u++) { + parsed.push({ + type: 'vec' + uniform[0].length, + method: uniform[u].length + 'fv', + name: name + '[' + u + ']', + value: uniform[u] + }); + } + } + // else error? + } + // Array of structures + else if (_typeof(uniform[0]) === 'object') { + for (u = 0; u < uniform.length; u++) { + // Set each struct in the array + parsed.push.apply(parsed, toConsumableArray(parseUniforms(uniform[u], name + '[' + u + ']'))); + } + } + } + // Boolean + else if (typeof uniform === 'boolean') { + parsed.push({ + type: 'bool', + method: '1i', + name: name, + value: uniform + }); + } + // Texture + else if (typeof uniform === 'string') { + parsed.push({ + type: 'sampler2D', + method: '1i', + name: name, + value: uniform + }); + } + // Structure + else if ((typeof uniform === 'undefined' ? 'undefined' : _typeof(uniform)) === 'object') { + // Set each field in the struct + parsed.push.apply(parsed, toConsumableArray(parseUniforms(uniform, name))); + } + // TODO: support other non-float types? (int, etc.) + } + return parsed; +} + +function isCanvasVisible(canvas) { + var bound = canvas.getBoundingClientRect(); + return bound.top + bound.height > 0 && bound.top < (window.innerHeight || document.documentElement.clientHeight); +} + +function isPowerOf2(value) { + return (value & value - 1) === 0; +} + +function isSafari() { + return (/^((?!chrome|android).)*safari/i.test(navigator.userAgent) + ); +} + + + + + + + +function isDiff(a, b) { + if (a && b) { + return a.toString() !== b.toString(); + } + return false; +} + +function getFile(url) { + var httpRequest = new XMLHttpRequest(); + httpRequest.open("GET", url, false); + httpRequest.send(); + if (httpRequest.status == 200) return httpRequest.responseText;else return ""; +} + +function subscribeMixin$1(target) { + var listeners = new Set(); + + return Object.assign(target, { + on: function on(type, f) { + var listener = {}; + listener[type] = f; + listeners.add(listener); + }, + off: function off(type, f) { + if (f) { + var listener = {}; + listener[type] = f; + listeners.delete(listener); + } else { + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; + + try { + for (var _iterator = listeners[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var item = _step.value; + var _iteratorNormalCompletion2 = true; + var _didIteratorError2 = false; + var _iteratorError2 = undefined; + + try { + for (var _iterator2 = Object.keys(item)[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { + var key = _step2.value; + + if (key === type) { + listeners.delete(item); + return; + } + } + } catch (err) { + _didIteratorError2 = true; + _iteratorError2 = err; + } finally { + try { + if (!_iteratorNormalCompletion2 && _iterator2.return) { + _iterator2.return(); + } + } finally { + if (_didIteratorError2) { + throw _iteratorError2; + } + } + } + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } + } + } + }, + listSubscriptions: function listSubscriptions() { + var _iteratorNormalCompletion3 = true; + var _didIteratorError3 = false; + var _iteratorError3 = undefined; + + try { + for (var _iterator3 = listeners[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { + var item = _step3.value; + + console.log(item); + } + } catch (err) { + _didIteratorError3 = true; + _iteratorError3 = err; + } finally { + try { + if (!_iteratorNormalCompletion3 && _iterator3.return) { + _iterator3.return(); + } + } finally { + if (_didIteratorError3) { + throw _iteratorError3; + } + } + } + }, + subscribe: function subscribe(listener) { + listeners.add(listener); + }, + unsubscribe: function unsubscribe(listener) { + listeners.delete(listener); + }, + unsubscribeAll: function unsubscribeAll() { + listeners.clear(); + }, + trigger: function trigger(event) { + for (var _len = arguments.length, data = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + data[_key - 1] = arguments[_key]; + } + + var _iteratorNormalCompletion4 = true; + var _didIteratorError4 = false; + var _iteratorError4 = undefined; + + try { + for (var _iterator4 = listeners[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { + var listener = _step4.value; + + if (typeof listener[event] === 'function') { + listener[event].apply(listener, toConsumableArray(data)); + } + } + } catch (err) { + _didIteratorError4 = true; + _iteratorError4 = err; + } finally { + try { + if (!_iteratorNormalCompletion4 && _iterator4.return) { + _iterator4.return(); + } + } finally { + if (_didIteratorError4) { + throw _iteratorError4; + } + } + } + } + }); +} + +// Texture management +// GL texture wrapper object for keeping track of a global set of textures, keyed by a unique user-defined name + +var Texture = function () { + function Texture(gl, name) { + var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; + classCallCheck(this, Texture); + + subscribeMixin$1(this); + + this.gl = gl; + this.texture = gl.createTexture(); + if (this.texture) { + this.valid = true; + } + this.bind(); + + this.name = name; + this.source = null; + this.sourceType = null; + this.loading = null; // a Promise object to track the loading state of this texture + + // Default to a 1-pixel black texture so we can safely render while we wait for an image to load + // See: http://stackoverflow.com/questions/19722247/webgl-wait-for-texture-to-load + this.setData(1, 1, new Uint8Array([0, 0, 0, 255]), { filtering: 'linear' }); + this.setFiltering(options.filtering); + + this.load(options); + } + + // Destroy a single texture instance + + + createClass(Texture, [{ + key: 'destroy', + value: function destroy() { + if (!this.valid) { + return; + } + this.gl.deleteTexture(this.texture); + this.texture = null; + delete this.data; + this.data = null; + this.valid = false; + } + }, { + key: 'bind', + value: function bind(unit) { + if (!this.valid) { + return; + } + if (typeof unit === 'number') { + if (Texture.activeUnit !== unit) { + this.gl.activeTexture(this.gl.TEXTURE0 + unit); + Texture.activeUnit = unit; + } + } + if (Texture.activeTexture !== this.texture) { + this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture); + Texture.activeTexture = this.texture; + } + } + }, { + key: 'load', + value: function load() { + var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + + this.loading = null; + + if (typeof options.url === 'string') { + if (this.url === undefined || options.url !== this.url) { + this.setUrl(options.url, options); + } + } else if (options.element) { + this.setElement(options.element, options); + } else if (options.data && options.width && options.height) { + this.setData(options.width, options.height, options.data, options); + } + } + + // Sets texture from an url + + }, { + key: 'setUrl', + value: function setUrl(url) { + var _this = this; + + var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + + if (!this.valid) { + return; + } + + this.url = url; // save URL reference (will be overwritten when element is loaded below) + this.source = this.url; + this.sourceType = 'url'; + + this.loading = new Promise(function (resolve, reject) { + var ext = url.split('.').pop().toLowerCase(); + var isVideo = ext === 'ogv' || ext === 'webm' || ext === 'mp4'; + + var element = undefined; + if (isVideo) { + element = document.createElement('video'); + element.autoplay = true; + + element.muted = true; /* required for modern browsers to autoplay video */ + setTimeout(function () { + element.play(); /* doesn't block promise but needs a more elegant solution */ + }, 1); + + options.filtering = 'nearest'; + // element.preload = 'auto'; + // element.style.display = 'none'; + // document.body.appendChild(element); + } else { + element = new Image(); + } + + element.onload = function () { + try { + _this.setElement(element, options); + } catch (e) { + console.log('Texture \'' + _this.name + '\': failed to load url: \'' + _this.source + '\'', e, options); + } + resolve(_this); + }; + element.onerror = function (e) { + // Warn and resolve on error + console.log('Texture \'' + _this.name + '\': failed to load url: \'' + _this.source + '\'', e, options); + resolve(_this); + }; + + // Safari has a bug loading data-URL elements with CORS enabled, so it must be disabled in that case + // https://bugs.webkit.org/show_bug.cgi?id=123978 + if (!(isSafari() && _this.source.slice(0, 5) === 'data:')) { + element.crossOrigin = 'anonymous'; + } + + element.src = _this.source; + if (isVideo) { + _this.setElement(element, options); + } + }); + return this.loading; + } + + // Sets texture to a raw image buffer + + }, { + key: 'setData', + value: function setData(width, height, data) { + var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; + + this.width = width; + this.height = height; + + this.source = data; + this.sourceType = 'data'; + + this.update(options); + this.setFiltering(options); + + this.loading = Promise.resolve(this); + return this.loading; + } + + // Sets the texture to track a element (canvas/image) + + }, { + key: 'setElement', + value: function setElement(element, options) { + var _this2 = this; + + var el = element; + + // a string element is interpeted as a CSS selector + if (typeof element === 'string') { + element = document.querySelector(element); + } + + if (element instanceof HTMLCanvasElement || element instanceof HTMLImageElement || element instanceof HTMLVideoElement) { + this.source = element; + this.sourceType = 'element'; + + if (element instanceof HTMLVideoElement) { + this.width = this.source.videoWidth; + this.height = this.source.videoHeight; + element.addEventListener('canplaythrough', function () { + _this2.intervalID = setInterval(function () { + _this2.update(options); + }, 15); + }, true); + element.addEventListener('ended', function () { + element.currentTime = 0; + element.play(); + }, true); + } else { + this.update(options); + } + this.setFiltering(options); + } else { + var msg = 'the \'element\' parameter (`element: ' + JSON.stringify(el) + '`) must be a CSS '; + msg += 'selector string, or a , or