(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