diff --git "a/public/aframe/aframe-master.js" "b/public/aframe/aframe-master.js" new file mode 100644--- /dev/null +++ "b/public/aframe/aframe-master.js" @@ -0,0 +1,47223 @@ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(); + else if(typeof define === 'function' && define.amd) + define([], factory); + else if(typeof exports === 'object') + exports["AFRAME"] = factory(); + else + root["AFRAME"] = factory(); +})(self, () => { +return /******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ({ + +/***/ "./node_modules/@ungap/custom-elements/index.js": +/*!******************************************************!*\ + !*** ./node_modules/@ungap/custom-elements/index.js ***! + \******************************************************/ +/***/ (() => { + +/*! (c) Andrea Giammarchi @webreflection ISC */ +(function () { + 'use strict'; + + var attributesObserver = function (whenDefined, MutationObserver) { + var attributeChanged = function attributeChanged(records) { + for (var i = 0, length = records.length; i < length; i++) { + dispatch(records[i]); + } + }; + var dispatch = function dispatch(_ref) { + var target = _ref.target, + attributeName = _ref.attributeName, + oldValue = _ref.oldValue; + target.attributeChangedCallback(attributeName, oldValue, target.getAttribute(attributeName)); + }; + return function (target, is) { + var attributeFilter = target.constructor.observedAttributes; + if (attributeFilter) { + whenDefined(is).then(function () { + new MutationObserver(attributeChanged).observe(target, { + attributes: true, + attributeOldValue: true, + attributeFilter: attributeFilter + }); + for (var i = 0, length = attributeFilter.length; i < length; i++) { + if (target.hasAttribute(attributeFilter[i])) dispatch({ + target: target, + attributeName: attributeFilter[i], + oldValue: null + }); + } + }); + } + return target; + }; + }; + function _unsupportedIterableToArray(o, minLen) { + if (!o) return; + if (typeof o === "string") return _arrayLikeToArray(o, minLen); + var n = Object.prototype.toString.call(o).slice(8, -1); + if (n === "Object" && o.constructor) n = o.constructor.name; + if (n === "Map" || n === "Set") return Array.from(o); + if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); + } + function _arrayLikeToArray(arr, len) { + if (len == null || len > arr.length) len = arr.length; + for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; + return arr2; + } + function _createForOfIteratorHelper(o, allowArrayLike) { + var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; + if (!it) { + if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { + if (it) o = it; + var i = 0; + var F = function () {}; + return { + s: F, + n: function () { + if (i >= o.length) return { + done: true + }; + return { + done: false, + value: o[i++] + }; + }, + e: function (e) { + throw e; + }, + f: F + }; + } + throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); + } + var normalCompletion = true, + didErr = false, + err; + return { + s: function () { + it = it.call(o); + }, + n: function () { + var step = it.next(); + normalCompletion = step.done; + return step; + }, + e: function (e) { + didErr = true; + err = e; + }, + f: function () { + try { + if (!normalCompletion && it.return != null) it.return(); + } finally { + if (didErr) throw err; + } + } + }; + } + + /*! (c) Andrea Giammarchi - ISC */ + var TRUE = true, + FALSE = false, + QSA$1 = 'querySelectorAll'; + + /** + * Start observing a generic document or root element. + * @param {(node:Element, connected:boolean) => void} callback triggered per each dis/connected element + * @param {Document|Element} [root=document] by default, the global document to observe + * @param {Function} [MO=MutationObserver] by default, the global MutationObserver + * @param {string[]} [query=['*']] the selectors to use within nodes + * @returns {MutationObserver} + */ + var notify = function notify(callback) { + var root = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : document; + var MO = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : MutationObserver; + var query = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : ['*']; + var loop = function loop(nodes, selectors, added, removed, connected, pass) { + var _iterator = _createForOfIteratorHelper(nodes), + _step; + try { + for (_iterator.s(); !(_step = _iterator.n()).done;) { + var node = _step.value; + if (pass || QSA$1 in node) { + if (connected) { + if (!added.has(node)) { + added.add(node); + removed["delete"](node); + callback(node, connected); + } + } else if (!removed.has(node)) { + removed.add(node); + added["delete"](node); + callback(node, connected); + } + if (!pass) loop(node[QSA$1](selectors), selectors, added, removed, connected, TRUE); + } + } + } catch (err) { + _iterator.e(err); + } finally { + _iterator.f(); + } + }; + var mo = new MO(function (records) { + if (query.length) { + var selectors = query.join(','); + var added = new Set(), + removed = new Set(); + var _iterator2 = _createForOfIteratorHelper(records), + _step2; + try { + for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) { + var _step2$value = _step2.value, + addedNodes = _step2$value.addedNodes, + removedNodes = _step2$value.removedNodes; + loop(removedNodes, selectors, added, removed, FALSE, FALSE); + loop(addedNodes, selectors, added, removed, TRUE, FALSE); + } + } catch (err) { + _iterator2.e(err); + } finally { + _iterator2.f(); + } + } + }); + var observe = mo.observe; + (mo.observe = function (node) { + return observe.call(mo, node, { + subtree: TRUE, + childList: TRUE + }); + })(root); + return mo; + }; + var QSA = 'querySelectorAll'; + var _self$1 = self, + document$2 = _self$1.document, + Element$1 = _self$1.Element, + MutationObserver$2 = _self$1.MutationObserver, + Set$2 = _self$1.Set, + WeakMap$1 = _self$1.WeakMap; + var elements = function elements(element) { + return QSA in element; + }; + var filter = [].filter; + var qsaObserver = function (options) { + var live = new WeakMap$1(); + var drop = function drop(elements) { + for (var i = 0, length = elements.length; i < length; i++) { + live["delete"](elements[i]); + } + }; + var flush = function flush() { + var records = observer.takeRecords(); + for (var i = 0, length = records.length; i < length; i++) { + parse(filter.call(records[i].removedNodes, elements), false); + parse(filter.call(records[i].addedNodes, elements), true); + } + }; + var matches = function matches(element) { + return element.matches || element.webkitMatchesSelector || element.msMatchesSelector; + }; + var notifier = function notifier(element, connected) { + var selectors; + if (connected) { + for (var q, m = matches(element), i = 0, length = query.length; i < length; i++) { + if (m.call(element, q = query[i])) { + if (!live.has(element)) live.set(element, new Set$2()); + selectors = live.get(element); + if (!selectors.has(q)) { + selectors.add(q); + options.handle(element, connected, q); + } + } + } + } else if (live.has(element)) { + selectors = live.get(element); + live["delete"](element); + selectors.forEach(function (q) { + options.handle(element, connected, q); + }); + } + }; + var parse = function parse(elements) { + var connected = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; + for (var i = 0, length = elements.length; i < length; i++) { + notifier(elements[i], connected); + } + }; + var query = options.query; + var root = options.root || document$2; + var observer = notify(notifier, root, MutationObserver$2, query); + var attachShadow = Element$1.prototype.attachShadow; + if (attachShadow) Element$1.prototype.attachShadow = function (init) { + var shadowRoot = attachShadow.call(this, init); + observer.observe(shadowRoot); + return shadowRoot; + }; + if (query.length) parse(root[QSA](query)); + return { + drop: drop, + flush: flush, + observer: observer, + parse: parse + }; + }; + var _self = self, + document$1 = _self.document, + Map = _self.Map, + MutationObserver$1 = _self.MutationObserver, + Object$1 = _self.Object, + Set$1 = _self.Set, + WeakMap = _self.WeakMap, + Element = _self.Element, + HTMLElement = _self.HTMLElement, + Node = _self.Node, + Error = _self.Error, + TypeError$1 = _self.TypeError, + Reflect = _self.Reflect; + var defineProperty = Object$1.defineProperty, + keys = Object$1.keys, + getOwnPropertyNames = Object$1.getOwnPropertyNames, + setPrototypeOf = Object$1.setPrototypeOf; + var legacy = !self.customElements; + var expando = function expando(element) { + var key = keys(element); + var value = []; + var length = key.length; + for (var i = 0; i < length; i++) { + value[i] = element[key[i]]; + delete element[key[i]]; + } + return function () { + for (var _i = 0; _i < length; _i++) { + element[key[_i]] = value[_i]; + } + }; + }; + if (legacy) { + var HTMLBuiltIn = function HTMLBuiltIn() { + var constructor = this.constructor; + if (!classes.has(constructor)) throw new TypeError$1('Illegal constructor'); + var is = classes.get(constructor); + if (override) return augment(override, is); + var element = createElement.call(document$1, is); + return augment(setPrototypeOf(element, constructor.prototype), is); + }; + var createElement = document$1.createElement; + var classes = new Map(); + var defined = new Map(); + var prototypes = new Map(); + var registry = new Map(); + var query = []; + var handle = function handle(element, connected, selector) { + var proto = prototypes.get(selector); + if (connected && !proto.isPrototypeOf(element)) { + var redefine = expando(element); + override = setPrototypeOf(element, proto); + try { + new proto.constructor(); + } finally { + override = null; + redefine(); + } + } + var method = "".concat(connected ? '' : 'dis', "connectedCallback"); + if (method in proto) element[method](); + }; + var _qsaObserver = qsaObserver({ + query: query, + handle: handle + }), + parse = _qsaObserver.parse; + var override = null; + var whenDefined = function whenDefined(name) { + if (!defined.has(name)) { + var _, + $ = new Promise(function ($) { + _ = $; + }); + defined.set(name, { + $: $, + _: _ + }); + } + return defined.get(name).$; + }; + var augment = attributesObserver(whenDefined, MutationObserver$1); + defineProperty(self, 'customElements', { + configurable: true, + value: { + define: function define(is, Class) { + if (registry.has(is)) throw new Error("the name \"".concat(is, "\" has already been used with this registry")); + classes.set(Class, is); + prototypes.set(is, Class.prototype); + registry.set(is, Class); + query.push(is); + whenDefined(is).then(function () { + parse(document$1.querySelectorAll(is)); + }); + defined.get(is)._(Class); + }, + get: function get(is) { + return registry.get(is); + }, + whenDefined: whenDefined + } + }); + defineProperty(HTMLBuiltIn.prototype = HTMLElement.prototype, 'constructor', { + value: HTMLBuiltIn + }); + defineProperty(self, 'HTMLElement', { + configurable: true, + value: HTMLBuiltIn + }); + defineProperty(document$1, 'createElement', { + configurable: true, + value: function value(name, options) { + var is = options && options.is; + var Class = is ? registry.get(is) : registry.get(name); + return Class ? new Class() : createElement.call(document$1, name); + } + }); + // in case ShadowDOM is used through a polyfill, to avoid issues + // with builtin extends within shadow roots + if (!('isConnected' in Node.prototype)) defineProperty(Node.prototype, 'isConnected', { + configurable: true, + get: function get() { + return !(this.ownerDocument.compareDocumentPosition(this) & this.DOCUMENT_POSITION_DISCONNECTED); + } + }); + } else { + legacy = !self.customElements.get('extends-li'); + if (legacy) { + try { + var LI = function LI() { + return self.Reflect.construct(HTMLLIElement, [], LI); + }; + LI.prototype = HTMLLIElement.prototype; + var is = 'extends-li'; + self.customElements.define('extends-li', LI, { + 'extends': 'li' + }); + legacy = document$1.createElement('li', { + is: is + }).outerHTML.indexOf(is) < 0; + var _self$customElements = self.customElements, + get = _self$customElements.get, + _whenDefined = _self$customElements.whenDefined; + defineProperty(self.customElements, 'whenDefined', { + configurable: true, + value: function value(is) { + var _this = this; + return _whenDefined.call(this, is).then(function (Class) { + return Class || get.call(_this, is); + }); + } + }); + } catch (o_O) {} + } + } + if (legacy) { + var parseShadow = function parseShadow(element) { + var root = shadowRoots.get(element); + _parse(root.querySelectorAll(this), element.isConnected); + }; + var customElements = self.customElements; + var _createElement = document$1.createElement; + var define = customElements.define, + _get = customElements.get, + upgrade = customElements.upgrade; + var _ref = Reflect || { + construct: function construct(HTMLElement) { + return HTMLElement.call(this); + } + }, + construct = _ref.construct; + var shadowRoots = new WeakMap(); + var shadows = new Set$1(); + var _classes = new Map(); + var _defined = new Map(); + var _prototypes = new Map(); + var _registry = new Map(); + var shadowed = []; + var _query = []; + var getCE = function getCE(is) { + return _registry.get(is) || _get.call(customElements, is); + }; + var _handle = function _handle(element, connected, selector) { + var proto = _prototypes.get(selector); + if (connected && !proto.isPrototypeOf(element)) { + var redefine = expando(element); + _override = setPrototypeOf(element, proto); + try { + new proto.constructor(); + } finally { + _override = null; + redefine(); + } + } + var method = "".concat(connected ? '' : 'dis', "connectedCallback"); + if (method in proto) element[method](); + }; + var _qsaObserver2 = qsaObserver({ + query: _query, + handle: _handle + }), + _parse = _qsaObserver2.parse; + var _qsaObserver3 = qsaObserver({ + query: shadowed, + handle: function handle(element, connected) { + if (shadowRoots.has(element)) { + if (connected) shadows.add(element);else shadows["delete"](element); + if (_query.length) parseShadow.call(_query, element); + } + } + }), + parseShadowed = _qsaObserver3.parse; + // qsaObserver also patches attachShadow + // be sure this runs *after* that + var attachShadow = Element.prototype.attachShadow; + if (attachShadow) Element.prototype.attachShadow = function (init) { + var root = attachShadow.call(this, init); + shadowRoots.set(this, root); + return root; + }; + var _whenDefined2 = function _whenDefined2(name) { + if (!_defined.has(name)) { + var _, + $ = new Promise(function ($) { + _ = $; + }); + _defined.set(name, { + $: $, + _: _ + }); + } + return _defined.get(name).$; + }; + var _augment = attributesObserver(_whenDefined2, MutationObserver$1); + var _override = null; + getOwnPropertyNames(self).filter(function (k) { + return /^HTML.*Element$/.test(k); + }).forEach(function (k) { + var HTMLElement = self[k]; + function HTMLBuiltIn() { + var constructor = this.constructor; + if (!_classes.has(constructor)) throw new TypeError$1('Illegal constructor'); + var _classes$get = _classes.get(constructor), + is = _classes$get.is, + tag = _classes$get.tag; + if (is) { + if (_override) return _augment(_override, is); + var element = _createElement.call(document$1, tag); + element.setAttribute('is', is); + return _augment(setPrototypeOf(element, constructor.prototype), is); + } else return construct.call(this, HTMLElement, [], constructor); + } + defineProperty(HTMLBuiltIn.prototype = HTMLElement.prototype, 'constructor', { + value: HTMLBuiltIn + }); + defineProperty(self, k, { + value: HTMLBuiltIn + }); + }); + defineProperty(document$1, 'createElement', { + configurable: true, + value: function value(name, options) { + var is = options && options.is; + if (is) { + var Class = _registry.get(is); + if (Class && _classes.get(Class).tag === name) return new Class(); + } + var element = _createElement.call(document$1, name); + if (is) element.setAttribute('is', is); + return element; + } + }); + defineProperty(customElements, 'get', { + configurable: true, + value: getCE + }); + defineProperty(customElements, 'whenDefined', { + configurable: true, + value: _whenDefined2 + }); + defineProperty(customElements, 'upgrade', { + configurable: true, + value: function value(element) { + var is = element.getAttribute('is'); + if (is) { + var _constructor = _registry.get(is); + if (_constructor) { + _augment(setPrototypeOf(element, _constructor.prototype), is); + // apparently unnecessary because this is handled by qsa observer + // if (element.isConnected && element.connectedCallback) + // element.connectedCallback(); + return; + } + } + upgrade.call(customElements, element); + } + }); + defineProperty(customElements, 'define', { + configurable: true, + value: function value(is, Class, options) { + if (getCE(is)) throw new Error("'".concat(is, "' has already been defined as a custom element")); + var selector; + var tag = options && options["extends"]; + _classes.set(Class, tag ? { + is: is, + tag: tag + } : { + is: '', + tag: is + }); + if (tag) { + selector = "".concat(tag, "[is=\"").concat(is, "\"]"); + _prototypes.set(selector, Class.prototype); + _registry.set(is, Class); + _query.push(selector); + } else { + define.apply(customElements, arguments); + shadowed.push(selector = is); + } + _whenDefined2(is).then(function () { + if (tag) { + _parse(document$1.querySelectorAll(selector)); + shadows.forEach(parseShadow, [selector]); + } else parseShadowed(document$1.querySelectorAll(selector)); + }); + _defined.get(is)._(Class); + } + }); + } +})(); + +/***/ }), + +/***/ "./node_modules/an-array/index.js": +/*!****************************************!*\ + !*** ./node_modules/an-array/index.js ***! + \****************************************/ +/***/ ((module) => { + +var str = Object.prototype.toString; +module.exports = anArray; +function anArray(arr) { + return arr.BYTES_PER_ELEMENT && str.call(arr.buffer) === '[object ArrayBuffer]' || Array.isArray(arr); +} + +/***/ }), + +/***/ "./node_modules/as-number/index.js": +/*!*****************************************!*\ + !*** ./node_modules/as-number/index.js ***! + \*****************************************/ +/***/ ((module) => { + +module.exports = function numtype(num, def) { + return typeof num === 'number' ? num : typeof def === 'number' ? def : 0; +}; + +/***/ }), + +/***/ "./node_modules/base64-js/index.js": +/*!*****************************************!*\ + !*** ./node_modules/base64-js/index.js ***! + \*****************************************/ +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +exports.byteLength = byteLength; +exports.toByteArray = toByteArray; +exports.fromByteArray = fromByteArray; +var lookup = []; +var revLookup = []; +var Arr = typeof Uint8Array !== 'undefined' ? Uint8Array : Array; +var code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; +for (var i = 0, len = code.length; i < len; ++i) { + lookup[i] = code[i]; + revLookup[code.charCodeAt(i)] = i; +} + +// Support decoding URL-safe base64 strings, as Node.js does. +// See: https://en.wikipedia.org/wiki/Base64#URL_applications +revLookup['-'.charCodeAt(0)] = 62; +revLookup['_'.charCodeAt(0)] = 63; +function getLens(b64) { + var len = b64.length; + if (len % 4 > 0) { + throw new Error('Invalid string. Length must be a multiple of 4'); + } + + // Trim off extra bytes after placeholder bytes are found + // See: https://github.com/beatgammit/base64-js/issues/42 + var validLen = b64.indexOf('='); + if (validLen === -1) validLen = len; + var placeHoldersLen = validLen === len ? 0 : 4 - validLen % 4; + return [validLen, placeHoldersLen]; +} + +// base64 is 4/3 + up to two characters of the original data +function byteLength(b64) { + var lens = getLens(b64); + var validLen = lens[0]; + var placeHoldersLen = lens[1]; + return (validLen + placeHoldersLen) * 3 / 4 - placeHoldersLen; +} +function _byteLength(b64, validLen, placeHoldersLen) { + return (validLen + placeHoldersLen) * 3 / 4 - placeHoldersLen; +} +function toByteArray(b64) { + var tmp; + var lens = getLens(b64); + var validLen = lens[0]; + var placeHoldersLen = lens[1]; + var arr = new Arr(_byteLength(b64, validLen, placeHoldersLen)); + var curByte = 0; + + // if there are placeholders, only get up to the last complete 4 chars + var len = placeHoldersLen > 0 ? validLen - 4 : validLen; + var i; + for (i = 0; i < len; i += 4) { + tmp = revLookup[b64.charCodeAt(i)] << 18 | revLookup[b64.charCodeAt(i + 1)] << 12 | revLookup[b64.charCodeAt(i + 2)] << 6 | revLookup[b64.charCodeAt(i + 3)]; + arr[curByte++] = tmp >> 16 & 0xFF; + arr[curByte++] = tmp >> 8 & 0xFF; + arr[curByte++] = tmp & 0xFF; + } + if (placeHoldersLen === 2) { + tmp = revLookup[b64.charCodeAt(i)] << 2 | revLookup[b64.charCodeAt(i + 1)] >> 4; + arr[curByte++] = tmp & 0xFF; + } + if (placeHoldersLen === 1) { + tmp = revLookup[b64.charCodeAt(i)] << 10 | revLookup[b64.charCodeAt(i + 1)] << 4 | revLookup[b64.charCodeAt(i + 2)] >> 2; + arr[curByte++] = tmp >> 8 & 0xFF; + arr[curByte++] = tmp & 0xFF; + } + return arr; +} +function tripletToBase64(num) { + return lookup[num >> 18 & 0x3F] + lookup[num >> 12 & 0x3F] + lookup[num >> 6 & 0x3F] + lookup[num & 0x3F]; +} +function encodeChunk(uint8, start, end) { + var tmp; + var output = []; + for (var i = start; i < end; i += 3) { + tmp = (uint8[i] << 16 & 0xFF0000) + (uint8[i + 1] << 8 & 0xFF00) + (uint8[i + 2] & 0xFF); + output.push(tripletToBase64(tmp)); + } + return output.join(''); +} +function fromByteArray(uint8) { + var tmp; + var len = uint8.length; + var extraBytes = len % 3; // if we have 1 byte left, pad 2 bytes + var parts = []; + var maxChunkLength = 16383; // must be multiple of 3 + + // go through the array every three bytes, we'll deal with trailing stuff later + for (var i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) { + parts.push(encodeChunk(uint8, i, i + maxChunkLength > len2 ? len2 : i + maxChunkLength)); + } + + // pad the end with zeros, but make sure to not forget the extra bytes + if (extraBytes === 1) { + tmp = uint8[len - 1]; + parts.push(lookup[tmp >> 2] + lookup[tmp << 4 & 0x3F] + '=='); + } else if (extraBytes === 2) { + tmp = (uint8[len - 2] << 8) + uint8[len - 1]; + parts.push(lookup[tmp >> 10] + lookup[tmp >> 4 & 0x3F] + lookup[tmp << 2 & 0x3F] + '='); + } + return parts.join(''); +} + +/***/ }), + +/***/ "./node_modules/buffer-equal/index.js": +/*!********************************************!*\ + !*** ./node_modules/buffer-equal/index.js ***! + \********************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var Buffer = (__webpack_require__(/*! buffer */ "./node_modules/buffer/index.js").Buffer); // for use with browserify + +module.exports = function (a, b) { + if (!Buffer.isBuffer(a)) return undefined; + if (!Buffer.isBuffer(b)) return undefined; + if (typeof a.equals === 'function') return a.equals(b); + if (a.length !== b.length) return false; + for (var i = 0; i < a.length; i++) { + if (a[i] !== b[i]) return false; + } + return true; +}; + +/***/ }), + +/***/ "./node_modules/buffer/index.js": +/*!**************************************!*\ + !*** ./node_modules/buffer/index.js ***! + \**************************************/ +/***/ ((__unused_webpack_module, exports, __webpack_require__) => { + +"use strict"; +/*! + * The buffer module from node.js, for the browser. + * + * @author Feross Aboukhadijeh + * @license MIT + */ +/* eslint-disable no-proto */ + + + +const base64 = __webpack_require__(/*! base64-js */ "./node_modules/base64-js/index.js"); +const ieee754 = __webpack_require__(/*! ieee754 */ "./node_modules/ieee754/index.js"); +const customInspectSymbol = typeof Symbol === 'function' && typeof Symbol['for'] === 'function' // eslint-disable-line dot-notation +? Symbol['for']('nodejs.util.inspect.custom') // eslint-disable-line dot-notation +: null; +exports.Buffer = Buffer; +exports.SlowBuffer = SlowBuffer; +exports.INSPECT_MAX_BYTES = 50; +const K_MAX_LENGTH = 0x7fffffff; +exports.kMaxLength = K_MAX_LENGTH; + +/** + * If `Buffer.TYPED_ARRAY_SUPPORT`: + * === true Use Uint8Array implementation (fastest) + * === false Print warning and recommend using `buffer` v4.x which has an Object + * implementation (most compatible, even IE6) + * + * Browsers that support typed arrays are IE 10+, Firefox 4+, Chrome 7+, Safari 5.1+, + * Opera 11.6+, iOS 4.2+. + * + * We report that the browser does not support typed arrays if the are not subclassable + * using __proto__. Firefox 4-29 lacks support for adding new properties to `Uint8Array` + * (See: https://bugzilla.mozilla.org/show_bug.cgi?id=695438). IE 10 lacks support + * for __proto__ and has a buggy typed array implementation. + */ +Buffer.TYPED_ARRAY_SUPPORT = typedArraySupport(); +if (!Buffer.TYPED_ARRAY_SUPPORT && typeof console !== 'undefined' && typeof console.error === 'function') { + console.error('This browser lacks typed array (Uint8Array) support which is required by ' + '`buffer` v5.x. Use `buffer` v4.x if you require old browser support.'); +} +function typedArraySupport() { + // Can typed array instances can be augmented? + try { + const arr = new Uint8Array(1); + const proto = { + foo: function () { + return 42; + } + }; + Object.setPrototypeOf(proto, Uint8Array.prototype); + Object.setPrototypeOf(arr, proto); + return arr.foo() === 42; + } catch (e) { + return false; + } +} +Object.defineProperty(Buffer.prototype, 'parent', { + enumerable: true, + get: function () { + if (!Buffer.isBuffer(this)) return undefined; + return this.buffer; + } +}); +Object.defineProperty(Buffer.prototype, 'offset', { + enumerable: true, + get: function () { + if (!Buffer.isBuffer(this)) return undefined; + return this.byteOffset; + } +}); +function createBuffer(length) { + if (length > K_MAX_LENGTH) { + throw new RangeError('The value "' + length + '" is invalid for option "size"'); + } + // Return an augmented `Uint8Array` instance + const buf = new Uint8Array(length); + Object.setPrototypeOf(buf, Buffer.prototype); + return buf; +} + +/** + * The Buffer constructor returns instances of `Uint8Array` that have their + * prototype changed to `Buffer.prototype`. Furthermore, `Buffer` is a subclass of + * `Uint8Array`, so the returned instances will have all the node `Buffer` methods + * and the `Uint8Array` methods. Square bracket notation works as expected -- it + * returns a single octet. + * + * The `Uint8Array` prototype remains unmodified. + */ + +function Buffer(arg, encodingOrOffset, length) { + // Common case. + if (typeof arg === 'number') { + if (typeof encodingOrOffset === 'string') { + throw new TypeError('The "string" argument must be of type string. Received type number'); + } + return allocUnsafe(arg); + } + return from(arg, encodingOrOffset, length); +} +Buffer.poolSize = 8192; // not used by this implementation + +function from(value, encodingOrOffset, length) { + if (typeof value === 'string') { + return fromString(value, encodingOrOffset); + } + if (ArrayBuffer.isView(value)) { + return fromArrayView(value); + } + if (value == null) { + throw new TypeError('The first argument must be one of type string, Buffer, ArrayBuffer, Array, ' + 'or Array-like Object. Received type ' + typeof value); + } + if (isInstance(value, ArrayBuffer) || value && isInstance(value.buffer, ArrayBuffer)) { + return fromArrayBuffer(value, encodingOrOffset, length); + } + if (typeof SharedArrayBuffer !== 'undefined' && (isInstance(value, SharedArrayBuffer) || value && isInstance(value.buffer, SharedArrayBuffer))) { + return fromArrayBuffer(value, encodingOrOffset, length); + } + if (typeof value === 'number') { + throw new TypeError('The "value" argument must not be of type number. Received type number'); + } + const valueOf = value.valueOf && value.valueOf(); + if (valueOf != null && valueOf !== value) { + return Buffer.from(valueOf, encodingOrOffset, length); + } + const b = fromObject(value); + if (b) return b; + if (typeof Symbol !== 'undefined' && Symbol.toPrimitive != null && typeof value[Symbol.toPrimitive] === 'function') { + return Buffer.from(value[Symbol.toPrimitive]('string'), encodingOrOffset, length); + } + throw new TypeError('The first argument must be one of type string, Buffer, ArrayBuffer, Array, ' + 'or Array-like Object. Received type ' + typeof value); +} + +/** + * Functionally equivalent to Buffer(arg, encoding) but throws a TypeError + * if value is a number. + * Buffer.from(str[, encoding]) + * Buffer.from(array) + * Buffer.from(buffer) + * Buffer.from(arrayBuffer[, byteOffset[, length]]) + **/ +Buffer.from = function (value, encodingOrOffset, length) { + return from(value, encodingOrOffset, length); +}; + +// Note: Change prototype *after* Buffer.from is defined to workaround Chrome bug: +// https://github.com/feross/buffer/pull/148 +Object.setPrototypeOf(Buffer.prototype, Uint8Array.prototype); +Object.setPrototypeOf(Buffer, Uint8Array); +function assertSize(size) { + if (typeof size !== 'number') { + throw new TypeError('"size" argument must be of type number'); + } else if (size < 0) { + throw new RangeError('The value "' + size + '" is invalid for option "size"'); + } +} +function alloc(size, fill, encoding) { + assertSize(size); + if (size <= 0) { + return createBuffer(size); + } + if (fill !== undefined) { + // Only pay attention to encoding if it's a string. This + // prevents accidentally sending in a number that would + // be interpreted as a start offset. + return typeof encoding === 'string' ? createBuffer(size).fill(fill, encoding) : createBuffer(size).fill(fill); + } + return createBuffer(size); +} + +/** + * Creates a new filled Buffer instance. + * alloc(size[, fill[, encoding]]) + **/ +Buffer.alloc = function (size, fill, encoding) { + return alloc(size, fill, encoding); +}; +function allocUnsafe(size) { + assertSize(size); + return createBuffer(size < 0 ? 0 : checked(size) | 0); +} + +/** + * Equivalent to Buffer(num), by default creates a non-zero-filled Buffer instance. + * */ +Buffer.allocUnsafe = function (size) { + return allocUnsafe(size); +}; +/** + * Equivalent to SlowBuffer(num), by default creates a non-zero-filled Buffer instance. + */ +Buffer.allocUnsafeSlow = function (size) { + return allocUnsafe(size); +}; +function fromString(string, encoding) { + if (typeof encoding !== 'string' || encoding === '') { + encoding = 'utf8'; + } + if (!Buffer.isEncoding(encoding)) { + throw new TypeError('Unknown encoding: ' + encoding); + } + const length = byteLength(string, encoding) | 0; + let buf = createBuffer(length); + const actual = buf.write(string, encoding); + if (actual !== length) { + // Writing a hex string, for example, that contains invalid characters will + // cause everything after the first invalid character to be ignored. (e.g. + // 'abxxcd' will be treated as 'ab') + buf = buf.slice(0, actual); + } + return buf; +} +function fromArrayLike(array) { + const length = array.length < 0 ? 0 : checked(array.length) | 0; + const buf = createBuffer(length); + for (let i = 0; i < length; i += 1) { + buf[i] = array[i] & 255; + } + return buf; +} +function fromArrayView(arrayView) { + if (isInstance(arrayView, Uint8Array)) { + const copy = new Uint8Array(arrayView); + return fromArrayBuffer(copy.buffer, copy.byteOffset, copy.byteLength); + } + return fromArrayLike(arrayView); +} +function fromArrayBuffer(array, byteOffset, length) { + if (byteOffset < 0 || array.byteLength < byteOffset) { + throw new RangeError('"offset" is outside of buffer bounds'); + } + if (array.byteLength < byteOffset + (length || 0)) { + throw new RangeError('"length" is outside of buffer bounds'); + } + let buf; + if (byteOffset === undefined && length === undefined) { + buf = new Uint8Array(array); + } else if (length === undefined) { + buf = new Uint8Array(array, byteOffset); + } else { + buf = new Uint8Array(array, byteOffset, length); + } + + // Return an augmented `Uint8Array` instance + Object.setPrototypeOf(buf, Buffer.prototype); + return buf; +} +function fromObject(obj) { + if (Buffer.isBuffer(obj)) { + const len = checked(obj.length) | 0; + const buf = createBuffer(len); + if (buf.length === 0) { + return buf; + } + obj.copy(buf, 0, 0, len); + return buf; + } + if (obj.length !== undefined) { + if (typeof obj.length !== 'number' || numberIsNaN(obj.length)) { + return createBuffer(0); + } + return fromArrayLike(obj); + } + if (obj.type === 'Buffer' && Array.isArray(obj.data)) { + return fromArrayLike(obj.data); + } +} +function checked(length) { + // Note: cannot use `length < K_MAX_LENGTH` here because that fails when + // length is NaN (which is otherwise coerced to zero.) + if (length >= K_MAX_LENGTH) { + throw new RangeError('Attempt to allocate Buffer larger than maximum ' + 'size: 0x' + K_MAX_LENGTH.toString(16) + ' bytes'); + } + return length | 0; +} +function SlowBuffer(length) { + if (+length != length) { + // eslint-disable-line eqeqeq + length = 0; + } + return Buffer.alloc(+length); +} +Buffer.isBuffer = function isBuffer(b) { + return b != null && b._isBuffer === true && b !== Buffer.prototype; // so Buffer.isBuffer(Buffer.prototype) will be false +}; + +Buffer.compare = function compare(a, b) { + if (isInstance(a, Uint8Array)) a = Buffer.from(a, a.offset, a.byteLength); + if (isInstance(b, Uint8Array)) b = Buffer.from(b, b.offset, b.byteLength); + if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) { + throw new TypeError('The "buf1", "buf2" arguments must be one of type Buffer or Uint8Array'); + } + if (a === b) return 0; + let x = a.length; + let y = b.length; + for (let i = 0, len = Math.min(x, y); i < len; ++i) { + if (a[i] !== b[i]) { + x = a[i]; + y = b[i]; + break; + } + } + if (x < y) return -1; + if (y < x) return 1; + return 0; +}; +Buffer.isEncoding = function isEncoding(encoding) { + switch (String(encoding).toLowerCase()) { + case 'hex': + case 'utf8': + case 'utf-8': + case 'ascii': + case 'latin1': + case 'binary': + case 'base64': + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return true; + default: + return false; + } +}; +Buffer.concat = function concat(list, length) { + if (!Array.isArray(list)) { + throw new TypeError('"list" argument must be an Array of Buffers'); + } + if (list.length === 0) { + return Buffer.alloc(0); + } + let i; + if (length === undefined) { + length = 0; + for (i = 0; i < list.length; ++i) { + length += list[i].length; + } + } + const buffer = Buffer.allocUnsafe(length); + let pos = 0; + for (i = 0; i < list.length; ++i) { + let buf = list[i]; + if (isInstance(buf, Uint8Array)) { + if (pos + buf.length > buffer.length) { + if (!Buffer.isBuffer(buf)) buf = Buffer.from(buf); + buf.copy(buffer, pos); + } else { + Uint8Array.prototype.set.call(buffer, buf, pos); + } + } else if (!Buffer.isBuffer(buf)) { + throw new TypeError('"list" argument must be an Array of Buffers'); + } else { + buf.copy(buffer, pos); + } + pos += buf.length; + } + return buffer; +}; +function byteLength(string, encoding) { + if (Buffer.isBuffer(string)) { + return string.length; + } + if (ArrayBuffer.isView(string) || isInstance(string, ArrayBuffer)) { + return string.byteLength; + } + if (typeof string !== 'string') { + throw new TypeError('The "string" argument must be one of type string, Buffer, or ArrayBuffer. ' + 'Received type ' + typeof string); + } + const len = string.length; + const mustMatch = arguments.length > 2 && arguments[2] === true; + if (!mustMatch && len === 0) return 0; + + // Use a for loop to avoid recursion + let loweredCase = false; + for (;;) { + switch (encoding) { + case 'ascii': + case 'latin1': + case 'binary': + return len; + case 'utf8': + case 'utf-8': + return utf8ToBytes(string).length; + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return len * 2; + case 'hex': + return len >>> 1; + case 'base64': + return base64ToBytes(string).length; + default: + if (loweredCase) { + return mustMatch ? -1 : utf8ToBytes(string).length; // assume utf8 + } + + encoding = ('' + encoding).toLowerCase(); + loweredCase = true; + } + } +} +Buffer.byteLength = byteLength; +function slowToString(encoding, start, end) { + let loweredCase = false; + + // No need to verify that "this.length <= MAX_UINT32" since it's a read-only + // property of a typed array. + + // This behaves neither like String nor Uint8Array in that we set start/end + // to their upper/lower bounds if the value passed is out of range. + // undefined is handled specially as per ECMA-262 6th Edition, + // Section 13.3.3.7 Runtime Semantics: KeyedBindingInitialization. + if (start === undefined || start < 0) { + start = 0; + } + // Return early if start > this.length. Done here to prevent potential uint32 + // coercion fail below. + if (start > this.length) { + return ''; + } + if (end === undefined || end > this.length) { + end = this.length; + } + if (end <= 0) { + return ''; + } + + // Force coercion to uint32. This will also coerce falsey/NaN values to 0. + end >>>= 0; + start >>>= 0; + if (end <= start) { + return ''; + } + if (!encoding) encoding = 'utf8'; + while (true) { + switch (encoding) { + case 'hex': + return hexSlice(this, start, end); + case 'utf8': + case 'utf-8': + return utf8Slice(this, start, end); + case 'ascii': + return asciiSlice(this, start, end); + case 'latin1': + case 'binary': + return latin1Slice(this, start, end); + case 'base64': + return base64Slice(this, start, end); + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return utf16leSlice(this, start, end); + default: + if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding); + encoding = (encoding + '').toLowerCase(); + loweredCase = true; + } + } +} + +// This property is used by `Buffer.isBuffer` (and the `is-buffer` npm package) +// to detect a Buffer instance. It's not possible to use `instanceof Buffer` +// reliably in a browserify context because there could be multiple different +// copies of the 'buffer' package in use. This method works even for Buffer +// instances that were created from another copy of the `buffer` package. +// See: https://github.com/feross/buffer/issues/154 +Buffer.prototype._isBuffer = true; +function swap(b, n, m) { + const i = b[n]; + b[n] = b[m]; + b[m] = i; +} +Buffer.prototype.swap16 = function swap16() { + const len = this.length; + if (len % 2 !== 0) { + throw new RangeError('Buffer size must be a multiple of 16-bits'); + } + for (let i = 0; i < len; i += 2) { + swap(this, i, i + 1); + } + return this; +}; +Buffer.prototype.swap32 = function swap32() { + const len = this.length; + if (len % 4 !== 0) { + throw new RangeError('Buffer size must be a multiple of 32-bits'); + } + for (let i = 0; i < len; i += 4) { + swap(this, i, i + 3); + swap(this, i + 1, i + 2); + } + return this; +}; +Buffer.prototype.swap64 = function swap64() { + const len = this.length; + if (len % 8 !== 0) { + throw new RangeError('Buffer size must be a multiple of 64-bits'); + } + for (let i = 0; i < len; i += 8) { + swap(this, i, i + 7); + swap(this, i + 1, i + 6); + swap(this, i + 2, i + 5); + swap(this, i + 3, i + 4); + } + return this; +}; +Buffer.prototype.toString = function toString() { + const length = this.length; + if (length === 0) return ''; + if (arguments.length === 0) return utf8Slice(this, 0, length); + return slowToString.apply(this, arguments); +}; +Buffer.prototype.toLocaleString = Buffer.prototype.toString; +Buffer.prototype.equals = function equals(b) { + if (!Buffer.isBuffer(b)) throw new TypeError('Argument must be a Buffer'); + if (this === b) return true; + return Buffer.compare(this, b) === 0; +}; +Buffer.prototype.inspect = function inspect() { + let str = ''; + const max = exports.INSPECT_MAX_BYTES; + str = this.toString('hex', 0, max).replace(/(.{2})/g, '$1 ').trim(); + if (this.length > max) str += ' ... '; + return ''; +}; +if (customInspectSymbol) { + Buffer.prototype[customInspectSymbol] = Buffer.prototype.inspect; +} +Buffer.prototype.compare = function compare(target, start, end, thisStart, thisEnd) { + if (isInstance(target, Uint8Array)) { + target = Buffer.from(target, target.offset, target.byteLength); + } + if (!Buffer.isBuffer(target)) { + throw new TypeError('The "target" argument must be one of type Buffer or Uint8Array. ' + 'Received type ' + typeof target); + } + if (start === undefined) { + start = 0; + } + if (end === undefined) { + end = target ? target.length : 0; + } + if (thisStart === undefined) { + thisStart = 0; + } + if (thisEnd === undefined) { + thisEnd = this.length; + } + if (start < 0 || end > target.length || thisStart < 0 || thisEnd > this.length) { + throw new RangeError('out of range index'); + } + if (thisStart >= thisEnd && start >= end) { + return 0; + } + if (thisStart >= thisEnd) { + return -1; + } + if (start >= end) { + return 1; + } + start >>>= 0; + end >>>= 0; + thisStart >>>= 0; + thisEnd >>>= 0; + if (this === target) return 0; + let x = thisEnd - thisStart; + let y = end - start; + const len = Math.min(x, y); + const thisCopy = this.slice(thisStart, thisEnd); + const targetCopy = target.slice(start, end); + for (let i = 0; i < len; ++i) { + if (thisCopy[i] !== targetCopy[i]) { + x = thisCopy[i]; + y = targetCopy[i]; + break; + } + } + if (x < y) return -1; + if (y < x) return 1; + return 0; +}; + +// Finds either the first index of `val` in `buffer` at offset >= `byteOffset`, +// OR the last index of `val` in `buffer` at offset <= `byteOffset`. +// +// Arguments: +// - buffer - a Buffer to search +// - val - a string, Buffer, or number +// - byteOffset - an index into `buffer`; will be clamped to an int32 +// - encoding - an optional encoding, relevant is val is a string +// - dir - true for indexOf, false for lastIndexOf +function bidirectionalIndexOf(buffer, val, byteOffset, encoding, dir) { + // Empty buffer means no match + if (buffer.length === 0) return -1; + + // Normalize byteOffset + if (typeof byteOffset === 'string') { + encoding = byteOffset; + byteOffset = 0; + } else if (byteOffset > 0x7fffffff) { + byteOffset = 0x7fffffff; + } else if (byteOffset < -0x80000000) { + byteOffset = -0x80000000; + } + byteOffset = +byteOffset; // Coerce to Number. + if (numberIsNaN(byteOffset)) { + // byteOffset: it it's undefined, null, NaN, "foo", etc, search whole buffer + byteOffset = dir ? 0 : buffer.length - 1; + } + + // Normalize byteOffset: negative offsets start from the end of the buffer + if (byteOffset < 0) byteOffset = buffer.length + byteOffset; + if (byteOffset >= buffer.length) { + if (dir) return -1;else byteOffset = buffer.length - 1; + } else if (byteOffset < 0) { + if (dir) byteOffset = 0;else return -1; + } + + // Normalize val + if (typeof val === 'string') { + val = Buffer.from(val, encoding); + } + + // Finally, search either indexOf (if dir is true) or lastIndexOf + if (Buffer.isBuffer(val)) { + // Special case: looking for empty string/buffer always fails + if (val.length === 0) { + return -1; + } + return arrayIndexOf(buffer, val, byteOffset, encoding, dir); + } else if (typeof val === 'number') { + val = val & 0xFF; // Search for a byte value [0-255] + if (typeof Uint8Array.prototype.indexOf === 'function') { + if (dir) { + return Uint8Array.prototype.indexOf.call(buffer, val, byteOffset); + } else { + return Uint8Array.prototype.lastIndexOf.call(buffer, val, byteOffset); + } + } + return arrayIndexOf(buffer, [val], byteOffset, encoding, dir); + } + throw new TypeError('val must be string, number or Buffer'); +} +function arrayIndexOf(arr, val, byteOffset, encoding, dir) { + let indexSize = 1; + let arrLength = arr.length; + let valLength = val.length; + if (encoding !== undefined) { + encoding = String(encoding).toLowerCase(); + if (encoding === 'ucs2' || encoding === 'ucs-2' || encoding === 'utf16le' || encoding === 'utf-16le') { + if (arr.length < 2 || val.length < 2) { + return -1; + } + indexSize = 2; + arrLength /= 2; + valLength /= 2; + byteOffset /= 2; + } + } + function read(buf, i) { + if (indexSize === 1) { + return buf[i]; + } else { + return buf.readUInt16BE(i * indexSize); + } + } + let i; + if (dir) { + let foundIndex = -1; + for (i = byteOffset; i < arrLength; i++) { + if (read(arr, i) === read(val, foundIndex === -1 ? 0 : i - foundIndex)) { + if (foundIndex === -1) foundIndex = i; + if (i - foundIndex + 1 === valLength) return foundIndex * indexSize; + } else { + if (foundIndex !== -1) i -= i - foundIndex; + foundIndex = -1; + } + } + } else { + if (byteOffset + valLength > arrLength) byteOffset = arrLength - valLength; + for (i = byteOffset; i >= 0; i--) { + let found = true; + for (let j = 0; j < valLength; j++) { + if (read(arr, i + j) !== read(val, j)) { + found = false; + break; + } + } + if (found) return i; + } + } + return -1; +} +Buffer.prototype.includes = function includes(val, byteOffset, encoding) { + return this.indexOf(val, byteOffset, encoding) !== -1; +}; +Buffer.prototype.indexOf = function indexOf(val, byteOffset, encoding) { + return bidirectionalIndexOf(this, val, byteOffset, encoding, true); +}; +Buffer.prototype.lastIndexOf = function lastIndexOf(val, byteOffset, encoding) { + return bidirectionalIndexOf(this, val, byteOffset, encoding, false); +}; +function hexWrite(buf, string, offset, length) { + offset = Number(offset) || 0; + const remaining = buf.length - offset; + if (!length) { + length = remaining; + } else { + length = Number(length); + if (length > remaining) { + length = remaining; + } + } + const strLen = string.length; + if (length > strLen / 2) { + length = strLen / 2; + } + let i; + for (i = 0; i < length; ++i) { + const parsed = parseInt(string.substr(i * 2, 2), 16); + if (numberIsNaN(parsed)) return i; + buf[offset + i] = parsed; + } + return i; +} +function utf8Write(buf, string, offset, length) { + return blitBuffer(utf8ToBytes(string, buf.length - offset), buf, offset, length); +} +function asciiWrite(buf, string, offset, length) { + return blitBuffer(asciiToBytes(string), buf, offset, length); +} +function base64Write(buf, string, offset, length) { + return blitBuffer(base64ToBytes(string), buf, offset, length); +} +function ucs2Write(buf, string, offset, length) { + return blitBuffer(utf16leToBytes(string, buf.length - offset), buf, offset, length); +} +Buffer.prototype.write = function write(string, offset, length, encoding) { + // Buffer#write(string) + if (offset === undefined) { + encoding = 'utf8'; + length = this.length; + offset = 0; + // Buffer#write(string, encoding) + } else if (length === undefined && typeof offset === 'string') { + encoding = offset; + length = this.length; + offset = 0; + // Buffer#write(string, offset[, length][, encoding]) + } else if (isFinite(offset)) { + offset = offset >>> 0; + if (isFinite(length)) { + length = length >>> 0; + if (encoding === undefined) encoding = 'utf8'; + } else { + encoding = length; + length = undefined; + } + } else { + throw new Error('Buffer.write(string, encoding, offset[, length]) is no longer supported'); + } + const remaining = this.length - offset; + if (length === undefined || length > remaining) length = remaining; + if (string.length > 0 && (length < 0 || offset < 0) || offset > this.length) { + throw new RangeError('Attempt to write outside buffer bounds'); + } + if (!encoding) encoding = 'utf8'; + let loweredCase = false; + for (;;) { + switch (encoding) { + case 'hex': + return hexWrite(this, string, offset, length); + case 'utf8': + case 'utf-8': + return utf8Write(this, string, offset, length); + case 'ascii': + case 'latin1': + case 'binary': + return asciiWrite(this, string, offset, length); + case 'base64': + // Warning: maxLength not taken into account in base64Write + return base64Write(this, string, offset, length); + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return ucs2Write(this, string, offset, length); + default: + if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding); + encoding = ('' + encoding).toLowerCase(); + loweredCase = true; + } + } +}; +Buffer.prototype.toJSON = function toJSON() { + return { + type: 'Buffer', + data: Array.prototype.slice.call(this._arr || this, 0) + }; +}; +function base64Slice(buf, start, end) { + if (start === 0 && end === buf.length) { + return base64.fromByteArray(buf); + } else { + return base64.fromByteArray(buf.slice(start, end)); + } +} +function utf8Slice(buf, start, end) { + end = Math.min(buf.length, end); + const res = []; + let i = start; + while (i < end) { + const firstByte = buf[i]; + let codePoint = null; + let bytesPerSequence = firstByte > 0xEF ? 4 : firstByte > 0xDF ? 3 : firstByte > 0xBF ? 2 : 1; + if (i + bytesPerSequence <= end) { + let secondByte, thirdByte, fourthByte, tempCodePoint; + switch (bytesPerSequence) { + case 1: + if (firstByte < 0x80) { + codePoint = firstByte; + } + break; + case 2: + secondByte = buf[i + 1]; + if ((secondByte & 0xC0) === 0x80) { + tempCodePoint = (firstByte & 0x1F) << 0x6 | secondByte & 0x3F; + if (tempCodePoint > 0x7F) { + codePoint = tempCodePoint; + } + } + break; + case 3: + secondByte = buf[i + 1]; + thirdByte = buf[i + 2]; + if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80) { + tempCodePoint = (firstByte & 0xF) << 0xC | (secondByte & 0x3F) << 0x6 | thirdByte & 0x3F; + if (tempCodePoint > 0x7FF && (tempCodePoint < 0xD800 || tempCodePoint > 0xDFFF)) { + codePoint = tempCodePoint; + } + } + break; + case 4: + secondByte = buf[i + 1]; + thirdByte = buf[i + 2]; + fourthByte = buf[i + 3]; + if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80 && (fourthByte & 0xC0) === 0x80) { + tempCodePoint = (firstByte & 0xF) << 0x12 | (secondByte & 0x3F) << 0xC | (thirdByte & 0x3F) << 0x6 | fourthByte & 0x3F; + if (tempCodePoint > 0xFFFF && tempCodePoint < 0x110000) { + codePoint = tempCodePoint; + } + } + } + } + if (codePoint === null) { + // we did not generate a valid codePoint so insert a + // replacement char (U+FFFD) and advance only 1 byte + codePoint = 0xFFFD; + bytesPerSequence = 1; + } else if (codePoint > 0xFFFF) { + // encode to utf16 (surrogate pair dance) + codePoint -= 0x10000; + res.push(codePoint >>> 10 & 0x3FF | 0xD800); + codePoint = 0xDC00 | codePoint & 0x3FF; + } + res.push(codePoint); + i += bytesPerSequence; + } + return decodeCodePointsArray(res); +} + +// Based on http://stackoverflow.com/a/22747272/680742, the browser with +// the lowest limit is Chrome, with 0x10000 args. +// We go 1 magnitude less, for safety +const MAX_ARGUMENTS_LENGTH = 0x1000; +function decodeCodePointsArray(codePoints) { + const len = codePoints.length; + if (len <= MAX_ARGUMENTS_LENGTH) { + return String.fromCharCode.apply(String, codePoints); // avoid extra slice() + } + + // Decode in chunks to avoid "call stack size exceeded". + let res = ''; + let i = 0; + while (i < len) { + res += String.fromCharCode.apply(String, codePoints.slice(i, i += MAX_ARGUMENTS_LENGTH)); + } + return res; +} +function asciiSlice(buf, start, end) { + let ret = ''; + end = Math.min(buf.length, end); + for (let i = start; i < end; ++i) { + ret += String.fromCharCode(buf[i] & 0x7F); + } + return ret; +} +function latin1Slice(buf, start, end) { + let ret = ''; + end = Math.min(buf.length, end); + for (let i = start; i < end; ++i) { + ret += String.fromCharCode(buf[i]); + } + return ret; +} +function hexSlice(buf, start, end) { + const len = buf.length; + if (!start || start < 0) start = 0; + if (!end || end < 0 || end > len) end = len; + let out = ''; + for (let i = start; i < end; ++i) { + out += hexSliceLookupTable[buf[i]]; + } + return out; +} +function utf16leSlice(buf, start, end) { + const bytes = buf.slice(start, end); + let res = ''; + // If bytes.length is odd, the last 8 bits must be ignored (same as node.js) + for (let i = 0; i < bytes.length - 1; i += 2) { + res += String.fromCharCode(bytes[i] + bytes[i + 1] * 256); + } + return res; +} +Buffer.prototype.slice = function slice(start, end) { + const len = this.length; + start = ~~start; + end = end === undefined ? len : ~~end; + if (start < 0) { + start += len; + if (start < 0) start = 0; + } else if (start > len) { + start = len; + } + if (end < 0) { + end += len; + if (end < 0) end = 0; + } else if (end > len) { + end = len; + } + if (end < start) end = start; + const newBuf = this.subarray(start, end); + // Return an augmented `Uint8Array` instance + Object.setPrototypeOf(newBuf, Buffer.prototype); + return newBuf; +}; + +/* + * Need to make sure that buffer isn't trying to write out of bounds. + */ +function checkOffset(offset, ext, length) { + if (offset % 1 !== 0 || offset < 0) throw new RangeError('offset is not uint'); + if (offset + ext > length) throw new RangeError('Trying to access beyond buffer length'); +} +Buffer.prototype.readUintLE = Buffer.prototype.readUIntLE = function readUIntLE(offset, byteLength, noAssert) { + offset = offset >>> 0; + byteLength = byteLength >>> 0; + if (!noAssert) checkOffset(offset, byteLength, this.length); + let val = this[offset]; + let mul = 1; + let i = 0; + while (++i < byteLength && (mul *= 0x100)) { + val += this[offset + i] * mul; + } + return val; +}; +Buffer.prototype.readUintBE = Buffer.prototype.readUIntBE = function readUIntBE(offset, byteLength, noAssert) { + offset = offset >>> 0; + byteLength = byteLength >>> 0; + if (!noAssert) { + checkOffset(offset, byteLength, this.length); + } + let val = this[offset + --byteLength]; + let mul = 1; + while (byteLength > 0 && (mul *= 0x100)) { + val += this[offset + --byteLength] * mul; + } + return val; +}; +Buffer.prototype.readUint8 = Buffer.prototype.readUInt8 = function readUInt8(offset, noAssert) { + offset = offset >>> 0; + if (!noAssert) checkOffset(offset, 1, this.length); + return this[offset]; +}; +Buffer.prototype.readUint16LE = Buffer.prototype.readUInt16LE = function readUInt16LE(offset, noAssert) { + offset = offset >>> 0; + if (!noAssert) checkOffset(offset, 2, this.length); + return this[offset] | this[offset + 1] << 8; +}; +Buffer.prototype.readUint16BE = Buffer.prototype.readUInt16BE = function readUInt16BE(offset, noAssert) { + offset = offset >>> 0; + if (!noAssert) checkOffset(offset, 2, this.length); + return this[offset] << 8 | this[offset + 1]; +}; +Buffer.prototype.readUint32LE = Buffer.prototype.readUInt32LE = function readUInt32LE(offset, noAssert) { + offset = offset >>> 0; + if (!noAssert) checkOffset(offset, 4, this.length); + return (this[offset] | this[offset + 1] << 8 | this[offset + 2] << 16) + this[offset + 3] * 0x1000000; +}; +Buffer.prototype.readUint32BE = Buffer.prototype.readUInt32BE = function readUInt32BE(offset, noAssert) { + offset = offset >>> 0; + if (!noAssert) checkOffset(offset, 4, this.length); + return this[offset] * 0x1000000 + (this[offset + 1] << 16 | this[offset + 2] << 8 | this[offset + 3]); +}; +Buffer.prototype.readBigUInt64LE = defineBigIntMethod(function readBigUInt64LE(offset) { + offset = offset >>> 0; + validateNumber(offset, 'offset'); + const first = this[offset]; + const last = this[offset + 7]; + if (first === undefined || last === undefined) { + boundsError(offset, this.length - 8); + } + const lo = first + this[++offset] * 2 ** 8 + this[++offset] * 2 ** 16 + this[++offset] * 2 ** 24; + const hi = this[++offset] + this[++offset] * 2 ** 8 + this[++offset] * 2 ** 16 + last * 2 ** 24; + return BigInt(lo) + (BigInt(hi) << BigInt(32)); +}); +Buffer.prototype.readBigUInt64BE = defineBigIntMethod(function readBigUInt64BE(offset) { + offset = offset >>> 0; + validateNumber(offset, 'offset'); + const first = this[offset]; + const last = this[offset + 7]; + if (first === undefined || last === undefined) { + boundsError(offset, this.length - 8); + } + const hi = first * 2 ** 24 + this[++offset] * 2 ** 16 + this[++offset] * 2 ** 8 + this[++offset]; + const lo = this[++offset] * 2 ** 24 + this[++offset] * 2 ** 16 + this[++offset] * 2 ** 8 + last; + return (BigInt(hi) << BigInt(32)) + BigInt(lo); +}); +Buffer.prototype.readIntLE = function readIntLE(offset, byteLength, noAssert) { + offset = offset >>> 0; + byteLength = byteLength >>> 0; + if (!noAssert) checkOffset(offset, byteLength, this.length); + let val = this[offset]; + let mul = 1; + let i = 0; + while (++i < byteLength && (mul *= 0x100)) { + val += this[offset + i] * mul; + } + mul *= 0x80; + if (val >= mul) val -= Math.pow(2, 8 * byteLength); + return val; +}; +Buffer.prototype.readIntBE = function readIntBE(offset, byteLength, noAssert) { + offset = offset >>> 0; + byteLength = byteLength >>> 0; + if (!noAssert) checkOffset(offset, byteLength, this.length); + let i = byteLength; + let mul = 1; + let val = this[offset + --i]; + while (i > 0 && (mul *= 0x100)) { + val += this[offset + --i] * mul; + } + mul *= 0x80; + if (val >= mul) val -= Math.pow(2, 8 * byteLength); + return val; +}; +Buffer.prototype.readInt8 = function readInt8(offset, noAssert) { + offset = offset >>> 0; + if (!noAssert) checkOffset(offset, 1, this.length); + if (!(this[offset] & 0x80)) return this[offset]; + return (0xff - this[offset] + 1) * -1; +}; +Buffer.prototype.readInt16LE = function readInt16LE(offset, noAssert) { + offset = offset >>> 0; + if (!noAssert) checkOffset(offset, 2, this.length); + const val = this[offset] | this[offset + 1] << 8; + return val & 0x8000 ? val | 0xFFFF0000 : val; +}; +Buffer.prototype.readInt16BE = function readInt16BE(offset, noAssert) { + offset = offset >>> 0; + if (!noAssert) checkOffset(offset, 2, this.length); + const val = this[offset + 1] | this[offset] << 8; + return val & 0x8000 ? val | 0xFFFF0000 : val; +}; +Buffer.prototype.readInt32LE = function readInt32LE(offset, noAssert) { + offset = offset >>> 0; + if (!noAssert) checkOffset(offset, 4, this.length); + return this[offset] | this[offset + 1] << 8 | this[offset + 2] << 16 | this[offset + 3] << 24; +}; +Buffer.prototype.readInt32BE = function readInt32BE(offset, noAssert) { + offset = offset >>> 0; + if (!noAssert) checkOffset(offset, 4, this.length); + return this[offset] << 24 | this[offset + 1] << 16 | this[offset + 2] << 8 | this[offset + 3]; +}; +Buffer.prototype.readBigInt64LE = defineBigIntMethod(function readBigInt64LE(offset) { + offset = offset >>> 0; + validateNumber(offset, 'offset'); + const first = this[offset]; + const last = this[offset + 7]; + if (first === undefined || last === undefined) { + boundsError(offset, this.length - 8); + } + const val = this[offset + 4] + this[offset + 5] * 2 ** 8 + this[offset + 6] * 2 ** 16 + (last << 24); // Overflow + + return (BigInt(val) << BigInt(32)) + BigInt(first + this[++offset] * 2 ** 8 + this[++offset] * 2 ** 16 + this[++offset] * 2 ** 24); +}); +Buffer.prototype.readBigInt64BE = defineBigIntMethod(function readBigInt64BE(offset) { + offset = offset >>> 0; + validateNumber(offset, 'offset'); + const first = this[offset]; + const last = this[offset + 7]; + if (first === undefined || last === undefined) { + boundsError(offset, this.length - 8); + } + const val = (first << 24) + + // Overflow + this[++offset] * 2 ** 16 + this[++offset] * 2 ** 8 + this[++offset]; + return (BigInt(val) << BigInt(32)) + BigInt(this[++offset] * 2 ** 24 + this[++offset] * 2 ** 16 + this[++offset] * 2 ** 8 + last); +}); +Buffer.prototype.readFloatLE = function readFloatLE(offset, noAssert) { + offset = offset >>> 0; + if (!noAssert) checkOffset(offset, 4, this.length); + return ieee754.read(this, offset, true, 23, 4); +}; +Buffer.prototype.readFloatBE = function readFloatBE(offset, noAssert) { + offset = offset >>> 0; + if (!noAssert) checkOffset(offset, 4, this.length); + return ieee754.read(this, offset, false, 23, 4); +}; +Buffer.prototype.readDoubleLE = function readDoubleLE(offset, noAssert) { + offset = offset >>> 0; + if (!noAssert) checkOffset(offset, 8, this.length); + return ieee754.read(this, offset, true, 52, 8); +}; +Buffer.prototype.readDoubleBE = function readDoubleBE(offset, noAssert) { + offset = offset >>> 0; + if (!noAssert) checkOffset(offset, 8, this.length); + return ieee754.read(this, offset, false, 52, 8); +}; +function checkInt(buf, value, offset, ext, max, min) { + if (!Buffer.isBuffer(buf)) throw new TypeError('"buffer" argument must be a Buffer instance'); + if (value > max || value < min) throw new RangeError('"value" argument is out of bounds'); + if (offset + ext > buf.length) throw new RangeError('Index out of range'); +} +Buffer.prototype.writeUintLE = Buffer.prototype.writeUIntLE = function writeUIntLE(value, offset, byteLength, noAssert) { + value = +value; + offset = offset >>> 0; + byteLength = byteLength >>> 0; + if (!noAssert) { + const maxBytes = Math.pow(2, 8 * byteLength) - 1; + checkInt(this, value, offset, byteLength, maxBytes, 0); + } + let mul = 1; + let i = 0; + this[offset] = value & 0xFF; + while (++i < byteLength && (mul *= 0x100)) { + this[offset + i] = value / mul & 0xFF; + } + return offset + byteLength; +}; +Buffer.prototype.writeUintBE = Buffer.prototype.writeUIntBE = function writeUIntBE(value, offset, byteLength, noAssert) { + value = +value; + offset = offset >>> 0; + byteLength = byteLength >>> 0; + if (!noAssert) { + const maxBytes = Math.pow(2, 8 * byteLength) - 1; + checkInt(this, value, offset, byteLength, maxBytes, 0); + } + let i = byteLength - 1; + let mul = 1; + this[offset + i] = value & 0xFF; + while (--i >= 0 && (mul *= 0x100)) { + this[offset + i] = value / mul & 0xFF; + } + return offset + byteLength; +}; +Buffer.prototype.writeUint8 = Buffer.prototype.writeUInt8 = function writeUInt8(value, offset, noAssert) { + value = +value; + offset = offset >>> 0; + if (!noAssert) checkInt(this, value, offset, 1, 0xff, 0); + this[offset] = value & 0xff; + return offset + 1; +}; +Buffer.prototype.writeUint16LE = Buffer.prototype.writeUInt16LE = function writeUInt16LE(value, offset, noAssert) { + value = +value; + offset = offset >>> 0; + if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0); + this[offset] = value & 0xff; + this[offset + 1] = value >>> 8; + return offset + 2; +}; +Buffer.prototype.writeUint16BE = Buffer.prototype.writeUInt16BE = function writeUInt16BE(value, offset, noAssert) { + value = +value; + offset = offset >>> 0; + if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0); + this[offset] = value >>> 8; + this[offset + 1] = value & 0xff; + return offset + 2; +}; +Buffer.prototype.writeUint32LE = Buffer.prototype.writeUInt32LE = function writeUInt32LE(value, offset, noAssert) { + value = +value; + offset = offset >>> 0; + if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0); + this[offset + 3] = value >>> 24; + this[offset + 2] = value >>> 16; + this[offset + 1] = value >>> 8; + this[offset] = value & 0xff; + return offset + 4; +}; +Buffer.prototype.writeUint32BE = Buffer.prototype.writeUInt32BE = function writeUInt32BE(value, offset, noAssert) { + value = +value; + offset = offset >>> 0; + if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0); + this[offset] = value >>> 24; + this[offset + 1] = value >>> 16; + this[offset + 2] = value >>> 8; + this[offset + 3] = value & 0xff; + return offset + 4; +}; +function wrtBigUInt64LE(buf, value, offset, min, max) { + checkIntBI(value, min, max, buf, offset, 7); + let lo = Number(value & BigInt(0xffffffff)); + buf[offset++] = lo; + lo = lo >> 8; + buf[offset++] = lo; + lo = lo >> 8; + buf[offset++] = lo; + lo = lo >> 8; + buf[offset++] = lo; + let hi = Number(value >> BigInt(32) & BigInt(0xffffffff)); + buf[offset++] = hi; + hi = hi >> 8; + buf[offset++] = hi; + hi = hi >> 8; + buf[offset++] = hi; + hi = hi >> 8; + buf[offset++] = hi; + return offset; +} +function wrtBigUInt64BE(buf, value, offset, min, max) { + checkIntBI(value, min, max, buf, offset, 7); + let lo = Number(value & BigInt(0xffffffff)); + buf[offset + 7] = lo; + lo = lo >> 8; + buf[offset + 6] = lo; + lo = lo >> 8; + buf[offset + 5] = lo; + lo = lo >> 8; + buf[offset + 4] = lo; + let hi = Number(value >> BigInt(32) & BigInt(0xffffffff)); + buf[offset + 3] = hi; + hi = hi >> 8; + buf[offset + 2] = hi; + hi = hi >> 8; + buf[offset + 1] = hi; + hi = hi >> 8; + buf[offset] = hi; + return offset + 8; +} +Buffer.prototype.writeBigUInt64LE = defineBigIntMethod(function writeBigUInt64LE(value, offset = 0) { + return wrtBigUInt64LE(this, value, offset, BigInt(0), BigInt('0xffffffffffffffff')); +}); +Buffer.prototype.writeBigUInt64BE = defineBigIntMethod(function writeBigUInt64BE(value, offset = 0) { + return wrtBigUInt64BE(this, value, offset, BigInt(0), BigInt('0xffffffffffffffff')); +}); +Buffer.prototype.writeIntLE = function writeIntLE(value, offset, byteLength, noAssert) { + value = +value; + offset = offset >>> 0; + if (!noAssert) { + const limit = Math.pow(2, 8 * byteLength - 1); + checkInt(this, value, offset, byteLength, limit - 1, -limit); + } + let i = 0; + let mul = 1; + let sub = 0; + this[offset] = value & 0xFF; + while (++i < byteLength && (mul *= 0x100)) { + if (value < 0 && sub === 0 && this[offset + i - 1] !== 0) { + sub = 1; + } + this[offset + i] = (value / mul >> 0) - sub & 0xFF; + } + return offset + byteLength; +}; +Buffer.prototype.writeIntBE = function writeIntBE(value, offset, byteLength, noAssert) { + value = +value; + offset = offset >>> 0; + if (!noAssert) { + const limit = Math.pow(2, 8 * byteLength - 1); + checkInt(this, value, offset, byteLength, limit - 1, -limit); + } + let i = byteLength - 1; + let mul = 1; + let sub = 0; + this[offset + i] = value & 0xFF; + while (--i >= 0 && (mul *= 0x100)) { + if (value < 0 && sub === 0 && this[offset + i + 1] !== 0) { + sub = 1; + } + this[offset + i] = (value / mul >> 0) - sub & 0xFF; + } + return offset + byteLength; +}; +Buffer.prototype.writeInt8 = function writeInt8(value, offset, noAssert) { + value = +value; + offset = offset >>> 0; + if (!noAssert) checkInt(this, value, offset, 1, 0x7f, -0x80); + if (value < 0) value = 0xff + value + 1; + this[offset] = value & 0xff; + return offset + 1; +}; +Buffer.prototype.writeInt16LE = function writeInt16LE(value, offset, noAssert) { + value = +value; + offset = offset >>> 0; + if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000); + this[offset] = value & 0xff; + this[offset + 1] = value >>> 8; + return offset + 2; +}; +Buffer.prototype.writeInt16BE = function writeInt16BE(value, offset, noAssert) { + value = +value; + offset = offset >>> 0; + if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000); + this[offset] = value >>> 8; + this[offset + 1] = value & 0xff; + return offset + 2; +}; +Buffer.prototype.writeInt32LE = function writeInt32LE(value, offset, noAssert) { + value = +value; + offset = offset >>> 0; + if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000); + this[offset] = value & 0xff; + this[offset + 1] = value >>> 8; + this[offset + 2] = value >>> 16; + this[offset + 3] = value >>> 24; + return offset + 4; +}; +Buffer.prototype.writeInt32BE = function writeInt32BE(value, offset, noAssert) { + value = +value; + offset = offset >>> 0; + if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000); + if (value < 0) value = 0xffffffff + value + 1; + this[offset] = value >>> 24; + this[offset + 1] = value >>> 16; + this[offset + 2] = value >>> 8; + this[offset + 3] = value & 0xff; + return offset + 4; +}; +Buffer.prototype.writeBigInt64LE = defineBigIntMethod(function writeBigInt64LE(value, offset = 0) { + return wrtBigUInt64LE(this, value, offset, -BigInt('0x8000000000000000'), BigInt('0x7fffffffffffffff')); +}); +Buffer.prototype.writeBigInt64BE = defineBigIntMethod(function writeBigInt64BE(value, offset = 0) { + return wrtBigUInt64BE(this, value, offset, -BigInt('0x8000000000000000'), BigInt('0x7fffffffffffffff')); +}); +function checkIEEE754(buf, value, offset, ext, max, min) { + if (offset + ext > buf.length) throw new RangeError('Index out of range'); + if (offset < 0) throw new RangeError('Index out of range'); +} +function writeFloat(buf, value, offset, littleEndian, noAssert) { + value = +value; + offset = offset >>> 0; + if (!noAssert) { + checkIEEE754(buf, value, offset, 4, 3.4028234663852886e+38, -3.4028234663852886e+38); + } + ieee754.write(buf, value, offset, littleEndian, 23, 4); + return offset + 4; +} +Buffer.prototype.writeFloatLE = function writeFloatLE(value, offset, noAssert) { + return writeFloat(this, value, offset, true, noAssert); +}; +Buffer.prototype.writeFloatBE = function writeFloatBE(value, offset, noAssert) { + return writeFloat(this, value, offset, false, noAssert); +}; +function writeDouble(buf, value, offset, littleEndian, noAssert) { + value = +value; + offset = offset >>> 0; + if (!noAssert) { + checkIEEE754(buf, value, offset, 8, 1.7976931348623157E+308, -1.7976931348623157E+308); + } + ieee754.write(buf, value, offset, littleEndian, 52, 8); + return offset + 8; +} +Buffer.prototype.writeDoubleLE = function writeDoubleLE(value, offset, noAssert) { + return writeDouble(this, value, offset, true, noAssert); +}; +Buffer.prototype.writeDoubleBE = function writeDoubleBE(value, offset, noAssert) { + return writeDouble(this, value, offset, false, noAssert); +}; + +// copy(targetBuffer, targetStart=0, sourceStart=0, sourceEnd=buffer.length) +Buffer.prototype.copy = function copy(target, targetStart, start, end) { + if (!Buffer.isBuffer(target)) throw new TypeError('argument should be a Buffer'); + if (!start) start = 0; + if (!end && end !== 0) end = this.length; + if (targetStart >= target.length) targetStart = target.length; + if (!targetStart) targetStart = 0; + if (end > 0 && end < start) end = start; + + // Copy 0 bytes; we're done + if (end === start) return 0; + if (target.length === 0 || this.length === 0) return 0; + + // Fatal error conditions + if (targetStart < 0) { + throw new RangeError('targetStart out of bounds'); + } + if (start < 0 || start >= this.length) throw new RangeError('Index out of range'); + if (end < 0) throw new RangeError('sourceEnd out of bounds'); + + // Are we oob? + if (end > this.length) end = this.length; + if (target.length - targetStart < end - start) { + end = target.length - targetStart + start; + } + const len = end - start; + if (this === target && typeof Uint8Array.prototype.copyWithin === 'function') { + // Use built-in when available, missing from IE11 + this.copyWithin(targetStart, start, end); + } else { + Uint8Array.prototype.set.call(target, this.subarray(start, end), targetStart); + } + return len; +}; + +// Usage: +// buffer.fill(number[, offset[, end]]) +// buffer.fill(buffer[, offset[, end]]) +// buffer.fill(string[, offset[, end]][, encoding]) +Buffer.prototype.fill = function fill(val, start, end, encoding) { + // Handle string cases: + if (typeof val === 'string') { + if (typeof start === 'string') { + encoding = start; + start = 0; + end = this.length; + } else if (typeof end === 'string') { + encoding = end; + end = this.length; + } + if (encoding !== undefined && typeof encoding !== 'string') { + throw new TypeError('encoding must be a string'); + } + if (typeof encoding === 'string' && !Buffer.isEncoding(encoding)) { + throw new TypeError('Unknown encoding: ' + encoding); + } + if (val.length === 1) { + const code = val.charCodeAt(0); + if (encoding === 'utf8' && code < 128 || encoding === 'latin1') { + // Fast path: If `val` fits into a single byte, use that numeric value. + val = code; + } + } + } else if (typeof val === 'number') { + val = val & 255; + } else if (typeof val === 'boolean') { + val = Number(val); + } + + // Invalid ranges are not set to a default, so can range check early. + if (start < 0 || this.length < start || this.length < end) { + throw new RangeError('Out of range index'); + } + if (end <= start) { + return this; + } + start = start >>> 0; + end = end === undefined ? this.length : end >>> 0; + if (!val) val = 0; + let i; + if (typeof val === 'number') { + for (i = start; i < end; ++i) { + this[i] = val; + } + } else { + const bytes = Buffer.isBuffer(val) ? val : Buffer.from(val, encoding); + const len = bytes.length; + if (len === 0) { + throw new TypeError('The value "' + val + '" is invalid for argument "value"'); + } + for (i = 0; i < end - start; ++i) { + this[i + start] = bytes[i % len]; + } + } + return this; +}; + +// CUSTOM ERRORS +// ============= + +// Simplified versions from Node, changed for Buffer-only usage +const errors = {}; +function E(sym, getMessage, Base) { + errors[sym] = class NodeError extends Base { + constructor() { + super(); + Object.defineProperty(this, 'message', { + value: getMessage.apply(this, arguments), + writable: true, + configurable: true + }); + + // Add the error code to the name to include it in the stack trace. + this.name = `${this.name} [${sym}]`; + // Access the stack to generate the error message including the error code + // from the name. + this.stack; // eslint-disable-line no-unused-expressions + // Reset the name to the actual name. + delete this.name; + } + get code() { + return sym; + } + set code(value) { + Object.defineProperty(this, 'code', { + configurable: true, + enumerable: true, + value, + writable: true + }); + } + toString() { + return `${this.name} [${sym}]: ${this.message}`; + } + }; +} +E('ERR_BUFFER_OUT_OF_BOUNDS', function (name) { + if (name) { + return `${name} is outside of buffer bounds`; + } + return 'Attempt to access memory outside buffer bounds'; +}, RangeError); +E('ERR_INVALID_ARG_TYPE', function (name, actual) { + return `The "${name}" argument must be of type number. Received type ${typeof actual}`; +}, TypeError); +E('ERR_OUT_OF_RANGE', function (str, range, input) { + let msg = `The value of "${str}" is out of range.`; + let received = input; + if (Number.isInteger(input) && Math.abs(input) > 2 ** 32) { + received = addNumericalSeparator(String(input)); + } else if (typeof input === 'bigint') { + received = String(input); + if (input > BigInt(2) ** BigInt(32) || input < -(BigInt(2) ** BigInt(32))) { + received = addNumericalSeparator(received); + } + received += 'n'; + } + msg += ` It must be ${range}. Received ${received}`; + return msg; +}, RangeError); +function addNumericalSeparator(val) { + let res = ''; + let i = val.length; + const start = val[0] === '-' ? 1 : 0; + for (; i >= start + 4; i -= 3) { + res = `_${val.slice(i - 3, i)}${res}`; + } + return `${val.slice(0, i)}${res}`; +} + +// CHECK FUNCTIONS +// =============== + +function checkBounds(buf, offset, byteLength) { + validateNumber(offset, 'offset'); + if (buf[offset] === undefined || buf[offset + byteLength] === undefined) { + boundsError(offset, buf.length - (byteLength + 1)); + } +} +function checkIntBI(value, min, max, buf, offset, byteLength) { + if (value > max || value < min) { + const n = typeof min === 'bigint' ? 'n' : ''; + let range; + if (byteLength > 3) { + if (min === 0 || min === BigInt(0)) { + range = `>= 0${n} and < 2${n} ** ${(byteLength + 1) * 8}${n}`; + } else { + range = `>= -(2${n} ** ${(byteLength + 1) * 8 - 1}${n}) and < 2 ** ` + `${(byteLength + 1) * 8 - 1}${n}`; + } + } else { + range = `>= ${min}${n} and <= ${max}${n}`; + } + throw new errors.ERR_OUT_OF_RANGE('value', range, value); + } + checkBounds(buf, offset, byteLength); +} +function validateNumber(value, name) { + if (typeof value !== 'number') { + throw new errors.ERR_INVALID_ARG_TYPE(name, 'number', value); + } +} +function boundsError(value, length, type) { + if (Math.floor(value) !== value) { + validateNumber(value, type); + throw new errors.ERR_OUT_OF_RANGE(type || 'offset', 'an integer', value); + } + if (length < 0) { + throw new errors.ERR_BUFFER_OUT_OF_BOUNDS(); + } + throw new errors.ERR_OUT_OF_RANGE(type || 'offset', `>= ${type ? 1 : 0} and <= ${length}`, value); +} + +// HELPER FUNCTIONS +// ================ + +const INVALID_BASE64_RE = /[^+/0-9A-Za-z-_]/g; +function base64clean(str) { + // Node takes equal signs as end of the Base64 encoding + str = str.split('=')[0]; + // Node strips out invalid characters like \n and \t from the string, base64-js does not + str = str.trim().replace(INVALID_BASE64_RE, ''); + // Node converts strings with length < 2 to '' + if (str.length < 2) return ''; + // Node allows for non-padded base64 strings (missing trailing ===), base64-js does not + while (str.length % 4 !== 0) { + str = str + '='; + } + return str; +} +function utf8ToBytes(string, units) { + units = units || Infinity; + let codePoint; + const length = string.length; + let leadSurrogate = null; + const bytes = []; + for (let i = 0; i < length; ++i) { + codePoint = string.charCodeAt(i); + + // is surrogate component + if (codePoint > 0xD7FF && codePoint < 0xE000) { + // last char was a lead + if (!leadSurrogate) { + // no lead yet + if (codePoint > 0xDBFF) { + // unexpected trail + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD); + continue; + } else if (i + 1 === length) { + // unpaired lead + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD); + continue; + } + + // valid lead + leadSurrogate = codePoint; + continue; + } + + // 2 leads in a row + if (codePoint < 0xDC00) { + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD); + leadSurrogate = codePoint; + continue; + } + + // valid surrogate pair + codePoint = (leadSurrogate - 0xD800 << 10 | codePoint - 0xDC00) + 0x10000; + } else if (leadSurrogate) { + // valid bmp char, but last char was a lead + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD); + } + leadSurrogate = null; + + // encode utf8 + if (codePoint < 0x80) { + if ((units -= 1) < 0) break; + bytes.push(codePoint); + } else if (codePoint < 0x800) { + if ((units -= 2) < 0) break; + bytes.push(codePoint >> 0x6 | 0xC0, codePoint & 0x3F | 0x80); + } else if (codePoint < 0x10000) { + if ((units -= 3) < 0) break; + bytes.push(codePoint >> 0xC | 0xE0, codePoint >> 0x6 & 0x3F | 0x80, codePoint & 0x3F | 0x80); + } else if (codePoint < 0x110000) { + if ((units -= 4) < 0) break; + bytes.push(codePoint >> 0x12 | 0xF0, codePoint >> 0xC & 0x3F | 0x80, codePoint >> 0x6 & 0x3F | 0x80, codePoint & 0x3F | 0x80); + } else { + throw new Error('Invalid code point'); + } + } + return bytes; +} +function asciiToBytes(str) { + const byteArray = []; + for (let i = 0; i < str.length; ++i) { + // Node's code seems to be doing this and not & 0x7F.. + byteArray.push(str.charCodeAt(i) & 0xFF); + } + return byteArray; +} +function utf16leToBytes(str, units) { + let c, hi, lo; + const byteArray = []; + for (let i = 0; i < str.length; ++i) { + if ((units -= 2) < 0) break; + c = str.charCodeAt(i); + hi = c >> 8; + lo = c % 256; + byteArray.push(lo); + byteArray.push(hi); + } + return byteArray; +} +function base64ToBytes(str) { + return base64.toByteArray(base64clean(str)); +} +function blitBuffer(src, dst, offset, length) { + let i; + for (i = 0; i < length; ++i) { + if (i + offset >= dst.length || i >= src.length) break; + dst[i + offset] = src[i]; + } + return i; +} + +// ArrayBuffer or Uint8Array objects from other contexts (i.e. iframes) do not pass +// the `instanceof` check but they should be treated as of that type. +// See: https://github.com/feross/buffer/issues/166 +function isInstance(obj, type) { + return obj instanceof type || obj != null && obj.constructor != null && obj.constructor.name != null && obj.constructor.name === type.name; +} +function numberIsNaN(obj) { + // For IE11 support + return obj !== obj; // eslint-disable-line no-self-compare +} + +// Create lookup table for `toString('hex')` +// See: https://github.com/feross/buffer/issues/219 +const hexSliceLookupTable = function () { + const alphabet = '0123456789abcdef'; + const table = new Array(256); + for (let i = 0; i < 16; ++i) { + const i16 = i * 16; + for (let j = 0; j < 16; ++j) { + table[i16 + j] = alphabet[i] + alphabet[j]; + } + } + return table; +}(); + +// Return not function with Error if BigInt not supported +function defineBigIntMethod(fn) { + return typeof BigInt === 'undefined' ? BufferBigIntNotDefined : fn; +} +function BufferBigIntNotDefined() { + throw new Error('BigInt not supported'); +} + +/***/ }), + +/***/ "./node_modules/css-loader/dist/runtime/api.js": +/*!*****************************************************!*\ + !*** ./node_modules/css-loader/dist/runtime/api.js ***! + \*****************************************************/ +/***/ ((module) => { + +"use strict"; + + +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ +module.exports = function (cssWithMappingToString) { + var list = []; + + // return the list of modules as css string + list.toString = function toString() { + return this.map(function (item) { + var content = ""; + var needLayer = typeof item[5] !== "undefined"; + if (item[4]) { + content += "@supports (".concat(item[4], ") {"); + } + if (item[2]) { + content += "@media ".concat(item[2], " {"); + } + if (needLayer) { + content += "@layer".concat(item[5].length > 0 ? " ".concat(item[5]) : "", " {"); + } + content += cssWithMappingToString(item); + if (needLayer) { + content += "}"; + } + if (item[2]) { + content += "}"; + } + if (item[4]) { + content += "}"; + } + return content; + }).join(""); + }; + + // import a list of modules into the list + list.i = function i(modules, media, dedupe, supports, layer) { + if (typeof modules === "string") { + modules = [[null, modules, undefined]]; + } + var alreadyImportedModules = {}; + if (dedupe) { + for (var k = 0; k < this.length; k++) { + var id = this[k][0]; + if (id != null) { + alreadyImportedModules[id] = true; + } + } + } + for (var _k = 0; _k < modules.length; _k++) { + var item = [].concat(modules[_k]); + if (dedupe && alreadyImportedModules[item[0]]) { + continue; + } + if (typeof layer !== "undefined") { + if (typeof item[5] === "undefined") { + item[5] = layer; + } else { + item[1] = "@layer".concat(item[5].length > 0 ? " ".concat(item[5]) : "", " {").concat(item[1], "}"); + item[5] = layer; + } + } + if (media) { + if (!item[2]) { + item[2] = media; + } else { + item[1] = "@media ".concat(item[2], " {").concat(item[1], "}"); + item[2] = media; + } + } + if (supports) { + if (!item[4]) { + item[4] = "".concat(supports); + } else { + item[1] = "@supports (".concat(item[4], ") {").concat(item[1], "}"); + item[4] = supports; + } + } + list.push(item); + } + }; + return list; +}; + +/***/ }), + +/***/ "./node_modules/css-loader/dist/runtime/getUrl.js": +/*!********************************************************!*\ + !*** ./node_modules/css-loader/dist/runtime/getUrl.js ***! + \********************************************************/ +/***/ ((module) => { + +"use strict"; + + +module.exports = function (url, options) { + if (!options) { + options = {}; + } + if (!url) { + return url; + } + url = String(url.__esModule ? url.default : url); + + // If url is already wrapped in quotes, remove them + if (/^['"].*['"]$/.test(url)) { + url = url.slice(1, -1); + } + if (options.hash) { + url += options.hash; + } + + // Should url be wrapped? + // See https://drafts.csswg.org/css-values-3/#urls + if (/["'() \t\n]|(%20)/.test(url) || options.needQuotes) { + return "\"".concat(url.replace(/"/g, '\\"').replace(/\n/g, "\\n"), "\""); + } + return url; +}; + +/***/ }), + +/***/ "./node_modules/css-loader/dist/runtime/sourceMaps.js": +/*!************************************************************!*\ + !*** ./node_modules/css-loader/dist/runtime/sourceMaps.js ***! + \************************************************************/ +/***/ ((module) => { + +"use strict"; + + +module.exports = function (item) { + var content = item[1]; + var cssMapping = item[3]; + if (!cssMapping) { + return content; + } + if (typeof btoa === "function") { + var base64 = btoa(unescape(encodeURIComponent(JSON.stringify(cssMapping)))); + var data = "sourceMappingURL=data:application/json;charset=utf-8;base64,".concat(base64); + var sourceMapping = "/*# ".concat(data, " */"); + return [content].concat([sourceMapping]).join("\n"); + } + return [content].join("\n"); +}; + +/***/ }), + +/***/ "./node_modules/custom-event-polyfill/polyfill.js": +/*!********************************************************!*\ + !*** ./node_modules/custom-event-polyfill/polyfill.js ***! + \********************************************************/ +/***/ (() => { + +// Polyfill for creating CustomEvents on IE9/10/11 + +// code pulled from: +// https://github.com/d4tocchini/customevent-polyfill +// https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent#Polyfill + +(function () { + if (typeof window === 'undefined') { + return; + } + try { + var ce = new window.CustomEvent('test', { + cancelable: true + }); + ce.preventDefault(); + if (ce.defaultPrevented !== true) { + // IE has problems with .preventDefault() on custom events + // http://stackoverflow.com/questions/23349191 + throw new Error('Could not prevent default'); + } + } catch (e) { + var CustomEvent = function (event, params) { + var evt, origPrevent; + params = params || {}; + params.bubbles = !!params.bubbles; + params.cancelable = !!params.cancelable; + evt = document.createEvent('CustomEvent'); + evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail); + origPrevent = evt.preventDefault; + evt.preventDefault = function () { + origPrevent.call(this); + try { + Object.defineProperty(this, 'defaultPrevented', { + get: function () { + return true; + } + }); + } catch (e) { + this.defaultPrevented = true; + } + }; + return evt; + }; + CustomEvent.prototype = window.Event.prototype; + window.CustomEvent = CustomEvent; // expose definition to window + } +})(); + +/***/ }), + +/***/ "./node_modules/debug/browser.js": +/*!***************************************!*\ + !*** ./node_modules/debug/browser.js ***! + \***************************************/ +/***/ ((module, exports, __webpack_require__) => { + +/** + * This is the web browser implementation of `debug()`. + * + * Expose `debug()` as the module. + */ + +exports = module.exports = __webpack_require__(/*! ./debug */ "./node_modules/debug/debug.js"); +exports.log = log; +exports.formatArgs = formatArgs; +exports.save = save; +exports.load = load; +exports.useColors = useColors; +exports.storage = 'undefined' != typeof chrome && 'undefined' != typeof chrome.storage ? chrome.storage.local : localstorage(); + +/** + * Colors. + */ + +exports.colors = ['lightseagreen', 'forestgreen', 'goldenrod', 'dodgerblue', 'darkorchid', 'crimson']; + +/** + * Currently only WebKit-based Web Inspectors, Firefox >= v31, + * and the Firebug extension (any Firefox version) are known + * to support "%c" CSS customizations. + * + * TODO: add a `localStorage` variable to explicitly enable/disable colors + */ + +function useColors() { + // is webkit? http://stackoverflow.com/a/16459606/376773 + return 'WebkitAppearance' in document.documentElement.style || + // is firebug? http://stackoverflow.com/a/398120/376773 + window.console && (console.firebug || console.exception && console.table) || + // is firefox >= v31? + // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages + navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31; +} + +/** + * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default. + */ + +exports.formatters.j = function (v) { + return JSON.stringify(v); +}; + +/** + * Colorize log arguments if enabled. + * + * @api public + */ + +function formatArgs() { + var args = arguments; + var useColors = this.useColors; + args[0] = (useColors ? '%c' : '') + this.namespace + (useColors ? ' %c' : ' ') + args[0] + (useColors ? '%c ' : ' '); + if (!useColors) return args; + var c = 'color: ' + this.color; + args = [args[0], c, 'color: inherit'].concat(Array.prototype.slice.call(args, 1)); + + // the final "%c" is somewhat tricky, because there could be other + // arguments passed either before or after the %c, so we need to + // figure out the correct index to insert the CSS into + var index = 0; + var lastC = 0; + args[0].replace(/%[a-z%]/g, function (match) { + if ('%%' === match) return; + index++; + if ('%c' === match) { + // we only are interested in the *last* %c + // (the user may have provided their own) + lastC = index; + } + }); + args.splice(lastC, 0, c); + return args; +} + +/** + * Invokes `console.log()` when available. + * No-op when `console.log` is not a "function". + * + * @api public + */ + +function log() { + // this hackery is required for IE8/9, where + // the `console.log` function doesn't have 'apply' + return 'object' === typeof console && console.log && Function.prototype.apply.call(console.log, console, arguments); +} + +/** + * Save `namespaces`. + * + * @param {String} namespaces + * @api private + */ + +function save(namespaces) { + try { + if (null == namespaces) { + exports.storage.removeItem('debug'); + } else { + exports.storage.debug = namespaces; + } + } catch (e) {} +} + +/** + * Load `namespaces`. + * + * @return {String} returns the previously persisted debug modes + * @api private + */ + +function load() { + var r; + try { + r = exports.storage.debug; + } catch (e) {} + return r; +} + +/** + * Enable namespaces listed in `localStorage.debug` initially. + */ + +exports.enable(load()); + +/** + * Localstorage attempts to return the localstorage. + * + * This is necessary because safari throws + * when a user disables cookies/localstorage + * and you attempt to access it. + * + * @return {LocalStorage} + * @api private + */ + +function localstorage() { + try { + return window.localStorage; + } catch (e) {} +} + +/***/ }), + +/***/ "./node_modules/debug/debug.js": +/*!*************************************!*\ + !*** ./node_modules/debug/debug.js ***! + \*************************************/ +/***/ ((module, exports) => { + +/** + * This is the common logic for both the Node.js and web browser + * implementations of `debug()`. + * + * Expose `debug()` as the module. + */ + +exports = module.exports = debug; +exports.coerce = coerce; +exports.disable = disable; +exports.enable = enable; +exports.enabled = enabled; + +/** + * The currently active debug mode names, and names to skip. + */ + +exports.names = []; +exports.skips = []; + +/** + * Map of special "%n" handling functions, for the debug "format" argument. + * + * Valid key names are a single, lowercased letter, i.e. "n". + */ + +exports.formatters = {}; + +/** + * Previously assigned color. + */ + +var prevColor = 0; + +/** + * Select a color. + * + * @return {Number} + * @api private + */ + +function selectColor() { + return exports.colors[prevColor++ % exports.colors.length]; +} + +/** + * Create a debugger with the given `namespace`. + * + * @param {String} namespace + * @return {Function} + * @api public + */ + +function debug(namespace) { + // define the `disabled` version + function disabled() {} + disabled.enabled = false; + + // define the `enabled` version + function enabled() { + var self = enabled; + + // add the `color` if not set + if (null == self.useColors) self.useColors = exports.useColors(); + if (null == self.color && self.useColors) self.color = selectColor(); + var args = Array.prototype.slice.call(arguments); + args[0] = exports.coerce(args[0]); + if ('string' !== typeof args[0]) { + // anything else let's inspect with %o + args = ['%o'].concat(args); + } + + // apply any `formatters` transformations + var index = 0; + args[0] = args[0].replace(/%([a-z%])/g, function (match, format) { + // if we encounter an escaped % then don't increase the array index + if (match === '%%') return match; + index++; + var formatter = exports.formatters[format]; + if ('function' === typeof formatter) { + var val = args[index]; + match = formatter.call(self, val); + + // now we need to remove `args[index]` since it's inlined in the `format` + args.splice(index, 1); + index--; + } + return match; + }); + if ('function' === typeof exports.formatArgs) { + args = exports.formatArgs.apply(self, args); + } + var logFn = enabled.log || exports.log || console.log.bind(console); + logFn.apply(self, args); + } + enabled.enabled = true; + var fn = exports.enabled(namespace) ? enabled : disabled; + fn.namespace = namespace; + return fn; +} + +/** + * Enables a debug mode by namespaces. This can include modes + * separated by a colon and wildcards. + * + * @param {String} namespaces + * @api public + */ + +function enable(namespaces) { + exports.save(namespaces); + var split = (namespaces || '').split(/[\s,]+/); + var len = split.length; + for (var i = 0; i < len; i++) { + if (!split[i]) continue; // ignore empty strings + namespaces = split[i].replace(/\*/g, '.*?'); + if (namespaces[0] === '-') { + exports.skips.push(new RegExp('^' + namespaces.substr(1) + '$')); + } else { + exports.names.push(new RegExp('^' + namespaces + '$')); + } + } +} + +/** + * Disable debug output. + * + * @api public + */ + +function disable() { + exports.enable(''); +} + +/** + * Returns true if the given mode name is enabled, false otherwise. + * + * @param {String} name + * @return {Boolean} + * @api public + */ + +function enabled(name) { + var i, len; + for (i = 0, len = exports.skips.length; i < len; i++) { + if (exports.skips[i].test(name)) { + return false; + } + } + for (i = 0, len = exports.names.length; i < len; i++) { + if (exports.names[i].test(name)) { + return true; + } + } + return false; +} + +/** + * Coerce `val`. + * + * @param {Mixed} val + * @return {Mixed} + * @api private + */ + +function coerce(val) { + if (val instanceof Error) return val.stack || val.message; + return val; +} + +/***/ }), + +/***/ "./node_modules/deep-assign/index.js": +/*!*******************************************!*\ + !*** ./node_modules/deep-assign/index.js ***! + \*******************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +"use strict"; + + +var isObj = __webpack_require__(/*! is-obj */ "./node_modules/is-obj/index.js"); +var hasOwnProperty = Object.prototype.hasOwnProperty; +var propIsEnumerable = Object.prototype.propertyIsEnumerable; +function toObject(val) { + if (val === null || val === undefined) { + throw new TypeError('Sources cannot be null or undefined'); + } + return Object(val); +} +function assignKey(to, from, key) { + var val = from[key]; + if (val === undefined || val === null) { + return; + } + if (hasOwnProperty.call(to, key)) { + if (to[key] === undefined || to[key] === null) { + throw new TypeError('Cannot convert undefined or null to object (' + key + ')'); + } + } + if (!hasOwnProperty.call(to, key) || !isObj(val)) { + to[key] = val; + } else { + to[key] = assign(Object(to[key]), from[key]); + } +} +function assign(to, from) { + if (to === from) { + return to; + } + from = Object(from); + for (var key in from) { + if (hasOwnProperty.call(from, key)) { + assignKey(to, from, key); + } + } + if (Object.getOwnPropertySymbols) { + var symbols = Object.getOwnPropertySymbols(from); + for (var i = 0; i < symbols.length; i++) { + if (propIsEnumerable.call(from, symbols[i])) { + assignKey(to, from, symbols[i]); + } + } + } + return to; +} +module.exports = function deepAssign(target) { + target = toObject(target); + for (var s = 1; s < arguments.length; s++) { + assign(target, arguments[s]); + } + return target; +}; + +/***/ }), + +/***/ "./node_modules/dtype/index.js": +/*!*************************************!*\ + !*** ./node_modules/dtype/index.js ***! + \*************************************/ +/***/ ((module) => { + +module.exports = function (dtype) { + switch (dtype) { + case 'int8': + return Int8Array; + case 'int16': + return Int16Array; + case 'int32': + return Int32Array; + case 'uint8': + return Uint8Array; + case 'uint16': + return Uint16Array; + case 'uint32': + return Uint32Array; + case 'float32': + return Float32Array; + case 'float64': + return Float64Array; + case 'array': + return Array; + case 'uint8_clamped': + return Uint8ClampedArray; + } +}; + +/***/ }), + +/***/ "./node_modules/global/window.js": +/*!***************************************!*\ + !*** ./node_modules/global/window.js ***! + \***************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var win; +if (typeof window !== "undefined") { + win = window; +} else if (typeof __webpack_require__.g !== "undefined") { + win = __webpack_require__.g; +} else if (typeof self !== "undefined") { + win = self; +} else { + win = {}; +} +module.exports = win; + +/***/ }), + +/***/ "./node_modules/ieee754/index.js": +/*!***************************************!*\ + !*** ./node_modules/ieee754/index.js ***! + \***************************************/ +/***/ ((__unused_webpack_module, exports) => { + +/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh */ +exports.read = function (buffer, offset, isLE, mLen, nBytes) { + var e, m; + var eLen = nBytes * 8 - mLen - 1; + var eMax = (1 << eLen) - 1; + var eBias = eMax >> 1; + var nBits = -7; + var i = isLE ? nBytes - 1 : 0; + var d = isLE ? -1 : 1; + var s = buffer[offset + i]; + i += d; + e = s & (1 << -nBits) - 1; + s >>= -nBits; + nBits += eLen; + for (; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8) {} + m = e & (1 << -nBits) - 1; + e >>= -nBits; + nBits += mLen; + for (; nBits > 0; m = m * 256 + buffer[offset + i], i += d, nBits -= 8) {} + if (e === 0) { + e = 1 - eBias; + } else if (e === eMax) { + return m ? NaN : (s ? -1 : 1) * Infinity; + } else { + m = m + Math.pow(2, mLen); + e = e - eBias; + } + return (s ? -1 : 1) * m * Math.pow(2, e - mLen); +}; +exports.write = function (buffer, value, offset, isLE, mLen, nBytes) { + var e, m, c; + var eLen = nBytes * 8 - mLen - 1; + var eMax = (1 << eLen) - 1; + var eBias = eMax >> 1; + var rt = mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0; + var i = isLE ? 0 : nBytes - 1; + var d = isLE ? 1 : -1; + var s = value < 0 || value === 0 && 1 / value < 0 ? 1 : 0; + value = Math.abs(value); + if (isNaN(value) || value === Infinity) { + m = isNaN(value) ? 1 : 0; + e = eMax; + } else { + e = Math.floor(Math.log(value) / Math.LN2); + if (value * (c = Math.pow(2, -e)) < 1) { + e--; + c *= 2; + } + if (e + eBias >= 1) { + value += rt / c; + } else { + value += rt * Math.pow(2, 1 - eBias); + } + if (value * c >= 2) { + e++; + c /= 2; + } + if (e + eBias >= eMax) { + m = 0; + e = eMax; + } else if (e + eBias >= 1) { + m = (value * c - 1) * Math.pow(2, mLen); + e = e + eBias; + } else { + m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen); + e = 0; + } + } + for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {} + e = e << mLen | m; + eLen += mLen; + for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {} + buffer[offset + i - d] |= s * 128; +}; + +/***/ }), + +/***/ "./node_modules/is-buffer/index.js": +/*!*****************************************!*\ + !*** ./node_modules/is-buffer/index.js ***! + \*****************************************/ +/***/ ((module) => { + +/*! + * Determine if an object is a Buffer + * + * @author Feross Aboukhadijeh + * @license MIT + */ + +// The _isBuffer check is for Safari 5-7 support, because it's missing +// Object.prototype.constructor. Remove this eventually +module.exports = function (obj) { + return obj != null && (isBuffer(obj) || isSlowBuffer(obj) || !!obj._isBuffer); +}; +function isBuffer(obj) { + return !!obj.constructor && typeof obj.constructor.isBuffer === 'function' && obj.constructor.isBuffer(obj); +} + +// For Node v0.10 support. Remove this eventually. +function isSlowBuffer(obj) { + return typeof obj.readFloatLE === 'function' && typeof obj.slice === 'function' && isBuffer(obj.slice(0, 0)); +} + +/***/ }), + +/***/ "./node_modules/is-function/index.js": +/*!*******************************************!*\ + !*** ./node_modules/is-function/index.js ***! + \*******************************************/ +/***/ ((module) => { + +module.exports = 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); +} +; + +/***/ }), + +/***/ "./node_modules/is-obj/index.js": +/*!**************************************!*\ + !*** ./node_modules/is-obj/index.js ***! + \**************************************/ +/***/ ((module) => { + +"use strict"; + + +module.exports = function (x) { + var type = typeof x; + return x !== null && (type === 'object' || type === 'function'); +}; + +/***/ }), + +/***/ "./node_modules/layout-bmfont-text/index.js": +/*!**************************************************!*\ + !*** ./node_modules/layout-bmfont-text/index.js ***! + \**************************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var wordWrap = __webpack_require__(/*! word-wrapper */ "./node_modules/word-wrapper/index.js"); +var xtend = __webpack_require__(/*! xtend */ "./node_modules/xtend/immutable.js"); +var number = __webpack_require__(/*! as-number */ "./node_modules/as-number/index.js"); +var X_HEIGHTS = ['x', 'e', 'a', 'o', 'n', 's', 'r', 'c', 'u', 'm', 'v', 'w', 'z']; +var M_WIDTHS = ['m', 'w']; +var CAP_HEIGHTS = ['H', 'I', 'N', 'E', 'F', 'K', 'L', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']; +var TAB_ID = '\t'.charCodeAt(0); +var SPACE_ID = ' '.charCodeAt(0); +var ALIGN_LEFT = 0, + ALIGN_CENTER = 1, + ALIGN_RIGHT = 2; +module.exports = function createLayout(opt) { + return new TextLayout(opt); +}; +function TextLayout(opt) { + this.glyphs = []; + this._measure = this.computeMetrics.bind(this); + this.update(opt); +} +TextLayout.prototype.update = function (opt) { + opt = xtend({ + measure: this._measure + }, opt); + this._opt = opt; + this._opt.tabSize = number(this._opt.tabSize, 4); + if (!opt.font) throw new Error('must provide a valid bitmap font'); + var glyphs = this.glyphs; + var text = opt.text || ''; + var font = opt.font; + this._setupSpaceGlyphs(font); + var lines = wordWrap.lines(text, opt); + var minWidth = opt.width || 0; + + //clear glyphs + glyphs.length = 0; + + //get max line width + var maxLineWidth = lines.reduce(function (prev, line) { + return Math.max(prev, line.width, minWidth); + }, 0); + + //the pen position + var x = 0; + var y = 0; + var lineHeight = number(opt.lineHeight, font.common.lineHeight); + var baseline = font.common.base; + var descender = lineHeight - baseline; + var letterSpacing = opt.letterSpacing || 0; + var height = lineHeight * lines.length - descender; + var align = getAlignType(this._opt.align); + + //draw text along baseline + y -= height; + + //the metrics for this text layout + this._width = maxLineWidth; + this._height = height; + this._descender = lineHeight - baseline; + this._baseline = baseline; + this._xHeight = getXHeight(font); + this._capHeight = getCapHeight(font); + this._lineHeight = lineHeight; + this._ascender = lineHeight - descender - this._xHeight; + + //layout each glyph + var self = this; + lines.forEach(function (line, lineIndex) { + var start = line.start; + var end = line.end; + var lineWidth = line.width; + var lastGlyph; + + //for each glyph in that line... + for (var i = start; i < end; i++) { + var id = text.charCodeAt(i); + var glyph = self.getGlyph(font, id); + if (glyph) { + if (lastGlyph) x += getKerning(font, lastGlyph.id, glyph.id); + var tx = x; + if (align === ALIGN_CENTER) tx += (maxLineWidth - lineWidth) / 2;else if (align === ALIGN_RIGHT) tx += maxLineWidth - lineWidth; + glyphs.push({ + position: [tx, y], + data: glyph, + index: i, + line: lineIndex + }); + + //move pen forward + x += glyph.xadvance + letterSpacing; + lastGlyph = glyph; + } + } + + //next line down + y += lineHeight; + x = 0; + }); + this._linesTotal = lines.length; +}; +TextLayout.prototype._setupSpaceGlyphs = function (font) { + //These are fallbacks, when the font doesn't include + //' ' or '\t' glyphs + this._fallbackSpaceGlyph = null; + this._fallbackTabGlyph = null; + if (!font.chars || font.chars.length === 0) return; + + //try to get space glyph + //then fall back to the 'm' or 'w' glyphs + //then fall back to the first glyph available + var space = getGlyphById(font, SPACE_ID) || getMGlyph(font) || font.chars[0]; + + //and create a fallback for tab + var tabWidth = this._opt.tabSize * space.xadvance; + this._fallbackSpaceGlyph = space; + this._fallbackTabGlyph = xtend(space, { + x: 0, + y: 0, + xadvance: tabWidth, + id: TAB_ID, + xoffset: 0, + yoffset: 0, + width: 0, + height: 0 + }); +}; +TextLayout.prototype.getGlyph = function (font, id) { + var glyph = getGlyphById(font, id); + if (glyph) return glyph;else if (id === TAB_ID) return this._fallbackTabGlyph;else if (id === SPACE_ID) return this._fallbackSpaceGlyph; + return null; +}; +TextLayout.prototype.computeMetrics = function (text, start, end, width) { + var letterSpacing = this._opt.letterSpacing || 0; + var font = this._opt.font; + var curPen = 0; + var curWidth = 0; + var count = 0; + var glyph; + var lastGlyph; + if (!font.chars || font.chars.length === 0) { + return { + start: start, + end: start, + width: 0 + }; + } + end = Math.min(text.length, end); + for (var i = start; i < end; i++) { + var id = text.charCodeAt(i); + var glyph = this.getGlyph(font, id); + if (glyph) { + //move pen forward + var xoff = glyph.xoffset; + var kern = lastGlyph ? getKerning(font, lastGlyph.id, glyph.id) : 0; + curPen += kern; + var nextPen = curPen + glyph.xadvance + letterSpacing; + var nextWidth = curPen + glyph.width; + + //we've hit our limit; we can't move onto the next glyph + if (nextWidth >= width || nextPen >= width) break; + + //otherwise continue along our line + curPen = nextPen; + curWidth = nextWidth; + lastGlyph = glyph; + } + count++; + } + + //make sure rightmost edge lines up with rendered glyphs + if (lastGlyph) curWidth += lastGlyph.xoffset; + return { + start: start, + end: start + count, + width: curWidth + }; +} + +//getters for the private vars +; +['width', 'height', 'descender', 'ascender', 'xHeight', 'baseline', 'capHeight', 'lineHeight'].forEach(addGetter); +function addGetter(name) { + Object.defineProperty(TextLayout.prototype, name, { + get: wrapper(name), + configurable: true + }); +} + +//create lookups for private vars +function wrapper(name) { + return new Function(['return function ' + name + '() {', ' return this._' + name, '}'].join('\n'))(); +} +function getGlyphById(font, id) { + if (!font.chars || font.chars.length === 0) return null; + var glyphIdx = findChar(font.chars, id); + if (glyphIdx >= 0) return font.chars[glyphIdx]; + return null; +} +function getXHeight(font) { + for (var i = 0; i < X_HEIGHTS.length; i++) { + var id = X_HEIGHTS[i].charCodeAt(0); + var idx = findChar(font.chars, id); + if (idx >= 0) return font.chars[idx].height; + } + return 0; +} +function getMGlyph(font) { + for (var i = 0; i < M_WIDTHS.length; i++) { + var id = M_WIDTHS[i].charCodeAt(0); + var idx = findChar(font.chars, id); + if (idx >= 0) return font.chars[idx]; + } + return 0; +} +function getCapHeight(font) { + for (var i = 0; i < CAP_HEIGHTS.length; i++) { + var id = CAP_HEIGHTS[i].charCodeAt(0); + var idx = findChar(font.chars, id); + if (idx >= 0) return font.chars[idx].height; + } + return 0; +} +function getKerning(font, left, right) { + if (!font.kernings || font.kernings.length === 0) return 0; + var table = font.kernings; + for (var i = 0; i < table.length; i++) { + var kern = table[i]; + if (kern.first === left && kern.second === right) return kern.amount; + } + return 0; +} +function getAlignType(align) { + if (align === 'center') return ALIGN_CENTER;else if (align === 'right') return ALIGN_RIGHT; + return ALIGN_LEFT; +} +function findChar(array, value, start) { + start = start || 0; + for (var i = start; i < array.length; i++) { + if (array[i].id === value) { + return i; + } + } + return -1; +} + +/***/ }), + +/***/ "./node_modules/load-bmfont/browser.js": +/*!*********************************************!*\ + !*** ./node_modules/load-bmfont/browser.js ***! + \*********************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +/* provided dependency */ var Buffer = __webpack_require__(/*! buffer */ "./node_modules/buffer/index.js")["Buffer"]; +var xhr = __webpack_require__(/*! xhr */ "./node_modules/xhr/index.js"); +var noop = function () {}; +var parseASCII = __webpack_require__(/*! parse-bmfont-ascii */ "./node_modules/parse-bmfont-ascii/index.js"); +var parseXML = __webpack_require__(/*! parse-bmfont-xml */ "./node_modules/parse-bmfont-xml/lib/browser.js"); +var readBinary = __webpack_require__(/*! parse-bmfont-binary */ "./node_modules/parse-bmfont-binary/index.js"); +var isBinaryFormat = __webpack_require__(/*! ./lib/is-binary */ "./node_modules/load-bmfont/lib/is-binary.js"); +var xtend = __webpack_require__(/*! xtend */ "./node_modules/xtend/immutable.js"); +var xml2 = function hasXML2() { + return self.XMLHttpRequest && "withCredentials" in new XMLHttpRequest(); +}(); +module.exports = function (opt, cb) { + cb = typeof cb === 'function' ? cb : noop; + if (typeof opt === 'string') opt = { + uri: opt + };else if (!opt) opt = {}; + var expectBinary = opt.binary; + if (expectBinary) opt = getBinaryOpts(opt); + xhr(opt, function (err, res, body) { + if (err) return cb(err); + if (!/^2/.test(res.statusCode)) return cb(new Error('http status code: ' + res.statusCode)); + if (!body) return cb(new Error('no body result')); + var binary = false; + + //if the response type is an array buffer, + //we need to convert it into a regular Buffer object + if (isArrayBuffer(body)) { + var array = new Uint8Array(body); + body = Buffer.from(array, 'binary'); + } + + //now check the string/Buffer response + //and see if it has a binary BMF header + if (isBinaryFormat(body)) { + binary = true; + //if we have a string, turn it into a Buffer + if (typeof body === 'string') body = Buffer.from(body, 'binary'); + } + + //we are not parsing a binary format, just ASCII/XML/etc + if (!binary) { + //might still be a buffer if responseType is 'arraybuffer' + if (Buffer.isBuffer(body)) body = body.toString(opt.encoding); + body = body.trim(); + } + var result; + try { + var type = res.headers['content-type']; + if (binary) result = readBinary(body);else if (/json/.test(type) || body.charAt(0) === '{') result = JSON.parse(body);else if (/xml/.test(type) || body.charAt(0) === '<') result = parseXML(body);else result = parseASCII(body); + } catch (e) { + cb(new Error('error parsing font ' + e.message)); + cb = noop; + } + cb(null, result); + }); +}; +function isArrayBuffer(arr) { + var str = Object.prototype.toString; + return str.call(arr) === '[object ArrayBuffer]'; +} +function getBinaryOpts(opt) { + //IE10+ and other modern browsers support array buffers + if (xml2) return xtend(opt, { + responseType: 'arraybuffer' + }); + if (typeof self.XMLHttpRequest === 'undefined') throw new Error('your browser does not support XHR loading'); + + //IE9 and XML1 browsers could still use an override + var req = new self.XMLHttpRequest(); + req.overrideMimeType('text/plain; charset=x-user-defined'); + return xtend({ + xhr: req + }, opt); +} + +/***/ }), + +/***/ "./node_modules/load-bmfont/lib/is-binary.js": +/*!***************************************************!*\ + !*** ./node_modules/load-bmfont/lib/is-binary.js ***! + \***************************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +/* provided dependency */ var Buffer = __webpack_require__(/*! buffer */ "./node_modules/buffer/index.js")["Buffer"]; +var equal = __webpack_require__(/*! buffer-equal */ "./node_modules/buffer-equal/index.js"); +var HEADER = Buffer.from([66, 77, 70, 3]); +module.exports = function (buf) { + if (typeof buf === 'string') return buf.substring(0, 3) === 'BMF'; + return buf.length > 4 && equal(buf.slice(0, 4), HEADER); +}; + +/***/ }), + +/***/ "./node_modules/object-assign/index.js": +/*!*********************************************!*\ + !*** ./node_modules/object-assign/index.js ***! + \*********************************************/ +/***/ ((module) => { + +"use strict"; +/* +object-assign +(c) Sindre Sorhus +@license MIT +*/ + + + +/* eslint-disable no-unused-vars */ +var getOwnPropertySymbols = Object.getOwnPropertySymbols; +var hasOwnProperty = Object.prototype.hasOwnProperty; +var propIsEnumerable = Object.prototype.propertyIsEnumerable; +function toObject(val) { + if (val === null || val === undefined) { + throw new TypeError('Object.assign cannot be called with null or undefined'); + } + return Object(val); +} +function shouldUseNative() { + try { + if (!Object.assign) { + return false; + } + + // Detect buggy property enumeration order in older V8 versions. + + // https://bugs.chromium.org/p/v8/issues/detail?id=4118 + var test1 = new String('abc'); // eslint-disable-line no-new-wrappers + test1[5] = 'de'; + if (Object.getOwnPropertyNames(test1)[0] === '5') { + return false; + } + + // https://bugs.chromium.org/p/v8/issues/detail?id=3056 + var test2 = {}; + for (var i = 0; i < 10; i++) { + test2['_' + String.fromCharCode(i)] = i; + } + var order2 = Object.getOwnPropertyNames(test2).map(function (n) { + return test2[n]; + }); + if (order2.join('') !== '0123456789') { + return false; + } + + // https://bugs.chromium.org/p/v8/issues/detail?id=3056 + var test3 = {}; + 'abcdefghijklmnopqrst'.split('').forEach(function (letter) { + test3[letter] = letter; + }); + if (Object.keys(Object.assign({}, test3)).join('') !== 'abcdefghijklmnopqrst') { + return false; + } + return true; + } catch (err) { + // We don't expect any of the above to throw, but better to be safe. + return false; + } +} +module.exports = shouldUseNative() ? Object.assign : function (target, source) { + var from; + var to = toObject(target); + var symbols; + for (var s = 1; s < arguments.length; s++) { + from = Object(arguments[s]); + for (var key in from) { + if (hasOwnProperty.call(from, key)) { + to[key] = from[key]; + } + } + if (getOwnPropertySymbols) { + symbols = getOwnPropertySymbols(from); + for (var i = 0; i < symbols.length; i++) { + if (propIsEnumerable.call(from, symbols[i])) { + to[symbols[i]] = from[symbols[i]]; + } + } + } + } + return to; +}; + +/***/ }), + +/***/ "./node_modules/parse-bmfont-ascii/index.js": +/*!**************************************************!*\ + !*** ./node_modules/parse-bmfont-ascii/index.js ***! + \**************************************************/ +/***/ ((module) => { + +module.exports = function parseBMFontAscii(data) { + if (!data) throw new Error('no data provided'); + data = data.toString().trim(); + var output = { + pages: [], + chars: [], + kernings: [] + }; + var lines = data.split(/\r\n?|\n/g); + if (lines.length === 0) throw new Error('no data in BMFont file'); + for (var i = 0; i < lines.length; i++) { + var lineData = splitLine(lines[i], i); + if (!lineData) + //skip empty lines + continue; + if (lineData.key === 'page') { + if (typeof lineData.data.id !== 'number') throw new Error('malformed file at line ' + i + ' -- needs page id=N'); + if (typeof lineData.data.file !== 'string') throw new Error('malformed file at line ' + i + ' -- needs page file="path"'); + output.pages[lineData.data.id] = lineData.data.file; + } else if (lineData.key === 'chars' || lineData.key === 'kernings') { + //... do nothing for these two ... + } else if (lineData.key === 'char') { + output.chars.push(lineData.data); + } else if (lineData.key === 'kerning') { + output.kernings.push(lineData.data); + } else { + output[lineData.key] = lineData.data; + } + } + return output; +}; +function splitLine(line, idx) { + line = line.replace(/\t+/g, ' ').trim(); + if (!line) return null; + var space = line.indexOf(' '); + if (space === -1) throw new Error("no named row at line " + idx); + var key = line.substring(0, space); + line = line.substring(space + 1); + //clear "letter" field as it is non-standard and + //requires additional complexity to parse " / = symbols + line = line.replace(/letter=[\'\"]\S+[\'\"]/gi, ''); + line = line.split("="); + line = line.map(function (str) { + return str.trim().match(/(".*?"|[^"\s]+)+(?=\s*|\s*$)/g); + }); + var data = []; + for (var i = 0; i < line.length; i++) { + var dt = line[i]; + if (i === 0) { + data.push({ + key: dt[0], + data: "" + }); + } else if (i === line.length - 1) { + data[data.length - 1].data = parseData(dt[0]); + } else { + data[data.length - 1].data = parseData(dt[0]); + data.push({ + key: dt[1], + data: "" + }); + } + } + var out = { + key: key, + data: {} + }; + data.forEach(function (v) { + out.data[v.key] = v.data; + }); + return out; +} +function parseData(data) { + if (!data || data.length === 0) return ""; + if (data.indexOf('"') === 0 || data.indexOf("'") === 0) return data.substring(1, data.length - 1); + if (data.indexOf(',') !== -1) return parseIntList(data); + return parseInt(data, 10); +} +function parseIntList(data) { + return data.split(',').map(function (val) { + return parseInt(val, 10); + }); +} + +/***/ }), + +/***/ "./node_modules/parse-bmfont-binary/index.js": +/*!***************************************************!*\ + !*** ./node_modules/parse-bmfont-binary/index.js ***! + \***************************************************/ +/***/ ((module) => { + +var HEADER = [66, 77, 70]; +module.exports = function readBMFontBinary(buf) { + if (buf.length < 6) throw new Error('invalid buffer length for BMFont'); + var header = HEADER.every(function (byte, i) { + return buf.readUInt8(i) === byte; + }); + if (!header) throw new Error('BMFont missing BMF byte header'); + var i = 3; + var vers = buf.readUInt8(i++); + if (vers > 3) throw new Error('Only supports BMFont Binary v3 (BMFont App v1.10)'); + var target = { + kernings: [], + chars: [] + }; + for (var b = 0; b < 5; b++) i += readBlock(target, buf, i); + return target; +}; +function readBlock(target, buf, i) { + if (i > buf.length - 1) return 0; + var blockID = buf.readUInt8(i++); + var blockSize = buf.readInt32LE(i); + i += 4; + switch (blockID) { + case 1: + target.info = readInfo(buf, i); + break; + case 2: + target.common = readCommon(buf, i); + break; + case 3: + target.pages = readPages(buf, i, blockSize); + break; + case 4: + target.chars = readChars(buf, i, blockSize); + break; + case 5: + target.kernings = readKernings(buf, i, blockSize); + break; + } + return 5 + blockSize; +} +function readInfo(buf, i) { + var info = {}; + info.size = buf.readInt16LE(i); + var bitField = buf.readUInt8(i + 2); + info.smooth = bitField >> 7 & 1; + info.unicode = bitField >> 6 & 1; + info.italic = bitField >> 5 & 1; + info.bold = bitField >> 4 & 1; + + //fixedHeight is only mentioned in binary spec + if (bitField >> 3 & 1) info.fixedHeight = 1; + info.charset = buf.readUInt8(i + 3) || ''; + info.stretchH = buf.readUInt16LE(i + 4); + info.aa = buf.readUInt8(i + 6); + info.padding = [buf.readInt8(i + 7), buf.readInt8(i + 8), buf.readInt8(i + 9), buf.readInt8(i + 10)]; + info.spacing = [buf.readInt8(i + 11), buf.readInt8(i + 12)]; + info.outline = buf.readUInt8(i + 13); + info.face = readStringNT(buf, i + 14); + return info; +} +function readCommon(buf, i) { + var common = {}; + common.lineHeight = buf.readUInt16LE(i); + common.base = buf.readUInt16LE(i + 2); + common.scaleW = buf.readUInt16LE(i + 4); + common.scaleH = buf.readUInt16LE(i + 6); + common.pages = buf.readUInt16LE(i + 8); + var bitField = buf.readUInt8(i + 10); + common.packed = 0; + common.alphaChnl = buf.readUInt8(i + 11); + common.redChnl = buf.readUInt8(i + 12); + common.greenChnl = buf.readUInt8(i + 13); + common.blueChnl = buf.readUInt8(i + 14); + return common; +} +function readPages(buf, i, size) { + var pages = []; + var text = readNameNT(buf, i); + var len = text.length + 1; + var count = size / len; + for (var c = 0; c < count; c++) { + pages[c] = buf.slice(i, i + text.length).toString('utf8'); + i += len; + } + return pages; +} +function readChars(buf, i, blockSize) { + var chars = []; + var count = blockSize / 20; + for (var c = 0; c < count; c++) { + var char = {}; + var off = c * 20; + char.id = buf.readUInt32LE(i + 0 + off); + char.x = buf.readUInt16LE(i + 4 + off); + char.y = buf.readUInt16LE(i + 6 + off); + char.width = buf.readUInt16LE(i + 8 + off); + char.height = buf.readUInt16LE(i + 10 + off); + char.xoffset = buf.readInt16LE(i + 12 + off); + char.yoffset = buf.readInt16LE(i + 14 + off); + char.xadvance = buf.readInt16LE(i + 16 + off); + char.page = buf.readUInt8(i + 18 + off); + char.chnl = buf.readUInt8(i + 19 + off); + chars[c] = char; + } + return chars; +} +function readKernings(buf, i, blockSize) { + var kernings = []; + var count = blockSize / 10; + for (var c = 0; c < count; c++) { + var kern = {}; + var off = c * 10; + kern.first = buf.readUInt32LE(i + 0 + off); + kern.second = buf.readUInt32LE(i + 4 + off); + kern.amount = buf.readInt16LE(i + 8 + off); + kernings[c] = kern; + } + return kernings; +} +function readNameNT(buf, offset) { + var pos = offset; + for (; pos < buf.length; pos++) { + if (buf[pos] === 0x00) break; + } + return buf.slice(offset, pos); +} +function readStringNT(buf, offset) { + return readNameNT(buf, offset).toString('utf8'); +} + +/***/ }), + +/***/ "./node_modules/parse-bmfont-xml/lib/browser.js": +/*!******************************************************!*\ + !*** ./node_modules/parse-bmfont-xml/lib/browser.js ***! + \******************************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var parseAttributes = __webpack_require__(/*! ./parse-attribs */ "./node_modules/parse-bmfont-xml/lib/parse-attribs.js"); +var parseFromString = __webpack_require__(/*! xml-parse-from-string */ "./node_modules/xml-parse-from-string/index.js"); + +//In some cases element.attribute.nodeName can return +//all lowercase values.. so we need to map them to the correct +//case +var NAME_MAP = { + scaleh: 'scaleH', + scalew: 'scaleW', + stretchh: 'stretchH', + lineheight: 'lineHeight', + alphachnl: 'alphaChnl', + redchnl: 'redChnl', + greenchnl: 'greenChnl', + bluechnl: 'blueChnl' +}; +module.exports = function parse(data) { + data = data.toString(); + var xmlRoot = parseFromString(data); + var output = { + pages: [], + chars: [], + kernings: [] + } + + //get config settings + ; + ['info', 'common'].forEach(function (key) { + var element = xmlRoot.getElementsByTagName(key)[0]; + if (element) output[key] = parseAttributes(getAttribs(element)); + }); + + //get page info + var pageRoot = xmlRoot.getElementsByTagName('pages')[0]; + if (!pageRoot) throw new Error('malformed file -- no element'); + var pages = pageRoot.getElementsByTagName('page'); + for (var i = 0; i < pages.length; i++) { + var p = pages[i]; + var id = parseInt(p.getAttribute('id'), 10); + var file = p.getAttribute('file'); + if (isNaN(id)) throw new Error('malformed file -- page "id" attribute is NaN'); + if (!file) throw new Error('malformed file -- needs page "file" attribute'); + output.pages[parseInt(id, 10)] = file; + } + + //get kernings / chars + ; + ['chars', 'kernings'].forEach(function (key) { + var element = xmlRoot.getElementsByTagName(key)[0]; + if (!element) return; + var childTag = key.substring(0, key.length - 1); + var children = element.getElementsByTagName(childTag); + for (var i = 0; i < children.length; i++) { + var child = children[i]; + output[key].push(parseAttributes(getAttribs(child))); + } + }); + return output; +}; +function getAttribs(element) { + var attribs = getAttribList(element); + return attribs.reduce(function (dict, attrib) { + var key = mapName(attrib.nodeName); + dict[key] = attrib.nodeValue; + return dict; + }, {}); +} +function getAttribList(element) { + //IE8+ and modern browsers + var attribs = []; + for (var i = 0; i < element.attributes.length; i++) attribs.push(element.attributes[i]); + return attribs; +} +function mapName(nodeName) { + return NAME_MAP[nodeName.toLowerCase()] || nodeName; +} + +/***/ }), + +/***/ "./node_modules/parse-bmfont-xml/lib/parse-attribs.js": +/*!************************************************************!*\ + !*** ./node_modules/parse-bmfont-xml/lib/parse-attribs.js ***! + \************************************************************/ +/***/ ((module) => { + +//Some versions of GlyphDesigner have a typo +//that causes some bugs with parsing. +//Need to confirm with recent version of the software +//to see whether this is still an issue or not. +var GLYPH_DESIGNER_ERROR = 'chasrset'; +module.exports = function parseAttributes(obj) { + if (GLYPH_DESIGNER_ERROR in obj) { + obj['charset'] = obj[GLYPH_DESIGNER_ERROR]; + delete obj[GLYPH_DESIGNER_ERROR]; + } + for (var k in obj) { + if (k === 'face' || k === 'charset') continue;else if (k === 'padding' || k === 'spacing') obj[k] = parseIntList(obj[k]);else obj[k] = parseInt(obj[k], 10); + } + return obj; +}; +function parseIntList(data) { + return data.split(',').map(function (val) { + return parseInt(val, 10); + }); +} + +/***/ }), + +/***/ "./node_modules/parse-headers/parse-headers.js": +/*!*****************************************************!*\ + !*** ./node_modules/parse-headers/parse-headers.js ***! + \*****************************************************/ +/***/ ((module) => { + +var trim = function (string) { + return string.replace(/^\s+|\s+$/g, ''); + }, + isArray = function (arg) { + return Object.prototype.toString.call(arg) === '[object Array]'; + }; +module.exports = 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; +}; + +/***/ }), + +/***/ "./node_modules/present/lib/present-browser.js": +/*!*****************************************************!*\ + !*** ./node_modules/present/lib/present-browser.js ***! + \*****************************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var performance = __webpack_require__.g.performance || {}; +var present = function () { + var names = ['now', 'webkitNow', 'msNow', 'mozNow', 'oNow']; + while (names.length) { + var name = names.shift(); + if (name in performance) { + return performance[name].bind(performance); + } + } + var dateNow = Date.now || function () { + return new Date().getTime(); + }; + var navigationStart = (performance.timing || {}).navigationStart || dateNow(); + return function () { + return dateNow() - navigationStart; + }; +}(); +present.performanceNow = performance.now; +present.noConflict = function () { + performance.now = present.performanceNow; +}; +present.conflict = function () { + performance.now = present; +}; +present.conflict(); +module.exports = present; + +/***/ }), + +/***/ "./node_modules/process/browser.js": +/*!*****************************************!*\ + !*** ./node_modules/process/browser.js ***! + \*****************************************/ +/***/ ((module) => { + +// shim for using process in browser +var process = module.exports = {}; + +// cached from whatever global is present so that test runners that stub it +// don't break things. But we need to wrap it in a try catch in case it is +// wrapped in strict mode code which doesn't define any globals. It's inside a +// function because try/catches deoptimize in certain engines. + +var cachedSetTimeout; +var cachedClearTimeout; +function defaultSetTimout() { + throw new Error('setTimeout has not been defined'); +} +function defaultClearTimeout() { + throw new Error('clearTimeout has not been defined'); +} +(function () { + try { + if (typeof setTimeout === 'function') { + cachedSetTimeout = setTimeout; + } else { + cachedSetTimeout = defaultSetTimout; + } + } catch (e) { + cachedSetTimeout = defaultSetTimout; + } + try { + if (typeof clearTimeout === 'function') { + cachedClearTimeout = clearTimeout; + } else { + cachedClearTimeout = defaultClearTimeout; + } + } catch (e) { + cachedClearTimeout = defaultClearTimeout; + } +})(); +function runTimeout(fun) { + if (cachedSetTimeout === setTimeout) { + //normal enviroments in sane situations + return setTimeout(fun, 0); + } + // if setTimeout wasn't available but was latter defined + if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) { + cachedSetTimeout = setTimeout; + return setTimeout(fun, 0); + } + try { + // when when somebody has screwed with setTimeout but no I.E. maddness + return cachedSetTimeout(fun, 0); + } catch (e) { + try { + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally + return cachedSetTimeout.call(null, fun, 0); + } catch (e) { + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error + return cachedSetTimeout.call(this, fun, 0); + } + } +} +function runClearTimeout(marker) { + if (cachedClearTimeout === clearTimeout) { + //normal enviroments in sane situations + return clearTimeout(marker); + } + // if clearTimeout wasn't available but was latter defined + if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) { + cachedClearTimeout = clearTimeout; + return clearTimeout(marker); + } + try { + // when when somebody has screwed with setTimeout but no I.E. maddness + return cachedClearTimeout(marker); + } catch (e) { + try { + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally + return cachedClearTimeout.call(null, marker); + } catch (e) { + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error. + // Some versions of I.E. have different rules for clearTimeout vs setTimeout + return cachedClearTimeout.call(this, marker); + } + } +} +var queue = []; +var draining = false; +var currentQueue; +var queueIndex = -1; +function cleanUpNextTick() { + if (!draining || !currentQueue) { + return; + } + draining = false; + if (currentQueue.length) { + queue = currentQueue.concat(queue); + } else { + queueIndex = -1; + } + if (queue.length) { + drainQueue(); + } +} +function drainQueue() { + if (draining) { + return; + } + var timeout = runTimeout(cleanUpNextTick); + draining = true; + var len = queue.length; + while (len) { + currentQueue = queue; + queue = []; + while (++queueIndex < len) { + if (currentQueue) { + currentQueue[queueIndex].run(); + } + } + queueIndex = -1; + len = queue.length; + } + currentQueue = null; + draining = false; + runClearTimeout(timeout); +} +process.nextTick = function (fun) { + var args = new Array(arguments.length - 1); + if (arguments.length > 1) { + for (var i = 1; i < arguments.length; i++) { + args[i - 1] = arguments[i]; + } + } + queue.push(new Item(fun, args)); + if (queue.length === 1 && !draining) { + runTimeout(drainQueue); + } +}; + +// v8 likes predictible objects +function Item(fun, array) { + this.fun = fun; + this.array = array; +} +Item.prototype.run = function () { + this.fun.apply(null, this.array); +}; +process.title = 'browser'; +process.browser = true; +process.env = {}; +process.argv = []; +process.version = ''; // empty string to avoid regexp issues +process.versions = {}; +function noop() {} +process.on = noop; +process.addListener = noop; +process.once = noop; +process.off = noop; +process.removeListener = noop; +process.removeAllListeners = noop; +process.emit = noop; +process.prependListener = noop; +process.prependOnceListener = noop; +process.listeners = function (name) { + return []; +}; +process.binding = function (name) { + throw new Error('process.binding is not supported'); +}; +process.cwd = function () { + return '/'; +}; +process.chdir = function (dir) { + throw new Error('process.chdir is not supported'); +}; +process.umask = function () { + return 0; +}; + +/***/ }), + +/***/ "./node_modules/promise-polyfill/Promise.js": +/*!**************************************************!*\ + !*** ./node_modules/promise-polyfill/Promise.js ***! + \**************************************************/ +/***/ (function(module) { + +(function (root) { + // Store setTimeout reference so promise-polyfill will be unaffected by + // other code modifying setTimeout (like sinon.useFakeTimers()) + var setTimeoutFunc = setTimeout; + + // Use polyfill for setImmediate for performance gains + var asap = typeof setImmediate === 'function' && setImmediate || function (fn) { + setTimeoutFunc(fn, 1); + }; + + // Polyfill for Function.prototype.bind + function bind(fn, thisArg) { + return function () { + fn.apply(thisArg, arguments); + }; + } + var isArray = Array.isArray || function (value) { + return Object.prototype.toString.call(value) === "[object Array]"; + }; + function Promise(fn) { + if (typeof this !== 'object') throw new TypeError('Promises must be constructed via new'); + if (typeof fn !== 'function') throw new TypeError('not a function'); + this._state = null; + this._value = null; + this._deferreds = []; + doResolve(fn, bind(resolve, this), bind(reject, this)); + } + function handle(deferred) { + var me = this; + if (this._state === null) { + this._deferreds.push(deferred); + return; + } + asap(function () { + var cb = me._state ? deferred.onFulfilled : deferred.onRejected; + if (cb === null) { + (me._state ? deferred.resolve : deferred.reject)(me._value); + return; + } + var ret; + try { + ret = cb(me._value); + } catch (e) { + deferred.reject(e); + return; + } + deferred.resolve(ret); + }); + } + function resolve(newValue) { + try { + //Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure + if (newValue === this) throw new TypeError('A promise cannot be resolved with itself.'); + if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) { + var then = newValue.then; + if (typeof then === 'function') { + doResolve(bind(then, newValue), bind(resolve, this), bind(reject, this)); + return; + } + } + this._state = true; + this._value = newValue; + finale.call(this); + } catch (e) { + reject.call(this, e); + } + } + function reject(newValue) { + this._state = false; + this._value = newValue; + finale.call(this); + } + function finale() { + for (var i = 0, len = this._deferreds.length; i < len; i++) { + handle.call(this, this._deferreds[i]); + } + this._deferreds = null; + } + function Handler(onFulfilled, onRejected, resolve, reject) { + this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null; + this.onRejected = typeof onRejected === 'function' ? onRejected : null; + this.resolve = resolve; + this.reject = reject; + } + + /** + * Take a potentially misbehaving resolver function and make sure + * onFulfilled and onRejected are only called once. + * + * Makes no guarantees about asynchrony. + */ + function doResolve(fn, onFulfilled, onRejected) { + var done = false; + try { + fn(function (value) { + if (done) return; + done = true; + onFulfilled(value); + }, function (reason) { + if (done) return; + done = true; + onRejected(reason); + }); + } catch (ex) { + if (done) return; + done = true; + onRejected(ex); + } + } + Promise.prototype['catch'] = function (onRejected) { + return this.then(null, onRejected); + }; + Promise.prototype.then = function (onFulfilled, onRejected) { + var me = this; + return new Promise(function (resolve, reject) { + handle.call(me, new Handler(onFulfilled, onRejected, resolve, reject)); + }); + }; + Promise.all = function () { + var args = Array.prototype.slice.call(arguments.length === 1 && isArray(arguments[0]) ? arguments[0] : arguments); + return new Promise(function (resolve, reject) { + if (args.length === 0) return resolve([]); + var remaining = args.length; + function res(i, val) { + try { + if (val && (typeof val === 'object' || typeof val === 'function')) { + var then = val.then; + if (typeof then === 'function') { + then.call(val, function (val) { + res(i, val); + }, reject); + return; + } + } + args[i] = val; + if (--remaining === 0) { + resolve(args); + } + } catch (ex) { + reject(ex); + } + } + for (var i = 0; i < args.length; i++) { + res(i, args[i]); + } + }); + }; + Promise.resolve = function (value) { + if (value && typeof value === 'object' && value.constructor === Promise) { + return value; + } + return new Promise(function (resolve) { + resolve(value); + }); + }; + Promise.reject = function (value) { + return new Promise(function (resolve, reject) { + reject(value); + }); + }; + Promise.race = function (values) { + return new Promise(function (resolve, reject) { + for (var i = 0, len = values.length; i < len; i++) { + values[i].then(resolve, reject); + } + }); + }; + + /** + * Set the immediate function to execute callbacks + * @param fn {function} Function to execute + * @private + */ + Promise._setImmediateFn = function _setImmediateFn(fn) { + asap = fn; + }; + if ( true && module.exports) { + module.exports = Promise; + } else if (!root.Promise) { + root.Promise = Promise; + } +})(this); + +/***/ }), + +/***/ "./node_modules/quad-indices/index.js": +/*!********************************************!*\ + !*** ./node_modules/quad-indices/index.js ***! + \********************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var dtype = __webpack_require__(/*! dtype */ "./node_modules/dtype/index.js"); +var anArray = __webpack_require__(/*! an-array */ "./node_modules/an-array/index.js"); +var isBuffer = __webpack_require__(/*! is-buffer */ "./node_modules/is-buffer/index.js"); +var CW = [0, 2, 3]; +var CCW = [2, 1, 3]; +module.exports = function createQuadElements(array, opt) { + //if user didn't specify an output array + if (!array || !(anArray(array) || isBuffer(array))) { + opt = array || {}; + array = null; + } + if (typeof opt === 'number') + //backwards-compatible + opt = { + count: opt + };else opt = opt || {}; + var type = typeof opt.type === 'string' ? opt.type : 'uint16'; + var count = typeof opt.count === 'number' ? opt.count : 1; + var start = opt.start || 0; + var dir = opt.clockwise !== false ? CW : CCW, + a = dir[0], + b = dir[1], + c = dir[2]; + var numIndices = count * 6; + var indices = array || new (dtype(type))(numIndices); + for (var i = 0, j = 0; i < numIndices; i += 6, j += 4) { + var x = i + start; + indices[x + 0] = j + 0; + indices[x + 1] = j + 1; + indices[x + 2] = j + 2; + indices[x + 3] = j + a; + indices[x + 4] = j + b; + indices[x + 5] = j + c; + } + return indices; +}; + +/***/ }), + +/***/ "./node_modules/super-animejs/lib/anime.es.js": +/*!****************************************************!*\ + !*** ./node_modules/super-animejs/lib/anime.es.js ***! + \****************************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* + * anime.js v3.0.0 + * (c) 2019 Julian Garnier + * Released under the MIT license + * animejs.com + */ + +// Defaults + +var defaultInstanceSettings = { + update: null, + begin: null, + loopBegin: null, + changeBegin: null, + change: null, + changeComplete: null, + loopComplete: null, + complete: null, + loop: 1, + direction: 'normal', + autoplay: true, + timelineOffset: 0 +}; +var defaultTweenSettings = { + duration: 1000, + delay: 0, + endDelay: 0, + easing: 'easeOutElastic(1, .5)', + round: 0 +}; +var validTransforms = ['translateX', 'translateY', 'translateZ', 'rotate', 'rotateX', 'rotateY', 'rotateZ', 'scale', 'scaleX', 'scaleY', 'scaleZ', 'skew', 'skewX', 'skewY', 'perspective']; + +// Caching + +var cache = { + CSS: {}, + springs: {} +}; + +// Utils + +function minMax(val, min, max) { + return Math.min(Math.max(val, min), max); +} +function stringContains(str, text) { + return str.indexOf(text) > -1; +} +function applyArguments(func, args) { + return func.apply(null, args); +} +var hexRegex = /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i; +var rgbPrefixRegex = /^rgb/; +var hslRegex = /^hsl/; +var is = { + arr: function (a) { + return Array.isArray(a); + }, + obj: function (a) { + return stringContains(Object.prototype.toString.call(a), 'Object'); + }, + pth: function (a) { + return is.obj(a) && a.hasOwnProperty('totalLength'); + }, + svg: function (a) { + return a instanceof SVGElement; + }, + inp: function (a) { + return a instanceof HTMLInputElement; + }, + dom: function (a) { + return a.nodeType || is.svg(a); + }, + str: function (a) { + return typeof a === 'string'; + }, + fnc: function (a) { + return typeof a === 'function'; + }, + und: function (a) { + return typeof a === 'undefined'; + }, + hex: function (a) { + return hexRegex.test(a); + }, + rgb: function (a) { + return rgbPrefixRegex.test(a); + }, + hsl: function (a) { + return hslRegex.test(a); + }, + col: function (a) { + return is.hex(a) || is.rgb(a) || is.hsl(a); + }, + key: function (a) { + return !defaultInstanceSettings.hasOwnProperty(a) && !defaultTweenSettings.hasOwnProperty(a) && a !== 'targets' && a !== 'keyframes'; + } +}; + +// Easings + +var easingFunctionRegex = /\(([^)]+)\)/; +function parseEasingParameters(string) { + var match = easingFunctionRegex.exec(string); + return match ? match[1].split(',').map(function (p) { + return parseFloat(p); + }) : []; +} + +// Spring solver inspired by Webkit Copyright © 2016 Apple Inc. All rights reserved. https://webkit.org/demos/spring/spring.js + +function spring(string, duration) { + var params = parseEasingParameters(string); + var mass = minMax(is.und(params[0]) ? 1 : params[0], .1, 100); + var stiffness = minMax(is.und(params[1]) ? 100 : params[1], .1, 100); + var damping = minMax(is.und(params[2]) ? 10 : params[2], .1, 100); + var velocity = minMax(is.und(params[3]) ? 0 : params[3], .1, 100); + var w0 = Math.sqrt(stiffness / mass); + var zeta = damping / (2 * Math.sqrt(stiffness * mass)); + var wd = zeta < 1 ? w0 * Math.sqrt(1 - zeta * zeta) : 0; + var a = 1; + var b = zeta < 1 ? (zeta * w0 + -velocity) / wd : -velocity + w0; + function solver(t) { + var progress = duration ? duration * t / 1000 : t; + if (zeta < 1) { + progress = Math.exp(-progress * zeta * w0) * (a * Math.cos(wd * progress) + b * Math.sin(wd * progress)); + } else { + progress = (a + b * progress) * Math.exp(-progress * w0); + } + if (t === 0 || t === 1) { + return t; + } + return 1 - progress; + } + function getDuration() { + var cached = cache.springs[string]; + if (cached) { + return cached; + } + var frame = 1 / 6; + var elapsed = 0; + var rest = 0; + while (true) { + elapsed += frame; + if (solver(elapsed) === 1) { + rest++; + if (rest >= 16) { + break; + } + } else { + rest = 0; + } + } + var duration = elapsed * frame * 1000; + cache.springs[string] = duration; + return duration; + } + return duration ? solver : getDuration; +} + +// Elastic easing adapted from jQueryUI http://api.jqueryui.com/easings/ + +function elastic(amplitude, period) { + if (amplitude === void 0) amplitude = 1; + if (period === void 0) period = .5; + var a = minMax(amplitude, 1, 10); + var p = minMax(period, .1, 2); + return function (t) { + return t === 0 || t === 1 ? t : -a * Math.pow(2, 10 * (t - 1)) * Math.sin((t - 1 - p / (Math.PI * 2) * Math.asin(1 / a)) * (Math.PI * 2) / p); + }; +} + +// Basic steps easing implementation https://developer.mozilla.org/fr/docs/Web/CSS/transition-timing-function + +function steps(steps) { + if (steps === void 0) steps = 10; + return function (t) { + return Math.round(t * steps) * (1 / steps); + }; +} + +// BezierEasing https://github.com/gre/bezier-easing + +var bezier = function () { + var kSplineTableSize = 11; + var kSampleStepSize = 1.0 / (kSplineTableSize - 1.0); + function A(aA1, aA2) { + return 1.0 - 3.0 * aA2 + 3.0 * aA1; + } + function B(aA1, aA2) { + return 3.0 * aA2 - 6.0 * aA1; + } + function C(aA1) { + return 3.0 * aA1; + } + function calcBezier(aT, aA1, aA2) { + return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT; + } + function getSlope(aT, aA1, aA2) { + return 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1); + } + function binarySubdivide(aX, aA, aB, mX1, mX2) { + var currentX, + currentT, + i = 0; + do { + currentT = aA + (aB - aA) / 2.0; + currentX = calcBezier(currentT, mX1, mX2) - aX; + if (currentX > 0.0) { + aB = currentT; + } else { + aA = currentT; + } + } while (Math.abs(currentX) > 0.0000001 && ++i < 10); + return currentT; + } + function newtonRaphsonIterate(aX, aGuessT, mX1, mX2) { + for (var i = 0; i < 4; ++i) { + var currentSlope = getSlope(aGuessT, mX1, mX2); + if (currentSlope === 0.0) { + return aGuessT; + } + var currentX = calcBezier(aGuessT, mX1, mX2) - aX; + aGuessT -= currentX / currentSlope; + } + return aGuessT; + } + function bezier(mX1, mY1, mX2, mY2) { + if (!(0 <= mX1 && mX1 <= 1 && 0 <= mX2 && mX2 <= 1)) { + return; + } + var sampleValues = new Float32Array(kSplineTableSize); + if (mX1 !== mY1 || mX2 !== mY2) { + for (var i = 0; i < kSplineTableSize; ++i) { + sampleValues[i] = calcBezier(i * kSampleStepSize, mX1, mX2); + } + } + function getTForX(aX) { + var intervalStart = 0; + var currentSample = 1; + var lastSample = kSplineTableSize - 1; + for (; currentSample !== lastSample && sampleValues[currentSample] <= aX; ++currentSample) { + intervalStart += kSampleStepSize; + } + --currentSample; + var dist = (aX - sampleValues[currentSample]) / (sampleValues[currentSample + 1] - sampleValues[currentSample]); + var guessForT = intervalStart + dist * kSampleStepSize; + var initialSlope = getSlope(guessForT, mX1, mX2); + if (initialSlope >= 0.001) { + return newtonRaphsonIterate(aX, guessForT, mX1, mX2); + } else if (initialSlope === 0.0) { + return guessForT; + } else { + return binarySubdivide(aX, intervalStart, intervalStart + kSampleStepSize, mX1, mX2); + } + } + return function (x) { + if (mX1 === mY1 && mX2 === mY2) { + return x; + } + if (x === 0 || x === 1) { + return x; + } + return calcBezier(getTForX(x), mY1, mY2); + }; + } + return bezier; +}(); +var penner = function () { + var names = ['Quad', 'Cubic', 'Quart', 'Quint', 'Sine', 'Expo', 'Circ', 'Back', 'Elastic']; + + // Approximated Penner equations http://matthewlein.com/ceaser/ + + var curves = { + In: [[0.550, 0.085, 0.680, 0.530], /* inQuad */ + [0.550, 0.055, 0.675, 0.190], /* inCubic */ + [0.895, 0.030, 0.685, 0.220], /* inQuart */ + [0.755, 0.050, 0.855, 0.060], /* inQuint */ + [0.470, 0.000, 0.745, 0.715], /* inSine */ + [0.950, 0.050, 0.795, 0.035], /* inExpo */ + [0.600, 0.040, 0.980, 0.335], /* inCirc */ + [0.600, -0.280, 0.735, 0.045], /* inBack */ + elastic /* inElastic */], + + Out: [[0.250, 0.460, 0.450, 0.940], /* outQuad */ + [0.215, 0.610, 0.355, 1.000], /* outCubic */ + [0.165, 0.840, 0.440, 1.000], /* outQuart */ + [0.230, 1.000, 0.320, 1.000], /* outQuint */ + [0.390, 0.575, 0.565, 1.000], /* outSine */ + [0.190, 1.000, 0.220, 1.000], /* outExpo */ + [0.075, 0.820, 0.165, 1.000], /* outCirc */ + [0.175, 0.885, 0.320, 1.275], /* outBack */ + function (a, p) { + return function (t) { + return 1 - elastic(a, p)(1 - t); + }; + } /* outElastic */], + + InOut: [[0.455, 0.030, 0.515, 0.955], /* inOutQuad */ + [0.645, 0.045, 0.355, 1.000], /* inOutCubic */ + [0.770, 0.000, 0.175, 1.000], /* inOutQuart */ + [0.860, 0.000, 0.070, 1.000], /* inOutQuint */ + [0.445, 0.050, 0.550, 0.950], /* inOutSine */ + [1.000, 0.000, 0.000, 1.000], /* inOutExpo */ + [0.785, 0.135, 0.150, 0.860], /* inOutCirc */ + [0.680, -0.550, 0.265, 1.550], /* inOutBack */ + function (a, p) { + return function (t) { + return t < .5 ? elastic(a, p)(t * 2) / 2 : 1 - elastic(a, p)(t * -2 + 2) / 2; + }; + } /* inOutElastic */] + }; + + var eases = { + linear: [0.250, 0.250, 0.750, 0.750] + }; + for (var coords in curves) { + for (var i = 0, len = curves[coords].length; i < len; i++) { + eases['ease' + coords + names[i]] = curves[coords][i]; + } + } + return eases; +}(); +function parseEasings(easing, duration) { + if (is.fnc(easing)) { + return easing; + } + var name = easing.split('(')[0]; + var ease = penner[name]; + var args = parseEasingParameters(easing); + switch (name) { + case 'spring': + return spring(easing, duration); + case 'cubicBezier': + return applyArguments(bezier, args); + case 'steps': + return applyArguments(steps, args); + default: + return is.fnc(ease) ? applyArguments(ease, args) : applyArguments(bezier, ease); + } +} + +// Strings + +function selectString(str) { + try { + var nodes = document.querySelectorAll(str); + return nodes; + } catch (e) { + return; + } +} + +// Arrays + +var auxArrayFilter = []; +function filterArray(arr, callback) { + var result = auxArrayFilter; + var len = arr.length; + var thisArg = arguments.length >= 2 ? arguments[1] : void 0; + for (var i = 0; i < len; i++) { + if (i in arr) { + var val = arr[i]; + if (callback.call(thisArg, val, i, arr)) { + result.push(val); + } + } + } + + // arr turns into the auxArray and we return the previously aux array. + auxArrayFilter = arr; + auxArrayFilter.length = 0; + return result; +} +function flattenArray(arr, result) { + if (!result) { + result = []; + } + for (var i = 0, length = arr.length; i < length; i++) { + var value = arr[i]; + if (Array.isArray(value)) { + flattenArray(value, result); + } else { + result.push(value); + } + } + return result; +} +function toArray(o) { + if (is.arr(o)) { + return o; + } + if (is.str(o)) { + o = selectString(o) || o; + } + if (o instanceof NodeList || o instanceof HTMLCollection) { + return [].slice.call(o); + } + return [o]; +} +function arrayContains(arr, val) { + return arr.some(function (a) { + return a === val; + }); +} + +// Objects + +function cloneObject(o) { + var clone = {}; + for (var p in o) { + clone[p] = o[p]; + } + return clone; +} +function replaceObjectProps(o1, o2) { + var o = cloneObject(o1); + for (var p in o1) { + o[p] = o2.hasOwnProperty(p) ? o2[p] : o1[p]; + } + return o; +} +function mergeObjects(o1, o2) { + var o = cloneObject(o1); + for (var p in o2) { + o[p] = is.und(o1[p]) ? o2[p] : o1[p]; + } + return o; +} + +// Colors + +var rgbRegex = /rgb\((\d+,\s*[\d]+,\s*[\d]+)\)/g; +function rgbToRgba(rgbValue) { + var rgb = rgbRegex.exec(rgbValue); + return rgb ? "rgba(" + rgb[1] + ",1)" : rgbValue; +} +var hexToRgbaHexRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; +var hexToRgbaRgbRegex = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i; +function hexToRgba(hexValue) { + var hex = hexValue.replace(hexToRgbaHexRegex, function (m, r, g, b) { + return r + r + g + g + b + b; + }); + var rgb = hexToRgbaRgbRegex.exec(hex); + var r = parseInt(rgb[1], 16); + var g = parseInt(rgb[2], 16); + var b = parseInt(rgb[3], 16); + return "rgba(" + r + "," + g + "," + b + ",1)"; +} +var hslToRgbaHsl1Regex = /hsl\((\d+),\s*([\d.]+)%,\s*([\d.]+)%\)/g; +var hslToRgbaHsl2Regex = /hsla\((\d+),\s*([\d.]+)%,\s*([\d.]+)%,\s*([\d.]+)\)/g; +function hslToRgba(hslValue) { + var hsl = hslToRgbaHsl1Regex.exec(hslValue) || hslToRgbaHsl2Regex.exec(hslValue); + var h = parseInt(hsl[1], 10) / 360; + var s = parseInt(hsl[2], 10) / 100; + var l = parseInt(hsl[3], 10) / 100; + var a = hsl[4] || 1; + function hue2rgb(p, q, t) { + if (t < 0) { + t += 1; + } + if (t > 1) { + t -= 1; + } + if (t < 1 / 6) { + return p + (q - p) * 6 * t; + } + if (t < 1 / 2) { + return q; + } + if (t < 2 / 3) { + return p + (q - p) * (2 / 3 - t) * 6; + } + return p; + } + var r, g, b; + if (s == 0) { + r = g = b = l; + } else { + var q = l < 0.5 ? l * (1 + s) : l + s - l * s; + var p = 2 * l - q; + r = hue2rgb(p, q, h + 1 / 3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1 / 3); + } + return "rgba(" + r * 255 + "," + g * 255 + "," + b * 255 + "," + a + ")"; +} +function colorToRgb(val) { + if (is.rgb(val)) { + return rgbToRgba(val); + } + if (is.hex(val)) { + return hexToRgba(val); + } + if (is.hsl(val)) { + return hslToRgba(val); + } +} + +// Units + +var unitRegex = /([\+\-]?[0-9#\.]+)(%|px|pt|em|rem|in|cm|mm|ex|ch|pc|vw|vh|vmin|vmax|deg|rad|turn)?$/; +function getUnit(val) { + var split = unitRegex.exec(val); + if (split) { + return split[2]; + } +} +function getTransformUnit(propName) { + if (stringContains(propName, 'translate') || propName === 'perspective') { + return 'px'; + } + if (stringContains(propName, 'rotate') || stringContains(propName, 'skew')) { + return 'deg'; + } +} + +// Values + +function getFunctionValue(val, animatable) { + if (!is.fnc(val)) { + return val; + } + return val(animatable.target, animatable.id, animatable.total); +} +function getAttribute(el, prop) { + return el.getAttribute(prop); +} +function convertPxToUnit(el, value, unit) { + var valueUnit = getUnit(value); + if (arrayContains([unit, 'deg', 'rad', 'turn'], valueUnit)) { + return value; + } + var cached = cache.CSS[value + unit]; + if (!is.und(cached)) { + return cached; + } + var baseline = 100; + var tempEl = document.createElement(el.tagName); + var parentEl = el.parentNode && el.parentNode !== document ? el.parentNode : document.body; + parentEl.appendChild(tempEl); + tempEl.style.position = 'absolute'; + tempEl.style.width = baseline + unit; + var factor = baseline / tempEl.offsetWidth; + parentEl.removeChild(tempEl); + var convertedUnit = factor * parseFloat(value); + cache.CSS[value + unit] = convertedUnit; + return convertedUnit; +} +function getCSSValue(el, prop, unit) { + if (prop in el.style) { + var uppercasePropName = prop.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); + var value = el.style[prop] || getComputedStyle(el).getPropertyValue(uppercasePropName) || '0'; + return unit ? convertPxToUnit(el, value, unit) : value; + } +} +function getAnimationType(el, prop) { + if (is.dom(el) && !is.inp(el) && (getAttribute(el, prop) || is.svg(el) && el[prop])) { + return 'attribute'; + } + if (is.dom(el) && arrayContains(validTransforms, prop)) { + return 'transform'; + } + if (is.dom(el) && prop !== 'transform' && getCSSValue(el, prop)) { + return 'css'; + } + if (el[prop] != null) { + return 'object'; + } +} +var transformRegex = /(\w+)\(([^)]*)\)/g; +function getElementTransforms(el) { + if (!is.dom(el)) { + return; + } + var str = el.style.transform || ''; + var transforms = new Map(); + var m; + while (m = transformRegex.exec(str)) { + transforms.set(m[1], m[2]); + } + return transforms; +} +function getTransformValue(el, propName, animatable, unit) { + var defaultVal = stringContains(propName, 'scale') ? 1 : 0 + getTransformUnit(propName); + var value = getElementTransforms(el).get(propName) || defaultVal; + if (animatable) { + animatable.transforms.list.set(propName, value); + animatable.transforms['last'] = propName; + } + return unit ? convertPxToUnit(el, value, unit) : value; +} +function getOriginalTargetValue(target, propName, unit, animatable) { + switch (getAnimationType(target, propName)) { + case 'transform': + return getTransformValue(target, propName, animatable, unit); + case 'css': + return getCSSValue(target, propName, unit); + case 'attribute': + return getAttribute(target, propName); + default: + return target[propName] || 0; + } +} +var operatorRegex = /^(\*=|\+=|-=)/; +function getRelativeValue(to, from) { + var operator = operatorRegex.exec(to); + if (!operator) { + return to; + } + var u = getUnit(to) || 0; + var x = parseFloat(from); + var y = parseFloat(to.replace(operator[0], '')); + switch (operator[0][0]) { + case '+': + return x + y + u; + case '-': + return x - y + u; + case '*': + return x * y + u; + } +} +var whitespaceRegex = /\s/g; +function validateValue(val, unit) { + if (is.col(val)) { + return colorToRgb(val); + } + var originalUnit = getUnit(val); + var unitLess = originalUnit ? val.substr(0, val.length - originalUnit.length) : val; + return unit && !whitespaceRegex.test(val) ? unitLess + unit : unitLess; +} + +// getTotalLength() equivalent for circle, rect, polyline, polygon and line shapes +// adapted from https://gist.github.com/SebLambla/3e0550c496c236709744 + +function getDistance(p1, p2) { + return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2)); +} +function getCircleLength(el) { + return Math.PI * 2 * getAttribute(el, 'r'); +} +function getRectLength(el) { + return getAttribute(el, 'width') * 2 + getAttribute(el, 'height') * 2; +} +function getLineLength(el) { + return getDistance({ + x: getAttribute(el, 'x1'), + y: getAttribute(el, 'y1') + }, { + x: getAttribute(el, 'x2'), + y: getAttribute(el, 'y2') + }); +} +function getPolylineLength(el) { + var points = el.points; + var totalLength = 0; + var previousPos; + for (var i = 0; i < points.numberOfItems; i++) { + var currentPos = points.getItem(i); + if (i > 0) { + totalLength += getDistance(previousPos, currentPos); + } + previousPos = currentPos; + } + return totalLength; +} +function getPolygonLength(el) { + var points = el.points; + return getPolylineLength(el) + getDistance(points.getItem(points.numberOfItems - 1), points.getItem(0)); +} + +// Path animation + +function getTotalLength(el) { + if (el.getTotalLength) { + return el.getTotalLength(); + } + switch (el.tagName.toLowerCase()) { + case 'circle': + return getCircleLength(el); + case 'rect': + return getRectLength(el); + case 'line': + return getLineLength(el); + case 'polyline': + return getPolylineLength(el); + case 'polygon': + return getPolygonLength(el); + } +} +function setDashoffset(el) { + var pathLength = getTotalLength(el); + el.setAttribute('stroke-dasharray', pathLength); + return pathLength; +} + +// Motion path + +function getParentSvgEl(el) { + var parentEl = el.parentNode; + while (is.svg(parentEl)) { + parentEl = parentEl.parentNode; + if (!is.svg(parentEl.parentNode)) { + break; + } + } + return parentEl; +} +function getParentSvg(pathEl, svgData) { + var svg = svgData || {}; + var parentSvgEl = svg.el || getParentSvgEl(pathEl); + var rect = parentSvgEl.getBoundingClientRect(); + var viewBoxAttr = getAttribute(parentSvgEl, 'viewBox'); + var width = rect.width; + var height = rect.height; + var viewBox = svg.viewBox || (viewBoxAttr ? viewBoxAttr.split(' ') : [0, 0, width, height]); + return { + el: parentSvgEl, + viewBox: viewBox, + x: viewBox[0] / 1, + y: viewBox[1] / 1, + w: width / viewBox[2], + h: height / viewBox[3] + }; +} +function getPath(path, percent) { + var pathEl = is.str(path) ? selectString(path)[0] : path; + var p = percent || 100; + return function (property) { + return { + property: property, + el: pathEl, + svg: getParentSvg(pathEl), + totalLength: getTotalLength(pathEl) * (p / 100) + }; + }; +} +function getPathProgress(path, progress) { + function point(offset) { + if (offset === void 0) offset = 0; + var l = progress + offset >= 1 ? progress + offset : 0; + return path.el.getPointAtLength(l); + } + var svg = getParentSvg(path.el, path.svg); + var p = point(); + var p0 = point(-1); + var p1 = point(+1); + switch (path.property) { + case 'x': + return (p.x - svg.x) * svg.w; + case 'y': + return (p.y - svg.y) * svg.h; + case 'angle': + return Math.atan2(p1.y - p0.y, p1.x - p0.x) * 180 / Math.PI; + } +} + +// Decompose value + +var valueRegex = /-?\d*\.?\d+/g; +function decomposeValue(val, unit) { + var value = validateValue(is.pth(val) ? val.totalLength : val, unit) + ''; + return { + original: value, + numbers: value.match(valueRegex) ? value.match(valueRegex).map(Number) : [0], + strings: is.str(val) || unit ? value.split(valueRegex) : [] + }; +} + +// Animatables + +function parseTargets(targets) { + var targetsArray = targets ? flattenArray(is.arr(targets) ? targets.map(toArray) : toArray(targets)) : []; + return filterArray(targetsArray, function (item, pos, self) { + return self.indexOf(item) === pos; + }); +} +function getAnimatables(targets) { + var parsed = parseTargets(targets); + return parsed.map(function (t, i) { + return { + target: t, + id: i, + total: parsed.length, + transforms: { + list: getElementTransforms(t) + } + }; + }); +} + +// Properties + +var springRegex = /^spring/; +function normalizePropertyTweens(prop, tweenSettings) { + var settings = cloneObject(tweenSettings); + // Override duration if easing is a spring + if (springRegex.test(settings.easing)) { + settings.duration = spring(settings.easing); + } + if (is.arr(prop)) { + var l = prop.length; + var isFromTo = l === 2 && !is.obj(prop[0]); + if (!isFromTo) { + // Duration divided by the number of tweens + if (!is.fnc(tweenSettings.duration)) { + settings.duration = tweenSettings.duration / l; + } + } else { + // Transform [from, to] values shorthand to a valid tween value + prop = { + value: prop + }; + } + } + var propArray = is.arr(prop) ? prop : [prop]; + return propArray.map(function (v, i) { + var obj = is.obj(v) && !is.pth(v) ? v : { + value: v + }; + // Default delay value should only be applied to the first tween + if (is.und(obj.delay)) { + obj.delay = !i ? tweenSettings.delay : 0; + } + // Default endDelay value should only be applied to the last tween + if (is.und(obj.endDelay)) { + obj.endDelay = i === propArray.length - 1 ? tweenSettings.endDelay : 0; + } + return obj; + }).map(function (k) { + return mergeObjects(k, settings); + }); +} +function flattenKeyframes(keyframes) { + var propertyNames = filterArray(flattenArray(keyframes.map(function (key) { + return Object.keys(key); + })), function (p) { + return is.key(p); + }).reduce(function (a, b) { + if (a.indexOf(b) < 0) { + a.push(b); + } + return a; + }, []); + var properties = {}; + var loop = function (i) { + var propName = propertyNames[i]; + properties[propName] = keyframes.map(function (key) { + var newKey = {}; + for (var p in key) { + if (is.key(p)) { + if (p == propName) { + newKey.value = key[p]; + } + } else { + newKey[p] = key[p]; + } + } + return newKey; + }); + }; + for (var i = 0; i < propertyNames.length; i++) loop(i); + return properties; +} +function getProperties(tweenSettings, params) { + var properties = []; + var keyframes = params.keyframes; + if (keyframes) { + params = mergeObjects(flattenKeyframes(keyframes), params); + } + for (var p in params) { + if (is.key(p)) { + properties.push({ + name: p, + tweens: normalizePropertyTweens(params[p], tweenSettings) + }); + } + } + return properties; +} + +// Tweens + +function normalizeTweenValues(tween, animatable) { + var t = {}; + for (var p in tween) { + var value = getFunctionValue(tween[p], animatable); + if (is.arr(value)) { + value = value.map(function (v) { + return getFunctionValue(v, animatable); + }); + if (value.length === 1) { + value = value[0]; + } + } + t[p] = value; + } + t.duration = parseFloat(t.duration); + t.delay = parseFloat(t.delay); + return t; +} +function normalizeTweens(prop, animatable) { + var previousTween; + return prop.tweens.map(function (t) { + var tween = normalizeTweenValues(t, animatable); + var tweenValue = tween.value; + var to = is.arr(tweenValue) ? tweenValue[1] : tweenValue; + var toUnit = getUnit(to); + var originalValue = getOriginalTargetValue(animatable.target, prop.name, toUnit, animatable); + var previousValue = previousTween ? previousTween.to.original : originalValue; + var from = is.arr(tweenValue) ? tweenValue[0] : previousValue; + var fromUnit = getUnit(from) || getUnit(originalValue); + var unit = toUnit || fromUnit; + if (is.und(to)) { + to = previousValue; + } + tween.from = decomposeValue(from, unit); + tween.to = decomposeValue(getRelativeValue(to, from), unit); + tween.start = previousTween ? previousTween.end : 0; + tween.end = tween.start + tween.delay + tween.duration + tween.endDelay; + tween.easing = parseEasings(tween.easing, tween.duration); + tween.isPath = is.pth(tweenValue); + tween.isColor = is.col(tween.from.original); + if (tween.isColor) { + tween.round = 1; + } + previousTween = tween; + return tween; + }); +} + +// Tween progress + +var setProgressValue = { + css: function (t, p, v) { + return t.style[p] = v; + }, + attribute: function (t, p, v) { + return t.setAttribute(p, v); + }, + object: function (t, p, v) { + return t[p] = v; + }, + transform: function (t, p, v, transforms, manual) { + transforms.list.set(p, v); + if (p === transforms.last || manual) { + var str = ''; + transforms.list.forEach(function (value, prop) { + str += prop + "(" + value + ") "; + }); + t.style.transform = str; + } + } +}; + +// Set Value helper + +function setTargetsValue(targets, properties) { + var animatables = getAnimatables(targets); + for (var i = 0, len = animatables.length; i < len; i++) { + var animatable = animatables[i]; + for (var property in properties) { + var value = getFunctionValue(properties[property], animatable); + var target = animatable.target; + var valueUnit = getUnit(value); + var originalValue = getOriginalTargetValue(target, property, valueUnit, animatable); + var unit = valueUnit || getUnit(originalValue); + var to = getRelativeValue(validateValue(value, unit), originalValue); + var animType = getAnimationType(target, property); + setProgressValue[animType](target, property, to, animatable.transforms, true); + } + } +} + +// Animations + +function createAnimation(animatable, prop) { + var animType = getAnimationType(animatable.target, prop.name); + if (animType) { + var tweens = normalizeTweens(prop, animatable); + var lastTween = tweens[tweens.length - 1]; + return { + type: animType, + property: prop.name, + animatable: animatable, + tweens: tweens, + duration: lastTween.end, + delay: tweens[0].delay, + endDelay: lastTween.endDelay + }; + } +} +function getAnimations(animatables, properties) { + return filterArray(flattenArray(animatables.map(function (animatable) { + return properties.map(function (prop) { + return createAnimation(animatable, prop); + }); + })), function (a) { + return !is.und(a); + }); +} + +// Create Instance + +function getInstanceTimings(animations, tweenSettings) { + var animLength = animations.length; + var getTlOffset = function (anim) { + return anim.timelineOffset ? anim.timelineOffset : 0; + }; + var timings = {}; + timings.duration = animLength ? Math.max.apply(Math, animations.map(function (anim) { + return getTlOffset(anim) + anim.duration; + })) : tweenSettings.duration; + timings.delay = animLength ? Math.min.apply(Math, animations.map(function (anim) { + return getTlOffset(anim) + anim.delay; + })) : tweenSettings.delay; + timings.endDelay = animLength ? timings.duration - Math.max.apply(Math, animations.map(function (anim) { + return getTlOffset(anim) + anim.duration - anim.endDelay; + })) : tweenSettings.endDelay; + return timings; +} +var instanceID = 0; +function createNewInstance(params) { + var instanceSettings = replaceObjectProps(defaultInstanceSettings, params); + var tweenSettings = replaceObjectProps(defaultTweenSettings, params); + var properties = getProperties(tweenSettings, params); + var animatables = getAnimatables(params.targets); + var animations = getAnimations(animatables, properties); + var timings = getInstanceTimings(animations, tweenSettings); + var id = instanceID; + instanceID++; + return mergeObjects(instanceSettings, { + id: id, + children: [], + animatables: animatables, + animations: animations, + duration: timings.duration, + delay: timings.delay, + endDelay: timings.endDelay + }); +} + +// Core + +var activeInstances = []; +var pausedInstances = []; +var raf; +var engine = function () { + function play() { + raf = requestAnimationFrame(step); + } + function step(t) { + var activeInstancesLength = activeInstances.length; + if (activeInstancesLength) { + var i = 0; + while (i < activeInstancesLength) { + var activeInstance = activeInstances[i]; + if (!activeInstance.paused) { + activeInstance.tick(t); + } else { + var instanceIndex = activeInstances.indexOf(activeInstance); + if (instanceIndex > -1) { + activeInstances.splice(instanceIndex, 1); + activeInstancesLength = activeInstances.length; + } + } + i++; + } + play(); + } else { + raf = cancelAnimationFrame(raf); + } + } + return play; +}(); +function handleVisibilityChange() { + if (document.hidden) { + for (var i = 0, len = activeInstances.length; i < len; i++) { + activeInstance[i].pause(); + } + pausedInstances = activeInstances.slice(0); + activeInstances = []; + } else { + for (var i$1 = 0, len$1 = pausedInstances.length; i$1 < len$1; i$1++) { + pausedInstances[i$1].play(); + } + } +} +document.addEventListener('visibilitychange', handleVisibilityChange); + +// Public Instance + +function anime(params) { + if (params === void 0) params = {}; + var startTime = 0, + lastTime = 0, + now = 0; + var children, + childrenLength = 0; + var resolve = null; + function makePromise() { + return window.Promise && new Promise(function (_resolve) { + return resolve = _resolve; + }); + } + var promise = makePromise(); + var instance = createNewInstance(params); + function toggleInstanceDirection() { + instance.reversed = !instance.reversed; + for (var i = 0, len = children.length; i < len; i++) { + children[i].reversed = instance.reversed; + } + } + function adjustTime(time) { + return instance.reversed ? instance.duration - time : time; + } + function resetTime() { + startTime = 0; + lastTime = adjustTime(instance.currentTime) * (1 / anime.speed); + } + function seekCild(time, child) { + if (child) { + child.seek(time - child.timelineOffset); + } + } + function syncInstanceChildren(time) { + if (!instance.reversePlayback) { + for (var i = 0; i < childrenLength; i++) { + seekCild(time, children[i]); + } + } else { + for (var i$1 = childrenLength; i$1--;) { + seekCild(time, children[i$1]); + } + } + } + function setAnimationsProgress(insTime) { + var i = 0; + var animations = instance.animations; + var animationsLength = animations.length; + while (i < animationsLength) { + var anim = animations[i]; + var animatable = anim.animatable; + var tweens = anim.tweens; + var tweenLength = tweens.length - 1; + var tween = tweens[tweenLength]; + // Only check for keyframes if there is more than one tween + if (tweenLength) { + tween = filterArray(tweens, function (t) { + return insTime < t.end; + })[0] || tween; + } + var elapsed = minMax(insTime - tween.start - tween.delay, 0, tween.duration) / tween.duration; + var eased = isNaN(elapsed) ? 1 : tween.easing(elapsed); + var strings = tween.to.strings; + var round = tween.round; + var numbers = []; + var toNumbersLength = tween.to.numbers.length; + var progress = void 0; + for (var n = 0; n < toNumbersLength; n++) { + var value = void 0; + var toNumber = tween.to.numbers[n]; + var fromNumber = tween.from.numbers[n] || 0; + if (!tween.isPath) { + value = fromNumber + eased * (toNumber - fromNumber); + } else { + value = getPathProgress(tween.value, eased * toNumber); + } + if (round) { + if (!(tween.isColor && n > 2)) { + value = Math.round(value * round) / round; + } + } + numbers.push(value); + } + // Manual Array.reduce for better performances + var stringsLength = strings.length; + if (!stringsLength) { + progress = numbers[0]; + } else { + progress = strings[0]; + for (var s = 0; s < stringsLength; s++) { + var a = strings[s]; + var b = strings[s + 1]; + var n$1 = numbers[s]; + if (!isNaN(n$1)) { + if (!b) { + progress += n$1 + ' '; + } else { + progress += n$1 + b; + } + } + } + } + setProgressValue[anim.type](animatable.target, anim.property, progress, animatable.transforms); + anim.currentValue = progress; + i++; + } + } + function setCallback(cb) { + if (instance[cb] && !instance.passThrough) { + instance[cb](instance); + } + } + function countIteration() { + if (instance.remaining && instance.remaining !== true) { + instance.remaining--; + } + } + function setInstanceProgress(engineTime) { + var insDuration = instance.duration; + var insDelay = instance.delay; + var insEndDelay = insDuration - instance.endDelay; + var insTime = adjustTime(engineTime); + instance.progress = minMax(insTime / insDuration * 100, 0, 100); + instance.reversePlayback = insTime < instance.currentTime; + if (children) { + syncInstanceChildren(insTime); + } + if (!instance.began && instance.currentTime > 0) { + instance.began = true; + setCallback('begin'); + setCallback('loopBegin'); + } + if (insTime <= insDelay && instance.currentTime !== 0) { + setAnimationsProgress(0); + } + if (insTime >= insEndDelay && instance.currentTime !== insDuration || !insDuration) { + setAnimationsProgress(insDuration); + } + if (insTime > insDelay && insTime < insEndDelay) { + if (!instance.changeBegan) { + instance.changeBegan = true; + instance.changeCompleted = false; + setCallback('changeBegin'); + } + setCallback('change'); + setAnimationsProgress(insTime); + } else { + if (instance.changeBegan) { + instance.changeCompleted = true; + instance.changeBegan = false; + setCallback('changeComplete'); + } + } + instance.currentTime = minMax(insTime, 0, insDuration); + if (instance.began) { + setCallback('update'); + } + if (engineTime >= insDuration) { + lastTime = 0; + countIteration(); + if (instance.remaining) { + startTime = now; + setCallback('loopComplete'); + setCallback('loopBegin'); + if (instance.direction === 'alternate') { + toggleInstanceDirection(); + } + } else { + instance.paused = true; + if (!instance.completed) { + instance.completed = true; + setCallback('loopComplete'); + setCallback('complete'); + if ('Promise' in window) { + resolve(); + promise = makePromise(); + } + } + } + } + } + instance.reset = function () { + var direction = instance.direction; + instance.passThrough = false; + instance.currentTime = 0; + instance.progress = 0; + instance.paused = true; + instance.began = false; + instance.changeBegan = false; + instance.completed = false; + instance.changeCompleted = false; + instance.reversePlayback = false; + instance.reversed = direction === 'reverse'; + instance.remaining = instance.loop; + children = instance.children; + childrenLength = children.length; + for (var i = childrenLength; i--;) { + instance.children[i].reset(); + } + if (instance.reversed && instance.loop !== true || direction === 'alternate' && instance.loop === 1) { + instance.remaining++; + } + setAnimationsProgress(0); + }; + + // Set Value helper + + instance.set = function (targets, properties) { + setTargetsValue(targets, properties); + return instance; + }; + instance.tick = function (t) { + now = t; + if (!startTime) { + startTime = now; + } + setInstanceProgress((now + (lastTime - startTime)) * anime.speed); + }; + instance.seek = function (time) { + setInstanceProgress(adjustTime(time)); + }; + instance.pause = function () { + instance.paused = true; + resetTime(); + }; + instance.play = function () { + if (!instance.paused) { + return; + } + instance.paused = false; + activeInstances.push(instance); + resetTime(); + if (!raf) { + engine(); + } + }; + instance.reverse = function () { + toggleInstanceDirection(); + resetTime(); + }; + instance.restart = function () { + instance.reset(); + instance.play(); + }; + instance.finished = promise; + instance.reset(); + if (instance.autoplay) { + instance.play(); + } + return instance; +} + +// Remove targets from animation + +function removeTargetsFromAnimations(targetsArray, animations) { + for (var a = animations.length; a--;) { + if (arrayContains(targetsArray, animations[a].animatable.target)) { + animations.splice(a, 1); + } + } +} +function removeTargets(targets) { + var targetsArray = parseTargets(targets); + for (var i = activeInstances.length; i--;) { + var instance = activeInstances[i]; + var animations = instance.animations; + var children = instance.children; + removeTargetsFromAnimations(targetsArray, animations); + for (var c = children.length; c--;) { + var child = children[c]; + var childAnimations = child.animations; + removeTargetsFromAnimations(targetsArray, childAnimations); + if (!childAnimations.length && !child.children.length) { + children.splice(c, 1); + } + } + if (!animations.length && !children.length) { + instance.pause(); + } + } +} + +// Stagger helpers + +function stagger(val, params) { + if (params === void 0) params = {}; + var direction = params.direction || 'normal'; + var easing = params.easing ? parseEasings(params.easing) : null; + var grid = params.grid; + var axis = params.axis; + var fromIndex = params.from || 0; + var fromFirst = fromIndex === 'first'; + var fromCenter = fromIndex === 'center'; + var fromLast = fromIndex === 'last'; + var isRange = is.arr(val); + var val1 = isRange ? parseFloat(val[0]) : parseFloat(val); + var val2 = isRange ? parseFloat(val[1]) : 0; + var unit = getUnit(isRange ? val[1] : val) || 0; + var start = params.start || 0 + (isRange ? val1 : 0); + var values = []; + var maxValue = 0; + return function (el, i, t) { + if (fromFirst) { + fromIndex = 0; + } + if (fromCenter) { + fromIndex = (t - 1) / 2; + } + if (fromLast) { + fromIndex = t - 1; + } + if (!values.length) { + for (var index = 0; index < t; index++) { + if (!grid) { + values.push(Math.abs(fromIndex - index)); + } else { + var fromX = !fromCenter ? fromIndex % grid[0] : (grid[0] - 1) / 2; + var fromY = !fromCenter ? Math.floor(fromIndex / grid[0]) : (grid[1] - 1) / 2; + var toX = index % grid[0]; + var toY = Math.floor(index / grid[0]); + var distanceX = fromX - toX; + var distanceY = fromY - toY; + var value = Math.sqrt(distanceX * distanceX + distanceY * distanceY); + if (axis === 'x') { + value = -distanceX; + } + if (axis === 'y') { + value = -distanceY; + } + values.push(value); + } + maxValue = Math.max.apply(Math, values); + } + if (easing) { + values = values.map(function (val) { + return easing(val / maxValue) * maxValue; + }); + } + if (direction === 'reverse') { + values = values.map(function (val) { + return axis ? val < 0 ? val * -1 : -val : Math.abs(maxValue - val); + }); + } + } + var spacing = isRange ? (val2 - val1) / maxValue : val1; + return start + spacing * (Math.round(values[i] * 100) / 100) + unit; + }; +} + +// Timeline + +function timeline(params) { + if (params === void 0) params = {}; + var tl = anime(params); + tl.duration = 0; + tl.add = function (instanceParams, timelineOffset) { + var tlIndex = activeInstances.indexOf(tl); + var children = tl.children; + if (tlIndex > -1) { + activeInstances.splice(tlIndex, 1); + } + function passThrough(ins) { + ins.passThrough = true; + } + for (var i = 0; i < children.length; i++) { + passThrough(children[i]); + } + var insParams = mergeObjects(instanceParams, replaceObjectProps(defaultTweenSettings, params)); + insParams.targets = insParams.targets || params.targets; + var tlDuration = tl.duration; + insParams.autoplay = false; + insParams.direction = tl.direction; + insParams.timelineOffset = is.und(timelineOffset) ? tlDuration : getRelativeValue(timelineOffset, tlDuration); + passThrough(tl); + tl.seek(insParams.timelineOffset); + var ins = anime(insParams); + passThrough(ins); + children.push(ins); + var timings = getInstanceTimings(children, params); + tl.delay = timings.delay; + tl.endDelay = timings.endDelay; + tl.duration = timings.duration; + tl.seek(0); + tl.reset(); + if (tl.autoplay) { + tl.play(); + } + return tl; + }; + return tl; +} +anime.version = '3.0.0'; +anime.speed = 1; +anime.running = activeInstances; +anime.remove = removeTargets; +anime.get = getOriginalTargetValue; +anime.set = setTargetsValue; +anime.convertPx = convertPxToUnit; +anime.path = getPath; +anime.setDashoffset = setDashoffset; +anime.stagger = stagger; +anime.timeline = timeline; +anime.easing = parseEasings; +anime.penner = penner; +anime.random = function (min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; +}; +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (anime); + +/***/ }), + +/***/ "./node_modules/three-bmfont-text/index.js": +/*!*************************************************!*\ + !*** ./node_modules/three-bmfont-text/index.js ***! + \*************************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var createLayout = __webpack_require__(/*! layout-bmfont-text */ "./node_modules/layout-bmfont-text/index.js"); +var createIndices = __webpack_require__(/*! quad-indices */ "./node_modules/quad-indices/index.js"); +var vertices = __webpack_require__(/*! ./lib/vertices */ "./node_modules/three-bmfont-text/lib/vertices.js"); +var utils = __webpack_require__(/*! ./lib/utils */ "./node_modules/three-bmfont-text/lib/utils.js"); +module.exports = function createTextGeometry(opt) { + return new TextGeometry(opt); +}; +class TextGeometry extends THREE.BufferGeometry { + constructor(opt) { + super(); + if (typeof opt === 'string') { + opt = { + text: opt + }; + } + + // use these as default values for any subsequent + // calls to update() + this._opt = Object.assign({}, opt); + + // also do an initial setup... + if (opt) this.update(opt); + } + update(opt) { + if (typeof opt === 'string') { + opt = { + text: opt + }; + } + + // use constructor defaults + opt = Object.assign({}, this._opt, opt); + if (!opt.font) { + throw new TypeError('must specify a { font } in options'); + } + this.layout = createLayout(opt); + + // get vec2 texcoords + var flipY = opt.flipY !== false; + + // the desired BMFont data + var font = opt.font; + + // determine texture size from font file + var texWidth = font.common.scaleW; + var texHeight = font.common.scaleH; + + // get visible glyphs + var glyphs = this.layout.glyphs.filter(function (glyph) { + var bitmap = glyph.data; + return bitmap.width * bitmap.height > 0; + }); + + // provide visible glyphs for convenience + this.visibleGlyphs = glyphs; + + // get common vertex data + var positions = vertices.positions(glyphs); + var uvs = vertices.uvs(glyphs, texWidth, texHeight, flipY); + var indices = createIndices([], { + clockwise: true, + type: 'uint16', + count: glyphs.length + }); + + // update vertex data + this.setIndex(indices); + this.setAttribute('position', new THREE.BufferAttribute(positions, 2)); + this.setAttribute('uv', new THREE.BufferAttribute(uvs, 2)); + + // update multipage data + if (!opt.multipage && 'page' in this.attributes) { + // disable multipage rendering + this.removeAttribute('page'); + } else if (opt.multipage) { + // enable multipage rendering + var pages = vertices.pages(glyphs); + this.setAttribute('page', new THREE.BufferAttribute(pages, 1)); + } + + // recompute bounding box and sphere, if present + if (this.boundingBox !== null) { + this.computeBoundingBox(); + } + if (this.boundingSphere !== null) { + this.computeBoundingSphere(); + } + } + computeBoundingSphere() { + if (this.boundingSphere === null) { + this.boundingSphere = new THREE.Sphere(); + } + var positions = this.attributes.position.array; + var itemSize = this.attributes.position.itemSize; + if (!positions || !itemSize || positions.length < 2) { + this.boundingSphere.radius = 0; + this.boundingSphere.center.set(0, 0, 0); + return; + } + utils.computeSphere(positions, this.boundingSphere); + if (isNaN(this.boundingSphere.radius)) { + console.error('THREE.BufferGeometry.computeBoundingSphere(): ' + 'Computed radius is NaN. The ' + '"position" attribute is likely to have NaN values.'); + } + } + computeBoundingBox() { + if (this.boundingBox === null) { + this.boundingBox = new THREE.Box3(); + } + var bbox = this.boundingBox; + var positions = this.attributes.position.array; + var itemSize = this.attributes.position.itemSize; + if (!positions || !itemSize || positions.length < 2) { + bbox.makeEmpty(); + return; + } + utils.computeBox(positions, bbox); + } +} + +/***/ }), + +/***/ "./node_modules/three-bmfont-text/lib/utils.js": +/*!*****************************************************!*\ + !*** ./node_modules/three-bmfont-text/lib/utils.js ***! + \*****************************************************/ +/***/ ((module) => { + +var itemSize = 2; +var box = { + min: [0, 0], + max: [0, 0] +}; +function bounds(positions) { + var count = positions.length / itemSize; + box.min[0] = positions[0]; + box.min[1] = positions[1]; + box.max[0] = positions[0]; + box.max[1] = positions[1]; + for (var i = 0; i < count; i++) { + var x = positions[i * itemSize + 0]; + var y = positions[i * itemSize + 1]; + box.min[0] = Math.min(x, box.min[0]); + box.min[1] = Math.min(y, box.min[1]); + box.max[0] = Math.max(x, box.max[0]); + box.max[1] = Math.max(y, box.max[1]); + } +} +module.exports.computeBox = function (positions, output) { + bounds(positions); + output.min.set(box.min[0], box.min[1], 0); + output.max.set(box.max[0], box.max[1], 0); +}; +module.exports.computeSphere = function (positions, output) { + bounds(positions); + var minX = box.min[0]; + var minY = box.min[1]; + var maxX = box.max[0]; + var maxY = box.max[1]; + var width = maxX - minX; + var height = maxY - minY; + var length = Math.sqrt(width * width + height * height); + output.center.set(minX + width / 2, minY + height / 2, 0); + output.radius = length / 2; +}; + +/***/ }), + +/***/ "./node_modules/three-bmfont-text/lib/vertices.js": +/*!********************************************************!*\ + !*** ./node_modules/three-bmfont-text/lib/vertices.js ***! + \********************************************************/ +/***/ ((module) => { + +module.exports.pages = function pages(glyphs) { + var pages = new Float32Array(glyphs.length * 4 * 1); + var i = 0; + glyphs.forEach(function (glyph) { + var id = glyph.data.page || 0; + pages[i++] = id; + pages[i++] = id; + pages[i++] = id; + pages[i++] = id; + }); + return pages; +}; +module.exports.uvs = function uvs(glyphs, texWidth, texHeight, flipY) { + var uvs = new Float32Array(glyphs.length * 4 * 2); + var i = 0; + glyphs.forEach(function (glyph) { + var bitmap = glyph.data; + var bw = bitmap.x + bitmap.width; + var bh = bitmap.y + bitmap.height; + + // top left position + var u0 = bitmap.x / texWidth; + var v1 = bitmap.y / texHeight; + var u1 = bw / texWidth; + var v0 = bh / texHeight; + if (flipY) { + v1 = (texHeight - bitmap.y) / texHeight; + v0 = (texHeight - bh) / texHeight; + } + + // BL + uvs[i++] = u0; + uvs[i++] = v1; + // TL + uvs[i++] = u0; + uvs[i++] = v0; + // TR + uvs[i++] = u1; + uvs[i++] = v0; + // BR + uvs[i++] = u1; + uvs[i++] = v1; + }); + return uvs; +}; +module.exports.positions = function positions(glyphs) { + var positions = new Float32Array(glyphs.length * 4 * 2); + var i = 0; + glyphs.forEach(function (glyph) { + var bitmap = glyph.data; + + // bottom left position + var x = glyph.position[0] + bitmap.xoffset; + var y = glyph.position[1] + bitmap.yoffset; + + // quad size + var w = bitmap.width; + var h = bitmap.height; + + // BL + positions[i++] = x; + positions[i++] = y; + // TL + positions[i++] = x; + positions[i++] = y + h; + // TR + positions[i++] = x + w; + positions[i++] = y + h; + // BR + positions[i++] = x + w; + positions[i++] = y; + }); + return positions; +}; + +/***/ }), + +/***/ "./node_modules/webvr-polyfill/build/webvr-polyfill.js": +/*!*************************************************************!*\ + !*** ./node_modules/webvr-polyfill/build/webvr-polyfill.js ***! + \*************************************************************/ +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +/** + * @license + * webvr-polyfill + * Copyright (c) 2015-2017 Google + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @license + * cardboard-vr-display + * Copyright (c) 2015-2017 Google + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @license + * webvr-polyfill-dpdb + * Copyright (c) 2017 Google + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @license + * wglu-preserve-state + * Copyright (c) 2016, Brandon Jones. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * @license + * nosleep.js + * Copyright (c) 2017, Rich Tibbett + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +(function (global, factory) { + true ? module.exports = factory() : 0; +})(this, function () { + 'use strict'; + + var commonjsGlobal = typeof window !== 'undefined' ? window : typeof __webpack_require__.g !== 'undefined' ? __webpack_require__.g : typeof self !== 'undefined' ? self : {}; + function unwrapExports(x) { + return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; + } + function createCommonjsModule(fn, module) { + return module = { + exports: {} + }, fn(module, module.exports), module.exports; + } + var isMobile = function isMobile() { + return /Android/i.test(navigator.userAgent) || /iPhone|iPad|iPod/i.test(navigator.userAgent); + }; + var copyArray = function copyArray(source, dest) { + for (var i = 0, n = source.length; i < n; i++) { + dest[i] = source[i]; + } + }; + var extend = function extend(dest, src) { + for (var key in src) { + if (src.hasOwnProperty(key)) { + dest[key] = src[key]; + } + } + return dest; + }; + var cardboardVrDisplay = createCommonjsModule(function (module, exports) { + /** + * @license + * cardboard-vr-display + * Copyright (c) 2015-2017 Google + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + /** + * @license + * gl-preserve-state + * Copyright (c) 2016, Brandon Jones. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + /** + * @license + * webvr-polyfill-dpdb + * Copyright (c) 2015-2017 Google + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + /** + * @license + * nosleep.js + * Copyright (c) 2017, Rich Tibbett + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + (function (global, factory) { + module.exports = factory(); + })(commonjsGlobal, function () { + 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 slicedToArray = function () { + function sliceIterator(arr, i) { + var _arr = []; + var _n = true; + var _d = false; + var _e = undefined; + try { + for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { + _arr.push(_s.value); + if (i && _arr.length === i) break; + } + } catch (err) { + _d = true; + _e = err; + } finally { + try { + if (!_n && _i["return"]) _i["return"](); + } finally { + if (_d) throw _e; + } + } + return _arr; + } + return function (arr, i) { + if (Array.isArray(arr)) { + return arr; + } else if (Symbol.iterator in Object(arr)) { + return sliceIterator(arr, i); + } else { + throw new TypeError("Invalid attempt to destructure non-iterable instance"); + } + }; + }(); + var MIN_TIMESTEP = 0.001; + var MAX_TIMESTEP = 1; + var dataUri = function dataUri(mimeType, svg) { + return 'data:' + mimeType + ',' + encodeURIComponent(svg); + }; + var lerp = function lerp(a, b, t) { + return a + (b - a) * t; + }; + var isIOS = function () { + var isIOS = /iPad|iPhone|iPod/.test(navigator.platform); + return function () { + return isIOS; + }; + }(); + var isWebViewAndroid = function () { + var isWebViewAndroid = navigator.userAgent.indexOf('Version') !== -1 && navigator.userAgent.indexOf('Android') !== -1 && navigator.userAgent.indexOf('Chrome') !== -1; + return function () { + return isWebViewAndroid; + }; + }(); + var isSafari = function () { + var isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); + return function () { + return isSafari; + }; + }(); + var isFirefoxAndroid = function () { + var isFirefoxAndroid = navigator.userAgent.indexOf('Firefox') !== -1 && navigator.userAgent.indexOf('Android') !== -1; + return function () { + return isFirefoxAndroid; + }; + }(); + var getChromeVersion = function () { + var match = navigator.userAgent.match(/.*Chrome\/([0-9]+)/); + var value = match ? parseInt(match[1], 10) : null; + return function () { + return value; + }; + }(); + var isSafariWithoutDeviceMotion = function () { + var value = false; + value = isIOS() && isSafari() && navigator.userAgent.indexOf('13_4') !== -1; + return function () { + return value; + }; + }(); + var isChromeWithoutDeviceMotion = function () { + var value = false; + if (getChromeVersion() === 65) { + var match = navigator.userAgent.match(/.*Chrome\/([0-9\.]*)/); + if (match) { + var _match$1$split = match[1].split('.'), + _match$1$split2 = slicedToArray(_match$1$split, 4), + major = _match$1$split2[0], + minor = _match$1$split2[1], + branch = _match$1$split2[2], + build = _match$1$split2[3]; + value = parseInt(branch, 10) === 3325 && parseInt(build, 10) < 148; + } + } + return function () { + return value; + }; + }(); + var isR7 = function () { + var isR7 = navigator.userAgent.indexOf('R7 Build') !== -1; + return function () { + return isR7; + }; + }(); + var isLandscapeMode = function isLandscapeMode() { + var rtn = window.orientation == 90 || window.orientation == -90; + return isR7() ? !rtn : rtn; + }; + var isTimestampDeltaValid = function isTimestampDeltaValid(timestampDeltaS) { + if (isNaN(timestampDeltaS)) { + return false; + } + if (timestampDeltaS <= MIN_TIMESTEP) { + return false; + } + if (timestampDeltaS > MAX_TIMESTEP) { + return false; + } + return true; + }; + var getScreenWidth = function getScreenWidth() { + return Math.max(window.screen.width, window.screen.height) * window.devicePixelRatio; + }; + var getScreenHeight = function getScreenHeight() { + return Math.min(window.screen.width, window.screen.height) * window.devicePixelRatio; + }; + var requestFullscreen = function requestFullscreen(element) { + if (isWebViewAndroid()) { + return false; + } + if (element.requestFullscreen) { + element.requestFullscreen(); + } else if (element.webkitRequestFullscreen) { + element.webkitRequestFullscreen(); + } else if (element.mozRequestFullScreen) { + element.mozRequestFullScreen(); + } else if (element.msRequestFullscreen) { + element.msRequestFullscreen(); + } else { + return false; + } + return true; + }; + var exitFullscreen = function exitFullscreen() { + if (document.exitFullscreen) { + document.exitFullscreen(); + } else if (document.webkitExitFullscreen) { + document.webkitExitFullscreen(); + } else if (document.mozCancelFullScreen) { + document.mozCancelFullScreen(); + } else if (document.msExitFullscreen) { + document.msExitFullscreen(); + } else { + return false; + } + return true; + }; + var getFullscreenElement = function getFullscreenElement() { + return document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement; + }; + var linkProgram = function linkProgram(gl, vertexSource, fragmentSource, attribLocationMap) { + var vertexShader = gl.createShader(gl.VERTEX_SHADER); + gl.shaderSource(vertexShader, vertexSource); + gl.compileShader(vertexShader); + var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); + gl.shaderSource(fragmentShader, fragmentSource); + gl.compileShader(fragmentShader); + var program = gl.createProgram(); + gl.attachShader(program, vertexShader); + gl.attachShader(program, fragmentShader); + for (var attribName in attribLocationMap) { + gl.bindAttribLocation(program, attribLocationMap[attribName], attribName); + } + gl.linkProgram(program); + gl.deleteShader(vertexShader); + gl.deleteShader(fragmentShader); + return program; + }; + var getProgramUniforms = function getProgramUniforms(gl, program) { + var uniforms = {}; + var uniformCount = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); + var uniformName = ''; + for (var i = 0; i < uniformCount; i++) { + var uniformInfo = gl.getActiveUniform(program, i); + uniformName = uniformInfo.name.replace('[0]', ''); + uniforms[uniformName] = gl.getUniformLocation(program, uniformName); + } + return uniforms; + }; + var orthoMatrix = function orthoMatrix(out, left, right, bottom, top, near, far) { + var lr = 1 / (left - right), + bt = 1 / (bottom - top), + nf = 1 / (near - far); + out[0] = -2 * lr; + out[1] = 0; + out[2] = 0; + out[3] = 0; + out[4] = 0; + out[5] = -2 * bt; + out[6] = 0; + out[7] = 0; + out[8] = 0; + out[9] = 0; + out[10] = 2 * nf; + out[11] = 0; + out[12] = (left + right) * lr; + out[13] = (top + bottom) * bt; + out[14] = (far + near) * nf; + out[15] = 1; + return out; + }; + var isMobile = function isMobile() { + var check = false; + (function (a) { + if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0, 4))) check = true; + })(navigator.userAgent || navigator.vendor || window.opera); + return check; + }; + var extend = function extend(dest, src) { + for (var key in src) { + if (src.hasOwnProperty(key)) { + dest[key] = src[key]; + } + } + return dest; + }; + var safariCssSizeWorkaround = function safariCssSizeWorkaround(canvas) { + if (isIOS()) { + var width = canvas.style.width; + var height = canvas.style.height; + canvas.style.width = parseInt(width) + 1 + 'px'; + canvas.style.height = parseInt(height) + 'px'; + setTimeout(function () { + canvas.style.width = width; + canvas.style.height = height; + }, 100); + } + window.canvas = canvas; + }; + var frameDataFromPose = function () { + var piOver180 = Math.PI / 180.0; + var rad45 = Math.PI * 0.25; + function mat4_perspectiveFromFieldOfView(out, fov, near, far) { + var upTan = Math.tan(fov ? fov.upDegrees * piOver180 : rad45), + downTan = Math.tan(fov ? fov.downDegrees * piOver180 : rad45), + leftTan = Math.tan(fov ? fov.leftDegrees * piOver180 : rad45), + rightTan = Math.tan(fov ? fov.rightDegrees * piOver180 : rad45), + xScale = 2.0 / (leftTan + rightTan), + yScale = 2.0 / (upTan + downTan); + out[0] = xScale; + out[1] = 0.0; + out[2] = 0.0; + out[3] = 0.0; + out[4] = 0.0; + out[5] = yScale; + out[6] = 0.0; + out[7] = 0.0; + out[8] = -((leftTan - rightTan) * xScale * 0.5); + out[9] = (upTan - downTan) * yScale * 0.5; + out[10] = far / (near - far); + out[11] = -1.0; + out[12] = 0.0; + out[13] = 0.0; + out[14] = far * near / (near - far); + out[15] = 0.0; + return out; + } + function mat4_fromRotationTranslation(out, q, v) { + var x = q[0], + y = q[1], + z = q[2], + w = q[3], + x2 = x + x, + y2 = y + y, + z2 = z + z, + xx = x * x2, + xy = x * y2, + xz = x * z2, + yy = y * y2, + yz = y * z2, + zz = z * z2, + wx = w * x2, + wy = w * y2, + wz = w * z2; + out[0] = 1 - (yy + zz); + out[1] = xy + wz; + out[2] = xz - wy; + out[3] = 0; + out[4] = xy - wz; + out[5] = 1 - (xx + zz); + out[6] = yz + wx; + out[7] = 0; + out[8] = xz + wy; + out[9] = yz - wx; + out[10] = 1 - (xx + yy); + out[11] = 0; + out[12] = v[0]; + out[13] = v[1]; + out[14] = v[2]; + out[15] = 1; + return out; + } + function mat4_translate(out, a, v) { + var x = v[0], + y = v[1], + z = v[2], + a00, + a01, + a02, + a03, + a10, + a11, + a12, + a13, + a20, + a21, + a22, + a23; + if (a === out) { + out[12] = a[0] * x + a[4] * y + a[8] * z + a[12]; + out[13] = a[1] * x + a[5] * y + a[9] * z + a[13]; + out[14] = a[2] * x + a[6] * y + a[10] * z + a[14]; + out[15] = a[3] * x + a[7] * y + a[11] * z + a[15]; + } else { + a00 = a[0]; + a01 = a[1]; + a02 = a[2]; + a03 = a[3]; + a10 = a[4]; + a11 = a[5]; + a12 = a[6]; + a13 = a[7]; + a20 = a[8]; + a21 = a[9]; + a22 = a[10]; + a23 = a[11]; + out[0] = a00; + out[1] = a01; + out[2] = a02; + out[3] = a03; + out[4] = a10; + out[5] = a11; + out[6] = a12; + out[7] = a13; + out[8] = a20; + out[9] = a21; + out[10] = a22; + out[11] = a23; + out[12] = a00 * x + a10 * y + a20 * z + a[12]; + out[13] = a01 * x + a11 * y + a21 * z + a[13]; + out[14] = a02 * x + a12 * y + a22 * z + a[14]; + out[15] = a03 * x + a13 * y + a23 * z + a[15]; + } + return out; + } + function mat4_invert(out, a) { + var a00 = a[0], + a01 = a[1], + a02 = a[2], + a03 = a[3], + a10 = a[4], + a11 = a[5], + a12 = a[6], + a13 = a[7], + a20 = a[8], + a21 = a[9], + a22 = a[10], + a23 = a[11], + a30 = a[12], + a31 = a[13], + a32 = a[14], + a33 = a[15], + b00 = a00 * a11 - a01 * a10, + b01 = a00 * a12 - a02 * a10, + b02 = a00 * a13 - a03 * a10, + b03 = a01 * a12 - a02 * a11, + b04 = a01 * a13 - a03 * a11, + b05 = a02 * a13 - a03 * a12, + b06 = a20 * a31 - a21 * a30, + b07 = a20 * a32 - a22 * a30, + b08 = a20 * a33 - a23 * a30, + b09 = a21 * a32 - a22 * a31, + b10 = a21 * a33 - a23 * a31, + b11 = a22 * a33 - a23 * a32, + det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; + if (!det) { + return null; + } + det = 1.0 / det; + out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det; + out[1] = (a02 * b10 - a01 * b11 - a03 * b09) * det; + out[2] = (a31 * b05 - a32 * b04 + a33 * b03) * det; + out[3] = (a22 * b04 - a21 * b05 - a23 * b03) * det; + out[4] = (a12 * b08 - a10 * b11 - a13 * b07) * det; + out[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det; + out[6] = (a32 * b02 - a30 * b05 - a33 * b01) * det; + out[7] = (a20 * b05 - a22 * b02 + a23 * b01) * det; + out[8] = (a10 * b10 - a11 * b08 + a13 * b06) * det; + out[9] = (a01 * b08 - a00 * b10 - a03 * b06) * det; + out[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det; + out[11] = (a21 * b02 - a20 * b04 - a23 * b00) * det; + out[12] = (a11 * b07 - a10 * b09 - a12 * b06) * det; + out[13] = (a00 * b09 - a01 * b07 + a02 * b06) * det; + out[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det; + out[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det; + return out; + } + var defaultOrientation = new Float32Array([0, 0, 0, 1]); + var defaultPosition = new Float32Array([0, 0, 0]); + function updateEyeMatrices(projection, view, pose, fov, offset, vrDisplay) { + mat4_perspectiveFromFieldOfView(projection, fov || null, vrDisplay.depthNear, vrDisplay.depthFar); + var orientation = pose.orientation || defaultOrientation; + var position = pose.position || defaultPosition; + mat4_fromRotationTranslation(view, orientation, position); + if (offset) mat4_translate(view, view, offset); + mat4_invert(view, view); + } + return function (frameData, pose, vrDisplay) { + if (!frameData || !pose) return false; + frameData.pose = pose; + frameData.timestamp = pose.timestamp; + updateEyeMatrices(frameData.leftProjectionMatrix, frameData.leftViewMatrix, pose, vrDisplay._getFieldOfView("left"), vrDisplay._getEyeOffset("left"), vrDisplay); + updateEyeMatrices(frameData.rightProjectionMatrix, frameData.rightViewMatrix, pose, vrDisplay._getFieldOfView("right"), vrDisplay._getEyeOffset("right"), vrDisplay); + return true; + }; + }(); + var isInsideCrossOriginIFrame = function isInsideCrossOriginIFrame() { + var isFramed = window.self !== window.top; + var refOrigin = getOriginFromUrl(document.referrer); + var thisOrigin = getOriginFromUrl(window.location.href); + return isFramed && refOrigin !== thisOrigin; + }; + var getOriginFromUrl = function getOriginFromUrl(url) { + var domainIdx; + var protoSepIdx = url.indexOf("://"); + if (protoSepIdx !== -1) { + domainIdx = protoSepIdx + 3; + } else { + domainIdx = 0; + } + var domainEndIdx = url.indexOf('/', domainIdx); + if (domainEndIdx === -1) { + domainEndIdx = url.length; + } + return url.substring(0, domainEndIdx); + }; + var getQuaternionAngle = function getQuaternionAngle(quat) { + if (quat.w > 1) { + console.warn('getQuaternionAngle: w > 1'); + return 0; + } + var angle = 2 * Math.acos(quat.w); + return angle; + }; + var warnOnce = function () { + var observedWarnings = {}; + return function (key, message) { + if (observedWarnings[key] === undefined) { + console.warn('webvr-polyfill: ' + message); + observedWarnings[key] = true; + } + }; + }(); + var deprecateWarning = function deprecateWarning(deprecated, suggested) { + var alternative = suggested ? 'Please use ' + suggested + ' instead.' : ''; + warnOnce(deprecated, deprecated + ' has been deprecated. ' + 'This may not work on native WebVR displays. ' + alternative); + }; + function WGLUPreserveGLState(gl, bindings, callback) { + if (!bindings) { + callback(gl); + return; + } + var boundValues = []; + var activeTexture = null; + for (var i = 0; i < bindings.length; ++i) { + var binding = bindings[i]; + switch (binding) { + case gl.TEXTURE_BINDING_2D: + case gl.TEXTURE_BINDING_CUBE_MAP: + var textureUnit = bindings[++i]; + if (textureUnit < gl.TEXTURE0 || textureUnit > gl.TEXTURE31) { + console.error("TEXTURE_BINDING_2D or TEXTURE_BINDING_CUBE_MAP must be followed by a valid texture unit"); + boundValues.push(null, null); + break; + } + if (!activeTexture) { + activeTexture = gl.getParameter(gl.ACTIVE_TEXTURE); + } + gl.activeTexture(textureUnit); + boundValues.push(gl.getParameter(binding), null); + break; + case gl.ACTIVE_TEXTURE: + activeTexture = gl.getParameter(gl.ACTIVE_TEXTURE); + boundValues.push(null); + break; + default: + boundValues.push(gl.getParameter(binding)); + break; + } + } + callback(gl); + for (var i = 0; i < bindings.length; ++i) { + var binding = bindings[i]; + var boundValue = boundValues[i]; + switch (binding) { + case gl.ACTIVE_TEXTURE: + break; + case gl.ARRAY_BUFFER_BINDING: + gl.bindBuffer(gl.ARRAY_BUFFER, boundValue); + break; + case gl.COLOR_CLEAR_VALUE: + gl.clearColor(boundValue[0], boundValue[1], boundValue[2], boundValue[3]); + break; + case gl.COLOR_WRITEMASK: + gl.colorMask(boundValue[0], boundValue[1], boundValue[2], boundValue[3]); + break; + case gl.CURRENT_PROGRAM: + gl.useProgram(boundValue); + break; + case gl.ELEMENT_ARRAY_BUFFER_BINDING: + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, boundValue); + break; + case gl.FRAMEBUFFER_BINDING: + gl.bindFramebuffer(gl.FRAMEBUFFER, boundValue); + break; + case gl.RENDERBUFFER_BINDING: + gl.bindRenderbuffer(gl.RENDERBUFFER, boundValue); + break; + case gl.TEXTURE_BINDING_2D: + var textureUnit = bindings[++i]; + if (textureUnit < gl.TEXTURE0 || textureUnit > gl.TEXTURE31) break; + gl.activeTexture(textureUnit); + gl.bindTexture(gl.TEXTURE_2D, boundValue); + break; + case gl.TEXTURE_BINDING_CUBE_MAP: + var textureUnit = bindings[++i]; + if (textureUnit < gl.TEXTURE0 || textureUnit > gl.TEXTURE31) break; + gl.activeTexture(textureUnit); + gl.bindTexture(gl.TEXTURE_CUBE_MAP, boundValue); + break; + case gl.VIEWPORT: + gl.viewport(boundValue[0], boundValue[1], boundValue[2], boundValue[3]); + break; + case gl.BLEND: + case gl.CULL_FACE: + case gl.DEPTH_TEST: + case gl.SCISSOR_TEST: + case gl.STENCIL_TEST: + if (boundValue) { + gl.enable(binding); + } else { + gl.disable(binding); + } + break; + default: + console.log("No GL restore behavior for 0x" + binding.toString(16)); + break; + } + if (activeTexture) { + gl.activeTexture(activeTexture); + } + } + } + var glPreserveState = WGLUPreserveGLState; + var distortionVS = ['attribute vec2 position;', 'attribute vec3 texCoord;', 'varying vec2 vTexCoord;', 'uniform vec4 viewportOffsetScale[2];', 'void main() {', ' vec4 viewport = viewportOffsetScale[int(texCoord.z)];', ' vTexCoord = (texCoord.xy * viewport.zw) + viewport.xy;', ' gl_Position = vec4( position, 1.0, 1.0 );', '}'].join('\n'); + var distortionFS = ['precision mediump float;', 'uniform sampler2D diffuse;', 'varying vec2 vTexCoord;', 'void main() {', ' gl_FragColor = texture2D(diffuse, vTexCoord);', '}'].join('\n'); + function CardboardDistorter(gl, cardboardUI, bufferScale, dirtySubmitFrameBindings) { + this.gl = gl; + this.cardboardUI = cardboardUI; + this.bufferScale = bufferScale; + this.dirtySubmitFrameBindings = dirtySubmitFrameBindings; + this.ctxAttribs = gl.getContextAttributes(); + this.instanceExt = gl.getExtension('ANGLE_instanced_arrays'); + this.meshWidth = 20; + this.meshHeight = 20; + this.bufferWidth = gl.drawingBufferWidth; + this.bufferHeight = gl.drawingBufferHeight; + this.realBindFramebuffer = gl.bindFramebuffer; + this.realEnable = gl.enable; + this.realDisable = gl.disable; + this.realColorMask = gl.colorMask; + this.realClearColor = gl.clearColor; + this.realViewport = gl.viewport; + if (!isIOS()) { + this.realCanvasWidth = Object.getOwnPropertyDescriptor(gl.canvas.__proto__, 'width'); + this.realCanvasHeight = Object.getOwnPropertyDescriptor(gl.canvas.__proto__, 'height'); + } + this.isPatched = false; + this.lastBoundFramebuffer = null; + this.cullFace = false; + this.depthTest = false; + this.blend = false; + this.scissorTest = false; + this.stencilTest = false; + this.viewport = [0, 0, 0, 0]; + this.colorMask = [true, true, true, true]; + this.clearColor = [0, 0, 0, 0]; + this.attribs = { + position: 0, + texCoord: 1 + }; + this.program = linkProgram(gl, distortionVS, distortionFS, this.attribs); + this.uniforms = getProgramUniforms(gl, this.program); + this.viewportOffsetScale = new Float32Array(8); + this.setTextureBounds(); + this.vertexBuffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + this.indexCount = 0; + this.renderTarget = gl.createTexture(); + this.framebuffer = gl.createFramebuffer(); + this.depthStencilBuffer = null; + this.depthBuffer = null; + this.stencilBuffer = null; + if (this.ctxAttribs.depth && this.ctxAttribs.stencil) { + this.depthStencilBuffer = gl.createRenderbuffer(); + } else if (this.ctxAttribs.depth) { + this.depthBuffer = gl.createRenderbuffer(); + } else if (this.ctxAttribs.stencil) { + this.stencilBuffer = gl.createRenderbuffer(); + } + this.patch(); + this.onResize(); + } + CardboardDistorter.prototype.destroy = function () { + var gl = this.gl; + this.unpatch(); + gl.deleteProgram(this.program); + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.indexBuffer); + gl.deleteTexture(this.renderTarget); + gl.deleteFramebuffer(this.framebuffer); + if (this.depthStencilBuffer) { + gl.deleteRenderbuffer(this.depthStencilBuffer); + } + if (this.depthBuffer) { + gl.deleteRenderbuffer(this.depthBuffer); + } + if (this.stencilBuffer) { + gl.deleteRenderbuffer(this.stencilBuffer); + } + if (this.cardboardUI) { + this.cardboardUI.destroy(); + } + }; + CardboardDistorter.prototype.onResize = function () { + var gl = this.gl; + var self = this; + var glState = [gl.RENDERBUFFER_BINDING, gl.TEXTURE_BINDING_2D, gl.TEXTURE0]; + glPreserveState(gl, glState, function (gl) { + self.realBindFramebuffer.call(gl, gl.FRAMEBUFFER, null); + if (self.scissorTest) { + self.realDisable.call(gl, gl.SCISSOR_TEST); + } + self.realColorMask.call(gl, true, true, true, true); + self.realViewport.call(gl, 0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); + self.realClearColor.call(gl, 0, 0, 0, 1); + gl.clear(gl.COLOR_BUFFER_BIT); + self.realBindFramebuffer.call(gl, gl.FRAMEBUFFER, self.framebuffer); + gl.bindTexture(gl.TEXTURE_2D, self.renderTarget); + gl.texImage2D(gl.TEXTURE_2D, 0, self.ctxAttribs.alpha ? gl.RGBA : gl.RGB, self.bufferWidth, self.bufferHeight, 0, self.ctxAttribs.alpha ? gl.RGBA : gl.RGB, gl.UNSIGNED_BYTE, null); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, self.renderTarget, 0); + if (self.ctxAttribs.depth && self.ctxAttribs.stencil) { + gl.bindRenderbuffer(gl.RENDERBUFFER, self.depthStencilBuffer); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, self.bufferWidth, self.bufferHeight); + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, self.depthStencilBuffer); + } else if (self.ctxAttribs.depth) { + gl.bindRenderbuffer(gl.RENDERBUFFER, self.depthBuffer); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, self.bufferWidth, self.bufferHeight); + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, self.depthBuffer); + } else if (self.ctxAttribs.stencil) { + gl.bindRenderbuffer(gl.RENDERBUFFER, self.stencilBuffer); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.STENCIL_INDEX8, self.bufferWidth, self.bufferHeight); + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.RENDERBUFFER, self.stencilBuffer); + } + if (!gl.checkFramebufferStatus(gl.FRAMEBUFFER) === gl.FRAMEBUFFER_COMPLETE) { + console.error('Framebuffer incomplete!'); + } + self.realBindFramebuffer.call(gl, gl.FRAMEBUFFER, self.lastBoundFramebuffer); + if (self.scissorTest) { + self.realEnable.call(gl, gl.SCISSOR_TEST); + } + self.realColorMask.apply(gl, self.colorMask); + self.realViewport.apply(gl, self.viewport); + self.realClearColor.apply(gl, self.clearColor); + }); + if (this.cardboardUI) { + this.cardboardUI.onResize(); + } + }; + CardboardDistorter.prototype.patch = function () { + if (this.isPatched) { + return; + } + var self = this; + var canvas = this.gl.canvas; + var gl = this.gl; + if (!isIOS()) { + canvas.width = getScreenWidth() * this.bufferScale; + canvas.height = getScreenHeight() * this.bufferScale; + Object.defineProperty(canvas, 'width', { + configurable: true, + enumerable: true, + get: function get() { + return self.bufferWidth; + }, + set: function set(value) { + self.bufferWidth = value; + self.realCanvasWidth.set.call(canvas, value); + self.onResize(); + } + }); + Object.defineProperty(canvas, 'height', { + configurable: true, + enumerable: true, + get: function get() { + return self.bufferHeight; + }, + set: function set(value) { + self.bufferHeight = value; + self.realCanvasHeight.set.call(canvas, value); + self.onResize(); + } + }); + } + this.lastBoundFramebuffer = gl.getParameter(gl.FRAMEBUFFER_BINDING); + if (this.lastBoundFramebuffer == null) { + this.lastBoundFramebuffer = this.framebuffer; + this.gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer); + } + this.gl.bindFramebuffer = function (target, framebuffer) { + self.lastBoundFramebuffer = framebuffer ? framebuffer : self.framebuffer; + self.realBindFramebuffer.call(gl, target, self.lastBoundFramebuffer); + }; + this.cullFace = gl.getParameter(gl.CULL_FACE); + this.depthTest = gl.getParameter(gl.DEPTH_TEST); + this.blend = gl.getParameter(gl.BLEND); + this.scissorTest = gl.getParameter(gl.SCISSOR_TEST); + this.stencilTest = gl.getParameter(gl.STENCIL_TEST); + gl.enable = function (pname) { + switch (pname) { + case gl.CULL_FACE: + self.cullFace = true; + break; + case gl.DEPTH_TEST: + self.depthTest = true; + break; + case gl.BLEND: + self.blend = true; + break; + case gl.SCISSOR_TEST: + self.scissorTest = true; + break; + case gl.STENCIL_TEST: + self.stencilTest = true; + break; + } + self.realEnable.call(gl, pname); + }; + gl.disable = function (pname) { + switch (pname) { + case gl.CULL_FACE: + self.cullFace = false; + break; + case gl.DEPTH_TEST: + self.depthTest = false; + break; + case gl.BLEND: + self.blend = false; + break; + case gl.SCISSOR_TEST: + self.scissorTest = false; + break; + case gl.STENCIL_TEST: + self.stencilTest = false; + break; + } + self.realDisable.call(gl, pname); + }; + this.colorMask = gl.getParameter(gl.COLOR_WRITEMASK); + gl.colorMask = function (r, g, b, a) { + self.colorMask[0] = r; + self.colorMask[1] = g; + self.colorMask[2] = b; + self.colorMask[3] = a; + self.realColorMask.call(gl, r, g, b, a); + }; + this.clearColor = gl.getParameter(gl.COLOR_CLEAR_VALUE); + gl.clearColor = function (r, g, b, a) { + self.clearColor[0] = r; + self.clearColor[1] = g; + self.clearColor[2] = b; + self.clearColor[3] = a; + self.realClearColor.call(gl, r, g, b, a); + }; + this.viewport = gl.getParameter(gl.VIEWPORT); + gl.viewport = function (x, y, w, h) { + self.viewport[0] = x; + self.viewport[1] = y; + self.viewport[2] = w; + self.viewport[3] = h; + self.realViewport.call(gl, x, y, w, h); + }; + this.isPatched = true; + safariCssSizeWorkaround(canvas); + }; + CardboardDistorter.prototype.unpatch = function () { + if (!this.isPatched) { + return; + } + var gl = this.gl; + var canvas = this.gl.canvas; + if (!isIOS()) { + Object.defineProperty(canvas, 'width', this.realCanvasWidth); + Object.defineProperty(canvas, 'height', this.realCanvasHeight); + } + canvas.width = this.bufferWidth; + canvas.height = this.bufferHeight; + gl.bindFramebuffer = this.realBindFramebuffer; + gl.enable = this.realEnable; + gl.disable = this.realDisable; + gl.colorMask = this.realColorMask; + gl.clearColor = this.realClearColor; + gl.viewport = this.realViewport; + if (this.lastBoundFramebuffer == this.framebuffer) { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + } + this.isPatched = false; + setTimeout(function () { + safariCssSizeWorkaround(canvas); + }, 1); + }; + CardboardDistorter.prototype.setTextureBounds = function (leftBounds, rightBounds) { + if (!leftBounds) { + leftBounds = [0, 0, 0.5, 1]; + } + if (!rightBounds) { + rightBounds = [0.5, 0, 0.5, 1]; + } + this.viewportOffsetScale[0] = leftBounds[0]; + this.viewportOffsetScale[1] = leftBounds[1]; + this.viewportOffsetScale[2] = leftBounds[2]; + this.viewportOffsetScale[3] = leftBounds[3]; + this.viewportOffsetScale[4] = rightBounds[0]; + this.viewportOffsetScale[5] = rightBounds[1]; + this.viewportOffsetScale[6] = rightBounds[2]; + this.viewportOffsetScale[7] = rightBounds[3]; + }; + CardboardDistorter.prototype.submitFrame = function () { + var gl = this.gl; + var self = this; + var glState = []; + if (!this.dirtySubmitFrameBindings) { + glState.push(gl.CURRENT_PROGRAM, gl.ARRAY_BUFFER_BINDING, gl.ELEMENT_ARRAY_BUFFER_BINDING, gl.TEXTURE_BINDING_2D, gl.TEXTURE0); + } + glPreserveState(gl, glState, function (gl) { + self.realBindFramebuffer.call(gl, gl.FRAMEBUFFER, null); + var positionDivisor = 0; + var texCoordDivisor = 0; + if (self.instanceExt) { + positionDivisor = gl.getVertexAttrib(self.attribs.position, self.instanceExt.VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE); + texCoordDivisor = gl.getVertexAttrib(self.attribs.texCoord, self.instanceExt.VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE); + } + if (self.cullFace) { + self.realDisable.call(gl, gl.CULL_FACE); + } + if (self.depthTest) { + self.realDisable.call(gl, gl.DEPTH_TEST); + } + if (self.blend) { + self.realDisable.call(gl, gl.BLEND); + } + if (self.scissorTest) { + self.realDisable.call(gl, gl.SCISSOR_TEST); + } + if (self.stencilTest) { + self.realDisable.call(gl, gl.STENCIL_TEST); + } + self.realColorMask.call(gl, true, true, true, true); + self.realViewport.call(gl, 0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); + if (self.ctxAttribs.alpha || isIOS()) { + self.realClearColor.call(gl, 0, 0, 0, 1); + gl.clear(gl.COLOR_BUFFER_BIT); + } + gl.useProgram(self.program); + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, self.indexBuffer); + gl.bindBuffer(gl.ARRAY_BUFFER, self.vertexBuffer); + gl.enableVertexAttribArray(self.attribs.position); + gl.enableVertexAttribArray(self.attribs.texCoord); + gl.vertexAttribPointer(self.attribs.position, 2, gl.FLOAT, false, 20, 0); + gl.vertexAttribPointer(self.attribs.texCoord, 3, gl.FLOAT, false, 20, 8); + if (self.instanceExt) { + if (positionDivisor != 0) { + self.instanceExt.vertexAttribDivisorANGLE(self.attribs.position, 0); + } + if (texCoordDivisor != 0) { + self.instanceExt.vertexAttribDivisorANGLE(self.attribs.texCoord, 0); + } + } + gl.activeTexture(gl.TEXTURE0); + gl.uniform1i(self.uniforms.diffuse, 0); + gl.bindTexture(gl.TEXTURE_2D, self.renderTarget); + gl.uniform4fv(self.uniforms.viewportOffsetScale, self.viewportOffsetScale); + gl.drawElements(gl.TRIANGLES, self.indexCount, gl.UNSIGNED_SHORT, 0); + if (self.cardboardUI) { + self.cardboardUI.renderNoState(); + } + self.realBindFramebuffer.call(self.gl, gl.FRAMEBUFFER, self.framebuffer); + if (!self.ctxAttribs.preserveDrawingBuffer) { + self.realClearColor.call(gl, 0, 0, 0, 0); + gl.clear(gl.COLOR_BUFFER_BIT); + } + if (!self.dirtySubmitFrameBindings) { + self.realBindFramebuffer.call(gl, gl.FRAMEBUFFER, self.lastBoundFramebuffer); + } + if (self.cullFace) { + self.realEnable.call(gl, gl.CULL_FACE); + } + if (self.depthTest) { + self.realEnable.call(gl, gl.DEPTH_TEST); + } + if (self.blend) { + self.realEnable.call(gl, gl.BLEND); + } + if (self.scissorTest) { + self.realEnable.call(gl, gl.SCISSOR_TEST); + } + if (self.stencilTest) { + self.realEnable.call(gl, gl.STENCIL_TEST); + } + self.realColorMask.apply(gl, self.colorMask); + self.realViewport.apply(gl, self.viewport); + if (self.ctxAttribs.alpha || !self.ctxAttribs.preserveDrawingBuffer) { + self.realClearColor.apply(gl, self.clearColor); + } + if (self.instanceExt) { + if (positionDivisor != 0) { + self.instanceExt.vertexAttribDivisorANGLE(self.attribs.position, positionDivisor); + } + if (texCoordDivisor != 0) { + self.instanceExt.vertexAttribDivisorANGLE(self.attribs.texCoord, texCoordDivisor); + } + } + }); + if (isIOS()) { + var canvas = gl.canvas; + if (canvas.width != self.bufferWidth || canvas.height != self.bufferHeight) { + self.bufferWidth = canvas.width; + self.bufferHeight = canvas.height; + self.onResize(); + } + } + }; + CardboardDistorter.prototype.updateDeviceInfo = function (deviceInfo) { + var gl = this.gl; + var self = this; + var glState = [gl.ARRAY_BUFFER_BINDING, gl.ELEMENT_ARRAY_BUFFER_BINDING]; + glPreserveState(gl, glState, function (gl) { + var vertices = self.computeMeshVertices_(self.meshWidth, self.meshHeight, deviceInfo); + gl.bindBuffer(gl.ARRAY_BUFFER, self.vertexBuffer); + gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW); + if (!self.indexCount) { + var indices = self.computeMeshIndices_(self.meshWidth, self.meshHeight); + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, self.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW); + self.indexCount = indices.length; + } + }); + }; + CardboardDistorter.prototype.computeMeshVertices_ = function (width, height, deviceInfo) { + var vertices = new Float32Array(2 * width * height * 5); + var lensFrustum = deviceInfo.getLeftEyeVisibleTanAngles(); + var noLensFrustum = deviceInfo.getLeftEyeNoLensTanAngles(); + var viewport = deviceInfo.getLeftEyeVisibleScreenRect(noLensFrustum); + var vidx = 0; + for (var e = 0; e < 2; e++) { + for (var j = 0; j < height; j++) { + for (var i = 0; i < width; i++, vidx++) { + var u = i / (width - 1); + var v = j / (height - 1); + var s = u; + var t = v; + var x = lerp(lensFrustum[0], lensFrustum[2], u); + var y = lerp(lensFrustum[3], lensFrustum[1], v); + var d = Math.sqrt(x * x + y * y); + var r = deviceInfo.distortion.distortInverse(d); + var p = x * r / d; + var q = y * r / d; + u = (p - noLensFrustum[0]) / (noLensFrustum[2] - noLensFrustum[0]); + v = (q - noLensFrustum[3]) / (noLensFrustum[1] - noLensFrustum[3]); + u = (viewport.x + u * viewport.width - 0.5) * 2.0; + v = (viewport.y + v * viewport.height - 0.5) * 2.0; + vertices[vidx * 5 + 0] = u; + vertices[vidx * 5 + 1] = v; + vertices[vidx * 5 + 2] = s; + vertices[vidx * 5 + 3] = t; + vertices[vidx * 5 + 4] = e; + } + } + var w = lensFrustum[2] - lensFrustum[0]; + lensFrustum[0] = -(w + lensFrustum[0]); + lensFrustum[2] = w - lensFrustum[2]; + w = noLensFrustum[2] - noLensFrustum[0]; + noLensFrustum[0] = -(w + noLensFrustum[0]); + noLensFrustum[2] = w - noLensFrustum[2]; + viewport.x = 1 - (viewport.x + viewport.width); + } + return vertices; + }; + CardboardDistorter.prototype.computeMeshIndices_ = function (width, height) { + var indices = new Uint16Array(2 * (width - 1) * (height - 1) * 6); + var halfwidth = width / 2; + var halfheight = height / 2; + var vidx = 0; + var iidx = 0; + for (var e = 0; e < 2; e++) { + for (var j = 0; j < height; j++) { + for (var i = 0; i < width; i++, vidx++) { + if (i == 0 || j == 0) continue; + if (i <= halfwidth == j <= halfheight) { + indices[iidx++] = vidx; + indices[iidx++] = vidx - width - 1; + indices[iidx++] = vidx - width; + indices[iidx++] = vidx - width - 1; + indices[iidx++] = vidx; + indices[iidx++] = vidx - 1; + } else { + indices[iidx++] = vidx - 1; + indices[iidx++] = vidx - width; + indices[iidx++] = vidx; + indices[iidx++] = vidx - width; + indices[iidx++] = vidx - 1; + indices[iidx++] = vidx - width - 1; + } + } + } + } + return indices; + }; + CardboardDistorter.prototype.getOwnPropertyDescriptor_ = function (proto, attrName) { + var descriptor = Object.getOwnPropertyDescriptor(proto, attrName); + if (descriptor.get === undefined || descriptor.set === undefined) { + descriptor.configurable = true; + descriptor.enumerable = true; + descriptor.get = function () { + return this.getAttribute(attrName); + }; + descriptor.set = function (val) { + this.setAttribute(attrName, val); + }; + } + return descriptor; + }; + var uiVS = ['attribute vec2 position;', 'uniform mat4 projectionMat;', 'void main() {', ' gl_Position = projectionMat * vec4( position, -1.0, 1.0 );', '}'].join('\n'); + var uiFS = ['precision mediump float;', 'uniform vec4 color;', 'void main() {', ' gl_FragColor = color;', '}'].join('\n'); + var DEG2RAD = Math.PI / 180.0; + var kAnglePerGearSection = 60; + var kOuterRimEndAngle = 12; + var kInnerRimBeginAngle = 20; + var kOuterRadius = 1; + var kMiddleRadius = 0.75; + var kInnerRadius = 0.3125; + var kCenterLineThicknessDp = 4; + var kButtonWidthDp = 28; + var kTouchSlopFactor = 1.5; + function CardboardUI(gl) { + this.gl = gl; + this.attribs = { + position: 0 + }; + this.program = linkProgram(gl, uiVS, uiFS, this.attribs); + this.uniforms = getProgramUniforms(gl, this.program); + this.vertexBuffer = gl.createBuffer(); + this.gearOffset = 0; + this.gearVertexCount = 0; + this.arrowOffset = 0; + this.arrowVertexCount = 0; + this.projMat = new Float32Array(16); + this.listener = null; + this.onResize(); + } + CardboardUI.prototype.destroy = function () { + var gl = this.gl; + if (this.listener) { + gl.canvas.removeEventListener('click', this.listener, false); + } + gl.deleteProgram(this.program); + gl.deleteBuffer(this.vertexBuffer); + }; + CardboardUI.prototype.listen = function (optionsCallback, backCallback) { + var canvas = this.gl.canvas; + this.listener = function (event) { + var midline = canvas.clientWidth / 2; + var buttonSize = kButtonWidthDp * kTouchSlopFactor; + if (event.clientX > midline - buttonSize && event.clientX < midline + buttonSize && event.clientY > canvas.clientHeight - buttonSize) { + optionsCallback(event); + } else if (event.clientX < buttonSize && event.clientY < buttonSize) { + backCallback(event); + } + }; + canvas.addEventListener('click', this.listener, false); + }; + CardboardUI.prototype.onResize = function () { + var gl = this.gl; + var self = this; + var glState = [gl.ARRAY_BUFFER_BINDING]; + glPreserveState(gl, glState, function (gl) { + var vertices = []; + var midline = gl.drawingBufferWidth / 2; + var physicalPixels = Math.max(screen.width, screen.height) * window.devicePixelRatio; + var scalingRatio = gl.drawingBufferWidth / physicalPixels; + var dps = scalingRatio * window.devicePixelRatio; + var lineWidth = kCenterLineThicknessDp * dps / 2; + var buttonSize = kButtonWidthDp * kTouchSlopFactor * dps; + var buttonScale = kButtonWidthDp * dps / 2; + var buttonBorder = (kButtonWidthDp * kTouchSlopFactor - kButtonWidthDp) * dps; + vertices.push(midline - lineWidth, buttonSize); + vertices.push(midline - lineWidth, gl.drawingBufferHeight); + vertices.push(midline + lineWidth, buttonSize); + vertices.push(midline + lineWidth, gl.drawingBufferHeight); + self.gearOffset = vertices.length / 2; + function addGearSegment(theta, r) { + var angle = (90 - theta) * DEG2RAD; + var x = Math.cos(angle); + var y = Math.sin(angle); + vertices.push(kInnerRadius * x * buttonScale + midline, kInnerRadius * y * buttonScale + buttonScale); + vertices.push(r * x * buttonScale + midline, r * y * buttonScale + buttonScale); + } + for (var i = 0; i <= 6; i++) { + var segmentTheta = i * kAnglePerGearSection; + addGearSegment(segmentTheta, kOuterRadius); + addGearSegment(segmentTheta + kOuterRimEndAngle, kOuterRadius); + addGearSegment(segmentTheta + kInnerRimBeginAngle, kMiddleRadius); + addGearSegment(segmentTheta + (kAnglePerGearSection - kInnerRimBeginAngle), kMiddleRadius); + addGearSegment(segmentTheta + (kAnglePerGearSection - kOuterRimEndAngle), kOuterRadius); + } + self.gearVertexCount = vertices.length / 2 - self.gearOffset; + self.arrowOffset = vertices.length / 2; + function addArrowVertex(x, y) { + vertices.push(buttonBorder + x, gl.drawingBufferHeight - buttonBorder - y); + } + var angledLineWidth = lineWidth / Math.sin(45 * DEG2RAD); + addArrowVertex(0, buttonScale); + addArrowVertex(buttonScale, 0); + addArrowVertex(buttonScale + angledLineWidth, angledLineWidth); + addArrowVertex(angledLineWidth, buttonScale + angledLineWidth); + addArrowVertex(angledLineWidth, buttonScale - angledLineWidth); + addArrowVertex(0, buttonScale); + addArrowVertex(buttonScale, buttonScale * 2); + addArrowVertex(buttonScale + angledLineWidth, buttonScale * 2 - angledLineWidth); + addArrowVertex(angledLineWidth, buttonScale - angledLineWidth); + addArrowVertex(0, buttonScale); + addArrowVertex(angledLineWidth, buttonScale - lineWidth); + addArrowVertex(kButtonWidthDp * dps, buttonScale - lineWidth); + addArrowVertex(angledLineWidth, buttonScale + lineWidth); + addArrowVertex(kButtonWidthDp * dps, buttonScale + lineWidth); + self.arrowVertexCount = vertices.length / 2 - self.arrowOffset; + gl.bindBuffer(gl.ARRAY_BUFFER, self.vertexBuffer); + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); + }); + }; + CardboardUI.prototype.render = function () { + var gl = this.gl; + var self = this; + var glState = [gl.CULL_FACE, gl.DEPTH_TEST, gl.BLEND, gl.SCISSOR_TEST, gl.STENCIL_TEST, gl.COLOR_WRITEMASK, gl.VIEWPORT, gl.CURRENT_PROGRAM, gl.ARRAY_BUFFER_BINDING]; + glPreserveState(gl, glState, function (gl) { + gl.disable(gl.CULL_FACE); + gl.disable(gl.DEPTH_TEST); + gl.disable(gl.BLEND); + gl.disable(gl.SCISSOR_TEST); + gl.disable(gl.STENCIL_TEST); + gl.colorMask(true, true, true, true); + gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); + self.renderNoState(); + }); + }; + CardboardUI.prototype.renderNoState = function () { + var gl = this.gl; + gl.useProgram(this.program); + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.enableVertexAttribArray(this.attribs.position); + gl.vertexAttribPointer(this.attribs.position, 2, gl.FLOAT, false, 8, 0); + gl.uniform4f(this.uniforms.color, 1.0, 1.0, 1.0, 1.0); + orthoMatrix(this.projMat, 0, gl.drawingBufferWidth, 0, gl.drawingBufferHeight, 0.1, 1024.0); + gl.uniformMatrix4fv(this.uniforms.projectionMat, false, this.projMat); + gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); + gl.drawArrays(gl.TRIANGLE_STRIP, this.gearOffset, this.gearVertexCount); + gl.drawArrays(gl.TRIANGLE_STRIP, this.arrowOffset, this.arrowVertexCount); + }; + function Distortion(coefficients) { + this.coefficients = coefficients; + } + Distortion.prototype.distortInverse = function (radius) { + var r0 = 0; + var r1 = 1; + var dr0 = radius - this.distort(r0); + while (Math.abs(r1 - r0) > 0.0001) { + var dr1 = radius - this.distort(r1); + var r2 = r1 - dr1 * ((r1 - r0) / (dr1 - dr0)); + r0 = r1; + r1 = r2; + dr0 = dr1; + } + return r1; + }; + Distortion.prototype.distort = function (radius) { + var r2 = radius * radius; + var ret = 0; + for (var i = 0; i < this.coefficients.length; i++) { + ret = r2 * (ret + this.coefficients[i]); + } + return (ret + 1) * radius; + }; + var degToRad = Math.PI / 180; + var radToDeg = 180 / Math.PI; + var Vector3 = function Vector3(x, y, z) { + this.x = x || 0; + this.y = y || 0; + this.z = z || 0; + }; + Vector3.prototype = { + constructor: Vector3, + set: function set(x, y, z) { + this.x = x; + this.y = y; + this.z = z; + return this; + }, + copy: function copy(v) { + this.x = v.x; + this.y = v.y; + this.z = v.z; + return this; + }, + length: function length() { + return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); + }, + normalize: function normalize() { + var scalar = this.length(); + if (scalar !== 0) { + var invScalar = 1 / scalar; + this.multiplyScalar(invScalar); + } else { + this.x = 0; + this.y = 0; + this.z = 0; + } + return this; + }, + multiplyScalar: function multiplyScalar(scalar) { + this.x *= scalar; + this.y *= scalar; + this.z *= scalar; + }, + applyQuaternion: function applyQuaternion(q) { + var x = this.x; + var y = this.y; + var z = this.z; + var qx = q.x; + var qy = q.y; + var qz = q.z; + var qw = q.w; + var ix = qw * x + qy * z - qz * y; + var iy = qw * y + qz * x - qx * z; + var iz = qw * z + qx * y - qy * x; + var iw = -qx * x - qy * y - qz * z; + this.x = ix * qw + iw * -qx + iy * -qz - iz * -qy; + this.y = iy * qw + iw * -qy + iz * -qx - ix * -qz; + this.z = iz * qw + iw * -qz + ix * -qy - iy * -qx; + return this; + }, + dot: function dot(v) { + return this.x * v.x + this.y * v.y + this.z * v.z; + }, + crossVectors: function crossVectors(a, b) { + var ax = a.x, + ay = a.y, + az = a.z; + var bx = b.x, + by = b.y, + bz = b.z; + this.x = ay * bz - az * by; + this.y = az * bx - ax * bz; + this.z = ax * by - ay * bx; + return this; + } + }; + var Quaternion = function Quaternion(x, y, z, w) { + this.x = x || 0; + this.y = y || 0; + this.z = z || 0; + this.w = w !== undefined ? w : 1; + }; + Quaternion.prototype = { + constructor: Quaternion, + set: function set(x, y, z, w) { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + return this; + }, + copy: function copy(quaternion) { + this.x = quaternion.x; + this.y = quaternion.y; + this.z = quaternion.z; + this.w = quaternion.w; + return this; + }, + setFromEulerXYZ: function setFromEulerXYZ(x, y, z) { + var c1 = Math.cos(x / 2); + var c2 = Math.cos(y / 2); + var c3 = Math.cos(z / 2); + var s1 = Math.sin(x / 2); + var s2 = Math.sin(y / 2); + var s3 = Math.sin(z / 2); + this.x = s1 * c2 * c3 + c1 * s2 * s3; + this.y = c1 * s2 * c3 - s1 * c2 * s3; + this.z = c1 * c2 * s3 + s1 * s2 * c3; + this.w = c1 * c2 * c3 - s1 * s2 * s3; + return this; + }, + setFromEulerYXZ: function setFromEulerYXZ(x, y, z) { + var c1 = Math.cos(x / 2); + var c2 = Math.cos(y / 2); + var c3 = Math.cos(z / 2); + var s1 = Math.sin(x / 2); + var s2 = Math.sin(y / 2); + var s3 = Math.sin(z / 2); + this.x = s1 * c2 * c3 + c1 * s2 * s3; + this.y = c1 * s2 * c3 - s1 * c2 * s3; + this.z = c1 * c2 * s3 - s1 * s2 * c3; + this.w = c1 * c2 * c3 + s1 * s2 * s3; + return this; + }, + setFromAxisAngle: function setFromAxisAngle(axis, angle) { + var halfAngle = angle / 2, + s = Math.sin(halfAngle); + this.x = axis.x * s; + this.y = axis.y * s; + this.z = axis.z * s; + this.w = Math.cos(halfAngle); + return this; + }, + multiply: function multiply(q) { + return this.multiplyQuaternions(this, q); + }, + multiplyQuaternions: function multiplyQuaternions(a, b) { + var qax = a.x, + qay = a.y, + qaz = a.z, + qaw = a.w; + var qbx = b.x, + qby = b.y, + qbz = b.z, + qbw = b.w; + this.x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby; + this.y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz; + this.z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx; + this.w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz; + return this; + }, + inverse: function inverse() { + this.x *= -1; + this.y *= -1; + this.z *= -1; + this.normalize(); + return this; + }, + normalize: function normalize() { + var l = Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w); + if (l === 0) { + this.x = 0; + this.y = 0; + this.z = 0; + this.w = 1; + } else { + l = 1 / l; + this.x = this.x * l; + this.y = this.y * l; + this.z = this.z * l; + this.w = this.w * l; + } + return this; + }, + slerp: function slerp(qb, t) { + if (t === 0) return this; + if (t === 1) return this.copy(qb); + var x = this.x, + y = this.y, + z = this.z, + w = this.w; + var cosHalfTheta = w * qb.w + x * qb.x + y * qb.y + z * qb.z; + if (cosHalfTheta < 0) { + this.w = -qb.w; + this.x = -qb.x; + this.y = -qb.y; + this.z = -qb.z; + cosHalfTheta = -cosHalfTheta; + } else { + this.copy(qb); + } + if (cosHalfTheta >= 1.0) { + this.w = w; + this.x = x; + this.y = y; + this.z = z; + return this; + } + var halfTheta = Math.acos(cosHalfTheta); + var sinHalfTheta = Math.sqrt(1.0 - cosHalfTheta * cosHalfTheta); + if (Math.abs(sinHalfTheta) < 0.001) { + this.w = 0.5 * (w + this.w); + this.x = 0.5 * (x + this.x); + this.y = 0.5 * (y + this.y); + this.z = 0.5 * (z + this.z); + return this; + } + var ratioA = Math.sin((1 - t) * halfTheta) / sinHalfTheta, + ratioB = Math.sin(t * halfTheta) / sinHalfTheta; + this.w = w * ratioA + this.w * ratioB; + this.x = x * ratioA + this.x * ratioB; + this.y = y * ratioA + this.y * ratioB; + this.z = z * ratioA + this.z * ratioB; + return this; + }, + setFromUnitVectors: function () { + var v1, r; + var EPS = 0.000001; + return function (vFrom, vTo) { + if (v1 === undefined) v1 = new Vector3(); + r = vFrom.dot(vTo) + 1; + if (r < EPS) { + r = 0; + if (Math.abs(vFrom.x) > Math.abs(vFrom.z)) { + v1.set(-vFrom.y, vFrom.x, 0); + } else { + v1.set(0, -vFrom.z, vFrom.y); + } + } else { + v1.crossVectors(vFrom, vTo); + } + this.x = v1.x; + this.y = v1.y; + this.z = v1.z; + this.w = r; + this.normalize(); + return this; + }; + }() + }; + function Device(params) { + this.width = params.width || getScreenWidth(); + this.height = params.height || getScreenHeight(); + this.widthMeters = params.widthMeters; + this.heightMeters = params.heightMeters; + this.bevelMeters = params.bevelMeters; + } + var DEFAULT_ANDROID = new Device({ + widthMeters: 0.110, + heightMeters: 0.062, + bevelMeters: 0.004 + }); + var DEFAULT_IOS = new Device({ + widthMeters: 0.1038, + heightMeters: 0.0584, + bevelMeters: 0.004 + }); + var Viewers = { + CardboardV1: new CardboardViewer({ + id: 'CardboardV1', + label: 'Cardboard I/O 2014', + fov: 40, + interLensDistance: 0.060, + baselineLensDistance: 0.035, + screenLensDistance: 0.042, + distortionCoefficients: [0.441, 0.156], + inverseCoefficients: [-0.4410035, 0.42756155, -0.4804439, 0.5460139, -0.58821183, 0.5733938, -0.48303202, 0.33299083, -0.17573841, 0.0651772, -0.01488963, 0.001559834] + }), + CardboardV2: new CardboardViewer({ + id: 'CardboardV2', + label: 'Cardboard I/O 2015', + fov: 60, + interLensDistance: 0.064, + baselineLensDistance: 0.035, + screenLensDistance: 0.039, + distortionCoefficients: [0.34, 0.55], + inverseCoefficients: [-0.33836704, -0.18162185, 0.862655, -1.2462051, 1.0560602, -0.58208317, 0.21609078, -0.05444823, 0.009177956, -9.904169E-4, 6.183535E-5, -1.6981803E-6] + }) + }; + function DeviceInfo(deviceParams, additionalViewers) { + this.viewer = Viewers.CardboardV2; + this.updateDeviceParams(deviceParams); + this.distortion = new Distortion(this.viewer.distortionCoefficients); + for (var i = 0; i < additionalViewers.length; i++) { + var viewer = additionalViewers[i]; + Viewers[viewer.id] = new CardboardViewer(viewer); + } + } + DeviceInfo.prototype.updateDeviceParams = function (deviceParams) { + this.device = this.determineDevice_(deviceParams) || this.device; + }; + DeviceInfo.prototype.getDevice = function () { + return this.device; + }; + DeviceInfo.prototype.setViewer = function (viewer) { + this.viewer = viewer; + this.distortion = new Distortion(this.viewer.distortionCoefficients); + }; + DeviceInfo.prototype.determineDevice_ = function (deviceParams) { + if (!deviceParams) { + if (isIOS()) { + console.warn('Using fallback iOS device measurements.'); + return DEFAULT_IOS; + } else { + console.warn('Using fallback Android device measurements.'); + return DEFAULT_ANDROID; + } + } + var METERS_PER_INCH = 0.0254; + var metersPerPixelX = METERS_PER_INCH / deviceParams.xdpi; + var metersPerPixelY = METERS_PER_INCH / deviceParams.ydpi; + var width = getScreenWidth(); + var height = getScreenHeight(); + return new Device({ + widthMeters: metersPerPixelX * width, + heightMeters: metersPerPixelY * height, + bevelMeters: deviceParams.bevelMm * 0.001 + }); + }; + DeviceInfo.prototype.getDistortedFieldOfViewLeftEye = function () { + var viewer = this.viewer; + var device = this.device; + var distortion = this.distortion; + var eyeToScreenDistance = viewer.screenLensDistance; + var outerDist = (device.widthMeters - viewer.interLensDistance) / 2; + var innerDist = viewer.interLensDistance / 2; + var bottomDist = viewer.baselineLensDistance - device.bevelMeters; + var topDist = device.heightMeters - bottomDist; + var outerAngle = radToDeg * Math.atan(distortion.distort(outerDist / eyeToScreenDistance)); + var innerAngle = radToDeg * Math.atan(distortion.distort(innerDist / eyeToScreenDistance)); + var bottomAngle = radToDeg * Math.atan(distortion.distort(bottomDist / eyeToScreenDistance)); + var topAngle = radToDeg * Math.atan(distortion.distort(topDist / eyeToScreenDistance)); + return { + leftDegrees: Math.min(outerAngle, viewer.fov), + rightDegrees: Math.min(innerAngle, viewer.fov), + downDegrees: Math.min(bottomAngle, viewer.fov), + upDegrees: Math.min(topAngle, viewer.fov) + }; + }; + DeviceInfo.prototype.getLeftEyeVisibleTanAngles = function () { + var viewer = this.viewer; + var device = this.device; + var distortion = this.distortion; + var fovLeft = Math.tan(-degToRad * viewer.fov); + var fovTop = Math.tan(degToRad * viewer.fov); + var fovRight = Math.tan(degToRad * viewer.fov); + var fovBottom = Math.tan(-degToRad * viewer.fov); + var halfWidth = device.widthMeters / 4; + var halfHeight = device.heightMeters / 2; + var verticalLensOffset = viewer.baselineLensDistance - device.bevelMeters - halfHeight; + var centerX = viewer.interLensDistance / 2 - halfWidth; + var centerY = -verticalLensOffset; + var centerZ = viewer.screenLensDistance; + var screenLeft = distortion.distort((centerX - halfWidth) / centerZ); + var screenTop = distortion.distort((centerY + halfHeight) / centerZ); + var screenRight = distortion.distort((centerX + halfWidth) / centerZ); + var screenBottom = distortion.distort((centerY - halfHeight) / centerZ); + var result = new Float32Array(4); + result[0] = Math.max(fovLeft, screenLeft); + result[1] = Math.min(fovTop, screenTop); + result[2] = Math.min(fovRight, screenRight); + result[3] = Math.max(fovBottom, screenBottom); + return result; + }; + DeviceInfo.prototype.getLeftEyeNoLensTanAngles = function () { + var viewer = this.viewer; + var device = this.device; + var distortion = this.distortion; + var result = new Float32Array(4); + var fovLeft = distortion.distortInverse(Math.tan(-degToRad * viewer.fov)); + var fovTop = distortion.distortInverse(Math.tan(degToRad * viewer.fov)); + var fovRight = distortion.distortInverse(Math.tan(degToRad * viewer.fov)); + var fovBottom = distortion.distortInverse(Math.tan(-degToRad * viewer.fov)); + var halfWidth = device.widthMeters / 4; + var halfHeight = device.heightMeters / 2; + var verticalLensOffset = viewer.baselineLensDistance - device.bevelMeters - halfHeight; + var centerX = viewer.interLensDistance / 2 - halfWidth; + var centerY = -verticalLensOffset; + var centerZ = viewer.screenLensDistance; + var screenLeft = (centerX - halfWidth) / centerZ; + var screenTop = (centerY + halfHeight) / centerZ; + var screenRight = (centerX + halfWidth) / centerZ; + var screenBottom = (centerY - halfHeight) / centerZ; + result[0] = Math.max(fovLeft, screenLeft); + result[1] = Math.min(fovTop, screenTop); + result[2] = Math.min(fovRight, screenRight); + result[3] = Math.max(fovBottom, screenBottom); + return result; + }; + DeviceInfo.prototype.getLeftEyeVisibleScreenRect = function (undistortedFrustum) { + var viewer = this.viewer; + var device = this.device; + var dist = viewer.screenLensDistance; + var eyeX = (device.widthMeters - viewer.interLensDistance) / 2; + var eyeY = viewer.baselineLensDistance - device.bevelMeters; + var left = (undistortedFrustum[0] * dist + eyeX) / device.widthMeters; + var top = (undistortedFrustum[1] * dist + eyeY) / device.heightMeters; + var right = (undistortedFrustum[2] * dist + eyeX) / device.widthMeters; + var bottom = (undistortedFrustum[3] * dist + eyeY) / device.heightMeters; + return { + x: left, + y: bottom, + width: right - left, + height: top - bottom + }; + }; + DeviceInfo.prototype.getFieldOfViewLeftEye = function (opt_isUndistorted) { + return opt_isUndistorted ? this.getUndistortedFieldOfViewLeftEye() : this.getDistortedFieldOfViewLeftEye(); + }; + DeviceInfo.prototype.getFieldOfViewRightEye = function (opt_isUndistorted) { + var fov = this.getFieldOfViewLeftEye(opt_isUndistorted); + return { + leftDegrees: fov.rightDegrees, + rightDegrees: fov.leftDegrees, + upDegrees: fov.upDegrees, + downDegrees: fov.downDegrees + }; + }; + DeviceInfo.prototype.getUndistortedFieldOfViewLeftEye = function () { + var p = this.getUndistortedParams_(); + return { + leftDegrees: radToDeg * Math.atan(p.outerDist), + rightDegrees: radToDeg * Math.atan(p.innerDist), + downDegrees: radToDeg * Math.atan(p.bottomDist), + upDegrees: radToDeg * Math.atan(p.topDist) + }; + }; + DeviceInfo.prototype.getUndistortedViewportLeftEye = function () { + var p = this.getUndistortedParams_(); + var viewer = this.viewer; + var device = this.device; + var eyeToScreenDistance = viewer.screenLensDistance; + var screenWidth = device.widthMeters / eyeToScreenDistance; + var screenHeight = device.heightMeters / eyeToScreenDistance; + var xPxPerTanAngle = device.width / screenWidth; + var yPxPerTanAngle = device.height / screenHeight; + var x = Math.round((p.eyePosX - p.outerDist) * xPxPerTanAngle); + var y = Math.round((p.eyePosY - p.bottomDist) * yPxPerTanAngle); + return { + x: x, + y: y, + width: Math.round((p.eyePosX + p.innerDist) * xPxPerTanAngle) - x, + height: Math.round((p.eyePosY + p.topDist) * yPxPerTanAngle) - y + }; + }; + DeviceInfo.prototype.getUndistortedParams_ = function () { + var viewer = this.viewer; + var device = this.device; + var distortion = this.distortion; + var eyeToScreenDistance = viewer.screenLensDistance; + var halfLensDistance = viewer.interLensDistance / 2 / eyeToScreenDistance; + var screenWidth = device.widthMeters / eyeToScreenDistance; + var screenHeight = device.heightMeters / eyeToScreenDistance; + var eyePosX = screenWidth / 2 - halfLensDistance; + var eyePosY = (viewer.baselineLensDistance - device.bevelMeters) / eyeToScreenDistance; + var maxFov = viewer.fov; + var viewerMax = distortion.distortInverse(Math.tan(degToRad * maxFov)); + var outerDist = Math.min(eyePosX, viewerMax); + var innerDist = Math.min(halfLensDistance, viewerMax); + var bottomDist = Math.min(eyePosY, viewerMax); + var topDist = Math.min(screenHeight - eyePosY, viewerMax); + return { + outerDist: outerDist, + innerDist: innerDist, + topDist: topDist, + bottomDist: bottomDist, + eyePosX: eyePosX, + eyePosY: eyePosY + }; + }; + function CardboardViewer(params) { + this.id = params.id; + this.label = params.label; + this.fov = params.fov; + this.interLensDistance = params.interLensDistance; + this.baselineLensDistance = params.baselineLensDistance; + this.screenLensDistance = params.screenLensDistance; + this.distortionCoefficients = params.distortionCoefficients; + this.inverseCoefficients = params.inverseCoefficients; + } + DeviceInfo.Viewers = Viewers; + var format = 1; + var last_updated = "2019-11-09T17:36:14Z"; + var devices = [{ + "type": "android", + "rules": [{ + "mdmh": "asus/*/Nexus 7/*" + }, { + "ua": "Nexus 7" + }], + "dpi": [320.8, 323], + "bw": 3, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "asus/*/ASUS_X00PD/*" + }, { + "ua": "ASUS_X00PD" + }], + "dpi": 245, + "bw": 3, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "asus/*/ASUS_X008D/*" + }, { + "ua": "ASUS_X008D" + }], + "dpi": 282, + "bw": 3, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "asus/*/ASUS_Z00AD/*" + }, { + "ua": "ASUS_Z00AD" + }], + "dpi": [403, 404.6], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "Google/*/Pixel 2 XL/*" + }, { + "ua": "Pixel 2 XL" + }], + "dpi": 537.9, + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "Google/*/Pixel 3 XL/*" + }, { + "ua": "Pixel 3 XL" + }], + "dpi": [558.5, 553.8], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "Google/*/Pixel XL/*" + }, { + "ua": "Pixel XL" + }], + "dpi": [537.9, 533], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "Google/*/Pixel 3/*" + }, { + "ua": "Pixel 3" + }], + "dpi": 442.4, + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "Google/*/Pixel 2/*" + }, { + "ua": "Pixel 2" + }], + "dpi": 441, + "bw": 3, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "Google/*/Pixel/*" + }, { + "ua": "Pixel" + }], + "dpi": [432.6, 436.7], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "HTC/*/HTC6435LVW/*" + }, { + "ua": "HTC6435LVW" + }], + "dpi": [449.7, 443.3], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "HTC/*/HTC One XL/*" + }, { + "ua": "HTC One XL" + }], + "dpi": [315.3, 314.6], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "htc/*/Nexus 9/*" + }, { + "ua": "Nexus 9" + }], + "dpi": 289, + "bw": 3, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "HTC/*/HTC One M9/*" + }, { + "ua": "HTC One M9" + }], + "dpi": [442.5, 443.3], + "bw": 3, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "HTC/*/HTC One_M8/*" + }, { + "ua": "HTC One_M8" + }], + "dpi": [449.7, 447.4], + "bw": 3, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "HTC/*/HTC One/*" + }, { + "ua": "HTC One" + }], + "dpi": 472.8, + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "Huawei/*/Nexus 6P/*" + }, { + "ua": "Nexus 6P" + }], + "dpi": [515.1, 518], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "Huawei/*/BLN-L24/*" + }, { + "ua": "HONORBLN-L24" + }], + "dpi": 480, + "bw": 4, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "Huawei/*/BKL-L09/*" + }, { + "ua": "BKL-L09" + }], + "dpi": 403, + "bw": 3.47, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "LENOVO/*/Lenovo PB2-690Y/*" + }, { + "ua": "Lenovo PB2-690Y" + }], + "dpi": [457.2, 454.713], + "bw": 3, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "LGE/*/Nexus 5X/*" + }, { + "ua": "Nexus 5X" + }], + "dpi": [422, 419.9], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "LGE/*/LGMS345/*" + }, { + "ua": "LGMS345" + }], + "dpi": [221.7, 219.1], + "bw": 3, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "LGE/*/LG-D800/*" + }, { + "ua": "LG-D800" + }], + "dpi": [422, 424.1], + "bw": 3, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "LGE/*/LG-D850/*" + }, { + "ua": "LG-D850" + }], + "dpi": [537.9, 541.9], + "bw": 3, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "LGE/*/VS985 4G/*" + }, { + "ua": "VS985 4G" + }], + "dpi": [537.9, 535.6], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "LGE/*/Nexus 5/*" + }, { + "ua": "Nexus 5 B" + }], + "dpi": [442.4, 444.8], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "LGE/*/Nexus 4/*" + }, { + "ua": "Nexus 4" + }], + "dpi": [319.8, 318.4], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "LGE/*/LG-P769/*" + }, { + "ua": "LG-P769" + }], + "dpi": [240.6, 247.5], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "LGE/*/LGMS323/*" + }, { + "ua": "LGMS323" + }], + "dpi": [206.6, 204.6], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "LGE/*/LGLS996/*" + }, { + "ua": "LGLS996" + }], + "dpi": [403.4, 401.5], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "Micromax/*/4560MMX/*" + }, { + "ua": "4560MMX" + }], + "dpi": [240, 219.4], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "Micromax/*/A250/*" + }, { + "ua": "Micromax A250" + }], + "dpi": [480, 446.4], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "Micromax/*/Micromax AQ4501/*" + }, { + "ua": "Micromax AQ4501" + }], + "dpi": 240, + "bw": 3, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "motorola/*/G5/*" + }, { + "ua": "Moto G (5) Plus" + }], + "dpi": [403.4, 403], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "motorola/*/DROID RAZR/*" + }, { + "ua": "DROID RAZR" + }], + "dpi": [368.1, 256.7], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "motorola/*/XT830C/*" + }, { + "ua": "XT830C" + }], + "dpi": [254, 255.9], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "motorola/*/XT1021/*" + }, { + "ua": "XT1021" + }], + "dpi": [254, 256.7], + "bw": 3, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "motorola/*/XT1023/*" + }, { + "ua": "XT1023" + }], + "dpi": [254, 256.7], + "bw": 3, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "motorola/*/XT1028/*" + }, { + "ua": "XT1028" + }], + "dpi": [326.6, 327.6], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "motorola/*/XT1034/*" + }, { + "ua": "XT1034" + }], + "dpi": [326.6, 328.4], + "bw": 3, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "motorola/*/XT1053/*" + }, { + "ua": "XT1053" + }], + "dpi": [315.3, 316.1], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "motorola/*/XT1562/*" + }, { + "ua": "XT1562" + }], + "dpi": [403.4, 402.7], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "motorola/*/Nexus 6/*" + }, { + "ua": "Nexus 6 B" + }], + "dpi": [494.3, 489.7], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "motorola/*/XT1063/*" + }, { + "ua": "XT1063" + }], + "dpi": [295, 296.6], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "motorola/*/XT1064/*" + }, { + "ua": "XT1064" + }], + "dpi": [295, 295.6], + "bw": 3, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "motorola/*/XT1092/*" + }, { + "ua": "XT1092" + }], + "dpi": [422, 424.1], + "bw": 3, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "motorola/*/XT1095/*" + }, { + "ua": "XT1095" + }], + "dpi": [422, 423.4], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "motorola/*/G4/*" + }, { + "ua": "Moto G (4)" + }], + "dpi": 401, + "bw": 4, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "OnePlus/*/A0001/*" + }, { + "ua": "A0001" + }], + "dpi": [403.4, 401], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "OnePlus/*/ONE E1001/*" + }, { + "ua": "ONE E1001" + }], + "dpi": [442.4, 441.4], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "OnePlus/*/ONE E1003/*" + }, { + "ua": "ONE E1003" + }], + "dpi": [442.4, 441.4], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "OnePlus/*/ONE E1005/*" + }, { + "ua": "ONE E1005" + }], + "dpi": [442.4, 441.4], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "OnePlus/*/ONE A2001/*" + }, { + "ua": "ONE A2001" + }], + "dpi": [391.9, 405.4], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "OnePlus/*/ONE A2003/*" + }, { + "ua": "ONE A2003" + }], + "dpi": [391.9, 405.4], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "OnePlus/*/ONE A2005/*" + }, { + "ua": "ONE A2005" + }], + "dpi": [391.9, 405.4], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "OnePlus/*/ONEPLUS A3000/*" + }, { + "ua": "ONEPLUS A3000" + }], + "dpi": 401, + "bw": 3, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "OnePlus/*/ONEPLUS A3003/*" + }, { + "ua": "ONEPLUS A3003" + }], + "dpi": 401, + "bw": 3, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "OnePlus/*/ONEPLUS A3010/*" + }, { + "ua": "ONEPLUS A3010" + }], + "dpi": 401, + "bw": 3, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "OnePlus/*/ONEPLUS A5000/*" + }, { + "ua": "ONEPLUS A5000 " + }], + "dpi": [403.411, 399.737], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "OnePlus/*/ONE A5010/*" + }, { + "ua": "ONEPLUS A5010" + }], + "dpi": [403, 400], + "bw": 2, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "OnePlus/*/ONEPLUS A6000/*" + }, { + "ua": "ONEPLUS A6000" + }], + "dpi": 401, + "bw": 3, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "OnePlus/*/ONEPLUS A6003/*" + }, { + "ua": "ONEPLUS A6003" + }], + "dpi": 401, + "bw": 3, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "OnePlus/*/ONEPLUS A6010/*" + }, { + "ua": "ONEPLUS A6010" + }], + "dpi": 401, + "bw": 2, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "OnePlus/*/ONEPLUS A6013/*" + }, { + "ua": "ONEPLUS A6013" + }], + "dpi": 401, + "bw": 2, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "OPPO/*/X909/*" + }, { + "ua": "X909" + }], + "dpi": [442.4, 444.1], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/GT-I9082/*" + }, { + "ua": "GT-I9082" + }], + "dpi": [184.7, 185.4], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/SM-G360P/*" + }, { + "ua": "SM-G360P" + }], + "dpi": [196.7, 205.4], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/Nexus S/*" + }, { + "ua": "Nexus S" + }], + "dpi": [234.5, 229.8], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/GT-I9300/*" + }, { + "ua": "GT-I9300" + }], + "dpi": [304.8, 303.9], + "bw": 5, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/SM-T230NU/*" + }, { + "ua": "SM-T230NU" + }], + "dpi": 216, + "bw": 3, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/SGH-T399/*" + }, { + "ua": "SGH-T399" + }], + "dpi": [217.7, 231.4], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/SGH-M919/*" + }, { + "ua": "SGH-M919" + }], + "dpi": [440.8, 437.7], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/SM-N9005/*" + }, { + "ua": "SM-N9005" + }], + "dpi": [386.4, 387], + "bw": 3, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/SAMSUNG-SM-N900A/*" + }, { + "ua": "SAMSUNG-SM-N900A" + }], + "dpi": [386.4, 387.7], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/GT-I9500/*" + }, { + "ua": "GT-I9500" + }], + "dpi": [442.5, 443.3], + "bw": 3, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/GT-I9505/*" + }, { + "ua": "GT-I9505" + }], + "dpi": 439.4, + "bw": 4, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/SM-G900F/*" + }, { + "ua": "SM-G900F" + }], + "dpi": [415.6, 431.6], + "bw": 5, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/SM-G900M/*" + }, { + "ua": "SM-G900M" + }], + "dpi": [415.6, 431.6], + "bw": 5, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/SM-G800F/*" + }, { + "ua": "SM-G800F" + }], + "dpi": 326.8, + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/SM-G906S/*" + }, { + "ua": "SM-G906S" + }], + "dpi": [562.7, 572.4], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/GT-I9300/*" + }, { + "ua": "GT-I9300" + }], + "dpi": [306.7, 304.8], + "bw": 5, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/SM-T535/*" + }, { + "ua": "SM-T535" + }], + "dpi": [142.6, 136.4], + "bw": 3, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/SM-N920C/*" + }, { + "ua": "SM-N920C" + }], + "dpi": [515.1, 518.4], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/SM-N920P/*" + }, { + "ua": "SM-N920P" + }], + "dpi": [386.3655, 390.144], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/SM-N920W8/*" + }, { + "ua": "SM-N920W8" + }], + "dpi": [515.1, 518.4], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/GT-I9300I/*" + }, { + "ua": "GT-I9300I" + }], + "dpi": [304.8, 305.8], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/GT-I9195/*" + }, { + "ua": "GT-I9195" + }], + "dpi": [249.4, 256.7], + "bw": 3, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/SPH-L520/*" + }, { + "ua": "SPH-L520" + }], + "dpi": [249.4, 255.9], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/SAMSUNG-SGH-I717/*" + }, { + "ua": "SAMSUNG-SGH-I717" + }], + "dpi": 285.8, + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/SPH-D710/*" + }, { + "ua": "SPH-D710" + }], + "dpi": [217.7, 204.2], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/GT-N7100/*" + }, { + "ua": "GT-N7100" + }], + "dpi": 265.1, + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/SCH-I605/*" + }, { + "ua": "SCH-I605" + }], + "dpi": 265.1, + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/Galaxy Nexus/*" + }, { + "ua": "Galaxy Nexus" + }], + "dpi": [315.3, 314.2], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/SM-N910H/*" + }, { + "ua": "SM-N910H" + }], + "dpi": [515.1, 518], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/SM-N910C/*" + }, { + "ua": "SM-N910C" + }], + "dpi": [515.2, 520.2], + "bw": 3, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/SM-G130M/*" + }, { + "ua": "SM-G130M" + }], + "dpi": [165.9, 164.8], + "bw": 3, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/SM-G928I/*" + }, { + "ua": "SM-G928I" + }], + "dpi": [515.1, 518.4], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/SM-G920F/*" + }, { + "ua": "SM-G920F" + }], + "dpi": 580.6, + "bw": 3, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/SM-G920P/*" + }, { + "ua": "SM-G920P" + }], + "dpi": [522.5, 577], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/SM-G925F/*" + }, { + "ua": "SM-G925F" + }], + "dpi": 580.6, + "bw": 3, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/SM-G925V/*" + }, { + "ua": "SM-G925V" + }], + "dpi": [522.5, 576.6], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/SM-G930F/*" + }, { + "ua": "SM-G930F" + }], + "dpi": 576.6, + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/SM-G935F/*" + }, { + "ua": "SM-G935F" + }], + "dpi": 533, + "bw": 3, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/SM-G950F/*" + }, { + "ua": "SM-G950F" + }], + "dpi": [562.707, 565.293], + "bw": 3, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/SM-G955U/*" + }, { + "ua": "SM-G955U" + }], + "dpi": [522.514, 525.762], + "bw": 3, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/SM-G955F/*" + }, { + "ua": "SM-G955F" + }], + "dpi": [522.514, 525.762], + "bw": 3, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/SM-G960F/*" + }, { + "ua": "SM-G960F" + }], + "dpi": [569.575, 571.5], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/SM-G9600/*" + }, { + "ua": "SM-G9600" + }], + "dpi": [569.575, 571.5], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/SM-G960T/*" + }, { + "ua": "SM-G960T" + }], + "dpi": [569.575, 571.5], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/SM-G960N/*" + }, { + "ua": "SM-G960N" + }], + "dpi": [569.575, 571.5], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/SM-G960U/*" + }, { + "ua": "SM-G960U" + }], + "dpi": [569.575, 571.5], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/SM-G9608/*" + }, { + "ua": "SM-G9608" + }], + "dpi": [569.575, 571.5], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/SM-G960FD/*" + }, { + "ua": "SM-G960FD" + }], + "dpi": [569.575, 571.5], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/SM-G960W/*" + }, { + "ua": "SM-G960W" + }], + "dpi": [569.575, 571.5], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/SM-G965F/*" + }, { + "ua": "SM-G965F" + }], + "dpi": 529, + "bw": 2, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "Sony/*/C6903/*" + }, { + "ua": "C6903" + }], + "dpi": [442.5, 443.3], + "bw": 3, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "Sony/*/D6653/*" + }, { + "ua": "D6653" + }], + "dpi": [428.6, 427.6], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "Sony/*/E6653/*" + }, { + "ua": "E6653" + }], + "dpi": [428.6, 425.7], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "Sony/*/E6853/*" + }, { + "ua": "E6853" + }], + "dpi": [403.4, 401.9], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "Sony/*/SGP321/*" + }, { + "ua": "SGP321" + }], + "dpi": [224.7, 224.1], + "bw": 3, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "TCT/*/ALCATEL ONE TOUCH Fierce/*" + }, { + "ua": "ALCATEL ONE TOUCH Fierce" + }], + "dpi": [240, 247.5], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "THL/*/thl 5000/*" + }, { + "ua": "thl 5000" + }], + "dpi": [480, 443.3], + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "Fly/*/IQ4412/*" + }, { + "ua": "IQ4412" + }], + "dpi": 307.9, + "bw": 3, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "ZTE/*/ZTE Blade L2/*" + }, { + "ua": "ZTE Blade L2" + }], + "dpi": 240, + "bw": 3, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "BENEVE/*/VR518/*" + }, { + "ua": "VR518" + }], + "dpi": 480, + "bw": 3, + "ac": 500 + }, { + "type": "ios", + "rules": [{ + "res": [640, 960] + }], + "dpi": [325.1, 328.4], + "bw": 4, + "ac": 1000 + }, { + "type": "ios", + "rules": [{ + "res": [640, 1136] + }], + "dpi": [317.1, 320.2], + "bw": 3, + "ac": 1000 + }, { + "type": "ios", + "rules": [{ + "res": [750, 1334] + }], + "dpi": 326.4, + "bw": 4, + "ac": 1000 + }, { + "type": "ios", + "rules": [{ + "res": [1242, 2208] + }], + "dpi": [453.6, 458.4], + "bw": 4, + "ac": 1000 + }, { + "type": "ios", + "rules": [{ + "res": [1125, 2001] + }], + "dpi": [410.9, 415.4], + "bw": 4, + "ac": 1000 + }, { + "type": "ios", + "rules": [{ + "res": [1125, 2436] + }], + "dpi": 458, + "bw": 4, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "Huawei/*/EML-L29/*" + }, { + "ua": "EML-L29" + }], + "dpi": 428, + "bw": 3.45, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "Nokia/*/Nokia 7.1/*" + }, { + "ua": "Nokia 7.1" + }], + "dpi": [432, 431.9], + "bw": 3, + "ac": 500 + }, { + "type": "ios", + "rules": [{ + "res": [1242, 2688] + }], + "dpi": 458, + "bw": 4, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/SM-G570M/*" + }, { + "ua": "SM-G570M" + }], + "dpi": 320, + "bw": 3.684, + "ac": 1000 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/SM-G970F/*" + }, { + "ua": "SM-G970F" + }], + "dpi": 438, + "bw": 2.281, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/SM-G973F/*" + }, { + "ua": "SM-G973F" + }], + "dpi": 550, + "bw": 2.002, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/SM-G975F/*" + }, { + "ua": "SM-G975F" + }], + "dpi": 522, + "bw": 2.054, + "ac": 500 + }, { + "type": "android", + "rules": [{ + "mdmh": "samsung/*/SM-G977F/*" + }, { + "ua": "SM-G977F" + }], + "dpi": 505, + "bw": 2.334, + "ac": 500 + }, { + "type": "ios", + "rules": [{ + "res": [828, 1792] + }], + "dpi": 326, + "bw": 5, + "ac": 500 + }]; + var DPDB_CACHE = { + format: format, + last_updated: last_updated, + devices: devices + }; + function Dpdb(url, onDeviceParamsUpdated) { + this.dpdb = DPDB_CACHE; + this.recalculateDeviceParams_(); + if (url) { + this.onDeviceParamsUpdated = onDeviceParamsUpdated; + var xhr = new XMLHttpRequest(); + var obj = this; + xhr.open('GET', url, true); + xhr.addEventListener('load', function () { + obj.loading = false; + if (xhr.status >= 200 && xhr.status <= 299) { + obj.dpdb = JSON.parse(xhr.response); + obj.recalculateDeviceParams_(); + } else { + console.error('Error loading online DPDB!'); + } + }); + xhr.send(); + } + } + Dpdb.prototype.getDeviceParams = function () { + return this.deviceParams; + }; + Dpdb.prototype.recalculateDeviceParams_ = function () { + var newDeviceParams = this.calcDeviceParams_(); + if (newDeviceParams) { + this.deviceParams = newDeviceParams; + if (this.onDeviceParamsUpdated) { + this.onDeviceParamsUpdated(this.deviceParams); + } + } else { + console.error('Failed to recalculate device parameters.'); + } + }; + Dpdb.prototype.calcDeviceParams_ = function () { + var db = this.dpdb; + if (!db) { + console.error('DPDB not available.'); + return null; + } + if (db.format != 1) { + console.error('DPDB has unexpected format version.'); + return null; + } + if (!db.devices || !db.devices.length) { + console.error('DPDB does not have a devices section.'); + return null; + } + var userAgent = navigator.userAgent || navigator.vendor || window.opera; + var width = getScreenWidth(); + var height = getScreenHeight(); + if (!db.devices) { + console.error('DPDB has no devices section.'); + return null; + } + for (var i = 0; i < db.devices.length; i++) { + var device = db.devices[i]; + if (!device.rules) { + console.warn('Device[' + i + '] has no rules section.'); + continue; + } + if (device.type != 'ios' && device.type != 'android') { + console.warn('Device[' + i + '] has invalid type.'); + continue; + } + if (isIOS() != (device.type == 'ios')) continue; + var matched = false; + for (var j = 0; j < device.rules.length; j++) { + var rule = device.rules[j]; + if (this.ruleMatches_(rule, userAgent, width, height)) { + matched = true; + break; + } + } + if (!matched) continue; + var xdpi = device.dpi[0] || device.dpi; + var ydpi = device.dpi[1] || device.dpi; + return new DeviceParams({ + xdpi: xdpi, + ydpi: ydpi, + bevelMm: device.bw + }); + } + console.warn('No DPDB device match.'); + return null; + }; + Dpdb.prototype.ruleMatches_ = function (rule, ua, screenWidth, screenHeight) { + if (!rule.ua && !rule.res) return false; + if (rule.ua && rule.ua.substring(0, 2) === 'SM') rule.ua = rule.ua.substring(0, 7); + if (rule.ua && ua.indexOf(rule.ua) < 0) return false; + if (rule.res) { + if (!rule.res[0] || !rule.res[1]) return false; + var resX = rule.res[0]; + var resY = rule.res[1]; + if (Math.min(screenWidth, screenHeight) != Math.min(resX, resY) || Math.max(screenWidth, screenHeight) != Math.max(resX, resY)) { + return false; + } + } + return true; + }; + function DeviceParams(params) { + this.xdpi = params.xdpi; + this.ydpi = params.ydpi; + this.bevelMm = params.bevelMm; + } + function SensorSample(sample, timestampS) { + this.set(sample, timestampS); + } + SensorSample.prototype.set = function (sample, timestampS) { + this.sample = sample; + this.timestampS = timestampS; + }; + SensorSample.prototype.copy = function (sensorSample) { + this.set(sensorSample.sample, sensorSample.timestampS); + }; + function ComplementaryFilter(kFilter, isDebug) { + this.kFilter = kFilter; + this.isDebug = isDebug; + this.currentAccelMeasurement = new SensorSample(); + this.currentGyroMeasurement = new SensorSample(); + this.previousGyroMeasurement = new SensorSample(); + if (isIOS()) { + this.filterQ = new Quaternion(-1, 0, 0, 1); + } else { + this.filterQ = new Quaternion(1, 0, 0, 1); + } + this.previousFilterQ = new Quaternion(); + this.previousFilterQ.copy(this.filterQ); + this.accelQ = new Quaternion(); + this.isOrientationInitialized = false; + this.estimatedGravity = new Vector3(); + this.measuredGravity = new Vector3(); + this.gyroIntegralQ = new Quaternion(); + } + ComplementaryFilter.prototype.addAccelMeasurement = function (vector, timestampS) { + this.currentAccelMeasurement.set(vector, timestampS); + }; + ComplementaryFilter.prototype.addGyroMeasurement = function (vector, timestampS) { + this.currentGyroMeasurement.set(vector, timestampS); + var deltaT = timestampS - this.previousGyroMeasurement.timestampS; + if (isTimestampDeltaValid(deltaT)) { + this.run_(); + } + this.previousGyroMeasurement.copy(this.currentGyroMeasurement); + }; + ComplementaryFilter.prototype.run_ = function () { + if (!this.isOrientationInitialized) { + this.accelQ = this.accelToQuaternion_(this.currentAccelMeasurement.sample); + this.previousFilterQ.copy(this.accelQ); + this.isOrientationInitialized = true; + return; + } + var deltaT = this.currentGyroMeasurement.timestampS - this.previousGyroMeasurement.timestampS; + var gyroDeltaQ = this.gyroToQuaternionDelta_(this.currentGyroMeasurement.sample, deltaT); + this.gyroIntegralQ.multiply(gyroDeltaQ); + this.filterQ.copy(this.previousFilterQ); + this.filterQ.multiply(gyroDeltaQ); + var invFilterQ = new Quaternion(); + invFilterQ.copy(this.filterQ); + invFilterQ.inverse(); + this.estimatedGravity.set(0, 0, -1); + this.estimatedGravity.applyQuaternion(invFilterQ); + this.estimatedGravity.normalize(); + this.measuredGravity.copy(this.currentAccelMeasurement.sample); + this.measuredGravity.normalize(); + var deltaQ = new Quaternion(); + deltaQ.setFromUnitVectors(this.estimatedGravity, this.measuredGravity); + deltaQ.inverse(); + if (this.isDebug) { + console.log('Delta: %d deg, G_est: (%s, %s, %s), G_meas: (%s, %s, %s)', radToDeg * getQuaternionAngle(deltaQ), this.estimatedGravity.x.toFixed(1), this.estimatedGravity.y.toFixed(1), this.estimatedGravity.z.toFixed(1), this.measuredGravity.x.toFixed(1), this.measuredGravity.y.toFixed(1), this.measuredGravity.z.toFixed(1)); + } + var targetQ = new Quaternion(); + targetQ.copy(this.filterQ); + targetQ.multiply(deltaQ); + this.filterQ.slerp(targetQ, 1 - this.kFilter); + this.previousFilterQ.copy(this.filterQ); + }; + ComplementaryFilter.prototype.getOrientation = function () { + return this.filterQ; + }; + ComplementaryFilter.prototype.accelToQuaternion_ = function (accel) { + var normAccel = new Vector3(); + normAccel.copy(accel); + normAccel.normalize(); + var quat = new Quaternion(); + quat.setFromUnitVectors(new Vector3(0, 0, -1), normAccel); + quat.inverse(); + return quat; + }; + ComplementaryFilter.prototype.gyroToQuaternionDelta_ = function (gyro, dt) { + var quat = new Quaternion(); + var axis = new Vector3(); + axis.copy(gyro); + axis.normalize(); + quat.setFromAxisAngle(axis, gyro.length() * dt); + return quat; + }; + function PosePredictor(predictionTimeS, isDebug) { + this.predictionTimeS = predictionTimeS; + this.isDebug = isDebug; + this.previousQ = new Quaternion(); + this.previousTimestampS = null; + this.deltaQ = new Quaternion(); + this.outQ = new Quaternion(); + } + PosePredictor.prototype.getPrediction = function (currentQ, gyro, timestampS) { + if (!this.previousTimestampS) { + this.previousQ.copy(currentQ); + this.previousTimestampS = timestampS; + return currentQ; + } + var axis = new Vector3(); + axis.copy(gyro); + axis.normalize(); + var angularSpeed = gyro.length(); + if (angularSpeed < degToRad * 20) { + if (this.isDebug) { + console.log('Moving slowly, at %s deg/s: no prediction', (radToDeg * angularSpeed).toFixed(1)); + } + this.outQ.copy(currentQ); + this.previousQ.copy(currentQ); + return this.outQ; + } + var predictAngle = angularSpeed * this.predictionTimeS; + this.deltaQ.setFromAxisAngle(axis, predictAngle); + this.outQ.copy(this.previousQ); + this.outQ.multiply(this.deltaQ); + this.previousQ.copy(currentQ); + this.previousTimestampS = timestampS; + return this.outQ; + }; + function FusionPoseSensor(kFilter, predictionTime, yawOnly, isDebug) { + this.yawOnly = yawOnly; + this.accelerometer = new Vector3(); + this.gyroscope = new Vector3(); + this.filter = new ComplementaryFilter(kFilter, isDebug); + this.posePredictor = new PosePredictor(predictionTime, isDebug); + this.isFirefoxAndroid = isFirefoxAndroid(); + this.isIOS = isIOS(); + var chromeVersion = getChromeVersion(); + this.isDeviceMotionInRadians = !this.isIOS && chromeVersion && chromeVersion < 66; + this.isWithoutDeviceMotion = isChromeWithoutDeviceMotion() || isSafariWithoutDeviceMotion(); + this.filterToWorldQ = new Quaternion(); + if (isIOS()) { + this.filterToWorldQ.setFromAxisAngle(new Vector3(1, 0, 0), Math.PI / 2); + } else { + this.filterToWorldQ.setFromAxisAngle(new Vector3(1, 0, 0), -Math.PI / 2); + } + this.inverseWorldToScreenQ = new Quaternion(); + this.worldToScreenQ = new Quaternion(); + this.originalPoseAdjustQ = new Quaternion(); + this.originalPoseAdjustQ.setFromAxisAngle(new Vector3(0, 0, 1), -window.orientation * Math.PI / 180); + this.setScreenTransform_(); + if (isLandscapeMode()) { + this.filterToWorldQ.multiply(this.inverseWorldToScreenQ); + } + this.resetQ = new Quaternion(); + this.orientationOut_ = new Float32Array(4); + this.start(); + } + FusionPoseSensor.prototype.getPosition = function () { + return null; + }; + FusionPoseSensor.prototype.getOrientation = function () { + var orientation = void 0; + if (this.isWithoutDeviceMotion && this._deviceOrientationQ) { + this.deviceOrientationFixQ = this.deviceOrientationFixQ || function () { + var z = new Quaternion().setFromAxisAngle(new Vector3(0, 0, -1), 0); + var y = new Quaternion(); + if (window.orientation === -90) { + y.setFromAxisAngle(new Vector3(0, 1, 0), Math.PI / -2); + } else { + y.setFromAxisAngle(new Vector3(0, 1, 0), Math.PI / 2); + } + return z.multiply(y); + }(); + this.deviceOrientationFilterToWorldQ = this.deviceOrientationFilterToWorldQ || function () { + var q = new Quaternion(); + q.setFromAxisAngle(new Vector3(1, 0, 0), -Math.PI / 2); + return q; + }(); + orientation = this._deviceOrientationQ; + var out = new Quaternion(); + out.copy(orientation); + out.multiply(this.deviceOrientationFilterToWorldQ); + out.multiply(this.resetQ); + out.multiply(this.worldToScreenQ); + out.multiplyQuaternions(this.deviceOrientationFixQ, out); + if (this.yawOnly) { + out.x = 0; + out.z = 0; + out.normalize(); + } + this.orientationOut_[0] = out.x; + this.orientationOut_[1] = out.y; + this.orientationOut_[2] = out.z; + this.orientationOut_[3] = out.w; + return this.orientationOut_; + } else { + var filterOrientation = this.filter.getOrientation(); + orientation = this.posePredictor.getPrediction(filterOrientation, this.gyroscope, this.previousTimestampS); + } + var out = new Quaternion(); + out.copy(this.filterToWorldQ); + out.multiply(this.resetQ); + out.multiply(orientation); + out.multiply(this.worldToScreenQ); + if (this.yawOnly) { + out.x = 0; + out.z = 0; + out.normalize(); + } + this.orientationOut_[0] = out.x; + this.orientationOut_[1] = out.y; + this.orientationOut_[2] = out.z; + this.orientationOut_[3] = out.w; + return this.orientationOut_; + }; + FusionPoseSensor.prototype.resetPose = function () { + this.resetQ.copy(this.filter.getOrientation()); + this.resetQ.x = 0; + this.resetQ.y = 0; + this.resetQ.z *= -1; + this.resetQ.normalize(); + if (isLandscapeMode()) { + this.resetQ.multiply(this.inverseWorldToScreenQ); + } + this.resetQ.multiply(this.originalPoseAdjustQ); + }; + FusionPoseSensor.prototype.onDeviceOrientation_ = function (e) { + this._deviceOrientationQ = this._deviceOrientationQ || new Quaternion(); + var alpha = e.alpha, + beta = e.beta, + gamma = e.gamma; + alpha = (alpha || 0) * Math.PI / 180; + beta = (beta || 0) * Math.PI / 180; + gamma = (gamma || 0) * Math.PI / 180; + this._deviceOrientationQ.setFromEulerYXZ(beta, alpha, -gamma); + }; + FusionPoseSensor.prototype.onDeviceMotion_ = function (deviceMotion) { + this.updateDeviceMotion_(deviceMotion); + }; + FusionPoseSensor.prototype.updateDeviceMotion_ = function (deviceMotion) { + var accGravity = deviceMotion.accelerationIncludingGravity; + var rotRate = deviceMotion.rotationRate; + var timestampS = deviceMotion.timeStamp / 1000; + var deltaS = timestampS - this.previousTimestampS; + if (deltaS < 0) { + warnOnce('fusion-pose-sensor:invalid:non-monotonic', 'Invalid timestamps detected: non-monotonic timestamp from devicemotion'); + this.previousTimestampS = timestampS; + return; + } else if (deltaS <= MIN_TIMESTEP || deltaS > MAX_TIMESTEP) { + warnOnce('fusion-pose-sensor:invalid:outside-threshold', 'Invalid timestamps detected: Timestamp from devicemotion outside expected range.'); + this.previousTimestampS = timestampS; + return; + } + this.accelerometer.set(-accGravity.x, -accGravity.y, -accGravity.z); + if (rotRate) { + if (isR7()) { + this.gyroscope.set(-rotRate.beta, rotRate.alpha, rotRate.gamma); + } else { + this.gyroscope.set(rotRate.alpha, rotRate.beta, rotRate.gamma); + } + if (!this.isDeviceMotionInRadians) { + this.gyroscope.multiplyScalar(Math.PI / 180); + } + this.filter.addGyroMeasurement(this.gyroscope, timestampS); + } + this.filter.addAccelMeasurement(this.accelerometer, timestampS); + this.previousTimestampS = timestampS; + }; + FusionPoseSensor.prototype.onOrientationChange_ = function (screenOrientation) { + this.setScreenTransform_(); + }; + FusionPoseSensor.prototype.onMessage_ = function (event) { + var message = event.data; + if (!message || !message.type) { + return; + } + var type = message.type.toLowerCase(); + if (type !== 'devicemotion') { + return; + } + this.updateDeviceMotion_(message.deviceMotionEvent); + }; + FusionPoseSensor.prototype.setScreenTransform_ = function () { + this.worldToScreenQ.set(0, 0, 0, 1); + switch (window.orientation) { + case 0: + break; + case 90: + this.worldToScreenQ.setFromAxisAngle(new Vector3(0, 0, 1), -Math.PI / 2); + break; + case -90: + this.worldToScreenQ.setFromAxisAngle(new Vector3(0, 0, 1), Math.PI / 2); + break; + case 180: + break; + } + this.inverseWorldToScreenQ.copy(this.worldToScreenQ); + this.inverseWorldToScreenQ.inverse(); + }; + FusionPoseSensor.prototype.start = function () { + this.onDeviceMotionCallback_ = this.onDeviceMotion_.bind(this); + this.onOrientationChangeCallback_ = this.onOrientationChange_.bind(this); + this.onMessageCallback_ = this.onMessage_.bind(this); + this.onDeviceOrientationCallback_ = this.onDeviceOrientation_.bind(this); + if (isIOS() && isInsideCrossOriginIFrame()) { + window.addEventListener('message', this.onMessageCallback_); + } + window.addEventListener('orientationchange', this.onOrientationChangeCallback_); + if (this.isWithoutDeviceMotion) { + window.addEventListener('deviceorientation', this.onDeviceOrientationCallback_); + } else { + window.addEventListener('devicemotion', this.onDeviceMotionCallback_); + } + }; + FusionPoseSensor.prototype.stop = function () { + window.removeEventListener('devicemotion', this.onDeviceMotionCallback_); + window.removeEventListener('deviceorientation', this.onDeviceOrientationCallback_); + window.removeEventListener('orientationchange', this.onOrientationChangeCallback_); + window.removeEventListener('message', this.onMessageCallback_); + }; + var SENSOR_FREQUENCY = 60; + var X_AXIS = new Vector3(1, 0, 0); + var Z_AXIS = new Vector3(0, 0, 1); + var SENSOR_TO_VR = new Quaternion(); + SENSOR_TO_VR.setFromAxisAngle(X_AXIS, -Math.PI / 2); + SENSOR_TO_VR.multiply(new Quaternion().setFromAxisAngle(Z_AXIS, Math.PI / 2)); + var PoseSensor = function () { + function PoseSensor(config) { + classCallCheck(this, PoseSensor); + this.config = config; + this.sensor = null; + this.fusionSensor = null; + this._out = new Float32Array(4); + this.api = null; + this.errors = []; + this._sensorQ = new Quaternion(); + this._outQ = new Quaternion(); + this._onSensorRead = this._onSensorRead.bind(this); + this._onSensorError = this._onSensorError.bind(this); + this.init(); + } + createClass(PoseSensor, [{ + key: 'init', + value: function init() { + var sensor = null; + try { + sensor = new RelativeOrientationSensor({ + frequency: SENSOR_FREQUENCY, + referenceFrame: 'screen' + }); + sensor.addEventListener('error', this._onSensorError); + } catch (error) { + this.errors.push(error); + if (error.name === 'SecurityError') { + console.error('Cannot construct sensors due to the Feature Policy'); + console.warn('Attempting to fall back using "devicemotion"; however this will ' + 'fail in the future without correct permissions.'); + this.useDeviceMotion(); + } else if (error.name === 'ReferenceError') { + this.useDeviceMotion(); + } else { + console.error(error); + } + } + if (sensor) { + this.api = 'sensor'; + this.sensor = sensor; + this.sensor.addEventListener('reading', this._onSensorRead); + this.sensor.start(); + } + } + }, { + key: 'useDeviceMotion', + value: function useDeviceMotion() { + this.api = 'devicemotion'; + this.fusionSensor = new FusionPoseSensor(this.config.K_FILTER, this.config.PREDICTION_TIME_S, this.config.YAW_ONLY, this.config.DEBUG); + if (this.sensor) { + this.sensor.removeEventListener('reading', this._onSensorRead); + this.sensor.removeEventListener('error', this._onSensorError); + this.sensor = null; + } + } + }, { + key: 'getOrientation', + value: function getOrientation() { + if (this.fusionSensor) { + return this.fusionSensor.getOrientation(); + } + if (!this.sensor || !this.sensor.quaternion) { + this._out[0] = this._out[1] = this._out[2] = 0; + this._out[3] = 1; + return this._out; + } + var q = this.sensor.quaternion; + this._sensorQ.set(q[0], q[1], q[2], q[3]); + var out = this._outQ; + out.copy(SENSOR_TO_VR); + out.multiply(this._sensorQ); + if (this.config.YAW_ONLY) { + out.x = out.z = 0; + out.normalize(); + } + this._out[0] = out.x; + this._out[1] = out.y; + this._out[2] = out.z; + this._out[3] = out.w; + return this._out; + } + }, { + key: '_onSensorError', + value: function _onSensorError(event) { + this.errors.push(event.error); + if (event.error.name === 'NotAllowedError') { + console.error('Permission to access sensor was denied'); + } else if (event.error.name === 'NotReadableError') { + console.error('Sensor could not be read'); + } else { + console.error(event.error); + } + this.useDeviceMotion(); + } + }, { + key: '_onSensorRead', + value: function _onSensorRead() {} + }]); + return PoseSensor; + }(); + var rotateInstructionsAsset = ""; + function RotateInstructions() { + this.loadIcon_(); + var overlay = document.createElement('div'); + var s = overlay.style; + s.position = 'fixed'; + s.top = 0; + s.right = 0; + s.bottom = 0; + s.left = 0; + s.backgroundColor = 'gray'; + s.fontFamily = 'sans-serif'; + s.zIndex = 1000000; + var img = document.createElement('img'); + img.src = this.icon; + var s = img.style; + s.marginLeft = '25%'; + s.marginTop = '25%'; + s.width = '50%'; + overlay.appendChild(img); + var text = document.createElement('div'); + var s = text.style; + s.textAlign = 'center'; + s.fontSize = '16px'; + s.lineHeight = '24px'; + s.margin = '24px 25%'; + s.width = '50%'; + text.innerHTML = 'Place your phone into your Cardboard viewer.'; + overlay.appendChild(text); + var snackbar = document.createElement('div'); + var s = snackbar.style; + s.backgroundColor = '#CFD8DC'; + s.position = 'fixed'; + s.bottom = 0; + s.width = '100%'; + s.height = '48px'; + s.padding = '14px 24px'; + s.boxSizing = 'border-box'; + s.color = '#656A6B'; + overlay.appendChild(snackbar); + var snackbarText = document.createElement('div'); + snackbarText.style.float = 'left'; + snackbarText.innerHTML = 'No Cardboard viewer?'; + var snackbarButton = document.createElement('a'); + snackbarButton.href = 'https://www.google.com/get/cardboard/get-cardboard/'; + snackbarButton.innerHTML = 'get one'; + snackbarButton.target = '_blank'; + var s = snackbarButton.style; + s.float = 'right'; + s.fontWeight = 600; + s.textTransform = 'uppercase'; + s.borderLeft = '1px solid gray'; + s.paddingLeft = '24px'; + s.textDecoration = 'none'; + s.color = '#656A6B'; + snackbar.appendChild(snackbarText); + snackbar.appendChild(snackbarButton); + this.overlay = overlay; + this.text = text; + this.hide(); + } + RotateInstructions.prototype.show = function (parent) { + if (!parent && !this.overlay.parentElement) { + document.body.appendChild(this.overlay); + } else if (parent) { + if (this.overlay.parentElement && this.overlay.parentElement != parent) this.overlay.parentElement.removeChild(this.overlay); + parent.appendChild(this.overlay); + } + this.overlay.style.display = 'block'; + var img = this.overlay.querySelector('img'); + var s = img.style; + if (isLandscapeMode()) { + s.width = '20%'; + s.marginLeft = '40%'; + s.marginTop = '3%'; + } else { + s.width = '50%'; + s.marginLeft = '25%'; + s.marginTop = '25%'; + } + }; + RotateInstructions.prototype.hide = function () { + this.overlay.style.display = 'none'; + }; + RotateInstructions.prototype.showTemporarily = function (ms, parent) { + this.show(parent); + this.timer = setTimeout(this.hide.bind(this), ms); + }; + RotateInstructions.prototype.disableShowTemporarily = function () { + clearTimeout(this.timer); + }; + RotateInstructions.prototype.update = function () { + this.disableShowTemporarily(); + if (!isLandscapeMode() && isMobile()) { + this.show(); + } else { + this.hide(); + } + }; + RotateInstructions.prototype.loadIcon_ = function () { + this.icon = dataUri('image/svg+xml', rotateInstructionsAsset); + }; + var DEFAULT_VIEWER = 'CardboardV1'; + var VIEWER_KEY = 'WEBVR_CARDBOARD_VIEWER'; + var CLASS_NAME = 'webvr-polyfill-viewer-selector'; + function ViewerSelector(defaultViewer) { + try { + this.selectedKey = localStorage.getItem(VIEWER_KEY); + } catch (error) { + console.error('Failed to load viewer profile: %s', error); + } + if (!this.selectedKey) { + this.selectedKey = defaultViewer || DEFAULT_VIEWER; + } + this.dialog = this.createDialog_(DeviceInfo.Viewers); + this.root = null; + this.onChangeCallbacks_ = []; + } + ViewerSelector.prototype.show = function (root) { + this.root = root; + root.appendChild(this.dialog); + var selected = this.dialog.querySelector('#' + this.selectedKey); + selected.checked = true; + this.dialog.style.display = 'block'; + }; + ViewerSelector.prototype.hide = function () { + if (this.root && this.root.contains(this.dialog)) { + this.root.removeChild(this.dialog); + } + this.dialog.style.display = 'none'; + }; + ViewerSelector.prototype.getCurrentViewer = function () { + return DeviceInfo.Viewers[this.selectedKey]; + }; + ViewerSelector.prototype.getSelectedKey_ = function () { + var input = this.dialog.querySelector('input[name=field]:checked'); + if (input) { + return input.id; + } + return null; + }; + ViewerSelector.prototype.onChange = function (cb) { + this.onChangeCallbacks_.push(cb); + }; + ViewerSelector.prototype.fireOnChange_ = function (viewer) { + for (var i = 0; i < this.onChangeCallbacks_.length; i++) { + this.onChangeCallbacks_[i](viewer); + } + }; + ViewerSelector.prototype.onSave_ = function () { + this.selectedKey = this.getSelectedKey_(); + if (!this.selectedKey || !DeviceInfo.Viewers[this.selectedKey]) { + console.error('ViewerSelector.onSave_: this should never happen!'); + return; + } + this.fireOnChange_(DeviceInfo.Viewers[this.selectedKey]); + try { + localStorage.setItem(VIEWER_KEY, this.selectedKey); + } catch (error) { + console.error('Failed to save viewer profile: %s', error); + } + this.hide(); + }; + ViewerSelector.prototype.createDialog_ = function (options) { + var container = document.createElement('div'); + container.classList.add(CLASS_NAME); + container.style.display = 'none'; + var overlay = document.createElement('div'); + var s = overlay.style; + s.position = 'fixed'; + s.left = 0; + s.top = 0; + s.width = '100%'; + s.height = '100%'; + s.background = 'rgba(0, 0, 0, 0.3)'; + overlay.addEventListener('click', this.hide.bind(this)); + var width = 280; + var dialog = document.createElement('div'); + var s = dialog.style; + s.boxSizing = 'border-box'; + s.position = 'fixed'; + s.top = '24px'; + s.left = '50%'; + s.marginLeft = -width / 2 + 'px'; + s.width = width + 'px'; + s.padding = '24px'; + s.overflow = 'hidden'; + s.background = '#fafafa'; + s.fontFamily = "'Roboto', sans-serif"; + s.boxShadow = '0px 5px 20px #666'; + dialog.appendChild(this.createH1_('Select your viewer')); + for (var id in options) { + dialog.appendChild(this.createChoice_(id, options[id].label)); + } + dialog.appendChild(this.createButton_('Save', this.onSave_.bind(this))); + container.appendChild(overlay); + container.appendChild(dialog); + return container; + }; + ViewerSelector.prototype.createH1_ = function (name) { + var h1 = document.createElement('h1'); + var s = h1.style; + s.color = 'black'; + s.fontSize = '20px'; + s.fontWeight = 'bold'; + s.marginTop = 0; + s.marginBottom = '24px'; + h1.innerHTML = name; + return h1; + }; + ViewerSelector.prototype.createChoice_ = function (id, name) { + var div = document.createElement('div'); + div.style.marginTop = '8px'; + div.style.color = 'black'; + var input = document.createElement('input'); + input.style.fontSize = '30px'; + input.setAttribute('id', id); + input.setAttribute('type', 'radio'); + input.setAttribute('value', id); + input.setAttribute('name', 'field'); + var label = document.createElement('label'); + label.style.marginLeft = '4px'; + label.setAttribute('for', id); + label.innerHTML = name; + div.appendChild(input); + div.appendChild(label); + return div; + }; + ViewerSelector.prototype.createButton_ = function (label, onclick) { + var button = document.createElement('button'); + button.innerHTML = label; + var s = button.style; + s.float = 'right'; + s.textTransform = 'uppercase'; + s.color = '#1094f7'; + s.fontSize = '14px'; + s.letterSpacing = 0; + s.border = 0; + s.background = 'none'; + s.marginTop = '16px'; + button.addEventListener('click', onclick); + return button; + }; + var commonjsGlobal$$1 = typeof window !== 'undefined' ? window : typeof commonjsGlobal !== 'undefined' ? commonjsGlobal : typeof self !== 'undefined' ? self : {}; + function unwrapExports$$1(x) { + return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; + } + function createCommonjsModule$$1(fn, module) { + return module = { + exports: {} + }, fn(module, module.exports), module.exports; + } + var NoSleep = createCommonjsModule$$1(function (module, exports) { + (function webpackUniversalModuleDefinition(root, factory) { + module.exports = factory(); + })(commonjsGlobal$$1, function () { + return function (modules) { + var installedModules = {}; + function __nested_webpack_require_167216__(moduleId) { + if (installedModules[moduleId]) { + return installedModules[moduleId].exports; + } + var module = installedModules[moduleId] = { + i: moduleId, + l: false, + exports: {} + }; + modules[moduleId].call(module.exports, module, module.exports, __nested_webpack_require_167216__); + module.l = true; + return module.exports; + } + __nested_webpack_require_167216__.m = modules; + __nested_webpack_require_167216__.c = installedModules; + __nested_webpack_require_167216__.d = function (exports, name, getter) { + if (!__nested_webpack_require_167216__.o(exports, name)) { + Object.defineProperty(exports, name, { + configurable: false, + enumerable: true, + get: getter + }); + } + }; + __nested_webpack_require_167216__.n = function (module) { + var getter = module && module.__esModule ? function getDefault() { + return module['default']; + } : function getModuleExports() { + return module; + }; + __nested_webpack_require_167216__.d(getter, 'a', getter); + return getter; + }; + __nested_webpack_require_167216__.o = function (object, property) { + return Object.prototype.hasOwnProperty.call(object, property); + }; + __nested_webpack_require_167216__.p = ""; + return __nested_webpack_require_167216__(__nested_webpack_require_167216__.s = 0); + }([function (module, exports, __nested_webpack_require_168841__) { + 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; + }; + }(); + function _classCallCheck(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } + } + var mediaFile = __nested_webpack_require_168841__(1); + var oldIOS = typeof navigator !== 'undefined' && parseFloat(('' + (/CPU.*OS ([0-9_]{3,4})[0-9_]{0,1}|(CPU like).*AppleWebKit.*Mobile/i.exec(navigator.userAgent) || [0, ''])[1]).replace('undefined', '3_2').replace('_', '.').replace('_', '')) < 10 && !window.MSStream; + var NoSleep = function () { + function NoSleep() { + _classCallCheck(this, NoSleep); + if (oldIOS) { + this.noSleepTimer = null; + } else { + this.noSleepVideo = document.createElement('video'); + this.noSleepVideo.setAttribute('playsinline', ''); + this.noSleepVideo.setAttribute('src', mediaFile); + this.noSleepVideo.addEventListener('timeupdate', function (e) { + if (this.noSleepVideo.currentTime > 0.5) { + this.noSleepVideo.currentTime = Math.random(); + } + }.bind(this)); + } + } + _createClass(NoSleep, [{ + key: 'enable', + value: function enable() { + if (oldIOS) { + this.disable(); + this.noSleepTimer = window.setInterval(function () { + window.location.href = '/'; + window.setTimeout(window.stop, 0); + }, 15000); + } else { + this.noSleepVideo.play(); + } + } + }, { + key: 'disable', + value: function disable() { + if (oldIOS) { + if (this.noSleepTimer) { + window.clearInterval(this.noSleepTimer); + this.noSleepTimer = null; + } + } else { + this.noSleepVideo.pause(); + } + } + }]); + return NoSleep; + }(); + module.exports = NoSleep; + }, function (module, exports, __webpack_require__) { + module.exports = 'data:video/mp4;base64,AAAAIGZ0eXBtcDQyAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQAACKBtZGF0AAAC8wYF///v3EXpvebZSLeWLNgg2SPu73gyNjQgLSBjb3JlIDE0MiByMjQ3OSBkZDc5YTYxIC0gSC4yNjQvTVBFRy00IEFWQyBjb2RlYyAtIENvcHlsZWZ0IDIwMDMtMjAxNCAtIGh0dHA6Ly93d3cudmlkZW9sYW4ub3JnL3gyNjQuaHRtbCAtIG9wdGlvbnM6IGNhYmFjPTEgcmVmPTEgZGVibG9jaz0xOjA6MCBhbmFseXNlPTB4MToweDExMSBtZT1oZXggc3VibWU9MiBwc3k9MSBwc3lfcmQ9MS4wMDowLjAwIG1peGVkX3JlZj0wIG1lX3JhbmdlPTE2IGNocm9tYV9tZT0xIHRyZWxsaXM9MCA4eDhkY3Q9MCBjcW09MCBkZWFkem9uZT0yMSwxMSBmYXN0X3Bza2lwPTEgY2hyb21hX3FwX29mZnNldD0wIHRocmVhZHM9NiBsb29rYWhlYWRfdGhyZWFkcz0xIHNsaWNlZF90aHJlYWRzPTAgbnI9MCBkZWNpbWF0ZT0xIGludGVybGFjZWQ9MCBibHVyYXlfY29tcGF0PTAgY29uc3RyYWluZWRfaW50cmE9MCBiZnJhbWVzPTMgYl9weXJhbWlkPTIgYl9hZGFwdD0xIGJfYmlhcz0wIGRpcmVjdD0xIHdlaWdodGI9MSBvcGVuX2dvcD0wIHdlaWdodHA9MSBrZXlpbnQ9MzAwIGtleWludF9taW49MzAgc2NlbmVjdXQ9NDAgaW50cmFfcmVmcmVzaD0wIHJjX2xvb2thaGVhZD0xMCByYz1jcmYgbWJ0cmVlPTEgY3JmPTIwLjAgcWNvbXA9MC42MCBxcG1pbj0wIHFwbWF4PTY5IHFwc3RlcD00IHZidl9tYXhyYXRlPTIwMDAwIHZidl9idWZzaXplPTI1MDAwIGNyZl9tYXg9MC4wIG5hbF9ocmQ9bm9uZSBmaWxsZXI9MCBpcF9yYXRpbz0xLjQwIGFxPTE6MS4wMACAAAAAOWWIhAA3//p+C7v8tDDSTjf97w55i3SbRPO4ZY+hkjD5hbkAkL3zpJ6h/LR1CAABzgB1kqqzUorlhQAAAAxBmiQYhn/+qZYADLgAAAAJQZ5CQhX/AAj5IQADQGgcIQADQGgcAAAACQGeYUQn/wALKCEAA0BoHAAAAAkBnmNEJ/8ACykhAANAaBwhAANAaBwAAAANQZpoNExDP/6plgAMuSEAA0BoHAAAAAtBnoZFESwr/wAI+SEAA0BoHCEAA0BoHAAAAAkBnqVEJ/8ACykhAANAaBwAAAAJAZ6nRCf/AAsoIQADQGgcIQADQGgcAAAADUGarDRMQz/+qZYADLghAANAaBwAAAALQZ7KRRUsK/8ACPkhAANAaBwAAAAJAZ7pRCf/AAsoIQADQGgcIQADQGgcAAAACQGe60Qn/wALKCEAA0BoHAAAAA1BmvA0TEM//qmWAAy5IQADQGgcIQADQGgcAAAAC0GfDkUVLCv/AAj5IQADQGgcAAAACQGfLUQn/wALKSEAA0BoHCEAA0BoHAAAAAkBny9EJ/8ACyghAANAaBwAAAANQZs0NExDP/6plgAMuCEAA0BoHAAAAAtBn1JFFSwr/wAI+SEAA0BoHCEAA0BoHAAAAAkBn3FEJ/8ACyghAANAaBwAAAAJAZ9zRCf/AAsoIQADQGgcIQADQGgcAAAADUGbeDRMQz/+qZYADLkhAANAaBwAAAALQZ+WRRUsK/8ACPghAANAaBwhAANAaBwAAAAJAZ+1RCf/AAspIQADQGgcAAAACQGft0Qn/wALKSEAA0BoHCEAA0BoHAAAAA1Bm7w0TEM//qmWAAy4IQADQGgcAAAAC0Gf2kUVLCv/AAj5IQADQGgcAAAACQGf+UQn/wALKCEAA0BoHCEAA0BoHAAAAAkBn/tEJ/8ACykhAANAaBwAAAANQZvgNExDP/6plgAMuSEAA0BoHCEAA0BoHAAAAAtBnh5FFSwr/wAI+CEAA0BoHAAAAAkBnj1EJ/8ACyghAANAaBwhAANAaBwAAAAJAZ4/RCf/AAspIQADQGgcAAAADUGaJDRMQz/+qZYADLghAANAaBwAAAALQZ5CRRUsK/8ACPkhAANAaBwhAANAaBwAAAAJAZ5hRCf/AAsoIQADQGgcAAAACQGeY0Qn/wALKSEAA0BoHCEAA0BoHAAAAA1Bmmg0TEM//qmWAAy5IQADQGgcAAAAC0GehkUVLCv/AAj5IQADQGgcIQADQGgcAAAACQGepUQn/wALKSEAA0BoHAAAAAkBnqdEJ/8ACyghAANAaBwAAAANQZqsNExDP/6plgAMuCEAA0BoHCEAA0BoHAAAAAtBnspFFSwr/wAI+SEAA0BoHAAAAAkBnulEJ/8ACyghAANAaBwhAANAaBwAAAAJAZ7rRCf/AAsoIQADQGgcAAAADUGa8DRMQz/+qZYADLkhAANAaBwhAANAaBwAAAALQZ8ORRUsK/8ACPkhAANAaBwAAAAJAZ8tRCf/AAspIQADQGgcIQADQGgcAAAACQGfL0Qn/wALKCEAA0BoHAAAAA1BmzQ0TEM//qmWAAy4IQADQGgcAAAAC0GfUkUVLCv/AAj5IQADQGgcIQADQGgcAAAACQGfcUQn/wALKCEAA0BoHAAAAAkBn3NEJ/8ACyghAANAaBwhAANAaBwAAAANQZt4NExC//6plgAMuSEAA0BoHAAAAAtBn5ZFFSwr/wAI+CEAA0BoHCEAA0BoHAAAAAkBn7VEJ/8ACykhAANAaBwAAAAJAZ+3RCf/AAspIQADQGgcAAAADUGbuzRMQn/+nhAAYsAhAANAaBwhAANAaBwAAAAJQZ/aQhP/AAspIQADQGgcAAAACQGf+UQn/wALKCEAA0BoHCEAA0BoHCEAA0BoHCEAA0BoHCEAA0BoHCEAA0BoHAAACiFtb292AAAAbG12aGQAAAAA1YCCX9WAgl8AAAPoAAAH/AABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAGGlvZHMAAAAAEICAgAcAT////v7/AAAF+XRyYWsAAABcdGtoZAAAAAPVgIJf1YCCXwAAAAEAAAAAAAAH0AAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAygAAAMoAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAB9AAABdwAAEAAAAABXFtZGlhAAAAIG1kaGQAAAAA1YCCX9WAgl8AAV+QAAK/IFXEAAAAAAAtaGRscgAAAAAAAAAAdmlkZQAAAAAAAAAAAAAAAFZpZGVvSGFuZGxlcgAAAAUcbWluZgAAABR2bWhkAAAAAQAAAAAAAAAAAAAAJGRpbmYAAAAcZHJlZgAAAAAAAAABAAAADHVybCAAAAABAAAE3HN0YmwAAACYc3RzZAAAAAAAAAABAAAAiGF2YzEAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAygDKAEgAAABIAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY//8AAAAyYXZjQwFNQCj/4QAbZ01AKOyho3ySTUBAQFAAAAMAEAAr8gDxgxlgAQAEaO+G8gAAABhzdHRzAAAAAAAAAAEAAAA8AAALuAAAABRzdHNzAAAAAAAAAAEAAAABAAAB8GN0dHMAAAAAAAAAPAAAAAEAABdwAAAAAQAAOpgAAAABAAAXcAAAAAEAAAAAAAAAAQAAC7gAAAABAAA6mAAAAAEAABdwAAAAAQAAAAAAAAABAAALuAAAAAEAADqYAAAAAQAAF3AAAAABAAAAAAAAAAEAAAu4AAAAAQAAOpgAAAABAAAXcAAAAAEAAAAAAAAAAQAAC7gAAAABAAA6mAAAAAEAABdwAAAAAQAAAAAAAAABAAALuAAAAAEAADqYAAAAAQAAF3AAAAABAAAAAAAAAAEAAAu4AAAAAQAAOpgAAAABAAAXcAAAAAEAAAAAAAAAAQAAC7gAAAABAAA6mAAAAAEAABdwAAAAAQAAAAAAAAABAAALuAAAAAEAADqYAAAAAQAAF3AAAAABAAAAAAAAAAEAAAu4AAAAAQAAOpgAAAABAAAXcAAAAAEAAAAAAAAAAQAAC7gAAAABAAA6mAAAAAEAABdwAAAAAQAAAAAAAAABAAALuAAAAAEAADqYAAAAAQAAF3AAAAABAAAAAAAAAAEAAAu4AAAAAQAAOpgAAAABAAAXcAAAAAEAAAAAAAAAAQAAC7gAAAABAAA6mAAAAAEAABdwAAAAAQAAAAAAAAABAAALuAAAAAEAAC7gAAAAAQAAF3AAAAABAAAAAAAAABxzdHNjAAAAAAAAAAEAAAABAAAAAQAAAAEAAAEEc3RzegAAAAAAAAAAAAAAPAAAAzQAAAAQAAAADQAAAA0AAAANAAAAEQAAAA8AAAANAAAADQAAABEAAAAPAAAADQAAAA0AAAARAAAADwAAAA0AAAANAAAAEQAAAA8AAAANAAAADQAAABEAAAAPAAAADQAAAA0AAAARAAAADwAAAA0AAAANAAAAEQAAAA8AAAANAAAADQAAABEAAAAPAAAADQAAAA0AAAARAAAADwAAAA0AAAANAAAAEQAAAA8AAAANAAAADQAAABEAAAAPAAAADQAAAA0AAAARAAAADwAAAA0AAAANAAAAEQAAAA8AAAANAAAADQAAABEAAAANAAAADQAAAQBzdGNvAAAAAAAAADwAAAAwAAADZAAAA3QAAAONAAADoAAAA7kAAAPQAAAD6wAAA/4AAAQXAAAELgAABEMAAARcAAAEbwAABIwAAAShAAAEugAABM0AAATkAAAE/wAABRIAAAUrAAAFQgAABV0AAAVwAAAFiQAABaAAAAW1AAAFzgAABeEAAAX+AAAGEwAABiwAAAY/AAAGVgAABnEAAAaEAAAGnQAABrQAAAbPAAAG4gAABvUAAAcSAAAHJwAAB0AAAAdTAAAHcAAAB4UAAAeeAAAHsQAAB8gAAAfjAAAH9gAACA8AAAgmAAAIQQAACFQAAAhnAAAIhAAACJcAAAMsdHJhawAAAFx0a2hkAAAAA9WAgl/VgIJfAAAAAgAAAAAAAAf8AAAAAAAAAAAAAAABAQAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAACsm1kaWEAAAAgbWRoZAAAAADVgIJf1YCCXwAArEQAAWAAVcQAAAAAACdoZGxyAAAAAAAAAABzb3VuAAAAAAAAAAAAAAAAU3RlcmVvAAAAAmNtaW5mAAAAEHNtaGQAAAAAAAAAAAAAACRkaW5mAAAAHGRyZWYAAAAAAAAAAQAAAAx1cmwgAAAAAQAAAidzdGJsAAAAZ3N0c2QAAAAAAAAAAQAAAFdtcDRhAAAAAAAAAAEAAAAAAAAAAAACABAAAAAArEQAAAAAADNlc2RzAAAAAAOAgIAiAAIABICAgBRAFQAAAAADDUAAAAAABYCAgAISEAaAgIABAgAAABhzdHRzAAAAAAAAAAEAAABYAAAEAAAAABxzdHNjAAAAAAAAAAEAAAABAAAAAQAAAAEAAAAUc3RzegAAAAAAAAAGAAAAWAAAAXBzdGNvAAAAAAAAAFgAAAOBAAADhwAAA5oAAAOtAAADswAAA8oAAAPfAAAD5QAAA/gAAAQLAAAEEQAABCgAAAQ9AAAEUAAABFYAAARpAAAEgAAABIYAAASbAAAErgAABLQAAATHAAAE3gAABPMAAAT5AAAFDAAABR8AAAUlAAAFPAAABVEAAAVXAAAFagAABX0AAAWDAAAFmgAABa8AAAXCAAAFyAAABdsAAAXyAAAF+AAABg0AAAYgAAAGJgAABjkAAAZQAAAGZQAABmsAAAZ+AAAGkQAABpcAAAauAAAGwwAABskAAAbcAAAG7wAABwYAAAcMAAAHIQAABzQAAAc6AAAHTQAAB2QAAAdqAAAHfwAAB5IAAAeYAAAHqwAAB8IAAAfXAAAH3QAAB/AAAAgDAAAICQAACCAAAAg1AAAIOwAACE4AAAhhAAAIeAAACH4AAAiRAAAIpAAACKoAAAiwAAAItgAACLwAAAjCAAAAFnVkdGEAAAAObmFtZVN0ZXJlbwAAAHB1ZHRhAAAAaG1ldGEAAAAAAAAAIWhkbHIAAAAAAAAAAG1kaXJhcHBsAAAAAAAAAAAAAAAAO2lsc3QAAAAzqXRvbwAAACtkYXRhAAAAAQAAAABIYW5kQnJha2UgMC4xMC4yIDIwMTUwNjExMDA='; + }]); + }); + }); + var NoSleep$1 = unwrapExports$$1(NoSleep); + var nextDisplayId = 1000; + var defaultLeftBounds = [0, 0, 0.5, 1]; + var defaultRightBounds = [0.5, 0, 0.5, 1]; + var raf = window.requestAnimationFrame; + var caf = window.cancelAnimationFrame; + function VRFrameData() { + this.leftProjectionMatrix = new Float32Array(16); + this.leftViewMatrix = new Float32Array(16); + this.rightProjectionMatrix = new Float32Array(16); + this.rightViewMatrix = new Float32Array(16); + this.pose = null; + } + function VRDisplayCapabilities(config) { + Object.defineProperties(this, { + hasPosition: { + writable: false, + enumerable: true, + value: config.hasPosition + }, + hasExternalDisplay: { + writable: false, + enumerable: true, + value: config.hasExternalDisplay + }, + canPresent: { + writable: false, + enumerable: true, + value: config.canPresent + }, + maxLayers: { + writable: false, + enumerable: true, + value: config.maxLayers + }, + hasOrientation: { + enumerable: true, + get: function get() { + deprecateWarning('VRDisplayCapabilities.prototype.hasOrientation', 'VRDisplay.prototype.getFrameData'); + return config.hasOrientation; + } + } + }); + } + function VRDisplay(config) { + config = config || {}; + var USE_WAKELOCK = 'wakelock' in config ? config.wakelock : true; + this.isPolyfilled = true; + this.displayId = nextDisplayId++; + this.displayName = ''; + this.depthNear = 0.01; + this.depthFar = 10000.0; + this.isPresenting = false; + Object.defineProperty(this, 'isConnected', { + get: function get() { + deprecateWarning('VRDisplay.prototype.isConnected', 'VRDisplayCapabilities.prototype.hasExternalDisplay'); + return false; + } + }); + this.capabilities = new VRDisplayCapabilities({ + hasPosition: false, + hasOrientation: false, + hasExternalDisplay: false, + canPresent: false, + maxLayers: 1 + }); + this.stageParameters = null; + this.waitingForPresent_ = false; + this.layer_ = null; + this.originalParent_ = null; + this.fullscreenElement_ = null; + this.fullscreenWrapper_ = null; + this.fullscreenElementCachedStyle_ = null; + this.fullscreenEventTarget_ = null; + this.fullscreenChangeHandler_ = null; + this.fullscreenErrorHandler_ = null; + if (USE_WAKELOCK && isMobile()) { + this.wakelock_ = new NoSleep$1(); + } + } + VRDisplay.prototype.getFrameData = function (frameData) { + return frameDataFromPose(frameData, this._getPose(), this); + }; + VRDisplay.prototype.getPose = function () { + deprecateWarning('VRDisplay.prototype.getPose', 'VRDisplay.prototype.getFrameData'); + return this._getPose(); + }; + VRDisplay.prototype.resetPose = function () { + deprecateWarning('VRDisplay.prototype.resetPose'); + return this._resetPose(); + }; + VRDisplay.prototype.getImmediatePose = function () { + deprecateWarning('VRDisplay.prototype.getImmediatePose', 'VRDisplay.prototype.getFrameData'); + return this._getPose(); + }; + VRDisplay.prototype.requestAnimationFrame = function (callback) { + return raf(callback); + }; + VRDisplay.prototype.cancelAnimationFrame = function (id) { + return caf(id); + }; + VRDisplay.prototype.wrapForFullscreen = function (element) { + if (isIOS()) { + return element; + } + if (!this.fullscreenWrapper_) { + this.fullscreenWrapper_ = document.createElement('div'); + var cssProperties = ['height: ' + Math.min(screen.height, screen.width) + 'px !important', 'top: 0 !important', 'left: 0 !important', 'right: 0 !important', 'border: 0', 'margin: 0', 'padding: 0', 'z-index: 999999 !important', 'position: fixed']; + this.fullscreenWrapper_.setAttribute('style', cssProperties.join('; ') + ';'); + this.fullscreenWrapper_.classList.add('webvr-polyfill-fullscreen-wrapper'); + } + if (this.fullscreenElement_ == element) { + return this.fullscreenWrapper_; + } + if (this.fullscreenElement_) { + if (this.originalParent_) { + this.originalParent_.appendChild(this.fullscreenElement_); + } else { + this.fullscreenElement_.parentElement.removeChild(this.fullscreenElement_); + } + } + this.fullscreenElement_ = element; + this.originalParent_ = element.parentElement; + if (!this.originalParent_) { + document.body.appendChild(element); + } + if (!this.fullscreenWrapper_.parentElement) { + var parent = this.fullscreenElement_.parentElement; + parent.insertBefore(this.fullscreenWrapper_, this.fullscreenElement_); + parent.removeChild(this.fullscreenElement_); + } + this.fullscreenWrapper_.insertBefore(this.fullscreenElement_, this.fullscreenWrapper_.firstChild); + this.fullscreenElementCachedStyle_ = this.fullscreenElement_.getAttribute('style'); + var self = this; + function applyFullscreenElementStyle() { + if (!self.fullscreenElement_) { + return; + } + var cssProperties = ['position: absolute', 'top: 0', 'left: 0', 'width: ' + Math.max(screen.width, screen.height) + 'px', 'height: ' + Math.min(screen.height, screen.width) + 'px', 'border: 0', 'margin: 0', 'padding: 0']; + self.fullscreenElement_.setAttribute('style', cssProperties.join('; ') + ';'); + } + applyFullscreenElementStyle(); + return this.fullscreenWrapper_; + }; + VRDisplay.prototype.removeFullscreenWrapper = function () { + if (!this.fullscreenElement_) { + return; + } + var element = this.fullscreenElement_; + if (this.fullscreenElementCachedStyle_) { + element.setAttribute('style', this.fullscreenElementCachedStyle_); + } else { + element.removeAttribute('style'); + } + this.fullscreenElement_ = null; + this.fullscreenElementCachedStyle_ = null; + var parent = this.fullscreenWrapper_.parentElement; + this.fullscreenWrapper_.removeChild(element); + if (this.originalParent_ === parent) { + parent.insertBefore(element, this.fullscreenWrapper_); + } else if (this.originalParent_) { + this.originalParent_.appendChild(element); + } + parent.removeChild(this.fullscreenWrapper_); + return element; + }; + VRDisplay.prototype.requestPresent = function (layers) { + var wasPresenting = this.isPresenting; + var self = this; + if (!(layers instanceof Array)) { + deprecateWarning('VRDisplay.prototype.requestPresent with non-array argument', 'an array of VRLayers as the first argument'); + layers = [layers]; + } + return new Promise(function (resolve, reject) { + if (!self.capabilities.canPresent) { + reject(new Error('VRDisplay is not capable of presenting.')); + return; + } + if (layers.length == 0 || layers.length > self.capabilities.maxLayers) { + reject(new Error('Invalid number of layers.')); + return; + } + var incomingLayer = layers[0]; + if (!incomingLayer.source) { + resolve(); + return; + } + var leftBounds = incomingLayer.leftBounds || defaultLeftBounds; + var rightBounds = incomingLayer.rightBounds || defaultRightBounds; + if (wasPresenting) { + var layer = self.layer_; + if (layer.source !== incomingLayer.source) { + layer.source = incomingLayer.source; + } + for (var i = 0; i < 4; i++) { + layer.leftBounds[i] = leftBounds[i]; + layer.rightBounds[i] = rightBounds[i]; + } + self.wrapForFullscreen(self.layer_.source); + self.updatePresent_(); + resolve(); + return; + } + self.layer_ = { + predistorted: incomingLayer.predistorted, + source: incomingLayer.source, + leftBounds: leftBounds.slice(0), + rightBounds: rightBounds.slice(0) + }; + self.waitingForPresent_ = false; + if (self.layer_ && self.layer_.source) { + var fullscreenElement = self.wrapForFullscreen(self.layer_.source); + var onFullscreenChange = function onFullscreenChange() { + var actualFullscreenElement = getFullscreenElement(); + self.isPresenting = fullscreenElement === actualFullscreenElement; + if (self.isPresenting) { + if (screen.orientation && screen.orientation.lock) { + screen.orientation.lock('landscape-primary').catch(function (error) { + console.error('screen.orientation.lock() failed due to', error.message); + }); + } + self.waitingForPresent_ = false; + self.beginPresent_(); + resolve(); + } else { + if (screen.orientation && screen.orientation.unlock) { + screen.orientation.unlock(); + } + self.removeFullscreenWrapper(); + self.disableWakeLock(); + self.endPresent_(); + self.removeFullscreenListeners_(); + } + self.fireVRDisplayPresentChange_(); + }; + var onFullscreenError = function onFullscreenError() { + if (!self.waitingForPresent_) { + return; + } + self.removeFullscreenWrapper(); + self.removeFullscreenListeners_(); + self.disableWakeLock(); + self.waitingForPresent_ = false; + self.isPresenting = false; + reject(new Error('Unable to present.')); + }; + self.addFullscreenListeners_(fullscreenElement, onFullscreenChange, onFullscreenError); + if (requestFullscreen(fullscreenElement)) { + self.enableWakeLock(); + self.waitingForPresent_ = true; + } else if (isIOS() || isWebViewAndroid()) { + self.enableWakeLock(); + self.isPresenting = true; + self.beginPresent_(); + self.fireVRDisplayPresentChange_(); + resolve(); + } + } + if (!self.waitingForPresent_ && !isIOS()) { + exitFullscreen(); + reject(new Error('Unable to present.')); + } + }); + }; + VRDisplay.prototype.exitPresent = function () { + var wasPresenting = this.isPresenting; + var self = this; + this.isPresenting = false; + this.layer_ = null; + this.disableWakeLock(); + return new Promise(function (resolve, reject) { + if (wasPresenting) { + if (!exitFullscreen() && isIOS()) { + self.endPresent_(); + self.fireVRDisplayPresentChange_(); + } + if (isWebViewAndroid()) { + self.removeFullscreenWrapper(); + self.removeFullscreenListeners_(); + self.endPresent_(); + self.fireVRDisplayPresentChange_(); + } + resolve(); + } else { + reject(new Error('Was not presenting to VRDisplay.')); + } + }); + }; + VRDisplay.prototype.getLayers = function () { + if (this.layer_) { + return [this.layer_]; + } + return []; + }; + VRDisplay.prototype.fireVRDisplayPresentChange_ = function () { + var event = new CustomEvent('vrdisplaypresentchange', { + detail: { + display: this + } + }); + window.dispatchEvent(event); + }; + VRDisplay.prototype.fireVRDisplayConnect_ = function () { + var event = new CustomEvent('vrdisplayconnect', { + detail: { + display: this + } + }); + window.dispatchEvent(event); + }; + VRDisplay.prototype.addFullscreenListeners_ = function (element, changeHandler, errorHandler) { + this.removeFullscreenListeners_(); + this.fullscreenEventTarget_ = element; + this.fullscreenChangeHandler_ = changeHandler; + this.fullscreenErrorHandler_ = errorHandler; + if (changeHandler) { + if (document.fullscreenEnabled) { + element.addEventListener('fullscreenchange', changeHandler, false); + } else if (document.webkitFullscreenEnabled) { + element.addEventListener('webkitfullscreenchange', changeHandler, false); + } else if (document.mozFullScreenEnabled) { + document.addEventListener('mozfullscreenchange', changeHandler, false); + } else if (document.msFullscreenEnabled) { + element.addEventListener('msfullscreenchange', changeHandler, false); + } + } + if (errorHandler) { + if (document.fullscreenEnabled) { + element.addEventListener('fullscreenerror', errorHandler, false); + } else if (document.webkitFullscreenEnabled) { + element.addEventListener('webkitfullscreenerror', errorHandler, false); + } else if (document.mozFullScreenEnabled) { + document.addEventListener('mozfullscreenerror', errorHandler, false); + } else if (document.msFullscreenEnabled) { + element.addEventListener('msfullscreenerror', errorHandler, false); + } + } + }; + VRDisplay.prototype.removeFullscreenListeners_ = function () { + if (!this.fullscreenEventTarget_) return; + var element = this.fullscreenEventTarget_; + if (this.fullscreenChangeHandler_) { + var changeHandler = this.fullscreenChangeHandler_; + element.removeEventListener('fullscreenchange', changeHandler, false); + element.removeEventListener('webkitfullscreenchange', changeHandler, false); + document.removeEventListener('mozfullscreenchange', changeHandler, false); + element.removeEventListener('msfullscreenchange', changeHandler, false); + } + if (this.fullscreenErrorHandler_) { + var errorHandler = this.fullscreenErrorHandler_; + element.removeEventListener('fullscreenerror', errorHandler, false); + element.removeEventListener('webkitfullscreenerror', errorHandler, false); + document.removeEventListener('mozfullscreenerror', errorHandler, false); + element.removeEventListener('msfullscreenerror', errorHandler, false); + } + this.fullscreenEventTarget_ = null; + this.fullscreenChangeHandler_ = null; + this.fullscreenErrorHandler_ = null; + }; + VRDisplay.prototype.enableWakeLock = function () { + if (this.wakelock_) { + this.wakelock_.enable(); + } + }; + VRDisplay.prototype.disableWakeLock = function () { + if (this.wakelock_) { + this.wakelock_.disable(); + } + }; + VRDisplay.prototype.beginPresent_ = function () {}; + VRDisplay.prototype.endPresent_ = function () {}; + VRDisplay.prototype.submitFrame = function (pose) {}; + VRDisplay.prototype.getEyeParameters = function (whichEye) { + return null; + }; + var config = { + ADDITIONAL_VIEWERS: [], + DEFAULT_VIEWER: '', + MOBILE_WAKE_LOCK: true, + DEBUG: false, + DPDB_URL: 'https://dpdb.webvr.rocks/dpdb.json', + K_FILTER: 0.98, + PREDICTION_TIME_S: 0.040, + CARDBOARD_UI_DISABLED: false, + ROTATE_INSTRUCTIONS_DISABLED: false, + YAW_ONLY: false, + BUFFER_SCALE: 0.5, + DIRTY_SUBMIT_FRAME_BINDINGS: false + }; + var Eye = { + LEFT: 'left', + RIGHT: 'right' + }; + function CardboardVRDisplay(config$$1) { + var defaults = extend({}, config); + config$$1 = extend(defaults, config$$1 || {}); + VRDisplay.call(this, { + wakelock: config$$1.MOBILE_WAKE_LOCK + }); + this.config = config$$1; + this.displayName = 'Cardboard VRDisplay'; + this.capabilities = new VRDisplayCapabilities({ + hasPosition: false, + hasOrientation: true, + hasExternalDisplay: false, + canPresent: true, + maxLayers: 1 + }); + this.stageParameters = null; + this.bufferScale_ = this.config.BUFFER_SCALE; + this.poseSensor_ = new PoseSensor(this.config); + this.distorter_ = null; + this.cardboardUI_ = null; + this.dpdb_ = new Dpdb(this.config.DPDB_URL, this.onDeviceParamsUpdated_.bind(this)); + this.deviceInfo_ = new DeviceInfo(this.dpdb_.getDeviceParams(), config$$1.ADDITIONAL_VIEWERS); + this.viewerSelector_ = new ViewerSelector(config$$1.DEFAULT_VIEWER); + this.viewerSelector_.onChange(this.onViewerChanged_.bind(this)); + this.deviceInfo_.setViewer(this.viewerSelector_.getCurrentViewer()); + if (!this.config.ROTATE_INSTRUCTIONS_DISABLED) { + this.rotateInstructions_ = new RotateInstructions(); + } + if (isIOS()) { + window.addEventListener('resize', this.onResize_.bind(this)); + } + } + CardboardVRDisplay.prototype = Object.create(VRDisplay.prototype); + CardboardVRDisplay.prototype._getPose = function () { + return { + position: null, + orientation: this.poseSensor_.getOrientation(), + linearVelocity: null, + linearAcceleration: null, + angularVelocity: null, + angularAcceleration: null + }; + }; + CardboardVRDisplay.prototype._resetPose = function () { + if (this.poseSensor_.resetPose) { + this.poseSensor_.resetPose(); + } + }; + CardboardVRDisplay.prototype._getFieldOfView = function (whichEye) { + var fieldOfView; + if (whichEye == Eye.LEFT) { + fieldOfView = this.deviceInfo_.getFieldOfViewLeftEye(); + } else if (whichEye == Eye.RIGHT) { + fieldOfView = this.deviceInfo_.getFieldOfViewRightEye(); + } else { + console.error('Invalid eye provided: %s', whichEye); + return null; + } + return fieldOfView; + }; + CardboardVRDisplay.prototype._getEyeOffset = function (whichEye) { + var offset; + if (whichEye == Eye.LEFT) { + offset = [-this.deviceInfo_.viewer.interLensDistance * 0.5, 0.0, 0.0]; + } else if (whichEye == Eye.RIGHT) { + offset = [this.deviceInfo_.viewer.interLensDistance * 0.5, 0.0, 0.0]; + } else { + console.error('Invalid eye provided: %s', whichEye); + return null; + } + return offset; + }; + CardboardVRDisplay.prototype.getEyeParameters = function (whichEye) { + var offset = this._getEyeOffset(whichEye); + var fieldOfView = this._getFieldOfView(whichEye); + var eyeParams = { + offset: offset, + renderWidth: this.deviceInfo_.device.width * 0.5 * this.bufferScale_, + renderHeight: this.deviceInfo_.device.height * this.bufferScale_ + }; + Object.defineProperty(eyeParams, 'fieldOfView', { + enumerable: true, + get: function get() { + deprecateWarning('VRFieldOfView', 'VRFrameData\'s projection matrices'); + return fieldOfView; + } + }); + return eyeParams; + }; + CardboardVRDisplay.prototype.onDeviceParamsUpdated_ = function (newParams) { + if (this.config.DEBUG) { + console.log('DPDB reported that device params were updated.'); + } + this.deviceInfo_.updateDeviceParams(newParams); + if (this.distorter_) { + this.distorter_.updateDeviceInfo(this.deviceInfo_); + } + }; + CardboardVRDisplay.prototype.updateBounds_ = function () { + if (this.layer_ && this.distorter_ && (this.layer_.leftBounds || this.layer_.rightBounds)) { + this.distorter_.setTextureBounds(this.layer_.leftBounds, this.layer_.rightBounds); + } + }; + CardboardVRDisplay.prototype.beginPresent_ = function () { + var gl = this.layer_.source.getContext('webgl'); + if (!gl) gl = this.layer_.source.getContext('experimental-webgl'); + if (!gl) gl = this.layer_.source.getContext('webgl2'); + if (!gl) return; + if (this.layer_.predistorted) { + if (!this.config.CARDBOARD_UI_DISABLED) { + gl.canvas.width = getScreenWidth() * this.bufferScale_; + gl.canvas.height = getScreenHeight() * this.bufferScale_; + this.cardboardUI_ = new CardboardUI(gl); + } + } else { + if (!this.config.CARDBOARD_UI_DISABLED) { + this.cardboardUI_ = new CardboardUI(gl); + } + this.distorter_ = new CardboardDistorter(gl, this.cardboardUI_, this.config.BUFFER_SCALE, this.config.DIRTY_SUBMIT_FRAME_BINDINGS); + this.distorter_.updateDeviceInfo(this.deviceInfo_); + } + if (this.cardboardUI_) { + this.cardboardUI_.listen(function (e) { + this.viewerSelector_.show(this.layer_.source.parentElement); + e.stopPropagation(); + e.preventDefault(); + }.bind(this), function (e) { + this.exitPresent(); + e.stopPropagation(); + e.preventDefault(); + }.bind(this)); + } + if (this.rotateInstructions_) { + if (isLandscapeMode() && isMobile()) { + this.rotateInstructions_.showTemporarily(3000, this.layer_.source.parentElement); + } else { + this.rotateInstructions_.update(); + } + } + this.orientationHandler = this.onOrientationChange_.bind(this); + window.addEventListener('orientationchange', this.orientationHandler); + this.vrdisplaypresentchangeHandler = this.updateBounds_.bind(this); + window.addEventListener('vrdisplaypresentchange', this.vrdisplaypresentchangeHandler); + this.fireVRDisplayDeviceParamsChange_(); + }; + CardboardVRDisplay.prototype.endPresent_ = function () { + if (this.distorter_) { + this.distorter_.destroy(); + this.distorter_ = null; + } + if (this.cardboardUI_) { + this.cardboardUI_.destroy(); + this.cardboardUI_ = null; + } + if (this.rotateInstructions_) { + this.rotateInstructions_.hide(); + } + this.viewerSelector_.hide(); + window.removeEventListener('orientationchange', this.orientationHandler); + window.removeEventListener('vrdisplaypresentchange', this.vrdisplaypresentchangeHandler); + }; + CardboardVRDisplay.prototype.updatePresent_ = function () { + this.endPresent_(); + this.beginPresent_(); + }; + CardboardVRDisplay.prototype.submitFrame = function (pose) { + if (this.distorter_) { + this.updateBounds_(); + this.distorter_.submitFrame(); + } else if (this.cardboardUI_ && this.layer_) { + var gl = this.layer_.source.getContext('webgl'); + if (!gl) gl = this.layer_.source.getContext('experimental-webgl'); + if (!gl) gl = this.layer_.source.getContext('webgl2'); + var canvas = gl.canvas; + if (canvas.width != this.lastWidth || canvas.height != this.lastHeight) { + this.cardboardUI_.onResize(); + } + this.lastWidth = canvas.width; + this.lastHeight = canvas.height; + this.cardboardUI_.render(); + } + }; + CardboardVRDisplay.prototype.onOrientationChange_ = function (e) { + this.viewerSelector_.hide(); + if (this.rotateInstructions_) { + this.rotateInstructions_.update(); + } + this.onResize_(); + }; + CardboardVRDisplay.prototype.onResize_ = function (e) { + if (this.layer_) { + var gl = this.layer_.source.getContext('webgl'); + if (!gl) gl = this.layer_.source.getContext('experimental-webgl'); + if (!gl) gl = this.layer_.source.getContext('webgl2'); + var cssProperties = ['position: absolute', 'top: 0', 'left: 0', 'width: 100vw', 'height: 100vh', 'border: 0', 'margin: 0', 'padding: 0px', 'box-sizing: content-box']; + gl.canvas.setAttribute('style', cssProperties.join('; ') + ';'); + safariCssSizeWorkaround(gl.canvas); + } + }; + CardboardVRDisplay.prototype.onViewerChanged_ = function (viewer) { + this.deviceInfo_.setViewer(viewer); + if (this.distorter_) { + this.distorter_.updateDeviceInfo(this.deviceInfo_); + } + this.fireVRDisplayDeviceParamsChange_(); + }; + CardboardVRDisplay.prototype.fireVRDisplayDeviceParamsChange_ = function () { + var event = new CustomEvent('vrdisplaydeviceparamschange', { + detail: { + vrdisplay: this, + deviceInfo: this.deviceInfo_ + } + }); + window.dispatchEvent(event); + }; + CardboardVRDisplay.VRFrameData = VRFrameData; + CardboardVRDisplay.VRDisplay = VRDisplay; + return CardboardVRDisplay; + }); + }); + var CardboardVRDisplay = unwrapExports(cardboardVrDisplay); + var version = "0.10.12"; + var DefaultConfig = { + ADDITIONAL_VIEWERS: [], + DEFAULT_VIEWER: '', + PROVIDE_MOBILE_VRDISPLAY: true, + MOBILE_WAKE_LOCK: true, + DEBUG: false, + DPDB_URL: 'https://dpdb.webvr.rocks/dpdb.json', + K_FILTER: 0.98, + PREDICTION_TIME_S: 0.040, + CARDBOARD_UI_DISABLED: false, + ROTATE_INSTRUCTIONS_DISABLED: false, + YAW_ONLY: false, + BUFFER_SCALE: 0.5, + DIRTY_SUBMIT_FRAME_BINDINGS: false + }; + function WebVRPolyfill(config) { + this.config = extend(extend({}, DefaultConfig), config); + this.polyfillDisplays = []; + this.enabled = false; + this.hasNative = 'getVRDisplays' in navigator; + this.native = {}; + this.native.getVRDisplays = navigator.getVRDisplays; + this.native.VRFrameData = window.VRFrameData; + this.native.VRDisplay = window.VRDisplay; + if (!this.hasNative || this.config.PROVIDE_MOBILE_VRDISPLAY && isMobile()) { + this.enable(); + this.getVRDisplays().then(function (displays) { + if (displays && displays[0] && displays[0].fireVRDisplayConnect_) { + displays[0].fireVRDisplayConnect_(); + } + }); + } + } + WebVRPolyfill.prototype.getPolyfillDisplays = function () { + if (this._polyfillDisplaysPopulated) { + return this.polyfillDisplays; + } + if (isMobile()) { + var vrDisplay = new CardboardVRDisplay({ + ADDITIONAL_VIEWERS: this.config.ADDITIONAL_VIEWERS, + DEFAULT_VIEWER: this.config.DEFAULT_VIEWER, + MOBILE_WAKE_LOCK: this.config.MOBILE_WAKE_LOCK, + DEBUG: this.config.DEBUG, + DPDB_URL: this.config.DPDB_URL, + CARDBOARD_UI_DISABLED: this.config.CARDBOARD_UI_DISABLED, + K_FILTER: this.config.K_FILTER, + PREDICTION_TIME_S: this.config.PREDICTION_TIME_S, + ROTATE_INSTRUCTIONS_DISABLED: this.config.ROTATE_INSTRUCTIONS_DISABLED, + YAW_ONLY: this.config.YAW_ONLY, + BUFFER_SCALE: this.config.BUFFER_SCALE, + DIRTY_SUBMIT_FRAME_BINDINGS: this.config.DIRTY_SUBMIT_FRAME_BINDINGS + }); + this.polyfillDisplays.push(vrDisplay); + } + this._polyfillDisplaysPopulated = true; + return this.polyfillDisplays; + }; + WebVRPolyfill.prototype.enable = function () { + this.enabled = true; + if (this.hasNative && this.native.VRFrameData) { + var NativeVRFrameData = this.native.VRFrameData; + var nativeFrameData = new this.native.VRFrameData(); + var nativeGetFrameData = this.native.VRDisplay.prototype.getFrameData; + window.VRDisplay.prototype.getFrameData = function (frameData) { + if (frameData instanceof NativeVRFrameData) { + nativeGetFrameData.call(this, frameData); + return; + } + nativeGetFrameData.call(this, nativeFrameData); + frameData.pose = nativeFrameData.pose; + copyArray(nativeFrameData.leftProjectionMatrix, frameData.leftProjectionMatrix); + copyArray(nativeFrameData.rightProjectionMatrix, frameData.rightProjectionMatrix); + copyArray(nativeFrameData.leftViewMatrix, frameData.leftViewMatrix); + copyArray(nativeFrameData.rightViewMatrix, frameData.rightViewMatrix); + }; + } + navigator.getVRDisplays = this.getVRDisplays.bind(this); + window.VRDisplay = CardboardVRDisplay.VRDisplay; + window.VRFrameData = CardboardVRDisplay.VRFrameData; + }; + WebVRPolyfill.prototype.getVRDisplays = function () { + var _this = this; + var config = this.config; + if (!this.hasNative) { + return Promise.resolve(this.getPolyfillDisplays()); + } + return this.native.getVRDisplays.call(navigator).then(function (nativeDisplays) { + return nativeDisplays.length > 0 ? nativeDisplays : _this.getPolyfillDisplays(); + }); + }; + WebVRPolyfill.version = version; + WebVRPolyfill.VRFrameData = CardboardVRDisplay.VRFrameData; + WebVRPolyfill.VRDisplay = CardboardVRDisplay.VRDisplay; + var webvrPolyfill = Object.freeze({ + default: WebVRPolyfill + }); + var require$$0 = webvrPolyfill && WebVRPolyfill || webvrPolyfill; + if (typeof commonjsGlobal !== 'undefined' && commonjsGlobal.window) { + if (!commonjsGlobal.document) { + commonjsGlobal.document = commonjsGlobal.window.document; + } + if (!commonjsGlobal.navigator) { + commonjsGlobal.navigator = commonjsGlobal.window.navigator; + } + } + var src = require$$0; + return src; +}); + +/***/ }), + +/***/ "./node_modules/word-wrapper/index.js": +/*!********************************************!*\ + !*** ./node_modules/word-wrapper/index.js ***! + \********************************************/ +/***/ ((module) => { + +var newline = /\n/; +var newlineChar = '\n'; +var whitespace = /\s/; +module.exports = function (text, opt) { + var lines = module.exports.lines(text, opt); + return lines.map(function (line) { + return text.substring(line.start, line.end); + }).join('\n'); +}; +module.exports.lines = function wordwrap(text, opt) { + opt = opt || {}; + + //zero width results in nothing visible + if (opt.width === 0 && opt.mode !== 'nowrap') return []; + text = text || ''; + var width = typeof opt.width === 'number' ? opt.width : Number.MAX_VALUE; + var start = Math.max(0, opt.start || 0); + var end = typeof opt.end === 'number' ? opt.end : text.length; + var mode = opt.mode; + var measure = opt.measure || monospace; + if (mode === 'pre') return pre(measure, text, start, end, width);else return greedy(measure, text, start, end, width, mode); +}; +function idxOf(text, chr, start, end) { + var idx = text.indexOf(chr, start); + if (idx === -1 || idx > end) return end; + return idx; +} +function isWhitespace(chr) { + return whitespace.test(chr); +} +function pre(measure, text, start, end, width) { + var lines = []; + var lineStart = start; + for (var i = start; i < end && i < text.length; i++) { + var chr = text.charAt(i); + var isNewline = newline.test(chr); + + //If we've reached a newline, then step down a line + //Or if we've reached the EOF + if (isNewline || i === end - 1) { + var lineEnd = isNewline ? i : i + 1; + var measured = measure(text, lineStart, lineEnd, width); + lines.push(measured); + lineStart = i + 1; + } + } + return lines; +} +function greedy(measure, text, start, end, width, mode) { + //A greedy word wrapper based on LibGDX algorithm + //https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/BitmapFontCache.java + var lines = []; + var testWidth = width; + //if 'nowrap' is specified, we only wrap on newline chars + if (mode === 'nowrap') testWidth = Number.MAX_VALUE; + while (start < end && start < text.length) { + //get next newline position + var newLine = idxOf(text, newlineChar, start, end); + + //eat whitespace at start of line + while (start < newLine) { + if (!isWhitespace(text.charAt(start))) break; + start++; + } + + //determine visible # of glyphs for the available width + var measured = measure(text, start, newLine, testWidth); + var lineEnd = start + (measured.end - measured.start); + var nextStart = lineEnd + newlineChar.length; + + //if we had to cut the line before the next newline... + if (lineEnd < newLine) { + //find char to break on + while (lineEnd > start) { + if (isWhitespace(text.charAt(lineEnd))) break; + lineEnd--; + } + if (lineEnd === start) { + if (nextStart > start + newlineChar.length) nextStart--; + lineEnd = nextStart; // If no characters to break, show all. + } else { + nextStart = lineEnd; + //eat whitespace at end of line + while (lineEnd > start) { + if (!isWhitespace(text.charAt(lineEnd - newlineChar.length))) break; + lineEnd--; + } + } + } + if (lineEnd >= start) { + var result = measure(text, start, lineEnd, testWidth); + lines.push(result); + } + start = nextStart; + } + return lines; +} + +//determines the visible number of glyphs within a given width +function monospace(text, start, end, width) { + var glyphs = Math.min(width, end - start); + return { + start: start, + end: start + glyphs + }; +} + +/***/ }), + +/***/ "./node_modules/xhr/index.js": +/*!***********************************!*\ + !*** ./node_modules/xhr/index.js ***! + \***********************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +"use strict"; + + +var window = __webpack_require__(/*! global/window */ "./node_modules/global/window.js"); +var isFunction = __webpack_require__(/*! is-function */ "./node_modules/is-function/index.js"); +var parseHeaders = __webpack_require__(/*! parse-headers */ "./node_modules/parse-headers/parse-headers.js"); +var xtend = __webpack_require__(/*! xtend */ "./node_modules/xtend/immutable.js"); +module.exports = createXHR; +// Allow use of default import syntax in TypeScript +module.exports["default"] = createXHR; +createXHR.XMLHttpRequest = window.XMLHttpRequest || noop; +createXHR.XDomainRequest = "withCredentials" in new createXHR.XMLHttpRequest() ? createXHR.XMLHttpRequest : window.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(options)) { + callback = options; + if (typeof uri === "string") { + params = { + uri: uri + }; + } + } else { + params = xtend(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() {} + +/***/ }), + +/***/ "./node_modules/xml-parse-from-string/index.js": +/*!*****************************************************!*\ + !*** ./node_modules/xml-parse-from-string/index.js ***! + \*****************************************************/ +/***/ ((module) => { + +module.exports = function xmlparser() { + //common browsers + if (typeof self.DOMParser !== 'undefined') { + return function (str) { + var parser = new self.DOMParser(); + return parser.parseFromString(str, 'application/xml'); + }; + } + + //IE8 fallback + if (typeof self.ActiveXObject !== 'undefined' && new self.ActiveXObject('Microsoft.XMLDOM')) { + return function (str) { + var xmlDoc = new self.ActiveXObject("Microsoft.XMLDOM"); + xmlDoc.async = "false"; + xmlDoc.loadXML(str); + return xmlDoc; + }; + } + + //last resort fallback + return function (str) { + var div = document.createElement('div'); + div.innerHTML = str; + return div; + }; +}(); + +/***/ }), + +/***/ "./node_modules/xtend/immutable.js": +/*!*****************************************!*\ + !*** ./node_modules/xtend/immutable.js ***! + \*****************************************/ +/***/ ((module) => { + +module.exports = 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; +} + +/***/ }), + +/***/ "./src/components/anchored.js": +/*!************************************!*\ + !*** ./src/components/anchored.js ***! + \************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +/* global THREE, XRRigidTransform, localStorage */ +var registerComponent = (__webpack_require__(/*! ../core/component */ "./src/core/component.js").registerComponent); +var utils = __webpack_require__(/*! ../utils/ */ "./src/utils/index.js"); +var warn = utils.debug('components:anchored:warn'); + +/** + * Anchored component. + * Feature only available in browsers that implement the WebXR anchors module. + * Once anchored the entity remains to a fixed position in real-world space. + * If the anchor is persistent, the anchor positioned remains across sessions or until the browser data is cleared. + */ +module.exports.Component = registerComponent('anchored', { + schema: { + persistent: { + default: false + } + }, + init: function () { + var sceneEl = this.el.sceneEl; + var webxrData = sceneEl.getAttribute('webxr'); + var optionalFeaturesArray = webxrData.optionalFeatures; + if (optionalFeaturesArray.indexOf('anchors') === -1) { + optionalFeaturesArray.push('anchors'); + this.el.sceneEl.setAttribute('webxr', webxrData); + } + this.auxQuaternion = new THREE.Quaternion(); + this.onEnterVR = this.onEnterVR.bind(this); + this.el.sceneEl.addEventListener('enter-vr', this.onEnterVR); + }, + onEnterVR: function () { + this.anchor = undefined; + this.requestPersistentAnchorPending = this.data.persistent; + this.requestAnchorPending = !this.data.persistent; + }, + tick: function () { + var sceneEl = this.el.sceneEl; + var xrManager = sceneEl.renderer.xr; + var frame; + var refSpace; + var pose; + var object3D = this.el.object3D; + if (!sceneEl.is('ar-mode') && !sceneEl.is('vr-mode')) { + return; + } + if (!this.anchor && this.requestPersistentAnchorPending) { + this.restorePersistentAnchor(); + } + if (!this.anchor && this.requestAnchorPending) { + this.createAnchor(); + } + if (!this.anchor) { + return; + } + frame = sceneEl.frame; + refSpace = xrManager.getReferenceSpace(); + pose = frame.getPose(this.anchor.anchorSpace, refSpace); + object3D.matrix.elements = pose.transform.matrix; + object3D.matrix.decompose(object3D.position, object3D.rotation, object3D.scale); + }, + createAnchor: async function createAnchor(position, quaternion) { + var sceneEl = this.el.sceneEl; + var xrManager = sceneEl.renderer.xr; + var frame; + var referenceSpace; + var anchorPose; + var anchor; + var object3D = this.el.object3D; + position = position || object3D.position; + quaternion = quaternion || this.auxQuaternion.setFromEuler(object3D.rotation); + if (!anchorsSupported(sceneEl)) { + warn('This browser doesn\'t support the WebXR anchors module'); + return; + } + if (this.anchor) { + this.deleteAnchor(); + } + frame = sceneEl.frame; + referenceSpace = xrManager.getReferenceSpace(); + anchorPose = new XRRigidTransform({ + x: position.x, + y: position.y, + z: position.z + }, { + x: quaternion.x, + y: quaternion.y, + z: quaternion.z, + w: quaternion.w + }); + this.requestAnchorPending = false; + anchor = await frame.createAnchor(anchorPose, referenceSpace); + if (this.data.persistent) { + if (this.el.id) { + this.persistentHandle = await anchor.requestPersistentHandle(); + localStorage.setItem(this.el.id, this.persistentHandle); + } else { + warn('The anchor won\'t be persisted because the entity has no assigned id.'); + } + } + sceneEl.object3D.attach(this.el.object3D); + this.anchor = anchor; + }, + restorePersistentAnchor: async function restorePersistentAnchor() { + var xrManager = this.el.sceneEl.renderer.xr; + var session = xrManager.getSession(); + var persistentAnchors = session.persistentAnchors; + var storedPersistentHandle; + this.requestPersistentAnchorPending = false; + if (!this.el.id) { + warn('The entity associated to the persistent anchor cannot be retrieved because it doesn\'t have an assigned id.'); + this.requestAnchorPending = true; + return; + } + if (persistentAnchors) { + storedPersistentHandle = localStorage.getItem(this.el.id); + for (var i = 0; i < persistentAnchors.length; ++i) { + if (storedPersistentHandle !== persistentAnchors[i]) { + continue; + } + this.anchor = await session.restorePersistentAnchor(persistentAnchors[i]); + if (this.anchor) { + this.persistentHandle = persistentAnchors[i]; + } + break; + } + if (!this.anchor) { + this.requestAnchorPending = true; + } + } else { + this.requestPersistentAnchorPending = true; + } + }, + deleteAnchor: function () { + var xrManager; + var session; + var anchor = this.anchor; + if (!anchor) { + return; + } + xrManager = this.el.sceneEl.renderer.xr; + session = xrManager.getSession(); + anchor.delete(); + this.el.sceneEl.object3D.add(this.el.object3D); + if (this.persistentHandle) { + session.deletePersistentAnchor(this.persistentHandle); + } + this.anchor = undefined; + } +}); +function anchorsSupported(sceneEl) { + var xrManager = sceneEl.renderer.xr; + var session = xrManager.getSession(); + return session && session.restorePersistentAnchor; +} + +/***/ }), + +/***/ "./src/components/animation.js": +/*!*************************************!*\ + !*** ./src/components/animation.js ***! + \*************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var anime = (__webpack_require__(/*! super-animejs */ "./node_modules/super-animejs/lib/anime.es.js")["default"]); +var components = (__webpack_require__(/*! ../core/component */ "./src/core/component.js").components); +var registerComponent = (__webpack_require__(/*! ../core/component */ "./src/core/component.js").registerComponent); +var THREE = __webpack_require__(/*! ../lib/three */ "./src/lib/three.js"); +var utils = __webpack_require__(/*! ../utils */ "./src/utils/index.js"); +var colorHelperFrom = new THREE.Color(); +var colorHelperTo = new THREE.Color(); +var getComponentProperty = utils.entity.getComponentProperty; +var setComponentProperty = utils.entity.setComponentProperty; +var splitCache = {}; +var TYPE_COLOR = 'color'; +var PROP_POSITION = 'position'; +var PROP_ROTATION = 'rotation'; +var PROP_SCALE = 'scale'; +var STRING_COMPONENTS = 'components'; +var STRING_OBJECT3D = 'object3D'; + +/** + * Animation component for A-Frame using anime.js. + * + * The component manually controls the tick by setting `autoplay: false` on anime.js and + * manually * calling `animation.tick()` in the tick handler. To pause or resume, we toggle a + * boolean * flag * `isAnimationPlaying`. + * + * anime.js animation config for tweenining Javascript objects and values works as: + * + * config = { + * targets: {foo: 0.0, bar: '#000'}, + * foo: 1.0, + * bar: '#FFF' + * } + * + * The above will tween each property in `targets`. The `to` values are set in the root of + * the config. + * + * @member {object} animation - anime.js instance. + * @member {boolean} animationIsPlaying - Control if animation is playing. + */ +module.exports.Component = registerComponent('animation', { + schema: { + autoplay: { + default: true + }, + delay: { + default: 0 + }, + dir: { + default: '' + }, + dur: { + default: 1000 + }, + easing: { + default: 'easeInQuad' + }, + elasticity: { + default: 400 + }, + enabled: { + default: true + }, + from: { + default: '' + }, + loop: { + default: 0, + parse: function (value) { + // Boolean or integer. + if (value === true || value === 'true') { + return true; + } + if (value === false || value === 'false') { + return false; + } + return parseInt(value, 10); + } + }, + property: { + default: '' + }, + startEvents: { + type: 'array' + }, + pauseEvents: { + type: 'array' + }, + resumeEvents: { + type: 'array' + }, + round: { + default: false + }, + to: { + default: '' + }, + type: { + default: '' + }, + isRawProperty: { + default: false + } + }, + multiple: true, + init: function () { + var self = this; + this.eventDetail = { + name: this.attrName + }; + this.time = 0; + this.animation = null; + this.animationIsPlaying = false; + this.onStartEvent = this.onStartEvent.bind(this); + this.beginAnimation = this.beginAnimation.bind(this); + this.pauseAnimation = this.pauseAnimation.bind(this); + this.resumeAnimation = this.resumeAnimation.bind(this); + this.fromColor = {}; + this.toColor = {}; + this.targets = {}; + this.targetsArray = []; + this.updateConfigForDefault = this.updateConfigForDefault.bind(this); + this.updateConfigForRawColor = this.updateConfigForRawColor.bind(this); + this.config = { + complete: function () { + self.animationIsPlaying = false; + self.el.emit('animationcomplete', self.eventDetail, false); + if (self.id) { + self.el.emit('animationcomplete__' + self.id, self.eventDetail, false); + } + } + }; + }, + update: function (oldData) { + var config = this.config; + var data = this.data; + this.animationIsPlaying = false; + if (!this.data.enabled) { + return; + } + if (!data.property) { + return; + } + + // Base config. + config.autoplay = false; + config.direction = data.dir; + config.duration = data.dur; + config.easing = data.easing; + config.elasticity = data.elasticity; + config.loop = data.loop; + config.round = data.round; + + // Start new animation. + this.createAndStartAnimation(); + }, + tick: function (t, dt) { + if (!this.animationIsPlaying) { + return; + } + this.time += dt; + this.animation.tick(this.time); + }, + remove: function () { + this.pauseAnimation(); + this.removeEventListeners(); + }, + pause: function () { + this.paused = true; + this.pausedWasPlaying = this.animationIsPlaying; + this.pauseAnimation(); + this.removeEventListeners(); + }, + /** + * `play` handler only for resuming scene. + */ + play: function () { + if (!this.paused) { + return; + } + this.paused = false; + this.addEventListeners(); + if (this.pausedWasPlaying) { + this.resumeAnimation(); + this.pausedWasPlaying = false; + } + }, + /** + * Start animation from scratch. + */ + createAndStartAnimation: function () { + var data = this.data; + this.updateConfig(); + this.animationIsPlaying = false; + this.animation = anime(this.config); + this.animation.began = true; + this.removeEventListeners(); + this.addEventListeners(); + + // Wait for start events for animation. + if (!data.autoplay || data.startEvents && data.startEvents.length) { + return; + } + + // Delay animation. + if (data.delay) { + setTimeout(this.beginAnimation, data.delay); + return; + } + + // Play animation. + this.beginAnimation(); + }, + /** + * This is before animation start (including from startEvents). + * Set to initial state (config.from, time = 0, seekTime = 0). + */ + beginAnimation: function () { + this.updateConfig(); + this.animation.began = true; + this.time = 0; + this.animationIsPlaying = true; + this.stopRelatedAnimations(); + this.el.emit('animationbegin', this.eventDetail, false); + }, + pauseAnimation: function () { + this.animationIsPlaying = false; + }, + resumeAnimation: function () { + this.animationIsPlaying = true; + }, + /** + * startEvents callback. + */ + onStartEvent: function () { + if (!this.data.enabled) { + return; + } + this.updateConfig(); + if (this.animation) { + this.animation.pause(); + } + this.animation = anime(this.config); + + // Include the delay before each start event. + if (this.data.delay) { + setTimeout(this.beginAnimation, this.data.delay); + return; + } + this.beginAnimation(); + }, + /** + * rawProperty: true and type: color; + */ + updateConfigForRawColor: function () { + var config = this.config; + var data = this.data; + var el = this.el; + var from; + var key; + var to; + if (this.waitComponentInitRawProperty(this.updateConfigForRawColor)) { + return; + } + from = data.from === '' ? getRawProperty(el, data.property) : data.from; + to = data.to; + + // Use r/g/b vector for color type. + this.setColorConfig(from, to); + from = this.fromColor; + to = this.toColor; + this.targetsArray.length = 0; + this.targetsArray.push(from); + config.targets = this.targetsArray; + for (key in to) { + config[key] = to[key]; + } + config.update = function () { + var lastValue = {}; + return function (anim) { + var value; + value = anim.animatables[0].target; + // For animation timeline. + if (value.r === lastValue.r && value.g === lastValue.g && value.b === lastValue.b) { + return; + } + setRawProperty(el, data.property, value, data.type); + }; + }(); + }, + /** + * Stuff property into generic `property` key. + */ + updateConfigForDefault: function () { + var config = this.config; + var data = this.data; + var el = this.el; + var from; + var isBoolean; + var isNumber; + var to; + if (this.waitComponentInitRawProperty(this.updateConfigForDefault)) { + return; + } + if (data.from === '') { + // Infer from. + from = isRawProperty(data) ? getRawProperty(el, data.property) : getComponentProperty(el, data.property); + } else { + // Explicit from. + from = data.from; + } + to = data.to; + isNumber = !isNaN(from || to); + if (isNumber) { + from = parseFloat(from); + to = parseFloat(to); + } else { + from = from ? from.toString() : from; + to = to ? to.toString() : to; + } + + // Convert booleans to integer to allow boolean flipping. + isBoolean = data.to === 'true' || data.to === 'false' || data.to === true || data.to === false; + if (isBoolean) { + from = data.from === 'true' || data.from === true ? 1 : 0; + to = data.to === 'true' || data.to === true ? 1 : 0; + } + this.targets.aframeProperty = from; + config.targets = this.targets; + config.aframeProperty = to; + config.update = function () { + var lastValue; + return function (anim) { + var value; + value = anim.animatables[0].target.aframeProperty; + + // Need to do a last value check for animation timeline since all the tweening + // begins simultaenously even if the value has not changed. Also better for perf + // anyways. + if (value === lastValue) { + return; + } + lastValue = value; + if (isBoolean) { + value = value >= 1; + } + if (isRawProperty(data)) { + setRawProperty(el, data.property, value, data.type); + } else { + setComponentProperty(el, data.property, value); + } + }; + }(); + }, + /** + * Extend x/y/z/w onto the config. + * Update vector by modifying object3D. + */ + updateConfigForVector: function () { + var config = this.config; + var data = this.data; + var el = this.el; + var key; + var from; + var to; + + // Parse coordinates. + from = data.from !== '' ? utils.coordinates.parse(data.from) // If data.from defined, use that. + : getComponentProperty(el, data.property); // If data.from not defined, get on the fly. + to = utils.coordinates.parse(data.to); + if (data.property === PROP_ROTATION) { + toRadians(from); + toRadians(to); + } + + // Set to and from. + this.targetsArray.length = 0; + this.targetsArray.push(from); + config.targets = this.targetsArray; + for (key in to) { + config[key] = to[key]; + } + + // If animating object3D transformation, run more optimized updater. + if (data.property === PROP_POSITION || data.property === PROP_ROTATION || data.property === PROP_SCALE) { + config.update = function () { + var lastValue = {}; + return function (anim) { + var value = anim.animatables[0].target; + + // For animation timeline. + if (value.x === lastValue.x && value.y === lastValue.y && value.z === lastValue.z) { + return; + } + lastValue.x = value.x; + lastValue.y = value.y; + lastValue.z = value.z; + el.object3D[data.property].set(value.x, value.y, value.z); + }; + }(); + return; + } + + // Animating some vector. + config.update = function () { + var lastValue = {}; + return function (anim) { + var value = anim.animatables[0].target; + + // Animate rotation through radians. + // For animation timeline. + if (value.x === lastValue.x && value.y === lastValue.y && value.z === lastValue.z) { + return; + } + lastValue.x = value.x; + lastValue.y = value.y; + lastValue.z = value.z; + setComponentProperty(el, data.property, value); + }; + }(); + }, + /** + * Update the config before each run. + */ + updateConfig: function () { + var propType; + + // Route config type. + propType = getPropertyType(this.el, this.data.property); + if (isRawProperty(this.data) && this.data.type === TYPE_COLOR) { + this.updateConfigForRawColor(); + } else if (propType === 'vec2' || propType === 'vec3' || propType === 'vec4') { + this.updateConfigForVector(); + } else { + this.updateConfigForDefault(); + } + }, + /** + * Wait for component to initialize. + */ + waitComponentInitRawProperty: function (cb) { + var componentName; + var data = this.data; + var el = this.el; + var self = this; + if (data.from !== '') { + return false; + } + if (!data.property.startsWith(STRING_COMPONENTS)) { + return false; + } + componentName = splitDot(data.property)[1]; + if (el.components[componentName]) { + return false; + } + el.addEventListener('componentinitialized', function wait(evt) { + if (evt.detail.name !== componentName) { + return; + } + cb(); + // Since the config was created async, create the animation now since we missed it + // earlier. + self.animation = anime(self.config); + el.removeEventListener('componentinitialized', wait); + }); + return true; + }, + /** + * Make sure two animations on the same property don't fight each other. + * e.g., animation__mouseenter="property: material.opacity" + * animation__mouseleave="property: material.opacity" + */ + stopRelatedAnimations: function () { + var component; + var componentName; + for (componentName in this.el.components) { + component = this.el.components[componentName]; + if (componentName === this.attrName) { + continue; + } + if (component.name !== 'animation') { + continue; + } + if (!component.animationIsPlaying) { + continue; + } + if (component.data.property !== this.data.property) { + continue; + } + component.animationIsPlaying = false; + } + }, + addEventListeners: function () { + var data = this.data; + var el = this.el; + addEventListeners(el, data.startEvents, this.onStartEvent); + addEventListeners(el, data.pauseEvents, this.pauseAnimation); + addEventListeners(el, data.resumeEvents, this.resumeAnimation); + }, + removeEventListeners: function () { + var data = this.data; + var el = this.el; + removeEventListeners(el, data.startEvents, this.onStartEvent); + removeEventListeners(el, data.pauseEvents, this.pauseAnimation); + removeEventListeners(el, data.resumeEvents, this.resumeAnimation); + }, + setColorConfig: function (from, to) { + colorHelperFrom.set(from); + colorHelperTo.set(to); + from = this.fromColor; + to = this.toColor; + from.r = colorHelperFrom.r; + from.g = colorHelperFrom.g; + from.b = colorHelperFrom.b; + to.r = colorHelperTo.r; + to.g = colorHelperTo.g; + to.b = colorHelperTo.b; + } +}); + +/** + * Given property name, check schema to see what type we are animating. + * We just care whether the property is a vector. + */ +function getPropertyType(el, property) { + var component; + var componentName; + var split; + var propertyName; + split = property.split('.'); + componentName = split[0]; + propertyName = split[1]; + component = el.components[componentName] || components[componentName]; + + // Primitives. + if (!component) { + return null; + } + + // Dynamic schema. We only care about vectors anyways. + if (propertyName && !component.schema[propertyName]) { + return null; + } + + // Multi-prop. + if (propertyName) { + return component.schema[propertyName].type; + } + + // Single-prop. + return component.schema.type; +} + +/** + * Convert object to radians. + */ +function toRadians(obj) { + obj.x = THREE.MathUtils.degToRad(obj.x); + obj.y = THREE.MathUtils.degToRad(obj.y); + obj.z = THREE.MathUtils.degToRad(obj.z); +} +function addEventListeners(el, eventNames, handler) { + var i; + for (i = 0; i < eventNames.length; i++) { + el.addEventListener(eventNames[i], handler); + } +} +function removeEventListeners(el, eventNames, handler) { + var i; + for (i = 0; i < eventNames.length; i++) { + el.removeEventListener(eventNames[i], handler); + } +} +function getRawProperty(el, path) { + var i; + var split; + var value; + split = splitDot(path); + value = el; + for (i = 0; i < split.length; i++) { + value = value[split[i]]; + } + if (value === undefined) { + console.log(el); + throw new Error('[animation] property (' + path + ') could not be found'); + } + return value; +} +function setRawProperty(el, path, value, type) { + var i; + var split; + var propertyName; + var targetValue; + if (path.startsWith('object3D.rotation')) { + value = THREE.MathUtils.degToRad(value); + } + + // Walk. + split = splitDot(path); + targetValue = el; + for (i = 0; i < split.length - 1; i++) { + targetValue = targetValue[split[i]]; + } + propertyName = split[split.length - 1]; + + // Raw color. + if (type === TYPE_COLOR) { + if ('r' in targetValue[propertyName]) { + targetValue[propertyName].r = value.r; + targetValue[propertyName].g = value.g; + targetValue[propertyName].b = value.b; + } else { + targetValue[propertyName].x = value.r; + targetValue[propertyName].y = value.g; + targetValue[propertyName].z = value.b; + } + return; + } + targetValue[propertyName] = value; +} +function splitDot(path) { + if (path in splitCache) { + return splitCache[path]; + } + splitCache[path] = path.split('.'); + return splitCache[path]; +} +function isRawProperty(data) { + return data.isRawProperty || data.property.startsWith(STRING_COMPONENTS) || data.property.startsWith(STRING_OBJECT3D); +} + +/***/ }), + +/***/ "./src/components/camera.js": +/*!**********************************!*\ + !*** ./src/components/camera.js ***! + \**********************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var registerComponent = (__webpack_require__(/*! ../core/component */ "./src/core/component.js").registerComponent); +var THREE = __webpack_require__(/*! ../lib/three */ "./src/lib/three.js"); + +/** + * Camera component. + * Pairs along with camera system to handle tracking the active camera. + */ +module.exports.Component = registerComponent('camera', { + schema: { + active: { + default: true + }, + far: { + default: 10000 + }, + fov: { + default: 80, + min: 0 + }, + near: { + default: 0.005, + min: 0 + }, + spectator: { + default: false + }, + zoom: { + default: 1, + min: 0 + } + }, + /** + * Initialize three.js camera and add it to the entity. + * Add reference from scene to this entity as the camera. + */ + init: function () { + var camera; + var el = this.el; + + // Create camera. + camera = this.camera = new THREE.PerspectiveCamera(); + el.setObject3D('camera', camera); + }, + /** + * Update three.js camera. + */ + update: function (oldData) { + var data = this.data; + var camera = this.camera; + + // Update properties. + camera.aspect = data.aspect || window.innerWidth / window.innerHeight; + camera.far = data.far; + camera.fov = data.fov; + camera.near = data.near; + camera.zoom = data.zoom; + camera.updateProjectionMatrix(); + this.updateActiveCamera(oldData); + this.updateSpectatorCamera(oldData); + }, + updateActiveCamera: function (oldData) { + var data = this.data; + var el = this.el; + var system = this.system; + // Active property did not change. + if (oldData && oldData.active === data.active || data.spectator) { + return; + } + + // If `active` property changes, or first update, handle active camera with system. + if (data.active && system.activeCameraEl !== el) { + // Camera enabled. Set camera to this camera. + system.setActiveCamera(el); + } else if (!data.active && system.activeCameraEl === el) { + // Camera disabled. Set camera to another camera. + system.disableActiveCamera(); + } + }, + updateSpectatorCamera: function (oldData) { + var data = this.data; + var el = this.el; + var system = this.system; + // spectator property did not change. + if (oldData && oldData.spectator === data.spectator) { + return; + } + + // If `spectator` property changes, or first update, handle spectator camera with system. + if (data.spectator && system.spectatorCameraEl !== el) { + // Camera enabled. Set camera to this camera. + system.setSpectatorCamera(el); + } else if (!data.spectator && system.spectatorCameraEl === el) { + // Camera disabled. Set camera to another camera. + system.disableSpectatorCamera(); + } + }, + /** + * Remove camera on remove (callback). + */ + remove: function () { + this.el.removeObject3D('camera'); + } +}); + +/***/ }), + +/***/ "./src/components/cursor.js": +/*!**********************************!*\ + !*** ./src/components/cursor.js ***! + \**********************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +/* global THREE, MouseEvent, TouchEvent */ +var registerComponent = (__webpack_require__(/*! ../core/component */ "./src/core/component.js").registerComponent); +var utils = __webpack_require__(/*! ../utils/ */ "./src/utils/index.js"); +var bind = utils.bind; +var EVENTS = { + CLICK: 'click', + FUSING: 'fusing', + MOUSEENTER: 'mouseenter', + MOUSEDOWN: 'mousedown', + MOUSELEAVE: 'mouseleave', + MOUSEUP: 'mouseup' +}; +var STATES = { + FUSING: 'cursor-fusing', + HOVERING: 'cursor-hovering', + HOVERED: 'cursor-hovered' +}; +var CANVAS_EVENTS = { + DOWN: ['mousedown', 'touchstart'], + UP: ['mouseup', 'touchend'] +}; +var WEBXR_EVENTS = { + DOWN: ['selectstart'], + UP: ['selectend'] +}; +var CANVAS_HOVER_CLASS = 'a-mouse-cursor-hover'; + +/** + * Cursor component. Applies the raycaster component specifically for starting the raycaster + * from the camera and pointing from camera's facing direction, and then only returning the + * closest intersection. Cursor can be fine-tuned by setting raycaster properties. + * + * @member {object} fuseTimeout - Timeout to trigger fuse-click. + * @member {Element} cursorDownEl - Entity that was last mousedowned during current click. + * @member {object} intersection - Attributes of the current intersection event, including + * 3D- and 2D-space coordinates. See: http://threejs.org/docs/api/core/Raycaster.html + * @member {Element} intersectedEl - Currently-intersected entity. Used to keep track to + * emit events when unintersecting. + */ +module.exports.Component = registerComponent('cursor', { + dependencies: ['raycaster'], + schema: { + downEvents: { + default: [] + }, + fuse: { + default: utils.device.isMobile() + }, + fuseTimeout: { + default: 1500, + min: 0 + }, + mouseCursorStylesEnabled: { + default: true + }, + upEvents: { + default: [] + }, + rayOrigin: { + default: 'entity', + oneOf: ['mouse', 'entity', 'xrselect'] + } + }, + multiple: true, + init: function () { + var self = this; + this.fuseTimeout = undefined; + this.cursorDownEl = null; + this.intersectedEl = null; + this.canvasBounds = document.body.getBoundingClientRect(); + this.isCursorDown = false; + this.activeXRInput = null; + + // Debounce. + this.updateCanvasBounds = utils.debounce(function updateCanvasBounds() { + self.canvasBounds = self.el.sceneEl.canvas.getBoundingClientRect(); + }, 500); + this.eventDetail = {}; + this.intersectedEventDetail = { + cursorEl: this.el + }; + + // Bind methods. + this.onCursorDown = bind(this.onCursorDown, this); + this.onCursorUp = bind(this.onCursorUp, this); + this.onIntersection = bind(this.onIntersection, this); + this.onIntersectionCleared = bind(this.onIntersectionCleared, this); + this.onMouseMove = bind(this.onMouseMove, this); + this.onEnterVR = bind(this.onEnterVR, this); + }, + update: function (oldData) { + if (this.data.rayOrigin === oldData.rayOrigin) { + return; + } + this.updateMouseEventListeners(); + }, + tick: function () { + // Update on frame to allow someone to select and mousemove + var frame = this.el.sceneEl.frame; + var inputSource = this.activeXRInput; + if (this.data.rayOrigin === 'xrselect' && frame && inputSource) { + this.onMouseMove({ + frame: frame, + inputSource: inputSource, + type: 'fakeselectevent' + }); + } + }, + play: function () { + this.addEventListeners(); + }, + pause: function () { + this.removeEventListeners(); + }, + remove: function () { + var el = this.el; + el.removeState(STATES.HOVERING); + el.removeState(STATES.FUSING); + clearTimeout(this.fuseTimeout); + if (this.intersectedEl) { + this.intersectedEl.removeState(STATES.HOVERED); + } + this.removeEventListeners(); + }, + addEventListeners: function () { + var canvas; + var data = this.data; + var el = this.el; + var self = this; + function addCanvasListeners() { + canvas = el.sceneEl.canvas; + if (data.downEvents.length || data.upEvents.length) { + return; + } + CANVAS_EVENTS.DOWN.forEach(function (downEvent) { + canvas.addEventListener(downEvent, self.onCursorDown); + }); + CANVAS_EVENTS.UP.forEach(function (upEvent) { + canvas.addEventListener(upEvent, self.onCursorUp); + }); + } + canvas = el.sceneEl.canvas; + if (canvas) { + addCanvasListeners(); + } else { + el.sceneEl.addEventListener('render-target-loaded', addCanvasListeners); + } + data.downEvents.forEach(function (downEvent) { + el.addEventListener(downEvent, self.onCursorDown); + }); + data.upEvents.forEach(function (upEvent) { + el.addEventListener(upEvent, self.onCursorUp); + }); + el.addEventListener('raycaster-intersection', this.onIntersection); + el.addEventListener('raycaster-closest-entity-changed', this.onIntersection); + el.addEventListener('raycaster-intersection-cleared', this.onIntersectionCleared); + el.sceneEl.addEventListener('rendererresize', this.updateCanvasBounds); + el.sceneEl.addEventListener('enter-vr', this.onEnterVR); + window.addEventListener('resize', this.updateCanvasBounds); + window.addEventListener('scroll', this.updateCanvasBounds); + this.updateMouseEventListeners(); + }, + removeEventListeners: function () { + var canvas; + var data = this.data; + var el = this.el; + var self = this; + canvas = el.sceneEl.canvas; + if (canvas && !data.downEvents.length && !data.upEvents.length) { + CANVAS_EVENTS.DOWN.forEach(function (downEvent) { + canvas.removeEventListener(downEvent, self.onCursorDown); + }); + CANVAS_EVENTS.UP.forEach(function (upEvent) { + canvas.removeEventListener(upEvent, self.onCursorUp); + }); + } + data.downEvents.forEach(function (downEvent) { + el.removeEventListener(downEvent, self.onCursorDown); + }); + data.upEvents.forEach(function (upEvent) { + el.removeEventListener(upEvent, self.onCursorUp); + }); + el.removeEventListener('raycaster-intersection', this.onIntersection); + el.removeEventListener('raycaster-intersection-cleared', this.onIntersectionCleared); + canvas.removeEventListener('mousemove', this.onMouseMove); + canvas.removeEventListener('touchstart', this.onMouseMove); + canvas.removeEventListener('touchmove', this.onMouseMove); + el.sceneEl.removeEventListener('rendererresize', this.updateCanvasBounds); + el.sceneEl.removeEventListener('enter-vr', this.onEnterVR); + window.removeEventListener('resize', this.updateCanvasBounds); + window.removeEventListener('scroll', this.updateCanvasBounds); + }, + updateMouseEventListeners: function () { + var canvas; + var el = this.el; + canvas = el.sceneEl.canvas; + canvas.removeEventListener('mousemove', this.onMouseMove); + canvas.removeEventListener('touchmove', this.onMouseMove); + el.setAttribute('raycaster', 'useWorldCoordinates', false); + if (this.data.rayOrigin !== 'mouse') { + return; + } + canvas.addEventListener('mousemove', this.onMouseMove, false); + canvas.addEventListener('touchmove', this.onMouseMove, false); + el.setAttribute('raycaster', 'useWorldCoordinates', true); + this.updateCanvasBounds(); + }, + onMouseMove: function () { + var direction = new THREE.Vector3(); + var mouse = new THREE.Vector2(); + var origin = new THREE.Vector3(); + var rayCasterConfig = { + origin: origin, + direction: direction + }; + return function (evt) { + var bounds = this.canvasBounds; + var camera = this.el.sceneEl.camera; + var left; + var point; + var top; + var frame; + var inputSource; + var referenceSpace; + var pose; + var transform; + camera.parent.updateMatrixWorld(); + + // Calculate mouse position based on the canvas element + if (evt.type === 'touchmove' || evt.type === 'touchstart') { + // Track the first touch for simplicity. + point = evt.touches.item(0); + } else { + point = evt; + } + left = point.clientX - bounds.left; + top = point.clientY - bounds.top; + mouse.x = left / bounds.width * 2 - 1; + mouse.y = -(top / bounds.height) * 2 + 1; + if (this.data.rayOrigin === 'xrselect' && (evt.type === 'selectstart' || evt.type === 'fakeselectevent')) { + frame = evt.frame; + inputSource = evt.inputSource; + referenceSpace = this.el.renderer.xr.getReferenceSpace(); + pose = frame.getPose(inputSource.targetRaySpace, referenceSpace); + transform = pose.transform; + direction.set(0, 0, -1); + direction.applyQuaternion(transform.orientation); + origin.copy(transform.position); + } else if (evt.type === 'fakeselectout') { + direction.set(0, 1, 0); + origin.set(0, 9999, 0); + } else if (camera && camera.isPerspectiveCamera) { + origin.setFromMatrixPosition(camera.matrixWorld); + direction.set(mouse.x, mouse.y, 0.5).unproject(camera).sub(origin).normalize(); + } else if (camera && camera.isOrthographicCamera) { + origin.set(mouse.x, mouse.y, (camera.near + camera.far) / (camera.near - camera.far)).unproject(camera); // set origin in plane of camera + direction.set(0, 0, -1).transformDirection(camera.matrixWorld); + } else { + console.error('AFRAME.Raycaster: Unsupported camera type: ' + camera.type); + } + this.el.setAttribute('raycaster', rayCasterConfig); + if (evt.type === 'touchmove') { + evt.preventDefault(); + } + }; + }(), + /** + * Trigger mousedown and keep track of the mousedowned entity. + */ + onCursorDown: function (evt) { + this.isCursorDown = true; + // Raycast again for touch. + if (this.data.rayOrigin === 'mouse' && evt.type === 'touchstart') { + this.onMouseMove(evt); + this.el.components.raycaster.checkIntersections(); + evt.preventDefault(); + } + if (this.data.rayOrigin === 'xrselect' && evt.type === 'selectstart') { + this.activeXRInput = evt.inputSource; + this.onMouseMove(evt); + this.el.components.raycaster.checkIntersections(); + + // if something was tapped on don't do ar-hit-test things + if (this.el.components.raycaster.intersectedEls.length && this.el.sceneEl.components['ar-hit-test'] !== undefined && this.el.sceneEl.getAttribute('ar-hit-test').enabled) { + // Cancel the ar-hit-test behaviours and disable the ar-hit-test + this.el.sceneEl.setAttribute('ar-hit-test', 'enabled', false); + this.reenableARHitTest = true; + } + } + this.twoWayEmit(EVENTS.MOUSEDOWN, evt); + this.cursorDownEl = this.intersectedEl; + }, + /** + * Trigger mouseup if: + * - Not fusing (mobile has no mouse). + * - Currently intersecting an entity. + * - Currently-intersected entity is the same as the one when mousedown was triggered, + * in case user mousedowned one entity, dragged to another, and mouseupped. + */ + onCursorUp: function (evt) { + if (!this.isCursorDown) { + return; + } + this.isCursorDown = false; + var data = this.data; + this.twoWayEmit(EVENTS.MOUSEUP, evt); + if (this.reenableARHitTest === true) { + this.el.sceneEl.setAttribute('ar-hit-test', 'enabled', true); + this.reenableARHitTest = undefined; + } + + // If intersected entity has changed since the cursorDown, still emit mouseUp on the + // previously cursorUp entity. + if (this.cursorDownEl && this.cursorDownEl !== this.intersectedEl) { + this.intersectedEventDetail.intersection = null; + this.cursorDownEl.emit(EVENTS.MOUSEUP, this.intersectedEventDetail); + } + if ((!data.fuse || data.rayOrigin === 'mouse' || data.rayOrigin === 'xrselect') && this.intersectedEl && this.cursorDownEl === this.intersectedEl) { + this.twoWayEmit(EVENTS.CLICK, evt); + } + + // if the current xr input stops selecting then make the ray caster point somewhere else + if (data.rayOrigin === 'xrselect' && this.activeXRInput === evt.inputSource) { + this.onMouseMove({ + type: 'fakeselectout' + }); + } + this.activeXRInput = null; + this.cursorDownEl = null; + if (evt.type === 'touchend') { + evt.preventDefault(); + } + }, + /** + * Handle intersection. + */ + onIntersection: function (evt) { + var currentIntersection; + var cursorEl = this.el; + var index; + var intersectedEl; + var intersection; + + // Select closest object, excluding the cursor. + index = evt.detail.els[0] === cursorEl ? 1 : 0; + intersection = evt.detail.intersections[index]; + intersectedEl = evt.detail.els[index]; + + // If cursor is the only intersected object, ignore the event. + if (!intersectedEl) { + return; + } + + // Already intersecting this entity. + if (this.intersectedEl === intersectedEl) { + return; + } + + // Ignore events further away than active intersection. + if (this.intersectedEl) { + currentIntersection = this.el.components.raycaster.getIntersection(this.intersectedEl); + if (currentIntersection && currentIntersection.distance <= intersection.distance) { + return; + } + } + + // Unset current intersection. + this.clearCurrentIntersection(true); + this.setIntersection(intersectedEl, intersection); + }, + /** + * Handle intersection cleared. + */ + onIntersectionCleared: function (evt) { + var clearedEls = evt.detail.clearedEls; + // Check if the current intersection has ended + if (clearedEls.indexOf(this.intersectedEl) === -1) { + return; + } + this.clearCurrentIntersection(); + }, + onEnterVR: function () { + this.clearCurrentIntersection(true); + var xrSession = this.el.sceneEl.xrSession; + var self = this; + if (!xrSession) { + return; + } + if (this.data.rayOrigin === 'mouse') { + return; + } + WEBXR_EVENTS.DOWN.forEach(function (downEvent) { + xrSession.addEventListener(downEvent, self.onCursorDown); + }); + WEBXR_EVENTS.UP.forEach(function (upEvent) { + xrSession.addEventListener(upEvent, self.onCursorUp); + }); + }, + setIntersection: function (intersectedEl, intersection) { + var cursorEl = this.el; + var data = this.data; + var self = this; + + // Already intersecting. + if (this.intersectedEl === intersectedEl) { + return; + } + + // Set new intersection. + this.intersectedEl = intersectedEl; + + // Hovering. + cursorEl.addState(STATES.HOVERING); + intersectedEl.addState(STATES.HOVERED); + this.twoWayEmit(EVENTS.MOUSEENTER); + if (this.data.mouseCursorStylesEnabled && this.data.rayOrigin === 'mouse') { + this.el.sceneEl.canvas.classList.add(CANVAS_HOVER_CLASS); + } + + // Begin fuse if necessary. + if (data.fuseTimeout === 0 || !data.fuse || data.rayOrigin === 'xrselect' || data.rayOrigin === 'mouse') { + return; + } + cursorEl.addState(STATES.FUSING); + this.twoWayEmit(EVENTS.FUSING); + this.fuseTimeout = setTimeout(function fuse() { + cursorEl.removeState(STATES.FUSING); + self.twoWayEmit(EVENTS.CLICK); + }, data.fuseTimeout); + }, + clearCurrentIntersection: function (ignoreRemaining) { + var index; + var intersection; + var intersections; + var cursorEl = this.el; + + // Nothing to be cleared. + if (!this.intersectedEl) { + return; + } + + // No longer hovering (or fusing). + this.intersectedEl.removeState(STATES.HOVERED); + cursorEl.removeState(STATES.HOVERING); + cursorEl.removeState(STATES.FUSING); + this.twoWayEmit(EVENTS.MOUSELEAVE); + if (this.data.mouseCursorStylesEnabled && this.data.rayOrigin === 'mouse') { + this.el.sceneEl.canvas.classList.remove(CANVAS_HOVER_CLASS); + } + + // Unset intersected entity (after emitting the event). + this.intersectedEl = null; + + // Clear fuseTimeout. + clearTimeout(this.fuseTimeout); + + // Set intersection to another raycasted element if any. + if (ignoreRemaining === true) { + return; + } + intersections = this.el.components.raycaster.intersections; + if (intersections.length === 0) { + return; + } + // Exclude the cursor. + index = intersections[0].object.el === cursorEl ? 1 : 0; + intersection = intersections[index]; + if (!intersection) { + return; + } + this.setIntersection(intersection.object.el, intersection); + }, + /** + * Helper to emit on both the cursor and the intersected entity (if exists). + */ + twoWayEmit: function (evtName, originalEvent) { + var el = this.el; + var intersectedEl = this.intersectedEl; + var intersection; + function addOriginalEvent(detail, evt) { + if (originalEvent instanceof MouseEvent) { + detail.mouseEvent = originalEvent; + } else if (typeof TouchEvent !== 'undefined' && originalEvent instanceof TouchEvent) { + detail.touchEvent = originalEvent; + } + } + intersection = this.el.components.raycaster.getIntersection(intersectedEl); + this.eventDetail.intersectedEl = intersectedEl; + this.eventDetail.intersection = intersection; + addOriginalEvent(this.eventDetail, originalEvent); + el.emit(evtName, this.eventDetail); + if (!intersectedEl) { + return; + } + this.intersectedEventDetail.intersection = intersection; + addOriginalEvent(this.intersectedEventDetail, originalEvent); + intersectedEl.emit(evtName, this.intersectedEventDetail); + } +}); + +/***/ }), + +/***/ "./src/components/generic-tracked-controller-controls.js": +/*!***************************************************************!*\ + !*** ./src/components/generic-tracked-controller-controls.js ***! + \***************************************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var registerComponent = (__webpack_require__(/*! ../core/component */ "./src/core/component.js").registerComponent); +var bind = __webpack_require__(/*! ../utils/bind */ "./src/utils/bind.js"); +var trackedControlsUtils = __webpack_require__(/*! ../utils/tracked-controls */ "./src/utils/tracked-controls.js"); +var checkControllerPresentAndSetup = trackedControlsUtils.checkControllerPresentAndSetup; +var emitIfAxesChanged = trackedControlsUtils.emitIfAxesChanged; +var onButtonEvent = trackedControlsUtils.onButtonEvent; +var GAMEPAD_ID_PREFIX = 'generic'; + +/** + * Button indices: + * 0 - trigger + * 1 - squeeze + * 2 - touchpad + * 3 - thumbstick + * + * Axis: + * 0 - touchpad + * 1 - thumbstick + * + */ +var INPUT_MAPPING = { + axes: { + touchpad: [0, 1], + thumbstick: [2, 3] + }, + buttons: ['trigger', 'squeeze', 'touchpad', 'thumbstick'] +}; + +/** + * Oculus Go controls. + * Interface with Oculus Go controller and map Gamepad events to + * controller buttons: trackpad, trigger + * Load a controller model and highlight the pressed buttons. + */ +module.exports.Component = registerComponent('generic-tracked-controller-controls', { + schema: { + hand: { + default: '' + }, + // This informs the degenerate arm model. + defaultModel: { + default: true + }, + defaultModelColor: { + default: 'gray' + }, + orientationOffset: { + type: 'vec3' + }, + disabled: { + default: false + } + }, + /** + * Button IDs: + * 0 - trackpad + * 1 - trigger + */ + mapping: INPUT_MAPPING, + bindMethods: function () { + this.onControllersUpdate = bind(this.onControllersUpdate, this); + this.checkIfControllerPresent = bind(this.checkIfControllerPresent, this); + this.removeControllersUpdateListener = bind(this.removeControllersUpdateListener, this); + this.onAxisMoved = bind(this.onAxisMoved, this); + }, + init: function () { + var self = this; + this.onButtonChanged = bind(this.onButtonChanged, this); + this.onButtonDown = function (evt) { + onButtonEvent(evt.detail.id, 'down', self); + }; + this.onButtonUp = function (evt) { + onButtonEvent(evt.detail.id, 'up', self); + }; + this.onButtonTouchStart = function (evt) { + onButtonEvent(evt.detail.id, 'touchstart', self); + }; + this.onButtonTouchEnd = function (evt) { + onButtonEvent(evt.detail.id, 'touchend', self); + }; + this.controllerPresent = false; + this.wasControllerConnected = false; + this.lastControllerCheck = 0; + this.bindMethods(); + + // generic-tracked-controller-controls has the lowest precedence. + // Disable this component if there are more specialized controls components. + this.el.addEventListener('controllerconnected', function (evt) { + if (evt.detail.name === self.name) { + return; + } + self.wasControllerConnected = true; + self.removeEventListeners(); + self.removeControllersUpdateListener(); + }); + }, + addEventListeners: function () { + var el = this.el; + el.addEventListener('buttonchanged', this.onButtonChanged); + el.addEventListener('buttondown', this.onButtonDown); + el.addEventListener('buttonup', this.onButtonUp); + el.addEventListener('touchstart', this.onButtonTouchStart); + el.addEventListener('touchend', this.onButtonTouchEnd); + el.addEventListener('axismove', this.onAxisMoved); + this.controllerEventsActive = true; + }, + removeEventListeners: function () { + var el = this.el; + el.removeEventListener('buttonchanged', this.onButtonChanged); + el.removeEventListener('buttondown', this.onButtonDown); + el.removeEventListener('buttonup', this.onButtonUp); + el.removeEventListener('touchstart', this.onButtonTouchStart); + el.removeEventListener('touchend', this.onButtonTouchEnd); + el.removeEventListener('axismove', this.onAxisMoved); + this.controllerEventsActive = false; + }, + checkIfControllerPresent: function () { + var data = this.data; + var hand = data.hand ? data.hand : undefined; + checkControllerPresentAndSetup(this, GAMEPAD_ID_PREFIX, { + hand: hand, + iterateControllerProfiles: true + }); + }, + play: function () { + if (this.wasControllerConnected) { + return; + } + this.checkIfControllerPresent(); + this.addControllersUpdateListener(); + }, + pause: function () { + this.removeEventListeners(); + this.removeControllersUpdateListener(); + }, + injectTrackedControls: function () { + var el = this.el; + var data = this.data; + + // Do nothing if tracked-controls already set. + // Generic controls have the lowest precedence. + if (this.el.components['tracked-controls']) { + this.removeEventListeners(); + return; + } + el.setAttribute('tracked-controls', { + hand: data.hand, + idPrefix: GAMEPAD_ID_PREFIX, + orientationOffset: data.orientationOffset, + iterateControllerProfiles: true + }); + if (!this.data.defaultModel) { + return; + } + this.initDefaultModel(); + }, + addControllersUpdateListener: function () { + this.el.sceneEl.addEventListener('controllersupdated', this.onControllersUpdate, false); + }, + removeControllersUpdateListener: function () { + this.el.sceneEl.removeEventListener('controllersupdated', this.onControllersUpdate, false); + }, + onControllersUpdate: function () { + if (!this.wasControllerConnected) { + return; + } + this.checkIfControllerPresent(); + }, + onButtonChanged: function (evt) { + var button = this.mapping.buttons[evt.detail.id]; + if (!button) return; + // Pass along changed event with button state, using button mapping for convenience. + this.el.emit(button + 'changed', evt.detail.state); + }, + onAxisMoved: function (evt) { + emitIfAxesChanged(this, this.mapping.axes, evt); + }, + initDefaultModel: function () { + var modelEl = this.modelEl = document.createElement('a-entity'); + modelEl.setAttribute('geometry', { + primitive: 'sphere', + radius: 0.03 + }); + modelEl.setAttribute('material', { + color: this.data.color + }); + this.el.appendChild(modelEl); + this.el.emit('controllermodelready', { + name: 'generic-tracked-controller-controls', + model: this.modelEl, + rayOrigin: { + origin: { + x: 0, + y: 0, + z: -0.01 + }, + direction: { + x: 0, + y: 0, + z: -1 + } + } + }); + } +}); + +/***/ }), + +/***/ "./src/components/geometry.js": +/*!************************************!*\ + !*** ./src/components/geometry.js ***! + \************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var geometries = (__webpack_require__(/*! ../core/geometry */ "./src/core/geometry.js").geometries); +var geometryNames = (__webpack_require__(/*! ../core/geometry */ "./src/core/geometry.js").geometryNames); +var registerComponent = (__webpack_require__(/*! ../core/component */ "./src/core/component.js").registerComponent); +var THREE = __webpack_require__(/*! ../lib/three */ "./src/lib/three.js"); +var dummyGeometry = new THREE.BufferGeometry(); + +/** + * Geometry component. Combined with material component to make a mesh in 3D object. + * Extended with registered geometries. + */ +module.exports.Component = registerComponent('geometry', { + schema: { + buffer: { + default: true + }, + primitive: { + default: 'box', + oneOf: geometryNames, + schemaChange: true + }, + skipCache: { + default: false + } + }, + init: function () { + this.geometry = null; + }, + /** + * Talk to geometry system to get or create geometry. + */ + update: function (previousData) { + var data = this.data; + var el = this.el; + var mesh; + var system = this.system; + + // Dispose old geometry if we created one. + if (this.geometry) { + system.unuseGeometry(previousData); + this.geometry = null; + } + + // Create new geometry. + this.geometry = system.getOrCreateGeometry(data); + + // Set on mesh. If mesh does not exist, create it. + mesh = el.getObject3D('mesh'); + if (mesh) { + mesh.geometry = this.geometry; + } else { + mesh = new THREE.Mesh(); + mesh.geometry = this.geometry; + // Default material if not defined on the entity. + if (!this.el.getAttribute('material')) { + mesh.material = new THREE.MeshStandardMaterial({ + color: Math.random() * 0xFFFFFF, + metalness: 0, + roughness: 0.5 + }); + } + el.setObject3D('mesh', mesh); + } + }, + /** + * Tell geometry system that entity is no longer using the geometry. + * Unset the geometry on the mesh + */ + remove: function () { + this.system.unuseGeometry(this.data); + this.el.getObject3D('mesh').geometry = dummyGeometry; + this.geometry = null; + }, + /** + * Update geometry component schema based on geometry type. + */ + updateSchema: function (data) { + var currentGeometryType = this.oldData && this.oldData.primitive; + var newGeometryType = data.primitive; + var schema = geometries[newGeometryType] && geometries[newGeometryType].schema; + + // Geometry has no schema. + if (!schema) { + throw new Error('Unknown geometry schema `' + newGeometryType + '`'); + } + // Nothing has changed. + if (currentGeometryType && currentGeometryType === newGeometryType) { + return; + } + this.extendSchema(schema); + } +}); + +/***/ }), + +/***/ "./src/components/gltf-model.js": +/*!**************************************!*\ + !*** ./src/components/gltf-model.js ***! + \**************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var registerComponent = (__webpack_require__(/*! ../core/component */ "./src/core/component.js").registerComponent); +var THREE = __webpack_require__(/*! ../lib/three */ "./src/lib/three.js"); +var utils = __webpack_require__(/*! ../utils/ */ "./src/utils/index.js"); +var warn = utils.debug('components:gltf-model:warn'); + +/** + * glTF model loader. + */ +module.exports.Component = registerComponent('gltf-model', { + schema: { + type: 'model' + }, + init: function () { + var self = this; + var dracoLoader = this.system.getDRACOLoader(); + var meshoptDecoder = this.system.getMeshoptDecoder(); + var ktxLoader = this.system.getKTX2Loader(); + this.model = null; + this.loader = new THREE.GLTFLoader(); + if (dracoLoader) { + this.loader.setDRACOLoader(dracoLoader); + } + if (meshoptDecoder) { + this.ready = meshoptDecoder.then(function (meshoptDecoder) { + self.loader.setMeshoptDecoder(meshoptDecoder); + }); + } else { + this.ready = Promise.resolve(); + } + if (ktxLoader) { + this.loader.setKTX2Loader(ktxLoader); + } + }, + update: function () { + var self = this; + var el = this.el; + var src = this.data; + if (!src) { + return; + } + this.remove(); + this.ready.then(function () { + self.loader.load(src, function gltfLoaded(gltfModel) { + self.model = gltfModel.scene || gltfModel.scenes[0]; + self.model.animations = gltfModel.animations; + el.setObject3D('mesh', self.model); + el.emit('model-loaded', { + format: 'gltf', + model: self.model + }); + }, undefined /* onProgress */, function gltfFailed(error) { + var message = error && error.message ? error.message : 'Failed to load glTF model'; + warn(message); + el.emit('model-error', { + format: 'gltf', + src: src + }); + }); + }); + }, + remove: function () { + if (!this.model) { + return; + } + this.el.removeObject3D('mesh'); + } +}); + +/***/ }), + +/***/ "./src/components/grabbable.js": +/*!*************************************!*\ + !*** ./src/components/grabbable.js ***! + \*************************************/ +/***/ ((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => { + +var registerComponent = (__webpack_require__(/*! ../core/component */ "./src/core/component.js").registerComponent); +registerComponent('grabbable', { + init: function () { + this.el.setAttribute('obb-collider', 'centerModel: true'); + } +}); + +/***/ }), + +/***/ "./src/components/hand-controls.js": +/*!*****************************************!*\ + !*** ./src/components/hand-controls.js ***! + \*****************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +/* global THREE */ +var registerComponent = (__webpack_require__(/*! ../core/component */ "./src/core/component.js").registerComponent); +var AFRAME_CDN_ROOT = (__webpack_require__(/*! ../constants */ "./src/constants/index.js").AFRAME_CDN_ROOT); +// Found at https://github.com/aframevr/assets. +var MODEL_URLS = { + toonLeft: AFRAME_CDN_ROOT + 'controllers/hands/leftHand.glb', + toonRight: AFRAME_CDN_ROOT + 'controllers/hands/rightHand.glb', + lowPolyLeft: AFRAME_CDN_ROOT + 'controllers/hands/leftHandLow.glb', + lowPolyRight: AFRAME_CDN_ROOT + 'controllers/hands/rightHandLow.glb', + highPolyLeft: AFRAME_CDN_ROOT + 'controllers/hands/leftHandHigh.glb', + highPolyRight: AFRAME_CDN_ROOT + 'controllers/hands/rightHandHigh.glb' +}; + +// Poses. +var ANIMATIONS = { + open: 'Open', + // point: grip active, trackpad surface active, trigger inactive. + point: 'Point', + // pointThumb: grip active, trigger inactive, trackpad surface inactive. + pointThumb: 'Point + Thumb', + // fist: grip active, trigger active, trackpad surface active. + fist: 'Fist', + // hold: trigger active, grip inactive. + hold: 'Hold', + // thumbUp: grip active, trigger active, trackpad surface inactive. + thumbUp: 'Thumb Up' +}; + +// Map animation to public events for the API. +var EVENTS = {}; +EVENTS[ANIMATIONS.fist] = 'grip'; +EVENTS[ANIMATIONS.thumbUp] = 'pistol'; +EVENTS[ANIMATIONS.point] = 'pointing'; + +/** + * Hand controls component that abstracts 6DoF controls: + * oculus-touch-controls, vive-controls, windows-motion-controls. + * + * Originally meant to be a sample implementation of applications-specific controls that + * abstracts multiple types of controllers. + * + * Auto-detect appropriate controller. + * Handle common events coming from the detected vendor-specific controls. + * Translate button events to semantic hand-related event names: + * (gripclose, gripopen, thumbup, thumbdown, pointup, pointdown) + * Load hand model with gestures that are applied based on the button pressed. + * + * @property {string} Hand mapping (`left`, `right`). + */ +module.exports.Component = registerComponent('hand-controls', { + schema: { + color: { + default: 'white', + type: 'color' + }, + hand: { + default: 'left' + }, + handModelStyle: { + default: 'lowPoly', + oneOf: ['lowPoly', 'highPoly', 'toon'] + } + }, + init: function () { + var self = this; + var el = this.el; + // Active buttons populated by events provided by the attached controls. + this.pressedButtons = {}; + this.touchedButtons = {}; + this.loader = new THREE.GLTFLoader(); + this.loader.setCrossOrigin('anonymous'); + this.onGripDown = function () { + self.handleButton('grip', 'down'); + }; + this.onGripUp = function () { + self.handleButton('grip', 'up'); + }; + this.onTrackpadDown = function () { + self.handleButton('trackpad', 'down'); + }; + this.onTrackpadUp = function () { + self.handleButton('trackpad', 'up'); + }; + this.onTrackpadTouchStart = function () { + self.handleButton('trackpad', 'touchstart'); + }; + this.onTrackpadTouchEnd = function () { + self.handleButton('trackpad', 'touchend'); + }; + this.onTriggerDown = function () { + self.handleButton('trigger', 'down'); + }; + this.onTriggerUp = function () { + self.handleButton('trigger', 'up'); + }; + this.onTriggerTouchStart = function () { + self.handleButton('trigger', 'touchstart'); + }; + this.onTriggerTouchEnd = function () { + self.handleButton('trigger', 'touchend'); + }; + this.onGripTouchStart = function () { + self.handleButton('grip', 'touchstart'); + }; + this.onGripTouchEnd = function () { + self.handleButton('grip', 'touchend'); + }; + this.onThumbstickDown = function () { + self.handleButton('thumbstick', 'down'); + }; + this.onThumbstickUp = function () { + self.handleButton('thumbstick', 'up'); + }; + this.onAorXTouchStart = function () { + self.handleButton('AorX', 'touchstart'); + }; + this.onAorXTouchEnd = function () { + self.handleButton('AorX', 'touchend'); + }; + this.onBorYTouchStart = function () { + self.handleButton('BorY', 'touchstart'); + }; + this.onBorYTouchEnd = function () { + self.handleButton('BorY', 'touchend'); + }; + this.onSurfaceTouchStart = function () { + self.handleButton('surface', 'touchstart'); + }; + this.onSurfaceTouchEnd = function () { + self.handleButton('surface', 'touchend'); + }; + this.onControllerConnected = this.onControllerConnected.bind(this); + this.onControllerDisconnected = this.onControllerDisconnected.bind(this); + el.addEventListener('controllerconnected', this.onControllerConnected); + el.addEventListener('controllerdisconnected', this.onControllerDisconnected); + + // Hidden by default. + el.object3D.visible = false; + }, + play: function () { + this.addEventListeners(); + }, + pause: function () { + this.removeEventListeners(); + }, + tick: function (time, delta) { + var mesh = this.el.getObject3D('mesh'); + if (!mesh || !mesh.mixer) { + return; + } + mesh.mixer.update(delta / 1000); + }, + onControllerConnected: function () { + this.el.object3D.visible = true; + }, + onControllerDisconnected: function () { + this.el.object3D.visible = false; + }, + addEventListeners: function () { + var el = this.el; + el.addEventListener('gripdown', this.onGripDown); + el.addEventListener('gripup', this.onGripUp); + el.addEventListener('trackpaddown', this.onTrackpadDown); + el.addEventListener('trackpadup', this.onTrackpadUp); + el.addEventListener('trackpadtouchstart', this.onTrackpadTouchStart); + el.addEventListener('trackpadtouchend', this.onTrackpadTouchEnd); + el.addEventListener('triggerdown', this.onTriggerDown); + el.addEventListener('triggerup', this.onTriggerUp); + el.addEventListener('triggertouchstart', this.onTriggerTouchStart); + el.addEventListener('triggertouchend', this.onTriggerTouchEnd); + el.addEventListener('griptouchstart', this.onGripTouchStart); + el.addEventListener('griptouchend', this.onGripTouchEnd); + el.addEventListener('thumbstickdown', this.onThumbstickDown); + el.addEventListener('thumbstickup', this.onThumbstickUp); + el.addEventListener('abuttontouchstart', this.onAorXTouchStart); + el.addEventListener('abuttontouchend', this.onAorXTouchEnd); + el.addEventListener('bbuttontouchstart', this.onBorYTouchStart); + el.addEventListener('bbuttontouchend', this.onBorYTouchEnd); + el.addEventListener('xbuttontouchstart', this.onAorXTouchStart); + el.addEventListener('xbuttontouchend', this.onAorXTouchEnd); + el.addEventListener('ybuttontouchstart', this.onBorYTouchStart); + el.addEventListener('ybuttontouchend', this.onBorYTouchEnd); + el.addEventListener('surfacetouchstart', this.onSurfaceTouchStart); + el.addEventListener('surfacetouchend', this.onSurfaceTouchEnd); + }, + removeEventListeners: function () { + var el = this.el; + el.removeEventListener('gripdown', this.onGripDown); + el.removeEventListener('gripup', this.onGripUp); + el.removeEventListener('trackpaddown', this.onTrackpadDown); + el.removeEventListener('trackpadup', this.onTrackpadUp); + el.removeEventListener('trackpadtouchstart', this.onTrackpadTouchStart); + el.removeEventListener('trackpadtouchend', this.onTrackpadTouchEnd); + el.removeEventListener('triggerdown', this.onTriggerDown); + el.removeEventListener('triggerup', this.onTriggerUp); + el.removeEventListener('triggertouchstart', this.onTriggerTouchStart); + el.removeEventListener('triggertouchend', this.onTriggerTouchEnd); + el.removeEventListener('griptouchstart', this.onGripTouchStart); + el.removeEventListener('griptouchend', this.onGripTouchEnd); + el.removeEventListener('thumbstickdown', this.onThumbstickDown); + el.removeEventListener('thumbstickup', this.onThumbstickUp); + el.removeEventListener('abuttontouchstart', this.onAorXTouchStart); + el.removeEventListener('abuttontouchend', this.onAorXTouchEnd); + el.removeEventListener('bbuttontouchstart', this.onBorYTouchStart); + el.removeEventListener('bbuttontouchend', this.onBorYTouchEnd); + el.removeEventListener('xbuttontouchstart', this.onAorXTouchStart); + el.removeEventListener('xbuttontouchend', this.onAorXTouchEnd); + el.removeEventListener('ybuttontouchstart', this.onBorYTouchStart); + el.removeEventListener('ybuttontouchend', this.onBorYTouchEnd); + el.removeEventListener('surfacetouchstart', this.onSurfaceTouchStart); + el.removeEventListener('surfacetouchend', this.onSurfaceTouchEnd); + }, + /** + * Update handler. More like the `init` handler since the only property is the hand, and + * that won't be changing much. + */ + update: function (previousHand) { + var controlConfiguration; + var el = this.el; + var hand = this.data.hand; + var handModelStyle = this.data.handModelStyle; + var handColor = this.data.color; + var self = this; + + // Get common configuration to abstract different vendor controls. + controlConfiguration = { + hand: hand, + model: false + }; + + // Set model. + if (hand !== previousHand) { + var handmodelUrl = MODEL_URLS[handModelStyle + hand.charAt(0).toUpperCase() + hand.slice(1)]; + this.loader.load(handmodelUrl, function (gltf) { + var mesh = gltf.scene.children[0]; + var handModelOrientationZ = hand === 'left' ? Math.PI / 2 : -Math.PI / 2; + // The WebXR standard defines the grip space such that a cylinder held in a closed hand points + // along the Z axis. The models currently have such a cylinder point along the X-Axis. + var handModelOrientationX = el.sceneEl.hasWebXR ? -Math.PI / 2 : 0; + mesh.mixer = new THREE.AnimationMixer(mesh); + self.clips = gltf.animations; + el.setObject3D('mesh', mesh); + mesh.traverse(function (object) { + if (!object.isMesh) { + return; + } + object.material.color = new THREE.Color(handColor); + }); + mesh.position.set(0, 0, 0); + mesh.rotation.set(handModelOrientationX, 0, handModelOrientationZ); + el.setAttribute('magicleap-controls', controlConfiguration); + el.setAttribute('vive-controls', controlConfiguration); + el.setAttribute('oculus-touch-controls', controlConfiguration); + el.setAttribute('pico-controls', controlConfiguration); + el.setAttribute('windows-motion-controls', controlConfiguration); + el.setAttribute('hp-mixed-reality-controls', controlConfiguration); + }); + } + }, + remove: function () { + this.el.removeObject3D('mesh'); + }, + /** + * Play model animation, based on which button was pressed and which kind of event. + * + * 1. Process buttons. + * 2. Determine gesture (this.determineGesture()). + * 3. Animation gesture (this.animationGesture()). + * 4. Emit gesture events (this.emitGestureEvents()). + * + * @param {string} button - Name of the button. + * @param {string} evt - Type of event for the button (i.e., down/up/touchstart/touchend). + */ + handleButton: function (button, evt) { + var lastGesture; + var isPressed = evt === 'down'; + var isTouched = evt === 'touchstart'; + + // Update objects. + if (evt.indexOf('touch') === 0) { + // Update touch object. + if (isTouched === this.touchedButtons[button]) { + return; + } + this.touchedButtons[button] = isTouched; + } else { + // Update button object. + if (isPressed === this.pressedButtons[button]) { + return; + } + this.pressedButtons[button] = isPressed; + } + + // Determine the gesture. + lastGesture = this.gesture; + this.gesture = this.determineGesture(); + + // Same gesture. + if (this.gesture === lastGesture) { + return; + } + // Animate gesture. + this.animateGesture(this.gesture, lastGesture); + + // Emit events. + this.emitGestureEvents(this.gesture, lastGesture); + }, + /** + * Determine which pose hand should be in considering active and touched buttons. + */ + determineGesture: function () { + var gesture; + var isGripActive = this.pressedButtons.grip; + var isSurfaceActive = this.pressedButtons.surface || this.touchedButtons.surface; + var isTrackpadActive = this.pressedButtons.trackpad || this.touchedButtons.trackpad; + var isTriggerActive = this.pressedButtons.trigger || this.touchedButtons.trigger; + var isABXYActive = this.touchedButtons.AorX || this.touchedButtons.BorY; + var isVive = isViveController(this.el.components['tracked-controls']); + + // Works well with Oculus Touch and Windows Motion Controls, but Vive needs tweaks. + if (isVive) { + if (isGripActive || isTriggerActive) { + gesture = ANIMATIONS.fist; + } else if (isTrackpadActive) { + gesture = ANIMATIONS.point; + } + } else { + if (isGripActive) { + if (isSurfaceActive || isABXYActive || isTrackpadActive) { + gesture = isTriggerActive ? ANIMATIONS.fist : ANIMATIONS.point; + } else { + gesture = isTriggerActive ? ANIMATIONS.thumbUp : ANIMATIONS.pointThumb; + } + } else if (isTriggerActive) { + gesture = ANIMATIONS.hold; + } + } + return gesture; + }, + /** + * Play corresponding clip to a gesture + */ + getClip: function (gesture) { + var clip; + var i; + for (i = 0; i < this.clips.length; i++) { + clip = this.clips[i]; + if (clip.name !== gesture) { + continue; + } + return clip; + } + }, + /** + * Play gesture animation. + * + * @param {string} gesture - Which pose to animate to. If absent, then animate to open. + * @param {string} lastGesture - Previous gesture, to reverse back to open if needed. + */ + animateGesture: function (gesture, lastGesture) { + if (gesture) { + this.playAnimation(gesture || ANIMATIONS.open, lastGesture, false); + return; + } + + // If no gesture, then reverse the current gesture back to open pose. + this.playAnimation(lastGesture, lastGesture, true); + }, + /** + * Emit `hand-controls`-specific events. + */ + emitGestureEvents: function (gesture, lastGesture) { + var el = this.el; + var eventName; + if (lastGesture === gesture) { + return; + } + + // Emit event for lastGesture not inactive. + eventName = getGestureEventName(lastGesture, false); + if (eventName) { + el.emit(eventName); + } + + // Emit event for current gesture now active. + eventName = getGestureEventName(gesture, true); + if (eventName) { + el.emit(eventName); + } + }, + /** + * Play hand animation based on button state. + * + * @param {string} gesture - Name of the animation as specified by the model. + * @param {string} lastGesture - Previous pose. + * @param {boolean} reverse - Whether animation should play in reverse. + */ + playAnimation: function (gesture, lastGesture, reverse) { + var clip; + var fromAction; + var mesh = this.el.getObject3D('mesh'); + var toAction; + if (!mesh) { + return; + } + + // Grab clip action. + clip = this.getClip(gesture); + toAction = mesh.mixer.clipAction(clip); + + // Reverse from gesture to no gesture. + if (reverse) { + toAction.paused = false; + toAction.timeScale = -1; + return; + } + toAction.clampWhenFinished = true; + toAction.loop = THREE.LoopOnce; + toAction.repetitions = 0; + toAction.timeScale = 1; + toAction.time = 0; + toAction.weight = 1; + + // No gesture to gesture. + if (!lastGesture) { + // Play animation. + mesh.mixer.stopAllAction(); + toAction.play(); + return; + } + + // Animate or crossfade from gesture to gesture. + clip = this.getClip(lastGesture); + toAction.reset(); + toAction.play(); + fromAction = mesh.mixer.clipAction(clip); + fromAction.crossFadeTo(toAction, 0.15, true); + } +}); + +/** + * Suffix gestures based on toggle state (e.g., open/close, up/down, start/end). + * + * @param {string} gesture + * @param {boolean} active + */ +function getGestureEventName(gesture, active) { + var eventName; + if (!gesture) { + return; + } + eventName = EVENTS[gesture]; + if (eventName === 'grip') { + return eventName + (active ? 'close' : 'open'); + } + if (eventName === 'point') { + return eventName + (active ? 'up' : 'down'); + } + if (eventName === 'pointing' || eventName === 'pistol') { + return eventName + (active ? 'start' : 'end'); + } +} +function isViveController(trackedControls) { + var controller = trackedControls && trackedControls.controller; + var isVive = controller && (controller.id && controller.id.indexOf('OpenVR ') === 0 || controller.profiles && controller.profiles[0] && controller.profiles[0] === 'htc-vive'); + return isVive; +} + +/***/ }), + +/***/ "./src/components/hand-tracking-controls.js": +/*!**************************************************!*\ + !*** ./src/components/hand-tracking-controls.js ***! + \**************************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +/* global THREE, XRHand */ +var registerComponent = (__webpack_require__(/*! ../core/component */ "./src/core/component.js").registerComponent); +var bind = __webpack_require__(/*! ../utils/bind */ "./src/utils/bind.js"); +var AEntity = (__webpack_require__(/*! ../core/a-entity */ "./src/core/a-entity.js").AEntity); +var trackedControlsUtils = __webpack_require__(/*! ../utils/tracked-controls */ "./src/utils/tracked-controls.js"); +var checkControllerPresentAndSetup = trackedControlsUtils.checkControllerPresentAndSetup; +var AFRAME_CDN_ROOT = (__webpack_require__(/*! ../constants */ "./src/constants/index.js").AFRAME_CDN_ROOT); +var LEFT_HAND_MODEL_URL = AFRAME_CDN_ROOT + 'controllers/oculus-hands/v4/left.glb'; +var RIGHT_HAND_MODEL_URL = AFRAME_CDN_ROOT + 'controllers/oculus-hands/v4/right.glb'; +var JOINTS = ['wrist', 'thumb-metacarpal', 'thumb-phalanx-proximal', 'thumb-phalanx-distal', 'thumb-tip', 'index-finger-metacarpal', 'index-finger-phalanx-proximal', 'index-finger-phalanx-intermediate', 'index-finger-phalanx-distal', 'index-finger-tip', 'middle-finger-metacarpal', 'middle-finger-phalanx-proximal', 'middle-finger-phalanx-intermediate', 'middle-finger-phalanx-distal', 'middle-finger-tip', 'ring-finger-metacarpal', 'ring-finger-phalanx-proximal', 'ring-finger-phalanx-intermediate', 'ring-finger-phalanx-distal', 'ring-finger-tip', 'pinky-finger-metacarpal', 'pinky-finger-phalanx-proximal', 'pinky-finger-phalanx-intermediate', 'pinky-finger-phalanx-distal', 'pinky-finger-tip']; +var WRIST_INDEX = 0; +var THUMB_TIP_INDEX = 4; +var INDEX_TIP_INDEX = 9; +var PINCH_START_DISTANCE = 0.015; +var PINCH_END_PERCENTAGE = 0.1; + +/** + * Controls for hand tracking + */ +module.exports.Component = registerComponent('hand-tracking-controls', { + schema: { + hand: { + default: 'right', + oneOf: ['left', 'right'] + }, + modelStyle: { + default: 'mesh', + oneOf: ['dots', 'mesh'] + }, + modelColor: { + default: 'white' + }, + modelOpacity: { + default: 1.0 + } + }, + bindMethods: function () { + this.onControllersUpdate = bind(this.onControllersUpdate, this); + this.checkIfControllerPresent = bind(this.checkIfControllerPresent, this); + this.removeControllersUpdateListener = bind(this.removeControllersUpdateListener, this); + }, + addEventListeners: function () { + this.el.addEventListener('model-loaded', this.onModelLoaded); + for (var i = 0; i < this.jointEls.length; ++i) { + this.jointEls[i].object3D.visible = true; + } + }, + removeEventListeners: function () { + this.el.removeEventListener('model-loaded', this.onModelLoaded); + for (var i = 0; i < this.jointEls.length; ++i) { + this.jointEls[i].object3D.visible = false; + } + }, + init: function () { + var sceneEl = this.el.sceneEl; + var webxrData = sceneEl.getAttribute('webxr'); + var optionalFeaturesArray = webxrData.optionalFeatures; + if (optionalFeaturesArray.indexOf('hand-tracking') === -1) { + optionalFeaturesArray.push('hand-tracking'); + sceneEl.setAttribute('webxr', webxrData); + } + this.wristObject3D = new THREE.Object3D(); + this.el.sceneEl.object3D.add(this.wristObject3D); + this.onModelLoaded = this.onModelLoaded.bind(this); + this.onChildAttached = this.onChildAttached.bind(this); + this.jointEls = []; + this.controllerPresent = false; + this.isPinched = false; + this.pinchEventDetail = { + position: new THREE.Vector3(), + wristRotation: new THREE.Quaternion() + }; + this.indexTipPosition = new THREE.Vector3(); + this.hasPoses = false; + this.jointPoses = new Float32Array(16 * JOINTS.length); + this.jointRadii = new Float32Array(JOINTS.length); + this.bindMethods(); + this.updateReferenceSpace = this.updateReferenceSpace.bind(this); + this.el.sceneEl.addEventListener('enter-vr', this.updateReferenceSpace); + this.el.sceneEl.addEventListener('exit-vr', this.updateReferenceSpace); + this.el.addEventListener('child-attached', this.onChildAttached); + this.el.object3D.visible = false; + this.wristObject3D.visible = false; + }, + onChildAttached: function (evt) { + this.addChildEntity(evt.detail.el); + }, + update: function () { + this.updateModelMaterial(); + }, + updateModelMaterial: function () { + var jointEls = this.jointEls; + var skinnedMesh = this.skinnedMesh; + var transparent = !(this.data.modelOpacity === 1.0); + if (skinnedMesh) { + this.skinnedMesh.material.color.set(this.data.modelColor); + this.skinnedMesh.material.transparent = transparent; + this.skinnedMesh.material.opacity = this.data.modelOpacity; + } + for (var i = 0; i < jointEls.length; i++) { + jointEls[i].setAttribute('material', { + color: this.data.modelColor, + transparent: transparent, + opacity: this.data.modelOpacity + }); + } + }, + updateReferenceSpace: function () { + var self = this; + var xrSession = this.el.sceneEl.xrSession; + this.referenceSpace = undefined; + if (!xrSession) { + return; + } + var referenceSpaceType = self.el.sceneEl.systems.webxr.sessionReferenceSpaceType; + xrSession.requestReferenceSpace(referenceSpaceType).then(function (referenceSpace) { + self.referenceSpace = referenceSpace; + }).catch(function (error) { + self.el.sceneEl.systems.webxr.warnIfFeatureNotRequested(referenceSpaceType, 'tracked-controls-webxr uses reference space ' + referenceSpaceType); + throw error; + }); + }, + checkIfControllerPresent: function () { + var data = this.data; + var hand = data.hand ? data.hand : undefined; + checkControllerPresentAndSetup(this, '', { + hand: hand, + iterateControllerProfiles: true, + handTracking: true + }); + }, + play: function () { + this.checkIfControllerPresent(); + this.addControllersUpdateListener(); + }, + tick: function () { + var sceneEl = this.el.sceneEl; + var controller = this.el.components['tracked-controls'] && this.el.components['tracked-controls'].controller; + var frame = sceneEl.frame; + var trackedControlsWebXR = this.el.components['tracked-controls-webxr']; + var referenceSpace = this.referenceSpace; + if (!controller || !frame || !referenceSpace || !trackedControlsWebXR) { + return; + } + this.hasPoses = false; + if (controller.hand) { + this.el.object3D.position.set(0, 0, 0); + this.el.object3D.rotation.set(0, 0, 0); + this.hasPoses = frame.fillPoses(controller.hand.values(), referenceSpace, this.jointPoses) && frame.fillJointRadii(controller.hand.values(), this.jointRadii); + this.updateHandModel(); + this.detectGesture(); + this.updateWristObject(); + } + }, + updateWristObject: function () { + var jointPose = new THREE.Matrix4(); + return function () { + var wristObject3D = this.wristObject3D; + if (!wristObject3D || !this.hasPoses) { + return; + } + jointPose.fromArray(this.jointPoses, WRIST_INDEX * 16); + wristObject3D.position.setFromMatrixPosition(jointPose); + wristObject3D.quaternion.setFromRotationMatrix(jointPose); + }; + }(), + updateHandModel: function () { + if (this.data.modelStyle === 'dots') { + this.updateHandDotsModel(); + } + if (this.data.modelStyle === 'mesh') { + this.updateHandMeshModel(); + } + }, + getBone: function (name) { + var bones = this.bones; + for (var i = 0; i < bones.length; i++) { + if (bones[i].name === name) { + return bones[i]; + } + } + return null; + }, + updateHandMeshModel: function () { + var jointPose = new THREE.Matrix4(); + return function () { + var i = 0; + var jointPoses = this.jointPoses; + var controller = this.el.components['tracked-controls'] && this.el.components['tracked-controls'].controller; + if (!controller || !this.mesh) { + return; + } + this.mesh.visible = false; + if (!this.hasPoses) { + return; + } + for (var inputjoint of controller.hand.values()) { + var bone = this.getBone(inputjoint.jointName); + if (bone != null) { + this.mesh.visible = true; + jointPose.fromArray(jointPoses, i * 16); + bone.position.setFromMatrixPosition(jointPose); + bone.quaternion.setFromRotationMatrix(jointPose); + } + i++; + } + }; + }(), + updateHandDotsModel: function () { + var jointPoses = this.jointPoses; + var jointRadii = this.jointRadii; + var controller = this.el.components['tracked-controls'] && this.el.components['tracked-controls'].controller; + var jointEl; + var object3D; + for (var i = 0; i < controller.hand.size; i++) { + jointEl = this.jointEls[i]; + object3D = jointEl.object3D; + jointEl.object3D.visible = this.hasPoses; + if (!this.hasPoses) { + continue; + } + object3D.matrix.fromArray(jointPoses, i * 16); + object3D.matrix.decompose(object3D.position, object3D.rotation, object3D.scale); + jointEl.setAttribute('scale', { + x: jointRadii[i], + y: jointRadii[i], + z: jointRadii[i] + }); + } + }, + detectGesture: function () { + this.detectPinch(); + }, + detectPinch: function () { + var thumbTipPosition = new THREE.Vector3(); + var jointPose = new THREE.Matrix4(); + return function () { + var indexTipPosition = this.indexTipPosition; + var pinchEventDetail = this.pinchEventDetail; + if (!this.hasPoses) { + return; + } + thumbTipPosition.setFromMatrixPosition(jointPose.fromArray(this.jointPoses, THUMB_TIP_INDEX * 16)); + indexTipPosition.setFromMatrixPosition(jointPose.fromArray(this.jointPoses, INDEX_TIP_INDEX * 16)); + pinchEventDetail.wristRotation.setFromRotationMatrix(jointPose.fromArray(this.jointPoses, WRIST_INDEX * 16)); + var distance = indexTipPosition.distanceTo(thumbTipPosition); + if (distance < PINCH_START_DISTANCE && this.isPinched === false) { + this.isPinched = true; + this.pinchDistance = distance; + pinchEventDetail.position.copy(indexTipPosition).add(thumbTipPosition).multiplyScalar(0.5); + this.el.emit('pinchstarted', pinchEventDetail); + } + if (distance > this.pinchDistance + this.pinchDistance * PINCH_END_PERCENTAGE && this.isPinched === true) { + this.isPinched = false; + pinchEventDetail.position.copy(indexTipPosition).add(thumbTipPosition).multiplyScalar(0.5); + this.el.emit('pinchended', pinchEventDetail); + } + if (this.isPinched) { + pinchEventDetail.position.copy(indexTipPosition).add(thumbTipPosition).multiplyScalar(0.5); + this.el.emit('pinchmoved', pinchEventDetail); + } + }; + }(), + pause: function () { + this.removeEventListeners(); + this.removeControllersUpdateListener(); + }, + injectTrackedControls: function () { + var el = this.el; + var data = this.data; + el.setAttribute('tracked-controls', { + id: '', + hand: data.hand, + iterateControllerProfiles: true, + handTrackingEnabled: true + }); + if (this.mesh) { + if (this.mesh !== el.getObject3D('mesh')) { + el.setObject3D('mesh', this.mesh); + } + return; + } + this.initDefaultModel(); + }, + addControllersUpdateListener: function () { + this.el.sceneEl.addEventListener('controllersupdated', this.onControllersUpdate, false); + }, + removeControllersUpdateListener: function () { + this.el.sceneEl.removeEventListener('controllersupdated', this.onControllersUpdate, false); + }, + onControllersUpdate: function () { + var el = this.el; + var controller; + this.checkIfControllerPresent(); + controller = el.components['tracked-controls'] && el.components['tracked-controls'].controller; + if (!this.mesh) { + return; + } + if (controller && controller.hand && controller.hand instanceof XRHand) { + el.setObject3D('mesh', this.mesh); + } + }, + initDefaultModel: function () { + var data = this.data; + if (data.modelStyle === 'dots') { + this.initDotsModel(); + } + if (data.modelStyle === 'mesh') { + this.initMeshHandModel(); + } + this.el.object3D.visible = true; + this.wristObject3D.visible = true; + }, + initDotsModel: function () { + // Add models just once. + if (this.jointEls.length !== 0) { + return; + } + for (var i = 0; i < JOINTS.length; ++i) { + var jointEl = this.jointEl = document.createElement('a-entity'); + jointEl.setAttribute('geometry', { + primitive: 'sphere', + radius: 1.0 + }); + jointEl.object3D.visible = false; + this.el.appendChild(jointEl); + this.jointEls.push(jointEl); + } + this.updateModelMaterial(); + }, + initMeshHandModel: function () { + var modelURL = this.data.hand === 'left' ? LEFT_HAND_MODEL_URL : RIGHT_HAND_MODEL_URL; + this.el.setAttribute('gltf-model', modelURL); + }, + onModelLoaded: function () { + var mesh = this.mesh = this.el.getObject3D('mesh').children[0]; + var skinnedMesh = this.skinnedMesh = mesh.getObjectByProperty('type', 'SkinnedMesh'); + if (!this.skinnedMesh) { + return; + } + this.bones = skinnedMesh.skeleton.bones; + this.el.removeObject3D('mesh'); + mesh.position.set(0, 0, 0); + mesh.rotation.set(0, 0, 0); + skinnedMesh.frustumCulled = false; + skinnedMesh.material = new THREE.MeshStandardMaterial(); + this.updateModelMaterial(); + this.setupChildrenEntities(); + this.el.setObject3D('mesh', mesh); + }, + setupChildrenEntities: function () { + var childrenEls = this.el.children; + for (var i = 0; i < childrenEls.length; ++i) { + if (!(childrenEls[i] instanceof AEntity)) { + continue; + } + this.addChildEntity(childrenEls[i]); + } + }, + addChildEntity: function (childEl) { + if (!(childEl instanceof AEntity)) { + return; + } + this.wristObject3D.add(childEl.object3D); + } +}); + +/***/ }), + +/***/ "./src/components/hand-tracking-grab-controls.js": +/*!*******************************************************!*\ + !*** ./src/components/hand-tracking-grab-controls.js ***! + \*******************************************************/ +/***/ ((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => { + +var registerComponent = (__webpack_require__(/*! ../core/component */ "./src/core/component.js").registerComponent); +var THREE = __webpack_require__(/*! ../lib/three */ "./src/lib/three.js"); +registerComponent('hand-tracking-grab-controls', { + schema: { + hand: { + default: 'right', + oneOf: ['left', 'right'] + }, + color: { + type: 'color', + default: 'white' + }, + hoverColor: { + type: 'color', + default: '#538df1' + }, + hoverEnabled: { + default: false + } + }, + init: function () { + var el = this.el; + var data = this.data; + var trackedObject3DVariable; + if (data.hand === 'right') { + trackedObject3DVariable = 'components.hand-tracking-controls.bones.3'; + } else { + trackedObject3DVariable = 'components.hand-tracking-controls.bones.21'; + } + el.setAttribute('hand-tracking-controls', { + hand: data.hand + }); + el.setAttribute('obb-collider', { + trackedObject3D: trackedObject3DVariable, + size: 0.04 + }); + this.auxMatrix = new THREE.Matrix4(); + this.auxQuaternion = new THREE.Quaternion(); + this.auxQuaternion2 = new THREE.Quaternion(); + this.auxVector = new THREE.Vector3(); + this.auxVector2 = new THREE.Vector3(); + this.grabbingObjectPosition = new THREE.Vector3(); + this.grabbedObjectPosition = new THREE.Vector3(); + this.grabbedObjectPositionDelta = new THREE.Vector3(); + this.grabDeltaPosition = new THREE.Vector3(); + this.grabInitialRotation = new THREE.Quaternion(); + this.onCollisionStarted = this.onCollisionStarted.bind(this); + this.el.addEventListener('obbcollisionstarted', this.onCollisionStarted); + this.onCollisionEnded = this.onCollisionEnded.bind(this); + this.el.addEventListener('obbcollisionended', this.onCollisionEnded); + this.onPinchStarted = this.onPinchStarted.bind(this); + this.el.addEventListener('pinchstarted', this.onPinchStarted); + this.onPinchEnded = this.onPinchEnded.bind(this); + this.el.addEventListener('pinchended', this.onPinchEnded); + this.onPinchMoved = this.onPinchMoved.bind(this); + this.el.addEventListener('pinchmoved', this.onPinchMoved); + }, + transferEntityOwnership: function () { + var grabbingElComponent; + var grabbingEls = this.el.sceneEl.querySelectorAll('[hand-tracking-grab-controls]'); + for (var i = 0; i < grabbingEls.length; ++i) { + grabbingElComponent = grabbingEls[i].components['hand-tracking-grab-controls']; + if (grabbingElComponent === this) { + continue; + } + if (this.grabbedEl && this.grabbedEl === grabbingElComponent.grabbedEl) { + grabbingElComponent.releaseGrabbedEntity(); + } + } + return false; + }, + onCollisionStarted: function (evt) { + var withEl = evt.detail.withEl; + if (this.collidedEl) { + return; + } + if (!withEl.getAttribute('grabbable')) { + return; + } + this.collidedEl = withEl; + this.grabbingObject3D = evt.detail.trackedObject3D; + if (this.data.hoverEnabled) { + this.el.setAttribute('hand-tracking-controls', 'modelColor', this.data.hoverColor); + } + }, + onCollisionEnded: function () { + this.collidedEl = undefined; + if (this.grabbedEl) { + return; + } + this.grabbingObject3D = undefined; + if (this.data.hoverEnabled) { + this.el.setAttribute('hand-tracking-controls', 'modelColor', this.data.color); + } + }, + onPinchStarted: function (evt) { + if (!this.collidedEl) { + return; + } + this.pinchPosition = evt.detail.position; + this.wristRotation = evt.detail.wristRotation; + this.grabbedEl = this.collidedEl; + this.transferEntityOwnership(); + this.grab(); + }, + onPinchEnded: function () { + this.releaseGrabbedEntity(); + }, + onPinchMoved: function (evt) { + this.wristRotation = evt.detail.wristRotation; + }, + releaseGrabbedEntity: function () { + var grabbedEl = this.grabbedEl; + if (!grabbedEl) { + return; + } + grabbedEl.object3D.updateMatrixWorld = this.originalUpdateMatrixWorld; + grabbedEl.object3D.matrixAutoUpdate = true; + grabbedEl.object3D.matrixWorldAutoUpdate = true; + grabbedEl.object3D.matrixWorld.decompose(this.auxVector, this.auxQuaternion, this.auxVector2); + grabbedEl.object3D.position.copy(this.auxVector); + grabbedEl.object3D.quaternion.copy(this.auxQuaternion); + this.el.emit('grabended', { + grabbedEl: grabbedEl + }); + this.grabbedEl = undefined; + }, + grab: function () { + var grabbedEl = this.grabbedEl; + var grabedObjectWorldPosition; + grabedObjectWorldPosition = grabbedEl.object3D.getWorldPosition(this.grabbedObjectPosition); + this.grabDeltaPosition.copy(grabedObjectWorldPosition).sub(this.pinchPosition); + this.grabInitialRotation.copy(this.auxQuaternion.copy(this.wristRotation).invert()); + this.originalUpdateMatrixWorld = grabbedEl.object3D.updateMatrixWorld; + grabbedEl.object3D.updateMatrixWorld = function () {/* no op */}; + grabbedEl.object3D.updateMatrixWorldChildren = function (force) { + var children = this.children; + for (var i = 0, l = children.length; i < l; i++) { + var child = children[i]; + if (child.matrixWorldAutoUpdate === true || force === true) { + child.updateMatrixWorld(true); + } + } + }; + grabbedEl.object3D.matrixAutoUpdate = false; + grabbedEl.object3D.matrixWorldAutoUpdate = false; + this.el.emit('grabstarted', { + grabbedEl: grabbedEl + }); + }, + tock: function () { + var auxMatrix = this.auxMatrix; + var auxQuaternion = this.auxQuaternion; + var auxQuaternion2 = this.auxQuaternion2; + var grabbedObject3D; + var grabbedEl = this.grabbedEl; + if (!grabbedEl) { + return; + } + + // We have to compose 4 transformations. + // Both grabbing and grabbed entities position and rotation. + + // 1. Move grabbed entity to the pinch position (middle point between index and thumb) + // 2. Apply the rotation delta (substract initial rotation) of the grabbing entity position (wrist). + // 3. Translate grabbed entity to the original position: distance betweeen grabbed and grabbing entities at collision time. + // 4. Apply grabbed entity rotation. + // 5. Preserve original scale. + + // Store grabbed entity local rotation. + grabbedObject3D = grabbedEl.object3D; + grabbedObject3D.getWorldQuaternion(auxQuaternion2); + + // Reset grabbed entity matrix. + grabbedObject3D.matrixWorld.identity(); + + // 1. + auxMatrix.identity(); + auxMatrix.makeTranslation(this.pinchPosition); + grabbedObject3D.matrixWorld.multiply(auxMatrix); + + // 2. + auxMatrix.identity(); + auxMatrix.makeRotationFromQuaternion(auxQuaternion.copy(this.wristRotation).multiply(this.grabInitialRotation)); + grabbedObject3D.matrixWorld.multiply(auxMatrix); + + // 3. + auxMatrix.identity(); + auxMatrix.makeTranslation(this.grabDeltaPosition); + grabbedObject3D.matrixWorld.multiply(auxMatrix); + + // 4. + auxMatrix.identity(); + auxMatrix.makeRotationFromQuaternion(auxQuaternion2); + grabbedObject3D.matrixWorld.multiply(auxMatrix); + + // 5. + auxMatrix.makeScale(grabbedEl.object3D.scale.x, grabbedEl.object3D.scale.y, grabbedEl.object3D.scale.z); + grabbedObject3D.matrixWorld.multiply(auxMatrix); + grabbedObject3D.updateMatrixWorldChildren(); + } +}); + +/***/ }), + +/***/ "./src/components/hide-on-enter-ar.js": +/*!********************************************!*\ + !*** ./src/components/hide-on-enter-ar.js ***! + \********************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var register = (__webpack_require__(/*! ../core/component */ "./src/core/component.js").registerComponent); +module.exports.Component = register('hide-on-enter-ar', { + init: function () { + var self = this; + this.el.sceneEl.addEventListener('enter-vr', function () { + if (self.el.sceneEl.is('ar-mode')) { + self.el.object3D.visible = false; + } + }); + this.el.sceneEl.addEventListener('exit-vr', function () { + self.el.object3D.visible = true; + }); + } +}); + +/***/ }), + +/***/ "./src/components/hide-on-enter-vr.js": +/*!********************************************!*\ + !*** ./src/components/hide-on-enter-vr.js ***! + \********************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var register = (__webpack_require__(/*! ../core/component */ "./src/core/component.js").registerComponent); +module.exports.Component = register('hide-on-enter-vr', { + init: function () { + var self = this; + this.el.sceneEl.addEventListener('enter-vr', function () { + if (self.el.sceneEl.is('vr-mode')) { + self.el.object3D.visible = false; + } + }); + this.el.sceneEl.addEventListener('exit-vr', function () { + self.el.object3D.visible = true; + }); + } +}); + +/***/ }), + +/***/ "./src/components/hp-mixed-reality-controls.js": +/*!*****************************************************!*\ + !*** ./src/components/hp-mixed-reality-controls.js ***! + \*****************************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var bind = __webpack_require__(/*! ../utils/bind */ "./src/utils/bind.js"); +var registerComponent = (__webpack_require__(/*! ../core/component */ "./src/core/component.js").registerComponent); +var THREE = __webpack_require__(/*! ../lib/three */ "./src/lib/three.js"); +var trackedControlsUtils = __webpack_require__(/*! ../utils/tracked-controls */ "./src/utils/tracked-controls.js"); +var checkControllerPresentAndSetup = trackedControlsUtils.checkControllerPresentAndSetup; +var emitIfAxesChanged = trackedControlsUtils.emitIfAxesChanged; +var onButtonEvent = trackedControlsUtils.onButtonEvent; + +// See Profiles Registry: +// https://github.com/immersive-web/webxr-input-profiles/tree/master/packages/registry +// TODO: Add a more robust system for deriving gamepad name. +var GAMEPAD_ID = 'hp-mixed-reality'; +var AFRAME_CDN_ROOT = (__webpack_require__(/*! ../constants */ "./src/constants/index.js").AFRAME_CDN_ROOT); +var HP_MIXEDL_REALITY_MODEL_GLB_BASE_URL = AFRAME_CDN_ROOT + 'controllers/hp/mixed-reality/'; +var HP_MIXED_REALITY_POSITION_OFFSET = { + x: 0, + y: 0, + z: 0.06 +}; +var HP_MIXED_REALITY_ROTATION_OFFSET = { + _x: Math.PI / 4, + _y: 0, + _z: 0, + _order: 'XYZ' +}; + +/** + * Button IDs: + * 0 - trigger + * 1 - grip + * 3 - X / A + * 4 - Y / B + * + * Axis: + * 2 - joystick x axis + * 3 - joystick y axis + */ +var INPUT_MAPPING_WEBXR = { + left: { + axes: { + touchpad: [2, 3] + }, + buttons: ['trigger', 'grip', 'none', 'thumbstick', 'xbutton', 'ybutton'] + }, + right: { + axes: { + touchpad: [2, 3] + }, + buttons: ['trigger', 'grip', 'none', 'thumbstick', 'abutton', 'bbutton'] + } +}; + +/** + * HP Mixed Reality Controls + */ +module.exports.Component = registerComponent('hp-mixed-reality-controls', { + schema: { + hand: { + default: 'none' + }, + model: { + default: true + }, + orientationOffset: { + type: 'vec3' + } + }, + mapping: INPUT_MAPPING_WEBXR, + init: function () { + var self = this; + this.controllerPresent = false; + this.lastControllerCheck = 0; + this.onButtonChanged = bind(this.onButtonChanged, this); + this.onButtonDown = function (evt) { + onButtonEvent(evt.detail.id, 'down', self, self.data.hand); + }; + this.onButtonUp = function (evt) { + onButtonEvent(evt.detail.id, 'up', self, self.data.hand); + }; + this.onButtonTouchEnd = function (evt) { + onButtonEvent(evt.detail.id, 'touchend', self, self.data.hand); + }; + this.onButtonTouchStart = function (evt) { + onButtonEvent(evt.detail.id, 'touchstart', self, self.data.hand); + }; + this.previousButtonValues = {}; + this.bindMethods(); + }, + update: function () { + var data = this.data; + this.controllerIndex = data.hand === 'right' ? 0 : data.hand === 'left' ? 1 : 2; + }, + play: function () { + this.checkIfControllerPresent(); + this.addControllersUpdateListener(); + }, + pause: function () { + this.removeEventListeners(); + this.removeControllersUpdateListener(); + }, + bindMethods: function () { + this.onModelLoaded = bind(this.onModelLoaded, this); + this.onControllersUpdate = bind(this.onControllersUpdate, this); + this.checkIfControllerPresent = bind(this.checkIfControllerPresent, this); + this.removeControllersUpdateListener = bind(this.removeControllersUpdateListener, this); + this.onAxisMoved = bind(this.onAxisMoved, this); + }, + addEventListeners: function () { + var el = this.el; + el.addEventListener('buttonchanged', this.onButtonChanged); + el.addEventListener('buttondown', this.onButtonDown); + el.addEventListener('buttonup', this.onButtonUp); + el.addEventListener('touchstart', this.onButtonTouchStart); + el.addEventListener('touchend', this.onButtonTouchEnd); + el.addEventListener('axismove', this.onAxisMoved); + el.addEventListener('model-loaded', this.onModelLoaded); + this.controllerEventsActive = true; + }, + removeEventListeners: function () { + var el = this.el; + el.removeEventListener('buttonchanged', this.onButtonChanged); + el.removeEventListener('buttondown', this.onButtonDown); + el.removeEventListener('buttonup', this.onButtonUp); + el.removeEventListener('touchstart', this.onButtonTouchStart); + el.removeEventListener('touchend', this.onButtonTouchEnd); + el.removeEventListener('axismove', this.onAxisMoved); + el.removeEventListener('model-loaded', this.onModelLoaded); + this.controllerEventsActive = false; + }, + checkIfControllerPresent: function () { + var data = this.data; + checkControllerPresentAndSetup(this, GAMEPAD_ID, { + index: this.controllerIndex, + hand: data.hand + }); + }, + injectTrackedControls: function () { + var el = this.el; + var data = this.data; + el.setAttribute('tracked-controls', { + // TODO: verify expected behavior between reserved prefixes. + idPrefix: GAMEPAD_ID, + hand: data.hand, + controller: this.controllerIndex, + orientationOffset: data.orientationOffset + }); + + // Load model. + if (!this.data.model) { + return; + } + this.el.setAttribute('gltf-model', HP_MIXEDL_REALITY_MODEL_GLB_BASE_URL + this.data.hand + '.glb'); + }, + addControllersUpdateListener: function () { + this.el.sceneEl.addEventListener('controllersupdated', this.onControllersUpdate, false); + }, + removeControllersUpdateListener: function () { + this.el.sceneEl.removeEventListener('controllersupdated', this.onControllersUpdate, false); + }, + onControllersUpdate: function () { + // Note that due to gamepadconnected event propagation issues, we don't rely on events. + this.checkIfControllerPresent(); + }, + onButtonChanged: function (evt) { + var button = this.mapping[this.data.hand].buttons[evt.detail.id]; + var analogValue; + if (!button) { + return; + } + if (button === 'trigger') { + analogValue = evt.detail.state.value; + console.log('analog value of trigger press: ' + analogValue); + } + + // Pass along changed event with button state, using button mapping for convenience. + this.el.emit(button + 'changed', evt.detail.state); + }, + onModelLoaded: function (evt) { + var controllerObject3D = evt.detail.model; + if (!this.data.model) { + return; + } + controllerObject3D.position.copy(HP_MIXED_REALITY_POSITION_OFFSET); + controllerObject3D.rotation.copy(HP_MIXED_REALITY_ROTATION_OFFSET); + this.el.emit('controllermodelready', { + name: 'hp-mixed-reality-controls', + model: this.data.model, + rayOrigin: new THREE.Vector3(0, 0, 0) + }); + }, + onAxisMoved: function (evt) { + emitIfAxesChanged(this, this.mapping.axes, evt); + } +}); + +/***/ }), + +/***/ "./src/components/index.js": +/*!*********************************!*\ + !*** ./src/components/index.js ***! + \*********************************/ +/***/ ((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => { + +__webpack_require__(/*! ./animation */ "./src/components/animation.js"); +__webpack_require__(/*! ./anchored */ "./src/components/anchored.js"); +__webpack_require__(/*! ./camera */ "./src/components/camera.js"); +__webpack_require__(/*! ./cursor */ "./src/components/cursor.js"); +__webpack_require__(/*! ./geometry */ "./src/components/geometry.js"); +__webpack_require__(/*! ./generic-tracked-controller-controls */ "./src/components/generic-tracked-controller-controls.js"); +__webpack_require__(/*! ./gltf-model */ "./src/components/gltf-model.js"); +__webpack_require__(/*! ./grabbable */ "./src/components/grabbable.js"); +__webpack_require__(/*! ./hand-tracking-controls */ "./src/components/hand-tracking-controls.js"); +__webpack_require__(/*! ./hand-tracking-grab-controls */ "./src/components/hand-tracking-grab-controls.js"); +__webpack_require__(/*! ./hand-controls */ "./src/components/hand-controls.js"); +__webpack_require__(/*! ./hide-on-enter-ar */ "./src/components/hide-on-enter-ar.js"); +__webpack_require__(/*! ./hide-on-enter-vr */ "./src/components/hide-on-enter-vr.js"); +__webpack_require__(/*! ./hp-mixed-reality-controls */ "./src/components/hp-mixed-reality-controls.js"); +__webpack_require__(/*! ./layer */ "./src/components/layer.js"); +__webpack_require__(/*! ./laser-controls */ "./src/components/laser-controls.js"); +__webpack_require__(/*! ./light */ "./src/components/light.js"); +__webpack_require__(/*! ./line */ "./src/components/line.js"); +__webpack_require__(/*! ./link */ "./src/components/link.js"); +__webpack_require__(/*! ./look-controls */ "./src/components/look-controls.js"); +__webpack_require__(/*! ./magicleap-controls */ "./src/components/magicleap-controls.js"); +__webpack_require__(/*! ./material */ "./src/components/material.js"); +__webpack_require__(/*! ./obb-collider */ "./src/components/obb-collider.js"); +__webpack_require__(/*! ./obj-model */ "./src/components/obj-model.js"); +__webpack_require__(/*! ./oculus-go-controls */ "./src/components/oculus-go-controls.js"); +__webpack_require__(/*! ./oculus-touch-controls */ "./src/components/oculus-touch-controls.js"); +__webpack_require__(/*! ./pico-controls */ "./src/components/pico-controls.js"); +__webpack_require__(/*! ./position */ "./src/components/position.js"); +__webpack_require__(/*! ./raycaster */ "./src/components/raycaster.js"); +__webpack_require__(/*! ./rotation */ "./src/components/rotation.js"); +__webpack_require__(/*! ./scale */ "./src/components/scale.js"); +__webpack_require__(/*! ./shadow */ "./src/components/shadow.js"); +__webpack_require__(/*! ./sound */ "./src/components/sound.js"); +__webpack_require__(/*! ./text */ "./src/components/text.js"); +__webpack_require__(/*! ./tracked-controls */ "./src/components/tracked-controls.js"); +__webpack_require__(/*! ./tracked-controls-webvr */ "./src/components/tracked-controls-webvr.js"); +__webpack_require__(/*! ./tracked-controls-webxr */ "./src/components/tracked-controls-webxr.js"); +__webpack_require__(/*! ./visible */ "./src/components/visible.js"); +__webpack_require__(/*! ./valve-index-controls */ "./src/components/valve-index-controls.js"); +__webpack_require__(/*! ./vive-controls */ "./src/components/vive-controls.js"); +__webpack_require__(/*! ./vive-focus-controls */ "./src/components/vive-focus-controls.js"); +__webpack_require__(/*! ./wasd-controls */ "./src/components/wasd-controls.js"); +__webpack_require__(/*! ./windows-motion-controls */ "./src/components/windows-motion-controls.js"); +__webpack_require__(/*! ./scene/ar-hit-test */ "./src/components/scene/ar-hit-test.js"); +__webpack_require__(/*! ./scene/background */ "./src/components/scene/background.js"); +__webpack_require__(/*! ./scene/debug */ "./src/components/scene/debug.js"); +__webpack_require__(/*! ./scene/device-orientation-permission-ui */ "./src/components/scene/device-orientation-permission-ui.js"); +__webpack_require__(/*! ./scene/embedded */ "./src/components/scene/embedded.js"); +__webpack_require__(/*! ./scene/inspector */ "./src/components/scene/inspector.js"); +__webpack_require__(/*! ./scene/fog */ "./src/components/scene/fog.js"); +__webpack_require__(/*! ./scene/keyboard-shortcuts */ "./src/components/scene/keyboard-shortcuts.js"); +__webpack_require__(/*! ./scene/pool */ "./src/components/scene/pool.js"); +__webpack_require__(/*! ./scene/real-world-meshing */ "./src/components/scene/real-world-meshing.js"); +__webpack_require__(/*! ./scene/reflection */ "./src/components/scene/reflection.js"); +__webpack_require__(/*! ./scene/screenshot */ "./src/components/scene/screenshot.js"); +__webpack_require__(/*! ./scene/stats */ "./src/components/scene/stats.js"); +__webpack_require__(/*! ./scene/xr-mode-ui */ "./src/components/scene/xr-mode-ui.js"); + +/***/ }), + +/***/ "./src/components/laser-controls.js": +/*!******************************************!*\ + !*** ./src/components/laser-controls.js ***! + \******************************************/ +/***/ ((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => { + +var registerComponent = (__webpack_require__(/*! ../core/component */ "./src/core/component.js").registerComponent); +var utils = __webpack_require__(/*! ../utils/ */ "./src/utils/index.js"); +registerComponent('laser-controls', { + schema: { + hand: { + default: 'right' + }, + model: { + default: true + }, + defaultModelColor: { + type: 'color', + default: 'grey' + } + }, + init: function () { + var config = this.config; + var data = this.data; + var el = this.el; + var self = this; + var controlsConfiguration = { + hand: data.hand, + model: data.model + }; + + // Set all controller models. + el.setAttribute('hp-mixed-reality-controls', controlsConfiguration); + el.setAttribute('magicleap-controls', controlsConfiguration); + el.setAttribute('oculus-go-controls', controlsConfiguration); + el.setAttribute('oculus-touch-controls', controlsConfiguration); + el.setAttribute('pico-controls', controlsConfiguration); + el.setAttribute('valve-index-controls', controlsConfiguration); + el.setAttribute('vive-controls', controlsConfiguration); + el.setAttribute('vive-focus-controls', controlsConfiguration); + el.setAttribute('windows-motion-controls', controlsConfiguration); + el.setAttribute('generic-tracked-controller-controls', { + hand: controlsConfiguration.hand + }); + + // Wait for controller to connect, or have a valid pointing pose, before creating ray + el.addEventListener('controllerconnected', createRay); + el.addEventListener('controllerdisconnected', hideRay); + el.addEventListener('controllermodelready', function (evt) { + createRay(evt); + self.modelReady = true; + }); + function createRay(evt) { + var controllerConfig = config[evt.detail.name]; + if (!controllerConfig) { + return; + } + + // Show the line unless a particular config opts to hide it, until a controllermodelready + // event comes through. + var raycasterConfig = utils.extend({ + showLine: true + }, controllerConfig.raycaster || {}); + + // The controllermodelready event contains a rayOrigin that takes into account + // offsets specific to the loaded model. + if (evt.detail.rayOrigin) { + raycasterConfig.origin = evt.detail.rayOrigin.origin; + raycasterConfig.direction = evt.detail.rayOrigin.direction; + raycasterConfig.showLine = true; + } + + // Only apply a default raycaster if it does not yet exist. This prevents it overwriting + // config applied from a controllermodelready event. + if (evt.detail.rayOrigin || !self.modelReady) { + el.setAttribute('raycaster', raycasterConfig); + } else { + el.setAttribute('raycaster', 'showLine', true); + } + el.setAttribute('cursor', utils.extend({ + fuse: false + }, controllerConfig.cursor)); + } + function hideRay(evt) { + var controllerConfig = config[evt.detail.name]; + if (!controllerConfig) { + return; + } + el.setAttribute('raycaster', 'showLine', false); + } + }, + config: { + 'generic-tracked-controller-controls': { + cursor: { + downEvents: ['triggerdown'], + upEvents: ['triggerup'] + } + }, + 'hp-mixed-reality-controls': { + cursor: { + downEvents: ['triggerdown'], + upEvents: ['triggerup'] + }, + raycaster: { + origin: { + x: 0, + y: 0, + z: 0 + } + } + }, + 'magicleap-controls': { + cursor: { + downEvents: ['trackpaddown', 'triggerdown'], + upEvents: ['trackpadup', 'triggerup'] + } + }, + 'oculus-go-controls': { + cursor: { + downEvents: ['triggerdown'], + upEvents: ['triggerup'] + }, + raycaster: { + origin: { + x: 0, + y: 0.0005, + z: 0 + } + } + }, + 'oculus-touch-controls': { + cursor: { + downEvents: ['triggerdown'], + upEvents: ['triggerup'] + }, + raycaster: { + origin: { + x: 0, + y: 0, + z: 0 + } + } + }, + 'pico-controls': { + cursor: { + downEvents: ['triggerdown'], + upEvents: ['triggerup'] + } + }, + 'valve-index-controls': { + cursor: { + downEvents: ['triggerdown'], + upEvents: ['triggerup'] + } + }, + 'vive-controls': { + cursor: { + downEvents: ['triggerdown'], + upEvents: ['triggerup'] + } + }, + 'vive-focus-controls': { + cursor: { + downEvents: ['trackpaddown', 'triggerdown'], + upEvents: ['trackpadup', 'triggerup'] + } + }, + 'windows-motion-controls': { + cursor: { + downEvents: ['triggerdown'], + upEvents: ['triggerup'] + }, + raycaster: { + showLine: false + } + } + } +}); + +/***/ }), + +/***/ "./src/components/layer.js": +/*!*********************************!*\ + !*** ./src/components/layer.js ***! + \*********************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +/* global THREE, XRRigidTransform, XRWebGLBinding */ +var registerComponent = (__webpack_require__(/*! ../core/component */ "./src/core/component.js").registerComponent); +var utils = __webpack_require__(/*! ../utils/ */ "./src/utils/index.js"); +var warn = utils.debug('components:layer:warn'); +module.exports.Component = registerComponent('layer', { + schema: { + type: { + default: 'quad', + oneOf: ['quad', 'monocubemap', 'stereocubemap'] + }, + src: { + type: 'map' + }, + rotateCubemap: { + default: false + }, + width: { + default: 0 + }, + height: { + default: 0 + } + }, + init: function () { + var gl = this.el.sceneEl.renderer.getContext(); + this.quaternion = new THREE.Quaternion(); + this.position = new THREE.Vector3(); + this.bindMethods(); + this.needsRedraw = false; + this.frameBuffer = gl.createFramebuffer(); + var webxrData = this.el.sceneEl.getAttribute('webxr'); + var requiredFeaturesArray = webxrData.requiredFeatures; + if (requiredFeaturesArray.indexOf('layers') === -1) { + requiredFeaturesArray.push('layers'); + this.el.sceneEl.setAttribute('webxr', webxrData); + } + this.el.sceneEl.addEventListener('enter-vr', this.onEnterVR); + this.el.sceneEl.addEventListener('exit-vr', this.onExitVR); + }, + bindMethods: function () { + this.onRequestedReferenceSpace = this.onRequestedReferenceSpace.bind(this); + this.onEnterVR = this.onEnterVR.bind(this); + this.onExitVR = this.onExitVR.bind(this); + }, + update: function (oldData) { + if (this.data.src !== oldData.src) { + this.updateSrc(); + } + }, + updateSrc: function () { + var type = this.data.type; + this.texture = undefined; + if (type === 'quad') { + this.loadQuadImage(); + return; + } + if (type === 'monocubemap' || type === 'stereocubemap') { + this.loadCubeMapImages(); + return; + } + }, + loadCubeMapImages: function () { + var glayer; + var xrGLFactory = this.xrGLFactory; + var frame = this.el.sceneEl.frame; + var src = this.data.src; + var type = this.data.type; + this.visibilityChanged = false; + if (!this.layer) { + return; + } + if (type !== 'monocubemap' && type !== 'stereocubemap') { + return; + } + if (!src.complete) { + this.pendingCubeMapUpdate = true; + } else { + this.pendingCubeMapUpdate = false; + } + if (!this.loadingScreen) { + this.loadingScreen = true; + } else { + this.loadingScreen = false; + } + if (type === 'monocubemap') { + glayer = xrGLFactory.getSubImage(this.layer, frame); + this.loadCubeMapImage(glayer.colorTexture, src, 0); + } else { + glayer = xrGLFactory.getSubImage(this.layer, frame, 'left'); + this.loadCubeMapImage(glayer.colorTexture, src, 0); + glayer = xrGLFactory.getSubImage(this.layer, frame, 'right'); + this.loadCubeMapImage(glayer.colorTexture, src, 6); + } + }, + loadQuadImage: function () { + var src = this.data.src; + var self = this; + this.el.sceneEl.systems.material.loadTexture(src, { + src: src + }, function textureLoaded(texture) { + self.el.sceneEl.renderer.initTexture(texture); + self.texture = texture; + if (src.tagName === 'VIDEO') { + setTimeout(function () { + self.textureIsVideo = true; + }, 1000); + } + if (self.layer) { + self.layer.height = self.data.height / 2 || self.texture.image.height / 1000; + self.layer.width = self.data.width / 2 || self.texture.image.width / 1000; + self.needsRedraw = true; + } + self.updateQuadPanel(); + }); + }, + preGenerateCubeMapTextures: function (src, callback) { + if (this.data.type === 'monocubemap') { + this.generateCubeMapTextures(src, 0, callback); + } else { + this.generateCubeMapTextures(src, 0, callback); + this.generateCubeMapTextures(src, 6, callback); + } + }, + generateCubeMapTextures: function (src, faceOffset, callback) { + var data = this.data; + var cubeFaceSize = this.cubeFaceSize; + var textureSourceCubeFaceSize = Math.min(src.width, src.height); + var cubefaceTextures = []; + var imgTmp0; + var imgTmp2; + for (var i = 0; i < 6; i++) { + var tempCanvas = document.createElement('CANVAS'); + tempCanvas.width = tempCanvas.height = cubeFaceSize; + var tempCanvasContext = tempCanvas.getContext('2d'); + if (data.rotateCubemap) { + if (i === 2 || i === 3) { + tempCanvasContext.save(); + tempCanvasContext.translate(cubeFaceSize, cubeFaceSize); + tempCanvasContext.rotate(Math.PI); + } + } + + // Note that this call to drawImage will not only copy the bytes to the + // canvas but also could resized the image if our cube face size is + // smaller than the source image due to GL max texture size. + tempCanvasContext.drawImage(src, (i + faceOffset) * textureSourceCubeFaceSize, + // top left x coord in source + 0, + // top left y coord in source + textureSourceCubeFaceSize, + // x pixel count from source + textureSourceCubeFaceSize, + // y pixel count from source + 0, + // dest x offset in the canvas + 0, + // dest y offset in the canvas + cubeFaceSize, + // x pixel count in dest + cubeFaceSize // y pixel count in dest + ); + + tempCanvasContext.restore(); + if (callback) { + callback(); + } + cubefaceTextures.push(tempCanvas); + } + if (data.rotateCubemap) { + imgTmp0 = cubefaceTextures[0]; + imgTmp2 = cubefaceTextures[1]; + cubefaceTextures[0] = imgTmp2; + cubefaceTextures[1] = imgTmp0; + imgTmp0 = cubefaceTextures[4]; + imgTmp2 = cubefaceTextures[5]; + cubefaceTextures[4] = imgTmp2; + cubefaceTextures[5] = imgTmp0; + } + if (callback) { + callback(); + } + return cubefaceTextures; + }, + loadCubeMapImage: function (layerColorTexture, src, faceOffset) { + var gl = this.el.sceneEl.renderer.getContext(); + var cubefaceTextures; + + // dont flip the pixels as we load them into the texture buffer. + // TEXTURE_CUBE_MAP expects the Y to be flipped for the faces and it already + // is flipped in our texture image. + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + gl.bindTexture(gl.TEXTURE_CUBE_MAP, layerColorTexture); + if (!src.complete || this.loadingScreen) { + cubefaceTextures = this.loadingScreenImages; + } else { + cubefaceTextures = this.generateCubeMapTextures(src, faceOffset); + } + var errorCode = 0; + cubefaceTextures.forEach(function (canvas, i) { + gl.texSubImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, canvas); + errorCode = gl.getError(); + }); + if (errorCode !== 0) { + console.log('renderingError, WebGL Error Code: ' + errorCode); + } + gl.bindTexture(gl.TEXTURE_CUBE_MAP, null); + }, + tick: function () { + if (!this.el.sceneEl.xrSession) { + return; + } + if (!this.layer && this.el.sceneEl.is('vr-mode')) { + this.initLayer(); + } + this.updateTransform(); + if (this.data.src.complete && (this.pendingCubeMapUpdate || this.loadingScreen || this.visibilityChanged)) { + this.loadCubeMapImages(); + } + if (!this.needsRedraw && !this.layer.needsRedraw && !this.textureIsVideo) { + return; + } + if (this.data.type === 'quad') { + this.draw(); + } + this.needsRedraw = false; + }, + initLayer: function () { + var self = this; + var type = this.data.type; + this.el.sceneEl.xrSession.onvisibilitychange = function (evt) { + self.visibilityChanged = evt.session.visibilityState !== 'hidden'; + }; + if (type === 'quad') { + this.initQuadLayer(); + return; + } + if (type === 'monocubemap' || type === 'stereocubemap') { + this.initCubeMapLayer(); + return; + } + }, + initQuadLayer: function () { + var sceneEl = this.el.sceneEl; + var gl = sceneEl.renderer.getContext(); + var xrGLFactory = this.xrGLFactory = new XRWebGLBinding(sceneEl.xrSession, gl); + if (!this.texture) { + return; + } + this.layer = xrGLFactory.createQuadLayer({ + space: this.referenceSpace, + viewPixelHeight: 2048, + viewPixelWidth: 2048, + height: this.data.height / 2 || this.texture.image.height / 1000, + width: this.data.width / 2 || this.texture.image.width / 1000 + }); + this.initLoadingScreenImages(); + sceneEl.renderer.xr.addLayer(this.layer); + }, + initCubeMapLayer: function () { + var src = this.data.src; + var sceneEl = this.el.sceneEl; + var gl = sceneEl.renderer.getContext(); + var glSizeLimit = gl.getParameter(gl.MAX_CUBE_MAP_TEXTURE_SIZE); + var cubeFaceSize = this.cubeFaceSize = Math.min(glSizeLimit, Math.min(src.width, src.height)); + var xrGLFactory = this.xrGLFactory = new XRWebGLBinding(sceneEl.xrSession, gl); + this.layer = xrGLFactory.createCubeLayer({ + space: this.referenceSpace, + viewPixelWidth: cubeFaceSize, + viewPixelHeight: cubeFaceSize, + layout: this.data.type === 'monocubemap' ? 'mono' : 'stereo', + isStatic: false + }); + this.initLoadingScreenImages(); + this.loadCubeMapImages(); + sceneEl.renderer.xr.addLayer(this.layer); + }, + initLoadingScreenImages: function () { + var cubeFaceSize = this.cubeFaceSize; + var loadingScreenImages = this.loadingScreenImages = []; + for (var i = 0; i < 6; i++) { + var tempCanvas = document.createElement('CANVAS'); + tempCanvas.width = tempCanvas.height = cubeFaceSize; + var tempCanvasContext = tempCanvas.getContext('2d'); + tempCanvas.width = tempCanvas.height = cubeFaceSize; + tempCanvasContext.fillStyle = 'black'; + tempCanvasContext.fillRect(0, 0, cubeFaceSize, cubeFaceSize); + if (i !== 2 && i !== 3) { + tempCanvasContext.translate(cubeFaceSize, 0); + tempCanvasContext.scale(-1, 1); + tempCanvasContext.fillStyle = 'white'; + tempCanvasContext.font = '30px Arial'; + tempCanvasContext.fillText('Loading', cubeFaceSize / 2, cubeFaceSize / 2); + } + loadingScreenImages.push(tempCanvas); + } + }, + destroyLayer: function () { + if (!this.layer) { + return; + } + this.el.sceneEl.renderer.xr.removeLayer(this.layer); + this.layer.destroy(); + this.layer = undefined; + }, + toggleCompositorLayer: function () { + this.enableCompositorLayer(!this.layerEnabled); + }, + enableCompositorLayer: function (enable) { + this.layerEnabled = enable; + this.quadPanelEl.object3D.visible = !this.layerEnabled; + }, + updateQuadPanel: function () { + var quadPanelEl = this.quadPanelEl; + if (!this.quadPanelEl) { + quadPanelEl = this.quadPanelEl = document.createElement('a-entity'); + this.el.appendChild(quadPanelEl); + } + quadPanelEl.setAttribute('material', { + shader: 'flat', + src: this.data.src, + transparent: true + }); + quadPanelEl.setAttribute('geometry', { + primitive: 'plane', + height: this.data.height || this.texture.image.height / 1000, + width: this.data.width || this.texture.image.height / 1000 + }); + }, + draw: function () { + var sceneEl = this.el.sceneEl; + var gl = this.el.sceneEl.renderer.getContext(); + var glayer = this.xrGLFactory.getSubImage(this.layer, sceneEl.frame); + var texture = sceneEl.renderer.properties.get(this.texture).__webglTexture; + var previousFrameBuffer = gl.getParameter(gl.FRAMEBUFFER_BINDING); + gl.viewport(glayer.viewport.x, glayer.viewport.y, glayer.viewport.width, glayer.viewport.height); + gl.bindFramebuffer(gl.FRAMEBUFFER, this.frameBuffer); + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, glayer.colorTexture, 0); + blitTexture(gl, texture, glayer, this.data.src); + gl.bindFramebuffer(gl.FRAMEBUFFER, previousFrameBuffer); + }, + updateTransform: function () { + var el = this.el; + var position = this.position; + var quaternion = this.quaternion; + el.object3D.updateMatrixWorld(); + position.setFromMatrixPosition(el.object3D.matrixWorld); + quaternion.setFromRotationMatrix(el.object3D.matrixWorld); + if (!this.layerEnabled) { + position.set(0, 0, 100000000); + } + this.layer.transform = new XRRigidTransform(position, quaternion); + }, + onEnterVR: function () { + var sceneEl = this.el.sceneEl; + var xrSession = sceneEl.xrSession; + if (!sceneEl.hasWebXR || !XRWebGLBinding || !xrSession) { + warn('The layer component requires WebXR and the layers API enabled'); + return; + } + xrSession.requestReferenceSpace('local-floor').then(this.onRequestedReferenceSpace); + this.needsRedraw = true; + this.layerEnabled = true; + if (this.quadPanelEl) { + this.quadPanelEl.object3D.visible = false; + } + if (this.data.src.play) { + this.data.src.play(); + } + }, + onExitVR: function () { + if (this.quadPanelEl) { + this.quadPanelEl.object3D.visible = true; + } + this.destroyLayer(); + }, + onRequestedReferenceSpace: function (referenceSpace) { + this.referenceSpace = referenceSpace; + } +}); +function blitTexture(gl, texture, subImage, textureEl) { + var xrReadFramebuffer = gl.createFramebuffer(); + var x1offset = subImage.viewport.x; + var y1offset = subImage.viewport.y; + var x2offset = subImage.viewport.x + subImage.viewport.width; + var y2offset = subImage.viewport.y + subImage.viewport.height; + + // Update video texture. + if (textureEl.tagName === 'VIDEO') { + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, textureEl.width, textureEl.height, gl.RGB, gl.UNSIGNED_BYTE, textureEl); + } + + // Bind texture to read framebuffer. + gl.bindFramebuffer(gl.READ_FRAMEBUFFER, xrReadFramebuffer); + gl.framebufferTexture2D(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); + + // Blit into layer buffer. + gl.readBuffer(gl.COLOR_ATTACHMENT0); + gl.blitFramebuffer(0, 0, textureEl.width, textureEl.height, x1offset, y1offset, x2offset, y2offset, gl.COLOR_BUFFER_BIT, gl.NEAREST); + gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null); + gl.deleteFramebuffer(xrReadFramebuffer); +} + +/***/ }), + +/***/ "./src/components/light.js": +/*!*********************************!*\ + !*** ./src/components/light.js ***! + \*********************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var bind = __webpack_require__(/*! ../utils/bind */ "./src/utils/bind.js"); +var utils = __webpack_require__(/*! ../utils */ "./src/utils/index.js"); +var diff = utils.diff; +var debug = __webpack_require__(/*! ../utils/debug */ "./src/utils/debug.js"); +var registerComponent = (__webpack_require__(/*! ../core/component */ "./src/core/component.js").registerComponent); +var THREE = __webpack_require__(/*! ../lib/three */ "./src/lib/three.js"); +var mathUtils = __webpack_require__(/*! ../utils/math */ "./src/utils/math.js"); +var degToRad = THREE.MathUtils.degToRad; +var warn = debug('components:light:warn'); +var CubeLoader = new THREE.CubeTextureLoader(); +var probeCache = {}; + +/** + * Light component. + */ +module.exports.Component = registerComponent('light', { + schema: { + angle: { + default: 60, + if: { + type: ['spot'] + } + }, + color: { + type: 'color', + if: { + type: ['ambient', 'directional', 'hemisphere', 'point', 'spot'] + } + }, + envMap: { + default: '', + if: { + type: ['probe'] + } + }, + groundColor: { + type: 'color', + if: { + type: ['hemisphere'] + } + }, + decay: { + default: 1, + if: { + type: ['point', 'spot'] + } + }, + distance: { + default: 0.0, + min: 0, + if: { + type: ['point', 'spot'] + } + }, + intensity: { + default: 1.0, + min: 0, + if: { + type: ['ambient', 'directional', 'hemisphere', 'point', 'spot', 'probe'] + } + }, + penumbra: { + default: 0, + min: 0, + max: 1, + if: { + type: ['spot'] + } + }, + type: { + default: 'directional', + oneOf: ['ambient', 'directional', 'hemisphere', 'point', 'spot', 'probe'], + schemaChange: true + }, + target: { + type: 'selector', + if: { + type: ['spot', 'directional'] + } + }, + // Shadows. + castShadow: { + default: false, + if: { + type: ['point', 'spot', 'directional'] + } + }, + shadowBias: { + default: 0, + if: { + castShadow: true + } + }, + shadowCameraFar: { + default: 500, + if: { + castShadow: true + } + }, + shadowCameraFov: { + default: 90, + if: { + castShadow: true + } + }, + shadowCameraNear: { + default: 0.5, + if: { + castShadow: true + } + }, + shadowCameraTop: { + default: 5, + if: { + castShadow: true + } + }, + shadowCameraRight: { + default: 5, + if: { + castShadow: true + } + }, + shadowCameraBottom: { + default: -5, + if: { + castShadow: true + } + }, + shadowCameraLeft: { + default: -5, + if: { + castShadow: true + } + }, + shadowCameraVisible: { + default: false, + if: { + castShadow: true + } + }, + shadowCameraAutomatic: { + default: '', + if: { + type: ['directional'] + } + }, + shadowMapHeight: { + default: 512, + if: { + castShadow: true + } + }, + shadowMapWidth: { + default: 512, + if: { + castShadow: true + } + }, + shadowRadius: { + default: 1, + if: { + castShadow: true + } + } + }, + /** + * Notifies scene a light has been added to remove default lighting. + */ + init: function () { + var el = this.el; + this.light = null; + this.defaultTarget = null; + this.system.registerLight(el); + }, + /** + * (Re)create or update light. + */ + update: function (oldData) { + var data = this.data; + var diffData = diff(data, oldData); + var light = this.light; + var self = this; + + // Existing light. + if (light && !('type' in diffData)) { + var shadowsLoaded = false; + // Light type has not changed. Update light. + Object.keys(diffData).forEach(function (key) { + var value = data[key]; + switch (key) { + case 'color': + { + light.color.set(value); + break; + } + case 'groundColor': + { + light.groundColor.set(value); + break; + } + case 'angle': + { + light.angle = degToRad(value); + break; + } + case 'target': + { + // Reset target if selector is null. + if (value === null) { + if (data.type === 'spot' || data.type === 'directional') { + light.target = self.defaultTarget; + } + } else { + // Target specified, set target to entity's `object3D` when it is loaded. + if (value.hasLoaded) { + self.onSetTarget(value, light); + } else { + value.addEventListener('loaded', bind(self.onSetTarget, self, value, light)); + } + } + break; + } + case 'envMap': + self.updateProbeMap(data, light); + break; + case 'castShadow': + case 'shadowBias': + case 'shadowCameraFar': + case 'shadowCameraFov': + case 'shadowCameraNear': + case 'shadowCameraTop': + case 'shadowCameraRight': + case 'shadowCameraBottom': + case 'shadowCameraLeft': + case 'shadowCameraVisible': + case 'shadowMapHeight': + case 'shadowMapWidth': + case 'shadowRadius': + if (!shadowsLoaded) { + self.updateShadow(); + shadowsLoaded = true; + } + break; + case 'shadowCameraAutomatic': + if (data.shadowCameraAutomatic) { + self.shadowCameraAutomaticEls = Array.from(document.querySelectorAll(data.shadowCameraAutomatic)); + } else { + self.shadowCameraAutomaticEls = []; + } + break; + default: + { + light[key] = value; + } + } + }); + return; + } + + // No light yet or light type has changed. Create and add light. + this.setLight(this.data); + this.updateShadow(); + }, + tick: function () { + var bbox = new THREE.Box3(); + var normal = new THREE.Vector3(); + var cameraWorldPosition = new THREE.Vector3(); + var tempMat = new THREE.Matrix4(); + var sphere = new THREE.Sphere(); + var tempVector = new THREE.Vector3(); + return function () { + if (!(this.data.type === 'directional' && this.light.shadow && this.light.shadow.camera instanceof THREE.OrthographicCamera && this.shadowCameraAutomaticEls.length)) return; + var camera = this.light.shadow.camera; + camera.getWorldDirection(normal); + camera.getWorldPosition(cameraWorldPosition); + tempMat.copy(camera.matrixWorld); + tempMat.invert(); + camera.near = 1; + camera.left = 100000; + camera.right = -100000; + camera.top = -100000; + camera.bottom = 100000; + this.shadowCameraAutomaticEls.forEach(function (el) { + bbox.setFromObject(el.object3D); + bbox.getBoundingSphere(sphere); + var distanceToPlane = mathUtils.distanceOfPointFromPlane(cameraWorldPosition, normal, sphere.center); + var pointOnCameraPlane = mathUtils.nearestPointInPlane(cameraWorldPosition, normal, sphere.center, tempVector); + var pointInXYPlane = pointOnCameraPlane.applyMatrix4(tempMat); + camera.near = Math.min(-distanceToPlane - sphere.radius - 1, camera.near); + camera.left = Math.min(-sphere.radius + pointInXYPlane.x, camera.left); + camera.right = Math.max(sphere.radius + pointInXYPlane.x, camera.right); + camera.top = Math.max(sphere.radius + pointInXYPlane.y, camera.top); + camera.bottom = Math.min(-sphere.radius + pointInXYPlane.y, camera.bottom); + }); + camera.updateProjectionMatrix(); + }; + }(), + setLight: function (data) { + var el = this.el; + var newLight = this.getLight(data); + if (newLight) { + if (this.light) { + el.removeObject3D('light'); + } + this.light = newLight; + this.light.el = el; + el.setObject3D('light', this.light); + + // HACK solution for issue #1624 + if (data.type === 'spot' || data.type === 'directional' || data.type === 'hemisphere') { + el.getObject3D('light').translateY(-1); + } + + // set and position default lighttarget as a child to enable spotlight orientation + if (data.type === 'spot') { + el.setObject3D('light-target', this.defaultTarget); + el.getObject3D('light-target').position.set(0, 0, -1); + } + if (data.shadowCameraAutomatic) { + this.shadowCameraAutomaticEls = Array.from(document.querySelectorAll(data.shadowCameraAutomatic)); + } else { + this.shadowCameraAutomaticEls = []; + } + } + }, + /** + * Updates shadow-related properties on the current light. + */ + updateShadow: function () { + var el = this.el; + var data = this.data; + var light = this.light; + light.castShadow = data.castShadow; + + // Shadow camera helper. + var cameraHelper = el.getObject3D('cameraHelper'); + if (data.shadowCameraVisible && !cameraHelper) { + el.setObject3D('cameraHelper', new THREE.CameraHelper(light.shadow.camera)); + } else if (!data.shadowCameraVisible && cameraHelper) { + el.removeObject3D('cameraHelper'); + } + if (!data.castShadow) { + return light; + } + + // Shadow appearance. + light.shadow.bias = data.shadowBias; + light.shadow.radius = data.shadowRadius; + light.shadow.mapSize.height = data.shadowMapHeight; + light.shadow.mapSize.width = data.shadowMapWidth; + + // Shadow camera. + light.shadow.camera.near = data.shadowCameraNear; + light.shadow.camera.far = data.shadowCameraFar; + if (light.shadow.camera instanceof THREE.OrthographicCamera) { + light.shadow.camera.top = data.shadowCameraTop; + light.shadow.camera.right = data.shadowCameraRight; + light.shadow.camera.bottom = data.shadowCameraBottom; + light.shadow.camera.left = data.shadowCameraLeft; + } else { + light.shadow.camera.fov = data.shadowCameraFov; + } + light.shadow.camera.updateProjectionMatrix(); + if (cameraHelper) { + cameraHelper.update(); + } + }, + /** + * Creates a new three.js light object given data object defining the light. + * + * @param {object} data + */ + getLight: function (data) { + var angle = data.angle; + var color = new THREE.Color(data.color); + color = color.getHex(); + var decay = data.decay; + var distance = data.distance; + var groundColor = new THREE.Color(data.groundColor); + groundColor = groundColor.getHex(); + var intensity = data.intensity; + var type = data.type; + var target = data.target; + var light = null; + switch (type.toLowerCase()) { + case 'ambient': + { + return new THREE.AmbientLight(color, intensity); + } + case 'directional': + { + light = new THREE.DirectionalLight(color, intensity); + this.defaultTarget = light.target; + if (target) { + if (target.hasLoaded) { + this.onSetTarget(target, light); + } else { + target.addEventListener('loaded', bind(this.onSetTarget, this, target, light)); + } + } + return light; + } + case 'hemisphere': + { + return new THREE.HemisphereLight(color, groundColor, intensity); + } + case 'point': + { + return new THREE.PointLight(color, intensity, distance, decay); + } + case 'spot': + { + light = new THREE.SpotLight(color, intensity, distance, degToRad(angle), data.penumbra, decay); + this.defaultTarget = light.target; + if (target) { + if (target.hasLoaded) { + this.onSetTarget(target, light); + } else { + target.addEventListener('loaded', bind(this.onSetTarget, this, target, light)); + } + } + return light; + } + case 'probe': + { + light = new THREE.LightProbe(); + this.updateProbeMap(data, light); + return light; + } + default: + { + warn('%s is not a valid light type. ' + 'Choose from ambient, directional, hemisphere, point, spot.', type); + } + } + }, + /** + * Generate the spherical harmonics for the LightProbe from a cube map + */ + updateProbeMap: function (data, light) { + if (!data.envMap) { + // reset parameters if no map + light.copy(new THREE.LightProbe()); + } + if (probeCache[data.envMap] instanceof window.Promise) { + probeCache[data.envMap].then(function (tempLightProbe) { + light.copy(tempLightProbe); + }); + } + if (probeCache[data.envMap] instanceof THREE.LightProbe) { + light.copy(probeCache[data.envMap]); + } + probeCache[data.envMap] = new window.Promise(function (resolve) { + utils.srcLoader.validateCubemapSrc(data.envMap, function loadEnvMap(urls) { + CubeLoader.load(urls, function (cube) { + var tempLightProbe = THREE.LightProbeGenerator.fromCubeTexture(cube); + probeCache[data.envMap] = tempLightProbe; + light.copy(tempLightProbe); + }); + }); + }); + }, + onSetTarget: function (targetEl, light) { + light.target = targetEl.object3D; + }, + /** + * Remove light on remove (callback). + */ + remove: function () { + var el = this.el; + el.removeObject3D('light'); + if (el.getObject3D('cameraHelper')) { + el.removeObject3D('cameraHelper'); + } + } +}); + +/***/ }), + +/***/ "./src/components/line.js": +/*!********************************!*\ + !*** ./src/components/line.js ***! + \********************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +/* global THREE */ +var registerComponent = (__webpack_require__(/*! ../core/component */ "./src/core/component.js").registerComponent); +module.exports.Component = registerComponent('line', { + schema: { + start: { + type: 'vec3', + default: { + x: 0, + y: 0, + z: 0 + } + }, + end: { + type: 'vec3', + default: { + x: 0, + y: 0, + z: 0 + } + }, + color: { + type: 'color', + default: '#74BEC1' + }, + opacity: { + type: 'number', + default: 1 + }, + visible: { + default: true + } + }, + multiple: true, + init: function () { + var data = this.data; + var geometry; + var material; + material = this.material = new THREE.LineBasicMaterial({ + color: data.color, + opacity: data.opacity, + transparent: data.opacity < 1, + visible: data.visible + }); + geometry = this.geometry = new THREE.BufferGeometry(); + geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(2 * 3), 3)); + this.line = new THREE.Line(geometry, material); + this.el.setObject3D(this.attrName, this.line); + }, + update: function (oldData) { + var data = this.data; + var geometry = this.geometry; + var geoNeedsUpdate = false; + var material = this.material; + var positionArray = geometry.attributes.position.array; + + // Update geometry. + if (!isEqualVec3(data.start, oldData.start)) { + positionArray[0] = data.start.x; + positionArray[1] = data.start.y; + positionArray[2] = data.start.z; + geoNeedsUpdate = true; + } + if (!isEqualVec3(data.end, oldData.end)) { + positionArray[3] = data.end.x; + positionArray[4] = data.end.y; + positionArray[5] = data.end.z; + geoNeedsUpdate = true; + } + if (geoNeedsUpdate) { + geometry.attributes.position.needsUpdate = true; + geometry.computeBoundingSphere(); + } + material.color.setStyle(data.color); + material.opacity = data.opacity; + material.transparent = data.opacity < 1; + material.visible = data.visible; + }, + remove: function () { + this.el.removeObject3D(this.attrName, this.line); + } +}); +function isEqualVec3(a, b) { + if (!a || !b) { + return false; + } + return a.x === b.x && a.y === b.y && a.z === b.z; +} + +/***/ }), + +/***/ "./src/components/link.js": +/*!********************************!*\ + !*** ./src/components/link.js ***! + \********************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var registerComponent = (__webpack_require__(/*! ../core/component */ "./src/core/component.js").registerComponent); +var registerShader = (__webpack_require__(/*! ../core/shader */ "./src/core/shader.js").registerShader); +var THREE = __webpack_require__(/*! ../lib/three */ "./src/lib/three.js"); + +/** + * Link component. Connect experiences and traverse between them in VR + * + * @member {object} hiddenEls - Store the hidden elements during peek mode. + */ +module.exports.Component = registerComponent('link', { + schema: { + backgroundColor: { + default: 'red', + type: 'color' + }, + borderColor: { + default: 'white', + type: 'color' + }, + highlighted: { + default: false + }, + highlightedColor: { + default: '#24CAFF', + type: 'color' + }, + href: { + default: '' + }, + image: { + type: 'asset' + }, + on: { + default: 'click' + }, + peekMode: { + default: false + }, + title: { + default: '' + }, + titleColor: { + default: 'white', + type: 'color' + }, + visualAspectEnabled: { + default: false + } + }, + init: function () { + this.navigate = this.navigate.bind(this); + this.previousQuaternion = undefined; + this.quaternionClone = new THREE.Quaternion(); + // Store hidden elements during peek mode so we can show them again later. + this.hiddenEls = []; + }, + update: function (oldData) { + var data = this.data; + var el = this.el; + var backgroundColor; + var strokeColor; + if (!data.visualAspectEnabled) { + return; + } + this.initVisualAspect(); + backgroundColor = data.highlighted ? data.highlightedColor : data.backgroundColor; + strokeColor = data.highlighted ? data.highlightedColor : data.borderColor; + el.setAttribute('material', 'backgroundColor', backgroundColor); + el.setAttribute('material', 'strokeColor', strokeColor); + if (data.on !== oldData.on) { + this.updateEventListener(); + } + if (oldData.peekMode !== undefined && data.peekMode !== oldData.peekMode) { + this.updatePeekMode(); + } + if (!data.image || oldData.image === data.image) { + return; + } + el.setAttribute('material', 'pano', typeof data.image === 'string' ? data.image : data.image.src); + }, + /* + * Toggle all elements and full 360 preview of the linked page. + */ + updatePeekMode: function () { + var el = this.el; + var sphereEl = this.sphereEl; + if (this.data.peekMode) { + this.hideAll(); + el.getObject3D('mesh').visible = false; + sphereEl.setAttribute('visible', true); + } else { + this.showAll(); + el.getObject3D('mesh').visible = true; + sphereEl.setAttribute('visible', false); + } + }, + play: function () { + this.updateEventListener(); + }, + pause: function () { + this.removeEventListener(); + }, + updateEventListener: function () { + var el = this.el; + if (!el.isPlaying) { + return; + } + this.removeEventListener(); + el.addEventListener(this.data.on, this.navigate); + }, + removeEventListener: function () { + var on = this.data.on; + if (!on) { + return; + } + this.el.removeEventListener(on, this.navigate); + }, + initVisualAspect: function () { + var el = this.el; + var semiSphereEl; + var sphereEl; + var textEl; + if (!this.data.visualAspectEnabled || this.visualAspectInitialized) { + return; + } + textEl = this.textEl = this.textEl || document.createElement('a-entity'); + sphereEl = this.sphereEl = this.sphereEl || document.createElement('a-entity'); + semiSphereEl = this.semiSphereEl = this.semiSphereEl || document.createElement('a-entity'); + + // Set portal. + el.setAttribute('geometry', { + primitive: 'circle', + radius: 1.0, + segments: 64 + }); + el.setAttribute('material', { + shader: 'portal', + pano: this.data.image, + side: 'double' + }); + + // Set text that displays the link title and URL. + textEl.setAttribute('text', { + color: this.data.titleColor, + align: 'center', + font: 'kelsonsans', + value: this.data.title || this.data.href, + width: 4 + }); + textEl.setAttribute('position', '0 1.5 0'); + el.appendChild(textEl); + + // Set sphere rendered when camera is close to portal to allow user to peek inside. + semiSphereEl.setAttribute('geometry', { + primitive: 'sphere', + radius: 1.0, + phiStart: 0, + segmentsWidth: 64, + segmentsHeight: 64, + phiLength: 180, + thetaStart: 0, + thetaLength: 360 + }); + semiSphereEl.setAttribute('material', { + shader: 'portal', + borderEnabled: 0.0, + pano: this.data.image, + side: 'back' + }); + semiSphereEl.setAttribute('rotation', '0 180 0'); + semiSphereEl.setAttribute('position', '0 0 0'); + semiSphereEl.setAttribute('visible', false); + el.appendChild(semiSphereEl); + + // Set sphere rendered when camera is close to portal to allow user to peek inside. + sphereEl.setAttribute('geometry', { + primitive: 'sphere', + radius: 10, + segmentsWidth: 64, + segmentsHeight: 64 + }); + sphereEl.setAttribute('material', { + shader: 'portal', + borderEnabled: 0.0, + pano: this.data.image, + side: 'back' + }); + sphereEl.setAttribute('visible', false); + el.appendChild(sphereEl); + this.visualAspectInitialized = true; + }, + navigate: function () { + window.location = this.data.href; + }, + /** + * 1. Swap plane that represents portal with sphere with a hole when the camera is close + * so user can peek inside portal. Sphere is rendered on oposite side of portal + * from where user enters. + * 2. Place the url/title above or inside portal depending on distance to camera. + * 3. Face portal to camera when far away from user. + */ + tick: function () { + var cameraWorldPosition = new THREE.Vector3(); + var elWorldPosition = new THREE.Vector3(); + var quaternion = new THREE.Quaternion(); + var scale = new THREE.Vector3(); + return function () { + var el = this.el; + var object3D = el.object3D; + var camera = el.sceneEl.camera; + var cameraPortalOrientation; + var distance; + var textEl = this.textEl; + if (!this.data.visualAspectEnabled) { + return; + } + + // Update matrices + object3D.updateMatrixWorld(); + camera.parent.updateMatrixWorld(); + camera.updateMatrixWorld(); + object3D.matrix.decompose(elWorldPosition, quaternion, scale); + elWorldPosition.setFromMatrixPosition(object3D.matrixWorld); + cameraWorldPosition.setFromMatrixPosition(camera.matrixWorld); + distance = elWorldPosition.distanceTo(cameraWorldPosition); + if (distance > 20) { + // Store original orientation to be restored when the portal stops facing the camera. + if (!this.previousQuaternion) { + this.quaternionClone.copy(quaternion); + this.previousQuaternion = this.quaternionClone; + } + // If the portal is far away from the user, face portal to camera. + object3D.lookAt(cameraWorldPosition); + } else { + // When portal is close to the user/camera. + cameraPortalOrientation = this.calculateCameraPortalOrientation(); + // If user gets very close to portal, replace with holed sphere they can peek in. + if (distance < 0.5) { + // Configure text size and sphere orientation depending side user approaches portal. + if (this.semiSphereEl.getAttribute('visible') === true) { + return; + } + textEl.setAttribute('text', 'width', 1.5); + if (cameraPortalOrientation <= 0.0) { + textEl.setAttribute('position', '0 0 0.75'); + textEl.setAttribute('rotation', '0 180 0'); + this.semiSphereEl.setAttribute('rotation', '0 0 0'); + } else { + textEl.setAttribute('position', '0 0 -0.75'); + textEl.setAttribute('rotation', '0 0 0'); + this.semiSphereEl.setAttribute('rotation', '0 180 0'); + } + el.getObject3D('mesh').visible = false; + this.semiSphereEl.setAttribute('visible', true); + this.peekCameraPortalOrientation = cameraPortalOrientation; + } else { + // Calculate wich side the camera is approaching the camera (back / front). + // Adjust text orientation based on camera position. + if (cameraPortalOrientation <= 0.0) { + textEl.setAttribute('rotation', '0 180 0'); + } else { + textEl.setAttribute('rotation', '0 0 0'); + } + textEl.setAttribute('text', 'width', 5); + textEl.setAttribute('position', '0 1.5 0'); + el.getObject3D('mesh').visible = true; + this.semiSphereEl.setAttribute('visible', false); + this.peekCameraPortalOrientation = undefined; + } + if (this.previousQuaternion) { + object3D.quaternion.copy(this.previousQuaternion); + this.previousQuaternion = undefined; + } + } + }; + }(), + hideAll: function () { + var el = this.el; + var hiddenEls = this.hiddenEls; + var self = this; + if (hiddenEls.length > 0) { + return; + } + el.sceneEl.object3D.traverse(function (object) { + if (object && object.el && object.el.hasAttribute('link-controls')) { + return; + } + if (!object.el || object === el.sceneEl.object3D || object.el === el || object.el === self.sphereEl || object.el === el.sceneEl.cameraEl || object.el.getAttribute('visible') === false || object.el === self.textEl || object.el === self.semiSphereEl) { + return; + } + object.el.setAttribute('visible', false); + hiddenEls.push(object.el); + }); + }, + showAll: function () { + this.hiddenEls.forEach(function (el) { + el.setAttribute('visible', true); + }); + this.hiddenEls = []; + }, + /** + * Calculate whether the camera faces the front or back face of the portal. + * @returns {number} > 0 if camera faces front of portal, < 0 if it faces back of portal. + */ + calculateCameraPortalOrientation: function () { + var mat4 = new THREE.Matrix4(); + var cameraPosition = new THREE.Vector3(); + var portalNormal = new THREE.Vector3(0, 0, 1); + var portalPosition = new THREE.Vector3(0, 0, 0); + return function () { + var el = this.el; + var camera = el.sceneEl.camera; + + // Reset tmp variables. + cameraPosition.set(0, 0, 0); + portalNormal.set(0, 0, 1); + portalPosition.set(0, 0, 0); + + // Apply portal orientation to the normal. + el.object3D.matrixWorld.extractRotation(mat4); + portalNormal.applyMatrix4(mat4); + + // Calculate portal world position. + el.object3D.updateMatrixWorld(); + el.object3D.localToWorld(portalPosition); + + // Calculate camera world position. + camera.parent.parent.updateMatrixWorld(); + camera.parent.updateMatrixWorld(); + camera.updateMatrixWorld(); + camera.localToWorld(cameraPosition); + + // Calculate vector from portal to camera. + // (portal) -------> (camera) + cameraPosition.sub(portalPosition).normalize(); + portalNormal.normalize(); + + // Side where camera approaches portal is given by sign of dot product of portal normal + // and portal to camera vectors. + return Math.sign(portalNormal.dot(cameraPosition)); + }; + }(), + remove: function () { + this.removeEventListener(); + } +}); + +/* eslint-disable */ +registerShader('portal', { + schema: { + borderEnabled: { + default: 1.0, + type: 'int', + is: 'uniform' + }, + backgroundColor: { + default: 'red', + type: 'color', + is: 'uniform' + }, + pano: { + type: 'map', + is: 'uniform' + }, + strokeColor: { + default: 'white', + type: 'color', + is: 'uniform' + } + }, + vertexShader: ['vec3 portalPosition;', 'varying vec3 vWorldPosition;', 'varying float vDistanceToCenter;', 'varying float vDistance;', 'void main() {', 'vDistanceToCenter = clamp(length(position - vec3(0.0, 0.0, 0.0)), 0.0, 1.0);', 'portalPosition = (modelMatrix * vec4(0.0, 0.0, 0.0, 1.0)).xyz;', 'vDistance = length(portalPosition - cameraPosition);', 'vWorldPosition = (modelMatrix * vec4(position, 1.0)).xyz;', 'gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);', '}'].join('\n'), + fragmentShader: ['#define RECIPROCAL_PI2 0.15915494', 'uniform sampler2D pano;', 'uniform vec3 strokeColor;', 'uniform vec3 backgroundColor;', 'uniform float borderEnabled;', 'varying float vDistanceToCenter;', 'varying float vDistance;', 'varying vec3 vWorldPosition;', 'void main() {', 'vec3 direction = normalize(vWorldPosition - cameraPosition);', 'vec2 sampleUV;', 'float borderThickness = clamp(exp(-vDistance / 50.0), 0.6, 0.95);', 'sampleUV.y = clamp(direction.y * 0.5 + 0.5, 0.0, 1.0);', 'sampleUV.x = atan(direction.z, -direction.x) * -RECIPROCAL_PI2 + 0.5;', 'if (vDistanceToCenter > borderThickness && borderEnabled == 1.0) {', 'gl_FragColor = vec4(strokeColor, 1.0);', '} else {', 'gl_FragColor = mix(texture2D(pano, sampleUV), vec4(backgroundColor, 1.0), clamp(pow((vDistance / 15.0), 2.0), 0.0, 1.0));', '}', '}'].join('\n') +}); +/* eslint-enable */ + +/***/ }), + +/***/ "./src/components/look-controls.js": +/*!*****************************************!*\ + !*** ./src/components/look-controls.js ***! + \*****************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +/* global DeviceOrientationEvent */ +var registerComponent = (__webpack_require__(/*! ../core/component */ "./src/core/component.js").registerComponent); +var THREE = __webpack_require__(/*! ../lib/three */ "./src/lib/three.js"); +var utils = __webpack_require__(/*! ../utils/ */ "./src/utils/index.js"); +var bind = utils.bind; + +// To avoid recalculation at every mouse movement tick +var PI_2 = Math.PI / 2; + +/** + * look-controls. Update entity pose, factoring mouse, touch, and WebVR API data. + */ +module.exports.Component = registerComponent('look-controls', { + dependencies: ['position', 'rotation'], + schema: { + enabled: { + default: true + }, + magicWindowTrackingEnabled: { + default: true + }, + pointerLockEnabled: { + default: false + }, + reverseMouseDrag: { + default: false + }, + reverseTouchDrag: { + default: false + }, + touchEnabled: { + default: true + }, + mouseEnabled: { + default: true + } + }, + init: function () { + this.deltaYaw = 0; + this.previousHMDPosition = new THREE.Vector3(); + this.hmdQuaternion = new THREE.Quaternion(); + this.magicWindowAbsoluteEuler = new THREE.Euler(); + this.magicWindowDeltaEuler = new THREE.Euler(); + this.position = new THREE.Vector3(); + this.magicWindowObject = new THREE.Object3D(); + this.rotation = {}; + this.deltaRotation = {}; + this.savedPose = null; + this.pointerLocked = false; + this.setupMouseControls(); + this.bindMethods(); + this.previousMouseEvent = {}; + this.setupMagicWindowControls(); + + // To save / restore camera pose + this.savedPose = { + position: new THREE.Vector3(), + rotation: new THREE.Euler() + }; + + // Call enter VR handler if the scene has entered VR before the event listeners attached. + if (this.el.sceneEl.is('vr-mode') || this.el.sceneEl.is('ar-mode')) { + this.onEnterVR(); + } + }, + setupMagicWindowControls: function () { + var magicWindowControls; + var data = this.data; + + // Only on mobile devices and only enabled if DeviceOrientation permission has been granted. + if (utils.device.isMobile() || utils.device.isMobileDeviceRequestingDesktopSite()) { + magicWindowControls = this.magicWindowControls = new THREE.DeviceOrientationControls(this.magicWindowObject); + if (typeof DeviceOrientationEvent !== 'undefined' && DeviceOrientationEvent.requestPermission) { + magicWindowControls.enabled = false; + if (this.el.sceneEl.components['device-orientation-permission-ui'].permissionGranted) { + magicWindowControls.enabled = data.magicWindowTrackingEnabled; + } else { + this.el.sceneEl.addEventListener('deviceorientationpermissiongranted', function () { + magicWindowControls.enabled = data.magicWindowTrackingEnabled; + }); + } + } + } + }, + update: function (oldData) { + var data = this.data; + + // Disable grab cursor classes if no longer enabled. + if (data.enabled !== oldData.enabled) { + this.updateGrabCursor(data.enabled); + } + + // Reset magic window eulers if tracking is disabled. + if (oldData && !data.magicWindowTrackingEnabled && oldData.magicWindowTrackingEnabled) { + this.magicWindowAbsoluteEuler.set(0, 0, 0); + this.magicWindowDeltaEuler.set(0, 0, 0); + } + + // Pass on magic window tracking setting to magicWindowControls. + if (this.magicWindowControls) { + this.magicWindowControls.enabled = data.magicWindowTrackingEnabled; + } + if (oldData && !data.pointerLockEnabled !== oldData.pointerLockEnabled) { + this.removeEventListeners(); + this.addEventListeners(); + if (this.pointerLocked) { + this.exitPointerLock(); + } + } + }, + tick: function (t) { + var data = this.data; + if (!data.enabled) { + return; + } + this.updateOrientation(); + }, + play: function () { + this.addEventListeners(); + }, + pause: function () { + this.removeEventListeners(); + if (this.pointerLocked) { + this.exitPointerLock(); + } + }, + remove: function () { + this.removeEventListeners(); + if (this.pointerLocked) { + this.exitPointerLock(); + } + }, + bindMethods: function () { + this.onMouseDown = bind(this.onMouseDown, this); + this.onMouseMove = bind(this.onMouseMove, this); + this.onMouseUp = bind(this.onMouseUp, this); + this.onTouchStart = bind(this.onTouchStart, this); + this.onTouchMove = bind(this.onTouchMove, this); + this.onTouchEnd = bind(this.onTouchEnd, this); + this.onEnterVR = bind(this.onEnterVR, this); + this.onExitVR = bind(this.onExitVR, this); + this.onPointerLockChange = bind(this.onPointerLockChange, this); + this.onPointerLockError = bind(this.onPointerLockError, this); + }, + /** + * Set up states and Object3Ds needed to store rotation data. + */ + setupMouseControls: function () { + this.mouseDown = false; + this.pitchObject = new THREE.Object3D(); + this.yawObject = new THREE.Object3D(); + this.yawObject.position.y = 10; + this.yawObject.add(this.pitchObject); + }, + /** + * Add mouse and touch event listeners to canvas. + */ + addEventListeners: function () { + var sceneEl = this.el.sceneEl; + var canvasEl = sceneEl.canvas; + + // Wait for canvas to load. + if (!canvasEl) { + sceneEl.addEventListener('render-target-loaded', bind(this.addEventListeners, this)); + return; + } + + // Mouse events. + canvasEl.addEventListener('mousedown', this.onMouseDown, false); + window.addEventListener('mousemove', this.onMouseMove, false); + window.addEventListener('mouseup', this.onMouseUp, false); + + // Touch events. + canvasEl.addEventListener('touchstart', this.onTouchStart); + window.addEventListener('touchmove', this.onTouchMove); + window.addEventListener('touchend', this.onTouchEnd); + + // sceneEl events. + sceneEl.addEventListener('enter-vr', this.onEnterVR); + sceneEl.addEventListener('exit-vr', this.onExitVR); + + // Pointer Lock events. + if (this.data.pointerLockEnabled) { + document.addEventListener('pointerlockchange', this.onPointerLockChange, false); + document.addEventListener('mozpointerlockchange', this.onPointerLockChange, false); + document.addEventListener('pointerlockerror', this.onPointerLockError, false); + } + }, + /** + * Remove mouse and touch event listeners from canvas. + */ + removeEventListeners: function () { + var sceneEl = this.el.sceneEl; + var canvasEl = sceneEl && sceneEl.canvas; + if (!canvasEl) { + return; + } + + // Mouse events. + canvasEl.removeEventListener('mousedown', this.onMouseDown); + window.removeEventListener('mousemove', this.onMouseMove); + window.removeEventListener('mouseup', this.onMouseUp); + + // Touch events. + canvasEl.removeEventListener('touchstart', this.onTouchStart); + window.removeEventListener('touchmove', this.onTouchMove); + window.removeEventListener('touchend', this.onTouchEnd); + + // sceneEl events. + sceneEl.removeEventListener('enter-vr', this.onEnterVR); + sceneEl.removeEventListener('exit-vr', this.onExitVR); + + // Pointer Lock events. + document.removeEventListener('pointerlockchange', this.onPointerLockChange, false); + document.removeEventListener('mozpointerlockchange', this.onPointerLockChange, false); + document.removeEventListener('pointerlockerror', this.onPointerLockError, false); + }, + /** + * Update orientation for mobile, mouse drag, and headset. + * Mouse-drag only enabled if HMD is not active. + */ + updateOrientation: function () { + var object3D = this.el.object3D; + var pitchObject = this.pitchObject; + var yawObject = this.yawObject; + var sceneEl = this.el.sceneEl; + + // In VR or AR mode, THREE is in charge of updating the camera pose. + if ((sceneEl.is('vr-mode') || sceneEl.is('ar-mode')) && sceneEl.checkHeadsetConnected()) { + // With WebXR THREE applies headset pose to the object3D internally. + return; + } + this.updateMagicWindowOrientation(); + + // On mobile, do camera rotation with touch events and sensors. + object3D.rotation.x = this.magicWindowDeltaEuler.x + pitchObject.rotation.x; + object3D.rotation.y = this.magicWindowDeltaEuler.y + yawObject.rotation.y; + object3D.rotation.z = this.magicWindowDeltaEuler.z; + }, + updateMagicWindowOrientation: function () { + var magicWindowAbsoluteEuler = this.magicWindowAbsoluteEuler; + var magicWindowDeltaEuler = this.magicWindowDeltaEuler; + // Calculate magic window HMD quaternion. + if (this.magicWindowControls && this.magicWindowControls.enabled) { + this.magicWindowControls.update(); + magicWindowAbsoluteEuler.setFromQuaternion(this.magicWindowObject.quaternion, 'YXZ'); + if (!this.previousMagicWindowYaw && magicWindowAbsoluteEuler.y !== 0) { + this.previousMagicWindowYaw = magicWindowAbsoluteEuler.y; + } + if (this.previousMagicWindowYaw) { + magicWindowDeltaEuler.x = magicWindowAbsoluteEuler.x; + magicWindowDeltaEuler.y += magicWindowAbsoluteEuler.y - this.previousMagicWindowYaw; + magicWindowDeltaEuler.z = magicWindowAbsoluteEuler.z; + this.previousMagicWindowYaw = magicWindowAbsoluteEuler.y; + } + } + }, + /** + * Translate mouse drag into rotation. + * + * Dragging up and down rotates the camera around the X-axis (yaw). + * Dragging left and right rotates the camera around the Y-axis (pitch). + */ + onMouseMove: function (evt) { + var direction; + var movementX; + var movementY; + var pitchObject = this.pitchObject; + var previousMouseEvent = this.previousMouseEvent; + var yawObject = this.yawObject; + + // Not dragging or not enabled. + if (!this.data.enabled || !this.mouseDown && !this.pointerLocked) { + return; + } + + // Calculate delta. + if (this.pointerLocked) { + movementX = evt.movementX || evt.mozMovementX || 0; + movementY = evt.movementY || evt.mozMovementY || 0; + } else { + movementX = evt.screenX - previousMouseEvent.screenX; + movementY = evt.screenY - previousMouseEvent.screenY; + } + this.previousMouseEvent.screenX = evt.screenX; + this.previousMouseEvent.screenY = evt.screenY; + + // Calculate rotation. + direction = this.data.reverseMouseDrag ? 1 : -1; + yawObject.rotation.y += movementX * 0.002 * direction; + pitchObject.rotation.x += movementY * 0.002 * direction; + pitchObject.rotation.x = Math.max(-PI_2, Math.min(PI_2, pitchObject.rotation.x)); + }, + /** + * Register mouse down to detect mouse drag. + */ + onMouseDown: function (evt) { + var sceneEl = this.el.sceneEl; + if (!this.data.enabled || !this.data.mouseEnabled || (sceneEl.is('vr-mode') || sceneEl.is('ar-mode')) && sceneEl.checkHeadsetConnected()) { + return; + } + // Handle only primary button. + if (evt.button !== 0) { + return; + } + var canvasEl = sceneEl && sceneEl.canvas; + this.mouseDown = true; + this.previousMouseEvent.screenX = evt.screenX; + this.previousMouseEvent.screenY = evt.screenY; + this.showGrabbingCursor(); + if (this.data.pointerLockEnabled && !this.pointerLocked) { + if (canvasEl.requestPointerLock) { + canvasEl.requestPointerLock(); + } else if (canvasEl.mozRequestPointerLock) { + canvasEl.mozRequestPointerLock(); + } + } + }, + /** + * Shows grabbing cursor on scene + */ + showGrabbingCursor: function () { + this.el.sceneEl.canvas.style.cursor = 'grabbing'; + }, + /** + * Hides grabbing cursor on scene + */ + hideGrabbingCursor: function () { + this.el.sceneEl.canvas.style.cursor = ''; + }, + /** + * Register mouse up to detect release of mouse drag. + */ + onMouseUp: function () { + this.mouseDown = false; + this.hideGrabbingCursor(); + }, + /** + * Register touch down to detect touch drag. + */ + onTouchStart: function (evt) { + if (evt.touches.length !== 1 || !this.data.touchEnabled || this.el.sceneEl.is('vr-mode') || this.el.sceneEl.is('ar-mode')) { + return; + } + this.touchStart = { + x: evt.touches[0].pageX, + y: evt.touches[0].pageY + }; + this.touchStarted = true; + }, + /** + * Translate touch move to Y-axis rotation. + */ + onTouchMove: function (evt) { + var direction; + var canvas = this.el.sceneEl.canvas; + var deltaY; + var yawObject = this.yawObject; + if (!this.touchStarted || !this.data.touchEnabled) { + return; + } + deltaY = 2 * Math.PI * (evt.touches[0].pageX - this.touchStart.x) / canvas.clientWidth; + direction = this.data.reverseTouchDrag ? 1 : -1; + // Limit touch orientaion to to yaw (y axis). + yawObject.rotation.y -= deltaY * 0.5 * direction; + this.touchStart = { + x: evt.touches[0].pageX, + y: evt.touches[0].pageY + }; + }, + /** + * Register touch end to detect release of touch drag. + */ + onTouchEnd: function () { + this.touchStarted = false; + }, + /** + * Save pose. + */ + onEnterVR: function () { + var sceneEl = this.el.sceneEl; + if (!sceneEl.checkHeadsetConnected()) { + return; + } + this.saveCameraPose(); + this.el.object3D.position.set(0, 0, 0); + this.el.object3D.rotation.set(0, 0, 0); + if (sceneEl.hasWebXR) { + this.el.object3D.matrixAutoUpdate = false; + this.el.object3D.updateMatrix(); + } + }, + /** + * Restore the pose. + */ + onExitVR: function () { + if (!this.el.sceneEl.checkHeadsetConnected()) { + return; + } + this.restoreCameraPose(); + this.previousHMDPosition.set(0, 0, 0); + this.el.object3D.matrixAutoUpdate = true; + }, + /** + * Update Pointer Lock state. + */ + onPointerLockChange: function () { + this.pointerLocked = !!(document.pointerLockElement || document.mozPointerLockElement); + }, + /** + * Recover from Pointer Lock error. + */ + onPointerLockError: function () { + this.pointerLocked = false; + }, + // Exits pointer-locked mode. + exitPointerLock: function () { + document.exitPointerLock(); + this.pointerLocked = false; + }, + /** + * Toggle the feature of showing/hiding the grab cursor. + */ + updateGrabCursor: function (enabled) { + var sceneEl = this.el.sceneEl; + function enableGrabCursor() { + sceneEl.canvas.classList.add('a-grab-cursor'); + } + function disableGrabCursor() { + sceneEl.canvas.classList.remove('a-grab-cursor'); + } + if (!sceneEl.canvas) { + if (enabled) { + sceneEl.addEventListener('render-target-loaded', enableGrabCursor); + } else { + sceneEl.addEventListener('render-target-loaded', disableGrabCursor); + } + return; + } + if (enabled) { + enableGrabCursor(); + return; + } + disableGrabCursor(); + }, + /** + * Save camera pose before entering VR to restore later if exiting. + */ + saveCameraPose: function () { + var el = this.el; + this.savedPose.position.copy(el.object3D.position); + this.savedPose.rotation.copy(el.object3D.rotation); + this.hasSavedPose = true; + }, + /** + * Reset camera pose to before entering VR. + */ + restoreCameraPose: function () { + var el = this.el; + var savedPose = this.savedPose; + if (!this.hasSavedPose) { + return; + } + + // Reset camera orientation. + el.object3D.position.copy(savedPose.position); + el.object3D.rotation.copy(savedPose.rotation); + this.hasSavedPose = false; + } +}); + +/***/ }), + +/***/ "./src/components/magicleap-controls.js": +/*!**********************************************!*\ + !*** ./src/components/magicleap-controls.js ***! + \**********************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var bind = __webpack_require__(/*! ../utils/bind */ "./src/utils/bind.js"); +var registerComponent = (__webpack_require__(/*! ../core/component */ "./src/core/component.js").registerComponent); +var trackedControlsUtils = __webpack_require__(/*! ../utils/tracked-controls */ "./src/utils/tracked-controls.js"); +var checkControllerPresentAndSetup = trackedControlsUtils.checkControllerPresentAndSetup; +var emitIfAxesChanged = trackedControlsUtils.emitIfAxesChanged; +var onButtonEvent = trackedControlsUtils.onButtonEvent; + +// See Profiles Registry: +// https://github.com/immersive-web/webxr-input-profiles/tree/master/packages/registry +// TODO: Add a more robust system for deriving gamepad name. +var GAMEPAD_ID_PREFIX = 'magicleap'; +var GAMEPAD_ID_SUFFIX = '-one'; +var GAMEPAD_ID_COMPOSITE = GAMEPAD_ID_PREFIX + GAMEPAD_ID_SUFFIX; +var AFRAME_CDN_ROOT = (__webpack_require__(/*! ../constants */ "./src/constants/index.js").AFRAME_CDN_ROOT); +var MAGICLEAP_CONTROLLER_MODEL_GLB_URL = AFRAME_CDN_ROOT + 'controllers/magicleap/magicleap-one-controller.glb'; + +/** + * Button IDs: + * 0 - trigger + * 1 - grip + * 2 - touchpad + * 3 - menu (never dispatched on this layer) + * + * Axis: + * 0 - touchpad x axis + * 1 - touchpad y axis + */ +var INPUT_MAPPING_WEBXR = { + axes: { + touchpad: [0, 1] + }, + buttons: ['trigger', 'grip', 'touchpad', 'menu'] +}; + +/** + * Magic Leap Controls + * Interface with Magic Leap control and map Gamepad events to controller + * buttons: trigger, grip, touchpad, and menu. + * Load a controller model. + */ +module.exports.Component = registerComponent('magicleap-controls', { + schema: { + hand: { + default: 'none' + }, + model: { + default: true + }, + orientationOffset: { + type: 'vec3' + } + }, + mapping: INPUT_MAPPING_WEBXR, + init: function () { + var self = this; + this.controllerPresent = false; + this.lastControllerCheck = 0; + this.onButtonChanged = bind(this.onButtonChanged, this); + this.onButtonDown = function (evt) { + onButtonEvent(evt.detail.id, 'down', self); + }; + this.onButtonUp = function (evt) { + onButtonEvent(evt.detail.id, 'up', self); + }; + this.onButtonTouchEnd = function (evt) { + onButtonEvent(evt.detail.id, 'touchend', self); + }; + this.onButtonTouchStart = function (evt) { + onButtonEvent(evt.detail.id, 'touchstart', self); + }; + this.previousButtonValues = {}; + this.bindMethods(); + }, + update: function () { + var data = this.data; + this.controllerIndex = data.hand === 'right' ? 0 : data.hand === 'left' ? 1 : 2; + }, + play: function () { + this.checkIfControllerPresent(); + this.addControllersUpdateListener(); + }, + pause: function () { + this.removeEventListeners(); + this.removeControllersUpdateListener(); + }, + bindMethods: function () { + this.onModelLoaded = bind(this.onModelLoaded, this); + this.onControllersUpdate = bind(this.onControllersUpdate, this); + this.checkIfControllerPresent = bind(this.checkIfControllerPresent, this); + this.removeControllersUpdateListener = bind(this.removeControllersUpdateListener, this); + this.onAxisMoved = bind(this.onAxisMoved, this); + }, + addEventListeners: function () { + var el = this.el; + el.addEventListener('buttonchanged', this.onButtonChanged); + el.addEventListener('buttondown', this.onButtonDown); + el.addEventListener('buttonup', this.onButtonUp); + el.addEventListener('touchstart', this.onButtonTouchStart); + el.addEventListener('touchend', this.onButtonTouchEnd); + el.addEventListener('axismove', this.onAxisMoved); + el.addEventListener('model-loaded', this.onModelLoaded); + this.controllerEventsActive = true; + }, + removeEventListeners: function () { + var el = this.el; + el.removeEventListener('buttonchanged', this.onButtonChanged); + el.removeEventListener('buttondown', this.onButtonDown); + el.removeEventListener('buttonup', this.onButtonUp); + el.removeEventListener('touchstart', this.onButtonTouchStart); + el.removeEventListener('touchend', this.onButtonTouchEnd); + el.removeEventListener('axismove', this.onAxisMoved); + el.removeEventListener('model-loaded', this.onModelLoaded); + this.controllerEventsActive = false; + }, + checkIfControllerPresent: function () { + var data = this.data; + checkControllerPresentAndSetup(this, GAMEPAD_ID_COMPOSITE, { + index: this.controllerIndex, + hand: data.hand + }); + }, + injectTrackedControls: function () { + var el = this.el; + var data = this.data; + el.setAttribute('tracked-controls', { + // TODO: verify expected behavior between reserved prefixes. + idPrefix: GAMEPAD_ID_COMPOSITE, + hand: data.hand, + controller: this.controllerIndex, + orientationOffset: data.orientationOffset + }); + + // Load model. + if (!this.data.model) { + return; + } + this.el.setAttribute('gltf-model', MAGICLEAP_CONTROLLER_MODEL_GLB_URL); + }, + addControllersUpdateListener: function () { + this.el.sceneEl.addEventListener('controllersupdated', this.onControllersUpdate, false); + }, + removeControllersUpdateListener: function () { + this.el.sceneEl.removeEventListener('controllersupdated', this.onControllersUpdate, false); + }, + onControllersUpdate: function () { + // Note that due to gamepadconnected event propagation issues, we don't rely on events. + this.checkIfControllerPresent(); + }, + /** + * Rotate the trigger button based on how hard the trigger is pressed. + */ + onButtonChanged: function (evt) { + var button = this.mapping.buttons[evt.detail.id]; + var analogValue; + if (!button) { + return; + } + if (button === 'trigger') { + analogValue = evt.detail.state.value; + console.log('analog value of trigger press: ' + analogValue); + } + + // Pass along changed event with button state, using button mapping for convenience. + this.el.emit(button + 'changed', evt.detail.state); + }, + onModelLoaded: function (evt) { + var controllerObject3D = evt.detail.model; + // our glb scale is too large. + controllerObject3D.scale.set(0.01, 0.01, 0.01); + }, + onAxisMoved: function (evt) { + emitIfAxesChanged(this, this.mapping.axes, evt); + }, + updateModel: function (buttonName, evtName) {}, + setButtonColor: function (buttonName, color) {} +}); + +/***/ }), + +/***/ "./src/components/material.js": +/*!************************************!*\ + !*** ./src/components/material.js ***! + \************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +/* global Promise */ +var utils = __webpack_require__(/*! ../utils/ */ "./src/utils/index.js"); +var component = __webpack_require__(/*! ../core/component */ "./src/core/component.js"); +var THREE = __webpack_require__(/*! ../lib/three */ "./src/lib/three.js"); +var shader = __webpack_require__(/*! ../core/shader */ "./src/core/shader.js"); +var error = utils.debug('components:material:error'); +var registerComponent = component.registerComponent; +var shaders = shader.shaders; +var shaderNames = shader.shaderNames; + +/** + * Material component. + * + * @member {object} shader - Determines how material is shaded. Defaults to `standard`, + * three.js's implementation of PBR. Another standard shading model is `flat` which + * uses MeshBasicMaterial. + */ +module.exports.Component = registerComponent('material', { + schema: { + alphaTest: { + default: 0.0, + min: 0.0, + max: 1.0 + }, + depthTest: { + default: true + }, + depthWrite: { + default: true + }, + flatShading: { + default: false + }, + npot: { + default: false + }, + offset: { + type: 'vec2', + default: { + x: 0, + y: 0 + } + }, + opacity: { + default: 1.0, + min: 0.0, + max: 1.0 + }, + repeat: { + type: 'vec2', + default: { + x: 1, + y: 1 + } + }, + shader: { + default: 'standard', + oneOf: shaderNames, + schemaChange: true + }, + side: { + default: 'front', + oneOf: ['front', 'back', 'double'] + }, + transparent: { + default: false + }, + vertexColorsEnabled: { + default: false + }, + visible: { + default: true + }, + blending: { + default: 'normal', + oneOf: ['none', 'normal', 'additive', 'subtractive', 'multiply'] + }, + dithering: { + default: true + }, + anisotropy: { + default: 0, + min: 0 + } + }, + init: function () { + this.material = null; + }, + /** + * Update or create material. + * + * @param {object|null} oldData + */ + update: function (oldData) { + var data = this.data; + if (!this.shader || data.shader !== oldData.shader) { + this.updateShader(data.shader); + } + this.shader.update(this.data); + this.updateMaterial(oldData); + }, + updateSchema: function (data) { + var currentShader; + var newShader; + var schema; + var shader; + newShader = data && data.shader; + currentShader = this.oldData && this.oldData.shader; + shader = newShader || currentShader; + schema = shaders[shader] && shaders[shader].schema; + if (!schema) { + error('Unknown shader schema ' + shader); + } + if (currentShader && newShader === currentShader) { + return; + } + this.extendSchema(schema); + this.updateBehavior(); + }, + updateBehavior: function () { + var key; + var sceneEl = this.el.sceneEl; + var schema = this.schema; + var self = this; + var tickProperties; + function tickTime(time, delta) { + var key; + for (key in tickProperties) { + tickProperties[key] = time; + } + self.shader.update(tickProperties); + } + this.tick = undefined; + tickProperties = {}; + for (key in schema) { + if (schema[key].type === 'time') { + this.tick = tickTime; + tickProperties[key] = true; + } + } + if (!sceneEl) { + return; + } + if (this.tick) { + sceneEl.addBehavior(this); + } else { + sceneEl.removeBehavior(this); + } + }, + updateShader: function (shaderName) { + var data = this.data; + var Shader = shaders[shaderName] && shaders[shaderName].Shader; + var shaderInstance; + if (!Shader) { + throw new Error('Unknown shader ' + shaderName); + } + + // Get material from A-Frame shader. + shaderInstance = this.shader = new Shader(); + shaderInstance.el = this.el; + shaderInstance.init(data); + this.setMaterial(shaderInstance.material); + this.updateSchema(data); + }, + /** + * Set and update base material properties. + * Set `needsUpdate` when needed. + */ + updateMaterial: function (oldData) { + var data = this.data; + var material = this.material; + var oldDataHasKeys; + + // Base material properties. + material.alphaTest = data.alphaTest; + material.depthTest = data.depthTest !== false; + material.depthWrite = data.depthWrite !== false; + material.opacity = data.opacity; + material.flatShading = data.flatShading; + material.side = parseSide(data.side); + material.transparent = data.transparent !== false || data.opacity < 1.0; + material.vertexColors = data.vertexColorsEnabled; + material.visible = data.visible; + material.blending = parseBlending(data.blending); + material.dithering = data.dithering; + + // Check if material needs update. + for (oldDataHasKeys in oldData) { + break; + } + if (oldDataHasKeys && (oldData.alphaTest !== data.alphaTest || oldData.side !== data.side || oldData.vertexColorsEnabled !== data.vertexColorsEnabled)) { + material.needsUpdate = true; + } + }, + /** + * Remove material on remove (callback). + * Dispose of it from memory and unsubscribe from scene updates. + */ + remove: function () { + var defaultMaterial = new THREE.MeshBasicMaterial(); + var material = this.material; + var object3D = this.el.getObject3D('mesh'); + if (object3D) { + object3D.material = defaultMaterial; + } + disposeMaterial(material, this.system); + }, + /** + * (Re)create new material. Has side-effects of setting `this.material` and updating + * material registration in scene. + * + * @param {object} data - Material component data. + * @param {object} type - Material type to create. + * @returns {object} Material. + */ + setMaterial: function (material) { + var el = this.el; + var mesh; + var system = this.system; + if (this.material) { + disposeMaterial(this.material, system); + } + this.material = material; + system.registerMaterial(material); + + // Set on mesh. If mesh does not exist, wait for it. + mesh = el.getObject3D('mesh'); + if (mesh) { + mesh.material = material; + } else { + el.addEventListener('object3dset', function waitForMesh(evt) { + if (evt.detail.type !== 'mesh' || evt.target !== el) { + return; + } + el.getObject3D('mesh').material = material; + el.removeEventListener('object3dset', waitForMesh); + }); + } + } +}); + +/** + * Return a three.js constant determining which material face sides to render + * based on the side parameter (passed as a component property). + * + * @param {string} [side=front] - `front`, `back`, or `double`. + * @returns {number} THREE.FrontSide, THREE.BackSide, or THREE.DoubleSide. + */ +function parseSide(side) { + switch (side) { + case 'back': + { + return THREE.BackSide; + } + case 'double': + { + return THREE.DoubleSide; + } + default: + { + // Including case `front`. + return THREE.FrontSide; + } + } +} + +/** + * Return a three.js constant determining blending + * + * @param {string} [blending=normal] + * - `none`, additive`, `subtractive`,`multiply` or `normal`. + * @returns {number} + */ +function parseBlending(blending) { + switch (blending) { + case 'none': + { + return THREE.NoBlending; + } + case 'additive': + { + return THREE.AdditiveBlending; + } + case 'subtractive': + { + return THREE.SubtractiveBlending; + } + case 'multiply': + { + return THREE.MultiplyBlending; + } + default: + { + return THREE.NormalBlending; + } + } +} + +/** + * Dispose of material from memory and unsubscribe material from scene updates like fog. + */ +function disposeMaterial(material, system) { + material.dispose(); + system.unregisterMaterial(material); +} + +/***/ }), + +/***/ "./src/components/obb-collider.js": +/*!****************************************!*\ + !*** ./src/components/obb-collider.js ***! + \****************************************/ +/***/ ((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => { + +var registerComponent = (__webpack_require__(/*! ../core/component */ "./src/core/component.js").registerComponent); +var THREE = __webpack_require__(/*! ../lib/three */ "./src/lib/three.js"); +registerComponent('obb-collider', { + schema: { + size: { + default: 0 + }, + trackedObject3D: { + default: '' + }, + minimumColliderDimension: { + default: 0.02 + }, + centerModel: { + default: false + } + }, + init: function () { + this.previousScale = new THREE.Vector3().copy(this.el.object3D.scale); + this.auxEuler = new THREE.Euler(); + this.boundingBox = new THREE.Box3(); + this.boundingBoxSize = new THREE.Vector3(); + this.updateCollider = this.updateCollider.bind(this); + this.onModelLoaded = this.onModelLoaded.bind(this); + this.updateBoundingBox = this.updateBoundingBox.bind(this); + this.el.addEventListener('model-loaded', this.onModelLoaded); + this.updateCollider(); + this.system.addCollider(this.el); + }, + remove: function () { + this.system.removeCollider(this.el); + }, + update: function () { + if (this.data.trackedObject3D) { + this.trackedObject3DPath = this.data.trackedObject3D.split('.'); + } + }, + onModelLoaded: function () { + if (this.data.centerModel) { + this.centerModel(); + } + this.updateCollider(); + }, + centerModel: function () { + var el = this.el; + var model = el.components['gltf-model'] && el.components['gltf-model'].model; + var box; + var center; + if (!model) { + return; + } + this.el.removeObject3D('mesh'); + box = new THREE.Box3().setFromObject(model); + center = box.getCenter(new THREE.Vector3()); + model.position.x += model.position.x - center.x; + model.position.y += model.position.y - center.y; + model.position.z += model.position.z - center.z; + this.el.setObject3D('mesh', model); + }, + updateCollider: function () { + var el = this.el; + var boundingBoxSize = this.boundingBoxSize; + var aabb = this.aabb = this.aabb || new THREE.OBB(); + this.obb = this.obb || new THREE.OBB(); + + // Defer if entity has not yet loaded. + if (!el.hasLoaded) { + el.addEventListener('loaded', this.updateCollider); + return; + } + this.updateBoundingBox(); + aabb.halfSize.copy(boundingBoxSize).multiplyScalar(0.5); + if (this.el.sceneEl.systems['obb-collider'].data.showColliders) { + this.showCollider(); + } + }, + showCollider: function () { + this.updateColliderMesh(); + this.renderColliderMesh.visible = true; + }, + updateColliderMesh: function () { + var renderColliderMesh = this.renderColliderMesh; + var boundingBoxSize = this.boundingBoxSize; + if (!renderColliderMesh) { + this.initColliderMesh(); + return; + } + + // Destroy current geometry. + renderColliderMesh.geometry.dispose(); + renderColliderMesh.geometry = new THREE.BoxGeometry(boundingBoxSize.x, boundingBoxSize.y, boundingBoxSize.z); + }, + hideCollider: function () { + if (!this.renderColliderMesh) { + return; + } + this.renderColliderMesh.visible = false; + }, + initColliderMesh: function () { + var boundingBoxSize; + var renderColliderGeometry; + var renderColliderMesh; + boundingBoxSize = this.boundingBoxSize; + renderColliderGeometry = this.renderColliderGeometry = new THREE.BoxGeometry(boundingBoxSize.x, boundingBoxSize.y, boundingBoxSize.z); + renderColliderMesh = this.renderColliderMesh = new THREE.Mesh(renderColliderGeometry, new THREE.MeshLambertMaterial({ + color: 0x00ff00, + side: THREE.DoubleSide + })); + renderColliderMesh.matrixAutoUpdate = false; + renderColliderMesh.matrixWorldAutoUpdate = false; + // THREE scene forces matrix world update even if matrixWorldAutoUpdate set to false. + renderColliderMesh.updateMatrixWorld = function () {/* no op */}; + this.el.sceneEl.object3D.add(renderColliderMesh); + }, + updateBoundingBox: function () { + var auxPosition = new THREE.Vector3(); + var auxScale = new THREE.Vector3(); + var auxQuaternion = new THREE.Quaternion(); + var identityQuaternion = new THREE.Quaternion(); + var auxMatrix = new THREE.Matrix4(); + return function () { + var auxEuler = this.auxEuler; + var boundingBox = this.boundingBox; + var size = this.data.size; + var trackedObject3D = this.trackedObject3D || this.el.object3D; + var boundingBoxSize = this.boundingBoxSize; + var minimumColliderDimension = this.data.minimumColliderDimension; + + // user defined size takes precedence. + if (size) { + this.boundingBoxSize.x = size; + this.boundingBoxSize.y = size; + this.boundingBoxSize.z = size; + return; + } + + // Bounding box is created axis-aligned AABB. + // If there's any rotation the box will have the wrong size. + // It undoes the local entity rotation and then restores so box has the expected size. + // We also undo the parent world rotation. + auxEuler.copy(trackedObject3D.rotation); + trackedObject3D.rotation.set(0, 0, 0); + trackedObject3D.parent.matrixWorld.decompose(auxPosition, auxQuaternion, auxScale); + auxMatrix.compose(auxPosition, identityQuaternion, auxScale); + trackedObject3D.parent.matrixWorld.copy(auxMatrix); + + // Calculate bounding box size. + boundingBox.setFromObject(trackedObject3D, true); + boundingBox.getSize(boundingBoxSize); + + // Enforce minimum dimensions. + boundingBoxSize.x = boundingBoxSize.x < minimumColliderDimension ? minimumColliderDimension : boundingBoxSize.x; + boundingBoxSize.y = boundingBoxSize.y < minimumColliderDimension ? minimumColliderDimension : boundingBoxSize.y; + boundingBoxSize.z = boundingBoxSize.z < minimumColliderDimension ? minimumColliderDimension : boundingBoxSize.z; + + // Restore rotations. + trackedObject3D.parent.matrixWorld.compose(auxPosition, auxQuaternion, auxScale); + this.el.object3D.rotation.copy(auxEuler); + }; + }(), + checkTrackedObject: function () { + var trackedObject3DPath = this.trackedObject3DPath; + var trackedObject3D; + if (trackedObject3DPath && trackedObject3DPath.length && !this.trackedObject3D) { + trackedObject3D = this.el; + for (var i = 0; i < trackedObject3DPath.length; i++) { + trackedObject3D = trackedObject3D[trackedObject3DPath[i]]; + if (!trackedObject3D) { + break; + } + } + if (trackedObject3D) { + this.trackedObject3D = trackedObject3D; + this.updateCollider(); + } + } + return this.trackedObject3D; + }, + tick: function () { + var auxPosition = new THREE.Vector3(); + var auxScale = new THREE.Vector3(); + var auxQuaternion = new THREE.Quaternion(); + var auxMatrix = new THREE.Matrix4(); + return function () { + var obb = this.obb; + var renderColliderMesh = this.renderColliderMesh; + var trackedObject3D = this.checkTrackedObject() || this.el.object3D; + if (!trackedObject3D) { + return; + } + trackedObject3D.updateMatrix(); + trackedObject3D.updateMatrixWorld(true); + trackedObject3D.matrixWorld.decompose(auxPosition, auxQuaternion, auxScale); + + // Recalculate collider if scale has changed. + if (Math.abs(auxScale.x - this.previousScale.x) > 0.0001 || Math.abs(auxScale.y - this.previousScale.y) > 0.0001 || Math.abs(auxScale.z - this.previousScale.z) > 0.0001) { + this.updateCollider(); + } + this.previousScale.copy(auxScale); + + // reset scale, keep position and rotation + auxScale.set(1, 1, 1); + auxMatrix.compose(auxPosition, auxQuaternion, auxScale); + // Update OBB visual representation. + if (renderColliderMesh) { + renderColliderMesh.matrixWorld.copy(auxMatrix); + } + + // Reset OBB with AABB and apply entity matrix. applyMatrix4 changes OBB internal state. + obb.copy(this.aabb); + obb.applyMatrix4(auxMatrix); + }; + }() +}); + +/***/ }), + +/***/ "./src/components/obj-model.js": +/*!*************************************!*\ + !*** ./src/components/obj-model.js ***! + \*************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var debug = __webpack_require__(/*! ../utils/debug */ "./src/utils/debug.js"); +var registerComponent = (__webpack_require__(/*! ../core/component */ "./src/core/component.js").registerComponent); +var THREE = __webpack_require__(/*! ../lib/three */ "./src/lib/three.js"); +var warn = debug('components:obj-model:warn'); +module.exports.Component = registerComponent('obj-model', { + schema: { + mtl: { + type: 'model' + }, + obj: { + type: 'model' + } + }, + init: function () { + var self = this; + this.model = null; + this.objLoader = new THREE.OBJLoader(); + this.mtlLoader = new THREE.MTLLoader(this.objLoader.manager); + // Allow cross-origin images to be loaded. + this.mtlLoader.crossOrigin = ''; + this.el.addEventListener('componentinitialized', function (evt) { + if (!self.model) { + return; + } + if (evt.detail.name !== 'material') { + return; + } + self.applyMaterial(); + }); + }, + update: function () { + var data = this.data; + if (!data.obj) { + return; + } + this.resetMesh(); + this.loadObj(data.obj, data.mtl); + }, + remove: function () { + if (!this.model) { + return; + } + this.resetMesh(); + }, + resetMesh: function () { + this.el.removeObject3D('mesh'); + }, + loadObj: function (objUrl, mtlUrl) { + var self = this; + var el = this.el; + var mtlLoader = this.mtlLoader; + var objLoader = this.objLoader; + var rendererSystem = this.el.sceneEl.systems.renderer; + var BASE_PATH = mtlUrl.substr(0, mtlUrl.lastIndexOf('/') + 1); + if (mtlUrl) { + // .OBJ with an .MTL. + if (el.hasAttribute('material')) { + warn('Material component properties are ignored when a .MTL is provided'); + } + mtlLoader.setResourcePath(BASE_PATH); + mtlLoader.load(mtlUrl, function (materials) { + materials.preload(); + objLoader.setMaterials(materials); + objLoader.load(objUrl, function (objModel) { + self.model = objModel; + self.model.traverse(function (object) { + if (object.isMesh) { + var material = object.material; + if (material.map) rendererSystem.applyColorCorrection(material.map); + if (material.emissiveMap) rendererSystem.applyColorCorrection(material.emissiveMap); + } + }); + el.setObject3D('mesh', objModel); + el.emit('model-loaded', { + format: 'obj', + model: objModel + }); + }); + }); + return; + } + + // .OBJ only. + objLoader.load(objUrl, function loadObjOnly(objModel) { + self.model = objModel; + self.applyMaterial(); + el.setObject3D('mesh', objModel); + el.emit('model-loaded', { + format: 'obj', + model: objModel + }); + }); + }, + /** + * Apply material from material component recursively. + */ + applyMaterial: function () { + var material = this.el.components.material; + if (!material) { + return; + } + this.model.traverse(function (child) { + if (child instanceof THREE.Mesh) { + child.material = material.material; + } + }); + } +}); + +/***/ }), + +/***/ "./src/components/oculus-go-controls.js": +/*!**********************************************!*\ + !*** ./src/components/oculus-go-controls.js ***! + \**********************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var registerComponent = (__webpack_require__(/*! ../core/component */ "./src/core/component.js").registerComponent); +var bind = __webpack_require__(/*! ../utils/bind */ "./src/utils/bind.js"); +var trackedControlsUtils = __webpack_require__(/*! ../utils/tracked-controls */ "./src/utils/tracked-controls.js"); +var checkControllerPresentAndSetup = trackedControlsUtils.checkControllerPresentAndSetup; +var emitIfAxesChanged = trackedControlsUtils.emitIfAxesChanged; +var onButtonEvent = trackedControlsUtils.onButtonEvent; +var isWebXRAvailable = (__webpack_require__(/*! ../utils/ */ "./src/utils/index.js").device.isWebXRAvailable); +var GAMEPAD_ID_WEBXR = 'oculus-go'; +var GAMEPAD_ID_WEBVR = 'Oculus Go'; +var AFRAME_CDN_ROOT = (__webpack_require__(/*! ../constants */ "./src/constants/index.js").AFRAME_CDN_ROOT); +var OCULUS_GO_CONTROLLER_MODEL_URL = AFRAME_CDN_ROOT + 'controllers/oculus/go/oculus-go-controller.gltf'; + +// Prefix for Gen1 and Gen2 Oculus Touch Controllers. +var GAMEPAD_ID_PREFIX = isWebXRAvailable ? GAMEPAD_ID_WEBXR : GAMEPAD_ID_WEBVR; + +/** + * Button indices: + * 0 - trackpad + * 1 - trigger + * + * Axis: + * 0 - trackpad x + * 1 - trackpad y + */ +var INPUT_MAPPING_WEBVR = { + axes: { + trackpad: [0, 1] + }, + buttons: ['trackpad', 'trigger'] +}; + +/** + * Button indices: + * 0 - trigger + * 1 - none + * 2 - touchpad + * + * Axis: + * 0 - touchpad x + * 1 - touchpad y + * Reference: https://github.com/immersive-web/webxr-input-profiles/blob/master/packages/registry/profiles/oculus/oculus-go.json + */ +var INPUT_MAPPING_WEBXR = { + axes: { + touchpad: [0, 1] + }, + buttons: ['trigger', 'none', 'touchpad'] +}; +var INPUT_MAPPING = isWebXRAvailable ? INPUT_MAPPING_WEBXR : INPUT_MAPPING_WEBVR; + +/** + * Oculus Go controls. + * Interface with Oculus Go controller and map Gamepad events to + * controller buttons: trackpad, trigger + * Load a controller model and highlight the pressed buttons. + */ +module.exports.Component = registerComponent('oculus-go-controls', { + schema: { + hand: { + default: '' + }, + // This informs the degenerate arm model. + buttonColor: { + type: 'color', + default: '#FFFFFF' + }, + buttonTouchedColor: { + type: 'color', + default: '#BBBBBB' + }, + buttonHighlightColor: { + type: 'color', + default: '#7A7A7A' + }, + model: { + default: true + }, + orientationOffset: { + type: 'vec3' + }, + armModel: { + default: true + } + }, + mapping: INPUT_MAPPING, + bindMethods: function () { + this.onModelLoaded = bind(this.onModelLoaded, this); + this.onControllersUpdate = bind(this.onControllersUpdate, this); + this.checkIfControllerPresent = bind(this.checkIfControllerPresent, this); + this.removeControllersUpdateListener = bind(this.removeControllersUpdateListener, this); + this.onAxisMoved = bind(this.onAxisMoved, this); + }, + init: function () { + var self = this; + this.onButtonChanged = bind(this.onButtonChanged, this); + this.onButtonDown = function (evt) { + onButtonEvent(evt.detail.id, 'down', self); + }; + this.onButtonUp = function (evt) { + onButtonEvent(evt.detail.id, 'up', self); + }; + this.onButtonTouchStart = function (evt) { + onButtonEvent(evt.detail.id, 'touchstart', self); + }; + this.onButtonTouchEnd = function (evt) { + onButtonEvent(evt.detail.id, 'touchend', self); + }; + this.controllerPresent = false; + this.lastControllerCheck = 0; + this.bindMethods(); + }, + addEventListeners: function () { + var el = this.el; + el.addEventListener('buttonchanged', this.onButtonChanged); + el.addEventListener('buttondown', this.onButtonDown); + el.addEventListener('buttonup', this.onButtonUp); + el.addEventListener('touchstart', this.onButtonTouchStart); + el.addEventListener('touchend', this.onButtonTouchEnd); + el.addEventListener('model-loaded', this.onModelLoaded); + el.addEventListener('axismove', this.onAxisMoved); + this.controllerEventsActive = true; + }, + removeEventListeners: function () { + var el = this.el; + el.removeEventListener('buttonchanged', this.onButtonChanged); + el.removeEventListener('buttondown', this.onButtonDown); + el.removeEventListener('buttonup', this.onButtonUp); + el.removeEventListener('touchstart', this.onButtonTouchStart); + el.removeEventListener('touchend', this.onButtonTouchEnd); + el.removeEventListener('model-loaded', this.onModelLoaded); + el.removeEventListener('axismove', this.onAxisMoved); + this.controllerEventsActive = false; + }, + checkIfControllerPresent: function () { + checkControllerPresentAndSetup(this, GAMEPAD_ID_PREFIX, this.data.hand ? { + hand: this.data.hand + } : {}); + }, + play: function () { + this.checkIfControllerPresent(); + this.addControllersUpdateListener(); + }, + pause: function () { + this.removeEventListeners(); + this.removeControllersUpdateListener(); + }, + injectTrackedControls: function () { + var el = this.el; + var data = this.data; + el.setAttribute('tracked-controls', { + armModel: data.armModel, + hand: data.hand, + idPrefix: GAMEPAD_ID_PREFIX, + orientationOffset: data.orientationOffset + }); + if (!this.data.model) { + return; + } + this.el.setAttribute('gltf-model', OCULUS_GO_CONTROLLER_MODEL_URL); + }, + addControllersUpdateListener: function () { + this.el.sceneEl.addEventListener('controllersupdated', this.onControllersUpdate, false); + }, + removeControllersUpdateListener: function () { + this.el.sceneEl.removeEventListener('controllersupdated', this.onControllersUpdate, false); + }, + onControllersUpdate: function () { + this.checkIfControllerPresent(); + }, + // No need for onButtonChanged, since Oculus Go controller has no analog buttons. + + onModelLoaded: function (evt) { + var controllerObject3D = evt.detail.model; + var buttonMeshes; + if (evt.target !== this.el || !this.data.model) { + return; + } + buttonMeshes = this.buttonMeshes = {}; + buttonMeshes.trigger = controllerObject3D.getObjectByName('oculus_go_button_trigger'); + buttonMeshes.trackpad = controllerObject3D.getObjectByName('oculus_go_touchpad'); + buttonMeshes.touchpad = controllerObject3D.getObjectByName('oculus_go_touchpad'); + }, + onButtonChanged: function (evt) { + var button = this.mapping.buttons[evt.detail.id]; + if (!button) return; + // Pass along changed event with button state, using button mapping for convenience. + this.el.emit(button + 'changed', evt.detail.state); + }, + onAxisMoved: function (evt) { + emitIfAxesChanged(this, this.mapping.axes, evt); + }, + updateModel: function (buttonName, evtName) { + if (!this.data.model) { + return; + } + this.updateButtonModel(buttonName, evtName); + }, + updateButtonModel: function (buttonName, state) { + var buttonMeshes = this.buttonMeshes; + if (!buttonMeshes || !buttonMeshes[buttonName]) { + return; + } + var color; + var button; + switch (state) { + case 'down': + color = this.data.buttonHighlightColor; + break; + case 'touchstart': + color = this.data.buttonTouchedColor; + break; + default: + color = this.data.buttonColor; + } + button = buttonMeshes[buttonName]; + button.material.color.set(color); + } +}); + +/***/ }), + +/***/ "./src/components/oculus-touch-controls.js": +/*!*************************************************!*\ + !*** ./src/components/oculus-touch-controls.js ***! + \*************************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var bind = __webpack_require__(/*! ../utils/bind */ "./src/utils/bind.js"); +var registerComponent = (__webpack_require__(/*! ../core/component */ "./src/core/component.js").registerComponent); +var THREE = __webpack_require__(/*! ../lib/three */ "./src/lib/three.js"); +var trackedControlsUtils = __webpack_require__(/*! ../utils/tracked-controls */ "./src/utils/tracked-controls.js"); +var checkControllerPresentAndSetup = trackedControlsUtils.checkControllerPresentAndSetup; +var emitIfAxesChanged = trackedControlsUtils.emitIfAxesChanged; +var onButtonEvent = trackedControlsUtils.onButtonEvent; +var isWebXRAvailable = (__webpack_require__(/*! ../utils/ */ "./src/utils/index.js").device.isWebXRAvailable); +var GAMEPAD_ID_WEBXR = 'oculus-touch'; +var GAMEPAD_ID_WEBVR = 'Oculus Touch'; + +// Prefix for Gen1 and Gen2 Oculus Touch Controllers. +var GAMEPAD_ID_PREFIX = isWebXRAvailable ? GAMEPAD_ID_WEBXR : GAMEPAD_ID_WEBVR; + +// First generation model URL. +var AFRAME_CDN_ROOT = (__webpack_require__(/*! ../constants */ "./src/constants/index.js").AFRAME_CDN_ROOT); +var TOUCH_CONTROLLER_MODEL_BASE_URL = AFRAME_CDN_ROOT + 'controllers/oculus/oculus-touch-controller-'; +var META_CONTROLLER_MODEL_BASE_URL = AFRAME_CDN_ROOT + 'controllers/meta/'; +var OCULUS_TOUCH_WEBVR = { + left: { + modelUrl: TOUCH_CONTROLLER_MODEL_BASE_URL + 'left.gltf', + rayOrigin: { + origin: { + x: 0.008, + y: -0.01, + z: 0 + }, + direction: { + x: 0, + y: -0.8, + z: -1 + } + }, + modelPivotOffset: new THREE.Vector3(-0.005, 0.003, -0.055), + modelPivotRotation: new THREE.Euler(0, 0, 0) + }, + right: { + modelUrl: TOUCH_CONTROLLER_MODEL_BASE_URL + 'right.gltf', + rayOrigin: { + origin: { + x: -0.008, + y: -0.01, + z: 0 + }, + direction: { + x: 0, + y: -0.8, + z: -1 + } + }, + modelPivotOffset: new THREE.Vector3(0.005, 0.003, -0.055), + modelPivotRotation: new THREE.Euler(0, 0, 0) + } +}; +var OCULUS_TOUCH_WEBXR = { + left: { + modelUrl: TOUCH_CONTROLLER_MODEL_BASE_URL + 'left.gltf', + rayOrigin: { + origin: { + x: 0.002, + y: -0.005, + z: -0.03 + }, + direction: { + x: 0, + y: -0.8, + z: -1 + } + }, + modelPivotOffset: new THREE.Vector3(-0.005, 0.036, -0.037), + modelPivotRotation: new THREE.Euler(Math.PI / 4.5, 0, 0) + }, + right: { + modelUrl: TOUCH_CONTROLLER_MODEL_BASE_URL + 'right.gltf', + rayOrigin: { + origin: { + x: -0.002, + y: -0.005, + z: -0.03 + }, + direction: { + x: 0, + y: -0.8, + z: -1 + } + }, + modelPivotOffset: new THREE.Vector3(0.005, 0.036, -0.037), + modelPivotRotation: new THREE.Euler(Math.PI / 4.5, 0, 0) + } +}; +var OCULUS_TOUCH_CONFIG = isWebXRAvailable ? OCULUS_TOUCH_WEBXR : OCULUS_TOUCH_WEBVR; +var CONTROLLER_DEFAULT = 'oculus-touch'; +var CONTROLLER_PROPERTIES = { + 'oculus-touch': OCULUS_TOUCH_CONFIG, + 'oculus-touch-v2': { + left: { + modelUrl: TOUCH_CONTROLLER_MODEL_BASE_URL + 'gen2-left.gltf', + rayOrigin: { + origin: { + x: -0.006, + y: -0.03, + z: -0.04 + }, + direction: { + x: 0, + y: -0.9, + z: -1 + } + }, + modelPivotOffset: new THREE.Vector3(0, -0.007, -0.021), + modelPivotRotation: new THREE.Euler(-Math.PI / 4, 0, 0) + }, + right: { + modelUrl: TOUCH_CONTROLLER_MODEL_BASE_URL + 'gen2-right.gltf', + rayOrigin: { + origin: { + x: 0.006, + y: -0.03, + z: -0.04 + }, + direction: { + x: 0, + y: -0.9, + z: -1 + } + }, + modelPivotOffset: new THREE.Vector3(0, -0.007, -0.021), + modelPivotRotation: new THREE.Euler(-Math.PI / 4, 0, 0) + } + }, + 'oculus-touch-v3': { + left: { + modelUrl: TOUCH_CONTROLLER_MODEL_BASE_URL + 'v3-left.glb', + rayOrigin: { + origin: { + x: 0.0065, + y: -0.0186, + z: -0.05 + }, + direction: { + x: 0.12394785839500175, + y: -0.5944043672340157, + z: -0.7945567170519814 + } + }, + modelPivotOffset: new THREE.Vector3(0, 0, 0), + modelPivotRotation: new THREE.Euler(0, 0, 0) + }, + right: { + modelUrl: TOUCH_CONTROLLER_MODEL_BASE_URL + 'v3-right.glb', + rayOrigin: { + origin: { + x: -0.0065, + y: -0.0186, + z: -0.05 + }, + direction: { + x: -0.12394785839500175, + y: -0.5944043672340157, + z: -0.7945567170519814 + } + }, + modelPivotOffset: new THREE.Vector3(0, 0, 0), + modelPivotRotation: new THREE.Euler(0, 0, 0) + } + }, + 'meta-quest-touch-pro': { + left: { + modelUrl: META_CONTROLLER_MODEL_BASE_URL + 'quest-touch-pro-left.glb', + rayOrigin: { + origin: { + x: 0.0065, + y: -0.0186, + z: -0.05 + }, + direction: { + x: 0.12394785839500175, + y: -0.5944043672340157, + z: -0.7945567170519814 + } + }, + modelPivotOffset: new THREE.Vector3(0, 0, 0), + modelPivotRotation: new THREE.Euler(0, 0, 0) + }, + right: { + modelUrl: META_CONTROLLER_MODEL_BASE_URL + 'quest-touch-pro-right.glb', + rayOrigin: { + origin: { + x: -0.0065, + y: -0.0186, + z: -0.05 + }, + direction: { + x: -0.12394785839500175, + y: -0.5944043672340157, + z: -0.7945567170519814 + } + }, + modelPivotOffset: new THREE.Vector3(0, 0, 0), + modelPivotRotation: new THREE.Euler(0, 0, 0) + } + }, + 'meta-quest-touch-plus': { + left: { + modelUrl: META_CONTROLLER_MODEL_BASE_URL + 'quest-touch-plus-left.glb', + rayOrigin: { + origin: { + x: 0.0065, + y: -0.0186, + z: -0.05 + }, + direction: { + x: 0.12394785839500175, + y: -0.5944043672340157, + z: -0.7945567170519814 + } + }, + modelPivotOffset: new THREE.Vector3(0, 0, 0), + modelPivotRotation: new THREE.Euler(0, 0, 0) + }, + right: { + modelUrl: META_CONTROLLER_MODEL_BASE_URL + 'quest-touch-plus-right.glb', + rayOrigin: { + origin: { + x: -0.0065, + y: -0.0186, + z: -0.05 + }, + direction: { + x: -0.12394785839500175, + y: -0.5944043672340157, + z: -0.7945567170519814 + } + }, + modelPivotOffset: new THREE.Vector3(0, 0, 0), + modelPivotRotation: new THREE.Euler(0, 0, 0) + } + } +}; + +/** + * Button indices: + * 0 - thumbstick (which has separate axismove / thumbstickmoved events) + * 1 - trigger (with analog value, which goes up to 1) + * 2 - grip (with analog value, which goes up to 1) + * 3 - X (left) or A (right) + * 4 - Y (left) or B (right) + * 5 - surface (touch only) + */ +var INPUT_MAPPING_WEBVR = { + left: { + axes: { + thumbstick: [0, 1] + }, + buttons: ['thumbstick', 'trigger', 'grip', 'xbutton', 'ybutton', 'surface'] + }, + right: { + axes: { + thumbstick: [0, 1] + }, + buttons: ['thumbstick', 'trigger', 'grip', 'abutton', 'bbutton', 'surface'] + } +}; + +/** + * Button indices: + * 0 - trigger + * 1 - grip + * 2 - none + * 3 - thumbstick + * 4 - X or A button + * 5 - Y or B button + * 6 - surface + * + * Axis: + * 0 - none + * 1 - none + * 2 - thumbstick + * 3 - thumbstick + * Reference: https://github.com/immersive-web/webxr-input-profiles/blob/master/packages/registry/profiles/oculus/oculus-touch.json + */ +var INPUT_MAPPING_WEBXR = { + left: { + axes: { + thumbstick: [2, 3] + }, + buttons: ['trigger', 'grip', 'none', 'thumbstick', 'xbutton', 'ybutton', 'surface'] + }, + right: { + axes: { + thumbstick: [2, 3] + }, + buttons: ['trigger', 'grip', 'none', 'thumbstick', 'abutton', 'bbutton', 'surface'] + } +}; +var INPUT_MAPPING = isWebXRAvailable ? INPUT_MAPPING_WEBXR : INPUT_MAPPING_WEBVR; + +/** + * Oculus Touch controls. + * Interface with Oculus Touch controllers and map Gamepad events to + * controller buttons: thumbstick, trigger, grip, xbutton, ybutton, surface + * Load a controller model and highlight the pressed buttons. + */ +module.exports.Component = registerComponent('oculus-touch-controls', { + schema: { + hand: { + default: 'left' + }, + buttonColor: { + type: 'color', + default: '#999' + }, + // Off-white. + buttonTouchColor: { + type: 'color', + default: '#8AB' + }, + buttonHighlightColor: { + type: 'color', + default: '#2DF' + }, + // Light blue. + model: { + default: true + }, + controllerType: { + default: 'auto', + oneOf: ['auto', 'oculus-touch', 'oculus-touch-v2', 'oculus-touch-v3'] + }, + orientationOffset: { + type: 'vec3', + default: { + x: 43, + y: 0, + z: 0 + } + } + }, + mapping: INPUT_MAPPING, + bindMethods: function () { + this.onButtonChanged = bind(this.onButtonChanged, this); + this.onThumbstickMoved = bind(this.onThumbstickMoved, this); + this.onModelLoaded = bind(this.onModelLoaded, this); + this.onControllersUpdate = bind(this.onControllersUpdate, this); + this.checkIfControllerPresent = bind(this.checkIfControllerPresent, this); + this.onAxisMoved = bind(this.onAxisMoved, this); + }, + init: function () { + var self = this; + this.onButtonDown = function (evt) { + onButtonEvent(evt.detail.id, 'down', self, self.data.hand); + }; + this.onButtonUp = function (evt) { + onButtonEvent(evt.detail.id, 'up', self, self.data.hand); + }; + this.onButtonTouchStart = function (evt) { + onButtonEvent(evt.detail.id, 'touchstart', self, self.data.hand); + }; + this.onButtonTouchEnd = function (evt) { + onButtonEvent(evt.detail.id, 'touchend', self, self.data.hand); + }; + this.controllerPresent = false; + this.lastControllerCheck = 0; + this.previousButtonValues = {}; + this.bindMethods(); + this.triggerEuler = new THREE.Euler(); + }, + addEventListeners: function () { + var el = this.el; + el.addEventListener('buttonchanged', this.onButtonChanged); + el.addEventListener('buttondown', this.onButtonDown); + el.addEventListener('buttonup', this.onButtonUp); + el.addEventListener('touchstart', this.onButtonTouchStart); + el.addEventListener('touchend', this.onButtonTouchEnd); + el.addEventListener('axismove', this.onAxisMoved); + el.addEventListener('model-loaded', this.onModelLoaded); + el.addEventListener('thumbstickmoved', this.onThumbstickMoved); + this.controllerEventsActive = true; + }, + removeEventListeners: function () { + var el = this.el; + el.removeEventListener('buttonchanged', this.onButtonChanged); + el.removeEventListener('buttondown', this.onButtonDown); + el.removeEventListener('buttonup', this.onButtonUp); + el.removeEventListener('touchstart', this.onButtonTouchStart); + el.removeEventListener('touchend', this.onButtonTouchEnd); + el.removeEventListener('axismove', this.onAxisMoved); + el.removeEventListener('model-loaded', this.onModelLoaded); + el.removeEventListener('thumbstickmoved', this.onThumbstickMoved); + this.controllerEventsActive = false; + }, + checkIfControllerPresent: function () { + checkControllerPresentAndSetup(this, GAMEPAD_ID_PREFIX, { + hand: this.data.hand, + iterateControllerProfiles: true + }); + }, + play: function () { + this.checkIfControllerPresent(); + this.addControllersUpdateListener(); + }, + pause: function () { + this.removeEventListeners(); + this.removeControllersUpdateListener(); + }, + loadModel: function (controller) { + var data = this.data; + var controllerId; + if (!data.model) { + return; + } + // If model has been already loaded + if (this.controllerObject3D) { + this.el.setObject3D('mesh', this.controllerObject3D); + return; + } + + // Set the controller display model based on the data passed in. + this.displayModel = CONTROLLER_PROPERTIES[data.controllerType] || CONTROLLER_PROPERTIES[CONTROLLER_DEFAULT]; + // If the developer is asking for auto-detection, use the retrieved displayName to identify the specific unit. + // This only works for WebVR currently. + if (data.controllerType === 'auto') { + var trackedControlsSystem = this.el.sceneEl.systems['tracked-controls-webvr']; + // WebVR + if (trackedControlsSystem && trackedControlsSystem.vrDisplay) { + var displayName = trackedControlsSystem.vrDisplay.displayName; + if (/^Oculus Quest$/.test(displayName)) { + this.displayModel = CONTROLLER_PROPERTIES['oculus-touch-v2']; + } + } else { + // WebXR + controllerId = CONTROLLER_DEFAULT; + var controllersPropertiesIds = Object.keys(CONTROLLER_PROPERTIES); + for (var i = 0; i < controller.profiles.length; i++) { + if (controllersPropertiesIds.indexOf(controller.profiles[i]) !== -1) { + controllerId = controller.profiles[i]; + break; + } + } + this.displayModel = CONTROLLER_PROPERTIES[controllerId]; + } + } + var modelUrl = this.displayModel[data.hand].modelUrl; + this.isTouchV3orPROorPlus = this.displayModel === CONTROLLER_PROPERTIES['oculus-touch-v3'] || this.displayModel === CONTROLLER_PROPERTIES['meta-quest-touch-pro'] || this.displayModel === CONTROLLER_PROPERTIES['meta-quest-touch-plus']; + this.el.setAttribute('gltf-model', modelUrl); + }, + injectTrackedControls: function (controller) { + var data = this.data; + var webXRId = GAMEPAD_ID_WEBXR; + var webVRId = data.hand === 'right' ? 'Oculus Touch (Right)' : 'Oculus Touch (Left)'; + var id = isWebXRAvailable ? webXRId : webVRId; + this.el.setAttribute('tracked-controls', { + id: id, + hand: data.hand, + orientationOffset: data.orientationOffset, + handTrackingEnabled: false, + iterateControllerProfiles: true, + space: 'gripSpace' + }); + this.loadModel(controller); + }, + addControllersUpdateListener: function () { + this.el.sceneEl.addEventListener('controllersupdated', this.onControllersUpdate, false); + }, + removeControllersUpdateListener: function () { + this.el.sceneEl.removeEventListener('controllersupdated', this.onControllersUpdate, false); + }, + onControllersUpdate: function () { + // Note that due to gamepadconnected event propagation issues, we don't rely on events. + this.checkIfControllerPresent(); + }, + onButtonChanged: function (evt) { + var button = this.mapping[this.data.hand].buttons[evt.detail.id]; + if (!button) { + return; + } + // move the button meshes + if (this.isTouchV3orPROorPlus) { + this.onButtonChangedV3orPROorPlus(evt); + } else { + var buttonMeshes = this.buttonMeshes; + var analogValue; + if (button === 'trigger' || button === 'grip') { + analogValue = evt.detail.state.value; + } + if (buttonMeshes) { + if (button === 'trigger' && buttonMeshes.trigger) { + buttonMeshes.trigger.rotation.x = this.originalXRotationTrigger - analogValue * (Math.PI / 26); + } + if (button === 'grip' && buttonMeshes.grip) { + analogValue *= this.data.hand === 'left' ? -1 : 1; + buttonMeshes.grip.position.x = this.originalXPositionGrip + analogValue * 0.004; + } + } + } + // Pass along changed event with button state, using the buttom mapping for convenience. + this.el.emit(button + 'changed', evt.detail.state); + }, + onButtonChangedV3orPROorPlus: function (evt) { + var button = this.mapping[this.data.hand].buttons[evt.detail.id]; + var buttonObjects = this.buttonObjects; + var analogValue; + if (!buttonObjects || !buttonObjects[button]) { + return; + } + analogValue = evt.detail.state.value; + buttonObjects[button].quaternion.slerpQuaternions(this.buttonRanges[button].min.quaternion, this.buttonRanges[button].max.quaternion, analogValue); + buttonObjects[button].position.lerpVectors(this.buttonRanges[button].min.position, this.buttonRanges[button].max.position, analogValue); + }, + onModelLoaded: function (evt) { + if (evt.target !== this.el || !this.data.model) { + return; + } + if (this.isTouchV3orPROorPlus) { + this.onTouchV3orPROorPlusModelLoaded(evt); + } else { + // All oculus headset controller models prior to the Quest 2 (i.e., Oculus Touch V3) + // used a consistent format that is handled here + var controllerObject3D = this.controllerObject3D = evt.detail.model; + var buttonMeshes; + buttonMeshes = this.buttonMeshes = {}; + buttonMeshes.grip = controllerObject3D.getObjectByName('buttonHand'); + this.originalXPositionGrip = buttonMeshes.grip && buttonMeshes.grip.position.x; + buttonMeshes.trigger = controllerObject3D.getObjectByName('buttonTrigger'); + this.originalXRotationTrigger = buttonMeshes.trigger && buttonMeshes.trigger.rotation.x; + buttonMeshes.thumbstick = controllerObject3D.getObjectByName('stick'); + buttonMeshes.xbutton = controllerObject3D.getObjectByName('buttonX'); + buttonMeshes.abutton = controllerObject3D.getObjectByName('buttonA'); + buttonMeshes.ybutton = controllerObject3D.getObjectByName('buttonY'); + buttonMeshes.bbutton = controllerObject3D.getObjectByName('buttonB'); + } + for (var button in this.buttonMeshes) { + if (this.buttonMeshes[button]) { + cloneMeshMaterial(this.buttonMeshes[button]); + } + } + this.applyOffset(evt.detail.model); + this.el.emit('controllermodelready', { + name: 'oculus-touch-controls', + model: this.data.model, + rayOrigin: this.displayModel[this.data.hand].rayOrigin + }); + }, + applyOffset: function (model) { + model.position.copy(this.displayModel[this.data.hand].modelPivotOffset); + model.rotation.copy(this.displayModel[this.data.hand].modelPivotRotation); + }, + onTouchV3orPROorPlusModelLoaded: function (evt) { + var controllerObject3D = this.controllerObject3D = evt.detail.model; + var buttonObjects = this.buttonObjects = {}; + var buttonMeshes = this.buttonMeshes = {}; + var buttonRanges = this.buttonRanges = {}; + buttonMeshes.grip = controllerObject3D.getObjectByName('squeeze'); + buttonObjects.grip = controllerObject3D.getObjectByName('xr_standard_squeeze_pressed_value'); + buttonRanges.grip = { + min: controllerObject3D.getObjectByName('xr_standard_squeeze_pressed_min'), + max: controllerObject3D.getObjectByName('xr_standard_squeeze_pressed_max') + }; + buttonObjects.grip.minX = buttonObjects.grip.position.x; + buttonMeshes.thumbstick = controllerObject3D.getObjectByName('thumbstick'); + buttonObjects.thumbstick = controllerObject3D.getObjectByName('xr_standard_thumbstick_pressed_value'); + buttonRanges.thumbstick = { + min: controllerObject3D.getObjectByName('xr_standard_thumbstick_pressed_min'), + max: controllerObject3D.getObjectByName('xr_standard_thumbstick_pressed_max') + }; + buttonObjects.thumbstickXAxis = controllerObject3D.getObjectByName('xr_standard_thumbstick_xaxis_pressed_value'); + buttonRanges.thumbstickXAxis = { + min: controllerObject3D.getObjectByName('xr_standard_thumbstick_xaxis_pressed_min'), + max: controllerObject3D.getObjectByName('xr_standard_thumbstick_xaxis_pressed_max') + }; + buttonObjects.thumbstickYAxis = controllerObject3D.getObjectByName('xr_standard_thumbstick_yaxis_pressed_value'); + buttonRanges.thumbstickYAxis = { + min: controllerObject3D.getObjectByName('xr_standard_thumbstick_yaxis_pressed_min'), + max: controllerObject3D.getObjectByName('xr_standard_thumbstick_yaxis_pressed_max') + }; + buttonMeshes.trigger = controllerObject3D.getObjectByName('trigger'); + buttonObjects.trigger = controllerObject3D.getObjectByName('xr_standard_trigger_pressed_value'); + buttonRanges.trigger = { + min: controllerObject3D.getObjectByName('xr_standard_trigger_pressed_min'), + max: controllerObject3D.getObjectByName('xr_standard_trigger_pressed_max') + }; + buttonRanges.trigger.diff = { + x: Math.abs(buttonRanges.trigger.max.rotation.x) - Math.abs(buttonRanges.trigger.min.rotation.x), + y: Math.abs(buttonRanges.trigger.max.rotation.y) - Math.abs(buttonRanges.trigger.min.rotation.y), + z: Math.abs(buttonRanges.trigger.max.rotation.z) - Math.abs(buttonRanges.trigger.min.rotation.z) + }; + var button1 = this.data.hand === 'left' ? 'x' : 'a'; + var button2 = this.data.hand === 'left' ? 'y' : 'b'; + var button1id = button1 + 'button'; + var button2id = button2 + 'button'; + buttonMeshes[button1id] = controllerObject3D.getObjectByName(button1 + '_button'); + buttonObjects[button1id] = controllerObject3D.getObjectByName(button1 + '_button_pressed_value'); + buttonRanges[button1id] = { + min: controllerObject3D.getObjectByName(button1 + '_button_pressed_min'), + max: controllerObject3D.getObjectByName(button1 + '_button_pressed_max') + }; + buttonMeshes[button2id] = controllerObject3D.getObjectByName(button2 + '_button'); + buttonObjects[button2id] = controllerObject3D.getObjectByName(button2 + '_button_pressed_value'); + buttonRanges[button2id] = { + min: controllerObject3D.getObjectByName(button2 + '_button_pressed_min'), + max: controllerObject3D.getObjectByName(button2 + '_button_pressed_max') + }; + }, + onAxisMoved: function (evt) { + emitIfAxesChanged(this, this.mapping[this.data.hand].axes, evt); + }, + onThumbstickMoved: function (evt) { + if (!this.buttonMeshes || !this.buttonMeshes.thumbstick) { + return; + } + if (this.isTouchV3orPROorPlus) { + this.updateThumbstickTouchV3orPROorPlus(evt); + return; + } + for (var axis in evt.detail) { + this.buttonObjects.thumbstick.rotation[this.axisMap[axis]] = this.buttonRanges.thumbstick.originalRotation[this.axisMap[axis]] - Math.PI / 8 * evt.detail[axis] * (axis === 'y' || this.data.hand === 'right' ? -1 : 1); + } + }, + axisMap: { + y: 'x', + x: 'z' + }, + updateThumbstickTouchV3orPROorPlus: function (evt) { + var normalizedXAxis = (evt.detail.x + 1.0) / 2.0; + this.buttonObjects.thumbstickXAxis.quaternion.slerpQuaternions(this.buttonRanges.thumbstickXAxis.min.quaternion, this.buttonRanges.thumbstickXAxis.max.quaternion, normalizedXAxis); + var normalizedYAxis = (evt.detail.y + 1.0) / 2.0; + this.buttonObjects.thumbstickYAxis.quaternion.slerpQuaternions(this.buttonRanges.thumbstickYAxis.min.quaternion, this.buttonRanges.thumbstickYAxis.max.quaternion, normalizedYAxis); + }, + updateModel: function (buttonName, evtName) { + if (!this.data.model) { + return; + } + this.updateButtonModel(buttonName, evtName); + }, + updateButtonModel: function (buttonName, state) { + // update the button mesh colors + var buttonMeshes = this.buttonMeshes; + var button; + var color; + if (!buttonMeshes) { + return; + } + if (buttonMeshes[buttonName]) { + color = state === 'up' || state === 'touchend' ? buttonMeshes[buttonName].originalColor || this.data.buttonColor : state === 'touchstart' ? this.data.buttonTouchColor : this.data.buttonHighlightColor; + button = buttonMeshes[buttonName]; + button.material.color.set(color); + } + } +}); + +/** + * Some of the controller models share the same material for different parts (buttons, triggers...). + * In order to change their color independently we have to create separate materials. + */ +function cloneMeshMaterial(object3d) { + object3d.traverse(function (node) { + var newMaterial; + if (node.type !== 'Mesh') return; + newMaterial = node.material.clone(); + object3d.originalColor = node.material.color; + node.material.dispose(); + node.material = newMaterial; + }); +} + +/***/ }), + +/***/ "./src/components/pico-controls.js": +/*!*****************************************!*\ + !*** ./src/components/pico-controls.js ***! + \*****************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var bind = __webpack_require__(/*! ../utils/bind */ "./src/utils/bind.js"); +var registerComponent = (__webpack_require__(/*! ../core/component */ "./src/core/component.js").registerComponent); +var THREE = __webpack_require__(/*! ../lib/three */ "./src/lib/three.js"); +var trackedControlsUtils = __webpack_require__(/*! ../utils/tracked-controls */ "./src/utils/tracked-controls.js"); +var checkControllerPresentAndSetup = trackedControlsUtils.checkControllerPresentAndSetup; +var emitIfAxesChanged = trackedControlsUtils.emitIfAxesChanged; +var onButtonEvent = trackedControlsUtils.onButtonEvent; + +// See Profiles Registry: +// https://github.com/immersive-web/webxr-input-profiles/tree/master/packages/registry +// TODO: Add a more robust system for deriving gamepad name. +var GAMEPAD_ID = 'pico-4'; +var AFRAME_CDN_ROOT = (__webpack_require__(/*! ../constants */ "./src/constants/index.js").AFRAME_CDN_ROOT); +var PICO_MODEL_GLB_BASE_URL = AFRAME_CDN_ROOT + 'controllers/pico/pico4/'; + +/** + * Button IDs: + * 0 - trigger + * 1 - grip + * 3 - X / A + * 4 - Y / B + * + * Axis: + * 2 - joystick x axis + * 3 - joystick y axis + */ +var INPUT_MAPPING_WEBXR = { + left: { + axes: { + touchpad: [2, 3] + }, + buttons: ['trigger', 'squeeze', 'none', 'thumbstick', 'xbutton', 'ybutton'] + }, + right: { + axes: { + touchpad: [2, 3] + }, + buttons: ['trigger', 'squeeze', 'none', 'thumbstick', 'abutton', 'bbutton'] + } +}; + +/** + * Pico Controls + */ +module.exports.Component = registerComponent('pico-controls', { + schema: { + hand: { + default: 'none' + }, + model: { + default: true + }, + orientationOffset: { + type: 'vec3' + } + }, + mapping: INPUT_MAPPING_WEBXR, + init: function () { + var self = this; + this.onButtonChanged = bind(this.onButtonChanged, this); + this.onButtonDown = function (evt) { + onButtonEvent(evt.detail.id, 'down', self, self.data.hand); + }; + this.onButtonUp = function (evt) { + onButtonEvent(evt.detail.id, 'up', self, self.data.hand); + }; + this.onButtonTouchEnd = function (evt) { + onButtonEvent(evt.detail.id, 'touchend', self, self.data.hand); + }; + this.onButtonTouchStart = function (evt) { + onButtonEvent(evt.detail.id, 'touchstart', self, self.data.hand); + }; + this.bindMethods(); + }, + update: function () { + var data = this.data; + this.controllerIndex = data.hand === 'right' ? 0 : data.hand === 'left' ? 1 : 2; + }, + play: function () { + this.checkIfControllerPresent(); + this.addControllersUpdateListener(); + }, + pause: function () { + this.removeEventListeners(); + this.removeControllersUpdateListener(); + }, + bindMethods: function () { + this.onModelLoaded = bind(this.onModelLoaded, this); + this.onControllersUpdate = bind(this.onControllersUpdate, this); + this.checkIfControllerPresent = bind(this.checkIfControllerPresent, this); + this.removeControllersUpdateListener = bind(this.removeControllersUpdateListener, this); + this.onAxisMoved = bind(this.onAxisMoved, this); + }, + addEventListeners: function () { + var el = this.el; + el.addEventListener('buttonchanged', this.onButtonChanged); + el.addEventListener('buttondown', this.onButtonDown); + el.addEventListener('buttonup', this.onButtonUp); + el.addEventListener('touchstart', this.onButtonTouchStart); + el.addEventListener('touchend', this.onButtonTouchEnd); + el.addEventListener('axismove', this.onAxisMoved); + el.addEventListener('model-loaded', this.onModelLoaded); + this.controllerEventsActive = true; + }, + removeEventListeners: function () { + var el = this.el; + el.removeEventListener('buttonchanged', this.onButtonChanged); + el.removeEventListener('buttondown', this.onButtonDown); + el.removeEventListener('buttonup', this.onButtonUp); + el.removeEventListener('touchstart', this.onButtonTouchStart); + el.removeEventListener('touchend', this.onButtonTouchEnd); + el.removeEventListener('axismove', this.onAxisMoved); + el.removeEventListener('model-loaded', this.onModelLoaded); + this.controllerEventsActive = false; + }, + checkIfControllerPresent: function () { + var data = this.data; + checkControllerPresentAndSetup(this, GAMEPAD_ID, { + index: this.controllerIndex, + hand: data.hand + }); + }, + injectTrackedControls: function () { + var el = this.el; + var data = this.data; + el.setAttribute('tracked-controls', { + // TODO: verify expected behavior between reserved prefixes. + idPrefix: GAMEPAD_ID, + hand: data.hand, + controller: this.controllerIndex, + orientationOffset: data.orientationOffset + }); + // Load model. + if (!this.data.model) { + return; + } + this.el.setAttribute('gltf-model', PICO_MODEL_GLB_BASE_URL + this.data.hand + '.glb'); + }, + addControllersUpdateListener: function () { + this.el.sceneEl.addEventListener('controllersupdated', this.onControllersUpdate, false); + }, + removeControllersUpdateListener: function () { + this.el.sceneEl.removeEventListener('controllersupdated', this.onControllersUpdate, false); + }, + onControllersUpdate: function () { + // Note that due to gamepadconnected event propagation issues, we don't rely on events. + this.checkIfControllerPresent(); + }, + onButtonChanged: function (evt) { + var button = this.mapping[this.data.hand].buttons[evt.detail.id]; + var analogValue; + if (!button) { + return; + } + if (button === 'trigger') { + analogValue = evt.detail.state.value; + console.log('analog value of trigger press: ' + analogValue); + } + + // Pass along changed event with button state, using button mapping for convenience. + this.el.emit(button + 'changed', evt.detail.state); + }, + onModelLoaded: function (evt) { + if (evt.target !== this.el || !this.data.model) { + return; + } + this.el.emit('controllermodelready', { + name: 'pico-controls', + model: this.data.model, + rayOrigin: new THREE.Vector3(0, 0, 0) + }); + }, + onAxisMoved: function (evt) { + emitIfAxesChanged(this, this.mapping.axes, evt); + } +}); + +/***/ }), + +/***/ "./src/components/position.js": +/*!************************************!*\ + !*** ./src/components/position.js ***! + \************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var registerComponent = (__webpack_require__(/*! ../core/component */ "./src/core/component.js").registerComponent); +module.exports.Component = registerComponent('position', { + schema: { + type: 'vec3' + }, + update: function () { + var object3D = this.el.object3D; + var data = this.data; + object3D.position.set(data.x, data.y, data.z); + }, + remove: function () { + // Pretty much for mixins. + this.el.object3D.position.set(0, 0, 0); + } +}); + +/***/ }), + +/***/ "./src/components/raycaster.js": +/*!*************************************!*\ + !*** ./src/components/raycaster.js ***! + \*************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +/* global MutationObserver */ + +var registerComponent = (__webpack_require__(/*! ../core/component */ "./src/core/component.js").registerComponent); +var THREE = __webpack_require__(/*! ../lib/three */ "./src/lib/three.js"); +var utils = __webpack_require__(/*! ../utils/ */ "./src/utils/index.js"); +var warn = utils.debug('components:raycaster:warn'); + +// Defines selectors that should be 'safe' for the MutationObserver used to +// refresh the whitelist. Matches classnames, IDs, and presence of attributes. +// Selectors for the value of an attribute, like [position=0 2 0], cannot be +// reliably detected and are therefore disallowed. +var OBSERVER_SELECTOR_RE = /^[\w\s-.,[\]#]*$/; + +// Configuration for the MutationObserver used to refresh the whitelist. +// Listens for addition/removal of elements and attributes within the scene. +var OBSERVER_CONFIG = { + childList: true, + attributes: true, + subtree: true +}; +var EVENTS = { + INTERSECT: 'raycaster-intersected', + INTERSECTION: 'raycaster-intersection', + INTERSECT_CLEAR: 'raycaster-intersected-cleared', + INTERSECTION_CLEAR: 'raycaster-intersection-cleared', + INTERSECTION_CLOSEST_ENTITY_CHANGED: 'raycaster-closest-entity-changed' +}; + +/** + * Raycaster component. + * + * Pass options to three.js Raycaster including which objects to test. + * Poll for intersections. + * Emit event on origin entity and on target entity on intersect. + * + * @member {array} intersectedEls - List of currently intersected entities. + * @member {array} objects - Cached list of meshes to intersect. + * @member {number} prevCheckTime - Previous time intersection was checked. To help interval. + * @member {object} raycaster - three.js Raycaster. + */ +module.exports.Component = registerComponent('raycaster', { + schema: { + autoRefresh: { + default: true + }, + direction: { + type: 'vec3', + default: { + x: 0, + y: 0, + z: -1 + } + }, + enabled: { + default: true + }, + far: { + default: 1000 + }, + interval: { + default: 0 + }, + near: { + default: 0 + }, + objects: { + default: '' + }, + origin: { + type: 'vec3' + }, + showLine: { + default: false + }, + lineColor: { + default: 'white' + }, + lineOpacity: { + default: 1 + }, + useWorldCoordinates: { + default: false + } + }, + multiple: true, + init: function () { + this.clearedIntersectedEls = []; + this.unitLineEndVec3 = new THREE.Vector3(); + this.intersectedEls = []; + this.intersections = []; + this.newIntersectedEls = []; + this.newIntersections = []; + this.objects = []; + this.prevCheckTime = undefined; + this.prevIntersectedEls = []; + this.rawIntersections = []; + this.raycaster = new THREE.Raycaster(); + this.updateOriginDirection(); + this.setDirty = this.setDirty.bind(this); + this.updateLine = this.updateLine.bind(this); + this.observer = new MutationObserver(this.setDirty); + this.dirty = true; + this.lineEndVec3 = new THREE.Vector3(); + this.otherLineEndVec3 = new THREE.Vector3(); + this.lineData = { + end: this.lineEndVec3 + }; + this.getIntersection = this.getIntersection.bind(this); + this.intersectedDetail = { + el: this.el, + getIntersection: this.getIntersection + }; + this.intersectedClearedDetail = { + el: this.el + }; + this.intersectionClearedDetail = { + clearedEls: this.clearedIntersectedEls + }; + this.intersectionDetail = {}; + }, + /** + * Create or update raycaster object. + */ + update: function (oldData) { + var data = this.data; + var el = this.el; + var raycaster = this.raycaster; + + // Set raycaster properties. + raycaster.far = data.far; + raycaster.near = data.near; + + // Draw line. + if (data.showLine && (data.far !== oldData.far || data.origin !== oldData.origin || data.direction !== oldData.direction || !oldData.showLine)) { + // Calculate unit vector for line direction. Can be multiplied via scalar and added + // to orign to adjust line length. + this.unitLineEndVec3.copy(data.direction).normalize(); + this.drawLine(); + } + if (!data.showLine && oldData.showLine) { + el.removeAttribute('line'); + } + if (data.objects !== oldData.objects && !OBSERVER_SELECTOR_RE.test(data.objects)) { + warn('[raycaster] Selector "' + data.objects + '" may not update automatically with DOM changes.'); + } + if (!data.objects) { + warn('[raycaster] For performance, please define raycaster.objects when using ' + 'raycaster or cursor components to whitelist which entities to intersect with. ' + 'e.g., raycaster="objects: [data-raycastable]".'); + } + if (data.autoRefresh !== oldData.autoRefresh && el.isPlaying) { + data.autoRefresh ? this.addEventListeners() : this.removeEventListeners(); + } + if (oldData.enabled && !data.enabled) { + this.clearAllIntersections(); + } + this.setDirty(); + }, + play: function () { + this.addEventListeners(); + }, + pause: function () { + this.removeEventListeners(); + }, + remove: function () { + if (this.data.showLine) { + this.el.removeAttribute('line'); + } + this.clearAllIntersections(); + }, + addEventListeners: function () { + if (!this.data.autoRefresh) { + return; + } + this.observer.observe(this.el.sceneEl, OBSERVER_CONFIG); + this.el.sceneEl.addEventListener('object3dset', this.setDirty); + this.el.sceneEl.addEventListener('object3dremove', this.setDirty); + }, + removeEventListeners: function () { + this.observer.disconnect(); + this.el.sceneEl.removeEventListener('object3dset', this.setDirty); + this.el.sceneEl.removeEventListener('object3dremove', this.setDirty); + }, + /** + * Mark the object list as dirty, to be refreshed before next raycast. + */ + setDirty: function () { + this.dirty = true; + }, + /** + * Update list of objects to test for intersection. + */ + refreshObjects: function () { + var data = this.data; + var els; + + // If objects not defined, intersect with everything. + els = data.objects ? this.el.sceneEl.querySelectorAll(data.objects) : this.el.sceneEl.querySelectorAll('*'); + this.objects = this.flattenObject3DMaps(els); + this.dirty = false; + }, + /** + * Check for intersections and cleared intersections on an interval. + */ + tock: function (time) { + var data = this.data; + var prevCheckTime = this.prevCheckTime; + if (!data.enabled) { + return; + } + + // Only check for intersection if interval time has passed. + if (prevCheckTime && time - prevCheckTime < data.interval) { + return; + } + + // Update check time. + this.prevCheckTime = time; + this.checkIntersections(); + }, + /** + * Raycast for intersections and emit events for current and cleared intersections. + */ + checkIntersections: function () { + var clearedIntersectedEls = this.clearedIntersectedEls; + var el = this.el; + var data = this.data; + var i; + var intersectedEls = this.intersectedEls; + var intersection; + var intersections = this.intersections; + var newIntersectedEls = this.newIntersectedEls; + var newIntersections = this.newIntersections; + var prevIntersectedEls = this.prevIntersectedEls; + var rawIntersections = this.rawIntersections; + + // Refresh the object whitelist if needed. + if (this.dirty) { + this.refreshObjects(); + } + + // Store old previously intersected entities. + copyArray(this.prevIntersectedEls, this.intersectedEls); + + // Raycast. + this.updateOriginDirection(); + rawIntersections.length = 0; + this.raycaster.intersectObjects(this.objects, true, rawIntersections); + + // Only keep intersections against objects that have a reference to an entity. + intersections.length = 0; + intersectedEls.length = 0; + for (i = 0; i < rawIntersections.length; i++) { + intersection = rawIntersections[i]; + // Don't intersect with own line. + if (data.showLine && intersection.object === el.getObject3D('line')) { + continue; + } + if (intersection.object.el) { + intersections.push(intersection); + intersectedEls.push(intersection.object.el); + } + } + + // Get newly intersected entities. + newIntersections.length = 0; + newIntersectedEls.length = 0; + for (i = 0; i < intersections.length; i++) { + if (prevIntersectedEls.indexOf(intersections[i].object.el) === -1) { + newIntersections.push(intersections[i]); + newIntersectedEls.push(intersections[i].object.el); + } + } + + // Emit intersection cleared on both entities per formerly intersected entity. + clearedIntersectedEls.length = 0; + for (i = 0; i < prevIntersectedEls.length; i++) { + if (intersectedEls.indexOf(prevIntersectedEls[i]) !== -1) { + continue; + } + prevIntersectedEls[i].emit(EVENTS.INTERSECT_CLEAR, this.intersectedClearedDetail); + clearedIntersectedEls.push(prevIntersectedEls[i]); + } + if (clearedIntersectedEls.length) { + el.emit(EVENTS.INTERSECTION_CLEAR, this.intersectionClearedDetail); + } + + // Emit intersected on intersected entity per intersected entity. + for (i = 0; i < newIntersectedEls.length; i++) { + newIntersectedEls[i].emit(EVENTS.INTERSECT, this.intersectedDetail); + } + + // Emit all intersections at once on raycasting entity. + if (newIntersections.length) { + this.intersectionDetail.els = newIntersectedEls; + this.intersectionDetail.intersections = newIntersections; + el.emit(EVENTS.INTERSECTION, this.intersectionDetail); + } + + // Emit event when the closest intersected entity has changed. + if (prevIntersectedEls.length === 0 && intersections.length > 0 || prevIntersectedEls.length > 0 && intersections.length === 0 || prevIntersectedEls.length && intersections.length && prevIntersectedEls[0] !== intersections[0].object.el) { + this.intersectionDetail.els = this.intersectedEls; + this.intersectionDetail.intersections = intersections; + el.emit(EVENTS.INTERSECTION_CLOSEST_ENTITY_CHANGED, this.intersectionDetail); + } + + // Update line length. + if (data.showLine) { + setTimeout(this.updateLine); + } + }, + updateLine: function () { + var el = this.el; + var intersections = this.intersections; + var lineLength; + if (intersections.length) { + if (intersections[0].object.el === el && intersections[1]) { + lineLength = intersections[1].distance; + } else { + lineLength = intersections[0].distance; + } + } + this.drawLine(lineLength); + }, + /** + * Return the most recent intersection details for a given entity, if any. + * @param {AEntity} el + * @return {Object} + */ + getIntersection: function (el) { + var i; + var intersection; + for (i = 0; i < this.intersections.length; i++) { + intersection = this.intersections[i]; + if (intersection.object.el === el) { + return intersection; + } + } + return null; + }, + /** + * Update origin and direction of raycaster using entity transforms and supplied origin or + * direction offsets. + */ + updateOriginDirection: function () { + var direction = new THREE.Vector3(); + var originVec3 = new THREE.Vector3(); + + // Closure to make quaternion/vector3 objects private. + return function updateOriginDirection() { + var el = this.el; + var data = this.data; + if (data.useWorldCoordinates) { + this.raycaster.set(data.origin, data.direction); + return; + } + el.object3D.updateMatrixWorld(); + originVec3.setFromMatrixPosition(el.object3D.matrixWorld); + + // If non-zero origin, translate the origin into world space. + if (data.origin.x !== 0 || data.origin.y !== 0 || data.origin.z !== 0) { + originVec3 = el.object3D.localToWorld(originVec3.copy(data.origin)); + } + + // three.js raycaster direction is relative to 0, 0, 0 NOT the origin / offset we + // provide. Apply the offset to the direction, then rotation from the object, + // and normalize. + direction.copy(data.direction).transformDirection(el.object3D.matrixWorld).normalize(); + + // Apply offset and direction, in world coordinates. + this.raycaster.set(originVec3, direction); + }; + }(), + /** + * Create or update line to give raycaster visual representation. + * Customize the line through through line component. + * We draw the line in the raycaster component to customize the line to the + * raycaster's origin, direction, and far. + * + * Unlike the raycaster, we create the line as a child of the object. The line will + * be affected by the transforms of the objects, so we don't have to calculate transforms + * like we do with the raycaster. + * + * @param {number} length - Length of line. Pass in to shorten the line to the intersection + * point. If not provided, length will default to the max length, `raycaster.far`. + */ + drawLine: function (length) { + var data = this.data; + var el = this.el; + var endVec3; + + // Switch each time vector so line update triggered and to avoid unnecessary vector clone. + endVec3 = this.lineData.end === this.lineEndVec3 ? this.otherLineEndVec3 : this.lineEndVec3; + + // Treat Infinity as 1000m for the line. + if (length === undefined) { + length = data.far === Infinity ? 1000 : data.far; + } + + // Update the length of the line if given. `unitLineEndVec3` is the direction + // given by data.direction, then we apply a scalar to give it a length and the + // origin point to offset it. + this.lineData.start = data.origin; + this.lineData.end = endVec3.copy(this.unitLineEndVec3).multiplyScalar(length).add(data.origin); + this.lineData.color = data.lineColor; + this.lineData.opacity = data.lineOpacity; + el.setAttribute('line', this.lineData); + }, + /** + * Return A-Frame attachments of each element's object3D group (e.g., mesh). + * Children are flattened by one level, removing the THREE.Group wrapper, + * so that non-recursive raycasting remains useful. + * + * Only push children defined as component attachements (e.g., setObject3D), + * NOT actual children in the scene graph hierarchy. + * + * @param {Array} els + * @return {Array} + */ + flattenObject3DMaps: function (els) { + var key; + var i; + var objects = this.objects; + var scene = this.el.sceneEl.object3D; + function isAttachedToScene(object) { + if (object.parent) { + return isAttachedToScene(object.parent); + } else { + return object === scene; + } + } + + // Push meshes and other attachments onto list of objects to intersect. + objects.length = 0; + for (i = 0; i < els.length; i++) { + var el = els[i]; + if (el.isEntity && el.object3D && isAttachedToScene(el.object3D)) { + for (key in el.object3DMap) { + objects.push(el.getObject3D(key)); + } + } + } + return objects; + }, + clearAllIntersections: function () { + var i; + for (i = 0; i < this.intersectedEls.length; i++) { + this.intersectedEls[i].emit(EVENTS.INTERSECT_CLEAR, this.intersectedClearedDetail); + } + copyArray(this.clearedIntersectedEls, this.intersectedEls); + this.intersectedEls.length = 0; + this.intersections.length = 0; + this.el.emit(EVENTS.INTERSECTION_CLEAR, this.intersectionClearedDetail); + } +}); + +/** + * Copy contents of one array to another without allocating new array. + */ +function copyArray(a, b) { + var i; + a.length = b.length; + for (i = 0; i < b.length; i++) { + a[i] = b[i]; + } +} + +/***/ }), + +/***/ "./src/components/rotation.js": +/*!************************************!*\ + !*** ./src/components/rotation.js ***! + \************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var degToRad = (__webpack_require__(/*! ../lib/three */ "./src/lib/three.js").MathUtils.degToRad); +var registerComponent = (__webpack_require__(/*! ../core/component */ "./src/core/component.js").registerComponent); +module.exports.Component = registerComponent('rotation', { + schema: { + type: 'vec3' + }, + /** + * Updates object3D rotation. + */ + update: function () { + var data = this.data; + var object3D = this.el.object3D; + object3D.rotation.set(degToRad(data.x), degToRad(data.y), degToRad(data.z)); + object3D.rotation.order = 'YXZ'; + }, + remove: function () { + // Pretty much for mixins. + this.el.object3D.rotation.set(0, 0, 0); + } +}); + +/***/ }), + +/***/ "./src/components/scale.js": +/*!*********************************!*\ + !*** ./src/components/scale.js ***! + \*********************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var registerComponent = (__webpack_require__(/*! ../core/component */ "./src/core/component.js").registerComponent); +module.exports.Component = registerComponent('scale', { + schema: { + type: 'vec3', + default: { + x: 1, + y: 1, + z: 1 + } + }, + update: function () { + var data = this.data; + var object3D = this.el.object3D; + object3D.scale.set(data.x, data.y, data.z); + }, + remove: function () { + // Pretty much for mixins. + this.el.object3D.scale.set(1, 1, 1); + } +}); + +/***/ }), + +/***/ "./src/components/scene/ar-hit-test.js": +/*!*********************************************!*\ + !*** ./src/components/scene/ar-hit-test.js ***! + \*********************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +/* global ImageData, Map, Set */ +var arrowURL = 'data:image/webp;base64,UklGRkQHAABXRUJQVlA4WAoAAAAQAAAA/wEA/wEAQUxQSL0DAAARDzD/ERGCjrY9sYYFfgo6aa1kJ7K0w9Lo3AadLSVeFxevQwj5kuM8RfR/Atw/C0+ozB/oUBrloFZs6ElSW88j1KA4yExNWQaqRZquIDF0JYmlq0hAuUDTFu66tng3teW7pa3cQf1V1edvur54M/Slm6Wv3Gx9zw0MXlQLntcsBN6wkHjTQuYtC4W3LTw8mGRVG57TbAROtxHfZNhInGkjc5aNwtk2Hg6Mvki14k+NkZzCwQgCxalcAv3kddRTPI1DcUrXId1FLf1uHpzaQz4tquhZVLlKesbVpqKeTj0n0F5PpXDlFN9UqmhalL/ImuZFo6KmToWLoKlddMprqlS8cKovBvHo2kTiFV2LN4msaxKZl3QNiair8xYRdDWivIvXVXmbcMqJ51UebZuFXxZt6xd4laxtciqRtA3Cv0nU1t+kEUFbI8JvCa+tvkm3FDlO/W+OR99+kWEp/YYo+tYfTVnf/K8cE/F///3vv//993eeL+a+uvjawLcX3xjYvJotBFY3kVjTRGFtE+BU2AiMbiQyhpHMWEYeBozAH5qNBYRDB5KBCaTDBKKBAZTDBoKBDjwHAN5ABeCJBsAZcAAC0YHHxAYSMYBiYgGZWEA2MYFCbCCZGAAIANFEB+AnYgMQTDQAYSJ2AN5EBZAm4gDgTDgAeSIu4DGygTIRN1CMLOCZiACykQlg4jsAycgA8AO+BxCNdJyDkcbwRirDGXGnx8w+FDPrkM3MQ9JQZMYhiiwV/RDMtIM3U1/DmXHUo+IR2kSR2ToWkQ1NIn2qf2J8LCqJKiDUiSADHY3whirhdHgZ94HKaR97PhE+twEUJUFoAcgyTct8hfSxSkShASDKdMJ/ritKHwgyQ0sD4D/miCxU5SbhOOUDTnZpccCjYP/i0bZ/8bAgtVGEoGapWIQXyzKVKLwgNJFk2rtMIgoNRJlOZF7SNSSyUEeQmbxBFKEmtYjEe8S8zOZ1AkJVCmS88FJOtF40Ksg4oUaFiygk3C8qlTVNyl8UTevCUdAE2t14PfVqU1FPp57TopKeQZWromddTQp6QOfTOEQt/ZDuipZ11w/wOiqO8dRORcc6BQEkDQMClaHcn5wV9yLbxsNZNgpn2sicYSNxuo34Js1G4FQbnuNsOPa28PCWhcKbFjJvWEi8ZiHwqgXPcxbc5db33Cx95WboSzddX7yp+vyN0+eul7ZyN7Xlu64t3jVt4c5pc4JLV5EYupJE0xUknC4nOjVlmaYpyLit53HCQ0+ScnqceNcS5dzUkd0/CwMAVlA4IGADAAAQXwCdASoAAgACP8ne6Wy/tjCpqJ/IA/A5CWlu4XYBG/Pz8AfwD8APz//f3v8E1fuHZnxKYACtfuHZnxKYACrYTb5mOslhxu843ecbvON3nG7zjd3a0VCn7G1MABVxwH/Xd25gAK1+4dmfEpe2+PHhQaj75++riG6FuYACtfuHZnxKYACRrK3q9xO8Ss3uWKnMhs/rDF1hi6wxdYYusMXWGI5QRcCFDZog5OgqNlse1NDuz/UoFa/cOzPiUwAEsAOK4/nu5eZHK2tlXxJfNYlMABWv3Dsz4bvNJ5YA/LtxJ38SmAArX7h2Z8Sk5vdZUYv7mZPiUwAFa/cOzPh21s5OgZxf1mfEpemRyFr/rM+JS9noA/LtxJ38SmAAlUJIotzAASn6TjdhK+D3Dsz4dyvB7h2Z8O2tnJ0DOL+sz4lL2nKLT4lL/+iSLOocxq639w7M34MNZdm55uJ8v8ra2cpVZnxKTq2F3PN/cNksAfl24k7+JTAASqrD37h2Z7b1W+VtbOUqsz4lJ1bC7nm/uGyWAPy7cSd/EpgAJVVh79w7M9t6rfK2tnKVWZ8Sk6thdzzf3DZLAH5duJO/iUwAEqqw9+4dme29VvlbWzlKrM+JSdWwu55v7hslgD8u3EnfxKYACVVYe/cOzPbeq3ytrZylVme0kYJ8557FLerqFrzIbPrrf3DZLAH5duJO/iUvaVMS9BoaF4p7pSDFTP1XMyfElelrM0DOL+sz4eBJ13nV1OppBGPuKb4YzXQgq9uH19uS/0+JS9t9fr6ZUlQBelDG6GMgq97otb5QMPJwtKyBTbFp8Sl7b6/X0ykkawEOsgdiE6Fi0vb/Eve6xkwsmug0Z4nGNHQO8839bpTsjpz7SWIJxKagvd1QWMa6FYT1KEw3j4XDT6vJ9Xk+nyfT5Pq8n1eEmk5dinMM/9Fcfz4Z3Dsz3KD2dw7LxBRxKrqUUGQPH/7zxr1KIfNpLEJ0MZB2ITM/0Z2EFoh12NlXnEcpYcbvON3nG7zjd5xu84vfcNIAAP7+y8ceyzbVxkakPYY4lcr72fqOnDwipv+yxC71wAADBrjKnAAAAAAAAAAAAAAw7oNGHttqWONcoFN/2WIDc2pa6WVFtFYROlsaMaTXdcOjXHz93+YxAglKa4AAAAA='; +var register = (__webpack_require__(/*! ../../core/component */ "./src/core/component.js").registerComponent); +var THREE = __webpack_require__(/*! ../../lib/three */ "./src/lib/three.js"); +var CAM_LAYER = 21; +var applyPose = function () { + var tempQuaternion = new THREE.Quaternion(); + var tempVec3 = new THREE.Vector3(); + function applyPose(pose, object3D, offset) { + object3D.position.copy(pose.transform.position); + object3D.quaternion.copy(pose.transform.orientation); + tempVec3.copy(offset); + tempQuaternion.copy(pose.transform.orientation); + tempVec3.applyQuaternion(tempQuaternion); + object3D.position.sub(tempVec3); + } + return applyPose; +}(); +applyPose.tempFakePose = { + transform: { + orientation: new THREE.Quaternion(), + position: new THREE.Vector3() + } +}; + +/** + * Class to handle hit-test from a single source + * + * For a normal space provide it as a space option + * new HitTest(renderer, { + * space: viewerSpace + * }); + * + * this is also useful for the targetRaySpace of an XRInputSource + * + * It can also describe a transient input source like so: + * + * var profileToSupport = 'generic-touchscreen'; + * var transientHitTest = new HitTest(renderer, { + * profile: profileToSupport + * }); + * + * Where the profile matches an item in a type of controller, profiles matching 'generic-touchscreen' + * will always be a transient input and as of 08/2021 all transient inputs are 'generic-touchscreen' + * + * @param {WebGLRenderer} renderer THREE.JS Renderer + * @param {} hitTestSourceDetails The source information either as the information for a transient hit-test or a regular hit-test + */ +function HitTest(renderer, hitTestSourceDetails) { + this.renderer = renderer; + this.xrHitTestSource = null; + renderer.xr.addEventListener('sessionend', function () { + this.xrHitTestSource = null; + }.bind(this)); + renderer.xr.addEventListener('sessionstart', function () { + this.sessionStart(hitTestSourceDetails); + }.bind(this)); + if (this.renderer.xr.isPresenting) { + this.sessionStart(hitTestSourceDetails); + } +} +HitTest.prototype.previousFrameAnchors = new Set(); +HitTest.prototype.anchorToObject3D = new Map(); +function warnAboutHitTest(e) { + console.warn(e.message); + console.warn('Cannot requestHitTestSource Are you missing: webxr="optionalFeatures: hit-test;" from ?'); +} +HitTest.prototype.sessionStart = function sessionStart(hitTestSourceDetails) { + this.session = this.renderer.xr.getSession(); + if (!('requestHitTestSource' in this.session)) { + warnAboutHitTest({ + message: 'No requestHitTestSource on the session.' + }); + return; + } + if (hitTestSourceDetails.space) { + this.session.requestHitTestSource(hitTestSourceDetails).then(function (xrHitTestSource) { + this.xrHitTestSource = xrHitTestSource; + }.bind(this)).catch(warnAboutHitTest); + } else if (hitTestSourceDetails.profile) { + this.session.requestHitTestSourceForTransientInput(hitTestSourceDetails).then(function (xrHitTestSource) { + this.xrHitTestSource = xrHitTestSource; + this.transient = true; + }.bind(this)).catch(warnAboutHitTest); + } +}; + +/** + * Turns the last hit test into an anchor, the provided Object3D will have it's + * position update to track the anchor. + * + * @param {Object3D} object3D object to track + * @param {Vector3} offset offset of the object from the origin that gets subtracted + * @returns + */ +HitTest.prototype.anchorFromLastHitTestResult = function (object3D, offset) { + var hitTest = this.lastHitTest; + if (!hitTest) { + return; + } + var object3DOptions = { + object3D: object3D, + offset: offset + }; + Array.from(this.anchorToObject3D.entries()).forEach(function (entry) { + var entryObject = entry[1].object3D; + var anchor = entry[0]; + if (entryObject === object3D) { + this.anchorToObject3D.delete(anchor); + anchor.delete(); + } + }.bind(this)); + if (hitTest.createAnchor) { + hitTest.createAnchor().then(function (anchor) { + this.anchorToObject3D.set(anchor, object3DOptions); + }.bind(this)).catch(function (e) { + console.warn(e.message); + console.warn('Cannot create anchor, are you missing: webxr="optionalFeatures: anchors;" from ?'); + }); + } +}; +HitTest.prototype.doHit = function doHit(frame) { + if (!this.renderer.xr.isPresenting) { + return; + } + var refSpace = this.renderer.xr.getReferenceSpace(); + var xrViewerPose = frame.getViewerPose(refSpace); + var hitTestResults; + var results; + if (this.xrHitTestSource && xrViewerPose) { + if (this.transient) { + hitTestResults = frame.getHitTestResultsForTransientInput(this.xrHitTestSource); + if (hitTestResults.length > 0) { + results = hitTestResults[0].results; + if (results.length > 0) { + this.lastHitTest = results[0]; + return results[0].getPose(refSpace); + } else { + return false; + } + } else { + return false; + } + } else { + hitTestResults = frame.getHitTestResults(this.xrHitTestSource); + if (hitTestResults.length > 0) { + this.lastHitTest = hitTestResults[0]; + return hitTestResults[0].getPose(refSpace); + } else { + return false; + } + } + } +}; + +// static function +HitTest.updateAnchorPoses = function (frame, refSpace) { + // If tracked anchors isn't defined because it's not supported then just use the empty set + var trackedAnchors = frame.trackedAnchors || HitTest.prototype.previousFrameAnchors; + HitTest.prototype.previousFrameAnchors.forEach(function (anchor) { + // Handle anchor tracking loss - `anchor` was present + // in the present frame but is no longer tracked. + if (!trackedAnchors.has(anchor)) { + HitTest.prototype.anchorToObject3D.delete(anchor); + } + }); + trackedAnchors.forEach(function (anchor) { + var anchorPose; + var object3DOptions; + var offset; + var object3D; + try { + // Query most recent pose of the anchor relative to some reference space: + anchorPose = frame.getPose(anchor.anchorSpace, refSpace); + } catch (e) { + // This will fail if the anchor has been deleted that frame + } + if (anchorPose) { + object3DOptions = HitTest.prototype.anchorToObject3D.get(anchor); + if (!object3DOptions) { + return; + } + offset = object3DOptions.offset; + object3D = object3DOptions.object3D; + applyPose(anchorPose, object3D, offset); + } + }); +}; +var hitTestCache; +module.exports.Component = register('ar-hit-test', { + schema: { + target: { + type: 'selector' + }, + enabled: { + default: true + }, + src: { + default: arrowURL, + type: 'map' + }, + type: { + default: 'footprint', + oneOf: ['footprint', 'map'] + }, + footprintDepth: { + default: 0.1 + }, + mapSize: { + type: 'vec2', + default: { + x: 0.5, + y: 0.5 + } + } + }, + init: function () { + this.hitTest = null; + this.imageDataArray = new Uint8ClampedArray(512 * 512 * 4); + this.imageData = new ImageData(this.imageDataArray, 512, 512); + this.textureCache = new Map(); + this.orthoCam = new THREE.OrthographicCamera(); + this.orthoCam.layers.set(CAM_LAYER); + this.textureTarget = new THREE.WebGLRenderTarget(512, 512, {}); + this.basicMaterial = new THREE.MeshBasicMaterial({ + color: 0x000000, + side: THREE.DoubleSide + }); + this.canvas = document.createElement('canvas'); + this.context = this.canvas.getContext('2d'); + this.context.imageSmoothingEnabled = false; + this.canvas.width = 512; + this.canvas.height = 512; + this.canvasTexture = new THREE.CanvasTexture(this.canvas, { + alpha: true + }); + this.canvasTexture.flipY = false; + + // Update WebXR to support hit-test and anchors + var webxrData = this.el.getAttribute('webxr'); + var optionalFeaturesArray = webxrData.optionalFeatures; + if (!optionalFeaturesArray.includes('hit-test') || !optionalFeaturesArray.includes('anchors')) { + optionalFeaturesArray.push('hit-test'); + optionalFeaturesArray.push('anchors'); + this.el.setAttribute('webxr', webxrData); + } + this.el.sceneEl.renderer.xr.addEventListener('sessionend', function () { + this.hitTest = null; + }.bind(this)); + this.el.sceneEl.renderer.xr.addEventListener('sessionstart', function () { + // Don't request Hit Test unless AR (breaks WebXR Emulator) + if (!this.el.is('ar-mode')) { + return; + } + var renderer = this.el.sceneEl.renderer; + var session = this.session = renderer.xr.getSession(); + this.hasPosedOnce = false; + this.bboxMesh.visible = false; + if (!hitTestCache) { + hitTestCache = new Map(); + } + + // Default to selecting through the face + session.requestReferenceSpace('viewer').then(function (viewerSpace) { + this.hitTest = new HitTest(renderer, { + space: viewerSpace + }); + hitTestCache.set(viewerSpace, this.hitTest); + this.el.emit('ar-hit-test-start'); + }.bind(this)); + + // These are transient inputs so need to be handled seperately + var profileToSupport = 'generic-touchscreen'; + var transientHitTest = new HitTest(renderer, { + profile: profileToSupport + }); + session.addEventListener('selectstart', function (e) { + if (this.data.enabled !== true) { + return; + } + var inputSource = e.inputSource; + this.bboxMesh.visible = true; + if (this.hasPosedOnce === true) { + this.el.emit('ar-hit-test-select-start', { + inputSource: inputSource, + position: this.bboxMesh.position, + orientation: this.bboxMesh.quaternion + }); + if (inputSource.profiles[0] === profileToSupport) { + this.hitTest = transientHitTest; + } else { + this.hitTest = hitTestCache.get(inputSource) || new HitTest(renderer, { + space: inputSource.targetRaySpace + }); + hitTestCache.set(inputSource, this.hitTest); + } + } + }.bind(this)); + session.addEventListener('selectend', function (e) { + if (!this.hitTest || this.data.enabled !== true) { + this.hitTest = null; + return; + } + var inputSource = e.inputSource; + var object; + if (this.hasPosedOnce === true) { + this.bboxMesh.visible = false; + + // if we have a target with a 3D object then automatically generate an anchor for it. + if (this.data.target) { + object = this.data.target.object3D; + if (object) { + applyPose.tempFakePose.transform.position.copy(this.bboxMesh.position); + applyPose.tempFakePose.transform.orientation.copy(this.bboxMesh.quaternion); + applyPose(applyPose.tempFakePose, object, this.bboxOffset); + object.visible = true; + + // create an anchor attatched to the object + this.hitTest.anchorFromLastHitTestResult(object, this.bboxOffset); + } + } + this.el.emit('ar-hit-test-select', { + inputSource: inputSource, + position: this.bboxMesh.position, + orientation: this.bboxMesh.quaternion + }); + } + this.hitTest = null; + }.bind(this)); + }.bind(this)); + this.bboxOffset = new THREE.Vector3(); + this.update = this.update.bind(this); + this.makeBBox(); + }, + update: function () { + // If it is disabled it's cleaned up + if (this.data.enabled === false) { + this.hitTest = null; + this.bboxMesh.visible = false; + } + if (this.data.target) { + if (this.data.target.object3D) { + this.data.target.addEventListener('model-loaded', this.update); + this.data.target.object3D.layers.enable(CAM_LAYER); + this.data.target.object3D.traverse(function (child) { + child.layers.enable(CAM_LAYER); + }); + } else { + this.data.target.addEventListener('loaded', this.update, { + once: true + }); + } + } + this.bboxNeedsUpdate = true; + }, + makeBBox: function () { + var geometry = new THREE.PlaneGeometry(1, 1); + var material = new THREE.MeshBasicMaterial({ + transparent: true, + color: 0xffffff + }); + geometry.rotateX(-Math.PI / 2); + geometry.rotateY(-Math.PI / 2); + this.bbox = new THREE.Box3(); + this.bboxMesh = new THREE.Mesh(geometry, material); + this.el.setObject3D('ar-hit-test', this.bboxMesh); + this.bboxMesh.visible = false; + }, + updateFootprint: function () { + var tempImageData; + var renderer = this.el.sceneEl.renderer; + var oldRenderTarget, oldBackground; + var isXREnabled = renderer.xr.enabled; + this.bboxMesh.material.map = this.canvasTexture; + this.bboxMesh.material.needsUpdate = true; + this.orthoCam.rotation.set(-Math.PI / 2, 0, -Math.PI / 2); + this.orthoCam.position.copy(this.bboxMesh.position); + this.orthoCam.position.y -= this.bboxMesh.scale.y / 2; + this.orthoCam.near = 0.1; + this.orthoCam.far = this.orthoCam.near + this.data.footprintDepth * this.bboxMesh.scale.y; + this.orthoCam.position.y += this.orthoCam.far; + this.orthoCam.right = this.bboxMesh.scale.z / 2; + this.orthoCam.left = -this.bboxMesh.scale.z / 2; + this.orthoCam.top = this.bboxMesh.scale.x / 2; + this.orthoCam.bottom = -this.bboxMesh.scale.x / 2; + this.orthoCam.updateProjectionMatrix(); + oldRenderTarget = renderer.getRenderTarget(); + renderer.setRenderTarget(this.textureTarget); + renderer.xr.enabled = false; + oldBackground = this.el.object3D.background; + this.el.object3D.overrideMaterial = this.basicMaterial; + this.el.object3D.background = null; + renderer.render(this.el.object3D, this.orthoCam); + this.el.object3D.background = oldBackground; + this.el.object3D.overrideMaterial = null; + renderer.xr.enabled = isXREnabled; + renderer.setRenderTarget(oldRenderTarget); + renderer.readRenderTargetPixels(this.textureTarget, 0, 0, 512, 512, this.imageDataArray); + this.context.putImageData(this.imageData, 0, 0); + this.context.shadowColor = 'white'; + this.context.shadowBlur = 10; + this.context.drawImage(this.canvas, 0, 0); + tempImageData = this.context.getImageData(0, 0, 512, 512); + for (var i = 0; i < 512 * 512; i++) { + // if it's a little bit transparent but not opaque make it middle transparent + if (tempImageData.data[i * 4 + 3] !== 0 && tempImageData.data[i * 4 + 3] !== 255) { + tempImageData.data[i * 4 + 3] = 128; + } + } + this.context.putImageData(tempImageData, 0, 0); + this.canvasTexture.needsUpdate = true; + }, + tick: function () { + var pose; + var frame = this.el.sceneEl.frame; + var renderer = this.el.sceneEl.renderer; + if (frame) { + // if we are in XR then update the positions of the objects attatched to anchors + HitTest.updateAnchorPoses(frame, renderer.xr.getReferenceSpace()); + } + if (this.bboxNeedsUpdate) { + this.bboxNeedsUpdate = false; + if (!this.data.target || this.data.type === 'map') { + var texture; + if (this.textureCache.has(this.data.src)) { + texture = this.textureCache.get(this.data.src); + } else { + texture = new THREE.TextureLoader().load(this.data.src); + this.textureCache.set(this.data.src, texture); + } + this.bboxMesh.material.map = texture; + this.bboxMesh.material.needsUpdate = true; + } + if (this.data.target && this.data.target.object3D) { + this.bbox.setFromObject(this.data.target.object3D); + this.bbox.getCenter(this.bboxMesh.position); + this.bbox.getSize(this.bboxMesh.scale); + if (this.data.type === 'footprint') { + // Add a little buffer for the footprint border + this.bboxMesh.scale.x *= 1.04; + this.bboxMesh.scale.z *= 1.04; + this.updateFootprint(); + } + this.bboxMesh.position.y -= this.bboxMesh.scale.y / 2; + this.bboxOffset.copy(this.bboxMesh.position); + this.bboxOffset.sub(this.data.target.object3D.position); + } else { + this.bboxMesh.scale.set(this.data.mapSize.x, 1, this.data.mapSize.y); + } + } + if (this.hitTest) { + pose = this.hitTest.doHit(frame); + if (pose) { + if (this.hasPosedOnce !== true) { + this.hasPosedOnce = true; + this.el.emit('ar-hit-test-achieved'); + } + this.bboxMesh.visible = true; + this.bboxMesh.position.copy(pose.transform.position); + this.bboxMesh.quaternion.copy(pose.transform.orientation); + } + } + } +}); + +/***/ }), + +/***/ "./src/components/scene/background.js": +/*!********************************************!*\ + !*** ./src/components/scene/background.js ***! + \********************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +/* global THREE */ +var register = (__webpack_require__(/*! ../../core/component */ "./src/core/component.js").registerComponent); +module.exports.Component = register('background', { + schema: { + color: { + type: 'color', + default: 'black' + }, + transparent: { + default: false + } + }, + update: function () { + var data = this.data; + var object3D = this.el.object3D; + if (data.transparent) { + object3D.background = null; + } else { + object3D.background = new THREE.Color(data.color); + } + }, + remove: function () { + var object3D = this.el.object3D; + object3D.background = null; + } +}); + +/***/ }), + +/***/ "./src/components/scene/debug.js": +/*!***************************************!*\ + !*** ./src/components/scene/debug.js ***! + \***************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var register = (__webpack_require__(/*! ../../core/component */ "./src/core/component.js").registerComponent); +module.exports.Component = register('debug', { + schema: { + default: true + } +}); + +/***/ }), + +/***/ "./src/components/scene/device-orientation-permission-ui.js": +/*!******************************************************************!*\ + !*** ./src/components/scene/device-orientation-permission-ui.js ***! + \******************************************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +/* global DeviceOrientationEvent, location */ +var registerComponent = (__webpack_require__(/*! ../../core/component */ "./src/core/component.js").registerComponent); +var utils = __webpack_require__(/*! ../../utils/ */ "./src/utils/index.js"); +var bind = utils.bind; +var constants = __webpack_require__(/*! ../../constants/ */ "./src/constants/index.js"); +var MODAL_CLASS = 'a-modal'; +var DIALOG_CLASS = 'a-dialog'; +var DIALOG_TEXT_CLASS = 'a-dialog-text'; +var DIALOG_TEXT_CONTAINER_CLASS = 'a-dialog-text-container'; +var DIALOG_BUTTONS_CONTAINER_CLASS = 'a-dialog-buttons-container'; +var DIALOG_BUTTON_CLASS = 'a-dialog-button'; +var DIALOG_ALLOW_BUTTON_CLASS = 'a-dialog-allow-button'; +var DIALOG_DENY_BUTTON_CLASS = 'a-dialog-deny-button'; +var DIALOG_OK_BUTTON_CLASS = 'a-dialog-ok-button'; + +/** + * UI for enabling device motion permission + */ +module.exports.Component = registerComponent('device-orientation-permission-ui', { + schema: { + enabled: { + default: true + }, + deviceMotionMessage: { + default: 'This immersive website requires access to your device motion sensors.' + }, + httpsMessage: { + default: 'Access this site over HTTPS to enter VR mode and grant access to the device sensors.' + }, + denyButtonText: { + default: 'Deny' + }, + allowButtonText: { + default: 'Allow' + }, + cancelButtonText: { + default: 'Cancel' + } + }, + init: function () { + var self = this; + if (!this.data.enabled) { + return; + } + if (location.hostname !== 'localhost' && location.hostname !== '127.0.0.1' && location.protocol === 'http:') { + this.showHTTPAlert(); + } + + // Browser doesn't support or doesn't require permission to DeviceOrientationEvent API. + if (typeof DeviceOrientationEvent === 'undefined' || !DeviceOrientationEvent.requestPermission) { + this.permissionGranted = true; + return; + } + this.onDeviceMotionDialogAllowClicked = bind(this.onDeviceMotionDialogAllowClicked, this); + this.onDeviceMotionDialogDenyClicked = bind(this.onDeviceMotionDialogDenyClicked, this); + // Show dialog only if permission has not yet been granted. + DeviceOrientationEvent.requestPermission().then(function () { + self.el.emit('deviceorientationpermissiongranted'); + self.permissionGranted = true; + }).catch(function () { + self.devicePermissionDialogEl = createPermissionDialog(self.data.denyButtonText, self.data.allowButtonText, self.data.deviceMotionMessage, self.onDeviceMotionDialogAllowClicked, self.onDeviceMotionDialogDenyClicked); + self.el.appendChild(self.devicePermissionDialogEl); + }); + }, + remove: function () { + // This removes the modal screen + if (this.devicePermissionDialogEl) { + this.el.removeChild(this.devicePermissionDialogEl); + } + }, + onDeviceMotionDialogDenyClicked: function () { + this.remove(); + }, + showHTTPAlert: function () { + var self = this; + var httpAlertEl = createAlertDialog(self.data.cancelButtonText, self.data.httpsMessage, function () { + self.el.removeChild(httpAlertEl); + }); + this.el.appendChild(httpAlertEl); + }, + /** + * Enable device motion permission when clicked. + */ + onDeviceMotionDialogAllowClicked: function () { + var self = this; + this.el.emit('deviceorientationpermissionrequested'); + DeviceOrientationEvent.requestPermission().then(function (response) { + if (response === 'granted') { + self.el.emit('deviceorientationpermissiongranted'); + self.permissionGranted = true; + } else { + self.el.emit('deviceorientationpermissionrejected'); + } + self.remove(); + }).catch(console.error); + } +}); + +/** + * Create a modal dialog that request users permission to access the Device Motion API. + * + * @param {function} onAllowClicked - click event handler + * @param {function} onDenyClicked - click event handler + * + * @returns {Element} Wrapper
. + */ +function createPermissionDialog(denyText, allowText, dialogText, onAllowClicked, onDenyClicked) { + var buttonsContainer; + var denyButton; + var acceptButton; + buttonsContainer = document.createElement('div'); + buttonsContainer.classList.add(DIALOG_BUTTONS_CONTAINER_CLASS); + + // Buttons + denyButton = document.createElement('button'); + denyButton.classList.add(DIALOG_BUTTON_CLASS, DIALOG_DENY_BUTTON_CLASS); + denyButton.setAttribute(constants.AFRAME_INJECTED, ''); + denyButton.innerHTML = denyText; + buttonsContainer.appendChild(denyButton); + acceptButton = document.createElement('button'); + acceptButton.classList.add(DIALOG_BUTTON_CLASS, DIALOG_ALLOW_BUTTON_CLASS); + acceptButton.setAttribute(constants.AFRAME_INJECTED, ''); + acceptButton.innerHTML = allowText; + buttonsContainer.appendChild(acceptButton); + + // Ask for sensor events to be used + acceptButton.addEventListener('click', function (evt) { + evt.stopPropagation(); + onAllowClicked(); + }); + denyButton.addEventListener('click', function (evt) { + evt.stopPropagation(); + onDenyClicked(); + }); + return createDialog(dialogText, buttonsContainer); +} +function createAlertDialog(closeText, dialogText, onOkClicked) { + var buttonsContainer; + var okButton; + buttonsContainer = document.createElement('div'); + buttonsContainer.classList.add(DIALOG_BUTTONS_CONTAINER_CLASS); + + // Buttons + okButton = document.createElement('button'); + okButton.classList.add(DIALOG_BUTTON_CLASS, DIALOG_OK_BUTTON_CLASS); + okButton.setAttribute(constants.AFRAME_INJECTED, ''); + okButton.innerHTML = closeText; + buttonsContainer.appendChild(okButton); + + // Ask for sensor events to be used + okButton.addEventListener('click', function (evt) { + evt.stopPropagation(); + onOkClicked(); + }); + return createDialog(dialogText, buttonsContainer); +} +function createDialog(text, buttonsContainerEl) { + var modalContainer; + var dialog; + var dialogTextContainer; + var dialogText; + modalContainer = document.createElement('div'); + modalContainer.classList.add(MODAL_CLASS); + modalContainer.setAttribute(constants.AFRAME_INJECTED, ''); + dialog = document.createElement('div'); + dialog.className = DIALOG_CLASS; + dialog.setAttribute(constants.AFRAME_INJECTED, ''); + modalContainer.appendChild(dialog); + dialogTextContainer = document.createElement('div'); + dialogTextContainer.classList.add(DIALOG_TEXT_CONTAINER_CLASS); + dialog.appendChild(dialogTextContainer); + dialogText = document.createElement('div'); + dialogText.classList.add(DIALOG_TEXT_CLASS); + dialogText.innerHTML = text; + dialogTextContainer.appendChild(dialogText); + dialog.appendChild(buttonsContainerEl); + return modalContainer; +} + +/***/ }), + +/***/ "./src/components/scene/embedded.js": +/*!******************************************!*\ + !*** ./src/components/scene/embedded.js ***! + \******************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var registerComponent = (__webpack_require__(/*! ../../core/component */ "./src/core/component.js").registerComponent); + +/** + * Component to embed an a-frame scene within the layout of a 2D page. + */ +module.exports.Component = registerComponent('embedded', { + dependencies: ['xr-mode-ui'], + schema: { + default: true + }, + update: function () { + var sceneEl = this.el; + var enterVREl = sceneEl.querySelector('.a-enter-vr'); + if (this.data === true) { + if (enterVREl) { + enterVREl.classList.add('embedded'); + } + sceneEl.removeFullScreenStyles(); + } else { + if (enterVREl) { + enterVREl.classList.remove('embedded'); + } + sceneEl.addFullScreenStyles(); + } + } +}); + +/***/ }), + +/***/ "./src/components/scene/fog.js": +/*!*************************************!*\ + !*** ./src/components/scene/fog.js ***! + \*************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var register = (__webpack_require__(/*! ../../core/component */ "./src/core/component.js").registerComponent); +var THREE = __webpack_require__(/*! ../../lib/three */ "./src/lib/three.js"); +var debug = __webpack_require__(/*! ../../utils/debug */ "./src/utils/debug.js"); +var warn = debug('components:fog:warn'); + +/** + * Fog component. + * Applies only to the scene entity. + */ +module.exports.Component = register('fog', { + schema: { + color: { + type: 'color', + default: '#000' + }, + density: { + default: 0.00025 + }, + far: { + default: 1000, + min: 0 + }, + near: { + default: 1, + min: 0 + }, + type: { + default: 'linear', + oneOf: ['linear', 'exponential'] + } + }, + update: function () { + var data = this.data; + var el = this.el; + var fog = this.el.object3D.fog; + if (!el.isScene) { + warn('Fog component can only be applied to '); + return; + } + + // (Re)create fog if fog doesn't exist or fog type changed. + if (!fog || data.type !== fog.name) { + el.object3D.fog = getFog(data); + return; + } + + // Fog data changed. Update fog. + Object.keys(this.schema).forEach(function (key) { + var value = data[key]; + if (key === 'color') { + value = new THREE.Color(value); + } + fog[key] = value; + }); + }, + /** + * Remove fog on remove (callback). + */ + remove: function () { + var el = this.el; + var fog = this.el.object3D.fog; + if (!fog) { + return; + } + el.object3D.fog = null; + } +}); + +/** + * Creates a fog object. Sets fog.name to be able to detect fog type changes. + * + * @param {object} data - Fog data. + * @returns {object} fog + */ +function getFog(data) { + var fog; + if (data.type === 'exponential') { + fog = new THREE.FogExp2(data.color, data.density); + } else { + fog = new THREE.Fog(data.color, data.near, data.far); + } + fog.name = data.type; + return fog; +} + +/***/ }), + +/***/ "./src/components/scene/inspector.js": +/*!*******************************************!*\ + !*** ./src/components/scene/inspector.js ***! + \*******************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +/* global AFRAME */ +var AFRAME_INJECTED = (__webpack_require__(/*! ../../constants */ "./src/constants/index.js").AFRAME_INJECTED); +var pkg = __webpack_require__(/*! ../../../package */ "./package.json"); +var registerComponent = (__webpack_require__(/*! ../../core/component */ "./src/core/component.js").registerComponent); +var utils = __webpack_require__(/*! ../../utils/ */ "./src/utils/index.js"); + +/** + * 0.4.2 to 0.4.x + * Will need to update this when A-Frame goes to 1.x.x. + */ +function getFuzzyPatchVersion(version) { + var split = version.split('.'); + split[2] = 'x'; + return split.join('.'); +} +var INSPECTOR_DEV_URL = 'https://aframe.io/aframe-inspector/dist/aframe-inspector.js'; +var INSPECTOR_RELEASE_URL = 'https://unpkg.com/aframe-inspector@' + getFuzzyPatchVersion(pkg.version) + '/dist/aframe-inspector.min.js'; +var INSPECTOR_URL = false ? 0 : INSPECTOR_RELEASE_URL; +var LOADING_MESSAGE = 'Loading Inspector'; +var LOADING_ERROR_MESSAGE = 'Error loading Inspector'; +module.exports.Component = registerComponent('inspector', { + schema: { + url: { + default: INSPECTOR_URL + } + }, + init: function () { + this.firstPlay = true; + this.onKeydown = this.onKeydown.bind(this); + this.onMessage = this.onMessage.bind(this); + this.initOverlay(); + window.addEventListener('keydown', this.onKeydown); + window.addEventListener('message', this.onMessage); + }, + play: function () { + var urlParam; + if (!this.firstPlay) { + return; + } + urlParam = utils.getUrlParameter('inspector'); + if (urlParam !== 'false' && !!urlParam) { + this.openInspector(); + this.firstPlay = false; + } + }, + initOverlay: function () { + var dotsHTML = '...'; + this.loadingMessageEl = document.createElement('div'); + this.loadingMessageEl.classList.add('a-inspector-loader'); + this.loadingMessageEl.innerHTML = LOADING_MESSAGE + dotsHTML; + }, + remove: function () { + this.removeEventListeners(); + }, + /** + * + + i keyboard shortcut. + */ + onKeydown: function (evt) { + var shortcutPressed = evt.keyCode === 73 && (evt.ctrlKey && evt.altKey || evt.getModifierState('AltGraph')); + if (!shortcutPressed) { + return; + } + this.openInspector(); + }, + showLoader: function () { + document.body.appendChild(this.loadingMessageEl); + }, + hideLoader: function () { + document.body.removeChild(this.loadingMessageEl); + }, + /** + * postMessage. aframe.io uses this to create a button on examples to open Inspector. + */ + onMessage: function (evt) { + if (evt.data === 'INJECT_AFRAME_INSPECTOR') { + this.openInspector(); + } + }, + openInspector: function (focusEl) { + var self = this; + var script; + + // Already injected. Open. + if (AFRAME.INSPECTOR || AFRAME.inspectorInjected) { + AFRAME.INSPECTOR.open(focusEl); + return; + } + this.showLoader(); + + // Inject. + script = document.createElement('script'); + script.src = this.data.url; + script.setAttribute('data-name', 'aframe-inspector'); + script.setAttribute(AFRAME_INJECTED, ''); + script.onload = function () { + AFRAME.INSPECTOR.open(focusEl); + self.hideLoader(); + self.removeEventListeners(); + }; + script.onerror = function () { + self.loadingMessageEl.innerHTML = LOADING_ERROR_MESSAGE; + }; + document.head.appendChild(script); + AFRAME.inspectorInjected = true; + }, + removeEventListeners: function () { + window.removeEventListener('keydown', this.onKeydown); + window.removeEventListener('message', this.onMessage); + } +}); + +/***/ }), + +/***/ "./src/components/scene/keyboard-shortcuts.js": +/*!****************************************************!*\ + !*** ./src/components/scene/keyboard-shortcuts.js ***! + \****************************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var registerComponent = (__webpack_require__(/*! ../../core/component */ "./src/core/component.js").registerComponent); +var shouldCaptureKeyEvent = (__webpack_require__(/*! ../../utils/ */ "./src/utils/index.js").shouldCaptureKeyEvent); +module.exports.Component = registerComponent('keyboard-shortcuts', { + schema: { + enterVR: { + default: true + }, + exitVR: { + default: true + } + }, + init: function () { + this.onKeyup = this.onKeyup.bind(this); + }, + update: function (oldData) { + var data = this.data; + this.enterVREnabled = data.enterVR; + }, + play: function () { + window.addEventListener('keyup', this.onKeyup, false); + }, + pause: function () { + window.removeEventListener('keyup', this.onKeyup); + }, + onKeyup: function (evt) { + var scene = this.el; + if (!shouldCaptureKeyEvent(evt)) { + return; + } + if (this.enterVREnabled && evt.keyCode === 70) { + // f. + scene.enterVR(); + } + if (this.enterVREnabled && evt.keyCode === 27) { + // escape. + scene.exitVR(); + } + } +}); + +/***/ }), + +/***/ "./src/components/scene/pool.js": +/*!**************************************!*\ + !*** ./src/components/scene/pool.js ***! + \**************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var debug = __webpack_require__(/*! ../../utils/debug */ "./src/utils/debug.js"); +var registerComponent = (__webpack_require__(/*! ../../core/component */ "./src/core/component.js").registerComponent); +var warn = debug('components:pool:warn'); + +/** + * Pool component to reuse entities. + * Avoids creating and destroying the same kind of entities. + * Helps reduce GC pauses. For example in a game to reuse enemies entities. + * + * @member {array} availableEls - Available entities in the pool. + * @member {array} usedEls - Entities of the pool in use. + */ +module.exports.Component = registerComponent('pool', { + schema: { + container: { + default: '' + }, + mixin: { + default: '' + }, + size: { + default: 0 + }, + dynamic: { + default: false + } + }, + multiple: true, + initPool: function () { + var i; + this.availableEls = []; + this.usedEls = []; + if (!this.data.mixin) { + warn('No mixin provided for pool component.'); + } + if (this.data.container) { + this.container = document.querySelector(this.data.container); + if (!this.container) { + warn('Container ' + this.data.container + ' not found.'); + } + } + this.container = this.container || this.el; + for (i = 0; i < this.data.size; ++i) { + this.createEntity(); + } + }, + update: function (oldData) { + var data = this.data; + if (oldData.mixin !== data.mixin || oldData.size !== data.size) { + this.initPool(); + } + }, + /** + * Add a new entity to the list of available entities. + */ + createEntity: function () { + var el; + el = document.createElement('a-entity'); + el.play = this.wrapPlay(el.play); + el.setAttribute('mixin', this.data.mixin); + el.object3D.visible = false; + el.pause(); + this.container.appendChild(el); + this.availableEls.push(el); + var usedEls = this.usedEls; + el.addEventListener('loaded', function () { + if (usedEls.indexOf(el) !== -1) { + return; + } + el.object3DParent = el.object3D.parent; + el.object3D.parent.remove(el.object3D); + }); + }, + /** + * Play wrapper for pooled entities. When pausing and playing a scene, don't want to play + * entities that are not in use. + */ + wrapPlay: function (playMethod) { + var usedEls = this.usedEls; + return function () { + if (usedEls.indexOf(this) === -1) { + return; + } + playMethod.call(this); + }; + }, + /** + * Used to request one of the available entities of the pool. + */ + requestEntity: function () { + var el; + if (this.availableEls.length === 0) { + if (this.data.dynamic === false) { + warn('Requested entity from empty pool: ' + this.attrName); + return; + } else { + warn('Requested entity from empty pool. This pool is dynamic and will resize ' + 'automatically. You might want to increase its initial size: ' + this.attrName); + } + this.createEntity(); + } + el = this.availableEls.shift(); + this.usedEls.push(el); + if (el.object3DParent) { + el.object3DParent.add(el.object3D); + this.updateRaycasters(); + } + el.object3D.visible = true; + return el; + }, + /** + * Used to return a used entity to the pool. + */ + returnEntity: function (el) { + var index = this.usedEls.indexOf(el); + if (index === -1) { + warn('The returned entity was not previously pooled from ' + this.attrName); + return; + } + this.usedEls.splice(index, 1); + this.availableEls.push(el); + el.object3DParent = el.object3D.parent; + el.object3D.parent.remove(el.object3D); + this.updateRaycasters(); + el.object3D.visible = false; + el.pause(); + return el; + }, + updateRaycasters: function () { + var raycasterEls = document.querySelectorAll('[raycaster]'); + raycasterEls.forEach(function (el) { + el.components['raycaster'].setDirty(); + }); + } +}); + +/***/ }), + +/***/ "./src/components/scene/real-world-meshing.js": +/*!****************************************************!*\ + !*** ./src/components/scene/real-world-meshing.js ***! + \****************************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +/* global XRPlane, XRMesh */ +var register = (__webpack_require__(/*! ../../core/component */ "./src/core/component.js").registerComponent); +var THREE = __webpack_require__(/*! ../../lib/three */ "./src/lib/three.js"); + +/** + * Real World Meshing. + * + * Create entities with meshes corresponding to 3D surfaces detected in user's enviornment. + * It requires a browser with support for the WebXR Mesh and Plane detection modules. + * + */ +module.exports.Component = register('real-world-meshing', { + schema: { + filterLabels: { + type: 'array' + }, + meshesEnabled: { + default: true + }, + meshMixin: { + default: true + }, + planesEnabled: { + default: true + }, + planeMixin: { + default: '' + } + }, + init: function () { + var webxrData = this.el.getAttribute('webxr'); + var requiredFeaturesArray = webxrData.requiredFeatures; + if (requiredFeaturesArray.indexOf('mesh-detection') === -1) { + requiredFeaturesArray.push('mesh-detection'); + this.el.setAttribute('webxr', webxrData); + } + if (requiredFeaturesArray.indexOf('plane-detection') === -1) { + requiredFeaturesArray.push('plane-detection'); + this.el.setAttribute('webxr', webxrData); + } + this.meshEntities = []; + this.initWorldMeshEntity = this.initWorldMeshEntity.bind(this); + }, + tick: function () { + if (!this.el.is('ar-mode')) { + return; + } + this.detectMeshes(); + this.updateMeshes(); + }, + detectMeshes: function () { + var data = this.data; + var detectedMeshes; + var detectedPlanes; + var sceneEl = this.el; + var xrManager = sceneEl.renderer.xr; + var frame; + var meshEntities = this.meshEntities; + var present = false; + var newMeshes = []; + var filterLabels = this.data.filterLabels; + frame = sceneEl.frame; + detectedMeshes = frame.detectedMeshes; + detectedPlanes = frame.detectedPlanes; + for (var i = 0; i < meshEntities.length; i++) { + meshEntities[i].present = false; + } + if (data.meshesEnabled) { + for (var mesh of detectedMeshes.values()) { + // Ignore meshes that don't match the filterLabels. + if (filterLabels.length && filterLabels.indexOf(mesh.semanticLabel) === -1) { + continue; + } + for (i = 0; i < meshEntities.length; i++) { + if (mesh === meshEntities[i].mesh) { + present = true; + meshEntities[i].present = true; + if (meshEntities[i].lastChangedTime < mesh.lastChangedTime) { + this.updateMeshGeometry(meshEntities[i].el, mesh); + } + meshEntities[i].lastChangedTime = mesh.lastChangedTime; + break; + } + } + if (!present) { + newMeshes.push(mesh); + } + present = false; + } + } + if (data.planesEnabled) { + for (mesh of detectedPlanes.values()) { + // Ignore meshes that don't match the filterLabels. + if (filterLabels.length && filterLabels.indexOf(mesh.semanticLabel) === -1) { + continue; + } + for (i = 0; i < meshEntities.length; i++) { + if (mesh === meshEntities[i].mesh) { + present = true; + meshEntities[i].present = true; + if (meshEntities[i].lastChangedTime < mesh.lastChangedTime) { + this.updateMeshGeometry(meshEntities[i].el, mesh); + } + meshEntities[i].lastChangedTime = mesh.lastChangedTime; + break; + } + } + if (!present) { + newMeshes.push(mesh); + } + present = false; + } + } + this.deleteMeshes(); + this.createNewMeshes(newMeshes); + }, + updateMeshes: function () { + var auxMatrix = new THREE.Matrix4(); + return function () { + var meshPose; + var sceneEl = this.el; + var meshEl; + var frame = sceneEl.frame; + var meshEntities = this.meshEntities; + var referenceSpace = sceneEl.renderer.xr.getReferenceSpace(); + var meshSpace; + for (var i = 0; i < meshEntities.length; i++) { + meshSpace = meshEntities[i].mesh.meshSpace || meshEntities[i].mesh.planeSpace; + meshPose = frame.getPose(meshSpace, referenceSpace); + meshEl = meshEntities[i].el; + if (!meshEl.hasLoaded) { + continue; + } + auxMatrix.fromArray(meshPose.transform.matrix); + auxMatrix.decompose(meshEl.object3D.position, meshEl.object3D.quaternion, meshEl.object3D.scale); + } + }; + }(), + deleteMeshes: function () { + var meshEntities = this.meshEntities; + var newMeshEntities = []; + for (var i = 0; i < meshEntities.length; i++) { + if (!meshEntities[i].present) { + this.el.removeChild(meshEntities[i]); + } else { + newMeshEntities.push(meshEntities[i]); + } + } + this.meshEntities = newMeshEntities; + }, + createNewMeshes: function (newMeshes) { + var meshEl; + for (var i = 0; i < newMeshes.length; i++) { + meshEl = document.createElement('a-entity'); + this.meshEntities.push({ + mesh: newMeshes[i], + el: meshEl + }); + meshEl.addEventListener('loaded', this.initWorldMeshEntity); + this.el.appendChild(meshEl); + } + }, + initMeshGeometry: function (mesh) { + var geometry; + var shape; + var polygon; + if (mesh instanceof XRPlane) { + shape = new THREE.Shape(); + polygon = mesh.polygon; + for (var i = 0; i < polygon.length; ++i) { + if (i === 0) { + shape.moveTo(polygon[i].x, polygon[i].z); + } else { + shape.lineTo(polygon[i].x, polygon[i].z); + } + } + geometry = new THREE.ShapeGeometry(shape); + geometry.rotateX(Math.PI / 2); + return geometry; + } + geometry = new THREE.BufferGeometry(); + geometry.setAttribute('position', new THREE.BufferAttribute(mesh.vertices, 3)); + geometry.setIndex(new THREE.BufferAttribute(mesh.indices, 1)); + return geometry; + }, + initWorldMeshEntity: function (evt) { + var el = evt.target; + var geometry; + var mesh; + var meshEntity; + var meshEntities = this.meshEntities; + for (var i = 0; i < meshEntities.length; i++) { + if (meshEntities[i].el === el) { + meshEntity = meshEntities[i]; + break; + } + } + geometry = this.initMeshGeometry(meshEntity.mesh); + mesh = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({ + color: Math.random() * 0xFFFFFF, + side: THREE.DoubleSide + })); + el.setObject3D('mesh', mesh); + if (meshEntity.mesh instanceof XRPlane && this.data.planeMixin) { + el.setAttribute('mixin', this.data.planeMixin); + } else { + if (this.data.meshMixin) { + el.setAttribute('mixin', this.data.meshMixin); + } + } + el.setAttribute('data-world-mesh', meshEntity.mesh.semanticLabel); + }, + updateMeshGeometry: function (entityEl, mesh) { + var entityMesh = entityEl.getObject3D('mesh'); + entityMesh.geometry.dispose(); + entityMesh.geometry = this.initMeshGeometry(mesh); + } +}); + +/***/ }), + +/***/ "./src/components/scene/reflection.js": +/*!********************************************!*\ + !*** ./src/components/scene/reflection.js ***! + \********************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +/* global THREE, XRWebGLBinding */ +var register = (__webpack_require__(/*! ../../core/component */ "./src/core/component.js").registerComponent); + +// source: view-source:https://storage.googleapis.com/chromium-webxr-test/r886480/proposals/lighting-estimation.html +function updateLights(estimate, probeLight, directionalLight, directionalLightPosition) { + var intensityScalar = Math.max(estimate.primaryLightIntensity.x, Math.max(estimate.primaryLightIntensity.y, estimate.primaryLightIntensity.z)); + probeLight.sh.fromArray(estimate.sphericalHarmonicsCoefficients); + probeLight.intensity = 1; + if (directionalLight) { + directionalLight.color.setRGB(estimate.primaryLightIntensity.x / intensityScalar, estimate.primaryLightIntensity.y / intensityScalar, estimate.primaryLightIntensity.z / intensityScalar); + directionalLight.intensity = intensityScalar; + directionalLightPosition.copy(estimate.primaryLightDirection); + } +} +module.exports.Component = register('reflection', { + schema: { + directionalLight: { + type: 'selector' + } + }, + init: function () { + var self = this; + this.cubeRenderTarget = new THREE.WebGLCubeRenderTarget(16); + this.cubeCamera = new THREE.CubeCamera(0.1, 1000, this.cubeRenderTarget); + this.lightingEstimationTexture = new THREE.WebGLCubeRenderTarget(16).texture; + this.needsVREnvironmentUpdate = true; + + // Update WebXR to support light-estimation + var webxrData = this.el.getAttribute('webxr'); + var optionalFeaturesArray = webxrData.optionalFeatures; + if (!optionalFeaturesArray.includes('light-estimation')) { + optionalFeaturesArray.push('light-estimation'); + this.el.setAttribute('webxr', webxrData); + } + this.el.addEventListener('enter-vr', function () { + if (!self.el.is('ar-mode')) { + return; + } + var renderer = self.el.renderer; + var session = renderer.xr.getSession(); + if (session.requestLightProbe) { + self.startLightProbe(); + } + }); + this.el.addEventListener('exit-vr', function () { + if (self.xrLightProbe) { + self.stopLightProbe(); + } + }); + this.el.object3D.environment = this.cubeRenderTarget.texture; + }, + stopLightProbe: function () { + this.xrLightProbe = null; + if (this.probeLight) { + this.probeLight.components.light.light.intensity = 0; + } + this.needsVREnvironmentUpdate = true; + this.el.object3D.environment = this.cubeRenderTarget.texture; + }, + startLightProbe: function () { + this.needsLightProbeUpdate = true; + }, + setupLightProbe: function () { + var renderer = this.el.renderer; + var xrSession = renderer.xr.getSession(); + var self = this; + var gl = renderer.getContext(); + if (!this.probeLight) { + var probeLight = document.createElement('a-light'); + probeLight.setAttribute('type', 'probe'); + probeLight.setAttribute('intensity', 0); + this.el.appendChild(probeLight); + this.probeLight = probeLight; + } + + // Ensure that we have any extensions needed to use the preferred cube map format. + switch (xrSession.preferredReflectionFormat) { + case 'srgba8': + gl.getExtension('EXT_sRGB'); + break; + case 'rgba16f': + gl.getExtension('OES_texture_half_float'); + break; + } + this.glBinding = new XRWebGLBinding(xrSession, gl); + gl.getExtension('EXT_sRGB'); + gl.getExtension('OES_texture_half_float'); + xrSession.requestLightProbe().then(function (lightProbe) { + self.xrLightProbe = lightProbe; + lightProbe.addEventListener('reflectionchange', self.updateXRCubeMap.bind(self)); + }).catch(function (err) { + console.warn('Lighting estimation not supported: ' + err.message); + console.warn('Are you missing: webxr="optionalFeatures: light-estimation;" from ?'); + }); + }, + updateXRCubeMap: function () { + // Update Cube Map, cubeMap maybe some unavailable on some hardware + var renderer = this.el.renderer; + var cubeMap = this.glBinding.getReflectionCubeMap(this.xrLightProbe); + if (cubeMap) { + var rendererProps = renderer.properties.get(this.lightingEstimationTexture); + rendererProps.__webglTexture = cubeMap; + this.lightingEstimationTexture.needsPMREMUpdate = true; + this.el.object3D.environment = this.lightingEstimationTexture; + } + }, + tick: function () { + var scene = this.el.object3D; + var renderer = this.el.renderer; + var frame = this.el.frame; + if (frame && this.xrLightProbe) { + // light estimate may not yet be available, it takes a few frames to start working + var estimate = frame.getLightEstimate(this.xrLightProbe); + if (estimate) { + updateLights(estimate, this.probeLight.components.light.light, this.data.directionalLight && this.data.directionalLight.components.light.light, this.data.directionalLight && this.data.directionalLight.object3D.position); + } + } + if (this.needsVREnvironmentUpdate) { + scene.environment = null; + this.needsVREnvironmentUpdate = false; + this.cubeCamera.position.set(0, 1.6, 0); + this.cubeCamera.update(renderer, scene); + scene.environment = this.cubeRenderTarget.texture; + } + if (this.needsLightProbeUpdate && frame) { + // wait until the XR Session has started before trying to make + // the light probe + this.setupLightProbe(); + this.needsLightProbeUpdate = false; + } + }, + remove: function () { + this.el.object3D.environment = null; + if (this.probeLight) { + this.el.removeChild(this.probeLight); + } + } +}); + +/***/ }), + +/***/ "./src/components/scene/screenshot.js": +/*!********************************************!*\ + !*** ./src/components/scene/screenshot.js ***! + \********************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +/* global ImageData, URL */ +var registerComponent = (__webpack_require__(/*! ../../core/component */ "./src/core/component.js").registerComponent); +var THREE = __webpack_require__(/*! ../../lib/three */ "./src/lib/three.js"); +var VERTEX_SHADER = ['attribute vec3 position;', 'attribute vec2 uv;', 'uniform mat4 projectionMatrix;', 'uniform mat4 modelViewMatrix;', 'varying vec2 vUv;', 'void main() {', ' vUv = vec2( 1.- uv.x, uv.y );', ' gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );', '}'].join('\n'); +var FRAGMENT_SHADER = ['precision mediump float;', 'uniform samplerCube map;', 'varying vec2 vUv;', '#define M_PI 3.141592653589793238462643383279', 'void main() {', ' vec2 uv = vUv;', ' float longitude = uv.x * 2. * M_PI - M_PI + M_PI / 2.;', ' float latitude = uv.y * M_PI;', ' vec3 dir = vec3(', ' - sin( longitude ) * sin( latitude ),', ' cos( latitude ),', ' - cos( longitude ) * sin( latitude )', ' );', ' normalize( dir );', ' gl_FragColor = vec4( textureCube( map, dir ).rgb, 1.0 );', '}'].join('\n'); + +/** + * Component to take screenshots of the scene using a keboard shortcut (alt+s). + * It can be configured to either take 360° captures (`equirectangular`) + * or regular screenshots (`projection`) + * + * This is based on https://github.com/spite/THREE.CubemapToEquirectangular + * To capture an equirectangular projection of the scene a THREE.CubeCamera is used + * The cube map produced by the CubeCamera is projected on a quad and then rendered to + * WebGLRenderTarget with an ortographic camera. + */ +module.exports.Component = registerComponent('screenshot', { + schema: { + width: { + default: 4096 + }, + height: { + default: 2048 + }, + camera: { + type: 'selector' + } + }, + setup: function () { + var el = this.el; + if (this.canvas) { + return; + } + var gl = el.renderer.getContext(); + if (!gl) { + return; + } + this.cubeMapSize = gl.getParameter(gl.MAX_CUBE_MAP_TEXTURE_SIZE); + this.material = new THREE.RawShaderMaterial({ + uniforms: { + map: { + type: 't', + value: null + } + }, + vertexShader: VERTEX_SHADER, + fragmentShader: FRAGMENT_SHADER, + side: THREE.DoubleSide + }); + this.quad = new THREE.Mesh(new THREE.PlaneGeometry(1, 1), this.material); + this.quad.visible = false; + this.camera = new THREE.OrthographicCamera(-1 / 2, 1 / 2, 1 / 2, -1 / 2, -10000, 10000); + this.canvas = document.createElement('canvas'); + this.ctx = this.canvas.getContext('2d'); + el.object3D.add(this.quad); + this.onKeyDown = this.onKeyDown.bind(this); + }, + getRenderTarget: function (width, height) { + return new THREE.WebGLRenderTarget(width, height, { + colorSpace: this.el.sceneEl.renderer.outputColorSpace, + minFilter: THREE.LinearFilter, + magFilter: THREE.LinearFilter, + wrapS: THREE.ClampToEdgeWrapping, + wrapT: THREE.ClampToEdgeWrapping, + format: THREE.RGBAFormat, + type: THREE.UnsignedByteType + }); + }, + resize: function (width, height) { + // Resize quad. + this.quad.scale.set(width, height, 1); + + // Resize camera. + this.camera.left = -1 * width / 2; + this.camera.right = width / 2; + this.camera.top = height / 2; + this.camera.bottom = -1 * height / 2; + this.camera.updateProjectionMatrix(); + + // Resize canvas. + this.canvas.width = width; + this.canvas.height = height; + }, + play: function () { + window.addEventListener('keydown', this.onKeyDown); + }, + /** + * + + s = Regular screenshot. + * + + + s = Equirectangular screenshot. + */ + onKeyDown: function (evt) { + var shortcutPressed = evt.keyCode === 83 && evt.ctrlKey && evt.altKey; + if (!this.data || !shortcutPressed) { + return; + } + var projection = evt.shiftKey ? 'equirectangular' : 'perspective'; + this.capture(projection); + }, + /** + * Capture a screenshot of the scene. + * + * @param {string} projection - Screenshot projection (equirectangular or perspective). + */ + setCapture: function (projection) { + var el = this.el; + var size; + var camera; + var cubeCamera; + var cubeRenderTarget; + // Configure camera. + if (projection === 'perspective') { + // Quad is only used in equirectangular mode. Hide it in this case. + this.quad.visible = false; + // Use scene camera. + camera = this.data.camera && this.data.camera.components.camera.camera || el.camera; + size = { + width: this.data.width, + height: this.data.height + }; + } else { + // Use ortho camera. + camera = this.camera; + cubeRenderTarget = new THREE.WebGLCubeRenderTarget(Math.min(this.cubeMapSize, 2048), { + format: THREE.RGBFormat, + generateMipmaps: true, + minFilter: THREE.LinearMipmapLinearFilter, + colorSpace: THREE.SRGBColorSpace + }); + // Create cube camera and copy position from scene camera. + cubeCamera = new THREE.CubeCamera(el.camera.near, el.camera.far, cubeRenderTarget); + // Copy camera position into cube camera; + el.camera.getWorldPosition(cubeCamera.position); + el.camera.getWorldQuaternion(cubeCamera.quaternion); + // Render scene with cube camera. + cubeCamera.update(el.renderer, el.object3D); + this.quad.material.uniforms.map.value = cubeCamera.renderTarget.texture; + size = { + width: this.data.width, + height: this.data.height + }; + // Use quad to project image taken by the cube camera. + this.quad.visible = true; + } + return { + camera: camera, + size: size, + projection: projection + }; + }, + /** + * Maintained for backwards compatibility. + */ + capture: function (projection) { + var isVREnabled = this.el.renderer.xr.enabled; + var renderer = this.el.renderer; + var params; + this.setup(); + // Disable VR. + renderer.xr.enabled = false; + params = this.setCapture(projection); + this.renderCapture(params.camera, params.size, params.projection); + // Trigger file download. + this.saveCapture(); + // Restore VR. + renderer.xr.enabled = isVREnabled; + }, + /** + * Return canvas instead of triggering download (e.g., for uploading blob to server). + */ + getCanvas: function (projection) { + var isVREnabled = this.el.renderer.xr.enabled; + var renderer = this.el.renderer; + // Disable VR. + var params = this.setCapture(projection); + renderer.xr.enabled = false; + this.renderCapture(params.camera, params.size, params.projection); + // Restore VR. + renderer.xr.enabled = isVREnabled; + return this.canvas; + }, + renderCapture: function (camera, size, projection) { + var autoClear = this.el.renderer.autoClear; + var el = this.el; + var imageData; + var output; + var pixels; + var renderer = el.renderer; + // Create rendering target and buffer to store the read pixels. + output = this.getRenderTarget(size.width, size.height); + pixels = new Uint8Array(4 * size.width * size.height); + // Resize quad, camera, and canvas. + this.resize(size.width, size.height); + // Render scene to render target. + renderer.autoClear = true; + renderer.clear(); + renderer.setRenderTarget(output); + renderer.render(el.object3D, camera); + renderer.autoClear = autoClear; + // Read image pizels back. + renderer.readRenderTargetPixels(output, 0, 0, size.width, size.height, pixels); + renderer.setRenderTarget(null); + if (projection === 'perspective') { + pixels = this.flipPixelsVertically(pixels, size.width, size.height); + } + imageData = new ImageData(new Uint8ClampedArray(pixels), size.width, size.height); + // Hide quad after projecting the image. + this.quad.visible = false; + // Copy pixels into canvas. + this.ctx.putImageData(imageData, 0, 0); + }, + flipPixelsVertically: function (pixels, width, height) { + var flippedPixels = pixels.slice(0); + for (var x = 0; x < width; ++x) { + for (var y = 0; y < height; ++y) { + flippedPixels[x * 4 + y * width * 4] = pixels[x * 4 + (height - y) * width * 4]; + flippedPixels[x * 4 + 1 + y * width * 4] = pixels[x * 4 + 1 + (height - y) * width * 4]; + flippedPixels[x * 4 + 2 + y * width * 4] = pixels[x * 4 + 2 + (height - y) * width * 4]; + flippedPixels[x * 4 + 3 + y * width * 4] = pixels[x * 4 + 3 + (height - y) * width * 4]; + } + } + return flippedPixels; + }, + /** + * Download capture to file. + */ + saveCapture: function () { + this.canvas.toBlob(function (blob) { + var fileName = 'screenshot-' + document.title.toLowerCase() + '-' + Date.now() + '.png'; + var linkEl = document.createElement('a'); + var url = URL.createObjectURL(blob); + linkEl.href = url; + linkEl.setAttribute('download', fileName); + linkEl.innerHTML = 'downloading...'; + linkEl.style.display = 'none'; + document.body.appendChild(linkEl); + setTimeout(function () { + linkEl.click(); + document.body.removeChild(linkEl); + }, 1); + }, 'image/png'); + } +}); + +/***/ }), + +/***/ "./src/components/scene/stats.js": +/*!***************************************!*\ + !*** ./src/components/scene/stats.js ***! + \***************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var registerComponent = (__webpack_require__(/*! ../../core/component */ "./src/core/component.js").registerComponent); +var RStats = __webpack_require__(/*! ../../../vendor/rStats */ "./vendor/rStats.js"); +var utils = __webpack_require__(/*! ../../utils */ "./src/utils/index.js"); +__webpack_require__(/*! ../../../vendor/rStats.extras */ "./vendor/rStats.extras.js"); +__webpack_require__(/*! ../../lib/rStatsAframe */ "./src/lib/rStatsAframe.js"); +var AFrameStats = window.aframeStats; +var bind = utils.bind; +var HIDDEN_CLASS = 'a-hidden'; +var ThreeStats = window.threeStats; + +/** + * Stats appended to document.body by RStats. + */ +module.exports.Component = registerComponent('stats', { + schema: { + default: true + }, + init: function () { + var scene = this.el; + if (utils.getUrlParameter('stats') === 'false') { + return; + } + this.stats = createStats(scene); + this.statsEl = document.querySelector('.rs-base'); + this.hideBound = bind(this.hide, this); + this.showBound = bind(this.show, this); + scene.addEventListener('enter-vr', this.hideBound); + scene.addEventListener('exit-vr', this.showBound); + }, + update: function () { + if (!this.stats) { + return; + } + return !this.data ? this.hide() : this.show(); + }, + remove: function () { + this.el.removeEventListener('enter-vr', this.hideBound); + this.el.removeEventListener('exit-vr', this.showBound); + if (!this.statsEl) { + return; + } // Scene detached. + this.statsEl.parentNode.removeChild(this.statsEl); + }, + tick: function () { + var stats = this.stats; + if (!stats) { + return; + } + stats('rAF').tick(); + stats('FPS').frame(); + stats().update(); + }, + hide: function () { + this.statsEl.classList.add(HIDDEN_CLASS); + }, + show: function () { + this.statsEl.classList.remove(HIDDEN_CLASS); + } +}); +function createStats(scene) { + var threeStats = new ThreeStats(scene.renderer); + var aframeStats = new AFrameStats(scene); + var plugins = scene.isMobile ? [] : [threeStats, aframeStats]; + return new RStats({ + css: [], + // Our stylesheet is injected from `src/index.js`. + values: { + fps: { + caption: 'fps', + below: 30 + } + }, + groups: [{ + caption: 'Framerate', + values: ['fps', 'raf'] + }], + plugins: plugins + }); +} + +/***/ }), + +/***/ "./src/components/scene/xr-mode-ui.js": +/*!********************************************!*\ + !*** ./src/components/scene/xr-mode-ui.js ***! + \********************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var registerComponent = (__webpack_require__(/*! ../../core/component */ "./src/core/component.js").registerComponent); +var constants = __webpack_require__(/*! ../../constants/ */ "./src/constants/index.js"); +var utils = __webpack_require__(/*! ../../utils/ */ "./src/utils/index.js"); +var bind = utils.bind; +var ENTER_VR_CLASS = 'a-enter-vr'; +var ENTER_AR_CLASS = 'a-enter-ar'; +var ENTER_VR_BTN_CLASS = 'a-enter-vr-button'; +var ENTER_AR_BTN_CLASS = 'a-enter-ar-button'; +var HIDDEN_CLASS = 'a-hidden'; +var ORIENTATION_MODAL_CLASS = 'a-orientation-modal'; + +/** + * UI for Aentering VR mode. + */ +module.exports.Component = registerComponent('xr-mode-ui', { + dependencies: ['canvas'], + schema: { + enabled: { + default: true + }, + cardboardModeEnabled: { + default: false + }, + enterVRButton: { + default: '' + }, + enterVREnabled: { + default: true + }, + enterARButton: { + default: '' + }, + enterAREnabled: { + default: true + }, + XRMode: { + default: 'vr', + oneOf: ['vr', 'ar', 'xr'] + } + }, + init: function () { + var self = this; + var sceneEl = this.el; + if (utils.getUrlParameter('ui') === 'false') { + return; + } + this.insideLoader = false; + this.enterVREl = null; + this.enterAREl = null; + this.orientationModalEl = null; + this.bindMethods(); + + // Hide/show VR UI when entering/exiting VR mode. + sceneEl.addEventListener('enter-vr', this.updateEnterInterfaces); + sceneEl.addEventListener('exit-vr', this.updateEnterInterfaces); + sceneEl.addEventListener('update-vr-devices', this.updateEnterInterfaces); + window.addEventListener('message', function (event) { + if (event.data.type === 'loaderReady') { + self.insideLoader = true; + self.remove(); + } + }); + + // Modal that tells the user to change orientation if in portrait. + window.addEventListener('orientationchange', this.toggleOrientationModalIfNeeded); + }, + bindMethods: function () { + this.onEnterVRButtonClick = bind(this.onEnterVRButtonClick, this); + this.onEnterARButtonClick = bind(this.onEnterARButtonClick, this); + this.onModalClick = bind(this.onModalClick, this); + this.toggleOrientationModalIfNeeded = bind(this.toggleOrientationModalIfNeeded, this); + this.updateEnterInterfaces = bind(this.updateEnterInterfaces, this); + }, + /** + * Exit VR when modal clicked. + */ + onModalClick: function () { + this.el.exitVR(); + }, + /** + * Enter VR when clicked. + */ + onEnterVRButtonClick: function () { + this.el.enterVR(); + }, + /** + * Enter AR when clicked. + */ + onEnterARButtonClick: function () { + this.el.enterAR(); + }, + update: function () { + var data = this.data; + var sceneEl = this.el; + if (!data.enabled || this.insideLoader || utils.getUrlParameter('ui') === 'false') { + return this.remove(); + } + if (this.enterVREl || this.enterAREl || this.orientationModalEl) { + return; + } + + // Add UI if enabled and not already present. + if (!this.enterVREl && data.enterVREnabled && (data.XRMode === 'xr' || data.XRMode === 'vr')) { + if (data.enterVRButton) { + // Custom button. + this.enterVREl = document.querySelector(data.enterVRButton); + this.enterVREl.addEventListener('click', this.onEnterVRButtonClick); + } else { + this.enterVREl = createEnterVRButton(this.onEnterVRButtonClick); + sceneEl.appendChild(this.enterVREl); + } + } + if (!this.enterAREl && data.enterAREnabled && (data.XRMode === 'xr' || data.XRMode === 'ar')) { + if (data.enterARButton) { + // Custom button. + this.enterAREl = document.querySelector(data.enterARButton); + this.enterAREl.addEventListener('click', this.onEnterARButtonClick); + } else { + this.enterAREl = createEnterARButton(this.onEnterARButtonClick, data.XRMode === 'xr'); + sceneEl.appendChild(this.enterAREl); + } + } + this.orientationModalEl = createOrientationModal(this.onModalClick); + sceneEl.appendChild(this.orientationModalEl); + this.updateEnterInterfaces(); + }, + remove: function () { + [this.enterVREl, this.enterAREl, this.orientationModalEl].forEach(function (uiElement) { + if (uiElement && uiElement.parentNode) { + uiElement.parentNode.removeChild(uiElement); + } + }); + this.enterVREl = undefined; + this.enterAREl = undefined; + this.orientationModalEl = undefined; + }, + updateEnterInterfaces: function () { + this.toggleEnterVRButtonIfNeeded(); + this.toggleEnterARButtonIfNeeded(); + this.toggleOrientationModalIfNeeded(); + }, + toggleEnterVRButtonIfNeeded: function () { + var sceneEl = this.el; + if (!this.enterVREl) { + return; + } + if (sceneEl.is('vr-mode') || (sceneEl.isMobile || utils.device.isMobileDeviceRequestingDesktopSite()) && !this.data.cardboardModeEnabled && !utils.device.checkVRSupport()) { + this.enterVREl.classList.add(HIDDEN_CLASS); + } else { + if (!utils.device.checkVRSupport()) { + this.enterVREl.classList.add('fullscreen'); + } + this.enterVREl.classList.remove(HIDDEN_CLASS); + sceneEl.enterVR(false, true); + } + }, + toggleEnterARButtonIfNeeded: function () { + var sceneEl = this.el; + if (!this.enterAREl) { + return; + } + // Hide the button while in a session, or if AR is not supported. + if (sceneEl.is('vr-mode') || !utils.device.checkARSupport()) { + this.enterAREl.classList.add(HIDDEN_CLASS); + } else { + this.enterAREl.classList.remove(HIDDEN_CLASS); + sceneEl.enterVR(true, true); + } + }, + toggleOrientationModalIfNeeded: function () { + var sceneEl = this.el; + var orientationModalEl = this.orientationModalEl; + if (!orientationModalEl || !sceneEl.isMobile) { + return; + } + if (!utils.device.isLandscape() && sceneEl.is('vr-mode')) { + // Show if in VR mode on portrait. + orientationModalEl.classList.remove(HIDDEN_CLASS); + } else { + orientationModalEl.classList.add(HIDDEN_CLASS); + } + } +}); + +/** + * Create a button that when clicked will enter into stereo-rendering mode for VR. + * + * Structure:
+ * + * @param {function} onClick - click event handler + * @returns {Element} Wrapper
. + */ +function createEnterVRButton(onClick) { + var vrButton; + var wrapper; + + // Create elements. + wrapper = document.createElement('div'); + wrapper.classList.add(ENTER_VR_CLASS); + wrapper.setAttribute(constants.AFRAME_INJECTED, ''); + vrButton = document.createElement('button'); + vrButton.className = ENTER_VR_BTN_CLASS; + vrButton.setAttribute('title', 'Enter VR mode with a headset or fullscreen without'); + vrButton.setAttribute(constants.AFRAME_INJECTED, ''); + if (utils.device.isMobile()) { + applyStickyHoverFix(vrButton); + } + // Insert elements. + wrapper.appendChild(vrButton); + vrButton.addEventListener('click', function (evt) { + onClick(); + evt.stopPropagation(); + }); + return wrapper; +} + +/** + * Create a button that when clicked will enter into AR mode + * + * Structure:
+ * + * @param {function} onClick - click event handler + * @returns {Element} Wrapper
. + */ +function createEnterARButton(onClick, xrMode) { + var arButton; + var wrapper; + + // Create elements. + wrapper = document.createElement('div'); + wrapper.classList.add(ENTER_AR_CLASS); + if (xrMode) { + wrapper.classList.add('xr'); + } + wrapper.setAttribute(constants.AFRAME_INJECTED, ''); + arButton = document.createElement('button'); + arButton.className = ENTER_AR_BTN_CLASS; + arButton.setAttribute('title', 'Enter AR mode with a headset or handheld device.'); + arButton.setAttribute(constants.AFRAME_INJECTED, ''); + if (utils.device.isMobile()) { + applyStickyHoverFix(arButton); + } + // Insert elements. + wrapper.appendChild(arButton); + arButton.addEventListener('click', function (evt) { + onClick(); + evt.stopPropagation(); + }); + return wrapper; +} + +/** + * Creates a modal dialog to request the user to switch to landscape orientation. + * + * @param {function} onClick - click event handler + * @returns {Element} Wrapper
. + */ +function createOrientationModal(onClick) { + var modal = document.createElement('div'); + modal.className = ORIENTATION_MODAL_CLASS; + modal.classList.add(HIDDEN_CLASS); + modal.setAttribute(constants.AFRAME_INJECTED, ''); + var exit = document.createElement('button'); + exit.setAttribute(constants.AFRAME_INJECTED, ''); + exit.innerHTML = 'Exit VR'; + + // Exit VR on close. + exit.addEventListener('click', onClick); + modal.appendChild(exit); + return modal; +} + +/** + * CSS hover state is sticky in iOS (as in 12/18/2019) + * They are not removed on mouseleave and this function applies a class + * to resets the style. + * + * @param {function} buttonEl - Button element + */ +function applyStickyHoverFix(buttonEl) { + buttonEl.addEventListener('touchstart', function () { + buttonEl.classList.remove('resethover'); + }); + buttonEl.addEventListener('touchend', function () { + buttonEl.classList.add('resethover'); + }); +} + +/***/ }), + +/***/ "./src/components/shadow.js": +/*!**********************************!*\ + !*** ./src/components/shadow.js ***! + \**********************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var component = __webpack_require__(/*! ../core/component */ "./src/core/component.js"); +var THREE = __webpack_require__(/*! ../lib/three */ "./src/lib/three.js"); +var bind = __webpack_require__(/*! ../utils/bind */ "./src/utils/bind.js"); +var registerComponent = component.registerComponent; + +/** + * Shadow component. + * + * When applied to an entity, that entity's geometry and any descendants will cast or receive + * shadows as specified by the `cast` and `receive` properties. + */ +module.exports.Component = registerComponent('shadow', { + schema: { + cast: { + default: true + }, + receive: { + default: true + } + }, + init: function () { + this.onMeshChanged = bind(this.update, this); + this.el.addEventListener('object3dset', this.onMeshChanged); + this.system.setShadowMapEnabled(true); + }, + update: function () { + var data = this.data; + this.updateDescendants(data.cast, data.receive); + }, + remove: function () { + var el = this.el; + el.removeEventListener('object3dset', this.onMeshChanged); + this.updateDescendants(false, false); + }, + updateDescendants: function (cast, receive) { + var sceneEl = this.el.sceneEl; + this.el.object3D.traverse(function (node) { + if (!(node instanceof THREE.Mesh)) { + return; + } + node.castShadow = cast; + node.receiveShadow = receive; + + // If scene has already rendered, materials must be updated. + if (sceneEl.hasLoaded && node.material) { + var materials = Array.isArray(node.material) ? node.material : [node.material]; + for (var i = 0; i < materials.length; i++) { + materials[i].needsUpdate = true; + } + } + }); + } +}); + +/***/ }), + +/***/ "./src/components/sound.js": +/*!*********************************!*\ + !*** ./src/components/sound.js ***! + \*********************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var registerComponent = (__webpack_require__(/*! ../core/component */ "./src/core/component.js").registerComponent); +var debug = __webpack_require__(/*! ../utils/debug */ "./src/utils/debug.js"); +var THREE = __webpack_require__(/*! ../lib/three */ "./src/lib/three.js"); +var warn = debug('components:sound:warn'); + +/** + * Sound component. + */ +module.exports.Component = registerComponent('sound', { + schema: { + autoplay: { + default: false + }, + distanceModel: { + default: 'inverse', + oneOf: ['linear', 'inverse', 'exponential'] + }, + loop: { + default: false + }, + loopStart: { + default: 0 + }, + loopEnd: { + default: 0 + }, + maxDistance: { + default: 10000 + }, + on: { + default: '' + }, + poolSize: { + default: 1 + }, + positional: { + default: true + }, + refDistance: { + default: 1 + }, + rolloffFactor: { + default: 1 + }, + src: { + type: 'audio' + }, + volume: { + default: 1 + } + }, + multiple: true, + init: function () { + var self = this; + this.listener = null; + this.audioLoader = new THREE.AudioLoader(); + this.pool = new THREE.Group(); + this.loaded = false; + this.mustPlay = false; + + // Don't pass evt because playSound takes a function as parameter. + this.playSoundBound = function () { + self.playSound(); + }; + }, + update: function (oldData) { + var data = this.data; + var i; + var sound; + var srcChanged = data.src !== oldData.src; + + // Create new sound if not yet created or changing `src`. + if (srcChanged) { + if (!data.src) { + return; + } + this.setupSound(); + } + for (i = 0; i < this.pool.children.length; i++) { + sound = this.pool.children[i]; + if (data.positional) { + sound.setDistanceModel(data.distanceModel); + sound.setMaxDistance(data.maxDistance); + sound.setRefDistance(data.refDistance); + sound.setRolloffFactor(data.rolloffFactor); + } + sound.setLoop(data.loop); + sound.setLoopStart(data.loopStart); + + // With a loop start specified without a specified loop end, the end of the loop should be the end of the file + if (data.loopStart !== 0 && data.loopEnd === 0) { + sound.setLoopEnd(sound.buffer.duration); + } else { + sound.setLoopEnd(data.loopEnd); + } + sound.setVolume(data.volume); + sound.isPaused = false; + } + if (data.on !== oldData.on) { + this.updateEventListener(oldData.on); + } + + // All sound values set. Load in `src`. + if (srcChanged) { + var self = this; + this.loaded = false; + this.audioLoader.load(data.src, function (buffer) { + for (i = 0; i < self.pool.children.length; i++) { + sound = self.pool.children[i]; + sound.setBuffer(buffer); + } + self.loaded = true; + + // Remove this key from cache, otherwise we can't play it again + THREE.Cache.remove(data.src); + if (self.data.autoplay || self.mustPlay) { + self.playSound(self.processSound); + } + self.el.emit('sound-loaded', self.evtDetail, false); + }); + } + }, + pause: function () { + this.stopSound(); + this.removeEventListener(); + }, + play: function () { + if (this.data.autoplay) { + this.playSound(); + } + this.updateEventListener(); + }, + remove: function () { + var i; + var sound; + this.removeEventListener(); + if (this.el.getObject3D(this.attrName)) { + this.el.removeObject3D(this.attrName); + } + try { + for (i = 0; i < this.pool.children.length; i++) { + sound = this.pool.children[i]; + sound.disconnect(); + } + } catch (e) { + // disconnect() will throw if it was never connected initially. + warn('Audio source not properly disconnected'); + } + }, + /** + * Update listener attached to the user defined on event. + */ + updateEventListener: function (oldEvt) { + var el = this.el; + if (oldEvt) { + el.removeEventListener(oldEvt, this.playSoundBound); + } + el.addEventListener(this.data.on, this.playSoundBound); + }, + removeEventListener: function () { + this.el.removeEventListener(this.data.on, this.playSoundBound); + }, + /** + * Removes current sound object, creates new sound object, adds to entity. + * + * @returns {object} sound + */ + setupSound: function () { + var el = this.el; + var i; + var sceneEl = el.sceneEl; + var self = this; + var sound; + if (this.pool.children.length > 0) { + this.stopSound(); + el.removeObject3D('sound'); + } + + // Only want one AudioListener. Cache it on the scene. + var listener = this.listener = sceneEl.audioListener || new THREE.AudioListener(); + sceneEl.audioListener = listener; + if (sceneEl.camera) { + sceneEl.camera.add(listener); + } + + // Wait for camera if necessary. + sceneEl.addEventListener('camera-set-active', function (evt) { + evt.detail.cameraEl.getObject3D('camera').add(listener); + }); + + // Create [poolSize] audio instances and attach them to pool + this.pool = new THREE.Group(); + for (i = 0; i < this.data.poolSize; i++) { + sound = this.data.positional ? new THREE.PositionalAudio(listener) : new THREE.Audio(listener); + this.pool.add(sound); + } + el.setObject3D(this.attrName, this.pool); + for (i = 0; i < this.pool.children.length; i++) { + sound = this.pool.children[i]; + sound.onEnded = function () { + this.isPlaying = false; + self.el.emit('sound-ended', self.evtDetail, false); + }; + } + }, + /** + * Pause all the sounds in the pool. + */ + pauseSound: function () { + var i; + var sound; + this.isPlaying = false; + for (i = 0; i < this.pool.children.length; i++) { + sound = this.pool.children[i]; + if (!sound.source || !sound.source.buffer || !sound.isPlaying || sound.isPaused) { + continue; + } + sound.isPaused = true; + sound.pause(); + } + }, + /** + * Look for an unused sound in the pool and play it if found. + */ + playSound: function (processSound) { + var found; + var i; + var sound; + if (!this.loaded) { + warn('Sound not loaded yet. It will be played once it finished loading'); + this.mustPlay = true; + this.processSound = processSound; + return; + } + found = false; + this.isPlaying = true; + for (i = 0; i < this.pool.children.length; i++) { + sound = this.pool.children[i]; + if (!sound.isPlaying && sound.buffer && !found) { + if (processSound) { + processSound(sound); + } + sound.play(); + sound.isPaused = false; + found = true; + continue; + } + } + if (!found) { + warn('All the sounds are playing. If you need to play more sounds simultaneously ' + 'consider increasing the size of pool with the `poolSize` attribute.', this.el); + return; + } + this.mustPlay = false; + this.processSound = undefined; + }, + /** + * Stop all the sounds in the pool. + */ + stopSound: function () { + var i; + var sound; + this.isPlaying = false; + for (i = 0; i < this.pool.children.length; i++) { + sound = this.pool.children[i]; + if (!sound.source || !sound.source.buffer) { + return; + } + sound.stop(); + } + } +}); + +/***/ }), + +/***/ "./src/components/text.js": +/*!********************************!*\ + !*** ./src/components/text.js ***! + \********************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var createTextGeometry = __webpack_require__(/*! three-bmfont-text */ "./node_modules/three-bmfont-text/index.js"); +var loadBMFont = __webpack_require__(/*! load-bmfont */ "./node_modules/load-bmfont/browser.js"); +var registerComponent = (__webpack_require__(/*! ../core/component */ "./src/core/component.js").registerComponent); +var coreShader = __webpack_require__(/*! ../core/shader */ "./src/core/shader.js"); +var THREE = __webpack_require__(/*! ../lib/three */ "./src/lib/three.js"); +var utils = __webpack_require__(/*! ../utils/ */ "./src/utils/index.js"); +var error = utils.debug('components:text:error'); +var shaders = coreShader.shaders; +var warn = utils.debug('components:text:warn'); + +// 1 to match other A-Frame default widths. +var DEFAULT_WIDTH = 1; + +// @bryik set anisotropy to 16. Improves look of large amounts of text when viewed from angle. +var MAX_ANISOTROPY = 16; +var AFRAME_CDN_ROOT = (__webpack_require__(/*! ../constants */ "./src/constants/index.js").AFRAME_CDN_ROOT); +var FONT_BASE_URL = AFRAME_CDN_ROOT + 'fonts/'; +var FONTS = { + aileronsemibold: FONT_BASE_URL + 'Aileron-Semibold.fnt', + dejavu: FONT_BASE_URL + 'DejaVu-sdf.fnt', + exo2bold: FONT_BASE_URL + 'Exo2Bold.fnt', + exo2semibold: FONT_BASE_URL + 'Exo2SemiBold.fnt', + kelsonsans: FONT_BASE_URL + 'KelsonSans.fnt', + monoid: FONT_BASE_URL + 'Monoid.fnt', + mozillavr: FONT_BASE_URL + 'mozillavr.fnt', + roboto: FONT_BASE_URL + 'Roboto-msdf.json', + sourcecodepro: FONT_BASE_URL + 'SourceCodePro.fnt' +}; +var MSDF_FONTS = ['roboto']; +var DEFAULT_FONT = 'roboto'; +module.exports.FONTS = FONTS; +var cache = new PromiseCache(); +var fontWidthFactors = {}; +var textures = {}; + +// Regular expression for detecting a URLs with a protocol prefix. +var protocolRe = /^\w+:/; + +/** + * SDF-based text component. + * Based on https://github.com/Jam3/three-bmfont-text. + * + * All the stock fonts are for the `sdf` registered shader, an improved version of jam3's + * original `sdf` shader. + */ +module.exports.Component = registerComponent('text', { + multiple: true, + schema: { + align: { + type: 'string', + default: 'left', + oneOf: ['left', 'right', 'center'] + }, + alphaTest: { + default: 0.5 + }, + // `anchor` defaults to center to match geometries. + anchor: { + default: 'center', + oneOf: ['left', 'right', 'center', 'align'] + }, + baseline: { + default: 'center', + oneOf: ['top', 'center', 'bottom'] + }, + color: { + type: 'color', + default: '#FFF' + }, + font: { + type: 'string', + default: DEFAULT_FONT + }, + // `fontImage` defaults to the font name as a .png (e.g., mozillavr.fnt -> mozillavr.png). + fontImage: { + type: 'string' + }, + // `height` has no default, will be populated at layout. + height: { + type: 'number' + }, + letterSpacing: { + type: 'number', + default: 0 + }, + // `lineHeight` defaults to font's `lineHeight` value. + lineHeight: { + type: 'number' + }, + // `negate` must be true for fonts generated with older versions of msdfgen (white background). + negate: { + type: 'boolean', + default: true + }, + opacity: { + type: 'number', + default: 1.0 + }, + shader: { + default: 'sdf', + oneOf: shaders + }, + side: { + default: 'front', + oneOf: ['front', 'back', 'double'] + }, + tabSize: { + default: 4 + }, + transparent: { + default: true + }, + value: { + type: 'string' + }, + whiteSpace: { + default: 'normal', + oneOf: ['normal', 'pre', 'nowrap'] + }, + // `width` defaults to geometry width if present, else `DEFAULT_WIDTH`. + width: { + type: 'number' + }, + // `wrapCount` units are about one default font character. Wrap roughly at this number. + wrapCount: { + type: 'number', + default: 40 + }, + // `wrapPixels` will wrap using bmfont pixel units (e.g., dejavu's is 32 pixels). + wrapPixels: { + type: 'number' + }, + // `xOffset` to add padding. + xOffset: { + type: 'number', + default: 0 + }, + // `yOffset` to adjust generated fonts from tools that may have incorrect metrics. + yOffset: { + type: 'number', + default: 0 + }, + // `zOffset` will provide a small z offset to avoid z-fighting. + zOffset: { + type: 'number', + default: 0.001 + } + }, + init: function () { + this.shaderData = {}; + this.geometry = createTextGeometry(); + this.createOrUpdateMaterial(); + this.explicitGeoDimensionsChecked = false; + }, + update: function (oldData) { + var data = this.data; + var font = this.currentFont; + if (textures[data.font]) { + this.texture = textures[data.font]; + } else { + // Create texture per font. + this.texture = textures[data.font] = new THREE.Texture(); + this.texture.anisotropy = MAX_ANISOTROPY; + } + + // Update material. + this.createOrUpdateMaterial(); + + // New font. `updateFont` will later change data and layout. + if (oldData.font !== data.font) { + this.updateFont(); + return; + } + + // Update geometry and layout. + if (font) { + this.updateGeometry(this.geometry, font); + this.updateLayout(); + } + }, + /** + * Clean up geometry, material, texture, mesh, objects. + */ + remove: function () { + this.geometry.dispose(); + this.geometry = null; + this.el.removeObject3D(this.attrName); + this.material.dispose(); + this.material = null; + this.texture.dispose(); + this.texture = null; + if (this.shaderObject) { + delete this.shaderObject; + } + }, + /** + * Update the shader of the material. + */ + createOrUpdateMaterial: function () { + var data = this.data; + var hasChangedShader; + var material = this.material; + var NewShader; + var shaderData = this.shaderData; + var shaderName; + + // Infer shader if using a stock font (or from `-msdf` filename convention). + shaderName = data.shader; + if (MSDF_FONTS.indexOf(data.font) !== -1 || data.font.indexOf('-msdf.') >= 0) { + shaderName = 'msdf'; + } else if (data.font in FONTS && MSDF_FONTS.indexOf(data.font) === -1) { + shaderName = 'sdf'; + } + hasChangedShader = (this.shaderObject && this.shaderObject.name) !== shaderName; + shaderData.alphaTest = data.alphaTest; + shaderData.color = data.color; + shaderData.map = this.texture; + shaderData.opacity = data.opacity; + shaderData.side = parseSide(data.side); + shaderData.transparent = data.transparent; + shaderData.negate = data.negate; + + // Shader has not changed, do an update. + if (!hasChangedShader) { + // Update shader material. + this.shaderObject.update(shaderData); + // Apparently, was not set on `init` nor `update`. + material.transparent = shaderData.transparent; + material.side = shaderData.side; + return; + } + + // Shader has changed. Create a shader material. + NewShader = createShader(this.el, shaderName, shaderData); + this.material = NewShader.material; + this.shaderObject = NewShader.shader; + + // Set new shader material. + this.material.side = shaderData.side; + if (this.mesh) { + this.mesh.material = this.material; + } + }, + /** + * Load font for geometry, load font image for material, and apply. + */ + updateFont: function () { + var data = this.data; + var el = this.el; + var fontSrc; + var geometry = this.geometry; + var self = this; + if (!data.font) { + warn('No font specified. Using the default font.'); + } + + // Make invisible during font swap. + if (this.mesh) { + this.mesh.visible = false; + } + + // Look up font URL to use, and perform cached load. + fontSrc = this.lookupFont(data.font || DEFAULT_FONT) || data.font; + cache.get(fontSrc, function doLoadFont() { + return loadFont(fontSrc, data.yOffset); + }).then(function setFont(font) { + var fontImgSrc; + if (font.pages.length !== 1) { + throw new Error('Currently only single-page bitmap fonts are supported.'); + } + if (!fontWidthFactors[fontSrc]) { + font.widthFactor = fontWidthFactors[font] = computeFontWidthFactor(font); + } + self.currentFont = font; + // Look up font image URL to use, and perform cached load. + fontImgSrc = self.getFontImageSrc(); + cache.get(fontImgSrc, function () { + return loadTexture(fontImgSrc); + }).then(function (image) { + // Make mesh visible and apply font image as texture. + var texture = self.texture; + // The component may have been removed at this point and texture will + // be null. This happens mainly while executing the tests, + // in this case we just return. + if (!texture) return; + texture.image = image; + texture.needsUpdate = true; + textures[data.font] = texture; + self.texture = texture; + self.initMesh(); + self.currentFont = font; + // Update geometry given font metrics. + self.updateGeometry(geometry, font); + self.updateLayout(); + self.mesh.visible = true; + el.emit('textfontset', { + font: data.font, + fontObj: font + }); + }).catch(function (err) { + error(err.message); + error(err.stack); + }); + }).catch(function (err) { + error(err.message); + error(err.stack); + }); + }, + initMesh: function () { + if (this.mesh) { + return; + } + this.mesh = new THREE.Mesh(this.geometry, this.material); + this.el.setObject3D(this.attrName, this.mesh); + }, + getFontImageSrc: function () { + if (this.data.fontImage) { + return this.data.fontImage; + } + var fontSrc = this.lookupFont(this.data.font || DEFAULT_FONT) || this.data.font; + var imageSrc = this.currentFont.pages[0]; + // If the image URL contains a non-HTTP(S) protocol, assume it's an absolute + // path on disk and try to infer the path from the font source instead. + if (imageSrc.match(protocolRe) && imageSrc.indexOf('http') !== 0) { + return fontSrc.replace(/(\.fnt)|(\.json)/, '.png'); + } + return THREE.LoaderUtils.extractUrlBase(fontSrc) + imageSrc; + }, + /** + * Update layout with anchor, alignment, baseline, and considering any meshes. + */ + updateLayout: function () { + var anchor; + var baseline; + var el = this.el; + var data = this.data; + var geometry = this.geometry; + var geometryComponent; + var height; + var layout; + var mesh = this.mesh; + var textRenderWidth; + var textScale; + var width; + var x; + var y; + if (!mesh || !geometry.layout) { + return; + } + + // Determine width to use (defined width, geometry's width, or default width). + geometryComponent = el.getAttribute('geometry'); + width = data.width || geometryComponent && geometryComponent.width || DEFAULT_WIDTH; + + // Determine wrap pixel count. Either specified or by experimental fudge factor. + // Note that experimental factor will never be correct for variable width fonts. + textRenderWidth = computeWidth(data.wrapPixels, data.wrapCount, this.currentFont.widthFactor); + textScale = width / textRenderWidth; + + // Determine height to use. + layout = geometry.layout; + height = textScale * (layout.height + layout.descender); + + // Update geometry dimensions to match text layout if width and height are set to 0. + // For example, scales a plane to fit text. + if (geometryComponent && geometryComponent.primitive === 'plane') { + if (!this.explicitGeoDimensionsChecked) { + this.explicitGeoDimensionsChecked = true; + this.hasExplicitGeoWidth = !!geometryComponent.width; + this.hasExplicitGeoHeight = !!geometryComponent.height; + } + if (!this.hasExplicitGeoWidth) { + el.setAttribute('geometry', 'width', width); + } + if (!this.hasExplicitGeoHeight) { + el.setAttribute('geometry', 'height', height); + } + } + + // Calculate X position to anchor text left, center, or right. + anchor = data.anchor === 'align' ? data.align : data.anchor; + if (anchor === 'left') { + x = 0; + } else if (anchor === 'right') { + x = -1 * layout.width; + } else if (anchor === 'center') { + x = -1 * layout.width / 2; + } else { + throw new TypeError('Invalid text.anchor property value', anchor); + } + + // Calculate Y position to anchor text top, center, or bottom. + baseline = data.baseline; + if (baseline === 'bottom') { + y = 0; + } else if (baseline === 'top') { + y = -1 * layout.height + layout.ascender; + } else if (baseline === 'center') { + y = -1 * layout.height / 2; + } else { + throw new TypeError('Invalid text.baseline property value', baseline); + } + + // Position and scale mesh to apply layout. + mesh.position.x = x * textScale + data.xOffset; + mesh.position.y = y * textScale; + // Place text slightly in front to avoid Z-fighting. + mesh.position.z = data.zOffset; + mesh.scale.set(textScale, -1 * textScale, textScale); + }, + /** + * Grab font from the constant. + * Set as a method for test stubbing purposes. + */ + lookupFont: function (key) { + return FONTS[key]; + }, + /** + * Update the text geometry using `three-bmfont-text.update`. + */ + updateGeometry: function () { + var geometryUpdateBase = {}; + var geometryUpdateData = {}; + var newLineRegex = /\\n/g; + var tabRegex = /\\t/g; + return function (geometry, font) { + var data = this.data; + geometryUpdateData.font = font; + geometryUpdateData.lineHeight = data.lineHeight && isFinite(data.lineHeight) ? data.lineHeight : font.common.lineHeight; + geometryUpdateData.text = data.value.toString().replace(newLineRegex, '\n').replace(tabRegex, '\t'); + geometryUpdateData.width = computeWidth(data.wrapPixels, data.wrapCount, font.widthFactor); + geometry.update(utils.extend(geometryUpdateBase, data, geometryUpdateData)); + }; + }() +}); + +/** + * Due to using negative scale, we return the opposite side specified. + * https://github.com/mrdoob/three.js/pull/12787/ + */ +function parseSide(side) { + switch (side) { + case 'back': + { + return THREE.FrontSide; + } + case 'double': + { + return THREE.DoubleSide; + } + default: + { + return THREE.BackSide; + } + } +} + +/** + * @returns {Promise} + */ +function loadFont(src, yOffset) { + return new Promise(function (resolve, reject) { + loadBMFont(src, function (err, font) { + if (err) { + error('Error loading font', src); + reject(err); + return; + } + + // Fix negative Y offsets for Roboto MSDF font from tool. Experimentally determined. + if (src.indexOf('/Roboto-msdf.json') >= 0) { + yOffset = 30; + } + if (yOffset) { + font.chars.map(function doOffset(ch) { + ch.yoffset += yOffset; + }); + } + resolve(font); + }); + }); +} + +/** + * @returns {Promise} + */ +function loadTexture(src) { + return new Promise(function (resolve, reject) { + new THREE.ImageLoader().load(src, function (image) { + resolve(image); + }, undefined, function () { + error('Error loading font image', src); + reject(null); + }); + }); +} +function createShader(el, shaderName, data) { + var shader; + var shaderObject; + + // Set up Shader. + shaderObject = new shaders[shaderName].Shader(); + shaderObject.el = el; + shaderObject.init(data); + shaderObject.update(data); + + // Get material. + shader = shaderObject.material; + // Apparently, was not set on `init` nor `update`. + shader.transparent = data.transparent; + return { + material: shader, + shader: shaderObject + }; +} + +/** + * Determine wrap pixel count. Either specified or by experimental fudge factor. + * Note that experimental factor will never be correct for variable width fonts. + */ +function computeWidth(wrapPixels, wrapCount, widthFactor) { + return wrapPixels || (0.5 + wrapCount) * widthFactor; +} + +/** + * Compute default font width factor to use. + */ +function computeFontWidthFactor(font) { + var sum = 0; + var digitsum = 0; + var digits = 0; + font.chars.map(function (ch) { + sum += ch.xadvance; + if (ch.id >= 48 && ch.id <= 57) { + digits++; + digitsum += ch.xadvance; + } + }); + return digits ? digitsum / digits : sum / font.chars.length; +} + +/** + * Get or create a promise given a key and promise generator. + * @todo Move to a utility and use in other parts of A-Frame. + */ +function PromiseCache() { + var cache = this.cache = {}; + this.get = function (key, promiseGenerator) { + if (key in cache) { + return cache[key]; + } + cache[key] = promiseGenerator(); + return cache[key]; + }; +} + +/***/ }), + +/***/ "./src/components/tracked-controls-webvr.js": +/*!**************************************************!*\ + !*** ./src/components/tracked-controls-webvr.js ***! + \**************************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var registerComponent = (__webpack_require__(/*! ../core/component */ "./src/core/component.js").registerComponent); +var controllerUtils = __webpack_require__(/*! ../utils/tracked-controls */ "./src/utils/tracked-controls.js"); +var DEFAULT_CAMERA_HEIGHT = (__webpack_require__(/*! ../constants */ "./src/constants/index.js").DEFAULT_CAMERA_HEIGHT); +var THREE = __webpack_require__(/*! ../lib/three */ "./src/lib/three.js"); +var DEFAULT_HANDEDNESS = (__webpack_require__(/*! ../constants */ "./src/constants/index.js").DEFAULT_HANDEDNESS); +// Vector from eyes to elbow (divided by user height). +var EYES_TO_ELBOW = { + x: 0.175, + y: -0.3, + z: -0.03 +}; +// Vector from eyes to elbow (divided by user height). +var FOREARM = { + x: 0, + y: 0, + z: -0.175 +}; + +// Due to unfortunate name collision, add empty touches array to avoid Daydream error. +var EMPTY_DAYDREAM_TOUCHES = { + touches: [] +}; +var EVENTS = { + AXISMOVE: 'axismove', + BUTTONCHANGED: 'buttonchanged', + BUTTONDOWN: 'buttondown', + BUTTONUP: 'buttonup', + TOUCHSTART: 'touchstart', + TOUCHEND: 'touchend' +}; + +/** + * Tracked controls component. + * Wrap the gamepad API for pose and button states. + * Select the appropriate controller and apply pose to the entity. + * Observe button states and emit appropriate events. + * + * @property {number} controller - Index of controller in array returned by Gamepad API. + * Only used if hand property is not set. + * @property {string} id - Selected controller among those returned by Gamepad API. + * @property {number} hand - If multiple controllers found with id, choose the one with the + * given value for hand. If set, we ignore 'controller' property + */ +module.exports.Component = registerComponent('tracked-controls-webvr', { + schema: { + autoHide: { + default: true + }, + controller: { + default: 0 + }, + id: { + type: 'string', + default: '' + }, + hand: { + type: 'string', + default: '' + }, + idPrefix: { + type: 'string', + default: '' + }, + orientationOffset: { + type: 'vec3' + }, + // Arm model parameters when not 6DoF. + armModel: { + default: false + }, + headElement: { + type: 'selector' + } + }, + init: function () { + // Copy variables back to tracked-controls for backwards compatibility. + // Some 3rd components rely on them. + this.axis = this.el.components['tracked-controls'].axis = [0, 0, 0]; + this.buttonStates = this.el.components['tracked-controls'].buttonStates = {}; + this.changedAxes = []; + this.targetControllerNumber = this.data.controller; + this.axisMoveEventDetail = { + axis: this.axis, + changed: this.changedAxes + }; + this.deltaControllerPosition = new THREE.Vector3(); + this.controllerQuaternion = new THREE.Quaternion(); + this.controllerEuler = new THREE.Euler(); + this.updateGamepad(); + this.buttonEventDetails = {}; + }, + tick: function (time, delta) { + var mesh = this.el.getObject3D('mesh'); + // Update mesh animations. + if (mesh && mesh.update) { + mesh.update(delta / 1000); + } + this.updateGamepad(); + this.updatePose(); + this.updateButtons(); + }, + /** + * Return default user height to use for non-6DOF arm model. + */ + defaultUserHeight: function () { + return DEFAULT_CAMERA_HEIGHT; + }, + /** + * Return head element to use for non-6DOF arm model. + */ + getHeadElement: function () { + return this.data.headElement || this.el.sceneEl.camera.el; + }, + /** + * Handle update controller match criteria (such as `id`, `idPrefix`, `hand`, `controller`) + */ + updateGamepad: function () { + var data = this.data; + var controller = controllerUtils.findMatchingControllerWebVR(this.system.controllers, data.id, data.idPrefix, data.hand, data.controller); + this.controller = controller; + // Legacy handle to the controller for old components. + this.el.components['tracked-controls'].controller = controller; + if (this.data.autoHide) { + this.el.object3D.visible = !!this.controller; + } + }, + /** + * Applies an artificial arm model to simulate elbow to wrist positioning + * based on the orientation of the controller. + * + * @param {object} controllerPosition - Existing vector to update with controller position. + */ + applyArmModel: function (controllerPosition) { + // Use controllerPosition and deltaControllerPosition to avoid creating variables. + var controller = this.controller; + var controllerEuler = this.controllerEuler; + var controllerQuaternion = this.controllerQuaternion; + var deltaControllerPosition = this.deltaControllerPosition; + var hand; + var headEl; + var headObject3D; + var pose; + var userHeight; + headEl = this.getHeadElement(); + headObject3D = headEl.object3D; + userHeight = this.defaultUserHeight(); + pose = controller.pose; + hand = (controller ? controller.hand : undefined) || DEFAULT_HANDEDNESS; + + // Use camera position as head position. + controllerPosition.copy(headObject3D.position); + // Set offset for degenerate "arm model" to elbow. + deltaControllerPosition.set(EYES_TO_ELBOW.x * (hand === 'left' ? -1 : hand === 'right' ? 1 : 0), EYES_TO_ELBOW.y, + // Lower than our eyes. + EYES_TO_ELBOW.z); // Slightly out in front. + // Scale offset by user height. + deltaControllerPosition.multiplyScalar(userHeight); + // Apply camera Y rotation (not X or Z, so you can look down at your hand). + deltaControllerPosition.applyAxisAngle(headObject3D.up, headObject3D.rotation.y); + // Apply rotated offset to position. + controllerPosition.add(deltaControllerPosition); + + // Set offset for degenerate "arm model" forearm. Forearm sticking out from elbow. + deltaControllerPosition.set(FOREARM.x, FOREARM.y, FOREARM.z); + // Scale offset by user height. + deltaControllerPosition.multiplyScalar(userHeight); + // Apply controller X/Y rotation (tilting up/down/left/right is usually moving the arm). + if (pose.orientation) { + controllerQuaternion.fromArray(pose.orientation); + } else { + controllerQuaternion.copy(headObject3D.quaternion); + } + controllerEuler.setFromQuaternion(controllerQuaternion); + controllerEuler.set(controllerEuler.x, controllerEuler.y, 0); + deltaControllerPosition.applyEuler(controllerEuler); + // Apply rotated offset to position. + controllerPosition.add(deltaControllerPosition); + }, + /** + * Read pose from controller (from Gamepad API), apply transforms, apply to entity. + */ + updatePose: function () { + var controller = this.controller; + var data = this.data; + var object3D = this.el.object3D; + var pose; + var vrDisplay = this.system.vrDisplay; + var standingMatrix; + if (!controller) { + return; + } + + // Compose pose from Gamepad. + pose = controller.pose; + if (pose.position) { + object3D.position.fromArray(pose.position); + } else { + // Controller not 6DOF, apply arm model. + if (data.armModel) { + this.applyArmModel(object3D.position); + } + } + if (pose.orientation) { + object3D.quaternion.fromArray(pose.orientation); + } + + // Apply transforms, if 6DOF and in VR. + if (vrDisplay && pose.position) { + standingMatrix = this.el.sceneEl.renderer.xr.getStandingMatrix(); + object3D.matrix.compose(object3D.position, object3D.quaternion, object3D.scale); + object3D.matrix.multiplyMatrices(standingMatrix, object3D.matrix); + object3D.matrix.decompose(object3D.position, object3D.quaternion, object3D.scale); + } + object3D.rotateX(this.data.orientationOffset.x * THREE.MathUtils.DEG2RAD); + object3D.rotateY(this.data.orientationOffset.y * THREE.MathUtils.DEG2RAD); + object3D.rotateZ(this.data.orientationOffset.z * THREE.MathUtils.DEG2RAD); + }, + /** + * Handle button changes including axes, presses, touches, values. + */ + updateButtons: function () { + var buttonState; + var controller = this.controller; + var id; + if (!controller) { + return; + } + + // Check every button. + for (id = 0; id < controller.buttons.length; ++id) { + // Initialize button state. + if (!this.buttonStates[id]) { + this.buttonStates[id] = { + pressed: false, + touched: false, + value: 0 + }; + } + if (!this.buttonEventDetails[id]) { + this.buttonEventDetails[id] = { + id: id, + state: this.buttonStates[id] + }; + } + buttonState = controller.buttons[id]; + this.handleButton(id, buttonState); + } + // Check axes. + this.handleAxes(); + }, + /** + * Handle presses and touches for a single button. + * + * @param {number} id - Index of button in Gamepad button array. + * @param {number} buttonState - Value of button state from 0 to 1. + * @returns {boolean} Whether button has changed in any way. + */ + handleButton: function (id, buttonState) { + var changed; + changed = this.handlePress(id, buttonState) | this.handleTouch(id, buttonState) | this.handleValue(id, buttonState); + if (!changed) { + return false; + } + this.el.emit(EVENTS.BUTTONCHANGED, this.buttonEventDetails[id], false); + return true; + }, + /** + * An axis is an array of values from -1 (up, left) to 1 (down, right). + * Compare each component of the axis to the previous value to determine change. + * + * @returns {boolean} Whether axes changed. + */ + handleAxes: function () { + var changed = false; + var controllerAxes = this.controller.axes; + var i; + var previousAxis = this.axis; + var changedAxes = this.changedAxes; + + // Check if axis changed. + this.changedAxes.splice(0, this.changedAxes.length); + for (i = 0; i < controllerAxes.length; ++i) { + changedAxes.push(previousAxis[i] !== controllerAxes[i]); + if (changedAxes[i]) { + changed = true; + } + } + if (!changed) { + return false; + } + this.axis.splice(0, this.axis.length); + for (i = 0; i < controllerAxes.length; i++) { + this.axis.push(controllerAxes[i]); + } + this.el.emit(EVENTS.AXISMOVE, this.axisMoveEventDetail, false); + return true; + }, + /** + * Determine whether a button press has occured and emit events as appropriate. + * + * @param {string} id - ID of the button to check. + * @param {object} buttonState - State of the button to check. + * @returns {boolean} Whether button press state changed. + */ + handlePress: function (id, buttonState) { + var evtName; + var previousButtonState = this.buttonStates[id]; + + // Not changed. + if (buttonState.pressed === previousButtonState.pressed) { + return false; + } + evtName = buttonState.pressed ? EVENTS.BUTTONDOWN : EVENTS.BUTTONUP; + this.el.emit(evtName, this.buttonEventDetails[id], false); + previousButtonState.pressed = buttonState.pressed; + return true; + }, + /** + * Determine whether a button touch has occured and emit events as appropriate. + * + * @param {string} id - ID of the button to check. + * @param {object} buttonState - State of the button to check. + * @returns {boolean} Whether button touch state changed. + */ + handleTouch: function (id, buttonState) { + var evtName; + var previousButtonState = this.buttonStates[id]; + + // Not changed. + if (buttonState.touched === previousButtonState.touched) { + return false; + } + evtName = buttonState.touched ? EVENTS.TOUCHSTART : EVENTS.TOUCHEND; + this.el.emit(evtName, this.buttonEventDetails[id], false, EMPTY_DAYDREAM_TOUCHES); + previousButtonState.touched = buttonState.touched; + return true; + }, + /** + * Determine whether a button value has changed. + * + * @param {string} id - Id of the button to check. + * @param {object} buttonState - State of the button to check. + * @returns {boolean} Whether button value changed. + */ + handleValue: function (id, buttonState) { + var previousButtonState = this.buttonStates[id]; + + // Not changed. + if (buttonState.value === previousButtonState.value) { + return false; + } + previousButtonState.value = buttonState.value; + return true; + } +}); + +/***/ }), + +/***/ "./src/components/tracked-controls-webxr.js": +/*!**************************************************!*\ + !*** ./src/components/tracked-controls-webxr.js ***! + \**************************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var controllerUtils = __webpack_require__(/*! ../utils/tracked-controls */ "./src/utils/tracked-controls.js"); +var registerComponent = (__webpack_require__(/*! ../core/component */ "./src/core/component.js").registerComponent); +var EVENTS = { + AXISMOVE: 'axismove', + BUTTONCHANGED: 'buttonchanged', + BUTTONDOWN: 'buttondown', + BUTTONUP: 'buttonup', + TOUCHSTART: 'touchstart', + TOUCHEND: 'touchend' +}; +module.exports.Component = registerComponent('tracked-controls-webxr', { + schema: { + id: { + type: 'string', + default: '' + }, + hand: { + type: 'string', + default: '' + }, + handTrackingEnabled: { + default: false + }, + index: { + type: 'int', + default: -1 + }, + iterateControllerProfiles: { + default: false + }, + space: { + type: 'string', + oneOf: ['targetRaySpace', 'gripSpace'], + default: 'gripSpace' + } + }, + init: function () { + this.updateController = this.updateController.bind(this); + this.buttonEventDetails = {}; + this.buttonStates = this.el.components['tracked-controls'].buttonStates = {}; + this.axis = this.el.components['tracked-controls'].axis = [0, 0, 0]; + this.changedAxes = []; + this.axisMoveEventDetail = { + axis: this.axis, + changed: this.changedAxes + }; + }, + update: function () { + this.updateController(); + }, + play: function () { + var sceneEl = this.el.sceneEl; + this.updateController(); + sceneEl.addEventListener('controllersupdated', this.updateController); + }, + pause: function () { + var sceneEl = this.el.sceneEl; + sceneEl.removeEventListener('controllersupdated', this.updateController); + }, + isControllerPresent: function (evt) { + if (!this.controller || this.controller.gamepad) { + return false; + } + if (evt.inputSource.handedness !== 'none' && evt.inputSource.handedness !== this.data.hand) { + return false; + } + return true; + }, + /** + * Handle update controller match criteria (such as `id`, `idPrefix`, `hand`, `controller`) + */ + updateController: function () { + this.controller = controllerUtils.findMatchingControllerWebXR(this.system.controllers, this.data.id, this.data.hand, this.data.index, this.data.iterateControllerProfiles, this.data.handTrackingEnabled); + // Legacy handle to the controller for old components. + this.el.components['tracked-controls'].controller = this.controller; + if (this.data.autoHide) { + this.el.object3D.visible = !!this.controller; + } + }, + tick: function () { + var sceneEl = this.el.sceneEl; + var controller = this.controller; + var frame = sceneEl.frame; + if (!controller || !sceneEl.frame || !this.system.referenceSpace) { + return; + } + if (!controller.hand) { + this.pose = frame.getPose(controller[this.data.space], this.system.referenceSpace); + this.updatePose(); + this.updateButtons(); + } + }, + updatePose: function () { + var object3D = this.el.object3D; + var pose = this.pose; + if (!pose) { + return; + } + object3D.matrix.elements = pose.transform.matrix; + object3D.matrix.decompose(object3D.position, object3D.rotation, object3D.scale); + }, + /** + * Handle button changes including axes, presses, touches, values. + */ + updateButtons: function () { + var buttonState; + var id; + var controller = this.controller; + var gamepad; + if (!controller || !controller.gamepad) { + return; + } + gamepad = controller.gamepad; + // Check every button. + for (id = 0; id < gamepad.buttons.length; ++id) { + // Initialize button state. + if (!this.buttonStates[id]) { + this.buttonStates[id] = { + pressed: false, + touched: false, + value: 0 + }; + } + if (!this.buttonEventDetails[id]) { + this.buttonEventDetails[id] = { + id: id, + state: this.buttonStates[id] + }; + } + buttonState = gamepad.buttons[id]; + this.handleButton(id, buttonState); + } + // Check axes. + this.handleAxes(); + }, + /** + * Handle presses and touches for a single button. + * + * @param {number} id - Index of button in Gamepad button array. + * @param {number} buttonState - Value of button state from 0 to 1. + * @returns {boolean} Whether button has changed in any way. + */ + handleButton: function (id, buttonState) { + var changed; + changed = this.handlePress(id, buttonState) | this.handleTouch(id, buttonState) | this.handleValue(id, buttonState); + if (!changed) { + return false; + } + this.el.emit(EVENTS.BUTTONCHANGED, this.buttonEventDetails[id], false); + return true; + }, + /** + * An axis is an array of values from -1 (up, left) to 1 (down, right). + * Compare each component of the axis to the previous value to determine change. + * + * @returns {boolean} Whether axes changed. + */ + handleAxes: function () { + var changed = false; + var controllerAxes = this.controller.gamepad.axes; + var i; + var previousAxis = this.axis; + var changedAxes = this.changedAxes; + + // Check if axis changed. + this.changedAxes.splice(0, this.changedAxes.length); + for (i = 0; i < controllerAxes.length; ++i) { + changedAxes.push(previousAxis[i] !== controllerAxes[i]); + if (changedAxes[i]) { + changed = true; + } + } + if (!changed) { + return false; + } + this.axis.splice(0, this.axis.length); + for (i = 0; i < controllerAxes.length; i++) { + this.axis.push(controllerAxes[i]); + } + this.el.emit(EVENTS.AXISMOVE, this.axisMoveEventDetail, false); + return true; + }, + /** + * Determine whether a button press has occured and emit events as appropriate. + * + * @param {string} id - ID of the button to check. + * @param {object} buttonState - State of the button to check. + * @returns {boolean} Whether button press state changed. + */ + handlePress: function (id, buttonState) { + var evtName; + var previousButtonState = this.buttonStates[id]; + + // Not changed. + if (buttonState.pressed === previousButtonState.pressed) { + return false; + } + evtName = buttonState.pressed ? EVENTS.BUTTONDOWN : EVENTS.BUTTONUP; + this.el.emit(evtName, this.buttonEventDetails[id], false); + previousButtonState.pressed = buttonState.pressed; + return true; + }, + /** + * Determine whether a button touch has occured and emit events as appropriate. + * + * @param {string} id - ID of the button to check. + * @param {object} buttonState - State of the button to check. + * @returns {boolean} Whether button touch state changed. + */ + handleTouch: function (id, buttonState) { + var evtName; + var previousButtonState = this.buttonStates[id]; + + // Not changed. + if (buttonState.touched === previousButtonState.touched) { + return false; + } + evtName = buttonState.touched ? EVENTS.TOUCHSTART : EVENTS.TOUCHEND; + this.el.emit(evtName, this.buttonEventDetails[id], false); + previousButtonState.touched = buttonState.touched; + return true; + }, + /** + * Determine whether a button value has changed. + * + * @param {string} id - Id of the button to check. + * @param {object} buttonState - State of the button to check. + * @returns {boolean} Whether button value changed. + */ + handleValue: function (id, buttonState) { + var previousButtonState = this.buttonStates[id]; + + // Not changed. + if (buttonState.value === previousButtonState.value) { + return false; + } + previousButtonState.value = buttonState.value; + return true; + } +}); + +/***/ }), + +/***/ "./src/components/tracked-controls.js": +/*!********************************************!*\ + !*** ./src/components/tracked-controls.js ***! + \********************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var registerComponent = (__webpack_require__(/*! ../core/component */ "./src/core/component.js").registerComponent); + +/** + * Tracked controls. + * Abstract controls that decide if the WebVR or WebXR version is going to be applied. + * + * @property {number} controller - Index of controller in array returned by Gamepad API. + * Only used if hand property is not set. + * @property {string} id - Selected controller among those returned by Gamepad API. + * @property {number} hand - If multiple controllers found with id, choose the one with the + * given value for hand. If set, we ignore 'controller' property + */ +module.exports.Component = registerComponent('tracked-controls', { + schema: { + autoHide: { + default: true + }, + controller: { + default: -1 + }, + id: { + type: 'string', + default: '' + }, + hand: { + type: 'string', + default: '' + }, + idPrefix: { + type: 'string', + default: '' + }, + handTrackingEnabled: { + default: false + }, + orientationOffset: { + type: 'vec3' + }, + // Arm model parameters when not 6DoF. + armModel: { + default: false + }, + headElement: { + type: 'selector' + }, + iterateControllerProfiles: { + default: false + }, + space: { + type: 'string', + oneOf: ['targetRaySpace', 'gripSpace'], + default: 'targetRaySpace' + } + }, + update: function () { + var data = this.data; + var el = this.el; + if (el.sceneEl.hasWebXR) { + el.setAttribute('tracked-controls-webxr', { + id: data.id, + hand: data.hand, + index: data.controller, + iterateControllerProfiles: data.iterateControllerProfiles, + handTrackingEnabled: data.handTrackingEnabled, + space: data.space + }); + } else { + el.setAttribute('tracked-controls-webvr', data); + } + } +}); + +/***/ }), + +/***/ "./src/components/valve-index-controls.js": +/*!************************************************!*\ + !*** ./src/components/valve-index-controls.js ***! + \************************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var registerComponent = (__webpack_require__(/*! ../core/component */ "./src/core/component.js").registerComponent); +var bind = __webpack_require__(/*! ../utils/bind */ "./src/utils/bind.js"); +var THREE = __webpack_require__(/*! ../lib/three */ "./src/lib/three.js"); +var trackedControlsUtils = __webpack_require__(/*! ../utils/tracked-controls */ "./src/utils/tracked-controls.js"); +var checkControllerPresentAndSetup = trackedControlsUtils.checkControllerPresentAndSetup; +var emitIfAxesChanged = trackedControlsUtils.emitIfAxesChanged; +var onButtonEvent = trackedControlsUtils.onButtonEvent; +var AFRAME_CDN_ROOT = (__webpack_require__(/*! ../constants */ "./src/constants/index.js").AFRAME_CDN_ROOT); +var INDEX_CONTROLLER_MODEL_BASE_URL = AFRAME_CDN_ROOT + 'controllers/valve/index/valve-index-'; +var INDEX_CONTROLLER_MODEL_URL = { + left: INDEX_CONTROLLER_MODEL_BASE_URL + 'left.glb', + right: INDEX_CONTROLLER_MODEL_BASE_URL + 'right.glb' +}; +var GAMEPAD_ID_PREFIX = 'valve'; +var isWebXRAvailable = (__webpack_require__(/*! ../utils/ */ "./src/utils/index.js").device.isWebXRAvailable); +var INDEX_CONTROLLER_POSITION_OFFSET_WEBVR = { + left: { + x: -0.00023692678902063457, + y: 0.04724540367838371, + z: -0.061959880395271096 + }, + right: { + x: 0.002471558599671131, + y: 0.055765208987076195, + z: -0.061068168708348844 + } +}; +var INDEX_CONTROLLER_POSITION_OFFSET_WEBXR = { + left: { + x: 0, + y: -0.05, + z: 0.06 + }, + right: { + x: 0, + y: -0.05, + z: 0.06 + } +}; +var INDEX_CONTROLLER_ROTATION_OFFSET_WEBVR = { + left: { + _x: 0.692295102620542, + _y: -0.0627618864318427, + _z: -0.06265893149611756, + _order: 'XYZ' + }, + right: { + _x: 0.6484021229942998, + _y: -0.032563619881892894, + _z: -0.1327973171917482, + _order: 'XYZ' + } +}; +var INDEX_CONTROLLER_ROTATION_OFFSET_WEBXR = { + left: { + _x: Math.PI / 3, + _y: 0, + _z: 0, + _order: 'XYZ' + }, + right: { + _x: Math.PI / 3, + _y: 0, + _z: 0, + _order: 'XYZ' + } +}; +var INDEX_CONTROLLER_ROTATION_OFFSET = isWebXRAvailable ? INDEX_CONTROLLER_ROTATION_OFFSET_WEBXR : INDEX_CONTROLLER_ROTATION_OFFSET_WEBVR; +var INDEX_CONTROLLER_POSITION_OFFSET = isWebXRAvailable ? INDEX_CONTROLLER_POSITION_OFFSET_WEBXR : INDEX_CONTROLLER_POSITION_OFFSET_WEBVR; +/** + * Vive controls. + * Interface with Vive controllers and map Gamepad events to controller buttons: + * trackpad, trigger, grip, menu, system + * Load a controller model and highlight the pressed buttons. + */ +module.exports.Component = registerComponent('valve-index-controls', { + schema: { + hand: { + default: 'left' + }, + buttonColor: { + type: 'color', + default: '#FAFAFA' + }, + // Off-white. + buttonHighlightColor: { + type: 'color', + default: '#22D1EE' + }, + // Light blue. + model: { + default: true + }, + orientationOffset: { + type: 'vec3' + } + }, + mapping: { + axes: { + trackpad: [0, 1], + thumbstick: [2, 3] + }, + buttons: ['trigger', 'grip', 'trackpad', 'thumbstick', 'abutton'] + }, + init: function () { + var self = this; + this.controllerPresent = false; + this.lastControllerCheck = 0; + this.onButtonChanged = bind(this.onButtonChanged, this); + this.onButtonDown = function (evt) { + onButtonEvent(evt.detail.id, 'down', self); + }; + this.onButtonUp = function (evt) { + onButtonEvent(evt.detail.id, 'up', self); + }; + this.onButtonTouchEnd = function (evt) { + onButtonEvent(evt.detail.id, 'touchend', self); + }; + this.onButtonTouchStart = function (evt) { + onButtonEvent(evt.detail.id, 'touchstart', self); + }; + this.previousButtonValues = {}; + this.bindMethods(); + }, + play: function () { + this.checkIfControllerPresent(); + this.addControllersUpdateListener(); + }, + pause: function () { + this.removeEventListeners(); + this.removeControllersUpdateListener(); + }, + bindMethods: function () { + this.onModelLoaded = bind(this.onModelLoaded, this); + this.onControllersUpdate = bind(this.onControllersUpdate, this); + this.checkIfControllerPresent = bind(this.checkIfControllerPresent, this); + this.removeControllersUpdateListener = bind(this.removeControllersUpdateListener, this); + this.onAxisMoved = bind(this.onAxisMoved, this); + }, + addEventListeners: function () { + var el = this.el; + el.addEventListener('buttonchanged', this.onButtonChanged); + el.addEventListener('buttondown', this.onButtonDown); + el.addEventListener('buttonup', this.onButtonUp); + el.addEventListener('touchend', this.onButtonTouchEnd); + el.addEventListener('touchstart', this.onButtonTouchStart); + el.addEventListener('model-loaded', this.onModelLoaded); + el.addEventListener('axismove', this.onAxisMoved); + this.controllerEventsActive = true; + }, + removeEventListeners: function () { + var el = this.el; + el.removeEventListener('buttonchanged', this.onButtonChanged); + el.removeEventListener('buttondown', this.onButtonDown); + el.removeEventListener('buttonup', this.onButtonUp); + el.removeEventListener('touchend', this.onButtonTouchEnd); + el.removeEventListener('touchstart', this.onButtonTouchStart); + el.removeEventListener('model-loaded', this.onModelLoaded); + el.removeEventListener('axismove', this.onAxisMoved); + this.controllerEventsActive = false; + }, + /** + * Once OpenVR returns correct hand data in supporting browsers, we can use hand property. + * var isPresent = checkControllerPresentAndSetup(this.el.sceneEl, GAMEPAD_ID_PREFIX, + { hand: data.hand }); + * Until then, use hardcoded index. + */ + checkIfControllerPresent: function () { + var data = this.data; + var controllerIndex = data.hand === 'right' ? 0 : data.hand === 'left' ? 1 : 2; + checkControllerPresentAndSetup(this, GAMEPAD_ID_PREFIX, { + index: controllerIndex, + iterateControllerProfiles: true, + hand: data.hand + }); + }, + injectTrackedControls: function () { + var el = this.el; + var data = this.data; + + // If we have an OpenVR Gamepad, use the fixed mapping. + el.setAttribute('tracked-controls', { + idPrefix: GAMEPAD_ID_PREFIX, + // Hand IDs: 1 = right, 0 = left, 2 = anything else. + controller: data.hand === 'right' ? 1 : data.hand === 'left' ? 0 : 2, + hand: data.hand, + orientationOffset: data.orientationOffset + }); + this.loadModel(); + }, + loadModel: function () { + var data = this.data; + if (!data.model) { + return; + } + this.el.setAttribute('gltf-model', '' + INDEX_CONTROLLER_MODEL_URL[data.hand] + ''); + }, + addControllersUpdateListener: function () { + this.el.sceneEl.addEventListener('controllersupdated', this.onControllersUpdate, false); + }, + removeControllersUpdateListener: function () { + this.el.sceneEl.removeEventListener('controllersupdated', this.onControllersUpdate, false); + }, + onControllersUpdate: function () { + this.checkIfControllerPresent(); + }, + /** + * Rotate the trigger button based on how hard the trigger is pressed. + */ + onButtonChanged: function (evt) { + var button = this.mapping.buttons[evt.detail.id]; + var buttonMeshes = this.buttonMeshes; + var analogValue; + if (!button) { + return; + } + if (button === 'trigger') { + analogValue = evt.detail.state.value; + // Update trigger rotation depending on button value. + if (buttonMeshes && buttonMeshes.trigger) { + buttonMeshes.trigger.rotation.x = this.triggerOriginalRotationX - analogValue * (Math.PI / 40); + } + } + + // Pass along changed event with button state, using button mapping for convenience. + this.el.emit(button + 'changed', evt.detail.state); + }, + onModelLoaded: function (evt) { + var buttonMeshes; + var controllerObject3D = evt.detail.model; + var self = this; + if (evt.target !== this.el || !this.data.model) { + return; + } + + // Store button meshes object to be able to change their colors. + buttonMeshes = this.buttonMeshes = {}; + buttonMeshes.grip = { + left: controllerObject3D.getObjectByName('leftgrip'), + right: controllerObject3D.getObjectByName('rightgrip') + }; + buttonMeshes.menu = controllerObject3D.getObjectByName('menubutton'); + buttonMeshes.system = controllerObject3D.getObjectByName('systembutton'); + buttonMeshes.trackpad = controllerObject3D.getObjectByName('touchpad'); + buttonMeshes.trigger = controllerObject3D.getObjectByName('trigger'); + this.triggerOriginalRotationX = buttonMeshes.trigger.rotation.x; + + // Set default colors. + Object.keys(buttonMeshes).forEach(function (buttonName) { + self.setButtonColor(buttonName, self.data.buttonColor); + }); + + // Offset pivot point. + controllerObject3D.position.copy(INDEX_CONTROLLER_POSITION_OFFSET[this.data.hand]); + controllerObject3D.rotation.copy(INDEX_CONTROLLER_ROTATION_OFFSET[this.data.hand]); + this.el.emit('controllermodelready', { + name: 'valve-index-controlls', + model: this.data.model, + rayOrigin: new THREE.Vector3(0, 0, 0) + }); + }, + onAxisMoved: function (evt) { + emitIfAxesChanged(this, this.mapping.axes, evt); + }, + updateModel: function (buttonName, evtName) { + var color; + var isTouch; + if (!this.data.model) { + return; + } + isTouch = evtName.indexOf('touch') !== -1; + // Don't change color for trackpad touch. + if (isTouch) { + return; + } + + // Update colors. + color = evtName === 'up' ? this.data.buttonColor : this.data.buttonHighlightColor; + this.setButtonColor(buttonName, color); + }, + setButtonColor: function (buttonName, color) { + // TODO: The meshes aren't set up correctly now, skipping for the moment + return; + } +}); + +/***/ }), + +/***/ "./src/components/visible.js": +/*!***********************************!*\ + !*** ./src/components/visible.js ***! + \***********************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var registerComponent = (__webpack_require__(/*! ../core/component */ "./src/core/component.js").registerComponent); + +/** + * Visibility component. + */ +module.exports.Component = registerComponent('visible', { + schema: { + default: true + }, + update: function () { + this.el.object3D.visible = this.data; + } +}); + +/***/ }), + +/***/ "./src/components/vive-controls.js": +/*!*****************************************!*\ + !*** ./src/components/vive-controls.js ***! + \*****************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var registerComponent = (__webpack_require__(/*! ../core/component */ "./src/core/component.js").registerComponent); +var bind = __webpack_require__(/*! ../utils/bind */ "./src/utils/bind.js"); +var trackedControlsUtils = __webpack_require__(/*! ../utils/tracked-controls */ "./src/utils/tracked-controls.js"); +var checkControllerPresentAndSetup = trackedControlsUtils.checkControllerPresentAndSetup; +var emitIfAxesChanged = trackedControlsUtils.emitIfAxesChanged; +var onButtonEvent = trackedControlsUtils.onButtonEvent; +var AFRAME_CDN_ROOT = (__webpack_require__(/*! ../constants */ "./src/constants/index.js").AFRAME_CDN_ROOT); +var VIVE_CONTROLLER_MODEL_OBJ_URL = AFRAME_CDN_ROOT + 'controllers/vive/vr_controller_vive.obj'; +var VIVE_CONTROLLER_MODEL_OBJ_MTL = AFRAME_CDN_ROOT + 'controllers/vive/vr_controller_vive.mtl'; +var isWebXRAvailable = (__webpack_require__(/*! ../utils/ */ "./src/utils/index.js").device.isWebXRAvailable); +var GAMEPAD_ID_WEBXR = 'htc-vive'; +var GAMEPAD_ID_WEBVR = 'OpenVR '; + +// Prefix for Gen1 and Gen2 Oculus Touch Controllers. +var GAMEPAD_ID_PREFIX = isWebXRAvailable ? GAMEPAD_ID_WEBXR : GAMEPAD_ID_WEBVR; + +/** + * Button IDs: + * 0 - trackpad + * 1 - trigger (intensity value from 0.5 to 1) + * 2 - grip + * 3 - menu (dispatch but better for menu options) + * 4 - system (never dispatched on this layer) + */ +var INPUT_MAPPING_WEBVR = { + axes: { + trackpad: [0, 1] + }, + buttons: ['trackpad', 'trigger', 'grip', 'menu', 'system'] +}; + +/** + * Button IDs: + * 0 - trigger + * 1 - squeeze + * 2 - touchpad + * 3 - none (dispatch but better for menu options) + * 4 - menu (never dispatched on this layer) + * + * Axis: + * 0 - touchpad x axis + * 1 - touchpad y axis + * Reference: https://github.com/immersive-web/webxr-input-profiles/blob/master/packages/registry/profiles/htc/htc-vive.json + */ +var INPUT_MAPPING_WEBXR = { + axes: { + thumbstick: [0, 1] + }, + buttons: ['trigger', 'grip', 'trackpad', 'none', 'menu'] +}; +var INPUT_MAPPING = isWebXRAvailable ? INPUT_MAPPING_WEBXR : INPUT_MAPPING_WEBVR; + +/** + * Vive controls. + * Interface with Vive controllers and map Gamepad events to controller buttons: + * trackpad, trigger, grip, menu, system + * Load a controller model and highlight the pressed buttons. + */ +module.exports.Component = registerComponent('vive-controls', { + schema: { + hand: { + default: 'left' + }, + buttonColor: { + type: 'color', + default: '#FAFAFA' + }, + // Off-white. + buttonHighlightColor: { + type: 'color', + default: '#22D1EE' + }, + // Light blue. + model: { + default: true + }, + orientationOffset: { + type: 'vec3' + } + }, + mapping: INPUT_MAPPING, + init: function () { + var self = this; + this.controllerPresent = false; + this.lastControllerCheck = 0; + this.onButtonChanged = bind(this.onButtonChanged, this); + this.onButtonDown = function (evt) { + onButtonEvent(evt.detail.id, 'down', self); + }; + this.onButtonUp = function (evt) { + onButtonEvent(evt.detail.id, 'up', self); + }; + this.onButtonTouchEnd = function (evt) { + onButtonEvent(evt.detail.id, 'touchend', self); + }; + this.onButtonTouchStart = function (evt) { + onButtonEvent(evt.detail.id, 'touchstart', self); + }; + this.previousButtonValues = {}; + this.bindMethods(); + }, + update: function () { + var data = this.data; + this.controllerIndex = data.hand === 'right' ? 0 : data.hand === 'left' ? 1 : 2; + }, + play: function () { + this.checkIfControllerPresent(); + this.addControllersUpdateListener(); + }, + pause: function () { + this.removeEventListeners(); + this.removeControllersUpdateListener(); + }, + bindMethods: function () { + this.onModelLoaded = bind(this.onModelLoaded, this); + this.onControllersUpdate = bind(this.onControllersUpdate, this); + this.checkIfControllerPresent = bind(this.checkIfControllerPresent, this); + this.removeControllersUpdateListener = bind(this.removeControllersUpdateListener, this); + this.onAxisMoved = bind(this.onAxisMoved, this); + }, + addEventListeners: function () { + var el = this.el; + el.addEventListener('buttonchanged', this.onButtonChanged); + el.addEventListener('buttondown', this.onButtonDown); + el.addEventListener('buttonup', this.onButtonUp); + el.addEventListener('touchend', this.onButtonTouchEnd); + el.addEventListener('touchstart', this.onButtonTouchStart); + el.addEventListener('model-loaded', this.onModelLoaded); + el.addEventListener('axismove', this.onAxisMoved); + this.controllerEventsActive = true; + }, + removeEventListeners: function () { + var el = this.el; + el.removeEventListener('buttonchanged', this.onButtonChanged); + el.removeEventListener('buttondown', this.onButtonDown); + el.removeEventListener('buttonup', this.onButtonUp); + el.removeEventListener('touchend', this.onButtonTouchEnd); + el.removeEventListener('touchstart', this.onButtonTouchStart); + el.removeEventListener('model-loaded', this.onModelLoaded); + el.removeEventListener('axismove', this.onAxisMoved); + this.controllerEventsActive = false; + }, + /** + * Once OpenVR returns correct hand data in supporting browsers, we can use hand property. + * var isPresent = checkControllerPresentAndSetup(this.el.sceneEl, GAMEPAD_ID_PREFIX, + { hand: data.hand }); + * Until then, use hardcoded index. + */ + checkIfControllerPresent: function () { + var data = this.data; + checkControllerPresentAndSetup(this, GAMEPAD_ID_PREFIX, { + index: this.controllerIndex, + hand: data.hand + }); + }, + injectTrackedControls: function () { + var el = this.el; + var data = this.data; + + // If we have an OpenVR Gamepad, use the fixed mapping. + el.setAttribute('tracked-controls', { + idPrefix: GAMEPAD_ID_PREFIX, + hand: data.hand, + controller: this.controllerIndex, + orientationOffset: data.orientationOffset + }); + + // Load model. + if (!this.data.model) { + return; + } + this.el.setAttribute('obj-model', { + obj: VIVE_CONTROLLER_MODEL_OBJ_URL, + mtl: VIVE_CONTROLLER_MODEL_OBJ_MTL + }); + }, + addControllersUpdateListener: function () { + this.el.sceneEl.addEventListener('controllersupdated', this.onControllersUpdate, false); + }, + removeControllersUpdateListener: function () { + this.el.sceneEl.removeEventListener('controllersupdated', this.onControllersUpdate, false); + }, + onControllersUpdate: function () { + this.checkIfControllerPresent(); + }, + /** + * Rotate the trigger button based on how hard the trigger is pressed. + */ + onButtonChanged: function (evt) { + var button = this.mapping.buttons[evt.detail.id]; + var buttonMeshes = this.buttonMeshes; + var analogValue; + if (!button) { + return; + } + if (button === 'trigger') { + analogValue = evt.detail.state.value; + // Update trigger rotation depending on button value. + if (buttonMeshes && buttonMeshes.trigger) { + buttonMeshes.trigger.rotation.x = -analogValue * (Math.PI / 12); + } + } + + // Pass along changed event with button state, using button mapping for convenience. + this.el.emit(button + 'changed', evt.detail.state); + }, + onModelLoaded: function (evt) { + var buttonMeshes; + var controllerObject3D = evt.detail.model; + var self = this; + if (evt.target !== this.el || !this.data.model) { + return; + } + + // Store button meshes object to be able to change their colors. + buttonMeshes = this.buttonMeshes = {}; + buttonMeshes.grip = { + left: controllerObject3D.getObjectByName('leftgrip'), + right: controllerObject3D.getObjectByName('rightgrip') + }; + buttonMeshes.menu = controllerObject3D.getObjectByName('menubutton'); + buttonMeshes.system = controllerObject3D.getObjectByName('systembutton'); + buttonMeshes.trackpad = controllerObject3D.getObjectByName('touchpad'); + buttonMeshes.trigger = controllerObject3D.getObjectByName('trigger'); + + // Set default colors. + Object.keys(buttonMeshes).forEach(function (buttonName) { + self.setButtonColor(buttonName, self.data.buttonColor); + }); + + // Offset pivot point. + controllerObject3D.position.set(0, -0.015, 0.04); + }, + onAxisMoved: function (evt) { + emitIfAxesChanged(this, this.mapping.axes, evt); + }, + updateModel: function (buttonName, evtName) { + var color; + var isTouch; + if (!this.data.model) { + return; + } + isTouch = evtName.indexOf('touch') !== -1; + // Don't change color for trackpad touch. + if (isTouch) { + return; + } + + // Update colors. + color = evtName === 'up' ? this.data.buttonColor : this.data.buttonHighlightColor; + this.setButtonColor(buttonName, color); + }, + setButtonColor: function (buttonName, color) { + var buttonMeshes = this.buttonMeshes; + if (!buttonMeshes) { + return; + } + + // Need to do both left and right sides for grip. + if (buttonName === 'grip') { + buttonMeshes.grip.left.material.color.set(color); + buttonMeshes.grip.right.material.color.set(color); + return; + } + buttonMeshes[buttonName].material.color.set(color); + } +}); + +/***/ }), + +/***/ "./src/components/vive-focus-controls.js": +/*!***********************************************!*\ + !*** ./src/components/vive-focus-controls.js ***! + \***********************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var registerComponent = (__webpack_require__(/*! ../core/component */ "./src/core/component.js").registerComponent); +var bind = __webpack_require__(/*! ../utils/bind */ "./src/utils/bind.js"); +var trackedControlsUtils = __webpack_require__(/*! ../utils/tracked-controls */ "./src/utils/tracked-controls.js"); +var checkControllerPresentAndSetup = trackedControlsUtils.checkControllerPresentAndSetup; +var emitIfAxesChanged = trackedControlsUtils.emitIfAxesChanged; +var onButtonEvent = trackedControlsUtils.onButtonEvent; +var GAMEPAD_ID_PREFIX = 'HTC Vive Focus'; +var AFRAME_CDN_ROOT = (__webpack_require__(/*! ../constants */ "./src/constants/index.js").AFRAME_CDN_ROOT); +var VIVE_FOCUS_CONTROLLER_MODEL_URL = AFRAME_CDN_ROOT + 'controllers/vive/focus-controller/focus-controller.gltf'; + +/** + * Vive Focus controls. + * Interface with Vive Focus controller and map Gamepad events to + * controller buttons: trackpad, trigger + * Load a controller model and highlight the pressed buttons. + */ +module.exports.Component = registerComponent('vive-focus-controls', { + schema: { + hand: { + default: '' + }, + // This informs the degenerate arm model. + buttonTouchedColor: { + type: 'color', + default: '#BBBBBB' + }, + buttonHighlightColor: { + type: 'color', + default: '#7A7A7A' + }, + model: { + default: true + }, + orientationOffset: { + type: 'vec3' + }, + armModel: { + default: true + } + }, + /** + * Button IDs: + * 0 - trackpad + * 1 - trigger + */ + mapping: { + axes: { + trackpad: [0, 1] + }, + buttons: ['trackpad', 'trigger'] + }, + bindMethods: function () { + this.onModelLoaded = bind(this.onModelLoaded, this); + this.onControllersUpdate = bind(this.onControllersUpdate, this); + this.checkIfControllerPresent = bind(this.checkIfControllerPresent, this); + this.removeControllersUpdateListener = bind(this.removeControllersUpdateListener, this); + this.onAxisMoved = bind(this.onAxisMoved, this); + }, + init: function () { + var self = this; + this.onButtonChanged = bind(this.onButtonChanged, this); + this.onButtonDown = function (evt) { + onButtonEvent(evt.detail.id, 'down', self); + }; + this.onButtonUp = function (evt) { + onButtonEvent(evt.detail.id, 'up', self); + }; + this.onButtonTouchStart = function (evt) { + onButtonEvent(evt.detail.id, 'touchstart', self); + }; + this.onButtonTouchEnd = function (evt) { + onButtonEvent(evt.detail.id, 'touchend', self); + }; + this.controllerPresent = false; + this.lastControllerCheck = 0; + this.bindMethods(); + }, + addEventListeners: function () { + var el = this.el; + el.addEventListener('buttonchanged', this.onButtonChanged); + el.addEventListener('buttondown', this.onButtonDown); + el.addEventListener('buttonup', this.onButtonUp); + el.addEventListener('touchstart', this.onButtonTouchStart); + el.addEventListener('touchend', this.onButtonTouchEnd); + el.addEventListener('model-loaded', this.onModelLoaded); + el.addEventListener('axismove', this.onAxisMoved); + this.controllerEventsActive = true; + this.addControllersUpdateListener(); + }, + removeEventListeners: function () { + var el = this.el; + el.removeEventListener('buttonchanged', this.onButtonChanged); + el.removeEventListener('buttondown', this.onButtonDown); + el.removeEventListener('buttonup', this.onButtonUp); + el.removeEventListener('touchstart', this.onButtonTouchStart); + el.removeEventListener('touchend', this.onButtonTouchEnd); + el.removeEventListener('model-loaded', this.onModelLoaded); + el.removeEventListener('axismove', this.onAxisMoved); + this.controllerEventsActive = false; + this.removeControllersUpdateListener(); + }, + checkIfControllerPresent: function () { + checkControllerPresentAndSetup(this, GAMEPAD_ID_PREFIX, this.data.hand ? { + hand: this.data.hand + } : {}); + }, + play: function () { + this.checkIfControllerPresent(); + this.addControllersUpdateListener(); + }, + pause: function () { + this.removeEventListeners(); + this.removeControllersUpdateListener(); + }, + injectTrackedControls: function () { + var el = this.el; + var data = this.data; + el.setAttribute('tracked-controls', { + armModel: data.armModel, + idPrefix: GAMEPAD_ID_PREFIX, + orientationOffset: data.orientationOffset + }); + if (!this.data.model) { + return; + } + this.el.setAttribute('gltf-model', VIVE_FOCUS_CONTROLLER_MODEL_URL); + }, + addControllersUpdateListener: function () { + this.el.sceneEl.addEventListener('controllersupdated', this.onControllersUpdate, false); + }, + removeControllersUpdateListener: function () { + this.el.sceneEl.removeEventListener('controllersupdated', this.onControllersUpdate, false); + }, + onControllersUpdate: function () { + this.checkIfControllerPresent(); + }, + onModelLoaded: function (evt) { + var controllerObject3D = evt.detail.model; + var buttonMeshes; + if (evt.target !== this.el || !this.data.model) { + return; + } + buttonMeshes = this.buttonMeshes = {}; + buttonMeshes.trigger = controllerObject3D.getObjectByName('BumperKey'); + buttonMeshes.triggerPressed = controllerObject3D.getObjectByName('BumperKey_Press'); + if (buttonMeshes.triggerPressed) { + buttonMeshes.triggerPressed.visible = false; + } + buttonMeshes.trackpad = controllerObject3D.getObjectByName('TouchPad'); + buttonMeshes.trackpadPressed = controllerObject3D.getObjectByName('TouchPad_Press'); + if (buttonMeshes.trackpadPressed) { + buttonMeshes.trackpadPressed.visible = false; + } + }, + // No analog buttons, only emits 0/1 values + onButtonChanged: function (evt) { + var button = this.mapping.buttons[evt.detail.id]; + if (!button) return; + // Pass along changed event with button state, using button mapping for convenience. + this.el.emit(button + 'changed', evt.detail.state); + }, + onAxisMoved: function (evt) { + emitIfAxesChanged(this, this.mapping.axes, evt); + }, + updateModel: function (buttonName, evtName) { + if (!this.data.model) { + return; + } + this.updateButtonModel(buttonName, evtName); + }, + updateButtonModel: function (buttonName, state) { + var buttonMeshes = this.buttonMeshes; + var pressedName = buttonName + 'Pressed'; + if (!buttonMeshes || !buttonMeshes[buttonName] || !buttonMeshes[pressedName]) { + return; + } + var color; + switch (state) { + case 'down': + color = this.data.buttonHighlightColor; + break; + case 'touchstart': + color = this.data.buttonTouchedColor; + break; + } + if (color) { + buttonMeshes[pressedName].material.color.set(color); + } + buttonMeshes[pressedName].visible = !!color; + buttonMeshes[buttonName].visible = !color; + } +}); + +/***/ }), + +/***/ "./src/components/wasd-controls.js": +/*!*****************************************!*\ + !*** ./src/components/wasd-controls.js ***! + \*****************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var KEYCODE_TO_CODE = (__webpack_require__(/*! ../constants */ "./src/constants/index.js").keyboardevent.KEYCODE_TO_CODE); +var registerComponent = (__webpack_require__(/*! ../core/component */ "./src/core/component.js").registerComponent); +var THREE = __webpack_require__(/*! ../lib/three */ "./src/lib/three.js"); +var utils = __webpack_require__(/*! ../utils/ */ "./src/utils/index.js"); +var bind = utils.bind; +var shouldCaptureKeyEvent = utils.shouldCaptureKeyEvent; +var CLAMP_VELOCITY = 0.00001; +var MAX_DELTA = 0.2; +var KEYS = ['KeyW', 'KeyA', 'KeyS', 'KeyD', 'ArrowUp', 'ArrowLeft', 'ArrowRight', 'ArrowDown']; + +/** + * WASD component to control entities using WASD keys. + */ +module.exports.Component = registerComponent('wasd-controls', { + schema: { + acceleration: { + default: 65 + }, + adAxis: { + default: 'x', + oneOf: ['x', 'y', 'z'] + }, + adEnabled: { + default: true + }, + adInverted: { + default: false + }, + enabled: { + default: true + }, + fly: { + default: false + }, + wsAxis: { + default: 'z', + oneOf: ['x', 'y', 'z'] + }, + wsEnabled: { + default: true + }, + wsInverted: { + default: false + } + }, + init: function () { + // To keep track of the pressed keys. + this.keys = {}; + this.easing = 1.1; + this.velocity = new THREE.Vector3(); + + // Bind methods and add event listeners. + this.onBlur = bind(this.onBlur, this); + this.onContextMenu = bind(this.onContextMenu, this); + this.onFocus = bind(this.onFocus, this); + this.onKeyDown = bind(this.onKeyDown, this); + this.onKeyUp = bind(this.onKeyUp, this); + this.onVisibilityChange = bind(this.onVisibilityChange, this); + this.attachVisibilityEventListeners(); + }, + tick: function (time, delta) { + var data = this.data; + var el = this.el; + var velocity = this.velocity; + if (!velocity[data.adAxis] && !velocity[data.wsAxis] && isEmptyObject(this.keys)) { + return; + } + + // Update velocity. + delta = delta / 1000; + this.updateVelocity(delta); + if (!velocity[data.adAxis] && !velocity[data.wsAxis]) { + return; + } + + // Get movement vector and translate position. + el.object3D.position.add(this.getMovementVector(delta)); + }, + update: function (oldData) { + // Reset velocity if axis have changed. + if (oldData.adAxis !== this.data.adAxis) { + this.velocity[oldData.adAxis] = 0; + } + if (oldData.wsAxis !== this.data.wsAxis) { + this.velocity[oldData.wsAxis] = 0; + } + }, + remove: function () { + this.removeKeyEventListeners(); + this.removeVisibilityEventListeners(); + }, + play: function () { + this.attachKeyEventListeners(); + }, + pause: function () { + this.keys = {}; + this.removeKeyEventListeners(); + }, + updateVelocity: function (delta) { + var acceleration; + var adAxis; + var adSign; + var data = this.data; + var keys = this.keys; + var velocity = this.velocity; + var wsAxis; + var wsSign; + adAxis = data.adAxis; + wsAxis = data.wsAxis; + + // If FPS too low, reset velocity. + if (delta > MAX_DELTA) { + velocity[adAxis] = 0; + velocity[wsAxis] = 0; + return; + } + + // https://gamedev.stackexchange.com/questions/151383/frame-rate-independant-movement-with-acceleration + var scaledEasing = Math.pow(1 / this.easing, delta * 60); + // Velocity Easing. + if (velocity[adAxis] !== 0) { + velocity[adAxis] = velocity[adAxis] * scaledEasing; + } + if (velocity[wsAxis] !== 0) { + velocity[wsAxis] = velocity[wsAxis] * scaledEasing; + } + + // Clamp velocity easing. + if (Math.abs(velocity[adAxis]) < CLAMP_VELOCITY) { + velocity[adAxis] = 0; + } + if (Math.abs(velocity[wsAxis]) < CLAMP_VELOCITY) { + velocity[wsAxis] = 0; + } + if (!data.enabled) { + return; + } + + // Update velocity using keys pressed. + acceleration = data.acceleration; + if (data.adEnabled) { + adSign = data.adInverted ? -1 : 1; + if (keys.KeyA || keys.ArrowLeft) { + velocity[adAxis] -= adSign * acceleration * delta; + } + if (keys.KeyD || keys.ArrowRight) { + velocity[adAxis] += adSign * acceleration * delta; + } + } + if (data.wsEnabled) { + wsSign = data.wsInverted ? -1 : 1; + if (keys.KeyW || keys.ArrowUp) { + velocity[wsAxis] -= wsSign * acceleration * delta; + } + if (keys.KeyS || keys.ArrowDown) { + velocity[wsAxis] += wsSign * acceleration * delta; + } + } + }, + getMovementVector: function () { + var directionVector = new THREE.Vector3(0, 0, 0); + var rotationEuler = new THREE.Euler(0, 0, 0, 'YXZ'); + return function (delta) { + var rotation = this.el.getAttribute('rotation'); + var velocity = this.velocity; + var xRotation; + directionVector.copy(velocity); + directionVector.multiplyScalar(delta); + + // Absolute. + if (!rotation) { + return directionVector; + } + xRotation = this.data.fly ? rotation.x : 0; + + // Transform direction relative to heading. + rotationEuler.set(THREE.MathUtils.degToRad(xRotation), THREE.MathUtils.degToRad(rotation.y), 0); + directionVector.applyEuler(rotationEuler); + return directionVector; + }; + }(), + attachVisibilityEventListeners: function () { + window.oncontextmenu = this.onContextMenu; + window.addEventListener('blur', this.onBlur); + window.addEventListener('focus', this.onFocus); + document.addEventListener('visibilitychange', this.onVisibilityChange); + }, + removeVisibilityEventListeners: function () { + window.removeEventListener('blur', this.onBlur); + window.removeEventListener('focus', this.onFocus); + document.removeEventListener('visibilitychange', this.onVisibilityChange); + }, + attachKeyEventListeners: function () { + window.addEventListener('keydown', this.onKeyDown); + window.addEventListener('keyup', this.onKeyUp); + }, + removeKeyEventListeners: function () { + window.removeEventListener('keydown', this.onKeyDown); + window.removeEventListener('keyup', this.onKeyUp); + }, + onContextMenu: function () { + var keys = Object.keys(this.keys); + for (var i = 0; i < keys.length; i++) { + delete this.keys[keys[i]]; + } + }, + onBlur: function () { + this.pause(); + }, + onFocus: function () { + this.play(); + }, + onVisibilityChange: function () { + if (document.hidden) { + this.onBlur(); + } else { + this.onFocus(); + } + }, + onKeyDown: function (event) { + var code; + if (!shouldCaptureKeyEvent(event)) { + return; + } + code = event.code || KEYCODE_TO_CODE[event.keyCode]; + if (KEYS.indexOf(code) !== -1) { + this.keys[code] = true; + } + }, + onKeyUp: function (event) { + var code; + code = event.code || KEYCODE_TO_CODE[event.keyCode]; + delete this.keys[code]; + } +}); +function isEmptyObject(keys) { + var key; + for (key in keys) { + return false; + } + return true; +} + +/***/ }), + +/***/ "./src/components/windows-motion-controls.js": +/*!***************************************************!*\ + !*** ./src/components/windows-motion-controls.js ***! + \***************************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +/* global THREE */ +var registerComponent = (__webpack_require__(/*! ../core/component */ "./src/core/component.js").registerComponent); +var bind = __webpack_require__(/*! ../utils/bind */ "./src/utils/bind.js"); +var trackedControlsUtils = __webpack_require__(/*! ../utils/tracked-controls */ "./src/utils/tracked-controls.js"); +var checkControllerPresentAndSetup = trackedControlsUtils.checkControllerPresentAndSetup; +var emitIfAxesChanged = trackedControlsUtils.emitIfAxesChanged; +var onButtonEvent = trackedControlsUtils.onButtonEvent; +var utils = __webpack_require__(/*! ../utils/ */ "./src/utils/index.js"); +var debug = utils.debug('components:windows-motion-controls:debug'); +var warn = utils.debug('components:windows-motion-controls:warn'); +var DEFAULT_HANDEDNESS = (__webpack_require__(/*! ../constants */ "./src/constants/index.js").DEFAULT_HANDEDNESS); +var AFRAME_CDN_ROOT = (__webpack_require__(/*! ../constants */ "./src/constants/index.js").AFRAME_CDN_ROOT); +var MODEL_BASE_URL = AFRAME_CDN_ROOT + 'controllers/microsoft/'; +var MODEL_FILENAMES = { + left: 'left.glb', + right: 'right.glb', + default: 'universal.glb' +}; +var isWebXRAvailable = (__webpack_require__(/*! ../utils/ */ "./src/utils/index.js").device.isWebXRAvailable); +var GAMEPAD_ID_WEBXR = 'windows-mixed-reality'; +var GAMEPAD_ID_WEBVR = 'Spatial Controller (Spatial Interaction Source) '; +var GAMEPAD_ID_PATTERN = /([0-9a-zA-Z]+-[0-9a-zA-Z]+)$/; +var GAMEPAD_ID_PREFIX = isWebXRAvailable ? GAMEPAD_ID_WEBXR : GAMEPAD_ID_WEBVR; +var INPUT_MAPPING_WEBVR = { + // A-Frame specific semantic axis names + axes: { + 'thumbstick': [0, 1], + 'trackpad': [2, 3] + }, + // A-Frame specific semantic button names + buttons: ['thumbstick', 'trigger', 'grip', 'menu', 'trackpad'], + // A mapping of the semantic name to node name in the glTF model file, + // that should be transformed by axis value. + // This array mirrors the browser Gamepad.axes array, such that + // the mesh corresponding to axis 0 is in this array index 0. + axisMeshNames: ['THUMBSTICK_X', 'THUMBSTICK_Y', 'TOUCHPAD_TOUCH_X', 'TOUCHPAD_TOUCH_Y'], + // A mapping of the semantic name to button node name in the glTF model file, + // that should be transformed by button value. + buttonMeshNames: { + 'trigger': 'SELECT', + 'menu': 'MENU', + 'grip': 'GRASP', + 'thumbstick': 'THUMBSTICK_PRESS', + 'trackpad': 'TOUCHPAD_PRESS' + }, + pointingPoseMeshName: 'POINTING_POSE' +}; +var INPUT_MAPPING_WEBXR = { + // A-Frame specific semantic axis names + axes: { + 'touchpad': [0, 1], + 'thumbstick': [2, 3] + }, + // A-Frame specific semantic button names + buttons: ['trigger', 'squeeze', 'touchpad', 'thumbstick', 'menu'], + // A mapping of the semantic name to node name in the glTF model file, + // that should be transformed by axis value. + // This array mirrors the browser Gamepad.axes array, such that + // the mesh corresponding to axis 0 is in this array index 0. + axisMeshNames: ['TOUCHPAD_TOUCH_X', 'TOUCHPAD_TOUCH_X', 'THUMBSTICK_X', 'THUMBSTICK_Y'], + // A mapping of the semantic name to button node name in the glTF model file, + // that should be transformed by button value. + buttonMeshNames: { + 'trigger': 'SELECT', + 'menu': 'MENU', + 'squeeze': 'GRASP', + 'thumbstick': 'THUMBSTICK_PRESS', + 'touchpad': 'TOUCHPAD_PRESS' + }, + pointingPoseMeshName: 'POINTING_POSE' +}; +var INPUT_MAPPING = isWebXRAvailable ? INPUT_MAPPING_WEBXR : INPUT_MAPPING_WEBVR; + +/** + * Windows Motion Controller controls. + * Interface with Windows Motion Controller controllers and map Gamepad events to + * controller buttons: trackpad, trigger, grip, menu, thumbstick + * Load a controller model and transform the pressed buttons. + */ +module.exports.Component = registerComponent('windows-motion-controls', { + schema: { + hand: { + default: DEFAULT_HANDEDNESS + }, + // It is possible to have multiple pairs of controllers attached (a pair has both left and right). + // Set this to 1 to use a controller from the second pair, 2 from the third pair, etc. + pair: { + default: 0 + }, + // If true, loads the controller glTF asset. + model: { + default: true + }, + // If true, will hide the model from the scene if no matching gamepad (based on ID & hand) is connected. + hideDisconnected: { + default: true + } + }, + mapping: INPUT_MAPPING, + bindMethods: function () { + this.onModelError = bind(this.onModelError, this); + this.onModelLoaded = bind(this.onModelLoaded, this); + this.onControllersUpdate = bind(this.onControllersUpdate, this); + this.checkIfControllerPresent = bind(this.checkIfControllerPresent, this); + this.onAxisMoved = bind(this.onAxisMoved, this); + }, + init: function () { + var self = this; + var el = this.el; + this.onButtonChanged = bind(this.onButtonChanged, this); + this.onButtonDown = function (evt) { + onButtonEvent(evt.detail.id, 'down', self); + }; + this.onButtonUp = function (evt) { + onButtonEvent(evt.detail.id, 'up', self); + }; + this.onButtonTouchStart = function (evt) { + onButtonEvent(evt.detail.id, 'touchstart', self); + }; + this.onButtonTouchEnd = function (evt) { + onButtonEvent(evt.detail.id, 'touchend', self); + }; + this.onControllerConnected = function () { + self.setModelVisibility(true); + }; + this.onControllerDisconnected = function () { + self.setModelVisibility(false); + }; + this.controllerPresent = false; + this.lastControllerCheck = 0; + this.previousButtonValues = {}; + this.bindMethods(); + + // Cache for submeshes that we have looked up by name. + this.loadedMeshInfo = { + buttonMeshes: null, + axisMeshes: null + }; + + // Pointing poses + this.rayOrigin = { + origin: new THREE.Vector3(), + direction: new THREE.Vector3(0, 0, -1), + createdFromMesh: false + }; + el.addEventListener('controllerconnected', this.onControllerConnected); + el.addEventListener('controllerdisconnected', this.onControllerDisconnected); + }, + addEventListeners: function () { + var el = this.el; + el.addEventListener('buttonchanged', this.onButtonChanged); + el.addEventListener('buttondown', this.onButtonDown); + el.addEventListener('buttonup', this.onButtonUp); + el.addEventListener('touchstart', this.onButtonTouchStart); + el.addEventListener('touchend', this.onButtonTouchEnd); + el.addEventListener('axismove', this.onAxisMoved); + el.addEventListener('model-error', this.onModelError); + el.addEventListener('model-loaded', this.onModelLoaded); + this.controllerEventsActive = true; + }, + removeEventListeners: function () { + var el = this.el; + el.removeEventListener('buttonchanged', this.onButtonChanged); + el.removeEventListener('buttondown', this.onButtonDown); + el.removeEventListener('buttonup', this.onButtonUp); + el.removeEventListener('touchstart', this.onButtonTouchStart); + el.removeEventListener('touchend', this.onButtonTouchEnd); + el.removeEventListener('axismove', this.onAxisMoved); + el.removeEventListener('model-error', this.onModelError); + el.removeEventListener('model-loaded', this.onModelLoaded); + this.controllerEventsActive = false; + }, + checkIfControllerPresent: function () { + checkControllerPresentAndSetup(this, GAMEPAD_ID_PREFIX, { + hand: this.data.hand, + index: this.data.pair, + iterateControllerProfiles: true + }); + }, + play: function () { + this.checkIfControllerPresent(); + this.addControllersUpdateListener(); + }, + pause: function () { + this.removeEventListeners(); + this.removeControllersUpdateListener(); + }, + updateControllerModel: function () { + // If we do not want to load a model, or, have already loaded the model, emit the controllermodelready event. + if (!this.data.model || this.rayOrigin.createdFromMesh) { + this.modelReady(); + return; + } + var sourceUrl = this.createControllerModelUrl(); + this.loadModel(sourceUrl); + }, + /** + * Helper function that constructs a URL from the controller ID suffix, for future proofed + * art assets. + */ + createControllerModelUrl: function (forceDefault) { + // Determine the device specific folder based on the ID suffix + var trackedControlsComponent = this.el.components['tracked-controls']; + var controller = trackedControlsComponent ? trackedControlsComponent.controller : null; + var device = 'default'; + var hand = this.data.hand; + var filename; + if (controller && !window.hasNativeWebXRImplementation) { + // Read hand directly from the controller, rather than this.data, as in the case that the controller + // is unhanded this.data will still have 'left' or 'right' (depending on what the user inserted in to the scene). + // In this case, we want to load the universal model, so need to get the '' from the controller. + hand = controller.hand; + if (!forceDefault) { + var match = controller.id.match(GAMEPAD_ID_PATTERN); + device = match && match[0] || device; + } + } + + // Hand + filename = MODEL_FILENAMES[hand] || MODEL_FILENAMES.default; + + // Final url + return MODEL_BASE_URL + device + '/' + filename; + }, + injectTrackedControls: function () { + var data = this.data; + this.el.setAttribute('tracked-controls', { + idPrefix: GAMEPAD_ID_PREFIX, + controller: data.pair, + hand: data.hand, + armModel: false + }); + this.updateControllerModel(); + }, + addControllersUpdateListener: function () { + this.el.sceneEl.addEventListener('controllersupdated', this.onControllersUpdate, false); + }, + removeControllersUpdateListener: function () { + this.el.sceneEl.removeEventListener('controllersupdated', this.onControllersUpdate, false); + }, + onControllersUpdate: function () { + this.checkIfControllerPresent(); + }, + onModelError: function (evt) { + var defaultUrl = this.createControllerModelUrl(true); + if (evt.detail.src !== defaultUrl) { + warn('Failed to load controller model for device, attempting to load default.'); + this.loadModel(defaultUrl); + } else { + warn('Failed to load default controller model.'); + } + }, + loadModel: function (url) { + // The model is loaded by the gltf-model compoent when this attribute is initially set, + // removed and re-loaded if the given url changes. + this.el.setAttribute('gltf-model', 'url(' + url + ')'); + }, + onModelLoaded: function (evt) { + var rootNode = this.controllerModel = evt.detail.model; + var loadedMeshInfo = this.loadedMeshInfo; + var i; + var meshName; + var mesh; + var meshInfo; + if (evt.target !== this.el) { + return; + } + debug('Processing model'); + + // Reset the caches + loadedMeshInfo.buttonMeshes = {}; + loadedMeshInfo.axisMeshes = {}; + + // Cache our meshes so we aren't traversing the hierarchy per frame + if (rootNode) { + // Button Meshes + for (i = 0; i < this.mapping.buttons.length; i++) { + meshName = this.mapping.buttonMeshNames[this.mapping.buttons[i]]; + if (!meshName) { + debug('Skipping unknown button at index: ' + i + ' with mapped name: ' + this.mapping.buttons[i]); + continue; + } + mesh = rootNode.getObjectByName(meshName); + if (!mesh) { + warn('Missing button mesh with name: ' + meshName); + continue; + } + meshInfo = { + index: i, + value: getImmediateChildByName(mesh, 'VALUE'), + pressed: getImmediateChildByName(mesh, 'PRESSED'), + unpressed: getImmediateChildByName(mesh, 'UNPRESSED') + }; + if (meshInfo.value && meshInfo.pressed && meshInfo.unpressed) { + loadedMeshInfo.buttonMeshes[this.mapping.buttons[i]] = meshInfo; + } else { + // If we didn't find the mesh, it simply means this button won't have transforms applied as mapped button value changes. + warn('Missing button submesh under mesh with name: ' + meshName + '(VALUE: ' + !!meshInfo.value + ', PRESSED: ' + !!meshInfo.pressed + ', UNPRESSED:' + !!meshInfo.unpressed + ')'); + } + } + + // Axis Meshes + for (i = 0; i < this.mapping.axisMeshNames.length; i++) { + meshName = this.mapping.axisMeshNames[i]; + if (!meshName) { + debug('Skipping unknown axis at index: ' + i); + continue; + } + mesh = rootNode.getObjectByName(meshName); + if (!mesh) { + warn('Missing axis mesh with name: ' + meshName); + continue; + } + meshInfo = { + index: i, + value: getImmediateChildByName(mesh, 'VALUE'), + min: getImmediateChildByName(mesh, 'MIN'), + max: getImmediateChildByName(mesh, 'MAX') + }; + if (meshInfo.value && meshInfo.min && meshInfo.max) { + loadedMeshInfo.axisMeshes[i] = meshInfo; + } else { + // If we didn't find the mesh, it simply means this axis won't have transforms applied as mapped axis values change. + warn('Missing axis submesh under mesh with name: ' + meshName + '(VALUE: ' + !!meshInfo.value + ', MIN: ' + !!meshInfo.min + ', MAX:' + !!meshInfo.max + ')'); + } + } + this.calculateRayOriginFromMesh(rootNode); + // Determine if the model has to be visible or not. + this.setModelVisibility(); + } + debug('Model load complete.'); + + // Look through only immediate children. This will return null if no mesh exists with the given name. + function getImmediateChildByName(object3d, value) { + for (var i = 0, l = object3d.children.length; i < l; i++) { + var obj = object3d.children[i]; + if (obj && obj['name'] === value) { + return obj; + } + } + return undefined; + } + }, + calculateRayOriginFromMesh: function () { + var quaternion = new THREE.Quaternion(); + return function (rootNode) { + var mesh; + + // Calculate the pointer pose (used for rays), by applying the world transform of th POINTER_POSE node + // in the glTF (assumes that root node is at world origin) + this.rayOrigin.origin.set(0, 0, 0); + this.rayOrigin.direction.set(0, 0, -1); + this.rayOrigin.createdFromMesh = true; + + // Try to read Pointing pose from the source model + mesh = rootNode.getObjectByName(this.mapping.pointingPoseMeshName); + if (mesh) { + var parent = rootNode.parent; + + // We need to read pose transforms accumulated from the root of the glTF, not the scene. + if (parent) { + rootNode.parent = null; + rootNode.updateMatrixWorld(true); + rootNode.parent = parent; + } + mesh.getWorldPosition(this.rayOrigin.origin); + mesh.getWorldQuaternion(quaternion); + this.rayOrigin.direction.applyQuaternion(quaternion); + + // Recalculate the world matrices now that the rootNode is re-attached to the parent. + if (parent) { + rootNode.updateMatrixWorld(true); + } + } else { + debug('Mesh does not contain pointing origin data, defaulting to none.'); + } + + // Emit event stating that our pointing ray is now accurate. + this.modelReady(); + }; + }(), + lerpAxisTransform: function () { + var quaternion = new THREE.Quaternion(); + return function (axis, axisValue) { + var axisMeshInfo = this.loadedMeshInfo.axisMeshes[axis]; + if (!axisMeshInfo) return; + var min = axisMeshInfo.min; + var max = axisMeshInfo.max; + var target = axisMeshInfo.value; + + // Convert from gamepad value range (-1 to +1) to lerp range (0 to 1) + var lerpValue = axisValue * 0.5 + 0.5; + target.setRotationFromQuaternion(quaternion.copy(min.quaternion).slerp(max.quaternion, lerpValue)); + target.position.lerpVectors(min.position, max.position, lerpValue); + }; + }(), + lerpButtonTransform: function () { + var quaternion = new THREE.Quaternion(); + return function (buttonName, buttonValue) { + var buttonMeshInfo = this.loadedMeshInfo.buttonMeshes[buttonName]; + if (!buttonMeshInfo) return; + var min = buttonMeshInfo.unpressed; + var max = buttonMeshInfo.pressed; + var target = buttonMeshInfo.value; + target.setRotationFromQuaternion(quaternion.copy(min.quaternion).slerp(max.quaternion, buttonValue)); + target.position.lerpVectors(min.position, max.position, buttonValue); + }; + }(), + modelReady: function () { + this.el.emit('controllermodelready', { + name: 'windows-motion-controls', + model: this.data.model, + rayOrigin: this.rayOrigin + }); + }, + onButtonChanged: function (evt) { + var buttonName = this.mapping.buttons[evt.detail.id]; + if (buttonName) { + // Update the button mesh transform + if (this.loadedMeshInfo && this.loadedMeshInfo.buttonMeshes) { + this.lerpButtonTransform(buttonName, evt.detail.state.value); + } + + // Only emit events for buttons that we know how to map from index to name + this.el.emit(buttonName + 'changed', evt.detail.state); + } + }, + onAxisMoved: function (evt) { + var numAxes = this.mapping.axisMeshNames.length; + + // Only attempt to update meshes if we have valid data. + if (this.loadedMeshInfo && this.loadedMeshInfo.axisMeshes) { + for (var axis = 0; axis < numAxes; axis++) { + // Update the button mesh transform + this.lerpAxisTransform(axis, evt.detail.axis[axis] || 0.0); + } + } + emitIfAxesChanged(this, this.mapping.axes, evt); + }, + setModelVisibility: function (visible) { + var model = this.el.getObject3D('mesh'); + if (!this.controllerPresent) { + return; + } + visible = visible !== undefined ? visible : this.modelVisible; + this.modelVisible = visible; + if (!model) { + return; + } + model.visible = visible; + } +}); + +/***/ }), + +/***/ "./src/constants/index.js": +/*!********************************!*\ + !*** ./src/constants/index.js ***! + \********************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +module.exports = { + AFRAME_CDN_ROOT: window.AFRAME_CDN_ROOT || 'https://cdn.aframe.io/', + AFRAME_INJECTED: 'aframe-injected', + DEFAULT_CAMERA_HEIGHT: 1.6, + DEFAULT_HANDEDNESS: 'right', + keyboardevent: __webpack_require__(/*! ./keyboardevent */ "./src/constants/keyboardevent.js") +}; + +/***/ }), + +/***/ "./src/constants/keyboardevent.js": +/*!****************************************!*\ + !*** ./src/constants/keyboardevent.js ***! + \****************************************/ +/***/ ((module) => { + +module.exports = { + // Tiny KeyboardEvent.code polyfill. + KEYCODE_TO_CODE: { + '38': 'ArrowUp', + '37': 'ArrowLeft', + '40': 'ArrowDown', + '39': 'ArrowRight', + '87': 'KeyW', + '65': 'KeyA', + '83': 'KeyS', + '68': 'KeyD' + } +}; + +/***/ }), + +/***/ "./src/core/a-assets.js": +/*!******************************!*\ + !*** ./src/core/a-assets.js ***! + \******************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +/* global customElements */ +var ANode = (__webpack_require__(/*! ./a-node */ "./src/core/a-node.js").ANode); +var bind = __webpack_require__(/*! ../utils/bind */ "./src/utils/bind.js"); +var debug = __webpack_require__(/*! ../utils/debug */ "./src/utils/debug.js"); +var THREE = __webpack_require__(/*! ../lib/three */ "./src/lib/three.js"); +var fileLoader = new THREE.FileLoader(); +var warn = debug('core:a-assets:warn'); + +/** + * Asset management system. Handles blocking on asset loading. + */ +class AAssets extends ANode { + constructor() { + super(); + this.isAssets = true; + this.fileLoader = fileLoader; + this.timeout = null; + } + connectedCallback() { + // Defer if DOM is not ready. + if (document.readyState !== 'complete') { + document.addEventListener('readystatechange', this.onReadyStateChange.bind(this)); + return; + } + this.doConnectedCallback(); + } + doConnectedCallback() { + var self = this; + var i; + var loaded = []; + var mediaEl; + var mediaEls; + var imgEl; + var imgEls; + var timeout; + super.connectedCallback(); + if (!this.parentNode.isScene) { + throw new Error(' must be a child of a .'); + } + + // Wait for s. + imgEls = this.querySelectorAll('img'); + for (i = 0; i < imgEls.length; i++) { + imgEl = fixUpMediaElement(imgEls[i]); + loaded.push(new Promise(function (resolve, reject) { + // Set in cache because we won't be needing to call three.js loader if we have. + // a loaded media element. + THREE.Cache.add(imgEls[i].getAttribute('src'), imgEl); + if (imgEl.complete) { + resolve(); + return; + } + imgEl.onload = resolve; + imgEl.onerror = reject; + })); + } + + // Wait for