UNPKG

tangram

Version:
1,461 lines (1,371 loc) 1.23 MB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Tangram = factory()); })(this, (function () { 'use strict'; // define() gets called for each chunk generated by the first Rollup pass. // The order the chunks are called in is controlled by the imports in bundle.js: // // shared.js: shared dependencies between main and worker threads // scene_worker.js: worker thread code // index.js: main thread code // Once all chunks have been provided, the worker thread code is assembled, // incorporating the shared chunk code, then turned into a blob URL which // can be used to instantiate the worker. var shared, worker, Tangram = {}; function define(_, chunk) { if (!shared) { shared = chunk; } else if (!worker) { worker = chunk; } else { var worker_bundle = 'var shared_chunk = {}; (' + shared + ')(shared_chunk); (' + worker + ')(shared_chunk);' var shared_chunk = {}; shared(shared_chunk); Tangram = chunk(shared_chunk); Tangram.workerURL = window.URL.createObjectURL(new Blob([worker_bundle], { type: 'text/javascript' })); } } define(['exports'], (function (exports) { 'use strict'; function _arrayWithHoles(r) { if (Array.isArray(r)) return r; } function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } } function _arrayLikeToArray$1(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } function _unsupportedIterableToArray$1(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray$1(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray$1(r, a) : void 0; } } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray$1(r, e) || _nonIterableRest(); } /*jshint worker: true*/ // Mark thread as main or worker var Thread = {}; try { if (window instanceof Window && window.document instanceof HTMLDocument) { // jshint ignore:line Thread.is_worker = false; Thread.is_main = true; } } catch (e) { Thread.is_worker = true; Thread.is_main = false; // Patch for 3rd party libs that require these globals to be present. Specifically, FontFaceObserver. // Brittle solution but allows that library to load on worker threads. self.window = { document: {} }; self.document = self.window.document; } function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } function toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } function toPropertyKey(t) { var i = toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } function _defineProperty(e, r, t) { return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray$1(r); } function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray$1(r) || _nonIterableSpread(); } function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); } function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, toPropertyKey(o.key), o); } } function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; } var version$1 = "0.22.0"; var version = 'v' + version$1; function _isNativeReflectConstruct$b() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct$b = function _isNativeReflectConstruct() { return !!t; })(); } function _setPrototypeOf(t, e) { return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) { return t.__proto__ = e, t; }, _setPrototypeOf(t, e); } function _construct(t, e, r) { if (_isNativeReflectConstruct$b()) return Reflect.construct.apply(null, arguments); var o = [null]; o.push.apply(o, e); var p = new (t.bind.apply(t, o))(); return r && _setPrototypeOf(p, r.prototype), p; } var WorkerBroker; var WorkerBroker$1 = WorkerBroker = {}; // Global list of all worker messages // Uniquely tracks every call made between main thread and a worker var message_id = 0; var messages = {}; // Register an object to receive calls from other thread WorkerBroker.targets = {}; WorkerBroker.addTarget = function (name, target) { WorkerBroker.targets[name] = target; }; WorkerBroker.removeTarget = function (name) { if (name) { delete WorkerBroker.targets[name]; } }; // Given a dot-notation-style method name, e.g. 'Object.object.method', // find the object to call the method on from the list of registered targets function findTarget(method) { var chain = []; if (typeof method === 'string') { chain = method.split('.'); method = chain.pop(); } var target = WorkerBroker.targets; for (var m = 0; m < chain.length; m++) { if (target[chain[m]]) { target = target[chain[m]]; } else { return []; } } return [method, target]; } // Main thread: // - Send messages to workers, and optionally receive an async response as a promise // - Receive messages from workers, and optionally send an async response back as a promise function setupMainThread() { // Send a message to a worker, and optionally get an async response // Arguments: // - worker: one or more web worker instances to send the message to (single value or array) // - method: the method with this name, specified with dot-notation, will be invoked in the worker // - message: spread of arguments to call the method with // Returns: // - a promise that will be fulfilled if the worker method returns a value (could be immediately, or async) // WorkerBroker.postMessage = function (worker, method) { for (var _len = arguments.length, message = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { message[_key - 2] = arguments[_key]; } // If more than one worker specified, post to multiple if (Array.isArray(worker)) { return Promise.all(worker.map(function (w) { var _WorkerBroker; return (_WorkerBroker = WorkerBroker).postMessage.apply(_WorkerBroker, [w, method].concat(_toConsumableArray(message))); })); } // Parse options var options = {}; if (_typeof(method) === 'object') { options = method; method = method.method; } // Track state of this message var promise = new Promise(function (resolve, reject) { messages[message_id] = { method: method, message: message, resolve: resolve, reject: reject }; }); var payload, transferables = []; if (message && message.length === 1 && message[0] instanceof WorkerBroker.withTransferables) { transferables = message[0].transferables; message = message[0].value; } payload = { type: 'main_send', // mark message as method invocation from main thread message_id: message_id, // unique id for this message, for life of program method: method, // will dispatch to a function of this name within the worker message: message // message payload }; if (options.stringify) { payload = JSON.stringify(payload); } worker.postMessage(payload, transferables.map(function (t) { return t.object; })); freeTransferables(transferables); if (transferables.length > 0) { log('trace', "'".concat(method, "' transferred ").concat(transferables.length, " objects to worker thread")); } message_id++; return promise; }; // Add a worker to communicate with - each worker must be registered from the main thread WorkerBroker.addWorker = function (worker) { if (!(worker instanceof Worker)) { throw Error('Worker broker could not add non-Worker object', worker); } worker.addEventListener('message', function WorkerBrokerMainThreadHandler(event) { var data = typeof event.data === 'string' ? JSON.parse(event.data) : event.data; var id = data.message_id; // Listen for messages coming back from the worker, and fulfill that message's promise if (data.type === 'worker_reply') { // Pass the result to the promise if (messages[id]) { if (data.error) { messages[id].reject(data.error); } else { messages[id].resolve(data.message); } delete messages[id]; } } // Listen for messages initiating a call from the worker, dispatch them, // and send any return value back to the worker // Unique id for this message & return call to main thread else if (data.type === 'worker_send' && id != null) { // Call the requested method and save the return value var result, error, target, method_name, method; try { var _findTarget = findTarget(data.method); var _findTarget2 = _slicedToArray(_findTarget, 2); method_name = _findTarget2[0]; target = _findTarget2[1]; if (!target) { throw Error("Worker broker could not dispatch message type ".concat(data.method, " on target ").concat(data.target, " because no object with that name is registered on main thread")); } method = typeof target[method_name] === 'function' && target[method_name]; if (!method) { throw Error("Worker broker could not dispatch message type ".concat(data.method, " on target ").concat(data.target, " because object has no method with that name")); } result = method.apply(target, data.message); } catch (e) { // Thrown errors will be passed back (in string form) to worker error = e; } // Send return value to worker var payload, transferables = []; // Async result if (result instanceof Promise) { result.then(function (value) { if (value instanceof WorkerBroker.withTransferables) { transferables = value.transferables; value = value.value[0]; } payload = { type: 'main_reply', message_id: id, message: value }; worker.postMessage(payload, transferables.map(function (t) { return t.object; })); freeTransferables(transferables); if (transferables.length > 0) { log('trace', "'".concat(method_name, "' transferred ").concat(transferables.length, " objects to worker thread")); } }, function (error) { worker.postMessage({ type: 'main_reply', message_id: id, error: error instanceof Error ? "".concat(error.message, ": ").concat(error.stack) : error }); }); } // Immediate result else { if (result instanceof WorkerBroker.withTransferables) { transferables = result.transferables; result = result.value[0]; } payload = { type: 'main_reply', message_id: id, message: result, error: error instanceof Error ? "".concat(error.message, ": ").concat(error.stack) : error }; worker.postMessage(payload, transferables.map(function (t) { return t.object; })); freeTransferables(transferables); if (transferables.length > 0) { log('trace', "'".concat(method_name, "' transferred ").concat(transferables.length, " objects to worker thread")); } } } }); }; // Expose for debugging WorkerBroker.getMessages = function () { return messages; }; WorkerBroker.getMessageId = function () { return message_id; }; } // Worker threads: // - Receive messages from main thread, and optionally send an async response back as a promise // - Send messages to main thread, and optionally receive an async response as a promise function setupWorkerThread() { // Send a message to the main thread, and optionally get an async response as a promise // Arguments: // - method: the method with this name, specified with dot-notation, will be invoked on the main thread // - message: array of arguments to call the method with // Returns: // - a promise that will be fulfilled if the main thread method returns a value (could be immediately, or async) // WorkerBroker.postMessage = function (method) { for (var _len2 = arguments.length, message = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { message[_key2 - 1] = arguments[_key2]; } // Parse options var options = {}; if (_typeof(method) === 'object') { options = method; method = method.method; } // Track state of this message var promise = new Promise(function (resolve, reject) { messages[message_id] = { method: method, message: message, resolve: resolve, reject: reject }; }); var payload, transferables = []; if (message && message.length === 1 && message[0] instanceof WorkerBroker.withTransferables) { transferables = message[0].transferables; message = message[0].value; } payload = { type: 'worker_send', // mark message as method invocation from worker message_id: message_id, // unique id for this message, for life of program method: method, // will dispatch to a method of this name on the main thread message: message // message payload }; if (options.stringify) { payload = JSON.stringify(payload); } self.postMessage(payload, transferables.map(function (t) { return t.object; })); freeTransferables(transferables); if (transferables.length > 0) { log('trace', "'".concat(method, "' transferred ").concat(transferables.length, " objects to main thread")); } message_id++; return promise; }; self.addEventListener('message', function WorkerBrokerWorkerThreadHandler(event) { var data = typeof event.data === 'string' ? JSON.parse(event.data) : event.data; var id = data.message_id; // Listen for messages coming back from the main thread, and fulfill that message's promise if (data.type === 'main_reply') { // Pass the result to the promise if (messages[id]) { if (data.error) { messages[id].reject(data.error); } else { messages[id].resolve(data.message); } delete messages[id]; } } // Receive messages from main thread, dispatch them, and send back a reply // Unique id for this message & return call to main thread else if (data.type === 'main_send' && id != null) { // Call the requested worker method and save the return value var result, error, target, method_name, method; try { var _findTarget3 = findTarget(data.method); var _findTarget4 = _slicedToArray(_findTarget3, 2); method_name = _findTarget4[0]; target = _findTarget4[1]; if (!target) { throw Error("Worker broker could not dispatch message type ".concat(data.method, " on target ").concat(data.target, " because no object with that name is registered on main thread")); } method = typeof target[method_name] === 'function' && target[method_name]; if (!method) { throw Error("Worker broker could not dispatch message type ".concat(data.method, " because worker has no method with that name")); } result = method.apply(target, data.message); } catch (e) { // Thrown errors will be passed back (in string form) to main thread error = e; } // Send return value to main thread var payload, transferables = []; // Async result if (result instanceof Promise) { result.then(function (value) { if (value instanceof WorkerBroker.withTransferables) { transferables = value.transferables; value = value.value[0]; } payload = { type: 'worker_reply', message_id: id, message: value }; self.postMessage(payload, transferables.map(function (t) { return t.object; })); freeTransferables(transferables); if (transferables.length > 0) { log('trace', "'".concat(method_name, "' transferred ").concat(transferables.length, " objects to main thread")); } }, function (error) { self.postMessage({ type: 'worker_reply', message_id: id, error: error instanceof Error ? "".concat(error.message, ": ").concat(error.stack) : error }); }); } // Immediate result else { if (result instanceof WorkerBroker.withTransferables) { transferables = result.transferables; result = result.value[0]; } payload = { type: 'worker_reply', message_id: id, message: result, error: error instanceof Error ? "".concat(error.message, ": ").concat(error.stack) : error }; self.postMessage(payload, transferables.map(function (t) { return t.object; })); freeTransferables(transferables); if (transferables.length > 0) { log('trace', "'".concat(method_name, "' transferred ").concat(transferables.length, " objects to main thread")); } } } }); } // Special value wrapper, to indicate that we want to find and include transferable objects in the message WorkerBroker.withTransferables = function () { for (var _len3 = arguments.length, value = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { value[_key3] = arguments[_key3]; } if (!(this instanceof WorkerBroker.withTransferables)) { return _construct(WorkerBroker.withTransferables, value); } this.value = value; this.transferables = findTransferables(this.value); }; // Build a list of transferable objects from a source object // Returns a list of info about each transferable: // - object: the actual transferable object // - parent: the parent object that the transferable is a property of (if any) // - property: the property name of the transferable on the parent object (if any) // TODO: add option in case you DON'T want to transfer objects function findTransferables(source) { var parent = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; var property = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; var list = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : []; if (!source) { return list; } if (Array.isArray(source)) { // Check each array element source.forEach(function (x, i) { return findTransferables(x, source, i, list); }); } else if (_typeof(source) === 'object') { // Is the object a transferable array buffer? if (source instanceof ArrayBuffer) { list.push({ object: source, parent: parent, property: property }); } // Or looks like a typed array (has an array buffer property)? else if (source.buffer instanceof ArrayBuffer) { list.push({ object: source.buffer, parent: parent, property: property }); } // Otherwise check each property else { for (var prop in source) { findTransferables(source[prop], source, prop, list); } } } return list; } // Remove neutered transferables from parent objects, as they should no longer be accessed after transfer function freeTransferables(transferables) { if (!Array.isArray(transferables)) { return; } transferables.filter(function (t) { return t.parent && t.property; }).forEach(function (t) { return delete t.parent[t.property]; }); } // Setup this thread as appropriate if (Thread.is_main) { setupMainThread(); } if (Thread.is_worker) { setupWorkerThread(); } var LEVELS = { silent: -1, error: 0, warn: 1, info: 2, debug: 3, trace: 4 }; var methods = {}; var logged_once = {}; function methodForLevel(level) { if (Thread.is_main) { methods[level] = methods[level] || (console[level] ? console[level] : console.log).bind(console); // eslint-disable-line no-console return methods[level]; } } // Logs message, proxying any log requests from worker threads back to the main thread. // Returns (asynchronously, due to proxying) a boolean indicating if the message was logged. // Option `once: true` can be used to only log each unique log message once (e.g. for warnings // that would otherwise be repetitive or possibly logged thousands of times, such as per feature). function log(opts) { var level = _typeof(opts) === 'object' ? opts.level : opts; if (LEVELS[level] <= LEVELS[log.level]) { for (var _len = arguments.length, msg = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { msg[_key - 1] = arguments[_key]; } if (Thread.is_worker) { // Proxy to main thread return WorkerBroker$1.postMessage.apply(WorkerBroker$1, [{ method: '_logProxy', stringify: true }, opts].concat(msg)); } else { // Only log message once? if (_typeof(opts) === 'object' && opts.once === true) { if (logged_once[JSON.stringify(msg)]) { return Promise.resolve(false); } logged_once[JSON.stringify(msg)] = true; } // Write to console (on main thread) var logger = methodForLevel(level); if (msg.length > 1) { logger.apply(void 0, ["Tangram ".concat(version, " [").concat(level, "]: ").concat(msg[0])].concat(_toConsumableArray(msg.slice(1)))); } else { logger("Tangram ".concat(version, " [").concat(level, "]: ").concat(msg[0])); } } return Promise.resolve(true); } return Promise.resolve(false); } log.level = 'info'; log.workers = null; log.setLevel = function (level) { log.level = level; if (Thread.is_main && Array.isArray(log.workers)) { WorkerBroker$1.postMessage(log.workers, '_logSetLevelProxy', level); } }; if (Thread.is_main) { log.setWorkers = function (workers) { log.workers = workers; }; log.reset = function () { logged_once = {}; }; } WorkerBroker$1.addTarget('_logProxy', log); // proxy log messages from worker to main thread WorkerBroker$1.addTarget('_logSetLevelProxy', log.setLevel); // proxy log level setting from main to worker thread var Utils = {}; WorkerBroker$1.addTarget('Utils', Utils); // Basic Safari detection // http://stackoverflow.com/questions/7944460/detect-safari-browser Utils.isSafari = function () { return /^((?!chrome|android).)*safari/i.test(navigator.userAgent); }; // Basic IE11 or Edge detection Utils.isMicrosoft = function () { return /(Trident\/7.0|Edge[ /](\d+[.\d]+))/i.test(navigator.userAgent); }; Utils._requests = {}; // XHR requests on current thread Utils._proxy_requests = {}; // XHR requests proxied to main thread // `request_key` is a user-provided key that can be later used to cancel the request Utils.io = function (url) { var timeout = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 60000; var responseType = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'text'; var method = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 'GET'; var headers = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {}; var request_key = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : null; var proxy = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : false; if (Thread.is_worker && Utils.isMicrosoft()) { // Some versions of IE11 and Edge will hang web workers when performing XHR requests // These requests can be proxied through the main thread // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/9545866/ log('debug', 'Proxying request for URL to worker', url); if (request_key) { Utils._proxy_requests[request_key] = true; // mark as proxied } return WorkerBroker$1.postMessage('Utils.io', url, timeout, responseType, method, headers, request_key, true); } else { var request = new XMLHttpRequest(); var promise = new Promise(function (resolve, reject) { request.open(method, url, true); request.timeout = timeout; request.responseType = responseType; // Attach optional request headers if (headers && _typeof(headers) === 'object') { for (var key in headers) { request.setRequestHeader(key, headers[key]); } } request.onload = function () { if (request.status === 200) { if (['text', 'json'].indexOf(request.responseType) > -1) { resolve({ body: request.responseText, status: request.status }); } else { resolve({ body: request.response, status: request.status }); } } else if (request.status === 204) { // No Content resolve({ body: null, status: request.status }); } else { reject(Error('Request error with a status of ' + request.statusText)); } }; request.onerror = function (evt) { reject(Error('There was a network error' + evt.toString())); }; request.ontimeout = function (evt) { reject(Error('timeout ' + evt.toString())); }; request.send(); }); promise = promise.then(function (response) { if (request_key) { delete Utils._requests[request_key]; } if (proxy) { return WorkerBroker$1.withTransferables(response); } return response; }); if (request_key) { Utils._requests[request_key] = request; } return promise; } }; // Çancel a pending network request by user-provided request key Utils.cancelRequest = function (key) { // Check for a request that was proxied to the main thread if (Thread.is_worker && Utils._proxy_requests[key]) { return WorkerBroker$1.postMessage('Utils.cancelRequest', key); // forward to main thread } var req = Utils._requests[key]; if (req) { log('trace', "Cancelling network request key '".concat(key, "'")); Utils._requests[key].abort(); delete Utils._requests[key]; } else { log('trace', "Could not find network request key '".concat(key, "'")); } }; // Stringify an object into JSON, but convert functions to strings Utils.serializeWithFunctions = function (obj) { if (typeof obj === 'function') { return obj.toString(); } var serialized = JSON.stringify(obj, function (k, v) { // Convert functions to strings if (typeof v === 'function') { return v.toString(); } return v; }); return serialized; }; // Default to allowing high pixel density // Returns true if display density changed Utils.use_high_density_display = true; Utils.updateDevicePixelRatio = function () { var prev = Utils.device_pixel_ratio; Utils.device_pixel_ratio = Utils.use_high_density_display && window.devicePixelRatio || 1; return Utils.device_pixel_ratio !== prev; }; if (Thread.is_main) { Utils.updateDevicePixelRatio(); } // Used for differentiating between power-of-2 and non-power-of-2 textures // Via: http://stackoverflow.com/questions/19722247/webgl-wait-for-texture-to-load Utils.isPowerOf2 = function (value) { return (value & value - 1) === 0; }; // Interpolate 'x' along a series of control points // 'points' is an array of control points in the form [x, y] // // Example: // Control points: // [0, 5]: when x=0, y=5 // [4, 10]: when x=4, y=10 // // Utils.interpolate(2, [[0, 5], [4, 10]]); // -> computes x=2, halfway between x=0 and x=4: (10 - 5) / 2 +5 // -> returns 7.5 // // TODO: add other interpolation methods besides linear // Utils.interpolate = function (x, points, transform) { // If this doesn't resemble a list of control points, just return the original value if (!Array.isArray(points) || !Array.isArray(points[0])) { return points; } else if (points.length < 1) { return points; } var x1, x2, d, y, y1, y2; // Min bounds if (x <= points[0][0]) { y = points[0][1]; if (typeof transform === 'function') { y = transform(y); } } // Max bounds else if (x >= points[points.length - 1][0]) { y = points[points.length - 1][1]; if (typeof transform === 'function') { y = transform(y); } } // Find which control points x is between else { for (var i = 0; i < points.length - 1; i++) { if (x >= points[i][0] && x < points[i + 1][0]) { // Linear interpolation x1 = points[i][0]; x2 = points[i + 1][0]; // Multiple values if (Array.isArray(points[i][1])) { y = []; for (var c = 0; c < points[i][1].length; c++) { if (typeof transform === 'function') { y1 = transform(points[i][1][c]); y2 = transform(points[i + 1][1][c]); d = y2 - y1; y[c] = d * (x - x1) / (x2 - x1) + y1; } else { d = points[i + 1][1][c] - points[i][1][c]; y[c] = d * (x - x1) / (x2 - x1) + points[i][1][c]; } } } // Single value else { if (typeof transform === 'function') { y1 = transform(points[i][1]); y2 = transform(points[i + 1][1]); d = y2 - y1; y = d * (x - x1) / (x2 - x1) + y1; } else { d = points[i + 1][1] - points[i][1]; y = d * (x - x1) / (x2 - x1) + points[i][1]; } } break; } } } return y; }; Utils.toCSSColor = function (color) { if (color != null) { if (color[3] === 1) { // full opacity return "rgb(".concat(color.slice(0, 3).map(function (c) { return Math.round(c * 255); }).join(', '), ")"); } // RGB is between [0, 255] opacity is between [0, 1] return "rgba(".concat(color.map(function (c, i) { return i < 3 && Math.round(c * 255) || c; }).join(', '), ")"); } }; var debugSettings; var debugSettings$1 = debugSettings = { // draws a blue rectangle border around the collision box of a label draw_label_collision_boxes: false, // draws a green rectangle border within the texture box of a label draw_label_texture_boxes: false, // suppreses fade-in of labels suppress_label_fade_in: false, // suppress animaton of label snap to pixel grid suppress_label_snap_animation: false, // show hidden labels for debugging show_hidden_labels: false, // collect feature/geometry stats on styling layers layer_stats: false, // draw scene in wireframe mode wireframe: false }; function mergeDebugSettings(settings) { Object.assign(debugSettings, settings); } // Adds a base origin to relative URLs function addBaseURL(url, base) { if (!url || !isRelativeURL(url)) { return url; } var relative_path = url[0] !== '/'; var base_info; if (base) { base_info = document.createElement('a'); // use a temporary element to parse URL base_info.href = base; } else { base_info = window.location; } if (relative_path) { var path = pathForURL(base_info.href); url = path + url; } else { var origin = base_info.origin; if (!origin) { origin = base_info.protocol + '//' + base_info.host; // IE11 doesn't have origin property } url = origin + url; } return url; } function pathForURL(url) { if (typeof url === 'string' && url.search(/^(data|blob):/) === -1) { var qs = url.indexOf('?'); if (qs > -1) { url = url.substr(0, qs); } var hash = url.indexOf('#'); if (hash > -1) { url = url.substr(0, hash); } return url.substr(0, url.lastIndexOf('/') + 1) || ''; } return ''; } function extensionForURL(url) { url = url.split('/').pop(); var last_dot = url.lastIndexOf('.'); if (last_dot > -1) { return url.substring(last_dot + 1); } } function isLocalURL(url) { if (typeof url !== 'string') { return; } return url.search(/^(data|blob):/) > -1; } function isRelativeURL(url) { if (typeof url !== 'string') { return; } return !(url.search(/^(http|https|data|blob):/) > -1 || url.substr(0, 2) === '//'); } // Resolves './' and '../' components from relative path, to get a "flattened" path function flattenRelativeURL(url) { var dirs = (url || '').split('/'); for (var d = 1; d < dirs.length; d++) { if (dirs[d] === '.') { dirs.splice(d, 1); d--; } else if (dirs[d] === '..') { d = d + 0; dirs.splice(d - 1, 2); d--; } } return dirs.join('/'); } // Add a set of query string params to a URL // params: hash of key/value pairs of query string parameters // returns array of: [modified URL, array of duplicate param name and values] function addParamsToURL(url, params) { if (!params || Object.keys(params).length === 0) { return [url, []]; } var qs_index = url.indexOf('?'); var hash_index = url.indexOf('#'); // Save and trim hash var hash = ''; if (hash_index > -1) { hash = url.slice(hash_index); url = url.slice(0, hash_index); } // Start query string if (qs_index === -1) { qs_index = url.length; url += '?'; } qs_index++; // advanced past '?' // Build query string params var url_params = ''; var dupes = []; for (var p in params) { if (getURLParameter(p, url) !== '') { dupes.push([p, params[p]]); continue; } url_params += "".concat(p, "=").concat(params[p], "&"); } // Insert new query string params and restore hash url = url.slice(0, qs_index) + url_params + url.slice(qs_index) + hash; return [url, dupes]; } // Polyfill (for Safari compatibility) var _createObjectURL; function createObjectURL(url) { if (_createObjectURL === undefined) { _createObjectURL = window.URL && window.URL.createObjectURL || window.webkitURL && window.webkitURL.createObjectURL; if (typeof _createObjectURL !== 'function') { _createObjectURL = null; log('warn', 'window.URL.createObjectURL (or vendor prefix) not found, unable to create local blob URLs'); } } if (_createObjectURL) { return _createObjectURL(url); } else { return url; } } // Via https://davidwalsh.name/query-string-javascript function getURLParameter(name, url) { name = name.replace(/[[]/, '\\[').replace(/[\]]/, '\\]'); var regex = new RegExp('[\\?&]' + name + '=([^&#]*)'); var results = regex.exec(url); return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' ')); } // import log from './log'; var Task = { id: 0, // unique id per task queue: [], // current queue of outstanding tasks max_time: 20, // default time in which all tasks should complete per frame start_time: null, // start time for tasks in current frame state: {}, // track flags about environment state (ex: whether user is currently moving the view) add: function add(task) { task.id = Task.id++; task.max_time = task.max_time || Task.max_time; // allow task to run for this much time (tasks have a global collective limit per frame, too) task.pause_factor = task.pause_factor || 1; // pause tasks by this many frames when they run too long var promise = new Promise(function (resolve, reject) { task.resolve = resolve; task.reject = reject; }); task.promise = promise; task.elapsed = 0; task.total_elapsed = 0; task.stats = { calls: 0 }; this.queue.push(task); // Run task immediately if under total frame time this.start_time = this.start_time || performance.now(); // start frame timer if necessary this.elapsed = performance.now() - this.start_time; if (this.elapsed < Task.max_time || task.immediate) { this.process(task); } return task.promise; }, remove: function remove(task) { var idx = this.queue.indexOf(task); if (idx > -1) { this.queue.splice(idx, 1); } }, process: function process(task) { // Skip task while user is moving the view, if the task requests it // (for intensive tasks that lock the UI, like canvas rasterization) if (this.state.user_moving_view && task.user_moving_view === false) { // log('debug', `*** SKIPPING task id ${task.id}, ${task.type} while user is moving view`); return; } // Skip task if it's currently paused if (task.pause) { // log('debug', `*** PAUSING task id ${task.id}, ${task.type} (${task.pause})`); task.pause--; return true; } task.stats.calls++; task.start_time = performance.now(); // start task timer return task.run(task); }, processAll: function processAll() { this.start_time = this.start_time || performance.now(); // start frame timer if necessary for (var i = 0; i < this.queue.length; i++) { // Exceeded either total task time, or total frame time var task = this.queue[i]; if (this.process(task) !== true) { // If the task didn't complete, pause it for a task-specific number of frames // (can be disabled by setting pause_factor to 0) if (!task.pause) { task.pause = task.elapsed > task.max_time ? task.pause_factor : 0; } task.total_elapsed += task.elapsed; } // Check total frame time this.elapsed = performance.now() - this.start_time; if (this.elapsed >= Task.max_time) { this.start_time = null; // reset frame timer break; } } }, finish: function finish(task, value) { task.elapsed = performance.now() - task.start_time; task.total_elapsed += task.elapsed; // log('debug', `task type ${task.type}, tile ${task.id}, finish after ${task.stats.calls} calls, ${task.total_elapsed.toFixed(2)} elapsed`); this.remove(task); task.resolve(value); return task.promise; }, cancel: function cancel(task) { var val; if (task.cancel instanceof Function) { val = task.cancel(task); // optional cancel function } task.resolve(val); }, shouldContinue: function shouldContinue(task) { // Suspend task if it runs over its specific per-frame limit, or the global limit task.elapsed = performance.now() - task.start_time; this.elapsed = performance.now() - this.start_time; return task.elapsed < task.max_time && this.elapsed < Task.max_time; }, removeForTile: function removeForTile(tile_id) { for (var idx = this.queue.length - 1; idx >= 0; idx--) { if (this.queue[idx].tile_id === tile_id) { // log('trace', `Task: remove tasks for tile ${tile_id}`); this.cancel(this.queue[idx]); this.queue.splice(idx, 1); } } }, setState: function setState(state) { this.state = state; } }; function subscribeMixin(target) { var listeners = []; return Object.assign(target, { subscribe: function subscribe(listener) { if (listeners.indexOf(listener) === -1) { listeners.push(listener); } }, unsubscribe: function unsubscribe(listener) { var index = listeners.indexOf(listener); if (index > -1) { listeners.splice(index, 1); } }, unsubscribeAll: function unsubscribeAll() { listeners = []; }, trigger: function trigger(event) { for (var _len = arguments.length, data = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { data[_key - 1] = arguments[_key]; } listeners.forEach(function (listener) { if (typeof listener[event] === 'function') { try { listener[event].apply(listener, data); } catch (e) { log('warn', "Caught exception in listener for event '".concat(event, "':"), e); } } }); }, hasSubscribersFor: function hasSubscribersFor(event) { var has = false; listeners.forEach(function (listener) { if (typeof listener[event] === 'function') { has = true; } }); return has; } }); } function sliceObject(obj, keys) { var sliced = {}; keys.forEach(function (k) { return sliced[k] = obj[k]; }); return sliced; } // GL texture wrapper object for keeping track of a global set of textures, keyed by a unique user-defined name var Texture = /*#__PURE__*/function () { function Texture(gl, name) { var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; _classCallCheck(this, Texture); options = Texture.sliceOptions(options); // exclude any non-texture-specific props this.gl = gl; this.texture = gl.createTexture(); if (this.texture) { this.valid = true; } this.bind(); this.name = name; this.retain_count = 0; this.config_type = null; this.loading = null; // a Promise object to track the loading state of this texture this.loaded = false; // successfully loaded as expected this.filtering = options.filtering; this.density = options.density || 1; // native pixel density of texture this.sprites = options.sprites; this.texcoords = {}; // sprite UVs ([0, 1] range) this.sizes = {}; // sprite sizes (pixel size) this.css_sizes = {}; // sprite sizes, adjusted for native texture pixel density this.aspects = {}; // sprite aspect ratios // Default to a 1-pixel transparent 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, 0]), { filtering: 'nearest' }); this.loaded = false; // don't consider loaded when only placeholder data is present // Destroy previous texture if present if (Texture.textures[this.name]) { // Preserve previous retain count this.retain_count = Texture.textures[this.name].retain_count; Texture.textures[this.name].retain_count = 0; // allow to be freed Texture.textures[this.name].destroy(); } // Cache texture instance and definition Texture.textures[this.name] = this; Texture.texture_configs[this.name] = JSON.stringify(Object.assign({ name: name }, options)); this.load(options); log('trace', "creating Texture ".concat(this.name)); } // Destroy a single texture instance return _createClass(Texture, [{ key: "destroy", value: function destroy() { var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, force = _ref.force; if (this.retain_count > 0 && !force) { log('error', "Texture '".concat(this.name, "': destroying texture with retain count of '").concat(this.retain_count, "'")); return; } if (!this.valid) { return; } this.gl.deleteTexture(this.texture); this.texture = null; if (Texture.textures[this.name] === this) { delete Texture.textures[this.name]; delete Texture.texture_configs[this.name]; } this.valid = false; log('trace', "destroying Texture ".concat(this.name)); } }, { key: "retain", value: function retain() { this.retain_count++; } }, { key: "release", value: function release() { if (this.retain_count <= 0) { log('error', "Texture '".concat(this.name, "': releasing texture with retain count of '").concat(this.retain_count, "'")); } this.retain_count--; if (this.retain_count <= 0) { this.destroy(); } } }, { key: "bind", value: function bind() { var unit = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0; if (!this.valid) { return; } if (Texture.activeUnit !== unit) { this.gl.activeTexture(this.gl.TEXTURE0 + unit); Texture.activeUnit = unit; Texture.boundTexture = null; // texture must be re-bound when unit changes } if (Texture.boundTexture !== this.texture) { this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture); Texture.boundTexture = this.texture; } } }, { key: "load", value: function load(options) { var _this = this; if (!options) { return this.loading || Promise.resolve(this); } this.loading = null; if (typeof options.url === 'string') { this.config_type = 'url'; this.setUrl(options.url, options); } else if (options.element) { this.config_type = 'element'; this.setElement(options.element, options); } else if (options.data && options.width && options.height) { this.config_type = 'data'; this.setData(options.width, options.height, options.data, options); } this.loading = this.loading && this.loading.then(function () { _this.calculateSprites(); return _this; }) || Promise.resolve(this); return this.loading; } // Sets texture from an url }, { key: "setUrl", value: function setUrl(url) { var _this2 = 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.loading = new Promise(function (resolve) { var image = new Image(); image.onload = function () { try { // For data URL images, first draw the image to a separate canvas element. Workaround for // obscure bug seen with small (<28px) SVG images encoded as data URLs in Chrome and Safari. if (_this2.url.slice(0, 5) === 'data:') { var canvas = document.createElement('canvas'); var ctx = canvas.getContext('2d'); canvas.width = image.width; canvas.height = image.height; ctx.drawImage(image, 0, 0); _this2.setElement(canvas, options); } else { _this2.setElement(image, options); } } catch (e) { _this2.loaded = false; log('warn', "Texture '".concat(_this2.name, "': failed to load url: '").concat(_this2.url, "'"), e, options); Texture.trigger('warning', { message: "Failed to load texture from ".concat(_this2.url), error: e, texture: options }); } _this2.loaded = true; resolve(_this2); }; image.onerror = function (e) { // Warn and resolve on error _this2.loaded = false; log('warn', "Texture '".concat(_this2.name, "': failed to load url: '"