]> git.openstreetmap.org Git - rails.git/blobdiff - vendor/assets/iD/iD/mapillary-js/mapillary.module.js
Update to iD v2.20.0
[rails.git] / vendor / assets / iD / iD / mapillary-js / mapillary.module.js
diff --git a/vendor/assets/iD/iD/mapillary-js/mapillary.module.js b/vendor/assets/iD/iD/mapillary-js/mapillary.module.js
new file mode 100644 (file)
index 0000000..b08ff0b
--- /dev/null
@@ -0,0 +1,88193 @@
+/*! *****************************************************************************
+Copyright (c) Microsoft Corporation.
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.
+***************************************************************************** */
+/* global Reflect, Promise */
+
+var extendStatics = function(d, b) {
+    extendStatics = Object.setPrototypeOf ||
+        ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
+        function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
+    return extendStatics(d, b);
+};
+
+function __extends(d, b) {
+    extendStatics(d, b);
+    function __() { this.constructor = d; }
+    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+}
+
+/** PURE_IMPORTS_START  PURE_IMPORTS_END */
+function isFunction(x) {
+    return typeof x === 'function';
+}
+
+/** PURE_IMPORTS_START  PURE_IMPORTS_END */
+var _enable_super_gross_mode_that_will_cause_bad_things = false;
+var config = {
+    Promise: undefined,
+    set useDeprecatedSynchronousErrorHandling(value) {
+        if (value) {
+            var error = /*@__PURE__*/ new Error();
+            /*@__PURE__*/ console.warn('DEPRECATED! RxJS was set to use deprecated synchronous error handling behavior by code at: \n' + error.stack);
+        }
+        _enable_super_gross_mode_that_will_cause_bad_things = value;
+    },
+    get useDeprecatedSynchronousErrorHandling() {
+        return _enable_super_gross_mode_that_will_cause_bad_things;
+    },
+};
+
+/** PURE_IMPORTS_START  PURE_IMPORTS_END */
+function hostReportError(err) {
+    setTimeout(function () { throw err; }, 0);
+}
+
+/** PURE_IMPORTS_START _config,_util_hostReportError PURE_IMPORTS_END */
+var empty$1 = {
+    closed: true,
+    next: function (value) { },
+    error: function (err) {
+        if (config.useDeprecatedSynchronousErrorHandling) {
+            throw err;
+        }
+        else {
+            hostReportError(err);
+        }
+    },
+    complete: function () { }
+};
+
+/** PURE_IMPORTS_START  PURE_IMPORTS_END */
+var isArray$1 = /*@__PURE__*/ (function () { return Array.isArray || (function (x) { return x && typeof x.length === 'number'; }); })();
+
+/** PURE_IMPORTS_START  PURE_IMPORTS_END */
+function isObject$1(x) {
+    return x !== null && typeof x === 'object';
+}
+
+/** PURE_IMPORTS_START  PURE_IMPORTS_END */
+var UnsubscriptionErrorImpl = /*@__PURE__*/ (function () {
+    function UnsubscriptionErrorImpl(errors) {
+        Error.call(this);
+        this.message = errors ?
+            errors.length + " errors occurred during unsubscription:\n" + errors.map(function (err, i) { return i + 1 + ") " + err.toString(); }).join('\n  ') : '';
+        this.name = 'UnsubscriptionError';
+        this.errors = errors;
+        return this;
+    }
+    UnsubscriptionErrorImpl.prototype = /*@__PURE__*/ Object.create(Error.prototype);
+    return UnsubscriptionErrorImpl;
+})();
+var UnsubscriptionError = UnsubscriptionErrorImpl;
+
+/** PURE_IMPORTS_START _util_isArray,_util_isObject,_util_isFunction,_util_UnsubscriptionError PURE_IMPORTS_END */
+var Subscription = /*@__PURE__*/ (function () {
+    function Subscription(unsubscribe) {
+        this.closed = false;
+        this._parentOrParents = null;
+        this._subscriptions = null;
+        if (unsubscribe) {
+            this._ctorUnsubscribe = true;
+            this._unsubscribe = unsubscribe;
+        }
+    }
+    Subscription.prototype.unsubscribe = function () {
+        var errors;
+        if (this.closed) {
+            return;
+        }
+        var _a = this, _parentOrParents = _a._parentOrParents, _ctorUnsubscribe = _a._ctorUnsubscribe, _unsubscribe = _a._unsubscribe, _subscriptions = _a._subscriptions;
+        this.closed = true;
+        this._parentOrParents = null;
+        this._subscriptions = null;
+        if (_parentOrParents instanceof Subscription) {
+            _parentOrParents.remove(this);
+        }
+        else if (_parentOrParents !== null) {
+            for (var index = 0; index < _parentOrParents.length; ++index) {
+                var parent_1 = _parentOrParents[index];
+                parent_1.remove(this);
+            }
+        }
+        if (isFunction(_unsubscribe)) {
+            if (_ctorUnsubscribe) {
+                this._unsubscribe = undefined;
+            }
+            try {
+                _unsubscribe.call(this);
+            }
+            catch (e) {
+                errors = e instanceof UnsubscriptionError ? flattenUnsubscriptionErrors(e.errors) : [e];
+            }
+        }
+        if (isArray$1(_subscriptions)) {
+            var index = -1;
+            var len = _subscriptions.length;
+            while (++index < len) {
+                var sub = _subscriptions[index];
+                if (isObject$1(sub)) {
+                    try {
+                        sub.unsubscribe();
+                    }
+                    catch (e) {
+                        errors = errors || [];
+                        if (e instanceof UnsubscriptionError) {
+                            errors = errors.concat(flattenUnsubscriptionErrors(e.errors));
+                        }
+                        else {
+                            errors.push(e);
+                        }
+                    }
+                }
+            }
+        }
+        if (errors) {
+            throw new UnsubscriptionError(errors);
+        }
+    };
+    Subscription.prototype.add = function (teardown) {
+        var subscription = teardown;
+        if (!teardown) {
+            return Subscription.EMPTY;
+        }
+        switch (typeof teardown) {
+            case 'function':
+                subscription = new Subscription(teardown);
+            case 'object':
+                if (subscription === this || subscription.closed || typeof subscription.unsubscribe !== 'function') {
+                    return subscription;
+                }
+                else if (this.closed) {
+                    subscription.unsubscribe();
+                    return subscription;
+                }
+                else if (!(subscription instanceof Subscription)) {
+                    var tmp = subscription;
+                    subscription = new Subscription();
+                    subscription._subscriptions = [tmp];
+                }
+                break;
+            default: {
+                throw new Error('unrecognized teardown ' + teardown + ' added to Subscription.');
+            }
+        }
+        var _parentOrParents = subscription._parentOrParents;
+        if (_parentOrParents === null) {
+            subscription._parentOrParents = this;
+        }
+        else if (_parentOrParents instanceof Subscription) {
+            if (_parentOrParents === this) {
+                return subscription;
+            }
+            subscription._parentOrParents = [_parentOrParents, this];
+        }
+        else if (_parentOrParents.indexOf(this) === -1) {
+            _parentOrParents.push(this);
+        }
+        else {
+            return subscription;
+        }
+        var subscriptions = this._subscriptions;
+        if (subscriptions === null) {
+            this._subscriptions = [subscription];
+        }
+        else {
+            subscriptions.push(subscription);
+        }
+        return subscription;
+    };
+    Subscription.prototype.remove = function (subscription) {
+        var subscriptions = this._subscriptions;
+        if (subscriptions) {
+            var subscriptionIndex = subscriptions.indexOf(subscription);
+            if (subscriptionIndex !== -1) {
+                subscriptions.splice(subscriptionIndex, 1);
+            }
+        }
+    };
+    Subscription.EMPTY = (function (empty) {
+        empty.closed = true;
+        return empty;
+    }(new Subscription()));
+    return Subscription;
+}());
+function flattenUnsubscriptionErrors(errors) {
+    return errors.reduce(function (errs, err) { return errs.concat((err instanceof UnsubscriptionError) ? err.errors : err); }, []);
+}
+
+/** PURE_IMPORTS_START  PURE_IMPORTS_END */
+var rxSubscriber = /*@__PURE__*/ (function () {
+    return typeof Symbol === 'function'
+        ? /*@__PURE__*/ Symbol('rxSubscriber')
+        : '@@rxSubscriber_' + /*@__PURE__*/ Math.random();
+})();
+
+/** PURE_IMPORTS_START tslib,_util_isFunction,_Observer,_Subscription,_internal_symbol_rxSubscriber,_config,_util_hostReportError PURE_IMPORTS_END */
+var Subscriber = /*@__PURE__*/ (function (_super) {
+    __extends(Subscriber, _super);
+    function Subscriber(destinationOrNext, error, complete) {
+        var _this = _super.call(this) || this;
+        _this.syncErrorValue = null;
+        _this.syncErrorThrown = false;
+        _this.syncErrorThrowable = false;
+        _this.isStopped = false;
+        switch (arguments.length) {
+            case 0:
+                _this.destination = empty$1;
+                break;
+            case 1:
+                if (!destinationOrNext) {
+                    _this.destination = empty$1;
+                    break;
+                }
+                if (typeof destinationOrNext === 'object') {
+                    if (destinationOrNext instanceof Subscriber) {
+                        _this.syncErrorThrowable = destinationOrNext.syncErrorThrowable;
+                        _this.destination = destinationOrNext;
+                        destinationOrNext.add(_this);
+                    }
+                    else {
+                        _this.syncErrorThrowable = true;
+                        _this.destination = new SafeSubscriber(_this, destinationOrNext);
+                    }
+                    break;
+                }
+            default:
+                _this.syncErrorThrowable = true;
+                _this.destination = new SafeSubscriber(_this, destinationOrNext, error, complete);
+                break;
+        }
+        return _this;
+    }
+    Subscriber.prototype[rxSubscriber] = function () { return this; };
+    Subscriber.create = function (next, error, complete) {
+        var subscriber = new Subscriber(next, error, complete);
+        subscriber.syncErrorThrowable = false;
+        return subscriber;
+    };
+    Subscriber.prototype.next = function (value) {
+        if (!this.isStopped) {
+            this._next(value);
+        }
+    };
+    Subscriber.prototype.error = function (err) {
+        if (!this.isStopped) {
+            this.isStopped = true;
+            this._error(err);
+        }
+    };
+    Subscriber.prototype.complete = function () {
+        if (!this.isStopped) {
+            this.isStopped = true;
+            this._complete();
+        }
+    };
+    Subscriber.prototype.unsubscribe = function () {
+        if (this.closed) {
+            return;
+        }
+        this.isStopped = true;
+        _super.prototype.unsubscribe.call(this);
+    };
+    Subscriber.prototype._next = function (value) {
+        this.destination.next(value);
+    };
+    Subscriber.prototype._error = function (err) {
+        this.destination.error(err);
+        this.unsubscribe();
+    };
+    Subscriber.prototype._complete = function () {
+        this.destination.complete();
+        this.unsubscribe();
+    };
+    Subscriber.prototype._unsubscribeAndRecycle = function () {
+        var _parentOrParents = this._parentOrParents;
+        this._parentOrParents = null;
+        this.unsubscribe();
+        this.closed = false;
+        this.isStopped = false;
+        this._parentOrParents = _parentOrParents;
+        return this;
+    };
+    return Subscriber;
+}(Subscription));
+var SafeSubscriber = /*@__PURE__*/ (function (_super) {
+    __extends(SafeSubscriber, _super);
+    function SafeSubscriber(_parentSubscriber, observerOrNext, error, complete) {
+        var _this = _super.call(this) || this;
+        _this._parentSubscriber = _parentSubscriber;
+        var next;
+        var context = _this;
+        if (isFunction(observerOrNext)) {
+            next = observerOrNext;
+        }
+        else if (observerOrNext) {
+            next = observerOrNext.next;
+            error = observerOrNext.error;
+            complete = observerOrNext.complete;
+            if (observerOrNext !== empty$1) {
+                context = Object.create(observerOrNext);
+                if (isFunction(context.unsubscribe)) {
+                    _this.add(context.unsubscribe.bind(context));
+                }
+                context.unsubscribe = _this.unsubscribe.bind(_this);
+            }
+        }
+        _this._context = context;
+        _this._next = next;
+        _this._error = error;
+        _this._complete = complete;
+        return _this;
+    }
+    SafeSubscriber.prototype.next = function (value) {
+        if (!this.isStopped && this._next) {
+            var _parentSubscriber = this._parentSubscriber;
+            if (!config.useDeprecatedSynchronousErrorHandling || !_parentSubscriber.syncErrorThrowable) {
+                this.__tryOrUnsub(this._next, value);
+            }
+            else if (this.__tryOrSetError(_parentSubscriber, this._next, value)) {
+                this.unsubscribe();
+            }
+        }
+    };
+    SafeSubscriber.prototype.error = function (err) {
+        if (!this.isStopped) {
+            var _parentSubscriber = this._parentSubscriber;
+            var useDeprecatedSynchronousErrorHandling = config.useDeprecatedSynchronousErrorHandling;
+            if (this._error) {
+                if (!useDeprecatedSynchronousErrorHandling || !_parentSubscriber.syncErrorThrowable) {
+                    this.__tryOrUnsub(this._error, err);
+                    this.unsubscribe();
+                }
+                else {
+                    this.__tryOrSetError(_parentSubscriber, this._error, err);
+                    this.unsubscribe();
+                }
+            }
+            else if (!_parentSubscriber.syncErrorThrowable) {
+                this.unsubscribe();
+                if (useDeprecatedSynchronousErrorHandling) {
+                    throw err;
+                }
+                hostReportError(err);
+            }
+            else {
+                if (useDeprecatedSynchronousErrorHandling) {
+                    _parentSubscriber.syncErrorValue = err;
+                    _parentSubscriber.syncErrorThrown = true;
+                }
+                else {
+                    hostReportError(err);
+                }
+                this.unsubscribe();
+            }
+        }
+    };
+    SafeSubscriber.prototype.complete = function () {
+        var _this = this;
+        if (!this.isStopped) {
+            var _parentSubscriber = this._parentSubscriber;
+            if (this._complete) {
+                var wrappedComplete = function () { return _this._complete.call(_this._context); };
+                if (!config.useDeprecatedSynchronousErrorHandling || !_parentSubscriber.syncErrorThrowable) {
+                    this.__tryOrUnsub(wrappedComplete);
+                    this.unsubscribe();
+                }
+                else {
+                    this.__tryOrSetError(_parentSubscriber, wrappedComplete);
+                    this.unsubscribe();
+                }
+            }
+            else {
+                this.unsubscribe();
+            }
+        }
+    };
+    SafeSubscriber.prototype.__tryOrUnsub = function (fn, value) {
+        try {
+            fn.call(this._context, value);
+        }
+        catch (err) {
+            this.unsubscribe();
+            if (config.useDeprecatedSynchronousErrorHandling) {
+                throw err;
+            }
+            else {
+                hostReportError(err);
+            }
+        }
+    };
+    SafeSubscriber.prototype.__tryOrSetError = function (parent, fn, value) {
+        if (!config.useDeprecatedSynchronousErrorHandling) {
+            throw new Error('bad call');
+        }
+        try {
+            fn.call(this._context, value);
+        }
+        catch (err) {
+            if (config.useDeprecatedSynchronousErrorHandling) {
+                parent.syncErrorValue = err;
+                parent.syncErrorThrown = true;
+                return true;
+            }
+            else {
+                hostReportError(err);
+                return true;
+            }
+        }
+        return false;
+    };
+    SafeSubscriber.prototype._unsubscribe = function () {
+        var _parentSubscriber = this._parentSubscriber;
+        this._context = null;
+        this._parentSubscriber = null;
+        _parentSubscriber.unsubscribe();
+    };
+    return SafeSubscriber;
+}(Subscriber));
+
+/** PURE_IMPORTS_START _Subscriber PURE_IMPORTS_END */
+function canReportError(observer) {
+    while (observer) {
+        var _a = observer, closed_1 = _a.closed, destination = _a.destination, isStopped = _a.isStopped;
+        if (closed_1 || isStopped) {
+            return false;
+        }
+        else if (destination && destination instanceof Subscriber) {
+            observer = destination;
+        }
+        else {
+            observer = null;
+        }
+    }
+    return true;
+}
+
+/** PURE_IMPORTS_START _Subscriber,_symbol_rxSubscriber,_Observer PURE_IMPORTS_END */
+function toSubscriber(nextOrObserver, error, complete) {
+    if (nextOrObserver) {
+        if (nextOrObserver instanceof Subscriber) {
+            return nextOrObserver;
+        }
+        if (nextOrObserver[rxSubscriber]) {
+            return nextOrObserver[rxSubscriber]();
+        }
+    }
+    if (!nextOrObserver && !error && !complete) {
+        return new Subscriber(empty$1);
+    }
+    return new Subscriber(nextOrObserver, error, complete);
+}
+
+/** PURE_IMPORTS_START  PURE_IMPORTS_END */
+var observable = /*@__PURE__*/ (function () { return typeof Symbol === 'function' && Symbol.observable || '@@observable'; })();
+
+/** PURE_IMPORTS_START  PURE_IMPORTS_END */
+function identity(x) {
+    return x;
+}
+
+/** PURE_IMPORTS_START _identity PURE_IMPORTS_END */
+function pipe() {
+    var fns = [];
+    for (var _i = 0; _i < arguments.length; _i++) {
+        fns[_i] = arguments[_i];
+    }
+    return pipeFromArray(fns);
+}
+function pipeFromArray(fns) {
+    if (fns.length === 0) {
+        return identity;
+    }
+    if (fns.length === 1) {
+        return fns[0];
+    }
+    return function piped(input) {
+        return fns.reduce(function (prev, fn) { return fn(prev); }, input);
+    };
+}
+
+/** PURE_IMPORTS_START _util_canReportError,_util_toSubscriber,_symbol_observable,_util_pipe,_config PURE_IMPORTS_END */
+var Observable = /*@__PURE__*/ (function () {
+    function Observable(subscribe) {
+        this._isScalar = false;
+        if (subscribe) {
+            this._subscribe = subscribe;
+        }
+    }
+    Observable.prototype.lift = function (operator) {
+        var observable = new Observable();
+        observable.source = this;
+        observable.operator = operator;
+        return observable;
+    };
+    Observable.prototype.subscribe = function (observerOrNext, error, complete) {
+        var operator = this.operator;
+        var sink = toSubscriber(observerOrNext, error, complete);
+        if (operator) {
+            sink.add(operator.call(sink, this.source));
+        }
+        else {
+            sink.add(this.source || (config.useDeprecatedSynchronousErrorHandling && !sink.syncErrorThrowable) ?
+                this._subscribe(sink) :
+                this._trySubscribe(sink));
+        }
+        if (config.useDeprecatedSynchronousErrorHandling) {
+            if (sink.syncErrorThrowable) {
+                sink.syncErrorThrowable = false;
+                if (sink.syncErrorThrown) {
+                    throw sink.syncErrorValue;
+                }
+            }
+        }
+        return sink;
+    };
+    Observable.prototype._trySubscribe = function (sink) {
+        try {
+            return this._subscribe(sink);
+        }
+        catch (err) {
+            if (config.useDeprecatedSynchronousErrorHandling) {
+                sink.syncErrorThrown = true;
+                sink.syncErrorValue = err;
+            }
+            if (canReportError(sink)) {
+                sink.error(err);
+            }
+            else {
+                console.warn(err);
+            }
+        }
+    };
+    Observable.prototype.forEach = function (next, promiseCtor) {
+        var _this = this;
+        promiseCtor = getPromiseCtor(promiseCtor);
+        return new promiseCtor(function (resolve, reject) {
+            var subscription;
+            subscription = _this.subscribe(function (value) {
+                try {
+                    next(value);
+                }
+                catch (err) {
+                    reject(err);
+                    if (subscription) {
+                        subscription.unsubscribe();
+                    }
+                }
+            }, reject, resolve);
+        });
+    };
+    Observable.prototype._subscribe = function (subscriber) {
+        var source = this.source;
+        return source && source.subscribe(subscriber);
+    };
+    Observable.prototype[observable] = function () {
+        return this;
+    };
+    Observable.prototype.pipe = function () {
+        var operations = [];
+        for (var _i = 0; _i < arguments.length; _i++) {
+            operations[_i] = arguments[_i];
+        }
+        if (operations.length === 0) {
+            return this;
+        }
+        return pipeFromArray(operations)(this);
+    };
+    Observable.prototype.toPromise = function (promiseCtor) {
+        var _this = this;
+        promiseCtor = getPromiseCtor(promiseCtor);
+        return new promiseCtor(function (resolve, reject) {
+            var value;
+            _this.subscribe(function (x) { return value = x; }, function (err) { return reject(err); }, function () { return resolve(value); });
+        });
+    };
+    Observable.create = function (subscribe) {
+        return new Observable(subscribe);
+    };
+    return Observable;
+}());
+function getPromiseCtor(promiseCtor) {
+    if (!promiseCtor) {
+        promiseCtor = Promise;
+    }
+    if (!promiseCtor) {
+        throw new Error('no Promise impl found');
+    }
+    return promiseCtor;
+}
+
+/** PURE_IMPORTS_START  PURE_IMPORTS_END */
+var ObjectUnsubscribedErrorImpl = /*@__PURE__*/ (function () {
+    function ObjectUnsubscribedErrorImpl() {
+        Error.call(this);
+        this.message = 'object unsubscribed';
+        this.name = 'ObjectUnsubscribedError';
+        return this;
+    }
+    ObjectUnsubscribedErrorImpl.prototype = /*@__PURE__*/ Object.create(Error.prototype);
+    return ObjectUnsubscribedErrorImpl;
+})();
+var ObjectUnsubscribedError = ObjectUnsubscribedErrorImpl;
+
+/** PURE_IMPORTS_START tslib,_Subscription PURE_IMPORTS_END */
+var SubjectSubscription = /*@__PURE__*/ (function (_super) {
+    __extends(SubjectSubscription, _super);
+    function SubjectSubscription(subject, subscriber) {
+        var _this = _super.call(this) || this;
+        _this.subject = subject;
+        _this.subscriber = subscriber;
+        _this.closed = false;
+        return _this;
+    }
+    SubjectSubscription.prototype.unsubscribe = function () {
+        if (this.closed) {
+            return;
+        }
+        this.closed = true;
+        var subject = this.subject;
+        var observers = subject.observers;
+        this.subject = null;
+        if (!observers || observers.length === 0 || subject.isStopped || subject.closed) {
+            return;
+        }
+        var subscriberIndex = observers.indexOf(this.subscriber);
+        if (subscriberIndex !== -1) {
+            observers.splice(subscriberIndex, 1);
+        }
+    };
+    return SubjectSubscription;
+}(Subscription));
+
+/** PURE_IMPORTS_START tslib,_Observable,_Subscriber,_Subscription,_util_ObjectUnsubscribedError,_SubjectSubscription,_internal_symbol_rxSubscriber PURE_IMPORTS_END */
+var SubjectSubscriber = /*@__PURE__*/ (function (_super) {
+    __extends(SubjectSubscriber, _super);
+    function SubjectSubscriber(destination) {
+        var _this = _super.call(this, destination) || this;
+        _this.destination = destination;
+        return _this;
+    }
+    return SubjectSubscriber;
+}(Subscriber));
+var Subject = /*@__PURE__*/ (function (_super) {
+    __extends(Subject, _super);
+    function Subject() {
+        var _this = _super.call(this) || this;
+        _this.observers = [];
+        _this.closed = false;
+        _this.isStopped = false;
+        _this.hasError = false;
+        _this.thrownError = null;
+        return _this;
+    }
+    Subject.prototype[rxSubscriber] = function () {
+        return new SubjectSubscriber(this);
+    };
+    Subject.prototype.lift = function (operator) {
+        var subject = new AnonymousSubject(this, this);
+        subject.operator = operator;
+        return subject;
+    };
+    Subject.prototype.next = function (value) {
+        if (this.closed) {
+            throw new ObjectUnsubscribedError();
+        }
+        if (!this.isStopped) {
+            var observers = this.observers;
+            var len = observers.length;
+            var copy = observers.slice();
+            for (var i = 0; i < len; i++) {
+                copy[i].next(value);
+            }
+        }
+    };
+    Subject.prototype.error = function (err) {
+        if (this.closed) {
+            throw new ObjectUnsubscribedError();
+        }
+        this.hasError = true;
+        this.thrownError = err;
+        this.isStopped = true;
+        var observers = this.observers;
+        var len = observers.length;
+        var copy = observers.slice();
+        for (var i = 0; i < len; i++) {
+            copy[i].error(err);
+        }
+        this.observers.length = 0;
+    };
+    Subject.prototype.complete = function () {
+        if (this.closed) {
+            throw new ObjectUnsubscribedError();
+        }
+        this.isStopped = true;
+        var observers = this.observers;
+        var len = observers.length;
+        var copy = observers.slice();
+        for (var i = 0; i < len; i++) {
+            copy[i].complete();
+        }
+        this.observers.length = 0;
+    };
+    Subject.prototype.unsubscribe = function () {
+        this.isStopped = true;
+        this.closed = true;
+        this.observers = null;
+    };
+    Subject.prototype._trySubscribe = function (subscriber) {
+        if (this.closed) {
+            throw new ObjectUnsubscribedError();
+        }
+        else {
+            return _super.prototype._trySubscribe.call(this, subscriber);
+        }
+    };
+    Subject.prototype._subscribe = function (subscriber) {
+        if (this.closed) {
+            throw new ObjectUnsubscribedError();
+        }
+        else if (this.hasError) {
+            subscriber.error(this.thrownError);
+            return Subscription.EMPTY;
+        }
+        else if (this.isStopped) {
+            subscriber.complete();
+            return Subscription.EMPTY;
+        }
+        else {
+            this.observers.push(subscriber);
+            return new SubjectSubscription(this, subscriber);
+        }
+    };
+    Subject.prototype.asObservable = function () {
+        var observable = new Observable();
+        observable.source = this;
+        return observable;
+    };
+    Subject.create = function (destination, source) {
+        return new AnonymousSubject(destination, source);
+    };
+    return Subject;
+}(Observable));
+var AnonymousSubject = /*@__PURE__*/ (function (_super) {
+    __extends(AnonymousSubject, _super);
+    function AnonymousSubject(destination, source) {
+        var _this = _super.call(this) || this;
+        _this.destination = destination;
+        _this.source = source;
+        return _this;
+    }
+    AnonymousSubject.prototype.next = function (value) {
+        var destination = this.destination;
+        if (destination && destination.next) {
+            destination.next(value);
+        }
+    };
+    AnonymousSubject.prototype.error = function (err) {
+        var destination = this.destination;
+        if (destination && destination.error) {
+            this.destination.error(err);
+        }
+    };
+    AnonymousSubject.prototype.complete = function () {
+        var destination = this.destination;
+        if (destination && destination.complete) {
+            this.destination.complete();
+        }
+    };
+    AnonymousSubject.prototype._subscribe = function (subscriber) {
+        var source = this.source;
+        if (source) {
+            return this.source.subscribe(subscriber);
+        }
+        else {
+            return Subscription.EMPTY;
+        }
+    };
+    return AnonymousSubject;
+}(Subject));
+
+/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */
+function refCount() {
+    return function refCountOperatorFunction(source) {
+        return source.lift(new RefCountOperator(source));
+    };
+}
+var RefCountOperator = /*@__PURE__*/ (function () {
+    function RefCountOperator(connectable) {
+        this.connectable = connectable;
+    }
+    RefCountOperator.prototype.call = function (subscriber, source) {
+        var connectable = this.connectable;
+        connectable._refCount++;
+        var refCounter = new RefCountSubscriber(subscriber, connectable);
+        var subscription = source.subscribe(refCounter);
+        if (!refCounter.closed) {
+            refCounter.connection = connectable.connect();
+        }
+        return subscription;
+    };
+    return RefCountOperator;
+}());
+var RefCountSubscriber = /*@__PURE__*/ (function (_super) {
+    __extends(RefCountSubscriber, _super);
+    function RefCountSubscriber(destination, connectable) {
+        var _this = _super.call(this, destination) || this;
+        _this.connectable = connectable;
+        return _this;
+    }
+    RefCountSubscriber.prototype._unsubscribe = function () {
+        var connectable = this.connectable;
+        if (!connectable) {
+            this.connection = null;
+            return;
+        }
+        this.connectable = null;
+        var refCount = connectable._refCount;
+        if (refCount <= 0) {
+            this.connection = null;
+            return;
+        }
+        connectable._refCount = refCount - 1;
+        if (refCount > 1) {
+            this.connection = null;
+            return;
+        }
+        var connection = this.connection;
+        var sharedConnection = connectable._connection;
+        this.connection = null;
+        if (sharedConnection && (!connection || sharedConnection === connection)) {
+            sharedConnection.unsubscribe();
+        }
+    };
+    return RefCountSubscriber;
+}(Subscriber));
+
+/** PURE_IMPORTS_START tslib,_Subject,_Observable,_Subscriber,_Subscription,_operators_refCount PURE_IMPORTS_END */
+var ConnectableObservable = /*@__PURE__*/ (function (_super) {
+    __extends(ConnectableObservable, _super);
+    function ConnectableObservable(source, subjectFactory) {
+        var _this = _super.call(this) || this;
+        _this.source = source;
+        _this.subjectFactory = subjectFactory;
+        _this._refCount = 0;
+        _this._isComplete = false;
+        return _this;
+    }
+    ConnectableObservable.prototype._subscribe = function (subscriber) {
+        return this.getSubject().subscribe(subscriber);
+    };
+    ConnectableObservable.prototype.getSubject = function () {
+        var subject = this._subject;
+        if (!subject || subject.isStopped) {
+            this._subject = this.subjectFactory();
+        }
+        return this._subject;
+    };
+    ConnectableObservable.prototype.connect = function () {
+        var connection = this._connection;
+        if (!connection) {
+            this._isComplete = false;
+            connection = this._connection = new Subscription();
+            connection.add(this.source
+                .subscribe(new ConnectableSubscriber(this.getSubject(), this)));
+            if (connection.closed) {
+                this._connection = null;
+                connection = Subscription.EMPTY;
+            }
+        }
+        return connection;
+    };
+    ConnectableObservable.prototype.refCount = function () {
+        return refCount()(this);
+    };
+    return ConnectableObservable;
+}(Observable));
+var connectableObservableDescriptor = /*@__PURE__*/ (function () {
+    var connectableProto = ConnectableObservable.prototype;
+    return {
+        operator: { value: null },
+        _refCount: { value: 0, writable: true },
+        _subject: { value: null, writable: true },
+        _connection: { value: null, writable: true },
+        _subscribe: { value: connectableProto._subscribe },
+        _isComplete: { value: connectableProto._isComplete, writable: true },
+        getSubject: { value: connectableProto.getSubject },
+        connect: { value: connectableProto.connect },
+        refCount: { value: connectableProto.refCount }
+    };
+})();
+var ConnectableSubscriber = /*@__PURE__*/ (function (_super) {
+    __extends(ConnectableSubscriber, _super);
+    function ConnectableSubscriber(destination, connectable) {
+        var _this = _super.call(this, destination) || this;
+        _this.connectable = connectable;
+        return _this;
+    }
+    ConnectableSubscriber.prototype._error = function (err) {
+        this._unsubscribe();
+        _super.prototype._error.call(this, err);
+    };
+    ConnectableSubscriber.prototype._complete = function () {
+        this.connectable._isComplete = true;
+        this._unsubscribe();
+        _super.prototype._complete.call(this);
+    };
+    ConnectableSubscriber.prototype._unsubscribe = function () {
+        var connectable = this.connectable;
+        if (connectable) {
+            this.connectable = null;
+            var connection = connectable._connection;
+            connectable._refCount = 0;
+            connectable._subject = null;
+            connectable._connection = null;
+            if (connection) {
+                connection.unsubscribe();
+            }
+        }
+    };
+    return ConnectableSubscriber;
+}(SubjectSubscriber));
+
+/** PURE_IMPORTS_START tslib,_Subject,_util_ObjectUnsubscribedError PURE_IMPORTS_END */
+var BehaviorSubject = /*@__PURE__*/ (function (_super) {
+    __extends(BehaviorSubject, _super);
+    function BehaviorSubject(_value) {
+        var _this = _super.call(this) || this;
+        _this._value = _value;
+        return _this;
+    }
+    Object.defineProperty(BehaviorSubject.prototype, "value", {
+        get: function () {
+            return this.getValue();
+        },
+        enumerable: true,
+        configurable: true
+    });
+    BehaviorSubject.prototype._subscribe = function (subscriber) {
+        var subscription = _super.prototype._subscribe.call(this, subscriber);
+        if (subscription && !subscription.closed) {
+            subscriber.next(this._value);
+        }
+        return subscription;
+    };
+    BehaviorSubject.prototype.getValue = function () {
+        if (this.hasError) {
+            throw this.thrownError;
+        }
+        else if (this.closed) {
+            throw new ObjectUnsubscribedError();
+        }
+        else {
+            return this._value;
+        }
+    };
+    BehaviorSubject.prototype.next = function (value) {
+        _super.prototype.next.call(this, this._value = value);
+    };
+    return BehaviorSubject;
+}(Subject));
+
+/** PURE_IMPORTS_START tslib,_Subscription PURE_IMPORTS_END */
+var Action = /*@__PURE__*/ (function (_super) {
+    __extends(Action, _super);
+    function Action(scheduler, work) {
+        return _super.call(this) || this;
+    }
+    Action.prototype.schedule = function (state, delay) {
+        return this;
+    };
+    return Action;
+}(Subscription));
+
+/** PURE_IMPORTS_START tslib,_Action PURE_IMPORTS_END */
+var AsyncAction = /*@__PURE__*/ (function (_super) {
+    __extends(AsyncAction, _super);
+    function AsyncAction(scheduler, work) {
+        var _this = _super.call(this, scheduler, work) || this;
+        _this.scheduler = scheduler;
+        _this.work = work;
+        _this.pending = false;
+        return _this;
+    }
+    AsyncAction.prototype.schedule = function (state, delay) {
+        if (delay === void 0) {
+            delay = 0;
+        }
+        if (this.closed) {
+            return this;
+        }
+        this.state = state;
+        var id = this.id;
+        var scheduler = this.scheduler;
+        if (id != null) {
+            this.id = this.recycleAsyncId(scheduler, id, delay);
+        }
+        this.pending = true;
+        this.delay = delay;
+        this.id = this.id || this.requestAsyncId(scheduler, this.id, delay);
+        return this;
+    };
+    AsyncAction.prototype.requestAsyncId = function (scheduler, id, delay) {
+        if (delay === void 0) {
+            delay = 0;
+        }
+        return setInterval(scheduler.flush.bind(scheduler, this), delay);
+    };
+    AsyncAction.prototype.recycleAsyncId = function (scheduler, id, delay) {
+        if (delay === void 0) {
+            delay = 0;
+        }
+        if (delay !== null && this.delay === delay && this.pending === false) {
+            return id;
+        }
+        clearInterval(id);
+        return undefined;
+    };
+    AsyncAction.prototype.execute = function (state, delay) {
+        if (this.closed) {
+            return new Error('executing a cancelled action');
+        }
+        this.pending = false;
+        var error = this._execute(state, delay);
+        if (error) {
+            return error;
+        }
+        else if (this.pending === false && this.id != null) {
+            this.id = this.recycleAsyncId(this.scheduler, this.id, null);
+        }
+    };
+    AsyncAction.prototype._execute = function (state, delay) {
+        var errored = false;
+        var errorValue = undefined;
+        try {
+            this.work(state);
+        }
+        catch (e) {
+            errored = true;
+            errorValue = !!e && e || new Error(e);
+        }
+        if (errored) {
+            this.unsubscribe();
+            return errorValue;
+        }
+    };
+    AsyncAction.prototype._unsubscribe = function () {
+        var id = this.id;
+        var scheduler = this.scheduler;
+        var actions = scheduler.actions;
+        var index = actions.indexOf(this);
+        this.work = null;
+        this.state = null;
+        this.pending = false;
+        this.scheduler = null;
+        if (index !== -1) {
+            actions.splice(index, 1);
+        }
+        if (id != null) {
+            this.id = this.recycleAsyncId(scheduler, id, null);
+        }
+        this.delay = null;
+    };
+    return AsyncAction;
+}(Action));
+
+/** PURE_IMPORTS_START tslib,_AsyncAction PURE_IMPORTS_END */
+var QueueAction = /*@__PURE__*/ (function (_super) {
+    __extends(QueueAction, _super);
+    function QueueAction(scheduler, work) {
+        var _this = _super.call(this, scheduler, work) || this;
+        _this.scheduler = scheduler;
+        _this.work = work;
+        return _this;
+    }
+    QueueAction.prototype.schedule = function (state, delay) {
+        if (delay === void 0) {
+            delay = 0;
+        }
+        if (delay > 0) {
+            return _super.prototype.schedule.call(this, state, delay);
+        }
+        this.delay = delay;
+        this.state = state;
+        this.scheduler.flush(this);
+        return this;
+    };
+    QueueAction.prototype.execute = function (state, delay) {
+        return (delay > 0 || this.closed) ?
+            _super.prototype.execute.call(this, state, delay) :
+            this._execute(state, delay);
+    };
+    QueueAction.prototype.requestAsyncId = function (scheduler, id, delay) {
+        if (delay === void 0) {
+            delay = 0;
+        }
+        if ((delay !== null && delay > 0) || (delay === null && this.delay > 0)) {
+            return _super.prototype.requestAsyncId.call(this, scheduler, id, delay);
+        }
+        return scheduler.flush(this);
+    };
+    return QueueAction;
+}(AsyncAction));
+
+var Scheduler = /*@__PURE__*/ (function () {
+    function Scheduler(SchedulerAction, now) {
+        if (now === void 0) {
+            now = Scheduler.now;
+        }
+        this.SchedulerAction = SchedulerAction;
+        this.now = now;
+    }
+    Scheduler.prototype.schedule = function (work, delay, state) {
+        if (delay === void 0) {
+            delay = 0;
+        }
+        return new this.SchedulerAction(this, work).schedule(state, delay);
+    };
+    Scheduler.now = function () { return Date.now(); };
+    return Scheduler;
+}());
+
+/** PURE_IMPORTS_START tslib,_Scheduler PURE_IMPORTS_END */
+var AsyncScheduler = /*@__PURE__*/ (function (_super) {
+    __extends(AsyncScheduler, _super);
+    function AsyncScheduler(SchedulerAction, now) {
+        if (now === void 0) {
+            now = Scheduler.now;
+        }
+        var _this = _super.call(this, SchedulerAction, function () {
+            if (AsyncScheduler.delegate && AsyncScheduler.delegate !== _this) {
+                return AsyncScheduler.delegate.now();
+            }
+            else {
+                return now();
+            }
+        }) || this;
+        _this.actions = [];
+        _this.active = false;
+        _this.scheduled = undefined;
+        return _this;
+    }
+    AsyncScheduler.prototype.schedule = function (work, delay, state) {
+        if (delay === void 0) {
+            delay = 0;
+        }
+        if (AsyncScheduler.delegate && AsyncScheduler.delegate !== this) {
+            return AsyncScheduler.delegate.schedule(work, delay, state);
+        }
+        else {
+            return _super.prototype.schedule.call(this, work, delay, state);
+        }
+    };
+    AsyncScheduler.prototype.flush = function (action) {
+        var actions = this.actions;
+        if (this.active) {
+            actions.push(action);
+            return;
+        }
+        var error;
+        this.active = true;
+        do {
+            if (error = action.execute(action.state, action.delay)) {
+                break;
+            }
+        } while (action = actions.shift());
+        this.active = false;
+        if (error) {
+            while (action = actions.shift()) {
+                action.unsubscribe();
+            }
+            throw error;
+        }
+    };
+    return AsyncScheduler;
+}(Scheduler));
+
+/** PURE_IMPORTS_START tslib,_AsyncScheduler PURE_IMPORTS_END */
+var QueueScheduler = /*@__PURE__*/ (function (_super) {
+    __extends(QueueScheduler, _super);
+    function QueueScheduler() {
+        return _super !== null && _super.apply(this, arguments) || this;
+    }
+    return QueueScheduler;
+}(AsyncScheduler));
+
+/** PURE_IMPORTS_START _QueueAction,_QueueScheduler PURE_IMPORTS_END */
+var queueScheduler = /*@__PURE__*/ new QueueScheduler(QueueAction);
+var queue = queueScheduler;
+
+/** PURE_IMPORTS_START _Observable PURE_IMPORTS_END */
+var EMPTY$1 = /*@__PURE__*/ new Observable(function (subscriber) { return subscriber.complete(); });
+function empty(scheduler) {
+    return scheduler ? emptyScheduled(scheduler) : EMPTY$1;
+}
+function emptyScheduled(scheduler) {
+    return new Observable(function (subscriber) { return scheduler.schedule(function () { return subscriber.complete(); }); });
+}
+
+/** PURE_IMPORTS_START  PURE_IMPORTS_END */
+function isScheduler(value) {
+    return value && typeof value.schedule === 'function';
+}
+
+/** PURE_IMPORTS_START  PURE_IMPORTS_END */
+var subscribeToArray = function (array) {
+    return function (subscriber) {
+        for (var i = 0, len = array.length; i < len && !subscriber.closed; i++) {
+            subscriber.next(array[i]);
+        }
+        subscriber.complete();
+    };
+};
+
+/** PURE_IMPORTS_START _Observable,_Subscription PURE_IMPORTS_END */
+function scheduleArray(input, scheduler) {
+    return new Observable(function (subscriber) {
+        var sub = new Subscription();
+        var i = 0;
+        sub.add(scheduler.schedule(function () {
+            if (i === input.length) {
+                subscriber.complete();
+                return;
+            }
+            subscriber.next(input[i++]);
+            if (!subscriber.closed) {
+                sub.add(this.schedule());
+            }
+        }));
+        return sub;
+    });
+}
+
+/** PURE_IMPORTS_START _Observable,_util_subscribeToArray,_scheduled_scheduleArray PURE_IMPORTS_END */
+function fromArray(input, scheduler) {
+    if (!scheduler) {
+        return new Observable(subscribeToArray(input));
+    }
+    else {
+        return scheduleArray(input, scheduler);
+    }
+}
+
+/** PURE_IMPORTS_START _util_isScheduler,_fromArray,_scheduled_scheduleArray PURE_IMPORTS_END */
+function of() {
+    var args = [];
+    for (var _i = 0; _i < arguments.length; _i++) {
+        args[_i] = arguments[_i];
+    }
+    var scheduler = args[args.length - 1];
+    if (isScheduler(scheduler)) {
+        args.pop();
+        return scheduleArray(args, scheduler);
+    }
+    else {
+        return fromArray(args);
+    }
+}
+
+/** PURE_IMPORTS_START _Observable PURE_IMPORTS_END */
+function throwError(error, scheduler) {
+    if (!scheduler) {
+        return new Observable(function (subscriber) { return subscriber.error(error); });
+    }
+    else {
+        return new Observable(function (subscriber) { return scheduler.schedule(dispatch$1, 0, { error: error, subscriber: subscriber }); });
+    }
+}
+function dispatch$1(_a) {
+    var error = _a.error, subscriber = _a.subscriber;
+    subscriber.error(error);
+}
+
+/** PURE_IMPORTS_START _observable_empty,_observable_of,_observable_throwError PURE_IMPORTS_END */
+var Notification = /*@__PURE__*/ (function () {
+    function Notification(kind, value, error) {
+        this.kind = kind;
+        this.value = value;
+        this.error = error;
+        this.hasValue = kind === 'N';
+    }
+    Notification.prototype.observe = function (observer) {
+        switch (this.kind) {
+            case 'N':
+                return observer.next && observer.next(this.value);
+            case 'E':
+                return observer.error && observer.error(this.error);
+            case 'C':
+                return observer.complete && observer.complete();
+        }
+    };
+    Notification.prototype.do = function (next, error, complete) {
+        var kind = this.kind;
+        switch (kind) {
+            case 'N':
+                return next && next(this.value);
+            case 'E':
+                return error && error(this.error);
+            case 'C':
+                return complete && complete();
+        }
+    };
+    Notification.prototype.accept = function (nextOrObserver, error, complete) {
+        if (nextOrObserver && typeof nextOrObserver.next === 'function') {
+            return this.observe(nextOrObserver);
+        }
+        else {
+            return this.do(nextOrObserver, error, complete);
+        }
+    };
+    Notification.prototype.toObservable = function () {
+        var kind = this.kind;
+        switch (kind) {
+            case 'N':
+                return of(this.value);
+            case 'E':
+                return throwError(this.error);
+            case 'C':
+                return empty();
+        }
+        throw new Error('unexpected notification kind value');
+    };
+    Notification.createNext = function (value) {
+        if (typeof value !== 'undefined') {
+            return new Notification('N', value);
+        }
+        return Notification.undefinedValueNotification;
+    };
+    Notification.createError = function (err) {
+        return new Notification('E', undefined, err);
+    };
+    Notification.createComplete = function () {
+        return Notification.completeNotification;
+    };
+    Notification.completeNotification = new Notification('C');
+    Notification.undefinedValueNotification = new Notification('N', undefined);
+    return Notification;
+}());
+
+/** PURE_IMPORTS_START tslib,_Subscriber,_Notification PURE_IMPORTS_END */
+var ObserveOnSubscriber = /*@__PURE__*/ (function (_super) {
+    __extends(ObserveOnSubscriber, _super);
+    function ObserveOnSubscriber(destination, scheduler, delay) {
+        if (delay === void 0) {
+            delay = 0;
+        }
+        var _this = _super.call(this, destination) || this;
+        _this.scheduler = scheduler;
+        _this.delay = delay;
+        return _this;
+    }
+    ObserveOnSubscriber.dispatch = function (arg) {
+        var notification = arg.notification, destination = arg.destination;
+        notification.observe(destination);
+        this.unsubscribe();
+    };
+    ObserveOnSubscriber.prototype.scheduleMessage = function (notification) {
+        var destination = this.destination;
+        destination.add(this.scheduler.schedule(ObserveOnSubscriber.dispatch, this.delay, new ObserveOnMessage(notification, this.destination)));
+    };
+    ObserveOnSubscriber.prototype._next = function (value) {
+        this.scheduleMessage(Notification.createNext(value));
+    };
+    ObserveOnSubscriber.prototype._error = function (err) {
+        this.scheduleMessage(Notification.createError(err));
+        this.unsubscribe();
+    };
+    ObserveOnSubscriber.prototype._complete = function () {
+        this.scheduleMessage(Notification.createComplete());
+        this.unsubscribe();
+    };
+    return ObserveOnSubscriber;
+}(Subscriber));
+var ObserveOnMessage = /*@__PURE__*/ (function () {
+    function ObserveOnMessage(notification, destination) {
+        this.notification = notification;
+        this.destination = destination;
+    }
+    return ObserveOnMessage;
+}());
+
+/** PURE_IMPORTS_START tslib,_Subject,_scheduler_queue,_Subscription,_operators_observeOn,_util_ObjectUnsubscribedError,_SubjectSubscription PURE_IMPORTS_END */
+var ReplaySubject = /*@__PURE__*/ (function (_super) {
+    __extends(ReplaySubject, _super);
+    function ReplaySubject(bufferSize, windowTime, scheduler) {
+        if (bufferSize === void 0) {
+            bufferSize = Number.POSITIVE_INFINITY;
+        }
+        if (windowTime === void 0) {
+            windowTime = Number.POSITIVE_INFINITY;
+        }
+        var _this = _super.call(this) || this;
+        _this.scheduler = scheduler;
+        _this._events = [];
+        _this._infiniteTimeWindow = false;
+        _this._bufferSize = bufferSize < 1 ? 1 : bufferSize;
+        _this._windowTime = windowTime < 1 ? 1 : windowTime;
+        if (windowTime === Number.POSITIVE_INFINITY) {
+            _this._infiniteTimeWindow = true;
+            _this.next = _this.nextInfiniteTimeWindow;
+        }
+        else {
+            _this.next = _this.nextTimeWindow;
+        }
+        return _this;
+    }
+    ReplaySubject.prototype.nextInfiniteTimeWindow = function (value) {
+        if (!this.isStopped) {
+            var _events = this._events;
+            _events.push(value);
+            if (_events.length > this._bufferSize) {
+                _events.shift();
+            }
+        }
+        _super.prototype.next.call(this, value);
+    };
+    ReplaySubject.prototype.nextTimeWindow = function (value) {
+        if (!this.isStopped) {
+            this._events.push(new ReplayEvent(this._getNow(), value));
+            this._trimBufferThenGetEvents();
+        }
+        _super.prototype.next.call(this, value);
+    };
+    ReplaySubject.prototype._subscribe = function (subscriber) {
+        var _infiniteTimeWindow = this._infiniteTimeWindow;
+        var _events = _infiniteTimeWindow ? this._events : this._trimBufferThenGetEvents();
+        var scheduler = this.scheduler;
+        var len = _events.length;
+        var subscription;
+        if (this.closed) {
+            throw new ObjectUnsubscribedError();
+        }
+        else if (this.isStopped || this.hasError) {
+            subscription = Subscription.EMPTY;
+        }
+        else {
+            this.observers.push(subscriber);
+            subscription = new SubjectSubscription(this, subscriber);
+        }
+        if (scheduler) {
+            subscriber.add(subscriber = new ObserveOnSubscriber(subscriber, scheduler));
+        }
+        if (_infiniteTimeWindow) {
+            for (var i = 0; i < len && !subscriber.closed; i++) {
+                subscriber.next(_events[i]);
+            }
+        }
+        else {
+            for (var i = 0; i < len && !subscriber.closed; i++) {
+                subscriber.next(_events[i].value);
+            }
+        }
+        if (this.hasError) {
+            subscriber.error(this.thrownError);
+        }
+        else if (this.isStopped) {
+            subscriber.complete();
+        }
+        return subscription;
+    };
+    ReplaySubject.prototype._getNow = function () {
+        return (this.scheduler || queue).now();
+    };
+    ReplaySubject.prototype._trimBufferThenGetEvents = function () {
+        var now = this._getNow();
+        var _bufferSize = this._bufferSize;
+        var _windowTime = this._windowTime;
+        var _events = this._events;
+        var eventsCount = _events.length;
+        var spliceCount = 0;
+        while (spliceCount < eventsCount) {
+            if ((now - _events[spliceCount].time) < _windowTime) {
+                break;
+            }
+            spliceCount++;
+        }
+        if (eventsCount > _bufferSize) {
+            spliceCount = Math.max(spliceCount, eventsCount - _bufferSize);
+        }
+        if (spliceCount > 0) {
+            _events.splice(0, spliceCount);
+        }
+        return _events;
+    };
+    return ReplaySubject;
+}(Subject));
+var ReplayEvent = /*@__PURE__*/ (function () {
+    function ReplayEvent(time, value) {
+        this.time = time;
+        this.value = value;
+    }
+    return ReplayEvent;
+}());
+
+/** PURE_IMPORTS_START _AsyncAction,_AsyncScheduler PURE_IMPORTS_END */
+var asyncScheduler = /*@__PURE__*/ new AsyncScheduler(AsyncAction);
+var async = asyncScheduler;
+
+/** PURE_IMPORTS_START  PURE_IMPORTS_END */
+function noop() { }
+
+/** PURE_IMPORTS_START  PURE_IMPORTS_END */
+var ArgumentOutOfRangeErrorImpl = /*@__PURE__*/ (function () {
+    function ArgumentOutOfRangeErrorImpl() {
+        Error.call(this);
+        this.message = 'argument out of range';
+        this.name = 'ArgumentOutOfRangeError';
+        return this;
+    }
+    ArgumentOutOfRangeErrorImpl.prototype = /*@__PURE__*/ Object.create(Error.prototype);
+    return ArgumentOutOfRangeErrorImpl;
+})();
+var ArgumentOutOfRangeError = ArgumentOutOfRangeErrorImpl;
+
+/** PURE_IMPORTS_START  PURE_IMPORTS_END */
+var EmptyErrorImpl = /*@__PURE__*/ (function () {
+    function EmptyErrorImpl() {
+        Error.call(this);
+        this.message = 'no elements in sequence';
+        this.name = 'EmptyError';
+        return this;
+    }
+    EmptyErrorImpl.prototype = /*@__PURE__*/ Object.create(Error.prototype);
+    return EmptyErrorImpl;
+})();
+var EmptyError = EmptyErrorImpl;
+
+/** PURE_IMPORTS_START  PURE_IMPORTS_END */
+var TimeoutErrorImpl = /*@__PURE__*/ (function () {
+    function TimeoutErrorImpl() {
+        Error.call(this);
+        this.message = 'Timeout has occurred';
+        this.name = 'TimeoutError';
+        return this;
+    }
+    TimeoutErrorImpl.prototype = /*@__PURE__*/ Object.create(Error.prototype);
+    return TimeoutErrorImpl;
+})();
+var TimeoutError = TimeoutErrorImpl;
+
+/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */
+function map(project, thisArg) {
+    return function mapOperation(source) {
+        if (typeof project !== 'function') {
+            throw new TypeError('argument is not a function. Are you looking for `mapTo()`?');
+        }
+        return source.lift(new MapOperator(project, thisArg));
+    };
+}
+var MapOperator = /*@__PURE__*/ (function () {
+    function MapOperator(project, thisArg) {
+        this.project = project;
+        this.thisArg = thisArg;
+    }
+    MapOperator.prototype.call = function (subscriber, source) {
+        return source.subscribe(new MapSubscriber(subscriber, this.project, this.thisArg));
+    };
+    return MapOperator;
+}());
+var MapSubscriber = /*@__PURE__*/ (function (_super) {
+    __extends(MapSubscriber, _super);
+    function MapSubscriber(destination, project, thisArg) {
+        var _this = _super.call(this, destination) || this;
+        _this.project = project;
+        _this.count = 0;
+        _this.thisArg = thisArg || _this;
+        return _this;
+    }
+    MapSubscriber.prototype._next = function (value) {
+        var result;
+        try {
+            result = this.project.call(this.thisArg, value, this.count++);
+        }
+        catch (err) {
+            this.destination.error(err);
+            return;
+        }
+        this.destination.next(result);
+    };
+    return MapSubscriber;
+}(Subscriber));
+
+/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */
+var OuterSubscriber = /*@__PURE__*/ (function (_super) {
+    __extends(OuterSubscriber, _super);
+    function OuterSubscriber() {
+        return _super !== null && _super.apply(this, arguments) || this;
+    }
+    OuterSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) {
+        this.destination.next(innerValue);
+    };
+    OuterSubscriber.prototype.notifyError = function (error, innerSub) {
+        this.destination.error(error);
+    };
+    OuterSubscriber.prototype.notifyComplete = function (innerSub) {
+        this.destination.complete();
+    };
+    return OuterSubscriber;
+}(Subscriber));
+
+/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */
+var InnerSubscriber = /*@__PURE__*/ (function (_super) {
+    __extends(InnerSubscriber, _super);
+    function InnerSubscriber(parent, outerValue, outerIndex) {
+        var _this = _super.call(this) || this;
+        _this.parent = parent;
+        _this.outerValue = outerValue;
+        _this.outerIndex = outerIndex;
+        _this.index = 0;
+        return _this;
+    }
+    InnerSubscriber.prototype._next = function (value) {
+        this.parent.notifyNext(this.outerValue, value, this.outerIndex, this.index++, this);
+    };
+    InnerSubscriber.prototype._error = function (error) {
+        this.parent.notifyError(error, this);
+        this.unsubscribe();
+    };
+    InnerSubscriber.prototype._complete = function () {
+        this.parent.notifyComplete(this);
+        this.unsubscribe();
+    };
+    return InnerSubscriber;
+}(Subscriber));
+
+/** PURE_IMPORTS_START _hostReportError PURE_IMPORTS_END */
+var subscribeToPromise = function (promise) {
+    return function (subscriber) {
+        promise.then(function (value) {
+            if (!subscriber.closed) {
+                subscriber.next(value);
+                subscriber.complete();
+            }
+        }, function (err) { return subscriber.error(err); })
+            .then(null, hostReportError);
+        return subscriber;
+    };
+};
+
+/** PURE_IMPORTS_START  PURE_IMPORTS_END */
+function getSymbolIterator() {
+    if (typeof Symbol !== 'function' || !Symbol.iterator) {
+        return '@@iterator';
+    }
+    return Symbol.iterator;
+}
+var iterator = /*@__PURE__*/ getSymbolIterator();
+
+/** PURE_IMPORTS_START _symbol_iterator PURE_IMPORTS_END */
+var subscribeToIterable = function (iterable) {
+    return function (subscriber) {
+        var iterator$1 = iterable[iterator]();
+        do {
+            var item = void 0;
+            try {
+                item = iterator$1.next();
+            }
+            catch (err) {
+                subscriber.error(err);
+                return subscriber;
+            }
+            if (item.done) {
+                subscriber.complete();
+                break;
+            }
+            subscriber.next(item.value);
+            if (subscriber.closed) {
+                break;
+            }
+        } while (true);
+        if (typeof iterator$1.return === 'function') {
+            subscriber.add(function () {
+                if (iterator$1.return) {
+                    iterator$1.return();
+                }
+            });
+        }
+        return subscriber;
+    };
+};
+
+/** PURE_IMPORTS_START _symbol_observable PURE_IMPORTS_END */
+var subscribeToObservable = function (obj) {
+    return function (subscriber) {
+        var obs = obj[observable]();
+        if (typeof obs.subscribe !== 'function') {
+            throw new TypeError('Provided object does not correctly implement Symbol.observable');
+        }
+        else {
+            return obs.subscribe(subscriber);
+        }
+    };
+};
+
+/** PURE_IMPORTS_START  PURE_IMPORTS_END */
+var isArrayLike = (function (x) { return x && typeof x.length === 'number' && typeof x !== 'function'; });
+
+/** PURE_IMPORTS_START  PURE_IMPORTS_END */
+function isPromise(value) {
+    return !!value && typeof value.subscribe !== 'function' && typeof value.then === 'function';
+}
+
+/** PURE_IMPORTS_START _subscribeToArray,_subscribeToPromise,_subscribeToIterable,_subscribeToObservable,_isArrayLike,_isPromise,_isObject,_symbol_iterator,_symbol_observable PURE_IMPORTS_END */
+var subscribeTo = function (result) {
+    if (!!result && typeof result[observable] === 'function') {
+        return subscribeToObservable(result);
+    }
+    else if (isArrayLike(result)) {
+        return subscribeToArray(result);
+    }
+    else if (isPromise(result)) {
+        return subscribeToPromise(result);
+    }
+    else if (!!result && typeof result[iterator] === 'function') {
+        return subscribeToIterable(result);
+    }
+    else {
+        var value = isObject$1(result) ? 'an invalid object' : "'" + result + "'";
+        var msg = "You provided " + value + " where a stream was expected."
+            + ' You can provide an Observable, Promise, Array, or Iterable.';
+        throw new TypeError(msg);
+    }
+};
+
+/** PURE_IMPORTS_START _InnerSubscriber,_subscribeTo,_Observable PURE_IMPORTS_END */
+function subscribeToResult(outerSubscriber, result, outerValue, outerIndex, innerSubscriber) {
+    if (innerSubscriber === void 0) {
+        innerSubscriber = new InnerSubscriber(outerSubscriber, outerValue, outerIndex);
+    }
+    if (innerSubscriber.closed) {
+        return undefined;
+    }
+    if (result instanceof Observable) {
+        return result.subscribe(innerSubscriber);
+    }
+    return subscribeTo(result)(innerSubscriber);
+}
+
+/** PURE_IMPORTS_START tslib,_util_isScheduler,_util_isArray,_OuterSubscriber,_util_subscribeToResult,_fromArray PURE_IMPORTS_END */
+var NONE = {};
+function combineLatest() {
+    var observables = [];
+    for (var _i = 0; _i < arguments.length; _i++) {
+        observables[_i] = arguments[_i];
+    }
+    var resultSelector = undefined;
+    var scheduler = undefined;
+    if (isScheduler(observables[observables.length - 1])) {
+        scheduler = observables.pop();
+    }
+    if (typeof observables[observables.length - 1] === 'function') {
+        resultSelector = observables.pop();
+    }
+    if (observables.length === 1 && isArray$1(observables[0])) {
+        observables = observables[0];
+    }
+    return fromArray(observables, scheduler).lift(new CombineLatestOperator(resultSelector));
+}
+var CombineLatestOperator = /*@__PURE__*/ (function () {
+    function CombineLatestOperator(resultSelector) {
+        this.resultSelector = resultSelector;
+    }
+    CombineLatestOperator.prototype.call = function (subscriber, source) {
+        return source.subscribe(new CombineLatestSubscriber(subscriber, this.resultSelector));
+    };
+    return CombineLatestOperator;
+}());
+var CombineLatestSubscriber = /*@__PURE__*/ (function (_super) {
+    __extends(CombineLatestSubscriber, _super);
+    function CombineLatestSubscriber(destination, resultSelector) {
+        var _this = _super.call(this, destination) || this;
+        _this.resultSelector = resultSelector;
+        _this.active = 0;
+        _this.values = [];
+        _this.observables = [];
+        return _this;
+    }
+    CombineLatestSubscriber.prototype._next = function (observable) {
+        this.values.push(NONE);
+        this.observables.push(observable);
+    };
+    CombineLatestSubscriber.prototype._complete = function () {
+        var observables = this.observables;
+        var len = observables.length;
+        if (len === 0) {
+            this.destination.complete();
+        }
+        else {
+            this.active = len;
+            this.toRespond = len;
+            for (var i = 0; i < len; i++) {
+                var observable = observables[i];
+                this.add(subscribeToResult(this, observable, undefined, i));
+            }
+        }
+    };
+    CombineLatestSubscriber.prototype.notifyComplete = function (unused) {
+        if ((this.active -= 1) === 0) {
+            this.destination.complete();
+        }
+    };
+    CombineLatestSubscriber.prototype.notifyNext = function (_outerValue, innerValue, outerIndex) {
+        var values = this.values;
+        var oldVal = values[outerIndex];
+        var toRespond = !this.toRespond
+            ? 0
+            : oldVal === NONE ? --this.toRespond : this.toRespond;
+        values[outerIndex] = innerValue;
+        if (toRespond === 0) {
+            if (this.resultSelector) {
+                this._tryResultSelector(values);
+            }
+            else {
+                this.destination.next(values.slice());
+            }
+        }
+    };
+    CombineLatestSubscriber.prototype._tryResultSelector = function (values) {
+        var result;
+        try {
+            result = this.resultSelector.apply(this, values);
+        }
+        catch (err) {
+            this.destination.error(err);
+            return;
+        }
+        this.destination.next(result);
+    };
+    return CombineLatestSubscriber;
+}(OuterSubscriber));
+
+/** PURE_IMPORTS_START _Observable,_Subscription,_symbol_observable PURE_IMPORTS_END */
+function scheduleObservable(input, scheduler) {
+    return new Observable(function (subscriber) {
+        var sub = new Subscription();
+        sub.add(scheduler.schedule(function () {
+            var observable$1 = input[observable]();
+            sub.add(observable$1.subscribe({
+                next: function (value) { sub.add(scheduler.schedule(function () { return subscriber.next(value); })); },
+                error: function (err) { sub.add(scheduler.schedule(function () { return subscriber.error(err); })); },
+                complete: function () { sub.add(scheduler.schedule(function () { return subscriber.complete(); })); },
+            }));
+        }));
+        return sub;
+    });
+}
+
+/** PURE_IMPORTS_START _Observable,_Subscription PURE_IMPORTS_END */
+function schedulePromise(input, scheduler) {
+    return new Observable(function (subscriber) {
+        var sub = new Subscription();
+        sub.add(scheduler.schedule(function () {
+            return input.then(function (value) {
+                sub.add(scheduler.schedule(function () {
+                    subscriber.next(value);
+                    sub.add(scheduler.schedule(function () { return subscriber.complete(); }));
+                }));
+            }, function (err) {
+                sub.add(scheduler.schedule(function () { return subscriber.error(err); }));
+            });
+        }));
+        return sub;
+    });
+}
+
+/** PURE_IMPORTS_START _Observable,_Subscription,_symbol_iterator PURE_IMPORTS_END */
+function scheduleIterable(input, scheduler) {
+    if (!input) {
+        throw new Error('Iterable cannot be null');
+    }
+    return new Observable(function (subscriber) {
+        var sub = new Subscription();
+        var iterator$1;
+        sub.add(function () {
+            if (iterator$1 && typeof iterator$1.return === 'function') {
+                iterator$1.return();
+            }
+        });
+        sub.add(scheduler.schedule(function () {
+            iterator$1 = input[iterator]();
+            sub.add(scheduler.schedule(function () {
+                if (subscriber.closed) {
+                    return;
+                }
+                var value;
+                var done;
+                try {
+                    var result = iterator$1.next();
+                    value = result.value;
+                    done = result.done;
+                }
+                catch (err) {
+                    subscriber.error(err);
+                    return;
+                }
+                if (done) {
+                    subscriber.complete();
+                }
+                else {
+                    subscriber.next(value);
+                    this.schedule();
+                }
+            }));
+        }));
+        return sub;
+    });
+}
+
+/** PURE_IMPORTS_START _symbol_observable PURE_IMPORTS_END */
+function isInteropObservable(input) {
+    return input && typeof input[observable] === 'function';
+}
+
+/** PURE_IMPORTS_START _symbol_iterator PURE_IMPORTS_END */
+function isIterable(input) {
+    return input && typeof input[iterator] === 'function';
+}
+
+/** PURE_IMPORTS_START _scheduleObservable,_schedulePromise,_scheduleArray,_scheduleIterable,_util_isInteropObservable,_util_isPromise,_util_isArrayLike,_util_isIterable PURE_IMPORTS_END */
+function scheduled(input, scheduler) {
+    if (input != null) {
+        if (isInteropObservable(input)) {
+            return scheduleObservable(input, scheduler);
+        }
+        else if (isPromise(input)) {
+            return schedulePromise(input, scheduler);
+        }
+        else if (isArrayLike(input)) {
+            return scheduleArray(input, scheduler);
+        }
+        else if (isIterable(input) || typeof input === 'string') {
+            return scheduleIterable(input, scheduler);
+        }
+    }
+    throw new TypeError((input !== null && typeof input || input) + ' is not observable');
+}
+
+/** PURE_IMPORTS_START _Observable,_util_subscribeTo,_scheduled_scheduled PURE_IMPORTS_END */
+function from(input, scheduler) {
+    if (!scheduler) {
+        if (input instanceof Observable) {
+            return input;
+        }
+        return new Observable(subscribeTo(input));
+    }
+    else {
+        return scheduled(input, scheduler);
+    }
+}
+
+/** PURE_IMPORTS_START tslib,_Subscriber,_Observable,_util_subscribeTo PURE_IMPORTS_END */
+var SimpleInnerSubscriber = /*@__PURE__*/ (function (_super) {
+    __extends(SimpleInnerSubscriber, _super);
+    function SimpleInnerSubscriber(parent) {
+        var _this = _super.call(this) || this;
+        _this.parent = parent;
+        return _this;
+    }
+    SimpleInnerSubscriber.prototype._next = function (value) {
+        this.parent.notifyNext(value);
+    };
+    SimpleInnerSubscriber.prototype._error = function (error) {
+        this.parent.notifyError(error);
+        this.unsubscribe();
+    };
+    SimpleInnerSubscriber.prototype._complete = function () {
+        this.parent.notifyComplete();
+        this.unsubscribe();
+    };
+    return SimpleInnerSubscriber;
+}(Subscriber));
+var SimpleOuterSubscriber = /*@__PURE__*/ (function (_super) {
+    __extends(SimpleOuterSubscriber, _super);
+    function SimpleOuterSubscriber() {
+        return _super !== null && _super.apply(this, arguments) || this;
+    }
+    SimpleOuterSubscriber.prototype.notifyNext = function (innerValue) {
+        this.destination.next(innerValue);
+    };
+    SimpleOuterSubscriber.prototype.notifyError = function (err) {
+        this.destination.error(err);
+    };
+    SimpleOuterSubscriber.prototype.notifyComplete = function () {
+        this.destination.complete();
+    };
+    return SimpleOuterSubscriber;
+}(Subscriber));
+function innerSubscribe(result, innerSubscriber) {
+    if (innerSubscriber.closed) {
+        return undefined;
+    }
+    if (result instanceof Observable) {
+        return result.subscribe(innerSubscriber);
+    }
+    return subscribeTo(result)(innerSubscriber);
+}
+
+/** PURE_IMPORTS_START tslib,_map,_observable_from,_innerSubscribe PURE_IMPORTS_END */
+function mergeMap(project, resultSelector, concurrent) {
+    if (concurrent === void 0) {
+        concurrent = Number.POSITIVE_INFINITY;
+    }
+    if (typeof resultSelector === 'function') {
+        return function (source) { return source.pipe(mergeMap(function (a, i) { return from(project(a, i)).pipe(map(function (b, ii) { return resultSelector(a, b, i, ii); })); }, concurrent)); };
+    }
+    else if (typeof resultSelector === 'number') {
+        concurrent = resultSelector;
+    }
+    return function (source) { return source.lift(new MergeMapOperator(project, concurrent)); };
+}
+var MergeMapOperator = /*@__PURE__*/ (function () {
+    function MergeMapOperator(project, concurrent) {
+        if (concurrent === void 0) {
+            concurrent = Number.POSITIVE_INFINITY;
+        }
+        this.project = project;
+        this.concurrent = concurrent;
+    }
+    MergeMapOperator.prototype.call = function (observer, source) {
+        return source.subscribe(new MergeMapSubscriber(observer, this.project, this.concurrent));
+    };
+    return MergeMapOperator;
+}());
+var MergeMapSubscriber = /*@__PURE__*/ (function (_super) {
+    __extends(MergeMapSubscriber, _super);
+    function MergeMapSubscriber(destination, project, concurrent) {
+        if (concurrent === void 0) {
+            concurrent = Number.POSITIVE_INFINITY;
+        }
+        var _this = _super.call(this, destination) || this;
+        _this.project = project;
+        _this.concurrent = concurrent;
+        _this.hasCompleted = false;
+        _this.buffer = [];
+        _this.active = 0;
+        _this.index = 0;
+        return _this;
+    }
+    MergeMapSubscriber.prototype._next = function (value) {
+        if (this.active < this.concurrent) {
+            this._tryNext(value);
+        }
+        else {
+            this.buffer.push(value);
+        }
+    };
+    MergeMapSubscriber.prototype._tryNext = function (value) {
+        var result;
+        var index = this.index++;
+        try {
+            result = this.project(value, index);
+        }
+        catch (err) {
+            this.destination.error(err);
+            return;
+        }
+        this.active++;
+        this._innerSub(result);
+    };
+    MergeMapSubscriber.prototype._innerSub = function (ish) {
+        var innerSubscriber = new SimpleInnerSubscriber(this);
+        var destination = this.destination;
+        destination.add(innerSubscriber);
+        var innerSubscription = innerSubscribe(ish, innerSubscriber);
+        if (innerSubscription !== innerSubscriber) {
+            destination.add(innerSubscription);
+        }
+    };
+    MergeMapSubscriber.prototype._complete = function () {
+        this.hasCompleted = true;
+        if (this.active === 0 && this.buffer.length === 0) {
+            this.destination.complete();
+        }
+        this.unsubscribe();
+    };
+    MergeMapSubscriber.prototype.notifyNext = function (innerValue) {
+        this.destination.next(innerValue);
+    };
+    MergeMapSubscriber.prototype.notifyComplete = function () {
+        var buffer = this.buffer;
+        this.active--;
+        if (buffer.length > 0) {
+            this._next(buffer.shift());
+        }
+        else if (this.active === 0 && this.hasCompleted) {
+            this.destination.complete();
+        }
+    };
+    return MergeMapSubscriber;
+}(SimpleOuterSubscriber));
+
+/** PURE_IMPORTS_START _mergeMap,_util_identity PURE_IMPORTS_END */
+function mergeAll(concurrent) {
+    if (concurrent === void 0) {
+        concurrent = Number.POSITIVE_INFINITY;
+    }
+    return mergeMap(identity, concurrent);
+}
+
+/** PURE_IMPORTS_START _mergeAll PURE_IMPORTS_END */
+function concatAll() {
+    return mergeAll(1);
+}
+
+/** PURE_IMPORTS_START _of,_operators_concatAll PURE_IMPORTS_END */
+function concat() {
+    var observables = [];
+    for (var _i = 0; _i < arguments.length; _i++) {
+        observables[_i] = arguments[_i];
+    }
+    return concatAll()(of.apply(void 0, observables));
+}
+
+/** PURE_IMPORTS_START _Observable,_util_isArray,_util_isFunction,_operators_map PURE_IMPORTS_END */
+function fromEvent(target, eventName, options, resultSelector) {
+    if (isFunction(options)) {
+        resultSelector = options;
+        options = undefined;
+    }
+    if (resultSelector) {
+        return fromEvent(target, eventName, options).pipe(map(function (args) { return isArray$1(args) ? resultSelector.apply(void 0, args) : resultSelector(args); }));
+    }
+    return new Observable(function (subscriber) {
+        function handler(e) {
+            if (arguments.length > 1) {
+                subscriber.next(Array.prototype.slice.call(arguments));
+            }
+            else {
+                subscriber.next(e);
+            }
+        }
+        setupSubscription(target, eventName, handler, subscriber, options);
+    });
+}
+function setupSubscription(sourceObj, eventName, handler, subscriber, options) {
+    var unsubscribe;
+    if (isEventTarget(sourceObj)) {
+        var source_1 = sourceObj;
+        sourceObj.addEventListener(eventName, handler, options);
+        unsubscribe = function () { return source_1.removeEventListener(eventName, handler, options); };
+    }
+    else if (isJQueryStyleEventEmitter(sourceObj)) {
+        var source_2 = sourceObj;
+        sourceObj.on(eventName, handler);
+        unsubscribe = function () { return source_2.off(eventName, handler); };
+    }
+    else if (isNodeStyleEventEmitter(sourceObj)) {
+        var source_3 = sourceObj;
+        sourceObj.addListener(eventName, handler);
+        unsubscribe = function () { return source_3.removeListener(eventName, handler); };
+    }
+    else if (sourceObj && sourceObj.length) {
+        for (var i = 0, len = sourceObj.length; i < len; i++) {
+            setupSubscription(sourceObj[i], eventName, handler, subscriber, options);
+        }
+    }
+    else {
+        throw new TypeError('Invalid event target');
+    }
+    subscriber.add(unsubscribe);
+}
+function isNodeStyleEventEmitter(sourceObj) {
+    return sourceObj && typeof sourceObj.addListener === 'function' && typeof sourceObj.removeListener === 'function';
+}
+function isJQueryStyleEventEmitter(sourceObj) {
+    return sourceObj && typeof sourceObj.on === 'function' && typeof sourceObj.off === 'function';
+}
+function isEventTarget(sourceObj) {
+    return sourceObj && typeof sourceObj.addEventListener === 'function' && typeof sourceObj.removeEventListener === 'function';
+}
+
+/** PURE_IMPORTS_START _isArray PURE_IMPORTS_END */
+function isNumeric(val) {
+    return !isArray$1(val) && (val - parseFloat(val) + 1) >= 0;
+}
+
+/** PURE_IMPORTS_START _Observable,_util_isScheduler,_operators_mergeAll,_fromArray PURE_IMPORTS_END */
+function merge() {
+    var observables = [];
+    for (var _i = 0; _i < arguments.length; _i++) {
+        observables[_i] = arguments[_i];
+    }
+    var concurrent = Number.POSITIVE_INFINITY;
+    var scheduler = null;
+    var last = observables[observables.length - 1];
+    if (isScheduler(last)) {
+        scheduler = observables.pop();
+        if (observables.length > 1 && typeof observables[observables.length - 1] === 'number') {
+            concurrent = observables.pop();
+        }
+    }
+    else if (typeof last === 'number') {
+        concurrent = observables.pop();
+    }
+    if (scheduler === null && observables.length === 1 && observables[0] instanceof Observable) {
+        return observables[0];
+    }
+    return mergeAll(concurrent)(fromArray(observables, scheduler));
+}
+
+/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */
+function filter(predicate, thisArg) {
+    return function filterOperatorFunction(source) {
+        return source.lift(new FilterOperator(predicate, thisArg));
+    };
+}
+var FilterOperator = /*@__PURE__*/ (function () {
+    function FilterOperator(predicate, thisArg) {
+        this.predicate = predicate;
+        this.thisArg = thisArg;
+    }
+    FilterOperator.prototype.call = function (subscriber, source) {
+        return source.subscribe(new FilterSubscriber(subscriber, this.predicate, this.thisArg));
+    };
+    return FilterOperator;
+}());
+var FilterSubscriber = /*@__PURE__*/ (function (_super) {
+    __extends(FilterSubscriber, _super);
+    function FilterSubscriber(destination, predicate, thisArg) {
+        var _this = _super.call(this, destination) || this;
+        _this.predicate = predicate;
+        _this.thisArg = thisArg;
+        _this.count = 0;
+        return _this;
+    }
+    FilterSubscriber.prototype._next = function (value) {
+        var result;
+        try {
+            result = this.predicate.call(this.thisArg, value, this.count++);
+        }
+        catch (err) {
+            this.destination.error(err);
+            return;
+        }
+        if (result) {
+            this.destination.next(value);
+        }
+    };
+    return FilterSubscriber;
+}(Subscriber));
+
+/** PURE_IMPORTS_START _Observable,_scheduler_async,_util_isNumeric,_util_isScheduler PURE_IMPORTS_END */
+function timer(dueTime, periodOrScheduler, scheduler) {
+    if (dueTime === void 0) {
+        dueTime = 0;
+    }
+    var period = -1;
+    if (isNumeric(periodOrScheduler)) {
+        period = Number(periodOrScheduler) < 1 && 1 || Number(periodOrScheduler);
+    }
+    else if (isScheduler(periodOrScheduler)) {
+        scheduler = periodOrScheduler;
+    }
+    if (!isScheduler(scheduler)) {
+        scheduler = async;
+    }
+    return new Observable(function (subscriber) {
+        var due = isNumeric(dueTime)
+            ? dueTime
+            : (+dueTime - scheduler.now());
+        return scheduler.schedule(dispatch, due, {
+            index: 0, period: period, subscriber: subscriber
+        });
+    });
+}
+function dispatch(state) {
+    var index = state.index, period = state.period, subscriber = state.subscriber;
+    subscriber.next(index);
+    if (subscriber.closed) {
+        return;
+    }
+    else if (period === -1) {
+        return subscriber.complete();
+    }
+    state.index = index + 1;
+    this.schedule(state, period);
+}
+
+/** PURE_IMPORTS_START tslib,_fromArray,_util_isArray,_Subscriber,_.._internal_symbol_iterator,_innerSubscribe PURE_IMPORTS_END */
+function zip() {
+    var observables = [];
+    for (var _i = 0; _i < arguments.length; _i++) {
+        observables[_i] = arguments[_i];
+    }
+    var resultSelector = observables[observables.length - 1];
+    if (typeof resultSelector === 'function') {
+        observables.pop();
+    }
+    return fromArray(observables, undefined).lift(new ZipOperator(resultSelector));
+}
+var ZipOperator = /*@__PURE__*/ (function () {
+    function ZipOperator(resultSelector) {
+        this.resultSelector = resultSelector;
+    }
+    ZipOperator.prototype.call = function (subscriber, source) {
+        return source.subscribe(new ZipSubscriber(subscriber, this.resultSelector));
+    };
+    return ZipOperator;
+}());
+var ZipSubscriber = /*@__PURE__*/ (function (_super) {
+    __extends(ZipSubscriber, _super);
+    function ZipSubscriber(destination, resultSelector, values) {
+        var _this = _super.call(this, destination) || this;
+        _this.resultSelector = resultSelector;
+        _this.iterators = [];
+        _this.active = 0;
+        _this.resultSelector = (typeof resultSelector === 'function') ? resultSelector : undefined;
+        return _this;
+    }
+    ZipSubscriber.prototype._next = function (value) {
+        var iterators = this.iterators;
+        if (isArray$1(value)) {
+            iterators.push(new StaticArrayIterator(value));
+        }
+        else if (typeof value[iterator] === 'function') {
+            iterators.push(new StaticIterator(value[iterator]()));
+        }
+        else {
+            iterators.push(new ZipBufferIterator(this.destination, this, value));
+        }
+    };
+    ZipSubscriber.prototype._complete = function () {
+        var iterators = this.iterators;
+        var len = iterators.length;
+        this.unsubscribe();
+        if (len === 0) {
+            this.destination.complete();
+            return;
+        }
+        this.active = len;
+        for (var i = 0; i < len; i++) {
+            var iterator = iterators[i];
+            if (iterator.stillUnsubscribed) {
+                var destination = this.destination;
+                destination.add(iterator.subscribe());
+            }
+            else {
+                this.active--;
+            }
+        }
+    };
+    ZipSubscriber.prototype.notifyInactive = function () {
+        this.active--;
+        if (this.active === 0) {
+            this.destination.complete();
+        }
+    };
+    ZipSubscriber.prototype.checkIterators = function () {
+        var iterators = this.iterators;
+        var len = iterators.length;
+        var destination = this.destination;
+        for (var i = 0; i < len; i++) {
+            var iterator = iterators[i];
+            if (typeof iterator.hasValue === 'function' && !iterator.hasValue()) {
+                return;
+            }
+        }
+        var shouldComplete = false;
+        var args = [];
+        for (var i = 0; i < len; i++) {
+            var iterator = iterators[i];
+            var result = iterator.next();
+            if (iterator.hasCompleted()) {
+                shouldComplete = true;
+            }
+            if (result.done) {
+                destination.complete();
+                return;
+            }
+            args.push(result.value);
+        }
+        if (this.resultSelector) {
+            this._tryresultSelector(args);
+        }
+        else {
+            destination.next(args);
+        }
+        if (shouldComplete) {
+            destination.complete();
+        }
+    };
+    ZipSubscriber.prototype._tryresultSelector = function (args) {
+        var result;
+        try {
+            result = this.resultSelector.apply(this, args);
+        }
+        catch (err) {
+            this.destination.error(err);
+            return;
+        }
+        this.destination.next(result);
+    };
+    return ZipSubscriber;
+}(Subscriber));
+var StaticIterator = /*@__PURE__*/ (function () {
+    function StaticIterator(iterator) {
+        this.iterator = iterator;
+        this.nextResult = iterator.next();
+    }
+    StaticIterator.prototype.hasValue = function () {
+        return true;
+    };
+    StaticIterator.prototype.next = function () {
+        var result = this.nextResult;
+        this.nextResult = this.iterator.next();
+        return result;
+    };
+    StaticIterator.prototype.hasCompleted = function () {
+        var nextResult = this.nextResult;
+        return Boolean(nextResult && nextResult.done);
+    };
+    return StaticIterator;
+}());
+var StaticArrayIterator = /*@__PURE__*/ (function () {
+    function StaticArrayIterator(array) {
+        this.array = array;
+        this.index = 0;
+        this.length = 0;
+        this.length = array.length;
+    }
+    StaticArrayIterator.prototype[iterator] = function () {
+        return this;
+    };
+    StaticArrayIterator.prototype.next = function (value) {
+        var i = this.index++;
+        var array = this.array;
+        return i < this.length ? { value: array[i], done: false } : { value: null, done: true };
+    };
+    StaticArrayIterator.prototype.hasValue = function () {
+        return this.array.length > this.index;
+    };
+    StaticArrayIterator.prototype.hasCompleted = function () {
+        return this.array.length === this.index;
+    };
+    return StaticArrayIterator;
+}());
+var ZipBufferIterator = /*@__PURE__*/ (function (_super) {
+    __extends(ZipBufferIterator, _super);
+    function ZipBufferIterator(destination, parent, observable) {
+        var _this = _super.call(this, destination) || this;
+        _this.parent = parent;
+        _this.observable = observable;
+        _this.stillUnsubscribed = true;
+        _this.buffer = [];
+        _this.isComplete = false;
+        return _this;
+    }
+    ZipBufferIterator.prototype[iterator] = function () {
+        return this;
+    };
+    ZipBufferIterator.prototype.next = function () {
+        var buffer = this.buffer;
+        if (buffer.length === 0 && this.isComplete) {
+            return { value: null, done: true };
+        }
+        else {
+            return { value: buffer.shift(), done: false };
+        }
+    };
+    ZipBufferIterator.prototype.hasValue = function () {
+        return this.buffer.length > 0;
+    };
+    ZipBufferIterator.prototype.hasCompleted = function () {
+        return this.buffer.length === 0 && this.isComplete;
+    };
+    ZipBufferIterator.prototype.notifyComplete = function () {
+        if (this.buffer.length > 0) {
+            this.isComplete = true;
+            this.parent.notifyInactive();
+        }
+        else {
+            this.destination.complete();
+        }
+    };
+    ZipBufferIterator.prototype.notifyNext = function (innerValue) {
+        this.buffer.push(innerValue);
+        this.parent.checkIterators();
+    };
+    ZipBufferIterator.prototype.subscribe = function () {
+        return innerSubscribe(this.observable, new SimpleInnerSubscriber(this));
+    };
+    return ZipBufferIterator;
+}(SimpleOuterSubscriber));
+
+/** PURE_IMPORTS_START tslib,_innerSubscribe PURE_IMPORTS_END */
+function audit(durationSelector) {
+    return function auditOperatorFunction(source) {
+        return source.lift(new AuditOperator(durationSelector));
+    };
+}
+var AuditOperator = /*@__PURE__*/ (function () {
+    function AuditOperator(durationSelector) {
+        this.durationSelector = durationSelector;
+    }
+    AuditOperator.prototype.call = function (subscriber, source) {
+        return source.subscribe(new AuditSubscriber(subscriber, this.durationSelector));
+    };
+    return AuditOperator;
+}());
+var AuditSubscriber = /*@__PURE__*/ (function (_super) {
+    __extends(AuditSubscriber, _super);
+    function AuditSubscriber(destination, durationSelector) {
+        var _this = _super.call(this, destination) || this;
+        _this.durationSelector = durationSelector;
+        _this.hasValue = false;
+        return _this;
+    }
+    AuditSubscriber.prototype._next = function (value) {
+        this.value = value;
+        this.hasValue = true;
+        if (!this.throttled) {
+            var duration = void 0;
+            try {
+                var durationSelector = this.durationSelector;
+                duration = durationSelector(value);
+            }
+            catch (err) {
+                return this.destination.error(err);
+            }
+            var innerSubscription = innerSubscribe(duration, new SimpleInnerSubscriber(this));
+            if (!innerSubscription || innerSubscription.closed) {
+                this.clearThrottle();
+            }
+            else {
+                this.add(this.throttled = innerSubscription);
+            }
+        }
+    };
+    AuditSubscriber.prototype.clearThrottle = function () {
+        var _a = this, value = _a.value, hasValue = _a.hasValue, throttled = _a.throttled;
+        if (throttled) {
+            this.remove(throttled);
+            this.throttled = undefined;
+            throttled.unsubscribe();
+        }
+        if (hasValue) {
+            this.value = undefined;
+            this.hasValue = false;
+            this.destination.next(value);
+        }
+    };
+    AuditSubscriber.prototype.notifyNext = function () {
+        this.clearThrottle();
+    };
+    AuditSubscriber.prototype.notifyComplete = function () {
+        this.clearThrottle();
+    };
+    return AuditSubscriber;
+}(SimpleOuterSubscriber));
+
+/** PURE_IMPORTS_START _scheduler_async,_audit,_observable_timer PURE_IMPORTS_END */
+function auditTime(duration, scheduler) {
+    if (scheduler === void 0) {
+        scheduler = async;
+    }
+    return audit(function () { return timer(duration, scheduler); });
+}
+
+/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */
+function bufferCount(bufferSize, startBufferEvery) {
+    if (startBufferEvery === void 0) {
+        startBufferEvery = null;
+    }
+    return function bufferCountOperatorFunction(source) {
+        return source.lift(new BufferCountOperator(bufferSize, startBufferEvery));
+    };
+}
+var BufferCountOperator = /*@__PURE__*/ (function () {
+    function BufferCountOperator(bufferSize, startBufferEvery) {
+        this.bufferSize = bufferSize;
+        this.startBufferEvery = startBufferEvery;
+        if (!startBufferEvery || bufferSize === startBufferEvery) {
+            this.subscriberClass = BufferCountSubscriber;
+        }
+        else {
+            this.subscriberClass = BufferSkipCountSubscriber;
+        }
+    }
+    BufferCountOperator.prototype.call = function (subscriber, source) {
+        return source.subscribe(new this.subscriberClass(subscriber, this.bufferSize, this.startBufferEvery));
+    };
+    return BufferCountOperator;
+}());
+var BufferCountSubscriber = /*@__PURE__*/ (function (_super) {
+    __extends(BufferCountSubscriber, _super);
+    function BufferCountSubscriber(destination, bufferSize) {
+        var _this = _super.call(this, destination) || this;
+        _this.bufferSize = bufferSize;
+        _this.buffer = [];
+        return _this;
+    }
+    BufferCountSubscriber.prototype._next = function (value) {
+        var buffer = this.buffer;
+        buffer.push(value);
+        if (buffer.length == this.bufferSize) {
+            this.destination.next(buffer);
+            this.buffer = [];
+        }
+    };
+    BufferCountSubscriber.prototype._complete = function () {
+        var buffer = this.buffer;
+        if (buffer.length > 0) {
+            this.destination.next(buffer);
+        }
+        _super.prototype._complete.call(this);
+    };
+    return BufferCountSubscriber;
+}(Subscriber));
+var BufferSkipCountSubscriber = /*@__PURE__*/ (function (_super) {
+    __extends(BufferSkipCountSubscriber, _super);
+    function BufferSkipCountSubscriber(destination, bufferSize, startBufferEvery) {
+        var _this = _super.call(this, destination) || this;
+        _this.bufferSize = bufferSize;
+        _this.startBufferEvery = startBufferEvery;
+        _this.buffers = [];
+        _this.count = 0;
+        return _this;
+    }
+    BufferSkipCountSubscriber.prototype._next = function (value) {
+        var _a = this, bufferSize = _a.bufferSize, startBufferEvery = _a.startBufferEvery, buffers = _a.buffers, count = _a.count;
+        this.count++;
+        if (count % startBufferEvery === 0) {
+            buffers.push([]);
+        }
+        for (var i = buffers.length; i--;) {
+            var buffer = buffers[i];
+            buffer.push(value);
+            if (buffer.length === bufferSize) {
+                buffers.splice(i, 1);
+                this.destination.next(buffer);
+            }
+        }
+    };
+    BufferSkipCountSubscriber.prototype._complete = function () {
+        var _a = this, buffers = _a.buffers, destination = _a.destination;
+        while (buffers.length > 0) {
+            var buffer = buffers.shift();
+            if (buffer.length > 0) {
+                destination.next(buffer);
+            }
+        }
+        _super.prototype._complete.call(this);
+    };
+    return BufferSkipCountSubscriber;
+}(Subscriber));
+
+/** PURE_IMPORTS_START tslib,_Subscription,_innerSubscribe PURE_IMPORTS_END */
+function bufferWhen(closingSelector) {
+    return function (source) {
+        return source.lift(new BufferWhenOperator(closingSelector));
+    };
+}
+var BufferWhenOperator = /*@__PURE__*/ (function () {
+    function BufferWhenOperator(closingSelector) {
+        this.closingSelector = closingSelector;
+    }
+    BufferWhenOperator.prototype.call = function (subscriber, source) {
+        return source.subscribe(new BufferWhenSubscriber(subscriber, this.closingSelector));
+    };
+    return BufferWhenOperator;
+}());
+var BufferWhenSubscriber = /*@__PURE__*/ (function (_super) {
+    __extends(BufferWhenSubscriber, _super);
+    function BufferWhenSubscriber(destination, closingSelector) {
+        var _this = _super.call(this, destination) || this;
+        _this.closingSelector = closingSelector;
+        _this.subscribing = false;
+        _this.openBuffer();
+        return _this;
+    }
+    BufferWhenSubscriber.prototype._next = function (value) {
+        this.buffer.push(value);
+    };
+    BufferWhenSubscriber.prototype._complete = function () {
+        var buffer = this.buffer;
+        if (buffer) {
+            this.destination.next(buffer);
+        }
+        _super.prototype._complete.call(this);
+    };
+    BufferWhenSubscriber.prototype._unsubscribe = function () {
+        this.buffer = undefined;
+        this.subscribing = false;
+    };
+    BufferWhenSubscriber.prototype.notifyNext = function () {
+        this.openBuffer();
+    };
+    BufferWhenSubscriber.prototype.notifyComplete = function () {
+        if (this.subscribing) {
+            this.complete();
+        }
+        else {
+            this.openBuffer();
+        }
+    };
+    BufferWhenSubscriber.prototype.openBuffer = function () {
+        var closingSubscription = this.closingSubscription;
+        if (closingSubscription) {
+            this.remove(closingSubscription);
+            closingSubscription.unsubscribe();
+        }
+        var buffer = this.buffer;
+        if (this.buffer) {
+            this.destination.next(buffer);
+        }
+        this.buffer = [];
+        var closingNotifier;
+        try {
+            var closingSelector = this.closingSelector;
+            closingNotifier = closingSelector();
+        }
+        catch (err) {
+            return this.error(err);
+        }
+        closingSubscription = new Subscription();
+        this.closingSubscription = closingSubscription;
+        this.add(closingSubscription);
+        this.subscribing = true;
+        closingSubscription.add(innerSubscribe(closingNotifier, new SimpleInnerSubscriber(this)));
+        this.subscribing = false;
+    };
+    return BufferWhenSubscriber;
+}(SimpleOuterSubscriber));
+
+/** PURE_IMPORTS_START tslib,_innerSubscribe PURE_IMPORTS_END */
+function catchError(selector) {
+    return function catchErrorOperatorFunction(source) {
+        var operator = new CatchOperator(selector);
+        var caught = source.lift(operator);
+        return (operator.caught = caught);
+    };
+}
+var CatchOperator = /*@__PURE__*/ (function () {
+    function CatchOperator(selector) {
+        this.selector = selector;
+    }
+    CatchOperator.prototype.call = function (subscriber, source) {
+        return source.subscribe(new CatchSubscriber(subscriber, this.selector, this.caught));
+    };
+    return CatchOperator;
+}());
+var CatchSubscriber = /*@__PURE__*/ (function (_super) {
+    __extends(CatchSubscriber, _super);
+    function CatchSubscriber(destination, selector, caught) {
+        var _this = _super.call(this, destination) || this;
+        _this.selector = selector;
+        _this.caught = caught;
+        return _this;
+    }
+    CatchSubscriber.prototype.error = function (err) {
+        if (!this.isStopped) {
+            var result = void 0;
+            try {
+                result = this.selector(err, this.caught);
+            }
+            catch (err2) {
+                _super.prototype.error.call(this, err2);
+                return;
+            }
+            this._unsubscribeAndRecycle();
+            var innerSubscriber = new SimpleInnerSubscriber(this);
+            this.add(innerSubscriber);
+            var innerSubscription = innerSubscribe(result, innerSubscriber);
+            if (innerSubscription !== innerSubscriber) {
+                this.add(innerSubscription);
+            }
+        }
+    };
+    return CatchSubscriber;
+}(SimpleOuterSubscriber));
+
+/** PURE_IMPORTS_START _mergeMap PURE_IMPORTS_END */
+function concatMap(project, resultSelector) {
+    return mergeMap(project, resultSelector, 1);
+}
+
+/** PURE_IMPORTS_START tslib,_Subscriber,_scheduler_async PURE_IMPORTS_END */
+function debounceTime(dueTime, scheduler) {
+    if (scheduler === void 0) {
+        scheduler = async;
+    }
+    return function (source) { return source.lift(new DebounceTimeOperator(dueTime, scheduler)); };
+}
+var DebounceTimeOperator = /*@__PURE__*/ (function () {
+    function DebounceTimeOperator(dueTime, scheduler) {
+        this.dueTime = dueTime;
+        this.scheduler = scheduler;
+    }
+    DebounceTimeOperator.prototype.call = function (subscriber, source) {
+        return source.subscribe(new DebounceTimeSubscriber(subscriber, this.dueTime, this.scheduler));
+    };
+    return DebounceTimeOperator;
+}());
+var DebounceTimeSubscriber = /*@__PURE__*/ (function (_super) {
+    __extends(DebounceTimeSubscriber, _super);
+    function DebounceTimeSubscriber(destination, dueTime, scheduler) {
+        var _this = _super.call(this, destination) || this;
+        _this.dueTime = dueTime;
+        _this.scheduler = scheduler;
+        _this.debouncedSubscription = null;
+        _this.lastValue = null;
+        _this.hasValue = false;
+        return _this;
+    }
+    DebounceTimeSubscriber.prototype._next = function (value) {
+        this.clearDebounce();
+        this.lastValue = value;
+        this.hasValue = true;
+        this.add(this.debouncedSubscription = this.scheduler.schedule(dispatchNext, this.dueTime, this));
+    };
+    DebounceTimeSubscriber.prototype._complete = function () {
+        this.debouncedNext();
+        this.destination.complete();
+    };
+    DebounceTimeSubscriber.prototype.debouncedNext = function () {
+        this.clearDebounce();
+        if (this.hasValue) {
+            var lastValue = this.lastValue;
+            this.lastValue = null;
+            this.hasValue = false;
+            this.destination.next(lastValue);
+        }
+    };
+    DebounceTimeSubscriber.prototype.clearDebounce = function () {
+        var debouncedSubscription = this.debouncedSubscription;
+        if (debouncedSubscription !== null) {
+            this.remove(debouncedSubscription);
+            debouncedSubscription.unsubscribe();
+            this.debouncedSubscription = null;
+        }
+    };
+    return DebounceTimeSubscriber;
+}(Subscriber));
+function dispatchNext(subscriber) {
+    subscriber.debouncedNext();
+}
+
+/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */
+function defaultIfEmpty(defaultValue) {
+    if (defaultValue === void 0) {
+        defaultValue = null;
+    }
+    return function (source) { return source.lift(new DefaultIfEmptyOperator(defaultValue)); };
+}
+var DefaultIfEmptyOperator = /*@__PURE__*/ (function () {
+    function DefaultIfEmptyOperator(defaultValue) {
+        this.defaultValue = defaultValue;
+    }
+    DefaultIfEmptyOperator.prototype.call = function (subscriber, source) {
+        return source.subscribe(new DefaultIfEmptySubscriber(subscriber, this.defaultValue));
+    };
+    return DefaultIfEmptyOperator;
+}());
+var DefaultIfEmptySubscriber = /*@__PURE__*/ (function (_super) {
+    __extends(DefaultIfEmptySubscriber, _super);
+    function DefaultIfEmptySubscriber(destination, defaultValue) {
+        var _this = _super.call(this, destination) || this;
+        _this.defaultValue = defaultValue;
+        _this.isEmpty = true;
+        return _this;
+    }
+    DefaultIfEmptySubscriber.prototype._next = function (value) {
+        this.isEmpty = false;
+        this.destination.next(value);
+    };
+    DefaultIfEmptySubscriber.prototype._complete = function () {
+        if (this.isEmpty) {
+            this.destination.next(this.defaultValue);
+        }
+        this.destination.complete();
+    };
+    return DefaultIfEmptySubscriber;
+}(Subscriber));
+
+/** PURE_IMPORTS_START  PURE_IMPORTS_END */
+function isDate(value) {
+    return value instanceof Date && !isNaN(+value);
+}
+
+/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */
+function distinctUntilChanged(compare, keySelector) {
+    return function (source) { return source.lift(new DistinctUntilChangedOperator(compare, keySelector)); };
+}
+var DistinctUntilChangedOperator = /*@__PURE__*/ (function () {
+    function DistinctUntilChangedOperator(compare, keySelector) {
+        this.compare = compare;
+        this.keySelector = keySelector;
+    }
+    DistinctUntilChangedOperator.prototype.call = function (subscriber, source) {
+        return source.subscribe(new DistinctUntilChangedSubscriber(subscriber, this.compare, this.keySelector));
+    };
+    return DistinctUntilChangedOperator;
+}());
+var DistinctUntilChangedSubscriber = /*@__PURE__*/ (function (_super) {
+    __extends(DistinctUntilChangedSubscriber, _super);
+    function DistinctUntilChangedSubscriber(destination, compare, keySelector) {
+        var _this = _super.call(this, destination) || this;
+        _this.keySelector = keySelector;
+        _this.hasKey = false;
+        if (typeof compare === 'function') {
+            _this.compare = compare;
+        }
+        return _this;
+    }
+    DistinctUntilChangedSubscriber.prototype.compare = function (x, y) {
+        return x === y;
+    };
+    DistinctUntilChangedSubscriber.prototype._next = function (value) {
+        var key;
+        try {
+            var keySelector = this.keySelector;
+            key = keySelector ? keySelector(value) : value;
+        }
+        catch (err) {
+            return this.destination.error(err);
+        }
+        var result = false;
+        if (this.hasKey) {
+            try {
+                var compare = this.compare;
+                result = compare(this.key, key);
+            }
+            catch (err) {
+                return this.destination.error(err);
+            }
+        }
+        else {
+            this.hasKey = true;
+        }
+        if (!result) {
+            this.key = key;
+            this.destination.next(value);
+        }
+    };
+    return DistinctUntilChangedSubscriber;
+}(Subscriber));
+
+/** PURE_IMPORTS_START tslib,_util_EmptyError,_Subscriber PURE_IMPORTS_END */
+function throwIfEmpty(errorFactory) {
+    if (errorFactory === void 0) {
+        errorFactory = defaultErrorFactory;
+    }
+    return function (source) {
+        return source.lift(new ThrowIfEmptyOperator(errorFactory));
+    };
+}
+var ThrowIfEmptyOperator = /*@__PURE__*/ (function () {
+    function ThrowIfEmptyOperator(errorFactory) {
+        this.errorFactory = errorFactory;
+    }
+    ThrowIfEmptyOperator.prototype.call = function (subscriber, source) {
+        return source.subscribe(new ThrowIfEmptySubscriber(subscriber, this.errorFactory));
+    };
+    return ThrowIfEmptyOperator;
+}());
+var ThrowIfEmptySubscriber = /*@__PURE__*/ (function (_super) {
+    __extends(ThrowIfEmptySubscriber, _super);
+    function ThrowIfEmptySubscriber(destination, errorFactory) {
+        var _this = _super.call(this, destination) || this;
+        _this.errorFactory = errorFactory;
+        _this.hasValue = false;
+        return _this;
+    }
+    ThrowIfEmptySubscriber.prototype._next = function (value) {
+        this.hasValue = true;
+        this.destination.next(value);
+    };
+    ThrowIfEmptySubscriber.prototype._complete = function () {
+        if (!this.hasValue) {
+            var err = void 0;
+            try {
+                err = this.errorFactory();
+            }
+            catch (e) {
+                err = e;
+            }
+            this.destination.error(err);
+        }
+        else {
+            return this.destination.complete();
+        }
+    };
+    return ThrowIfEmptySubscriber;
+}(Subscriber));
+function defaultErrorFactory() {
+    return new EmptyError();
+}
+
+/** PURE_IMPORTS_START tslib,_Subscriber,_util_ArgumentOutOfRangeError,_observable_empty PURE_IMPORTS_END */
+function take(count) {
+    return function (source) {
+        if (count === 0) {
+            return empty();
+        }
+        else {
+            return source.lift(new TakeOperator(count));
+        }
+    };
+}
+var TakeOperator = /*@__PURE__*/ (function () {
+    function TakeOperator(total) {
+        this.total = total;
+        if (this.total < 0) {
+            throw new ArgumentOutOfRangeError;
+        }
+    }
+    TakeOperator.prototype.call = function (subscriber, source) {
+        return source.subscribe(new TakeSubscriber(subscriber, this.total));
+    };
+    return TakeOperator;
+}());
+var TakeSubscriber = /*@__PURE__*/ (function (_super) {
+    __extends(TakeSubscriber, _super);
+    function TakeSubscriber(destination, total) {
+        var _this = _super.call(this, destination) || this;
+        _this.total = total;
+        _this.count = 0;
+        return _this;
+    }
+    TakeSubscriber.prototype._next = function (value) {
+        var total = this.total;
+        var count = ++this.count;
+        if (count <= total) {
+            this.destination.next(value);
+            if (count === total) {
+                this.destination.complete();
+                this.unsubscribe();
+            }
+        }
+    };
+    return TakeSubscriber;
+}(Subscriber));
+
+/** PURE_IMPORTS_START tslib,_innerSubscribe PURE_IMPORTS_END */
+function expand(project, concurrent, scheduler) {
+    if (concurrent === void 0) {
+        concurrent = Number.POSITIVE_INFINITY;
+    }
+    concurrent = (concurrent || 0) < 1 ? Number.POSITIVE_INFINITY : concurrent;
+    return function (source) { return source.lift(new ExpandOperator(project, concurrent, scheduler)); };
+}
+var ExpandOperator = /*@__PURE__*/ (function () {
+    function ExpandOperator(project, concurrent, scheduler) {
+        this.project = project;
+        this.concurrent = concurrent;
+        this.scheduler = scheduler;
+    }
+    ExpandOperator.prototype.call = function (subscriber, source) {
+        return source.subscribe(new ExpandSubscriber(subscriber, this.project, this.concurrent, this.scheduler));
+    };
+    return ExpandOperator;
+}());
+var ExpandSubscriber = /*@__PURE__*/ (function (_super) {
+    __extends(ExpandSubscriber, _super);
+    function ExpandSubscriber(destination, project, concurrent, scheduler) {
+        var _this = _super.call(this, destination) || this;
+        _this.project = project;
+        _this.concurrent = concurrent;
+        _this.scheduler = scheduler;
+        _this.index = 0;
+        _this.active = 0;
+        _this.hasCompleted = false;
+        if (concurrent < Number.POSITIVE_INFINITY) {
+            _this.buffer = [];
+        }
+        return _this;
+    }
+    ExpandSubscriber.dispatch = function (arg) {
+        var subscriber = arg.subscriber, result = arg.result, value = arg.value, index = arg.index;
+        subscriber.subscribeToProjection(result, value, index);
+    };
+    ExpandSubscriber.prototype._next = function (value) {
+        var destination = this.destination;
+        if (destination.closed) {
+            this._complete();
+            return;
+        }
+        var index = this.index++;
+        if (this.active < this.concurrent) {
+            destination.next(value);
+            try {
+                var project = this.project;
+                var result = project(value, index);
+                if (!this.scheduler) {
+                    this.subscribeToProjection(result, value, index);
+                }
+                else {
+                    var state = { subscriber: this, result: result, value: value, index: index };
+                    var destination_1 = this.destination;
+                    destination_1.add(this.scheduler.schedule(ExpandSubscriber.dispatch, 0, state));
+                }
+            }
+            catch (e) {
+                destination.error(e);
+            }
+        }
+        else {
+            this.buffer.push(value);
+        }
+    };
+    ExpandSubscriber.prototype.subscribeToProjection = function (result, value, index) {
+        this.active++;
+        var destination = this.destination;
+        destination.add(innerSubscribe(result, new SimpleInnerSubscriber(this)));
+    };
+    ExpandSubscriber.prototype._complete = function () {
+        this.hasCompleted = true;
+        if (this.hasCompleted && this.active === 0) {
+            this.destination.complete();
+        }
+        this.unsubscribe();
+    };
+    ExpandSubscriber.prototype.notifyNext = function (innerValue) {
+        this._next(innerValue);
+    };
+    ExpandSubscriber.prototype.notifyComplete = function () {
+        var buffer = this.buffer;
+        this.active--;
+        if (buffer && buffer.length > 0) {
+            this._next(buffer.shift());
+        }
+        if (this.hasCompleted && this.active === 0) {
+            this.destination.complete();
+        }
+    };
+    return ExpandSubscriber;
+}(SimpleOuterSubscriber));
+
+/** PURE_IMPORTS_START tslib,_Subscriber,_Subscription PURE_IMPORTS_END */
+function finalize(callback) {
+    return function (source) { return source.lift(new FinallyOperator(callback)); };
+}
+var FinallyOperator = /*@__PURE__*/ (function () {
+    function FinallyOperator(callback) {
+        this.callback = callback;
+    }
+    FinallyOperator.prototype.call = function (subscriber, source) {
+        return source.subscribe(new FinallySubscriber(subscriber, this.callback));
+    };
+    return FinallyOperator;
+}());
+var FinallySubscriber = /*@__PURE__*/ (function (_super) {
+    __extends(FinallySubscriber, _super);
+    function FinallySubscriber(destination, callback) {
+        var _this = _super.call(this, destination) || this;
+        _this.add(new Subscription(callback));
+        return _this;
+    }
+    return FinallySubscriber;
+}(Subscriber));
+
+/** PURE_IMPORTS_START _util_EmptyError,_filter,_take,_defaultIfEmpty,_throwIfEmpty,_util_identity PURE_IMPORTS_END */
+function first(predicate, defaultValue) {
+    var hasDefaultValue = arguments.length >= 2;
+    return function (source) { return source.pipe(predicate ? filter(function (v, i) { return predicate(v, i, source); }) : identity, take(1), hasDefaultValue ? defaultIfEmpty(defaultValue) : throwIfEmpty(function () { return new EmptyError(); })); };
+}
+
+/** PURE_IMPORTS_START tslib,_Subscriber,_util_ArgumentOutOfRangeError,_observable_empty PURE_IMPORTS_END */
+function takeLast(count) {
+    return function takeLastOperatorFunction(source) {
+        if (count === 0) {
+            return empty();
+        }
+        else {
+            return source.lift(new TakeLastOperator(count));
+        }
+    };
+}
+var TakeLastOperator = /*@__PURE__*/ (function () {
+    function TakeLastOperator(total) {
+        this.total = total;
+        if (this.total < 0) {
+            throw new ArgumentOutOfRangeError;
+        }
+    }
+    TakeLastOperator.prototype.call = function (subscriber, source) {
+        return source.subscribe(new TakeLastSubscriber(subscriber, this.total));
+    };
+    return TakeLastOperator;
+}());
+var TakeLastSubscriber = /*@__PURE__*/ (function (_super) {
+    __extends(TakeLastSubscriber, _super);
+    function TakeLastSubscriber(destination, total) {
+        var _this = _super.call(this, destination) || this;
+        _this.total = total;
+        _this.ring = new Array();
+        _this.count = 0;
+        return _this;
+    }
+    TakeLastSubscriber.prototype._next = function (value) {
+        var ring = this.ring;
+        var total = this.total;
+        var count = this.count++;
+        if (ring.length < total) {
+            ring.push(value);
+        }
+        else {
+            var index = count % total;
+            ring[index] = value;
+        }
+    };
+    TakeLastSubscriber.prototype._complete = function () {
+        var destination = this.destination;
+        var count = this.count;
+        if (count > 0) {
+            var total = this.count >= this.total ? this.total : this.count;
+            var ring = this.ring;
+            for (var i = 0; i < total; i++) {
+                var idx = (count++) % total;
+                destination.next(ring[idx]);
+            }
+        }
+        destination.complete();
+    };
+    return TakeLastSubscriber;
+}(Subscriber));
+
+/** PURE_IMPORTS_START _util_EmptyError,_filter,_takeLast,_throwIfEmpty,_defaultIfEmpty,_util_identity PURE_IMPORTS_END */
+function last(predicate, defaultValue) {
+    var hasDefaultValue = arguments.length >= 2;
+    return function (source) { return source.pipe(predicate ? filter(function (v, i) { return predicate(v, i, source); }) : identity, takeLast(1), hasDefaultValue ? defaultIfEmpty(defaultValue) : throwIfEmpty(function () { return new EmptyError(); })); };
+}
+
+/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */
+function scan(accumulator, seed) {
+    var hasSeed = false;
+    if (arguments.length >= 2) {
+        hasSeed = true;
+    }
+    return function scanOperatorFunction(source) {
+        return source.lift(new ScanOperator(accumulator, seed, hasSeed));
+    };
+}
+var ScanOperator = /*@__PURE__*/ (function () {
+    function ScanOperator(accumulator, seed, hasSeed) {
+        if (hasSeed === void 0) {
+            hasSeed = false;
+        }
+        this.accumulator = accumulator;
+        this.seed = seed;
+        this.hasSeed = hasSeed;
+    }
+    ScanOperator.prototype.call = function (subscriber, source) {
+        return source.subscribe(new ScanSubscriber(subscriber, this.accumulator, this.seed, this.hasSeed));
+    };
+    return ScanOperator;
+}());
+var ScanSubscriber = /*@__PURE__*/ (function (_super) {
+    __extends(ScanSubscriber, _super);
+    function ScanSubscriber(destination, accumulator, _seed, hasSeed) {
+        var _this = _super.call(this, destination) || this;
+        _this.accumulator = accumulator;
+        _this._seed = _seed;
+        _this.hasSeed = hasSeed;
+        _this.index = 0;
+        return _this;
+    }
+    Object.defineProperty(ScanSubscriber.prototype, "seed", {
+        get: function () {
+            return this._seed;
+        },
+        set: function (value) {
+            this.hasSeed = true;
+            this._seed = value;
+        },
+        enumerable: true,
+        configurable: true
+    });
+    ScanSubscriber.prototype._next = function (value) {
+        if (!this.hasSeed) {
+            this.seed = value;
+            this.destination.next(value);
+        }
+        else {
+            return this._tryNext(value);
+        }
+    };
+    ScanSubscriber.prototype._tryNext = function (value) {
+        var index = this.index++;
+        var result;
+        try {
+            result = this.accumulator(this.seed, value, index);
+        }
+        catch (err) {
+            this.destination.error(err);
+        }
+        this.seed = result;
+        this.destination.next(result);
+    };
+    return ScanSubscriber;
+}(Subscriber));
+
+/** PURE_IMPORTS_START _scan,_takeLast,_defaultIfEmpty,_util_pipe PURE_IMPORTS_END */
+function reduce(accumulator, seed) {
+    if (arguments.length >= 2) {
+        return function reduceOperatorFunctionWithSeed(source) {
+            return pipe(scan(accumulator, seed), takeLast(1), defaultIfEmpty(seed))(source);
+        };
+    }
+    return function reduceOperatorFunction(source) {
+        return pipe(scan(function (acc, value, index) { return accumulator(acc, value, index + 1); }), takeLast(1))(source);
+    };
+}
+
+/** PURE_IMPORTS_START _observable_ConnectableObservable PURE_IMPORTS_END */
+function multicast(subjectOrSubjectFactory, selector) {
+    return function multicastOperatorFunction(source) {
+        var subjectFactory;
+        if (typeof subjectOrSubjectFactory === 'function') {
+            subjectFactory = subjectOrSubjectFactory;
+        }
+        else {
+            subjectFactory = function subjectFactory() {
+                return subjectOrSubjectFactory;
+            };
+        }
+        if (typeof selector === 'function') {
+            return source.lift(new MulticastOperator(subjectFactory, selector));
+        }
+        var connectable = Object.create(source, connectableObservableDescriptor);
+        connectable.source = source;
+        connectable.subjectFactory = subjectFactory;
+        return connectable;
+    };
+}
+var MulticastOperator = /*@__PURE__*/ (function () {
+    function MulticastOperator(subjectFactory, selector) {
+        this.subjectFactory = subjectFactory;
+        this.selector = selector;
+    }
+    MulticastOperator.prototype.call = function (subscriber, source) {
+        var selector = this.selector;
+        var subject = this.subjectFactory();
+        var subscription = selector(subject).subscribe(subscriber);
+        subscription.add(source.subscribe(subject));
+        return subscription;
+    };
+    return MulticastOperator;
+}());
+
+/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */
+function pairwise() {
+    return function (source) { return source.lift(new PairwiseOperator()); };
+}
+var PairwiseOperator = /*@__PURE__*/ (function () {
+    function PairwiseOperator() {
+    }
+    PairwiseOperator.prototype.call = function (subscriber, source) {
+        return source.subscribe(new PairwiseSubscriber(subscriber));
+    };
+    return PairwiseOperator;
+}());
+var PairwiseSubscriber = /*@__PURE__*/ (function (_super) {
+    __extends(PairwiseSubscriber, _super);
+    function PairwiseSubscriber(destination) {
+        var _this = _super.call(this, destination) || this;
+        _this.hasPrev = false;
+        return _this;
+    }
+    PairwiseSubscriber.prototype._next = function (value) {
+        var pair;
+        if (this.hasPrev) {
+            pair = [this.prev, value];
+        }
+        else {
+            this.hasPrev = true;
+        }
+        this.prev = value;
+        if (pair) {
+            this.destination.next(pair);
+        }
+    };
+    return PairwiseSubscriber;
+}(Subscriber));
+
+/** PURE_IMPORTS_START _map PURE_IMPORTS_END */
+function pluck() {
+    var properties = [];
+    for (var _i = 0; _i < arguments.length; _i++) {
+        properties[_i] = arguments[_i];
+    }
+    var length = properties.length;
+    if (length === 0) {
+        throw new Error('list of properties cannot be empty.');
+    }
+    return function (source) { return map(plucker(properties, length))(source); };
+}
+function plucker(props, length) {
+    var mapper = function (x) {
+        var currentProp = x;
+        for (var i = 0; i < length; i++) {
+            var p = currentProp != null ? currentProp[props[i]] : undefined;
+            if (p !== void 0) {
+                currentProp = p;
+            }
+            else {
+                return undefined;
+            }
+        }
+        return currentProp;
+    };
+    return mapper;
+}
+
+/** PURE_IMPORTS_START _Subject,_multicast PURE_IMPORTS_END */
+function publish(selector) {
+    return selector ?
+        multicast(function () { return new Subject(); }, selector) :
+        multicast(new Subject());
+}
+
+/** PURE_IMPORTS_START _ReplaySubject,_multicast PURE_IMPORTS_END */
+function publishReplay(bufferSize, windowTime, selectorOrScheduler, scheduler) {
+    if (selectorOrScheduler && typeof selectorOrScheduler !== 'function') {
+        scheduler = selectorOrScheduler;
+    }
+    var selector = typeof selectorOrScheduler === 'function' ? selectorOrScheduler : undefined;
+    var subject = new ReplaySubject(bufferSize, windowTime, scheduler);
+    return function (source) { return multicast(function () { return subject; }, selector)(source); };
+}
+
+/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */
+function retry(count) {
+    if (count === void 0) {
+        count = -1;
+    }
+    return function (source) { return source.lift(new RetryOperator(count, source)); };
+}
+var RetryOperator = /*@__PURE__*/ (function () {
+    function RetryOperator(count, source) {
+        this.count = count;
+        this.source = source;
+    }
+    RetryOperator.prototype.call = function (subscriber, source) {
+        return source.subscribe(new RetrySubscriber(subscriber, this.count, this.source));
+    };
+    return RetryOperator;
+}());
+var RetrySubscriber = /*@__PURE__*/ (function (_super) {
+    __extends(RetrySubscriber, _super);
+    function RetrySubscriber(destination, count, source) {
+        var _this = _super.call(this, destination) || this;
+        _this.count = count;
+        _this.source = source;
+        return _this;
+    }
+    RetrySubscriber.prototype.error = function (err) {
+        if (!this.isStopped) {
+            var _a = this, source = _a.source, count = _a.count;
+            if (count === 0) {
+                return _super.prototype.error.call(this, err);
+            }
+            else if (count > -1) {
+                this.count = count - 1;
+            }
+            source.subscribe(this._unsubscribeAndRecycle());
+        }
+    };
+    return RetrySubscriber;
+}(Subscriber));
+
+/** PURE_IMPORTS_START tslib,_innerSubscribe PURE_IMPORTS_END */
+function sample(notifier) {
+    return function (source) { return source.lift(new SampleOperator(notifier)); };
+}
+var SampleOperator = /*@__PURE__*/ (function () {
+    function SampleOperator(notifier) {
+        this.notifier = notifier;
+    }
+    SampleOperator.prototype.call = function (subscriber, source) {
+        var sampleSubscriber = new SampleSubscriber(subscriber);
+        var subscription = source.subscribe(sampleSubscriber);
+        subscription.add(innerSubscribe(this.notifier, new SimpleInnerSubscriber(sampleSubscriber)));
+        return subscription;
+    };
+    return SampleOperator;
+}());
+var SampleSubscriber = /*@__PURE__*/ (function (_super) {
+    __extends(SampleSubscriber, _super);
+    function SampleSubscriber() {
+        var _this = _super !== null && _super.apply(this, arguments) || this;
+        _this.hasValue = false;
+        return _this;
+    }
+    SampleSubscriber.prototype._next = function (value) {
+        this.value = value;
+        this.hasValue = true;
+    };
+    SampleSubscriber.prototype.notifyNext = function () {
+        this.emitValue();
+    };
+    SampleSubscriber.prototype.notifyComplete = function () {
+        this.emitValue();
+    };
+    SampleSubscriber.prototype.emitValue = function () {
+        if (this.hasValue) {
+            this.hasValue = false;
+            this.destination.next(this.value);
+        }
+    };
+    return SampleSubscriber;
+}(SimpleOuterSubscriber));
+
+/** PURE_IMPORTS_START _multicast,_refCount,_Subject PURE_IMPORTS_END */
+function shareSubjectFactory() {
+    return new Subject();
+}
+function share() {
+    return function (source) { return refCount()(multicast(shareSubjectFactory)(source)); };
+}
+
+/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */
+function skip(count) {
+    return function (source) { return source.lift(new SkipOperator(count)); };
+}
+var SkipOperator = /*@__PURE__*/ (function () {
+    function SkipOperator(total) {
+        this.total = total;
+    }
+    SkipOperator.prototype.call = function (subscriber, source) {
+        return source.subscribe(new SkipSubscriber(subscriber, this.total));
+    };
+    return SkipOperator;
+}());
+var SkipSubscriber = /*@__PURE__*/ (function (_super) {
+    __extends(SkipSubscriber, _super);
+    function SkipSubscriber(destination, total) {
+        var _this = _super.call(this, destination) || this;
+        _this.total = total;
+        _this.count = 0;
+        return _this;
+    }
+    SkipSubscriber.prototype._next = function (x) {
+        if (++this.count > this.total) {
+            this.destination.next(x);
+        }
+    };
+    return SkipSubscriber;
+}(Subscriber));
+
+/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */
+function skipWhile(predicate) {
+    return function (source) { return source.lift(new SkipWhileOperator(predicate)); };
+}
+var SkipWhileOperator = /*@__PURE__*/ (function () {
+    function SkipWhileOperator(predicate) {
+        this.predicate = predicate;
+    }
+    SkipWhileOperator.prototype.call = function (subscriber, source) {
+        return source.subscribe(new SkipWhileSubscriber(subscriber, this.predicate));
+    };
+    return SkipWhileOperator;
+}());
+var SkipWhileSubscriber = /*@__PURE__*/ (function (_super) {
+    __extends(SkipWhileSubscriber, _super);
+    function SkipWhileSubscriber(destination, predicate) {
+        var _this = _super.call(this, destination) || this;
+        _this.predicate = predicate;
+        _this.skipping = true;
+        _this.index = 0;
+        return _this;
+    }
+    SkipWhileSubscriber.prototype._next = function (value) {
+        var destination = this.destination;
+        if (this.skipping) {
+            this.tryCallPredicate(value);
+        }
+        if (!this.skipping) {
+            destination.next(value);
+        }
+    };
+    SkipWhileSubscriber.prototype.tryCallPredicate = function (value) {
+        try {
+            var result = this.predicate(value, this.index++);
+            this.skipping = Boolean(result);
+        }
+        catch (err) {
+            this.destination.error(err);
+        }
+    };
+    return SkipWhileSubscriber;
+}(Subscriber));
+
+/** PURE_IMPORTS_START _observable_concat,_util_isScheduler PURE_IMPORTS_END */
+function startWith() {
+    var array = [];
+    for (var _i = 0; _i < arguments.length; _i++) {
+        array[_i] = arguments[_i];
+    }
+    var scheduler = array[array.length - 1];
+    if (isScheduler(scheduler)) {
+        array.pop();
+        return function (source) { return concat(array, source, scheduler); };
+    }
+    else {
+        return function (source) { return concat(array, source); };
+    }
+}
+
+/** PURE_IMPORTS_START tslib,_map,_observable_from,_innerSubscribe PURE_IMPORTS_END */
+function switchMap(project, resultSelector) {
+    if (typeof resultSelector === 'function') {
+        return function (source) { return source.pipe(switchMap(function (a, i) { return from(project(a, i)).pipe(map(function (b, ii) { return resultSelector(a, b, i, ii); })); })); };
+    }
+    return function (source) { return source.lift(new SwitchMapOperator(project)); };
+}
+var SwitchMapOperator = /*@__PURE__*/ (function () {
+    function SwitchMapOperator(project) {
+        this.project = project;
+    }
+    SwitchMapOperator.prototype.call = function (subscriber, source) {
+        return source.subscribe(new SwitchMapSubscriber(subscriber, this.project));
+    };
+    return SwitchMapOperator;
+}());
+var SwitchMapSubscriber = /*@__PURE__*/ (function (_super) {
+    __extends(SwitchMapSubscriber, _super);
+    function SwitchMapSubscriber(destination, project) {
+        var _this = _super.call(this, destination) || this;
+        _this.project = project;
+        _this.index = 0;
+        return _this;
+    }
+    SwitchMapSubscriber.prototype._next = function (value) {
+        var result;
+        var index = this.index++;
+        try {
+            result = this.project(value, index);
+        }
+        catch (error) {
+            this.destination.error(error);
+            return;
+        }
+        this._innerSub(result);
+    };
+    SwitchMapSubscriber.prototype._innerSub = function (result) {
+        var innerSubscription = this.innerSubscription;
+        if (innerSubscription) {
+            innerSubscription.unsubscribe();
+        }
+        var innerSubscriber = new SimpleInnerSubscriber(this);
+        var destination = this.destination;
+        destination.add(innerSubscriber);
+        this.innerSubscription = innerSubscribe(result, innerSubscriber);
+        if (this.innerSubscription !== innerSubscriber) {
+            destination.add(this.innerSubscription);
+        }
+    };
+    SwitchMapSubscriber.prototype._complete = function () {
+        var innerSubscription = this.innerSubscription;
+        if (!innerSubscription || innerSubscription.closed) {
+            _super.prototype._complete.call(this);
+        }
+        this.unsubscribe();
+    };
+    SwitchMapSubscriber.prototype._unsubscribe = function () {
+        this.innerSubscription = undefined;
+    };
+    SwitchMapSubscriber.prototype.notifyComplete = function () {
+        this.innerSubscription = undefined;
+        if (this.isStopped) {
+            _super.prototype._complete.call(this);
+        }
+    };
+    SwitchMapSubscriber.prototype.notifyNext = function (innerValue) {
+        this.destination.next(innerValue);
+    };
+    return SwitchMapSubscriber;
+}(SimpleOuterSubscriber));
+
+/** PURE_IMPORTS_START tslib,_innerSubscribe PURE_IMPORTS_END */
+function takeUntil(notifier) {
+    return function (source) { return source.lift(new TakeUntilOperator(notifier)); };
+}
+var TakeUntilOperator = /*@__PURE__*/ (function () {
+    function TakeUntilOperator(notifier) {
+        this.notifier = notifier;
+    }
+    TakeUntilOperator.prototype.call = function (subscriber, source) {
+        var takeUntilSubscriber = new TakeUntilSubscriber(subscriber);
+        var notifierSubscription = innerSubscribe(this.notifier, new SimpleInnerSubscriber(takeUntilSubscriber));
+        if (notifierSubscription && !takeUntilSubscriber.seenValue) {
+            takeUntilSubscriber.add(notifierSubscription);
+            return source.subscribe(takeUntilSubscriber);
+        }
+        return takeUntilSubscriber;
+    };
+    return TakeUntilOperator;
+}());
+var TakeUntilSubscriber = /*@__PURE__*/ (function (_super) {
+    __extends(TakeUntilSubscriber, _super);
+    function TakeUntilSubscriber(destination) {
+        var _this = _super.call(this, destination) || this;
+        _this.seenValue = false;
+        return _this;
+    }
+    TakeUntilSubscriber.prototype.notifyNext = function () {
+        this.seenValue = true;
+        this.complete();
+    };
+    TakeUntilSubscriber.prototype.notifyComplete = function () {
+    };
+    return TakeUntilSubscriber;
+}(SimpleOuterSubscriber));
+
+/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */
+function takeWhile(predicate, inclusive) {
+    if (inclusive === void 0) {
+        inclusive = false;
+    }
+    return function (source) {
+        return source.lift(new TakeWhileOperator(predicate, inclusive));
+    };
+}
+var TakeWhileOperator = /*@__PURE__*/ (function () {
+    function TakeWhileOperator(predicate, inclusive) {
+        this.predicate = predicate;
+        this.inclusive = inclusive;
+    }
+    TakeWhileOperator.prototype.call = function (subscriber, source) {
+        return source.subscribe(new TakeWhileSubscriber(subscriber, this.predicate, this.inclusive));
+    };
+    return TakeWhileOperator;
+}());
+var TakeWhileSubscriber = /*@__PURE__*/ (function (_super) {
+    __extends(TakeWhileSubscriber, _super);
+    function TakeWhileSubscriber(destination, predicate, inclusive) {
+        var _this = _super.call(this, destination) || this;
+        _this.predicate = predicate;
+        _this.inclusive = inclusive;
+        _this.index = 0;
+        return _this;
+    }
+    TakeWhileSubscriber.prototype._next = function (value) {
+        var destination = this.destination;
+        var result;
+        try {
+            result = this.predicate(value, this.index++);
+        }
+        catch (err) {
+            destination.error(err);
+            return;
+        }
+        this.nextOrComplete(value, result);
+    };
+    TakeWhileSubscriber.prototype.nextOrComplete = function (value, predicateResult) {
+        var destination = this.destination;
+        if (Boolean(predicateResult)) {
+            destination.next(value);
+        }
+        else {
+            if (this.inclusive) {
+                destination.next(value);
+            }
+            destination.complete();
+        }
+    };
+    return TakeWhileSubscriber;
+}(Subscriber));
+
+/** PURE_IMPORTS_START tslib,_Subscriber,_util_noop,_util_isFunction PURE_IMPORTS_END */
+function tap(nextOrObserver, error, complete) {
+    return function tapOperatorFunction(source) {
+        return source.lift(new DoOperator(nextOrObserver, error, complete));
+    };
+}
+var DoOperator = /*@__PURE__*/ (function () {
+    function DoOperator(nextOrObserver, error, complete) {
+        this.nextOrObserver = nextOrObserver;
+        this.error = error;
+        this.complete = complete;
+    }
+    DoOperator.prototype.call = function (subscriber, source) {
+        return source.subscribe(new TapSubscriber(subscriber, this.nextOrObserver, this.error, this.complete));
+    };
+    return DoOperator;
+}());
+var TapSubscriber = /*@__PURE__*/ (function (_super) {
+    __extends(TapSubscriber, _super);
+    function TapSubscriber(destination, observerOrNext, error, complete) {
+        var _this = _super.call(this, destination) || this;
+        _this._tapNext = noop;
+        _this._tapError = noop;
+        _this._tapComplete = noop;
+        _this._tapError = error || noop;
+        _this._tapComplete = complete || noop;
+        if (isFunction(observerOrNext)) {
+            _this._context = _this;
+            _this._tapNext = observerOrNext;
+        }
+        else if (observerOrNext) {
+            _this._context = observerOrNext;
+            _this._tapNext = observerOrNext.next || noop;
+            _this._tapError = observerOrNext.error || noop;
+            _this._tapComplete = observerOrNext.complete || noop;
+        }
+        return _this;
+    }
+    TapSubscriber.prototype._next = function (value) {
+        try {
+            this._tapNext.call(this._context, value);
+        }
+        catch (err) {
+            this.destination.error(err);
+            return;
+        }
+        this.destination.next(value);
+    };
+    TapSubscriber.prototype._error = function (err) {
+        try {
+            this._tapError.call(this._context, err);
+        }
+        catch (err) {
+            this.destination.error(err);
+            return;
+        }
+        this.destination.error(err);
+    };
+    TapSubscriber.prototype._complete = function () {
+        try {
+            this._tapComplete.call(this._context);
+        }
+        catch (err) {
+            this.destination.error(err);
+            return;
+        }
+        return this.destination.complete();
+    };
+    return TapSubscriber;
+}(Subscriber));
+
+/** PURE_IMPORTS_START tslib,_scheduler_async,_util_isDate,_innerSubscribe PURE_IMPORTS_END */
+function timeoutWith(due, withObservable, scheduler) {
+    if (scheduler === void 0) {
+        scheduler = async;
+    }
+    return function (source) {
+        var absoluteTimeout = isDate(due);
+        var waitFor = absoluteTimeout ? (+due - scheduler.now()) : Math.abs(due);
+        return source.lift(new TimeoutWithOperator(waitFor, absoluteTimeout, withObservable, scheduler));
+    };
+}
+var TimeoutWithOperator = /*@__PURE__*/ (function () {
+    function TimeoutWithOperator(waitFor, absoluteTimeout, withObservable, scheduler) {
+        this.waitFor = waitFor;
+        this.absoluteTimeout = absoluteTimeout;
+        this.withObservable = withObservable;
+        this.scheduler = scheduler;
+    }
+    TimeoutWithOperator.prototype.call = function (subscriber, source) {
+        return source.subscribe(new TimeoutWithSubscriber(subscriber, this.absoluteTimeout, this.waitFor, this.withObservable, this.scheduler));
+    };
+    return TimeoutWithOperator;
+}());
+var TimeoutWithSubscriber = /*@__PURE__*/ (function (_super) {
+    __extends(TimeoutWithSubscriber, _super);
+    function TimeoutWithSubscriber(destination, absoluteTimeout, waitFor, withObservable, scheduler) {
+        var _this = _super.call(this, destination) || this;
+        _this.absoluteTimeout = absoluteTimeout;
+        _this.waitFor = waitFor;
+        _this.withObservable = withObservable;
+        _this.scheduler = scheduler;
+        _this.scheduleTimeout();
+        return _this;
+    }
+    TimeoutWithSubscriber.dispatchTimeout = function (subscriber) {
+        var withObservable = subscriber.withObservable;
+        subscriber._unsubscribeAndRecycle();
+        subscriber.add(innerSubscribe(withObservable, new SimpleInnerSubscriber(subscriber)));
+    };
+    TimeoutWithSubscriber.prototype.scheduleTimeout = function () {
+        var action = this.action;
+        if (action) {
+            this.action = action.schedule(this, this.waitFor);
+        }
+        else {
+            this.add(this.action = this.scheduler.schedule(TimeoutWithSubscriber.dispatchTimeout, this.waitFor, this));
+        }
+    };
+    TimeoutWithSubscriber.prototype._next = function (value) {
+        if (!this.absoluteTimeout) {
+            this.scheduleTimeout();
+        }
+        _super.prototype._next.call(this, value);
+    };
+    TimeoutWithSubscriber.prototype._unsubscribe = function () {
+        this.action = undefined;
+        this.scheduler = null;
+        this.withObservable = null;
+    };
+    return TimeoutWithSubscriber;
+}(SimpleOuterSubscriber));
+
+/** PURE_IMPORTS_START _scheduler_async,_util_TimeoutError,_timeoutWith,_observable_throwError PURE_IMPORTS_END */
+function timeout(due, scheduler) {
+    if (scheduler === void 0) {
+        scheduler = async;
+    }
+    return timeoutWith(due, throwError(new TimeoutError()), scheduler);
+}
+
+/** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */
+function withLatestFrom() {
+    var args = [];
+    for (var _i = 0; _i < arguments.length; _i++) {
+        args[_i] = arguments[_i];
+    }
+    return function (source) {
+        var project;
+        if (typeof args[args.length - 1] === 'function') {
+            project = args.pop();
+        }
+        var observables = args;
+        return source.lift(new WithLatestFromOperator(observables, project));
+    };
+}
+var WithLatestFromOperator = /*@__PURE__*/ (function () {
+    function WithLatestFromOperator(observables, project) {
+        this.observables = observables;
+        this.project = project;
+    }
+    WithLatestFromOperator.prototype.call = function (subscriber, source) {
+        return source.subscribe(new WithLatestFromSubscriber(subscriber, this.observables, this.project));
+    };
+    return WithLatestFromOperator;
+}());
+var WithLatestFromSubscriber = /*@__PURE__*/ (function (_super) {
+    __extends(WithLatestFromSubscriber, _super);
+    function WithLatestFromSubscriber(destination, observables, project) {
+        var _this = _super.call(this, destination) || this;
+        _this.observables = observables;
+        _this.project = project;
+        _this.toRespond = [];
+        var len = observables.length;
+        _this.values = new Array(len);
+        for (var i = 0; i < len; i++) {
+            _this.toRespond.push(i);
+        }
+        for (var i = 0; i < len; i++) {
+            var observable = observables[i];
+            _this.add(subscribeToResult(_this, observable, undefined, i));
+        }
+        return _this;
+    }
+    WithLatestFromSubscriber.prototype.notifyNext = function (_outerValue, innerValue, outerIndex) {
+        this.values[outerIndex] = innerValue;
+        var toRespond = this.toRespond;
+        if (toRespond.length > 0) {
+            var found = toRespond.indexOf(outerIndex);
+            if (found !== -1) {
+                toRespond.splice(found, 1);
+            }
+        }
+    };
+    WithLatestFromSubscriber.prototype.notifyComplete = function () {
+    };
+    WithLatestFromSubscriber.prototype._next = function (value) {
+        if (this.toRespond.length === 0) {
+            var args = [value].concat(this.values);
+            if (this.project) {
+                this._tryProject(args);
+            }
+            else {
+                this.destination.next(args);
+            }
+        }
+    };
+    WithLatestFromSubscriber.prototype._tryProject = function (args) {
+        var result;
+        try {
+            result = this.project.apply(this, args);
+        }
+        catch (err) {
+            this.destination.error(err);
+            return;
+        }
+        this.destination.next(result);
+    };
+    return WithLatestFromSubscriber;
+}(OuterSubscriber));
+
+/**
+ * @class Filter
+ *
+ * @classdesc Represents a class for creating image filters. Implementation and
+ * definitions based on https://github.com/mapbox/feature-filter.
+ */
+class FilterCreator {
+    /**
+     * Create a filter from a filter expression.
+     *
+     * @description The following filters are supported:
+     *
+     * Comparison
+     * `==`
+     * `!=`
+     * `<`
+     * `<=`
+     * `>`
+     * `>=`
+     *
+     * Set membership
+     * `in`
+     * `!in`
+     *
+     * Combining
+     * `all`
+     *
+     * @param {FilterExpression} filter - Comparison, set membership or combinding filter
+     * expression.
+     * @returns {FilterFunction} Function taking a image and returning a boolean that
+     * indicates whether the image passed the test or not.
+     */
+    createFilter(filter) {
+        return new Function("node", "return " + this._compile(filter) + ";");
+    }
+    _compile(filter) {
+        if (filter == null || filter.length <= 1) {
+            return "true";
+        }
+        const operator = filter[0];
+        const operation = operator === "==" ? this._compileComparisonOp("===", filter[1], filter[2], false) :
+            operator === "!=" ? this._compileComparisonOp("!==", filter[1], filter[2], false) :
+                operator === ">" ||
+                    operator === ">=" ||
+                    operator === "<" ||
+                    operator === "<=" ? this._compileComparisonOp(operator, filter[1], filter[2], true) :
+                    operator === "in" ?
+                        this._compileInOp(filter[1], filter.slice(2)) :
+                        operator === "!in" ?
+                            this._compileNegation(this._compileInOp(filter[1], filter.slice(2))) :
+                            operator === "all" ? this._compileLogicalOp(filter.slice(1), "&&") :
+                                "true";
+        return "(" + operation + ")";
+    }
+    _compare(a, b) {
+        return a < b ? -1 : a > b ? 1 : 0;
+    }
+    _compileComparisonOp(operator, property, value, checkType) {
+        const left = this._compilePropertyReference(property);
+        const right = JSON.stringify(value);
+        return (checkType ? "typeof " + left + "===typeof " + right + "&&" : "") + left + operator + right;
+    }
+    _compileInOp(property, values) {
+        const compare = this._compare;
+        const left = JSON.stringify(values.sort(compare));
+        const right = this._compilePropertyReference(property);
+        return left + ".indexOf(" + right + ")!==-1";
+    }
+    _compileLogicalOp(filters, operator) {
+        const compile = this._compile.bind(this);
+        return filters.map(compile).join(operator);
+    }
+    _compileNegation(expression) {
+        return "!(" + expression + ")";
+    }
+    _compilePropertyReference(property) {
+        return "node[" + JSON.stringify(property) + "]";
+    }
+}
+
+// threejs.org/license
+const REVISION = '125';
+const CullFaceNone = 0;
+const CullFaceBack = 1;
+const CullFaceFront = 2;
+const PCFShadowMap = 1;
+const PCFSoftShadowMap = 2;
+const VSMShadowMap = 3;
+const FrontSide = 0;
+const BackSide = 1;
+const DoubleSide = 2;
+const FlatShading = 1;
+const NoBlending = 0;
+const NormalBlending = 1;
+const AdditiveBlending = 2;
+const SubtractiveBlending = 3;
+const MultiplyBlending = 4;
+const CustomBlending = 5;
+const AddEquation = 100;
+const SubtractEquation = 101;
+const ReverseSubtractEquation = 102;
+const MinEquation = 103;
+const MaxEquation = 104;
+const ZeroFactor = 200;
+const OneFactor = 201;
+const SrcColorFactor = 202;
+const OneMinusSrcColorFactor = 203;
+const SrcAlphaFactor = 204;
+const OneMinusSrcAlphaFactor = 205;
+const DstAlphaFactor = 206;
+const OneMinusDstAlphaFactor = 207;
+const DstColorFactor = 208;
+const OneMinusDstColorFactor = 209;
+const SrcAlphaSaturateFactor = 210;
+const NeverDepth = 0;
+const AlwaysDepth = 1;
+const LessDepth = 2;
+const LessEqualDepth = 3;
+const EqualDepth = 4;
+const GreaterEqualDepth = 5;
+const GreaterDepth = 6;
+const NotEqualDepth = 7;
+const MultiplyOperation = 0;
+const MixOperation = 1;
+const AddOperation = 2;
+const NoToneMapping = 0;
+const LinearToneMapping = 1;
+const ReinhardToneMapping = 2;
+const CineonToneMapping = 3;
+const ACESFilmicToneMapping = 4;
+const CustomToneMapping = 5;
+
+const UVMapping = 300;
+const CubeReflectionMapping = 301;
+const CubeRefractionMapping = 302;
+const EquirectangularReflectionMapping = 303;
+const EquirectangularRefractionMapping = 304;
+const CubeUVReflectionMapping = 306;
+const CubeUVRefractionMapping = 307;
+const RepeatWrapping = 1000;
+const ClampToEdgeWrapping = 1001;
+const MirroredRepeatWrapping = 1002;
+const NearestFilter = 1003;
+const NearestMipmapNearestFilter = 1004;
+const NearestMipmapLinearFilter = 1005;
+const LinearFilter = 1006;
+const LinearMipmapNearestFilter = 1007;
+const LinearMipmapLinearFilter = 1008;
+const UnsignedByteType = 1009;
+const ByteType = 1010;
+const ShortType = 1011;
+const UnsignedShortType = 1012;
+const IntType = 1013;
+const UnsignedIntType = 1014;
+const FloatType = 1015;
+const HalfFloatType = 1016;
+const UnsignedShort4444Type = 1017;
+const UnsignedShort5551Type = 1018;
+const UnsignedShort565Type = 1019;
+const UnsignedInt248Type = 1020;
+const AlphaFormat = 1021;
+const RGBFormat = 1022;
+const RGBAFormat = 1023;
+const LuminanceFormat = 1024;
+const LuminanceAlphaFormat = 1025;
+const DepthFormat = 1026;
+const DepthStencilFormat = 1027;
+const RedFormat = 1028;
+const RedIntegerFormat = 1029;
+const RGFormat = 1030;
+const RGIntegerFormat = 1031;
+const RGBIntegerFormat = 1032;
+const RGBAIntegerFormat = 1033;
+
+const RGB_S3TC_DXT1_Format = 33776;
+const RGBA_S3TC_DXT1_Format = 33777;
+const RGBA_S3TC_DXT3_Format = 33778;
+const RGBA_S3TC_DXT5_Format = 33779;
+const RGB_PVRTC_4BPPV1_Format = 35840;
+const RGB_PVRTC_2BPPV1_Format = 35841;
+const RGBA_PVRTC_4BPPV1_Format = 35842;
+const RGBA_PVRTC_2BPPV1_Format = 35843;
+const RGB_ETC1_Format = 36196;
+const RGB_ETC2_Format = 37492;
+const RGBA_ETC2_EAC_Format = 37496;
+const RGBA_ASTC_4x4_Format = 37808;
+const RGBA_ASTC_5x4_Format = 37809;
+const RGBA_ASTC_5x5_Format = 37810;
+const RGBA_ASTC_6x5_Format = 37811;
+const RGBA_ASTC_6x6_Format = 37812;
+const RGBA_ASTC_8x5_Format = 37813;
+const RGBA_ASTC_8x6_Format = 37814;
+const RGBA_ASTC_8x8_Format = 37815;
+const RGBA_ASTC_10x5_Format = 37816;
+const RGBA_ASTC_10x6_Format = 37817;
+const RGBA_ASTC_10x8_Format = 37818;
+const RGBA_ASTC_10x10_Format = 37819;
+const RGBA_ASTC_12x10_Format = 37820;
+const RGBA_ASTC_12x12_Format = 37821;
+const RGBA_BPTC_Format = 36492;
+const SRGB8_ALPHA8_ASTC_4x4_Format = 37840;
+const SRGB8_ALPHA8_ASTC_5x4_Format = 37841;
+const SRGB8_ALPHA8_ASTC_5x5_Format = 37842;
+const SRGB8_ALPHA8_ASTC_6x5_Format = 37843;
+const SRGB8_ALPHA8_ASTC_6x6_Format = 37844;
+const SRGB8_ALPHA8_ASTC_8x5_Format = 37845;
+const SRGB8_ALPHA8_ASTC_8x6_Format = 37846;
+const SRGB8_ALPHA8_ASTC_8x8_Format = 37847;
+const SRGB8_ALPHA8_ASTC_10x5_Format = 37848;
+const SRGB8_ALPHA8_ASTC_10x6_Format = 37849;
+const SRGB8_ALPHA8_ASTC_10x8_Format = 37850;
+const SRGB8_ALPHA8_ASTC_10x10_Format = 37851;
+const SRGB8_ALPHA8_ASTC_12x10_Format = 37852;
+const SRGB8_ALPHA8_ASTC_12x12_Format = 37853;
+const LoopOnce = 2200;
+const LoopRepeat = 2201;
+const LoopPingPong = 2202;
+const InterpolateDiscrete = 2300;
+const InterpolateLinear = 2301;
+const InterpolateSmooth = 2302;
+const ZeroCurvatureEnding = 2400;
+const ZeroSlopeEnding = 2401;
+const WrapAroundEnding = 2402;
+const NormalAnimationBlendMode = 2500;
+const AdditiveAnimationBlendMode = 2501;
+const TrianglesDrawMode = 0;
+const LinearEncoding = 3000;
+const sRGBEncoding = 3001;
+const GammaEncoding = 3007;
+const RGBEEncoding = 3002;
+const LogLuvEncoding = 3003;
+const RGBM7Encoding = 3004;
+const RGBM16Encoding = 3005;
+const RGBDEncoding = 3006;
+const BasicDepthPacking = 3200;
+const RGBADepthPacking = 3201;
+const TangentSpaceNormalMap = 0;
+const ObjectSpaceNormalMap = 1;
+const KeepStencilOp = 7680;
+const AlwaysStencilFunc = 519;
+
+const StaticDrawUsage = 35044;
+const DynamicDrawUsage = 35048;
+const GLSL3 = '300 es';
+
+/**
+ * https://github.com/mrdoob/eventdispatcher.js/
+ */
+
+function EventDispatcher() {}
+
+Object.assign( EventDispatcher.prototype, {
+
+       addEventListener: function ( type, listener ) {
+
+               if ( this._listeners === undefined ) this._listeners = {};
+
+               const listeners = this._listeners;
+
+               if ( listeners[ type ] === undefined ) {
+
+                       listeners[ type ] = [];
+
+               }
+
+               if ( listeners[ type ].indexOf( listener ) === - 1 ) {
+
+                       listeners[ type ].push( listener );
+
+               }
+
+       },
+
+       hasEventListener: function ( type, listener ) {
+
+               if ( this._listeners === undefined ) return false;
+
+               const listeners = this._listeners;
+
+               return listeners[ type ] !== undefined && listeners[ type ].indexOf( listener ) !== - 1;
+
+       },
+
+       removeEventListener: function ( type, listener ) {
+
+               if ( this._listeners === undefined ) return;
+
+               const listeners = this._listeners;
+               const listenerArray = listeners[ type ];
+
+               if ( listenerArray !== undefined ) {
+
+                       const index = listenerArray.indexOf( listener );
+
+                       if ( index !== - 1 ) {
+
+                               listenerArray.splice( index, 1 );
+
+                       }
+
+               }
+
+       },
+
+       dispatchEvent: function ( event ) {
+
+               if ( this._listeners === undefined ) return;
+
+               const listeners = this._listeners;
+               const listenerArray = listeners[ event.type ];
+
+               if ( listenerArray !== undefined ) {
+
+                       event.target = this;
+
+                       // Make a copy, in case listeners are removed while iterating.
+                       const array = listenerArray.slice( 0 );
+
+                       for ( let i = 0, l = array.length; i < l; i ++ ) {
+
+                               array[ i ].call( this, event );
+
+                       }
+
+               }
+
+       }
+
+} );
+
+const _lut = [];
+
+for ( let i = 0; i < 256; i ++ ) {
+
+       _lut[ i ] = ( i < 16 ? '0' : '' ) + ( i ).toString( 16 );
+
+}
+
+let _seed = 1234567;
+
+const MathUtils = {
+
+       DEG2RAD: Math.PI / 180,
+       RAD2DEG: 180 / Math.PI,
+
+       generateUUID: function () {
+
+               // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/21963136#21963136
+
+               const d0 = Math.random() * 0xffffffff | 0;
+               const d1 = Math.random() * 0xffffffff | 0;
+               const d2 = Math.random() * 0xffffffff | 0;
+               const d3 = Math.random() * 0xffffffff | 0;
+               const uuid = _lut[ d0 & 0xff ] + _lut[ d0 >> 8 & 0xff ] + _lut[ d0 >> 16 & 0xff ] + _lut[ d0 >> 24 & 0xff ] + '-' +
+                       _lut[ d1 & 0xff ] + _lut[ d1 >> 8 & 0xff ] + '-' + _lut[ d1 >> 16 & 0x0f | 0x40 ] + _lut[ d1 >> 24 & 0xff ] + '-' +
+                       _lut[ d2 & 0x3f | 0x80 ] + _lut[ d2 >> 8 & 0xff ] + '-' + _lut[ d2 >> 16 & 0xff ] + _lut[ d2 >> 24 & 0xff ] +
+                       _lut[ d3 & 0xff ] + _lut[ d3 >> 8 & 0xff ] + _lut[ d3 >> 16 & 0xff ] + _lut[ d3 >> 24 & 0xff ];
+
+               // .toUpperCase() here flattens concatenated strings to save heap memory space.
+               return uuid.toUpperCase();
+
+       },
+
+       clamp: function ( value, min, max ) {
+
+               return Math.max( min, Math.min( max, value ) );
+
+       },
+
+       // compute euclidian modulo of m % n
+       // https://en.wikipedia.org/wiki/Modulo_operation
+
+       euclideanModulo: function ( n, m ) {
+
+               return ( ( n % m ) + m ) % m;
+
+       },
+
+       // Linear mapping from range <a1, a2> to range <b1, b2>
+
+       mapLinear: function ( x, a1, a2, b1, b2 ) {
+
+               return b1 + ( x - a1 ) * ( b2 - b1 ) / ( a2 - a1 );
+
+       },
+
+       // https://en.wikipedia.org/wiki/Linear_interpolation
+
+       lerp: function ( x, y, t ) {
+
+               return ( 1 - t ) * x + t * y;
+
+       },
+
+       // http://www.rorydriscoll.com/2016/03/07/frame-rate-independent-damping-using-lerp/
+
+       damp: function ( x, y, lambda, dt ) {
+
+               return MathUtils.lerp( x, y, 1 - Math.exp( - lambda * dt ) );
+
+       },
+
+       // https://www.desmos.com/calculator/vcsjnyz7x4
+
+       pingpong: function ( x, length = 1 ) {
+
+               return length - Math.abs( MathUtils.euclideanModulo( x, length * 2 ) - length );
+
+       },
+
+       // http://en.wikipedia.org/wiki/Smoothstep
+
+       smoothstep: function ( x, min, max ) {
+
+               if ( x <= min ) return 0;
+               if ( x >= max ) return 1;
+
+               x = ( x - min ) / ( max - min );
+
+               return x * x * ( 3 - 2 * x );
+
+       },
+
+       smootherstep: function ( x, min, max ) {
+
+               if ( x <= min ) return 0;
+               if ( x >= max ) return 1;
+
+               x = ( x - min ) / ( max - min );
+
+               return x * x * x * ( x * ( x * 6 - 15 ) + 10 );
+
+       },
+
+       // Random integer from <low, high> interval
+
+       randInt: function ( low, high ) {
+
+               return low + Math.floor( Math.random() * ( high - low + 1 ) );
+
+       },
+
+       // Random float from <low, high> interval
+
+       randFloat: function ( low, high ) {
+
+               return low + Math.random() * ( high - low );
+
+       },
+
+       // Random float from <-range/2, range/2> interval
+
+       randFloatSpread: function ( range ) {
+
+               return range * ( 0.5 - Math.random() );
+
+       },
+
+       // Deterministic pseudo-random float in the interval [ 0, 1 ]
+
+       seededRandom: function ( s ) {
+
+               if ( s !== undefined ) _seed = s % 2147483647;
+
+               // Park-Miller algorithm
+
+               _seed = _seed * 16807 % 2147483647;
+
+               return ( _seed - 1 ) / 2147483646;
+
+       },
+
+       degToRad: function ( degrees ) {
+
+               return degrees * MathUtils.DEG2RAD;
+
+       },
+
+       radToDeg: function ( radians ) {
+
+               return radians * MathUtils.RAD2DEG;
+
+       },
+
+       isPowerOfTwo: function ( value ) {
+
+               return ( value & ( value - 1 ) ) === 0 && value !== 0;
+
+       },
+
+       ceilPowerOfTwo: function ( value ) {
+
+               return Math.pow( 2, Math.ceil( Math.log( value ) / Math.LN2 ) );
+
+       },
+
+       floorPowerOfTwo: function ( value ) {
+
+               return Math.pow( 2, Math.floor( Math.log( value ) / Math.LN2 ) );
+
+       },
+
+       setQuaternionFromProperEuler: function ( q, a, b, c, order ) {
+
+               // Intrinsic Proper Euler Angles - see https://en.wikipedia.org/wiki/Euler_angles
+
+               // rotations are applied to the axes in the order specified by 'order'
+               // rotation by angle 'a' is applied first, then by angle 'b', then by angle 'c'
+               // angles are in radians
+
+               const cos = Math.cos;
+               const sin = Math.sin;
+
+               const c2 = cos( b / 2 );
+               const s2 = sin( b / 2 );
+
+               const c13 = cos( ( a + c ) / 2 );
+               const s13 = sin( ( a + c ) / 2 );
+
+               const c1_3 = cos( ( a - c ) / 2 );
+               const s1_3 = sin( ( a - c ) / 2 );
+
+               const c3_1 = cos( ( c - a ) / 2 );
+               const s3_1 = sin( ( c - a ) / 2 );
+
+               switch ( order ) {
+
+                       case 'XYX':
+                               q.set( c2 * s13, s2 * c1_3, s2 * s1_3, c2 * c13 );
+                               break;
+
+                       case 'YZY':
+                               q.set( s2 * s1_3, c2 * s13, s2 * c1_3, c2 * c13 );
+                               break;
+
+                       case 'ZXZ':
+                               q.set( s2 * c1_3, s2 * s1_3, c2 * s13, c2 * c13 );
+                               break;
+
+                       case 'XZX':
+                               q.set( c2 * s13, s2 * s3_1, s2 * c3_1, c2 * c13 );
+                               break;
+
+                       case 'YXY':
+                               q.set( s2 * c3_1, c2 * s13, s2 * s3_1, c2 * c13 );
+                               break;
+
+                       case 'ZYZ':
+                               q.set( s2 * s3_1, s2 * c3_1, c2 * s13, c2 * c13 );
+                               break;
+
+                       default:
+                               console.warn( 'THREE.MathUtils: .setQuaternionFromProperEuler() encountered an unknown order: ' + order );
+
+               }
+
+       }
+
+};
+
+class Vector2 {
+
+       constructor( x = 0, y = 0 ) {
+
+               Object.defineProperty( this, 'isVector2', { value: true } );
+
+               this.x = x;
+               this.y = y;
+
+       }
+
+       get width() {
+
+               return this.x;
+
+       }
+
+       set width( value ) {
+
+               this.x = value;
+
+       }
+
+       get height() {
+
+               return this.y;
+
+       }
+
+       set height( value ) {
+
+               this.y = value;
+
+       }
+
+       set( x, y ) {
+
+               this.x = x;
+               this.y = y;
+
+               return this;
+
+       }
+
+       setScalar( scalar ) {
+
+               this.x = scalar;
+               this.y = scalar;
+
+               return this;
+
+       }
+
+       setX( x ) {
+
+               this.x = x;
+
+               return this;
+
+       }
+
+       setY( y ) {
+
+               this.y = y;
+
+               return this;
+
+       }
+
+       setComponent( index, value ) {
+
+               switch ( index ) {
+
+                       case 0: this.x = value; break;
+                       case 1: this.y = value; break;
+                       default: throw new Error( 'index is out of range: ' + index );
+
+               }
+
+               return this;
+
+       }
+
+       getComponent( index ) {
+
+               switch ( index ) {
+
+                       case 0: return this.x;
+                       case 1: return this.y;
+                       default: throw new Error( 'index is out of range: ' + index );
+
+               }
+
+       }
+
+       clone() {
+
+               return new this.constructor( this.x, this.y );
+
+       }
+
+       copy( v ) {
+
+               this.x = v.x;
+               this.y = v.y;
+
+               return this;
+
+       }
+
+       add( v, w ) {
+
+               if ( w !== undefined ) {
+
+                       console.warn( 'THREE.Vector2: .add() now only accepts one argument. Use .addVectors( a, b ) instead.' );
+                       return this.addVectors( v, w );
+
+               }
+
+               this.x += v.x;
+               this.y += v.y;
+
+               return this;
+
+       }
+
+       addScalar( s ) {
+
+               this.x += s;
+               this.y += s;
+
+               return this;
+
+       }
+
+       addVectors( a, b ) {
+
+               this.x = a.x + b.x;
+               this.y = a.y + b.y;
+
+               return this;
+
+       }
+
+       addScaledVector( v, s ) {
+
+               this.x += v.x * s;
+               this.y += v.y * s;
+
+               return this;
+
+       }
+
+       sub( v, w ) {
+
+               if ( w !== undefined ) {
+
+                       console.warn( 'THREE.Vector2: .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' );
+                       return this.subVectors( v, w );
+
+               }
+
+               this.x -= v.x;
+               this.y -= v.y;
+
+               return this;
+
+       }
+
+       subScalar( s ) {
+
+               this.x -= s;
+               this.y -= s;
+
+               return this;
+
+       }
+
+       subVectors( a, b ) {
+
+               this.x = a.x - b.x;
+               this.y = a.y - b.y;
+
+               return this;
+
+       }
+
+       multiply( v ) {
+
+               this.x *= v.x;
+               this.y *= v.y;
+
+               return this;
+
+       }
+
+       multiplyScalar( scalar ) {
+
+               this.x *= scalar;
+               this.y *= scalar;
+
+               return this;
+
+       }
+
+       divide( v ) {
+
+               this.x /= v.x;
+               this.y /= v.y;
+
+               return this;
+
+       }
+
+       divideScalar( scalar ) {
+
+               return this.multiplyScalar( 1 / scalar );
+
+       }
+
+       applyMatrix3( m ) {
+
+               const x = this.x, y = this.y;
+               const e = m.elements;
+
+               this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ];
+               this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ];
+
+               return this;
+
+       }
+
+       min( v ) {
+
+               this.x = Math.min( this.x, v.x );
+               this.y = Math.min( this.y, v.y );
+
+               return this;
+
+       }
+
+       max( v ) {
+
+               this.x = Math.max( this.x, v.x );
+               this.y = Math.max( this.y, v.y );
+
+               return this;
+
+       }
+
+       clamp( min, max ) {
+
+               // assumes min < max, componentwise
+
+               this.x = Math.max( min.x, Math.min( max.x, this.x ) );
+               this.y = Math.max( min.y, Math.min( max.y, this.y ) );
+
+               return this;
+
+       }
+
+       clampScalar( minVal, maxVal ) {
+
+               this.x = Math.max( minVal, Math.min( maxVal, this.x ) );
+               this.y = Math.max( minVal, Math.min( maxVal, this.y ) );
+
+               return this;
+
+       }
+
+       clampLength( min, max ) {
+
+               const length = this.length();
+
+               return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) );
+
+       }
+
+       floor() {
+
+               this.x = Math.floor( this.x );
+               this.y = Math.floor( this.y );
+
+               return this;
+
+       }
+
+       ceil() {
+
+               this.x = Math.ceil( this.x );
+               this.y = Math.ceil( this.y );
+
+               return this;
+
+       }
+
+       round() {
+
+               this.x = Math.round( this.x );
+               this.y = Math.round( this.y );
+
+               return this;
+
+       }
+
+       roundToZero() {
+
+               this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x );
+               this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y );
+
+               return this;
+
+       }
+
+       negate() {
+
+               this.x = - this.x;
+               this.y = - this.y;
+
+               return this;
+
+       }
+
+       dot( v ) {
+
+               return this.x * v.x + this.y * v.y;
+
+       }
+
+       cross( v ) {
+
+               return this.x * v.y - this.y * v.x;
+
+       }
+
+       lengthSq() {
+
+               return this.x * this.x + this.y * this.y;
+
+       }
+
+       length() {
+
+               return Math.sqrt( this.x * this.x + this.y * this.y );
+
+       }
+
+       manhattanLength() {
+
+               return Math.abs( this.x ) + Math.abs( this.y );
+
+       }
+
+       normalize() {
+
+               return this.divideScalar( this.length() || 1 );
+
+       }
+
+       angle() {
+
+               // computes the angle in radians with respect to the positive x-axis
+
+               const angle = Math.atan2( - this.y, - this.x ) + Math.PI;
+
+               return angle;
+
+       }
+
+       distanceTo( v ) {
+
+               return Math.sqrt( this.distanceToSquared( v ) );
+
+       }
+
+       distanceToSquared( v ) {
+
+               const dx = this.x - v.x, dy = this.y - v.y;
+               return dx * dx + dy * dy;
+
+       }
+
+       manhattanDistanceTo( v ) {
+
+               return Math.abs( this.x - v.x ) + Math.abs( this.y - v.y );
+
+       }
+
+       setLength( length ) {
+
+               return this.normalize().multiplyScalar( length );
+
+       }
+
+       lerp( v, alpha ) {
+
+               this.x += ( v.x - this.x ) * alpha;
+               this.y += ( v.y - this.y ) * alpha;
+
+               return this;
+
+       }
+
+       lerpVectors( v1, v2, alpha ) {
+
+               this.x = v1.x + ( v2.x - v1.x ) * alpha;
+               this.y = v1.y + ( v2.y - v1.y ) * alpha;
+
+               return this;
+
+       }
+
+       equals( v ) {
+
+               return ( ( v.x === this.x ) && ( v.y === this.y ) );
+
+       }
+
+       fromArray( array, offset = 0 ) {
+
+               this.x = array[ offset ];
+               this.y = array[ offset + 1 ];
+
+               return this;
+
+       }
+
+       toArray( array = [], offset = 0 ) {
+
+               array[ offset ] = this.x;
+               array[ offset + 1 ] = this.y;
+
+               return array;
+
+       }
+
+       fromBufferAttribute( attribute, index, offset ) {
+
+               if ( offset !== undefined ) {
+
+                       console.warn( 'THREE.Vector2: offset has been removed from .fromBufferAttribute().' );
+
+               }
+
+               this.x = attribute.getX( index );
+               this.y = attribute.getY( index );
+
+               return this;
+
+       }
+
+       rotateAround( center, angle ) {
+
+               const c = Math.cos( angle ), s = Math.sin( angle );
+
+               const x = this.x - center.x;
+               const y = this.y - center.y;
+
+               this.x = x * c - y * s + center.x;
+               this.y = x * s + y * c + center.y;
+
+               return this;
+
+       }
+
+       random() {
+
+               this.x = Math.random();
+               this.y = Math.random();
+
+               return this;
+
+       }
+
+}
+
+class Matrix3 {
+
+       constructor() {
+
+               Object.defineProperty( this, 'isMatrix3', { value: true } );
+
+               this.elements = [
+
+                       1, 0, 0,
+                       0, 1, 0,
+                       0, 0, 1
+
+               ];
+
+               if ( arguments.length > 0 ) {
+
+                       console.error( 'THREE.Matrix3: the constructor no longer reads arguments. use .set() instead.' );
+
+               }
+
+       }
+
+       set( n11, n12, n13, n21, n22, n23, n31, n32, n33 ) {
+
+               const te = this.elements;
+
+               te[ 0 ] = n11; te[ 1 ] = n21; te[ 2 ] = n31;
+               te[ 3 ] = n12; te[ 4 ] = n22; te[ 5 ] = n32;
+               te[ 6 ] = n13; te[ 7 ] = n23; te[ 8 ] = n33;
+
+               return this;
+
+       }
+
+       identity() {
+
+               this.set(
+
+                       1, 0, 0,
+                       0, 1, 0,
+                       0, 0, 1
+
+               );
+
+               return this;
+
+       }
+
+       clone() {
+
+               return new this.constructor().fromArray( this.elements );
+
+       }
+
+       copy( m ) {
+
+               const te = this.elements;
+               const me = m.elements;
+
+               te[ 0 ] = me[ 0 ]; te[ 1 ] = me[ 1 ]; te[ 2 ] = me[ 2 ];
+               te[ 3 ] = me[ 3 ]; te[ 4 ] = me[ 4 ]; te[ 5 ] = me[ 5 ];
+               te[ 6 ] = me[ 6 ]; te[ 7 ] = me[ 7 ]; te[ 8 ] = me[ 8 ];
+
+               return this;
+
+       }
+
+       extractBasis( xAxis, yAxis, zAxis ) {
+
+               xAxis.setFromMatrix3Column( this, 0 );
+               yAxis.setFromMatrix3Column( this, 1 );
+               zAxis.setFromMatrix3Column( this, 2 );
+
+               return this;
+
+       }
+
+       setFromMatrix4( m ) {
+
+               const me = m.elements;
+
+               this.set(
+
+                       me[ 0 ], me[ 4 ], me[ 8 ],
+                       me[ 1 ], me[ 5 ], me[ 9 ],
+                       me[ 2 ], me[ 6 ], me[ 10 ]
+
+               );
+
+               return this;
+
+       }
+
+       multiply( m ) {
+
+               return this.multiplyMatrices( this, m );
+
+       }
+
+       premultiply( m ) {
+
+               return this.multiplyMatrices( m, this );
+
+       }
+
+       multiplyMatrices( a, b ) {
+
+               const ae = a.elements;
+               const be = b.elements;
+               const te = this.elements;
+
+               const a11 = ae[ 0 ], a12 = ae[ 3 ], a13 = ae[ 6 ];
+               const a21 = ae[ 1 ], a22 = ae[ 4 ], a23 = ae[ 7 ];
+               const a31 = ae[ 2 ], a32 = ae[ 5 ], a33 = ae[ 8 ];
+
+               const b11 = be[ 0 ], b12 = be[ 3 ], b13 = be[ 6 ];
+               const b21 = be[ 1 ], b22 = be[ 4 ], b23 = be[ 7 ];
+               const b31 = be[ 2 ], b32 = be[ 5 ], b33 = be[ 8 ];
+
+               te[ 0 ] = a11 * b11 + a12 * b21 + a13 * b31;
+               te[ 3 ] = a11 * b12 + a12 * b22 + a13 * b32;
+               te[ 6 ] = a11 * b13 + a12 * b23 + a13 * b33;
+
+               te[ 1 ] = a21 * b11 + a22 * b21 + a23 * b31;
+               te[ 4 ] = a21 * b12 + a22 * b22 + a23 * b32;
+               te[ 7 ] = a21 * b13 + a22 * b23 + a23 * b33;
+
+               te[ 2 ] = a31 * b11 + a32 * b21 + a33 * b31;
+               te[ 5 ] = a31 * b12 + a32 * b22 + a33 * b32;
+               te[ 8 ] = a31 * b13 + a32 * b23 + a33 * b33;
+
+               return this;
+
+       }
+
+       multiplyScalar( s ) {
+
+               const te = this.elements;
+
+               te[ 0 ] *= s; te[ 3 ] *= s; te[ 6 ] *= s;
+               te[ 1 ] *= s; te[ 4 ] *= s; te[ 7 ] *= s;
+               te[ 2 ] *= s; te[ 5 ] *= s; te[ 8 ] *= s;
+
+               return this;
+
+       }
+
+       determinant() {
+
+               const te = this.elements;
+
+               const a = te[ 0 ], b = te[ 1 ], c = te[ 2 ],
+                       d = te[ 3 ], e = te[ 4 ], f = te[ 5 ],
+                       g = te[ 6 ], h = te[ 7 ], i = te[ 8 ];
+
+               return a * e * i - a * f * h - b * d * i + b * f * g + c * d * h - c * e * g;
+
+       }
+
+       invert() {
+
+               const te = this.elements,
+
+                       n11 = te[ 0 ], n21 = te[ 1 ], n31 = te[ 2 ],
+                       n12 = te[ 3 ], n22 = te[ 4 ], n32 = te[ 5 ],
+                       n13 = te[ 6 ], n23 = te[ 7 ], n33 = te[ 8 ],
+
+                       t11 = n33 * n22 - n32 * n23,
+                       t12 = n32 * n13 - n33 * n12,
+                       t13 = n23 * n12 - n22 * n13,
+
+                       det = n11 * t11 + n21 * t12 + n31 * t13;
+
+               if ( det === 0 ) return this.set( 0, 0, 0, 0, 0, 0, 0, 0, 0 );
+
+               const detInv = 1 / det;
+
+               te[ 0 ] = t11 * detInv;
+               te[ 1 ] = ( n31 * n23 - n33 * n21 ) * detInv;
+               te[ 2 ] = ( n32 * n21 - n31 * n22 ) * detInv;
+
+               te[ 3 ] = t12 * detInv;
+               te[ 4 ] = ( n33 * n11 - n31 * n13 ) * detInv;
+               te[ 5 ] = ( n31 * n12 - n32 * n11 ) * detInv;
+
+               te[ 6 ] = t13 * detInv;
+               te[ 7 ] = ( n21 * n13 - n23 * n11 ) * detInv;
+               te[ 8 ] = ( n22 * n11 - n21 * n12 ) * detInv;
+
+               return this;
+
+       }
+
+       transpose() {
+
+               let tmp;
+               const m = this.elements;
+
+               tmp = m[ 1 ]; m[ 1 ] = m[ 3 ]; m[ 3 ] = tmp;
+               tmp = m[ 2 ]; m[ 2 ] = m[ 6 ]; m[ 6 ] = tmp;
+               tmp = m[ 5 ]; m[ 5 ] = m[ 7 ]; m[ 7 ] = tmp;
+
+               return this;
+
+       }
+
+       getNormalMatrix( matrix4 ) {
+
+               return this.setFromMatrix4( matrix4 ).copy( this ).invert().transpose();
+
+       }
+
+       transposeIntoArray( r ) {
+
+               const m = this.elements;
+
+               r[ 0 ] = m[ 0 ];
+               r[ 1 ] = m[ 3 ];
+               r[ 2 ] = m[ 6 ];
+               r[ 3 ] = m[ 1 ];
+               r[ 4 ] = m[ 4 ];
+               r[ 5 ] = m[ 7 ];
+               r[ 6 ] = m[ 2 ];
+               r[ 7 ] = m[ 5 ];
+               r[ 8 ] = m[ 8 ];
+
+               return this;
+
+       }
+
+       setUvTransform( tx, ty, sx, sy, rotation, cx, cy ) {
+
+               const c = Math.cos( rotation );
+               const s = Math.sin( rotation );
+
+               this.set(
+                       sx * c, sx * s, - sx * ( c * cx + s * cy ) + cx + tx,
+                       - sy * s, sy * c, - sy * ( - s * cx + c * cy ) + cy + ty,
+                       0, 0, 1
+               );
+
+               return this;
+
+       }
+
+       scale( sx, sy ) {
+
+               const te = this.elements;
+
+               te[ 0 ] *= sx; te[ 3 ] *= sx; te[ 6 ] *= sx;
+               te[ 1 ] *= sy; te[ 4 ] *= sy; te[ 7 ] *= sy;
+
+               return this;
+
+       }
+
+       rotate( theta ) {
+
+               const c = Math.cos( theta );
+               const s = Math.sin( theta );
+
+               const te = this.elements;
+
+               const a11 = te[ 0 ], a12 = te[ 3 ], a13 = te[ 6 ];
+               const a21 = te[ 1 ], a22 = te[ 4 ], a23 = te[ 7 ];
+
+               te[ 0 ] = c * a11 + s * a21;
+               te[ 3 ] = c * a12 + s * a22;
+               te[ 6 ] = c * a13 + s * a23;
+
+               te[ 1 ] = - s * a11 + c * a21;
+               te[ 4 ] = - s * a12 + c * a22;
+               te[ 7 ] = - s * a13 + c * a23;
+
+               return this;
+
+       }
+
+       translate( tx, ty ) {
+
+               const te = this.elements;
+
+               te[ 0 ] += tx * te[ 2 ]; te[ 3 ] += tx * te[ 5 ]; te[ 6 ] += tx * te[ 8 ];
+               te[ 1 ] += ty * te[ 2 ]; te[ 4 ] += ty * te[ 5 ]; te[ 7 ] += ty * te[ 8 ];
+
+               return this;
+
+       }
+
+       equals( matrix ) {
+
+               const te = this.elements;
+               const me = matrix.elements;
+
+               for ( let i = 0; i < 9; i ++ ) {
+
+                       if ( te[ i ] !== me[ i ] ) return false;
+
+               }
+
+               return true;
+
+       }
+
+       fromArray( array, offset = 0 ) {
+
+               for ( let i = 0; i < 9; i ++ ) {
+
+                       this.elements[ i ] = array[ i + offset ];
+
+               }
+
+               return this;
+
+       }
+
+       toArray( array = [], offset = 0 ) {
+
+               const te = this.elements;
+
+               array[ offset ] = te[ 0 ];
+               array[ offset + 1 ] = te[ 1 ];
+               array[ offset + 2 ] = te[ 2 ];
+
+               array[ offset + 3 ] = te[ 3 ];
+               array[ offset + 4 ] = te[ 4 ];
+               array[ offset + 5 ] = te[ 5 ];
+
+               array[ offset + 6 ] = te[ 6 ];
+               array[ offset + 7 ] = te[ 7 ];
+               array[ offset + 8 ] = te[ 8 ];
+
+               return array;
+
+       }
+
+}
+
+let _canvas;
+
+const ImageUtils = {
+
+       getDataURL: function ( image ) {
+
+               if ( /^data:/i.test( image.src ) ) {
+
+                       return image.src;
+
+               }
+
+               if ( typeof HTMLCanvasElement == 'undefined' ) {
+
+                       return image.src;
+
+               }
+
+               let canvas;
+
+               if ( image instanceof HTMLCanvasElement ) {
+
+                       canvas = image;
+
+               } else {
+
+                       if ( _canvas === undefined ) _canvas = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' );
+
+                       _canvas.width = image.width;
+                       _canvas.height = image.height;
+
+                       const context = _canvas.getContext( '2d' );
+
+                       if ( image instanceof ImageData ) {
+
+                               context.putImageData( image, 0, 0 );
+
+                       } else {
+
+                               context.drawImage( image, 0, 0, image.width, image.height );
+
+                       }
+
+                       canvas = _canvas;
+
+               }
+
+               if ( canvas.width > 2048 || canvas.height > 2048 ) {
+
+                       return canvas.toDataURL( 'image/jpeg', 0.6 );
+
+               } else {
+
+                       return canvas.toDataURL( 'image/png' );
+
+               }
+
+       }
+
+};
+
+let textureId = 0;
+
+function Texture( image = Texture.DEFAULT_IMAGE, mapping = Texture.DEFAULT_MAPPING, wrapS = ClampToEdgeWrapping, wrapT = ClampToEdgeWrapping, magFilter = LinearFilter, minFilter = LinearMipmapLinearFilter, format = RGBAFormat, type = UnsignedByteType, anisotropy = 1, encoding = LinearEncoding ) {
+
+       Object.defineProperty( this, 'id', { value: textureId ++ } );
+
+       this.uuid = MathUtils.generateUUID();
+
+       this.name = '';
+
+       this.image = image;
+       this.mipmaps = [];
+
+       this.mapping = mapping;
+
+       this.wrapS = wrapS;
+       this.wrapT = wrapT;
+
+       this.magFilter = magFilter;
+       this.minFilter = minFilter;
+
+       this.anisotropy = anisotropy;
+
+       this.format = format;
+       this.internalFormat = null;
+       this.type = type;
+
+       this.offset = new Vector2( 0, 0 );
+       this.repeat = new Vector2( 1, 1 );
+       this.center = new Vector2( 0, 0 );
+       this.rotation = 0;
+
+       this.matrixAutoUpdate = true;
+       this.matrix = new Matrix3();
+
+       this.generateMipmaps = true;
+       this.premultiplyAlpha = false;
+       this.flipY = true;
+       this.unpackAlignment = 4;       // valid values: 1, 2, 4, 8 (see http://www.khronos.org/opengles/sdk/docs/man/xhtml/glPixelStorei.xml)
+
+       // Values of encoding !== THREE.LinearEncoding only supported on map, envMap and emissiveMap.
+       //
+       // Also changing the encoding after already used by a Material will not automatically make the Material
+       // update. You need to explicitly call Material.needsUpdate to trigger it to recompile.
+       this.encoding = encoding;
+
+       this.version = 0;
+       this.onUpdate = null;
+
+}
+
+Texture.DEFAULT_IMAGE = undefined;
+Texture.DEFAULT_MAPPING = UVMapping;
+
+Texture.prototype = Object.assign( Object.create( EventDispatcher.prototype ), {
+
+       constructor: Texture,
+
+       isTexture: true,
+
+       updateMatrix: function () {
+
+               this.matrix.setUvTransform( this.offset.x, this.offset.y, this.repeat.x, this.repeat.y, this.rotation, this.center.x, this.center.y );
+
+       },
+
+       clone: function () {
+
+               return new this.constructor().copy( this );
+
+       },
+
+       copy: function ( source ) {
+
+               this.name = source.name;
+
+               this.image = source.image;
+               this.mipmaps = source.mipmaps.slice( 0 );
+
+               this.mapping = source.mapping;
+
+               this.wrapS = source.wrapS;
+               this.wrapT = source.wrapT;
+
+               this.magFilter = source.magFilter;
+               this.minFilter = source.minFilter;
+
+               this.anisotropy = source.anisotropy;
+
+               this.format = source.format;
+               this.internalFormat = source.internalFormat;
+               this.type = source.type;
+
+               this.offset.copy( source.offset );
+               this.repeat.copy( source.repeat );
+               this.center.copy( source.center );
+               this.rotation = source.rotation;
+
+               this.matrixAutoUpdate = source.matrixAutoUpdate;
+               this.matrix.copy( source.matrix );
+
+               this.generateMipmaps = source.generateMipmaps;
+               this.premultiplyAlpha = source.premultiplyAlpha;
+               this.flipY = source.flipY;
+               this.unpackAlignment = source.unpackAlignment;
+               this.encoding = source.encoding;
+
+               return this;
+
+       },
+
+       toJSON: function ( meta ) {
+
+               const isRootObject = ( meta === undefined || typeof meta === 'string' );
+
+               if ( ! isRootObject && meta.textures[ this.uuid ] !== undefined ) {
+
+                       return meta.textures[ this.uuid ];
+
+               }
+
+               const output = {
+
+                       metadata: {
+                               version: 4.5,
+                               type: 'Texture',
+                               generator: 'Texture.toJSON'
+                       },
+
+                       uuid: this.uuid,
+                       name: this.name,
+
+                       mapping: this.mapping,
+
+                       repeat: [ this.repeat.x, this.repeat.y ],
+                       offset: [ this.offset.x, this.offset.y ],
+                       center: [ this.center.x, this.center.y ],
+                       rotation: this.rotation,
+
+                       wrap: [ this.wrapS, this.wrapT ],
+
+                       format: this.format,
+                       type: this.type,
+                       encoding: this.encoding,
+
+                       minFilter: this.minFilter,
+                       magFilter: this.magFilter,
+                       anisotropy: this.anisotropy,
+
+                       flipY: this.flipY,
+
+                       premultiplyAlpha: this.premultiplyAlpha,
+                       unpackAlignment: this.unpackAlignment
+
+               };
+
+               if ( this.image !== undefined ) {
+
+                       // TODO: Move to THREE.Image
+
+                       const image = this.image;
+
+                       if ( image.uuid === undefined ) {
+
+                               image.uuid = MathUtils.generateUUID(); // UGH
+
+                       }
+
+                       if ( ! isRootObject && meta.images[ image.uuid ] === undefined ) {
+
+                               let url;
+
+                               if ( Array.isArray( image ) ) {
+
+                                       // process array of images e.g. CubeTexture
+
+                                       url = [];
+
+                                       for ( let i = 0, l = image.length; i < l; i ++ ) {
+
+                                               // check cube texture with data textures
+
+                                               if ( image[ i ].isDataTexture ) {
+
+                                                       url.push( serializeImage( image[ i ].image ) );
+
+                                               } else {
+
+                                                       url.push( serializeImage( image[ i ] ) );
+
+                                               }
+
+                                       }
+
+                               } else {
+
+                                       // process single image
+
+                                       url = serializeImage( image );
+
+                               }
+
+                               meta.images[ image.uuid ] = {
+                                       uuid: image.uuid,
+                                       url: url
+                               };
+
+                       }
+
+                       output.image = image.uuid;
+
+               }
+
+               if ( ! isRootObject ) {
+
+                       meta.textures[ this.uuid ] = output;
+
+               }
+
+               return output;
+
+       },
+
+       dispose: function () {
+
+               this.dispatchEvent( { type: 'dispose' } );
+
+       },
+
+       transformUv: function ( uv ) {
+
+               if ( this.mapping !== UVMapping ) return uv;
+
+               uv.applyMatrix3( this.matrix );
+
+               if ( uv.x < 0 || uv.x > 1 ) {
+
+                       switch ( this.wrapS ) {
+
+                               case RepeatWrapping:
+
+                                       uv.x = uv.x - Math.floor( uv.x );
+                                       break;
+
+                               case ClampToEdgeWrapping:
+
+                                       uv.x = uv.x < 0 ? 0 : 1;
+                                       break;
+
+                               case MirroredRepeatWrapping:
+
+                                       if ( Math.abs( Math.floor( uv.x ) % 2 ) === 1 ) {
+
+                                               uv.x = Math.ceil( uv.x ) - uv.x;
+
+                                       } else {
+
+                                               uv.x = uv.x - Math.floor( uv.x );
+
+                                       }
+
+                                       break;
+
+                       }
+
+               }
+
+               if ( uv.y < 0 || uv.y > 1 ) {
+
+                       switch ( this.wrapT ) {
+
+                               case RepeatWrapping:
+
+                                       uv.y = uv.y - Math.floor( uv.y );
+                                       break;
+
+                               case ClampToEdgeWrapping:
+
+                                       uv.y = uv.y < 0 ? 0 : 1;
+                                       break;
+
+                               case MirroredRepeatWrapping:
+
+                                       if ( Math.abs( Math.floor( uv.y ) % 2 ) === 1 ) {
+
+                                               uv.y = Math.ceil( uv.y ) - uv.y;
+
+                                       } else {
+
+                                               uv.y = uv.y - Math.floor( uv.y );
+
+                                       }
+
+                                       break;
+
+                       }
+
+               }
+
+               if ( this.flipY ) {
+
+                       uv.y = 1 - uv.y;
+
+               }
+
+               return uv;
+
+       }
+
+} );
+
+Object.defineProperty( Texture.prototype, 'needsUpdate', {
+
+       set: function ( value ) {
+
+               if ( value === true ) this.version ++;
+
+       }
+
+} );
+
+function serializeImage( image ) {
+
+       if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) ||
+               ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) ||
+               ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) {
+
+               // default images
+
+               return ImageUtils.getDataURL( image );
+
+       } else {
+
+               if ( image.data ) {
+
+                       // images of DataTexture
+
+                       return {
+                               data: Array.prototype.slice.call( image.data ),
+                               width: image.width,
+                               height: image.height,
+                               type: image.data.constructor.name
+                       };
+
+               } else {
+
+                       console.warn( 'THREE.Texture: Unable to serialize Texture.' );
+                       return {};
+
+               }
+
+       }
+
+}
+
+class Vector4 {
+
+       constructor( x = 0, y = 0, z = 0, w = 1 ) {
+
+               Object.defineProperty( this, 'isVector4', { value: true } );
+
+               this.x = x;
+               this.y = y;
+               this.z = z;
+               this.w = w;
+
+       }
+
+       get width() {
+
+               return this.z;
+
+       }
+
+       set width( value ) {
+
+               this.z = value;
+
+       }
+
+       get height() {
+
+               return this.w;
+
+       }
+
+       set height( value ) {
+
+               this.w = value;
+
+       }
+
+       set( x, y, z, w ) {
+
+               this.x = x;
+               this.y = y;
+               this.z = z;
+               this.w = w;
+
+               return this;
+
+       }
+
+       setScalar( scalar ) {
+
+               this.x = scalar;
+               this.y = scalar;
+               this.z = scalar;
+               this.w = scalar;
+
+               return this;
+
+       }
+
+       setX( x ) {
+
+               this.x = x;
+
+               return this;
+
+       }
+
+       setY( y ) {
+
+               this.y = y;
+
+               return this;
+
+       }
+
+       setZ( z ) {
+
+               this.z = z;
+
+               return this;
+
+       }
+
+       setW( w ) {
+
+               this.w = w;
+
+               return this;
+
+       }
+
+       setComponent( index, value ) {
+
+               switch ( index ) {
+
+                       case 0: this.x = value; break;
+                       case 1: this.y = value; break;
+                       case 2: this.z = value; break;
+                       case 3: this.w = value; break;
+                       default: throw new Error( 'index is out of range: ' + index );
+
+               }
+
+               return this;
+
+       }
+
+       getComponent( index ) {
+
+               switch ( index ) {
+
+                       case 0: return this.x;
+                       case 1: return this.y;
+                       case 2: return this.z;
+                       case 3: return this.w;
+                       default: throw new Error( 'index is out of range: ' + index );
+
+               }
+
+       }
+
+       clone() {
+
+               return new this.constructor( this.x, this.y, this.z, this.w );
+
+       }
+
+       copy( v ) {
+
+               this.x = v.x;
+               this.y = v.y;
+               this.z = v.z;
+               this.w = ( v.w !== undefined ) ? v.w : 1;
+
+               return this;
+
+       }
+
+       add( v, w ) {
+
+               if ( w !== undefined ) {
+
+                       console.warn( 'THREE.Vector4: .add() now only accepts one argument. Use .addVectors( a, b ) instead.' );
+                       return this.addVectors( v, w );
+
+               }
+
+               this.x += v.x;
+               this.y += v.y;
+               this.z += v.z;
+               this.w += v.w;
+
+               return this;
+
+       }
+
+       addScalar( s ) {
+
+               this.x += s;
+               this.y += s;
+               this.z += s;
+               this.w += s;
+
+               return this;
+
+       }
+
+       addVectors( a, b ) {
+
+               this.x = a.x + b.x;
+               this.y = a.y + b.y;
+               this.z = a.z + b.z;
+               this.w = a.w + b.w;
+
+               return this;
+
+       }
+
+       addScaledVector( v, s ) {
+
+               this.x += v.x * s;
+               this.y += v.y * s;
+               this.z += v.z * s;
+               this.w += v.w * s;
+
+               return this;
+
+       }
+
+       sub( v, w ) {
+
+               if ( w !== undefined ) {
+
+                       console.warn( 'THREE.Vector4: .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' );
+                       return this.subVectors( v, w );
+
+               }
+
+               this.x -= v.x;
+               this.y -= v.y;
+               this.z -= v.z;
+               this.w -= v.w;
+
+               return this;
+
+       }
+
+       subScalar( s ) {
+
+               this.x -= s;
+               this.y -= s;
+               this.z -= s;
+               this.w -= s;
+
+               return this;
+
+       }
+
+       subVectors( a, b ) {
+
+               this.x = a.x - b.x;
+               this.y = a.y - b.y;
+               this.z = a.z - b.z;
+               this.w = a.w - b.w;
+
+               return this;
+
+       }
+
+       multiply( v ) {
+
+               this.x *= v.x;
+               this.y *= v.y;
+               this.z *= v.z;
+               this.w *= v.w;
+
+               return this;
+
+       }
+
+       multiplyScalar( scalar ) {
+
+               this.x *= scalar;
+               this.y *= scalar;
+               this.z *= scalar;
+               this.w *= scalar;
+
+               return this;
+
+       }
+
+       applyMatrix4( m ) {
+
+               const x = this.x, y = this.y, z = this.z, w = this.w;
+               const e = m.elements;
+
+               this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] * w;
+               this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] * w;
+               this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] * w;
+               this.w = e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] * w;
+
+               return this;
+
+       }
+
+       divideScalar( scalar ) {
+
+               return this.multiplyScalar( 1 / scalar );
+
+       }
+
+       setAxisAngleFromQuaternion( q ) {
+
+               // http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToAngle/index.htm
+
+               // q is assumed to be normalized
+
+               this.w = 2 * Math.acos( q.w );
+
+               const s = Math.sqrt( 1 - q.w * q.w );
+
+               if ( s < 0.0001 ) {
+
+                       this.x = 1;
+                       this.y = 0;
+                       this.z = 0;
+
+               } else {
+
+                       this.x = q.x / s;
+                       this.y = q.y / s;
+                       this.z = q.z / s;
+
+               }
+
+               return this;
+
+       }
+
+       setAxisAngleFromRotationMatrix( m ) {
+
+               // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToAngle/index.htm
+
+               // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
+
+               let angle, x, y, z; // variables for result
+               const epsilon = 0.01,           // margin to allow for rounding errors
+                       epsilon2 = 0.1,         // margin to distinguish between 0 and 180 degrees
+
+                       te = m.elements,
+
+                       m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ],
+                       m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ],
+                       m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ];
+
+               if ( ( Math.abs( m12 - m21 ) < epsilon ) &&
+                    ( Math.abs( m13 - m31 ) < epsilon ) &&
+                    ( Math.abs( m23 - m32 ) < epsilon ) ) {
+
+                       // singularity found
+                       // first check for identity matrix which must have +1 for all terms
+                       // in leading diagonal and zero in other terms
+
+                       if ( ( Math.abs( m12 + m21 ) < epsilon2 ) &&
+                            ( Math.abs( m13 + m31 ) < epsilon2 ) &&
+                            ( Math.abs( m23 + m32 ) < epsilon2 ) &&
+                            ( Math.abs( m11 + m22 + m33 - 3 ) < epsilon2 ) ) {
+
+                               // this singularity is identity matrix so angle = 0
+
+                               this.set( 1, 0, 0, 0 );
+
+                               return this; // zero angle, arbitrary axis
+
+                       }
+
+                       // otherwise this singularity is angle = 180
+
+                       angle = Math.PI;
+
+                       const xx = ( m11 + 1 ) / 2;
+                       const yy = ( m22 + 1 ) / 2;
+                       const zz = ( m33 + 1 ) / 2;
+                       const xy = ( m12 + m21 ) / 4;
+                       const xz = ( m13 + m31 ) / 4;
+                       const yz = ( m23 + m32 ) / 4;
+
+                       if ( ( xx > yy ) && ( xx > zz ) ) {
+
+                               // m11 is the largest diagonal term
+
+                               if ( xx < epsilon ) {
+
+                                       x = 0;
+                                       y = 0.707106781;
+                                       z = 0.707106781;
+
+                               } else {
+
+                                       x = Math.sqrt( xx );
+                                       y = xy / x;
+                                       z = xz / x;
+
+                               }
+
+                       } else if ( yy > zz ) {
+
+                               // m22 is the largest diagonal term
+
+                               if ( yy < epsilon ) {
+
+                                       x = 0.707106781;
+                                       y = 0;
+                                       z = 0.707106781;
+
+                               } else {
+
+                                       y = Math.sqrt( yy );
+                                       x = xy / y;
+                                       z = yz / y;
+
+                               }
+
+                       } else {
+
+                               // m33 is the largest diagonal term so base result on this
+
+                               if ( zz < epsilon ) {
+
+                                       x = 0.707106781;
+                                       y = 0.707106781;
+                                       z = 0;
+
+                               } else {
+
+                                       z = Math.sqrt( zz );
+                                       x = xz / z;
+                                       y = yz / z;
+
+                               }
+
+                       }
+
+                       this.set( x, y, z, angle );
+
+                       return this; // return 180 deg rotation
+
+               }
+
+               // as we have reached here there are no singularities so we can handle normally
+
+               let s = Math.sqrt( ( m32 - m23 ) * ( m32 - m23 ) +
+                       ( m13 - m31 ) * ( m13 - m31 ) +
+                       ( m21 - m12 ) * ( m21 - m12 ) ); // used to normalize
+
+               if ( Math.abs( s ) < 0.001 ) s = 1;
+
+               // prevent divide by zero, should not happen if matrix is orthogonal and should be
+               // caught by singularity test above, but I've left it in just in case
+
+               this.x = ( m32 - m23 ) / s;
+               this.y = ( m13 - m31 ) / s;
+               this.z = ( m21 - m12 ) / s;
+               this.w = Math.acos( ( m11 + m22 + m33 - 1 ) / 2 );
+
+               return this;
+
+       }
+
+       min( v ) {
+
+               this.x = Math.min( this.x, v.x );
+               this.y = Math.min( this.y, v.y );
+               this.z = Math.min( this.z, v.z );
+               this.w = Math.min( this.w, v.w );
+
+               return this;
+
+       }
+
+       max( v ) {
+
+               this.x = Math.max( this.x, v.x );
+               this.y = Math.max( this.y, v.y );
+               this.z = Math.max( this.z, v.z );
+               this.w = Math.max( this.w, v.w );
+
+               return this;
+
+       }
+
+       clamp( min, max ) {
+
+               // assumes min < max, componentwise
+
+               this.x = Math.max( min.x, Math.min( max.x, this.x ) );
+               this.y = Math.max( min.y, Math.min( max.y, this.y ) );
+               this.z = Math.max( min.z, Math.min( max.z, this.z ) );
+               this.w = Math.max( min.w, Math.min( max.w, this.w ) );
+
+               return this;
+
+       }
+
+       clampScalar( minVal, maxVal ) {
+
+               this.x = Math.max( minVal, Math.min( maxVal, this.x ) );
+               this.y = Math.max( minVal, Math.min( maxVal, this.y ) );
+               this.z = Math.max( minVal, Math.min( maxVal, this.z ) );
+               this.w = Math.max( minVal, Math.min( maxVal, this.w ) );
+
+               return this;
+
+       }
+
+       clampLength( min, max ) {
+
+               const length = this.length();
+
+               return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) );
+
+       }
+
+       floor() {
+
+               this.x = Math.floor( this.x );
+               this.y = Math.floor( this.y );
+               this.z = Math.floor( this.z );
+               this.w = Math.floor( this.w );
+
+               return this;
+
+       }
+
+       ceil() {
+
+               this.x = Math.ceil( this.x );
+               this.y = Math.ceil( this.y );
+               this.z = Math.ceil( this.z );
+               this.w = Math.ceil( this.w );
+
+               return this;
+
+       }
+
+       round() {
+
+               this.x = Math.round( this.x );
+               this.y = Math.round( this.y );
+               this.z = Math.round( this.z );
+               this.w = Math.round( this.w );
+
+               return this;
+
+       }
+
+       roundToZero() {
+
+               this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x );
+               this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y );
+               this.z = ( this.z < 0 ) ? Math.ceil( this.z ) : Math.floor( this.z );
+               this.w = ( this.w < 0 ) ? Math.ceil( this.w ) : Math.floor( this.w );
+
+               return this;
+
+       }
+
+       negate() {
+
+               this.x = - this.x;
+               this.y = - this.y;
+               this.z = - this.z;
+               this.w = - this.w;
+
+               return this;
+
+       }
+
+       dot( v ) {
+
+               return this.x * v.x + this.y * v.y + this.z * v.z + this.w * v.w;
+
+       }
+
+       lengthSq() {
+
+               return this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w;
+
+       }
+
+       length() {
+
+               return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w );
+
+       }
+
+       manhattanLength() {
+
+               return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ) + Math.abs( this.w );
+
+       }
+
+       normalize() {
+
+               return this.divideScalar( this.length() || 1 );
+
+       }
+
+       setLength( length ) {
+
+               return this.normalize().multiplyScalar( length );
+
+       }
+
+       lerp( v, alpha ) {
+
+               this.x += ( v.x - this.x ) * alpha;
+               this.y += ( v.y - this.y ) * alpha;
+               this.z += ( v.z - this.z ) * alpha;
+               this.w += ( v.w - this.w ) * alpha;
+
+               return this;
+
+       }
+
+       lerpVectors( v1, v2, alpha ) {
+
+               this.x = v1.x + ( v2.x - v1.x ) * alpha;
+               this.y = v1.y + ( v2.y - v1.y ) * alpha;
+               this.z = v1.z + ( v2.z - v1.z ) * alpha;
+               this.w = v1.w + ( v2.w - v1.w ) * alpha;
+
+               return this;
+
+       }
+
+       equals( v ) {
+
+               return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) && ( v.w === this.w ) );
+
+       }
+
+       fromArray( array, offset = 0 ) {
+
+               this.x = array[ offset ];
+               this.y = array[ offset + 1 ];
+               this.z = array[ offset + 2 ];
+               this.w = array[ offset + 3 ];
+
+               return this;
+
+       }
+
+       toArray( array = [], offset = 0 ) {
+
+               array[ offset ] = this.x;
+               array[ offset + 1 ] = this.y;
+               array[ offset + 2 ] = this.z;
+               array[ offset + 3 ] = this.w;
+
+               return array;
+
+       }
+
+       fromBufferAttribute( attribute, index, offset ) {
+
+               if ( offset !== undefined ) {
+
+                       console.warn( 'THREE.Vector4: offset has been removed from .fromBufferAttribute().' );
+
+               }
+
+               this.x = attribute.getX( index );
+               this.y = attribute.getY( index );
+               this.z = attribute.getZ( index );
+               this.w = attribute.getW( index );
+
+               return this;
+
+       }
+
+       random() {
+
+               this.x = Math.random();
+               this.y = Math.random();
+               this.z = Math.random();
+               this.w = Math.random();
+
+               return this;
+
+       }
+
+}
+
+/*
+ In options, we can specify:
+ * Texture parameters for an auto-generated target texture
+ * depthBuffer/stencilBuffer: Booleans to indicate if we should generate these buffers
+*/
+class WebGLRenderTarget extends EventDispatcher {
+
+       constructor( width, height, options ) {
+
+               super();
+
+               Object.defineProperty( this, 'isWebGLRenderTarget', { value: true } );
+
+               this.width = width;
+               this.height = height;
+
+               this.scissor = new Vector4( 0, 0, width, height );
+               this.scissorTest = false;
+
+               this.viewport = new Vector4( 0, 0, width, height );
+
+               options = options || {};
+
+               this.texture = new Texture( undefined, options.mapping, options.wrapS, options.wrapT, options.magFilter, options.minFilter, options.format, options.type, options.anisotropy, options.encoding );
+
+               this.texture.image = {};
+               this.texture.image.width = width;
+               this.texture.image.height = height;
+
+               this.texture.generateMipmaps = options.generateMipmaps !== undefined ? options.generateMipmaps : false;
+               this.texture.minFilter = options.minFilter !== undefined ? options.minFilter : LinearFilter;
+
+               this.depthBuffer = options.depthBuffer !== undefined ? options.depthBuffer : true;
+               this.stencilBuffer = options.stencilBuffer !== undefined ? options.stencilBuffer : false;
+               this.depthTexture = options.depthTexture !== undefined ? options.depthTexture : null;
+
+       }
+
+       setSize( width, height ) {
+
+               if ( this.width !== width || this.height !== height ) {
+
+                       this.width = width;
+                       this.height = height;
+
+                       this.texture.image.width = width;
+                       this.texture.image.height = height;
+
+                       this.dispose();
+
+               }
+
+               this.viewport.set( 0, 0, width, height );
+               this.scissor.set( 0, 0, width, height );
+
+       }
+
+       clone() {
+
+               return new this.constructor().copy( this );
+
+       }
+
+       copy( source ) {
+
+               this.width = source.width;
+               this.height = source.height;
+
+               this.viewport.copy( source.viewport );
+
+               this.texture = source.texture.clone();
+
+               this.depthBuffer = source.depthBuffer;
+               this.stencilBuffer = source.stencilBuffer;
+               this.depthTexture = source.depthTexture;
+
+               return this;
+
+       }
+
+       dispose() {
+
+               this.dispatchEvent( { type: 'dispose' } );
+
+       }
+
+}
+
+class Quaternion {
+
+       constructor( x = 0, y = 0, z = 0, w = 1 ) {
+
+               Object.defineProperty( this, 'isQuaternion', { value: true } );
+
+               this._x = x;
+               this._y = y;
+               this._z = z;
+               this._w = w;
+
+       }
+
+       static slerp( qa, qb, qm, t ) {
+
+               return qm.copy( qa ).slerp( qb, t );
+
+       }
+
+       static slerpFlat( dst, dstOffset, src0, srcOffset0, src1, srcOffset1, t ) {
+
+               // fuzz-free, array-based Quaternion SLERP operation
+
+               let x0 = src0[ srcOffset0 + 0 ],
+                       y0 = src0[ srcOffset0 + 1 ],
+                       z0 = src0[ srcOffset0 + 2 ],
+                       w0 = src0[ srcOffset0 + 3 ];
+
+               const x1 = src1[ srcOffset1 + 0 ],
+                       y1 = src1[ srcOffset1 + 1 ],
+                       z1 = src1[ srcOffset1 + 2 ],
+                       w1 = src1[ srcOffset1 + 3 ];
+
+               if ( w0 !== w1 || x0 !== x1 || y0 !== y1 || z0 !== z1 ) {
+
+                       let s = 1 - t;
+                       const cos = x0 * x1 + y0 * y1 + z0 * z1 + w0 * w1,
+                               dir = ( cos >= 0 ? 1 : - 1 ),
+                               sqrSin = 1 - cos * cos;
+
+                       // Skip the Slerp for tiny steps to avoid numeric problems:
+                       if ( sqrSin > Number.EPSILON ) {
+
+                               const sin = Math.sqrt( sqrSin ),
+                                       len = Math.atan2( sin, cos * dir );
+
+                               s = Math.sin( s * len ) / sin;
+                               t = Math.sin( t * len ) / sin;
+
+                       }
+
+                       const tDir = t * dir;
+
+                       x0 = x0 * s + x1 * tDir;
+                       y0 = y0 * s + y1 * tDir;
+                       z0 = z0 * s + z1 * tDir;
+                       w0 = w0 * s + w1 * tDir;
+
+                       // Normalize in case we just did a lerp:
+                       if ( s === 1 - t ) {
+
+                               const f = 1 / Math.sqrt( x0 * x0 + y0 * y0 + z0 * z0 + w0 * w0 );
+
+                               x0 *= f;
+                               y0 *= f;
+                               z0 *= f;
+                               w0 *= f;
+
+                       }
+
+               }
+
+               dst[ dstOffset ] = x0;
+               dst[ dstOffset + 1 ] = y0;
+               dst[ dstOffset + 2 ] = z0;
+               dst[ dstOffset + 3 ] = w0;
+
+       }
+
+       static multiplyQuaternionsFlat( dst, dstOffset, src0, srcOffset0, src1, srcOffset1 ) {
+
+               const x0 = src0[ srcOffset0 ];
+               const y0 = src0[ srcOffset0 + 1 ];
+               const z0 = src0[ srcOffset0 + 2 ];
+               const w0 = src0[ srcOffset0 + 3 ];
+
+               const x1 = src1[ srcOffset1 ];
+               const y1 = src1[ srcOffset1 + 1 ];
+               const z1 = src1[ srcOffset1 + 2 ];
+               const w1 = src1[ srcOffset1 + 3 ];
+
+               dst[ dstOffset ] = x0 * w1 + w0 * x1 + y0 * z1 - z0 * y1;
+               dst[ dstOffset + 1 ] = y0 * w1 + w0 * y1 + z0 * x1 - x0 * z1;
+               dst[ dstOffset + 2 ] = z0 * w1 + w0 * z1 + x0 * y1 - y0 * x1;
+               dst[ dstOffset + 3 ] = w0 * w1 - x0 * x1 - y0 * y1 - z0 * z1;
+
+               return dst;
+
+       }
+
+       get x() {
+
+               return this._x;
+
+       }
+
+       set x( value ) {
+
+               this._x = value;
+               this._onChangeCallback();
+
+       }
+
+       get y() {
+
+               return this._y;
+
+       }
+
+       set y( value ) {
+
+               this._y = value;
+               this._onChangeCallback();
+
+       }
+
+       get z() {
+
+               return this._z;
+
+       }
+
+       set z( value ) {
+
+               this._z = value;
+               this._onChangeCallback();
+
+       }
+
+       get w() {
+
+               return this._w;
+
+       }
+
+       set w( value ) {
+
+               this._w = value;
+               this._onChangeCallback();
+
+       }
+
+       set( x, y, z, w ) {
+
+               this._x = x;
+               this._y = y;
+               this._z = z;
+               this._w = w;
+
+               this._onChangeCallback();
+
+               return this;
+
+       }
+
+       clone() {
+
+               return new this.constructor( this._x, this._y, this._z, this._w );
+
+       }
+
+       copy( quaternion ) {
+
+               this._x = quaternion.x;
+               this._y = quaternion.y;
+               this._z = quaternion.z;
+               this._w = quaternion.w;
+
+               this._onChangeCallback();
+
+               return this;
+
+       }
+
+       setFromEuler( euler, update ) {
+
+               if ( ! ( euler && euler.isEuler ) ) {
+
+                       throw new Error( 'THREE.Quaternion: .setFromEuler() now expects an Euler rotation rather than a Vector3 and order.' );
+
+               }
+
+               const x = euler._x, y = euler._y, z = euler._z, order = euler._order;
+
+               // http://www.mathworks.com/matlabcentral/fileexchange/
+               //      20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/
+               //      content/SpinCalc.m
+
+               const cos = Math.cos;
+               const sin = Math.sin;
+
+               const c1 = cos( x / 2 );
+               const c2 = cos( y / 2 );
+               const c3 = cos( z / 2 );
+
+               const s1 = sin( x / 2 );
+               const s2 = sin( y / 2 );
+               const s3 = sin( z / 2 );
+
+               switch ( order ) {
+
+                       case 'XYZ':
+                               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;
+                               break;
+
+                       case 'YXZ':
+                               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;
+                               break;
+
+                       case 'ZXY':
+                               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;
+                               break;
+
+                       case 'ZYX':
+                               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;
+                               break;
+
+                       case 'YZX':
+                               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;
+                               break;
+
+                       case 'XZY':
+                               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;
+                               break;
+
+                       default:
+                               console.warn( 'THREE.Quaternion: .setFromEuler() encountered an unknown order: ' + order );
+
+               }
+
+               if ( update !== false ) this._onChangeCallback();
+
+               return this;
+
+       }
+
+       setFromAxisAngle( axis, angle ) {
+
+               // http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.htm
+
+               // assumes axis is normalized
+
+               const 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 );
+
+               this._onChangeCallback();
+
+               return this;
+
+       }
+
+       setFromRotationMatrix( m ) {
+
+               // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm
+
+               // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
+
+               const te = m.elements,
+
+                       m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ],
+                       m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ],
+                       m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ],
+
+                       trace = m11 + m22 + m33;
+
+               if ( trace > 0 ) {
+
+                       const s = 0.5 / Math.sqrt( trace + 1.0 );
+
+                       this._w = 0.25 / s;
+                       this._x = ( m32 - m23 ) * s;
+                       this._y = ( m13 - m31 ) * s;
+                       this._z = ( m21 - m12 ) * s;
+
+               } else if ( m11 > m22 && m11 > m33 ) {
+
+                       const s = 2.0 * Math.sqrt( 1.0 + m11 - m22 - m33 );
+
+                       this._w = ( m32 - m23 ) / s;
+                       this._x = 0.25 * s;
+                       this._y = ( m12 + m21 ) / s;
+                       this._z = ( m13 + m31 ) / s;
+
+               } else if ( m22 > m33 ) {
+
+                       const s = 2.0 * Math.sqrt( 1.0 + m22 - m11 - m33 );
+
+                       this._w = ( m13 - m31 ) / s;
+                       this._x = ( m12 + m21 ) / s;
+                       this._y = 0.25 * s;
+                       this._z = ( m23 + m32 ) / s;
+
+               } else {
+
+                       const s = 2.0 * Math.sqrt( 1.0 + m33 - m11 - m22 );
+
+                       this._w = ( m21 - m12 ) / s;
+                       this._x = ( m13 + m31 ) / s;
+                       this._y = ( m23 + m32 ) / s;
+                       this._z = 0.25 * s;
+
+               }
+
+               this._onChangeCallback();
+
+               return this;
+
+       }
+
+       setFromUnitVectors( vFrom, vTo ) {
+
+               // assumes direction vectors vFrom and vTo are normalized
+
+               const EPS = 0.000001;
+
+               let r = vFrom.dot( vTo ) + 1;
+
+               if ( r < EPS ) {
+
+                       r = 0;
+
+                       if ( Math.abs( vFrom.x ) > Math.abs( vFrom.z ) ) {
+
+                               this._x = - vFrom.y;
+                               this._y = vFrom.x;
+                               this._z = 0;
+                               this._w = r;
+
+                       } else {
+
+                               this._x = 0;
+                               this._y = - vFrom.z;
+                               this._z = vFrom.y;
+                               this._w = r;
+
+                       }
+
+               } else {
+
+                       // crossVectors( vFrom, vTo ); // inlined to avoid cyclic dependency on Vector3
+
+                       this._x = vFrom.y * vTo.z - vFrom.z * vTo.y;
+                       this._y = vFrom.z * vTo.x - vFrom.x * vTo.z;
+                       this._z = vFrom.x * vTo.y - vFrom.y * vTo.x;
+                       this._w = r;
+
+               }
+
+               return this.normalize();
+
+       }
+
+       angleTo( q ) {
+
+               return 2 * Math.acos( Math.abs( MathUtils.clamp( this.dot( q ), - 1, 1 ) ) );
+
+       }
+
+       rotateTowards( q, step ) {
+
+               const angle = this.angleTo( q );
+
+               if ( angle === 0 ) return this;
+
+               const t = Math.min( 1, step / angle );
+
+               this.slerp( q, t );
+
+               return this;
+
+       }
+
+       identity() {
+
+               return this.set( 0, 0, 0, 1 );
+
+       }
+
+       invert() {
+
+               // quaternion is assumed to have unit length
+
+               return this.conjugate();
+
+       }
+
+       conjugate() {
+
+               this._x *= - 1;
+               this._y *= - 1;
+               this._z *= - 1;
+
+               this._onChangeCallback();
+
+               return this;
+
+       }
+
+       dot( v ) {
+
+               return this._x * v._x + this._y * v._y + this._z * v._z + this._w * v._w;
+
+       }
+
+       lengthSq() {
+
+               return this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w;
+
+       }
+
+       length() {
+
+               return Math.sqrt( this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w );
+
+       }
+
+       normalize() {
+
+               let l = this.length();
+
+               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;
+
+               }
+
+               this._onChangeCallback();
+
+               return this;
+
+       }
+
+       multiply( q, p ) {
+
+               if ( p !== undefined ) {
+
+                       console.warn( 'THREE.Quaternion: .multiply() now only accepts one argument. Use .multiplyQuaternions( a, b ) instead.' );
+                       return this.multiplyQuaternions( q, p );
+
+               }
+
+               return this.multiplyQuaternions( this, q );
+
+       }
+
+       premultiply( q ) {
+
+               return this.multiplyQuaternions( q, this );
+
+       }
+
+       multiplyQuaternions( a, b ) {
+
+               // from http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/code/index.htm
+
+               const qax = a._x, qay = a._y, qaz = a._z, qaw = a._w;
+               const 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;
+
+               this._onChangeCallback();
+
+               return this;
+
+       }
+
+       slerp( qb, t ) {
+
+               if ( t === 0 ) return this;
+               if ( t === 1 ) return this.copy( qb );
+
+               const x = this._x, y = this._y, z = this._z, w = this._w;
+
+               // http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/
+
+               let 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;
+
+               }
+
+               const sqrSinHalfTheta = 1.0 - cosHalfTheta * cosHalfTheta;
+
+               if ( sqrSinHalfTheta <= Number.EPSILON ) {
+
+                       const s = 1 - t;
+                       this._w = s * w + t * this._w;
+                       this._x = s * x + t * this._x;
+                       this._y = s * y + t * this._y;
+                       this._z = s * z + t * this._z;
+
+                       this.normalize();
+                       this._onChangeCallback();
+
+                       return this;
+
+               }
+
+               const sinHalfTheta = Math.sqrt( sqrSinHalfTheta );
+               const halfTheta = Math.atan2( sinHalfTheta, cosHalfTheta );
+               const 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 );
+
+               this._onChangeCallback();
+
+               return this;
+
+       }
+
+       equals( quaternion ) {
+
+               return ( quaternion._x === this._x ) && ( quaternion._y === this._y ) && ( quaternion._z === this._z ) && ( quaternion._w === this._w );
+
+       }
+
+       fromArray( array, offset = 0 ) {
+
+               this._x = array[ offset ];
+               this._y = array[ offset + 1 ];
+               this._z = array[ offset + 2 ];
+               this._w = array[ offset + 3 ];
+
+               this._onChangeCallback();
+
+               return this;
+
+       }
+
+       toArray( array = [], offset = 0 ) {
+
+               array[ offset ] = this._x;
+               array[ offset + 1 ] = this._y;
+               array[ offset + 2 ] = this._z;
+               array[ offset + 3 ] = this._w;
+
+               return array;
+
+       }
+
+       fromBufferAttribute( attribute, index ) {
+
+               this._x = attribute.getX( index );
+               this._y = attribute.getY( index );
+               this._z = attribute.getZ( index );
+               this._w = attribute.getW( index );
+
+               return this;
+
+       }
+
+       _onChange( callback ) {
+
+               this._onChangeCallback = callback;
+
+               return this;
+
+       }
+
+       _onChangeCallback() {}
+
+}
+
+class Vector3 {
+
+       constructor( x = 0, y = 0, z = 0 ) {
+
+               Object.defineProperty( this, 'isVector3', { value: true } );
+
+               this.x = x;
+               this.y = y;
+               this.z = z;
+
+       }
+
+       set( x, y, z ) {
+
+               if ( z === undefined ) z = this.z; // sprite.scale.set(x,y)
+
+               this.x = x;
+               this.y = y;
+               this.z = z;
+
+               return this;
+
+       }
+
+       setScalar( scalar ) {
+
+               this.x = scalar;
+               this.y = scalar;
+               this.z = scalar;
+
+               return this;
+
+       }
+
+       setX( x ) {
+
+               this.x = x;
+
+               return this;
+
+       }
+
+       setY( y ) {
+
+               this.y = y;
+
+               return this;
+
+       }
+
+       setZ( z ) {
+
+               this.z = z;
+
+               return this;
+
+       }
+
+       setComponent( index, value ) {
+
+               switch ( index ) {
+
+                       case 0: this.x = value; break;
+                       case 1: this.y = value; break;
+                       case 2: this.z = value; break;
+                       default: throw new Error( 'index is out of range: ' + index );
+
+               }
+
+               return this;
+
+       }
+
+       getComponent( index ) {
+
+               switch ( index ) {
+
+                       case 0: return this.x;
+                       case 1: return this.y;
+                       case 2: return this.z;
+                       default: throw new Error( 'index is out of range: ' + index );
+
+               }
+
+       }
+
+       clone() {
+
+               return new this.constructor( this.x, this.y, this.z );
+
+       }
+
+       copy( v ) {
+
+               this.x = v.x;
+               this.y = v.y;
+               this.z = v.z;
+
+               return this;
+
+       }
+
+       add( v, w ) {
+
+               if ( w !== undefined ) {
+
+                       console.warn( 'THREE.Vector3: .add() now only accepts one argument. Use .addVectors( a, b ) instead.' );
+                       return this.addVectors( v, w );
+
+               }
+
+               this.x += v.x;
+               this.y += v.y;
+               this.z += v.z;
+
+               return this;
+
+       }
+
+       addScalar( s ) {
+
+               this.x += s;
+               this.y += s;
+               this.z += s;
+
+               return this;
+
+       }
+
+       addVectors( a, b ) {
+
+               this.x = a.x + b.x;
+               this.y = a.y + b.y;
+               this.z = a.z + b.z;
+
+               return this;
+
+       }
+
+       addScaledVector( v, s ) {
+
+               this.x += v.x * s;
+               this.y += v.y * s;
+               this.z += v.z * s;
+
+               return this;
+
+       }
+
+       sub( v, w ) {
+
+               if ( w !== undefined ) {
+
+                       console.warn( 'THREE.Vector3: .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' );
+                       return this.subVectors( v, w );
+
+               }
+
+               this.x -= v.x;
+               this.y -= v.y;
+               this.z -= v.z;
+
+               return this;
+
+       }
+
+       subScalar( s ) {
+
+               this.x -= s;
+               this.y -= s;
+               this.z -= s;
+
+               return this;
+
+       }
+
+       subVectors( a, b ) {
+
+               this.x = a.x - b.x;
+               this.y = a.y - b.y;
+               this.z = a.z - b.z;
+
+               return this;
+
+       }
+
+       multiply( v, w ) {
+
+               if ( w !== undefined ) {
+
+                       console.warn( 'THREE.Vector3: .multiply() now only accepts one argument. Use .multiplyVectors( a, b ) instead.' );
+                       return this.multiplyVectors( v, w );
+
+               }
+
+               this.x *= v.x;
+               this.y *= v.y;
+               this.z *= v.z;
+
+               return this;
+
+       }
+
+       multiplyScalar( scalar ) {
+
+               this.x *= scalar;
+               this.y *= scalar;
+               this.z *= scalar;
+
+               return this;
+
+       }
+
+       multiplyVectors( a, b ) {
+
+               this.x = a.x * b.x;
+               this.y = a.y * b.y;
+               this.z = a.z * b.z;
+
+               return this;
+
+       }
+
+       applyEuler( euler ) {
+
+               if ( ! ( euler && euler.isEuler ) ) {
+
+                       console.error( 'THREE.Vector3: .applyEuler() now expects an Euler rotation rather than a Vector3 and order.' );
+
+               }
+
+               return this.applyQuaternion( _quaternion.setFromEuler( euler ) );
+
+       }
+
+       applyAxisAngle( axis, angle ) {
+
+               return this.applyQuaternion( _quaternion.setFromAxisAngle( axis, angle ) );
+
+       }
+
+       applyMatrix3( m ) {
+
+               const x = this.x, y = this.y, z = this.z;
+               const e = m.elements;
+
+               this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ] * z;
+               this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ] * z;
+               this.z = e[ 2 ] * x + e[ 5 ] * y + e[ 8 ] * z;
+
+               return this;
+
+       }
+
+       applyNormalMatrix( m ) {
+
+               return this.applyMatrix3( m ).normalize();
+
+       }
+
+       applyMatrix4( m ) {
+
+               const x = this.x, y = this.y, z = this.z;
+               const e = m.elements;
+
+               const w = 1 / ( e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] );
+
+               this.x = ( e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] ) * w;
+               this.y = ( e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] ) * w;
+               this.z = ( e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] ) * w;
+
+               return this;
+
+       }
+
+       applyQuaternion( q ) {
+
+               const x = this.x, y = this.y, z = this.z;
+               const qx = q.x, qy = q.y, qz = q.z, qw = q.w;
+
+               // calculate quat * vector
+
+               const ix = qw * x + qy * z - qz * y;
+               const iy = qw * y + qz * x - qx * z;
+               const iz = qw * z + qx * y - qy * x;
+               const iw = - qx * x - qy * y - qz * z;
+
+               // calculate result * inverse quat
+
+               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;
+
+       }
+
+       project( camera ) {
+
+               return this.applyMatrix4( camera.matrixWorldInverse ).applyMatrix4( camera.projectionMatrix );
+
+       }
+
+       unproject( camera ) {
+
+               return this.applyMatrix4( camera.projectionMatrixInverse ).applyMatrix4( camera.matrixWorld );
+
+       }
+
+       transformDirection( m ) {
+
+               // input: THREE.Matrix4 affine matrix
+               // vector interpreted as a direction
+
+               const x = this.x, y = this.y, z = this.z;
+               const e = m.elements;
+
+               this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z;
+               this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z;
+               this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z;
+
+               return this.normalize();
+
+       }
+
+       divide( v ) {
+
+               this.x /= v.x;
+               this.y /= v.y;
+               this.z /= v.z;
+
+               return this;
+
+       }
+
+       divideScalar( scalar ) {
+
+               return this.multiplyScalar( 1 / scalar );
+
+       }
+
+       min( v ) {
+
+               this.x = Math.min( this.x, v.x );
+               this.y = Math.min( this.y, v.y );
+               this.z = Math.min( this.z, v.z );
+
+               return this;
+
+       }
+
+       max( v ) {
+
+               this.x = Math.max( this.x, v.x );
+               this.y = Math.max( this.y, v.y );
+               this.z = Math.max( this.z, v.z );
+
+               return this;
+
+       }
+
+       clamp( min, max ) {
+
+               // assumes min < max, componentwise
+
+               this.x = Math.max( min.x, Math.min( max.x, this.x ) );
+               this.y = Math.max( min.y, Math.min( max.y, this.y ) );
+               this.z = Math.max( min.z, Math.min( max.z, this.z ) );
+
+               return this;
+
+       }
+
+       clampScalar( minVal, maxVal ) {
+
+               this.x = Math.max( minVal, Math.min( maxVal, this.x ) );
+               this.y = Math.max( minVal, Math.min( maxVal, this.y ) );
+               this.z = Math.max( minVal, Math.min( maxVal, this.z ) );
+
+               return this;
+
+       }
+
+       clampLength( min, max ) {
+
+               const length = this.length();
+
+               return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) );
+
+       }
+
+       floor() {
+
+               this.x = Math.floor( this.x );
+               this.y = Math.floor( this.y );
+               this.z = Math.floor( this.z );
+
+               return this;
+
+       }
+
+       ceil() {
+
+               this.x = Math.ceil( this.x );
+               this.y = Math.ceil( this.y );
+               this.z = Math.ceil( this.z );
+
+               return this;
+
+       }
+
+       round() {
+
+               this.x = Math.round( this.x );
+               this.y = Math.round( this.y );
+               this.z = Math.round( this.z );
+
+               return this;
+
+       }
+
+       roundToZero() {
+
+               this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x );
+               this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y );
+               this.z = ( this.z < 0 ) ? Math.ceil( this.z ) : Math.floor( this.z );
+
+               return this;
+
+       }
+
+       negate() {
+
+               this.x = - this.x;
+               this.y = - this.y;
+               this.z = - this.z;
+
+               return this;
+
+       }
+
+       dot( v ) {
+
+               return this.x * v.x + this.y * v.y + this.z * v.z;
+
+       }
+
+       // TODO lengthSquared?
+
+       lengthSq() {
+
+               return this.x * this.x + this.y * this.y + this.z * this.z;
+
+       }
+
+       length() {
+
+               return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z );
+
+       }
+
+       manhattanLength() {
+
+               return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z );
+
+       }
+
+       normalize() {
+
+               return this.divideScalar( this.length() || 1 );
+
+       }
+
+       setLength( length ) {
+
+               return this.normalize().multiplyScalar( length );
+
+       }
+
+       lerp( v, alpha ) {
+
+               this.x += ( v.x - this.x ) * alpha;
+               this.y += ( v.y - this.y ) * alpha;
+               this.z += ( v.z - this.z ) * alpha;
+
+               return this;
+
+       }
+
+       lerpVectors( v1, v2, alpha ) {
+
+               this.x = v1.x + ( v2.x - v1.x ) * alpha;
+               this.y = v1.y + ( v2.y - v1.y ) * alpha;
+               this.z = v1.z + ( v2.z - v1.z ) * alpha;
+
+               return this;
+
+       }
+
+       cross( v, w ) {
+
+               if ( w !== undefined ) {
+
+                       console.warn( 'THREE.Vector3: .cross() now only accepts one argument. Use .crossVectors( a, b ) instead.' );
+                       return this.crossVectors( v, w );
+
+               }
+
+               return this.crossVectors( this, v );
+
+       }
+
+       crossVectors( a, b ) {
+
+               const ax = a.x, ay = a.y, az = a.z;
+               const 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;
+
+       }
+
+       projectOnVector( v ) {
+
+               const denominator = v.lengthSq();
+
+               if ( denominator === 0 ) return this.set( 0, 0, 0 );
+
+               const scalar = v.dot( this ) / denominator;
+
+               return this.copy( v ).multiplyScalar( scalar );
+
+       }
+
+       projectOnPlane( planeNormal ) {
+
+               _vector.copy( this ).projectOnVector( planeNormal );
+
+               return this.sub( _vector );
+
+       }
+
+       reflect( normal ) {
+
+               // reflect incident vector off plane orthogonal to normal
+               // normal is assumed to have unit length
+
+               return this.sub( _vector.copy( normal ).multiplyScalar( 2 * this.dot( normal ) ) );
+
+       }
+
+       angleTo( v ) {
+
+               const denominator = Math.sqrt( this.lengthSq() * v.lengthSq() );
+
+               if ( denominator === 0 ) return Math.PI / 2;
+
+               const theta = this.dot( v ) / denominator;
+
+               // clamp, to handle numerical problems
+
+               return Math.acos( MathUtils.clamp( theta, - 1, 1 ) );
+
+       }
+
+       distanceTo( v ) {
+
+               return Math.sqrt( this.distanceToSquared( v ) );
+
+       }
+
+       distanceToSquared( v ) {
+
+               const dx = this.x - v.x, dy = this.y - v.y, dz = this.z - v.z;
+
+               return dx * dx + dy * dy + dz * dz;
+
+       }
+
+       manhattanDistanceTo( v ) {
+
+               return Math.abs( this.x - v.x ) + Math.abs( this.y - v.y ) + Math.abs( this.z - v.z );
+
+       }
+
+       setFromSpherical( s ) {
+
+               return this.setFromSphericalCoords( s.radius, s.phi, s.theta );
+
+       }
+
+       setFromSphericalCoords( radius, phi, theta ) {
+
+               const sinPhiRadius = Math.sin( phi ) * radius;
+
+               this.x = sinPhiRadius * Math.sin( theta );
+               this.y = Math.cos( phi ) * radius;
+               this.z = sinPhiRadius * Math.cos( theta );
+
+               return this;
+
+       }
+
+       setFromCylindrical( c ) {
+
+               return this.setFromCylindricalCoords( c.radius, c.theta, c.y );
+
+       }
+
+       setFromCylindricalCoords( radius, theta, y ) {
+
+               this.x = radius * Math.sin( theta );
+               this.y = y;
+               this.z = radius * Math.cos( theta );
+
+               return this;
+
+       }
+
+       setFromMatrixPosition( m ) {
+
+               const e = m.elements;
+
+               this.x = e[ 12 ];
+               this.y = e[ 13 ];
+               this.z = e[ 14 ];
+
+               return this;
+
+       }
+
+       setFromMatrixScale( m ) {
+
+               const sx = this.setFromMatrixColumn( m, 0 ).length();
+               const sy = this.setFromMatrixColumn( m, 1 ).length();
+               const sz = this.setFromMatrixColumn( m, 2 ).length();
+
+               this.x = sx;
+               this.y = sy;
+               this.z = sz;
+
+               return this;
+
+       }
+
+       setFromMatrixColumn( m, index ) {
+
+               return this.fromArray( m.elements, index * 4 );
+
+       }
+
+       setFromMatrix3Column( m, index ) {
+
+               return this.fromArray( m.elements, index * 3 );
+
+       }
+
+       equals( v ) {
+
+               return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) );
+
+       }
+
+       fromArray( array, offset = 0 ) {
+
+               this.x = array[ offset ];
+               this.y = array[ offset + 1 ];
+               this.z = array[ offset + 2 ];
+
+               return this;
+
+       }
+
+       toArray( array = [], offset = 0 ) {
+
+               array[ offset ] = this.x;
+               array[ offset + 1 ] = this.y;
+               array[ offset + 2 ] = this.z;
+
+               return array;
+
+       }
+
+       fromBufferAttribute( attribute, index, offset ) {
+
+               if ( offset !== undefined ) {
+
+                       console.warn( 'THREE.Vector3: offset has been removed from .fromBufferAttribute().' );
+
+               }
+
+               this.x = attribute.getX( index );
+               this.y = attribute.getY( index );
+               this.z = attribute.getZ( index );
+
+               return this;
+
+       }
+
+       random() {
+
+               this.x = Math.random();
+               this.y = Math.random();
+               this.z = Math.random();
+
+               return this;
+
+       }
+
+}
+
+const _vector = /*@__PURE__*/ new Vector3();
+const _quaternion = /*@__PURE__*/ new Quaternion();
+
+class Box3 {
+
+       constructor( min, max ) {
+
+               Object.defineProperty( this, 'isBox3', { value: true } );
+
+               this.min = ( min !== undefined ) ? min : new Vector3( + Infinity, + Infinity, + Infinity );
+               this.max = ( max !== undefined ) ? max : new Vector3( - Infinity, - Infinity, - Infinity );
+
+       }
+
+       set( min, max ) {
+
+               this.min.copy( min );
+               this.max.copy( max );
+
+               return this;
+
+       }
+
+       setFromArray( array ) {
+
+               let minX = + Infinity;
+               let minY = + Infinity;
+               let minZ = + Infinity;
+
+               let maxX = - Infinity;
+               let maxY = - Infinity;
+               let maxZ = - Infinity;
+
+               for ( let i = 0, l = array.length; i < l; i += 3 ) {
+
+                       const x = array[ i ];
+                       const y = array[ i + 1 ];
+                       const z = array[ i + 2 ];
+
+                       if ( x < minX ) minX = x;
+                       if ( y < minY ) minY = y;
+                       if ( z < minZ ) minZ = z;
+
+                       if ( x > maxX ) maxX = x;
+                       if ( y > maxY ) maxY = y;
+                       if ( z > maxZ ) maxZ = z;
+
+               }
+
+               this.min.set( minX, minY, minZ );
+               this.max.set( maxX, maxY, maxZ );
+
+               return this;
+
+       }
+
+       setFromBufferAttribute( attribute ) {
+
+               let minX = + Infinity;
+               let minY = + Infinity;
+               let minZ = + Infinity;
+
+               let maxX = - Infinity;
+               let maxY = - Infinity;
+               let maxZ = - Infinity;
+
+               for ( let i = 0, l = attribute.count; i < l; i ++ ) {
+
+                       const x = attribute.getX( i );
+                       const y = attribute.getY( i );
+                       const z = attribute.getZ( i );
+
+                       if ( x < minX ) minX = x;
+                       if ( y < minY ) minY = y;
+                       if ( z < minZ ) minZ = z;
+
+                       if ( x > maxX ) maxX = x;
+                       if ( y > maxY ) maxY = y;
+                       if ( z > maxZ ) maxZ = z;
+
+               }
+
+               this.min.set( minX, minY, minZ );
+               this.max.set( maxX, maxY, maxZ );
+
+               return this;
+
+       }
+
+       setFromPoints( points ) {
+
+               this.makeEmpty();
+
+               for ( let i = 0, il = points.length; i < il; i ++ ) {
+
+                       this.expandByPoint( points[ i ] );
+
+               }
+
+               return this;
+
+       }
+
+       setFromCenterAndSize( center, size ) {
+
+               const halfSize = _vector$1.copy( size ).multiplyScalar( 0.5 );
+
+               this.min.copy( center ).sub( halfSize );
+               this.max.copy( center ).add( halfSize );
+
+               return this;
+
+       }
+
+       setFromObject( object ) {
+
+               this.makeEmpty();
+
+               return this.expandByObject( object );
+
+       }
+
+       clone() {
+
+               return new this.constructor().copy( this );
+
+       }
+
+       copy( box ) {
+
+               this.min.copy( box.min );
+               this.max.copy( box.max );
+
+               return this;
+
+       }
+
+       makeEmpty() {
+
+               this.min.x = this.min.y = this.min.z = + Infinity;
+               this.max.x = this.max.y = this.max.z = - Infinity;
+
+               return this;
+
+       }
+
+       isEmpty() {
+
+               // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes
+
+               return ( this.max.x < this.min.x ) || ( this.max.y < this.min.y ) || ( this.max.z < this.min.z );
+
+       }
+
+       getCenter( target ) {
+
+               if ( target === undefined ) {
+
+                       console.warn( 'THREE.Box3: .getCenter() target is now required' );
+                       target = new Vector3();
+
+               }
+
+               return this.isEmpty() ? target.set( 0, 0, 0 ) : target.addVectors( this.min, this.max ).multiplyScalar( 0.5 );
+
+       }
+
+       getSize( target ) {
+
+               if ( target === undefined ) {
+
+                       console.warn( 'THREE.Box3: .getSize() target is now required' );
+                       target = new Vector3();
+
+               }
+
+               return this.isEmpty() ? target.set( 0, 0, 0 ) : target.subVectors( this.max, this.min );
+
+       }
+
+       expandByPoint( point ) {
+
+               this.min.min( point );
+               this.max.max( point );
+
+               return this;
+
+       }
+
+       expandByVector( vector ) {
+
+               this.min.sub( vector );
+               this.max.add( vector );
+
+               return this;
+
+       }
+
+       expandByScalar( scalar ) {
+
+               this.min.addScalar( - scalar );
+               this.max.addScalar( scalar );
+
+               return this;
+
+       }
+
+       expandByObject( object ) {
+
+               // Computes the world-axis-aligned bounding box of an object (including its children),
+               // accounting for both the object's, and children's, world transforms
+
+               object.updateWorldMatrix( false, false );
+
+               const geometry = object.geometry;
+
+               if ( geometry !== undefined ) {
+
+                       if ( geometry.boundingBox === null ) {
+
+                               geometry.computeBoundingBox();
+
+                       }
+
+                       _box.copy( geometry.boundingBox );
+                       _box.applyMatrix4( object.matrixWorld );
+
+                       this.union( _box );
+
+               }
+
+               const children = object.children;
+
+               for ( let i = 0, l = children.length; i < l; i ++ ) {
+
+                       this.expandByObject( children[ i ] );
+
+               }
+
+               return this;
+
+       }
+
+       containsPoint( point ) {
+
+               return point.x < this.min.x || point.x > this.max.x ||
+                       point.y < this.min.y || point.y > this.max.y ||
+                       point.z < this.min.z || point.z > this.max.z ? false : true;
+
+       }
+
+       containsBox( box ) {
+
+               return this.min.x <= box.min.x && box.max.x <= this.max.x &&
+                       this.min.y <= box.min.y && box.max.y <= this.max.y &&
+                       this.min.z <= box.min.z && box.max.z <= this.max.z;
+
+       }
+
+       getParameter( point, target ) {
+
+               // This can potentially have a divide by zero if the box
+               // has a size dimension of 0.
+
+               if ( target === undefined ) {
+
+                       console.warn( 'THREE.Box3: .getParameter() target is now required' );
+                       target = new Vector3();
+
+               }
+
+               return target.set(
+                       ( point.x - this.min.x ) / ( this.max.x - this.min.x ),
+                       ( point.y - this.min.y ) / ( this.max.y - this.min.y ),
+                       ( point.z - this.min.z ) / ( this.max.z - this.min.z )
+               );
+
+       }
+
+       intersectsBox( box ) {
+
+               // using 6 splitting planes to rule out intersections.
+               return box.max.x < this.min.x || box.min.x > this.max.x ||
+                       box.max.y < this.min.y || box.min.y > this.max.y ||
+                       box.max.z < this.min.z || box.min.z > this.max.z ? false : true;
+
+       }
+
+       intersectsSphere( sphere ) {
+
+               // Find the point on the AABB closest to the sphere center.
+               this.clampPoint( sphere.center, _vector$1 );
+
+               // If that point is inside the sphere, the AABB and sphere intersect.
+               return _vector$1.distanceToSquared( sphere.center ) <= ( sphere.radius * sphere.radius );
+
+       }
+
+       intersectsPlane( plane ) {
+
+               // We compute the minimum and maximum dot product values. If those values
+               // are on the same side (back or front) of the plane, then there is no intersection.
+
+               let min, max;
+
+               if ( plane.normal.x > 0 ) {
+
+                       min = plane.normal.x * this.min.x;
+                       max = plane.normal.x * this.max.x;
+
+               } else {
+
+                       min = plane.normal.x * this.max.x;
+                       max = plane.normal.x * this.min.x;
+
+               }
+
+               if ( plane.normal.y > 0 ) {
+
+                       min += plane.normal.y * this.min.y;
+                       max += plane.normal.y * this.max.y;
+
+               } else {
+
+                       min += plane.normal.y * this.max.y;
+                       max += plane.normal.y * this.min.y;
+
+               }
+
+               if ( plane.normal.z > 0 ) {
+
+                       min += plane.normal.z * this.min.z;
+                       max += plane.normal.z * this.max.z;
+
+               } else {
+
+                       min += plane.normal.z * this.max.z;
+                       max += plane.normal.z * this.min.z;
+
+               }
+
+               return ( min <= - plane.constant && max >= - plane.constant );
+
+       }
+
+       intersectsTriangle( triangle ) {
+
+               if ( this.isEmpty() ) {
+
+                       return false;
+
+               }
+
+               // compute box center and extents
+               this.getCenter( _center );
+               _extents.subVectors( this.max, _center );
+
+               // translate triangle to aabb origin
+               _v0.subVectors( triangle.a, _center );
+               _v1.subVectors( triangle.b, _center );
+               _v2.subVectors( triangle.c, _center );
+
+               // compute edge vectors for triangle
+               _f0.subVectors( _v1, _v0 );
+               _f1.subVectors( _v2, _v1 );
+               _f2.subVectors( _v0, _v2 );
+
+               // test against axes that are given by cross product combinations of the edges of the triangle and the edges of the aabb
+               // make an axis testing of each of the 3 sides of the aabb against each of the 3 sides of the triangle = 9 axis of separation
+               // axis_ij = u_i x f_j (u0, u1, u2 = face normals of aabb = x,y,z axes vectors since aabb is axis aligned)
+               let axes = [
+                       0, - _f0.z, _f0.y, 0, - _f1.z, _f1.y, 0, - _f2.z, _f2.y,
+                       _f0.z, 0, - _f0.x, _f1.z, 0, - _f1.x, _f2.z, 0, - _f2.x,
+                       - _f0.y, _f0.x, 0, - _f1.y, _f1.x, 0, - _f2.y, _f2.x, 0
+               ];
+               if ( ! satForAxes( axes, _v0, _v1, _v2, _extents ) ) {
+
+                       return false;
+
+               }
+
+               // test 3 face normals from the aabb
+               axes = [ 1, 0, 0, 0, 1, 0, 0, 0, 1 ];
+               if ( ! satForAxes( axes, _v0, _v1, _v2, _extents ) ) {
+
+                       return false;
+
+               }
+
+               // finally testing the face normal of the triangle
+               // use already existing triangle edge vectors here
+               _triangleNormal.crossVectors( _f0, _f1 );
+               axes = [ _triangleNormal.x, _triangleNormal.y, _triangleNormal.z ];
+
+               return satForAxes( axes, _v0, _v1, _v2, _extents );
+
+       }
+
+       clampPoint( point, target ) {
+
+               if ( target === undefined ) {
+
+                       console.warn( 'THREE.Box3: .clampPoint() target is now required' );
+                       target = new Vector3();
+
+               }
+
+               return target.copy( point ).clamp( this.min, this.max );
+
+       }
+
+       distanceToPoint( point ) {
+
+               const clampedPoint = _vector$1.copy( point ).clamp( this.min, this.max );
+
+               return clampedPoint.sub( point ).length();
+
+       }
+
+       getBoundingSphere( target ) {
+
+               if ( target === undefined ) {
+
+                       console.error( 'THREE.Box3: .getBoundingSphere() target is now required' );
+                       //target = new Sphere(); // removed to avoid cyclic dependency
+
+               }
+
+               this.getCenter( target.center );
+
+               target.radius = this.getSize( _vector$1 ).length() * 0.5;
+
+               return target;
+
+       }
+
+       intersect( box ) {
+
+               this.min.max( box.min );
+               this.max.min( box.max );
+
+               // ensure that if there is no overlap, the result is fully empty, not slightly empty with non-inf/+inf values that will cause subsequence intersects to erroneously return valid values.
+               if ( this.isEmpty() ) this.makeEmpty();
+
+               return this;
+
+       }
+
+       union( box ) {
+
+               this.min.min( box.min );
+               this.max.max( box.max );
+
+               return this;
+
+       }
+
+       applyMatrix4( matrix ) {
+
+               // transform of empty box is an empty box.
+               if ( this.isEmpty() ) return this;
+
+               // NOTE: I am using a binary pattern to specify all 2^3 combinations below
+               _points[ 0 ].set( this.min.x, this.min.y, this.min.z ).applyMatrix4( matrix ); // 000
+               _points[ 1 ].set( this.min.x, this.min.y, this.max.z ).applyMatrix4( matrix ); // 001
+               _points[ 2 ].set( this.min.x, this.max.y, this.min.z ).applyMatrix4( matrix ); // 010
+               _points[ 3 ].set( this.min.x, this.max.y, this.max.z ).applyMatrix4( matrix ); // 011
+               _points[ 4 ].set( this.max.x, this.min.y, this.min.z ).applyMatrix4( matrix ); // 100
+               _points[ 5 ].set( this.max.x, this.min.y, this.max.z ).applyMatrix4( matrix ); // 101
+               _points[ 6 ].set( this.max.x, this.max.y, this.min.z ).applyMatrix4( matrix ); // 110
+               _points[ 7 ].set( this.max.x, this.max.y, this.max.z ).applyMatrix4( matrix ); // 111
+
+               this.setFromPoints( _points );
+
+               return this;
+
+       }
+
+       translate( offset ) {
+
+               this.min.add( offset );
+               this.max.add( offset );
+
+               return this;
+
+       }
+
+       equals( box ) {
+
+               return box.min.equals( this.min ) && box.max.equals( this.max );
+
+       }
+
+}
+
+function satForAxes( axes, v0, v1, v2, extents ) {
+
+       for ( let i = 0, j = axes.length - 3; i <= j; i += 3 ) {
+
+               _testAxis.fromArray( axes, i );
+               // project the aabb onto the seperating axis
+               const r = extents.x * Math.abs( _testAxis.x ) + extents.y * Math.abs( _testAxis.y ) + extents.z * Math.abs( _testAxis.z );
+               // project all 3 vertices of the triangle onto the seperating axis
+               const p0 = v0.dot( _testAxis );
+               const p1 = v1.dot( _testAxis );
+               const p2 = v2.dot( _testAxis );
+               // actual test, basically see if either of the most extreme of the triangle points intersects r
+               if ( Math.max( - Math.max( p0, p1, p2 ), Math.min( p0, p1, p2 ) ) > r ) {
+
+                       // points of the projected triangle are outside the projected half-length of the aabb
+                       // the axis is seperating and we can exit
+                       return false;
+
+               }
+
+       }
+
+       return true;
+
+}
+
+const _points = [
+       /*@__PURE__*/ new Vector3(),
+       /*@__PURE__*/ new Vector3(),
+       /*@__PURE__*/ new Vector3(),
+       /*@__PURE__*/ new Vector3(),
+       /*@__PURE__*/ new Vector3(),
+       /*@__PURE__*/ new Vector3(),
+       /*@__PURE__*/ new Vector3(),
+       /*@__PURE__*/ new Vector3()
+];
+
+const _vector$1 = /*@__PURE__*/ new Vector3();
+
+const _box = /*@__PURE__*/ new Box3();
+
+// triangle centered vertices
+
+const _v0 = /*@__PURE__*/ new Vector3();
+const _v1 = /*@__PURE__*/ new Vector3();
+const _v2 = /*@__PURE__*/ new Vector3();
+
+// triangle edge vectors
+
+const _f0 = /*@__PURE__*/ new Vector3();
+const _f1 = /*@__PURE__*/ new Vector3();
+const _f2 = /*@__PURE__*/ new Vector3();
+
+const _center = /*@__PURE__*/ new Vector3();
+const _extents = /*@__PURE__*/ new Vector3();
+const _triangleNormal = /*@__PURE__*/ new Vector3();
+const _testAxis = /*@__PURE__*/ new Vector3();
+
+const _box$1 = /*@__PURE__*/ new Box3();
+
+class Sphere {
+
+       constructor( center, radius ) {
+
+               this.center = ( center !== undefined ) ? center : new Vector3();
+               this.radius = ( radius !== undefined ) ? radius : - 1;
+
+       }
+
+       set( center, radius ) {
+
+               this.center.copy( center );
+               this.radius = radius;
+
+               return this;
+
+       }
+
+       setFromPoints( points, optionalCenter ) {
+
+               const center = this.center;
+
+               if ( optionalCenter !== undefined ) {
+
+                       center.copy( optionalCenter );
+
+               } else {
+
+                       _box$1.setFromPoints( points ).getCenter( center );
+
+               }
+
+               let maxRadiusSq = 0;
+
+               for ( let i = 0, il = points.length; i < il; i ++ ) {
+
+                       maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( points[ i ] ) );
+
+               }
+
+               this.radius = Math.sqrt( maxRadiusSq );
+
+               return this;
+
+       }
+
+       clone() {
+
+               return new this.constructor().copy( this );
+
+       }
+
+       copy( sphere ) {
+
+               this.center.copy( sphere.center );
+               this.radius = sphere.radius;
+
+               return this;
+
+       }
+
+       isEmpty() {
+
+               return ( this.radius < 0 );
+
+       }
+
+       makeEmpty() {
+
+               this.center.set( 0, 0, 0 );
+               this.radius = - 1;
+
+               return this;
+
+       }
+
+       containsPoint( point ) {
+
+               return ( point.distanceToSquared( this.center ) <= ( this.radius * this.radius ) );
+
+       }
+
+       distanceToPoint( point ) {
+
+               return ( point.distanceTo( this.center ) - this.radius );
+
+       }
+
+       intersectsSphere( sphere ) {
+
+               const radiusSum = this.radius + sphere.radius;
+
+               return sphere.center.distanceToSquared( this.center ) <= ( radiusSum * radiusSum );
+
+       }
+
+       intersectsBox( box ) {
+
+               return box.intersectsSphere( this );
+
+       }
+
+       intersectsPlane( plane ) {
+
+               return Math.abs( plane.distanceToPoint( this.center ) ) <= this.radius;
+
+       }
+
+       clampPoint( point, target ) {
+
+               const deltaLengthSq = this.center.distanceToSquared( point );
+
+               if ( target === undefined ) {
+
+                       console.warn( 'THREE.Sphere: .clampPoint() target is now required' );
+                       target = new Vector3();
+
+               }
+
+               target.copy( point );
+
+               if ( deltaLengthSq > ( this.radius * this.radius ) ) {
+
+                       target.sub( this.center ).normalize();
+                       target.multiplyScalar( this.radius ).add( this.center );
+
+               }
+
+               return target;
+
+       }
+
+       getBoundingBox( target ) {
+
+               if ( target === undefined ) {
+
+                       console.warn( 'THREE.Sphere: .getBoundingBox() target is now required' );
+                       target = new Box3();
+
+               }
+
+               if ( this.isEmpty() ) {
+
+                       // Empty sphere produces empty bounding box
+                       target.makeEmpty();
+                       return target;
+
+               }
+
+               target.set( this.center, this.center );
+               target.expandByScalar( this.radius );
+
+               return target;
+
+       }
+
+       applyMatrix4( matrix ) {
+
+               this.center.applyMatrix4( matrix );
+               this.radius = this.radius * matrix.getMaxScaleOnAxis();
+
+               return this;
+
+       }
+
+       translate( offset ) {
+
+               this.center.add( offset );
+
+               return this;
+
+       }
+
+       equals( sphere ) {
+
+               return sphere.center.equals( this.center ) && ( sphere.radius === this.radius );
+
+       }
+
+}
+
+const _vector$2 = /*@__PURE__*/ new Vector3();
+const _segCenter = /*@__PURE__*/ new Vector3();
+const _segDir = /*@__PURE__*/ new Vector3();
+const _diff = /*@__PURE__*/ new Vector3();
+
+const _edge1 = /*@__PURE__*/ new Vector3();
+const _edge2 = /*@__PURE__*/ new Vector3();
+const _normal = /*@__PURE__*/ new Vector3();
+
+class Ray {
+
+       constructor( origin, direction ) {
+
+               this.origin = ( origin !== undefined ) ? origin : new Vector3();
+               this.direction = ( direction !== undefined ) ? direction : new Vector3( 0, 0, - 1 );
+
+       }
+
+       set( origin, direction ) {
+
+               this.origin.copy( origin );
+               this.direction.copy( direction );
+
+               return this;
+
+       }
+
+       clone() {
+
+               return new this.constructor().copy( this );
+
+       }
+
+       copy( ray ) {
+
+               this.origin.copy( ray.origin );
+               this.direction.copy( ray.direction );
+
+               return this;
+
+       }
+
+       at( t, target ) {
+
+               if ( target === undefined ) {
+
+                       console.warn( 'THREE.Ray: .at() target is now required' );
+                       target = new Vector3();
+
+               }
+
+               return target.copy( this.direction ).multiplyScalar( t ).add( this.origin );
+
+       }
+
+       lookAt( v ) {
+
+               this.direction.copy( v ).sub( this.origin ).normalize();
+
+               return this;
+
+       }
+
+       recast( t ) {
+
+               this.origin.copy( this.at( t, _vector$2 ) );
+
+               return this;
+
+       }
+
+       closestPointToPoint( point, target ) {
+
+               if ( target === undefined ) {
+
+                       console.warn( 'THREE.Ray: .closestPointToPoint() target is now required' );
+                       target = new Vector3();
+
+               }
+
+               target.subVectors( point, this.origin );
+
+               const directionDistance = target.dot( this.direction );
+
+               if ( directionDistance < 0 ) {
+
+                       return target.copy( this.origin );
+
+               }
+
+               return target.copy( this.direction ).multiplyScalar( directionDistance ).add( this.origin );
+
+       }
+
+       distanceToPoint( point ) {
+
+               return Math.sqrt( this.distanceSqToPoint( point ) );
+
+       }
+
+       distanceSqToPoint( point ) {
+
+               const directionDistance = _vector$2.subVectors( point, this.origin ).dot( this.direction );
+
+               // point behind the ray
+
+               if ( directionDistance < 0 ) {
+
+                       return this.origin.distanceToSquared( point );
+
+               }
+
+               _vector$2.copy( this.direction ).multiplyScalar( directionDistance ).add( this.origin );
+
+               return _vector$2.distanceToSquared( point );
+
+       }
+
+       distanceSqToSegment( v0, v1, optionalPointOnRay, optionalPointOnSegment ) {
+
+               // from http://www.geometrictools.com/GTEngine/Include/Mathematics/GteDistRaySegment.h
+               // It returns the min distance between the ray and the segment
+               // defined by v0 and v1
+               // It can also set two optional targets :
+               // - The closest point on the ray
+               // - The closest point on the segment
+
+               _segCenter.copy( v0 ).add( v1 ).multiplyScalar( 0.5 );
+               _segDir.copy( v1 ).sub( v0 ).normalize();
+               _diff.copy( this.origin ).sub( _segCenter );
+
+               const segExtent = v0.distanceTo( v1 ) * 0.5;
+               const a01 = - this.direction.dot( _segDir );
+               const b0 = _diff.dot( this.direction );
+               const b1 = - _diff.dot( _segDir );
+               const c = _diff.lengthSq();
+               const det = Math.abs( 1 - a01 * a01 );
+               let s0, s1, sqrDist, extDet;
+
+               if ( det > 0 ) {
+
+                       // The ray and segment are not parallel.
+
+                       s0 = a01 * b1 - b0;
+                       s1 = a01 * b0 - b1;
+                       extDet = segExtent * det;
+
+                       if ( s0 >= 0 ) {
+
+                               if ( s1 >= - extDet ) {
+
+                                       if ( s1 <= extDet ) {
+
+                                               // region 0
+                                               // Minimum at interior points of ray and segment.
+
+                                               const invDet = 1 / det;
+                                               s0 *= invDet;
+                                               s1 *= invDet;
+                                               sqrDist = s0 * ( s0 + a01 * s1 + 2 * b0 ) + s1 * ( a01 * s0 + s1 + 2 * b1 ) + c;
+
+                                       } else {
+
+                                               // region 1
+
+                                               s1 = segExtent;
+                                               s0 = Math.max( 0, - ( a01 * s1 + b0 ) );
+                                               sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
+
+                                       }
+
+                               } else {
+
+                                       // region 5
+
+                                       s1 = - segExtent;
+                                       s0 = Math.max( 0, - ( a01 * s1 + b0 ) );
+                                       sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
+
+                               }
+
+                       } else {
+
+                               if ( s1 <= - extDet ) {
+
+                                       // region 4
+
+                                       s0 = Math.max( 0, - ( - a01 * segExtent + b0 ) );
+                                       s1 = ( s0 > 0 ) ? - segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent );
+                                       sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
+
+                               } else if ( s1 <= extDet ) {
+
+                                       // region 3
+
+                                       s0 = 0;
+                                       s1 = Math.min( Math.max( - segExtent, - b1 ), segExtent );
+                                       sqrDist = s1 * ( s1 + 2 * b1 ) + c;
+
+                               } else {
+
+                                       // region 2
+
+                                       s0 = Math.max( 0, - ( a01 * segExtent + b0 ) );
+                                       s1 = ( s0 > 0 ) ? segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent );
+                                       sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
+
+                               }
+
+                       }
+
+               } else {
+
+                       // Ray and segment are parallel.
+
+                       s1 = ( a01 > 0 ) ? - segExtent : segExtent;
+                       s0 = Math.max( 0, - ( a01 * s1 + b0 ) );
+                       sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
+
+               }
+
+               if ( optionalPointOnRay ) {
+
+                       optionalPointOnRay.copy( this.direction ).multiplyScalar( s0 ).add( this.origin );
+
+               }
+
+               if ( optionalPointOnSegment ) {
+
+                       optionalPointOnSegment.copy( _segDir ).multiplyScalar( s1 ).add( _segCenter );
+
+               }
+
+               return sqrDist;
+
+       }
+
+       intersectSphere( sphere, target ) {
+
+               _vector$2.subVectors( sphere.center, this.origin );
+               const tca = _vector$2.dot( this.direction );
+               const d2 = _vector$2.dot( _vector$2 ) - tca * tca;
+               const radius2 = sphere.radius * sphere.radius;
+
+               if ( d2 > radius2 ) return null;
+
+               const thc = Math.sqrt( radius2 - d2 );
+
+               // t0 = first intersect point - entrance on front of sphere
+               const t0 = tca - thc;
+
+               // t1 = second intersect point - exit point on back of sphere
+               const t1 = tca + thc;
+
+               // test to see if both t0 and t1 are behind the ray - if so, return null
+               if ( t0 < 0 && t1 < 0 ) return null;
+
+               // test to see if t0 is behind the ray:
+               // if it is, the ray is inside the sphere, so return the second exit point scaled by t1,
+               // in order to always return an intersect point that is in front of the ray.
+               if ( t0 < 0 ) return this.at( t1, target );
+
+               // else t0 is in front of the ray, so return the first collision point scaled by t0
+               return this.at( t0, target );
+
+       }
+
+       intersectsSphere( sphere ) {
+
+               return this.distanceSqToPoint( sphere.center ) <= ( sphere.radius * sphere.radius );
+
+       }
+
+       distanceToPlane( plane ) {
+
+               const denominator = plane.normal.dot( this.direction );
+
+               if ( denominator === 0 ) {
+
+                       // line is coplanar, return origin
+                       if ( plane.distanceToPoint( this.origin ) === 0 ) {
+
+                               return 0;
+
+                       }
+
+                       // Null is preferable to undefined since undefined means.... it is undefined
+
+                       return null;
+
+               }
+
+               const t = - ( this.origin.dot( plane.normal ) + plane.constant ) / denominator;
+
+               // Return if the ray never intersects the plane
+
+               return t >= 0 ? t : null;
+
+       }
+
+       intersectPlane( plane, target ) {
+
+               const t = this.distanceToPlane( plane );
+
+               if ( t === null ) {
+
+                       return null;
+
+               }
+
+               return this.at( t, target );
+
+       }
+
+       intersectsPlane( plane ) {
+
+               // check if the ray lies on the plane first
+
+               const distToPoint = plane.distanceToPoint( this.origin );
+
+               if ( distToPoint === 0 ) {
+
+                       return true;
+
+               }
+
+               const denominator = plane.normal.dot( this.direction );
+
+               if ( denominator * distToPoint < 0 ) {
+
+                       return true;
+
+               }
+
+               // ray origin is behind the plane (and is pointing behind it)
+
+               return false;
+
+       }
+
+       intersectBox( box, target ) {
+
+               let tmin, tmax, tymin, tymax, tzmin, tzmax;
+
+               const invdirx = 1 / this.direction.x,
+                       invdiry = 1 / this.direction.y,
+                       invdirz = 1 / this.direction.z;
+
+               const origin = this.origin;
+
+               if ( invdirx >= 0 ) {
+
+                       tmin = ( box.min.x - origin.x ) * invdirx;
+                       tmax = ( box.max.x - origin.x ) * invdirx;
+
+               } else {
+
+                       tmin = ( box.max.x - origin.x ) * invdirx;
+                       tmax = ( box.min.x - origin.x ) * invdirx;
+
+               }
+
+               if ( invdiry >= 0 ) {
+
+                       tymin = ( box.min.y - origin.y ) * invdiry;
+                       tymax = ( box.max.y - origin.y ) * invdiry;
+
+               } else {
+
+                       tymin = ( box.max.y - origin.y ) * invdiry;
+                       tymax = ( box.min.y - origin.y ) * invdiry;
+
+               }
+
+               if ( ( tmin > tymax ) || ( tymin > tmax ) ) return null;
+
+               // These lines also handle the case where tmin or tmax is NaN
+               // (result of 0 * Infinity). x !== x returns true if x is NaN
+
+               if ( tymin > tmin || tmin !== tmin ) tmin = tymin;
+
+               if ( tymax < tmax || tmax !== tmax ) tmax = tymax;
+
+               if ( invdirz >= 0 ) {
+
+                       tzmin = ( box.min.z - origin.z ) * invdirz;
+                       tzmax = ( box.max.z - origin.z ) * invdirz;
+
+               } else {
+
+                       tzmin = ( box.max.z - origin.z ) * invdirz;
+                       tzmax = ( box.min.z - origin.z ) * invdirz;
+
+               }
+
+               if ( ( tmin > tzmax ) || ( tzmin > tmax ) ) return null;
+
+               if ( tzmin > tmin || tmin !== tmin ) tmin = tzmin;
+
+               if ( tzmax < tmax || tmax !== tmax ) tmax = tzmax;
+
+               //return point closest to the ray (positive side)
+
+               if ( tmax < 0 ) return null;
+
+               return this.at( tmin >= 0 ? tmin : tmax, target );
+
+       }
+
+       intersectsBox( box ) {
+
+               return this.intersectBox( box, _vector$2 ) !== null;
+
+       }
+
+       intersectTriangle( a, b, c, backfaceCulling, target ) {
+
+               // Compute the offset origin, edges, and normal.
+
+               // from http://www.geometrictools.com/GTEngine/Include/Mathematics/GteIntrRay3Triangle3.h
+
+               _edge1.subVectors( b, a );
+               _edge2.subVectors( c, a );
+               _normal.crossVectors( _edge1, _edge2 );
+
+               // Solve Q + t*D = b1*E1 + b2*E2 (Q = kDiff, D = ray direction,
+               // E1 = kEdge1, E2 = kEdge2, N = Cross(E1,E2)) by
+               //   |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2))
+               //   |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q))
+               //   |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N)
+               let DdN = this.direction.dot( _normal );
+               let sign;
+
+               if ( DdN > 0 ) {
+
+                       if ( backfaceCulling ) return null;
+                       sign = 1;
+
+               } else if ( DdN < 0 ) {
+
+                       sign = - 1;
+                       DdN = - DdN;
+
+               } else {
+
+                       return null;
+
+               }
+
+               _diff.subVectors( this.origin, a );
+               const DdQxE2 = sign * this.direction.dot( _edge2.crossVectors( _diff, _edge2 ) );
+
+               // b1 < 0, no intersection
+               if ( DdQxE2 < 0 ) {
+
+                       return null;
+
+               }
+
+               const DdE1xQ = sign * this.direction.dot( _edge1.cross( _diff ) );
+
+               // b2 < 0, no intersection
+               if ( DdE1xQ < 0 ) {
+
+                       return null;
+
+               }
+
+               // b1+b2 > 1, no intersection
+               if ( DdQxE2 + DdE1xQ > DdN ) {
+
+                       return null;
+
+               }
+
+               // Line intersects triangle, check if ray does.
+               const QdN = - sign * _diff.dot( _normal );
+
+               // t < 0, no intersection
+               if ( QdN < 0 ) {
+
+                       return null;
+
+               }
+
+               // Ray intersects triangle.
+               return this.at( QdN / DdN, target );
+
+       }
+
+       applyMatrix4( matrix4 ) {
+
+               this.origin.applyMatrix4( matrix4 );
+               this.direction.transformDirection( matrix4 );
+
+               return this;
+
+       }
+
+       equals( ray ) {
+
+               return ray.origin.equals( this.origin ) && ray.direction.equals( this.direction );
+
+       }
+
+}
+
+class Matrix4 {
+
+       constructor() {
+
+               Object.defineProperty( this, 'isMatrix4', { value: true } );
+
+               this.elements = [
+
+                       1, 0, 0, 0,
+                       0, 1, 0, 0,
+                       0, 0, 1, 0,
+                       0, 0, 0, 1
+
+               ];
+
+               if ( arguments.length > 0 ) {
+
+                       console.error( 'THREE.Matrix4: the constructor no longer reads arguments. use .set() instead.' );
+
+               }
+
+       }
+
+       set( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ) {
+
+               const te = this.elements;
+
+               te[ 0 ] = n11; te[ 4 ] = n12; te[ 8 ] = n13; te[ 12 ] = n14;
+               te[ 1 ] = n21; te[ 5 ] = n22; te[ 9 ] = n23; te[ 13 ] = n24;
+               te[ 2 ] = n31; te[ 6 ] = n32; te[ 10 ] = n33; te[ 14 ] = n34;
+               te[ 3 ] = n41; te[ 7 ] = n42; te[ 11 ] = n43; te[ 15 ] = n44;
+
+               return this;
+
+       }
+
+       identity() {
+
+               this.set(
+
+                       1, 0, 0, 0,
+                       0, 1, 0, 0,
+                       0, 0, 1, 0,
+                       0, 0, 0, 1
+
+               );
+
+               return this;
+
+       }
+
+       clone() {
+
+               return new Matrix4().fromArray( this.elements );
+
+       }
+
+       copy( m ) {
+
+               const te = this.elements;
+               const me = m.elements;
+
+               te[ 0 ] = me[ 0 ]; te[ 1 ] = me[ 1 ]; te[ 2 ] = me[ 2 ]; te[ 3 ] = me[ 3 ];
+               te[ 4 ] = me[ 4 ]; te[ 5 ] = me[ 5 ]; te[ 6 ] = me[ 6 ]; te[ 7 ] = me[ 7 ];
+               te[ 8 ] = me[ 8 ]; te[ 9 ] = me[ 9 ]; te[ 10 ] = me[ 10 ]; te[ 11 ] = me[ 11 ];
+               te[ 12 ] = me[ 12 ]; te[ 13 ] = me[ 13 ]; te[ 14 ] = me[ 14 ]; te[ 15 ] = me[ 15 ];
+
+               return this;
+
+       }
+
+       copyPosition( m ) {
+
+               const te = this.elements, me = m.elements;
+
+               te[ 12 ] = me[ 12 ];
+               te[ 13 ] = me[ 13 ];
+               te[ 14 ] = me[ 14 ];
+
+               return this;
+
+       }
+
+       setFromMatrix3( m ) {
+
+               const me = m.elements;
+
+               this.set(
+
+                       me[ 0 ], me[ 3 ], me[ 6 ], 0,
+                       me[ 1 ], me[ 4 ], me[ 7 ], 0,
+                       me[ 2 ], me[ 5 ], me[ 8 ], 0,
+                       0, 0, 0, 1
+
+               );
+
+               return this;
+
+       }
+
+       extractBasis( xAxis, yAxis, zAxis ) {
+
+               xAxis.setFromMatrixColumn( this, 0 );
+               yAxis.setFromMatrixColumn( this, 1 );
+               zAxis.setFromMatrixColumn( this, 2 );
+
+               return this;
+
+       }
+
+       makeBasis( xAxis, yAxis, zAxis ) {
+
+               this.set(
+                       xAxis.x, yAxis.x, zAxis.x, 0,
+                       xAxis.y, yAxis.y, zAxis.y, 0,
+                       xAxis.z, yAxis.z, zAxis.z, 0,
+                       0, 0, 0, 1
+               );
+
+               return this;
+
+       }
+
+       extractRotation( m ) {
+
+               // this method does not support reflection matrices
+
+               const te = this.elements;
+               const me = m.elements;
+
+               const scaleX = 1 / _v1$1.setFromMatrixColumn( m, 0 ).length();
+               const scaleY = 1 / _v1$1.setFromMatrixColumn( m, 1 ).length();
+               const scaleZ = 1 / _v1$1.setFromMatrixColumn( m, 2 ).length();
+
+               te[ 0 ] = me[ 0 ] * scaleX;
+               te[ 1 ] = me[ 1 ] * scaleX;
+               te[ 2 ] = me[ 2 ] * scaleX;
+               te[ 3 ] = 0;
+
+               te[ 4 ] = me[ 4 ] * scaleY;
+               te[ 5 ] = me[ 5 ] * scaleY;
+               te[ 6 ] = me[ 6 ] * scaleY;
+               te[ 7 ] = 0;
+
+               te[ 8 ] = me[ 8 ] * scaleZ;
+               te[ 9 ] = me[ 9 ] * scaleZ;
+               te[ 10 ] = me[ 10 ] * scaleZ;
+               te[ 11 ] = 0;
+
+               te[ 12 ] = 0;
+               te[ 13 ] = 0;
+               te[ 14 ] = 0;
+               te[ 15 ] = 1;
+
+               return this;
+
+       }
+
+       makeRotationFromEuler( euler ) {
+
+               if ( ! ( euler && euler.isEuler ) ) {
+
+                       console.error( 'THREE.Matrix4: .makeRotationFromEuler() now expects a Euler rotation rather than a Vector3 and order.' );
+
+               }
+
+               const te = this.elements;
+
+               const x = euler.x, y = euler.y, z = euler.z;
+               const a = Math.cos( x ), b = Math.sin( x );
+               const c = Math.cos( y ), d = Math.sin( y );
+               const e = Math.cos( z ), f = Math.sin( z );
+
+               if ( euler.order === 'XYZ' ) {
+
+                       const ae = a * e, af = a * f, be = b * e, bf = b * f;
+
+                       te[ 0 ] = c * e;
+                       te[ 4 ] = - c * f;
+                       te[ 8 ] = d;
+
+                       te[ 1 ] = af + be * d;
+                       te[ 5 ] = ae - bf * d;
+                       te[ 9 ] = - b * c;
+
+                       te[ 2 ] = bf - ae * d;
+                       te[ 6 ] = be + af * d;
+                       te[ 10 ] = a * c;
+
+               } else if ( euler.order === 'YXZ' ) {
+
+                       const ce = c * e, cf = c * f, de = d * e, df = d * f;
+
+                       te[ 0 ] = ce + df * b;
+                       te[ 4 ] = de * b - cf;
+                       te[ 8 ] = a * d;
+
+                       te[ 1 ] = a * f;
+                       te[ 5 ] = a * e;
+                       te[ 9 ] = - b;
+
+                       te[ 2 ] = cf * b - de;
+                       te[ 6 ] = df + ce * b;
+                       te[ 10 ] = a * c;
+
+               } else if ( euler.order === 'ZXY' ) {
+
+                       const ce = c * e, cf = c * f, de = d * e, df = d * f;
+
+                       te[ 0 ] = ce - df * b;
+                       te[ 4 ] = - a * f;
+                       te[ 8 ] = de + cf * b;
+
+                       te[ 1 ] = cf + de * b;
+                       te[ 5 ] = a * e;
+                       te[ 9 ] = df - ce * b;
+
+                       te[ 2 ] = - a * d;
+                       te[ 6 ] = b;
+                       te[ 10 ] = a * c;
+
+               } else if ( euler.order === 'ZYX' ) {
+
+                       const ae = a * e, af = a * f, be = b * e, bf = b * f;
+
+                       te[ 0 ] = c * e;
+                       te[ 4 ] = be * d - af;
+                       te[ 8 ] = ae * d + bf;
+
+                       te[ 1 ] = c * f;
+                       te[ 5 ] = bf * d + ae;
+                       te[ 9 ] = af * d - be;
+
+                       te[ 2 ] = - d;
+                       te[ 6 ] = b * c;
+                       te[ 10 ] = a * c;
+
+               } else if ( euler.order === 'YZX' ) {
+
+                       const ac = a * c, ad = a * d, bc = b * c, bd = b * d;
+
+                       te[ 0 ] = c * e;
+                       te[ 4 ] = bd - ac * f;
+                       te[ 8 ] = bc * f + ad;
+
+                       te[ 1 ] = f;
+                       te[ 5 ] = a * e;
+                       te[ 9 ] = - b * e;
+
+                       te[ 2 ] = - d * e;
+                       te[ 6 ] = ad * f + bc;
+                       te[ 10 ] = ac - bd * f;
+
+               } else if ( euler.order === 'XZY' ) {
+
+                       const ac = a * c, ad = a * d, bc = b * c, bd = b * d;
+
+                       te[ 0 ] = c * e;
+                       te[ 4 ] = - f;
+                       te[ 8 ] = d * e;
+
+                       te[ 1 ] = ac * f + bd;
+                       te[ 5 ] = a * e;
+                       te[ 9 ] = ad * f - bc;
+
+                       te[ 2 ] = bc * f - ad;
+                       te[ 6 ] = b * e;
+                       te[ 10 ] = bd * f + ac;
+
+               }
+
+               // bottom row
+               te[ 3 ] = 0;
+               te[ 7 ] = 0;
+               te[ 11 ] = 0;
+
+               // last column
+               te[ 12 ] = 0;
+               te[ 13 ] = 0;
+               te[ 14 ] = 0;
+               te[ 15 ] = 1;
+
+               return this;
+
+       }
+
+       makeRotationFromQuaternion( q ) {
+
+               return this.compose( _zero, q, _one );
+
+       }
+
+       lookAt( eye, target, up ) {
+
+               const te = this.elements;
+
+               _z.subVectors( eye, target );
+
+               if ( _z.lengthSq() === 0 ) {
+
+                       // eye and target are in the same position
+
+                       _z.z = 1;
+
+               }
+
+               _z.normalize();
+               _x.crossVectors( up, _z );
+
+               if ( _x.lengthSq() === 0 ) {
+
+                       // up and z are parallel
+
+                       if ( Math.abs( up.z ) === 1 ) {
+
+                               _z.x += 0.0001;
+
+                       } else {
+
+                               _z.z += 0.0001;
+
+                       }
+
+                       _z.normalize();
+                       _x.crossVectors( up, _z );
+
+               }
+
+               _x.normalize();
+               _y.crossVectors( _z, _x );
+
+               te[ 0 ] = _x.x; te[ 4 ] = _y.x; te[ 8 ] = _z.x;
+               te[ 1 ] = _x.y; te[ 5 ] = _y.y; te[ 9 ] = _z.y;
+               te[ 2 ] = _x.z; te[ 6 ] = _y.z; te[ 10 ] = _z.z;
+
+               return this;
+
+       }
+
+       multiply( m, n ) {
+
+               if ( n !== undefined ) {
+
+                       console.warn( 'THREE.Matrix4: .multiply() now only accepts one argument. Use .multiplyMatrices( a, b ) instead.' );
+                       return this.multiplyMatrices( m, n );
+
+               }
+
+               return this.multiplyMatrices( this, m );
+
+       }
+
+       premultiply( m ) {
+
+               return this.multiplyMatrices( m, this );
+
+       }
+
+       multiplyMatrices( a, b ) {
+
+               const ae = a.elements;
+               const be = b.elements;
+               const te = this.elements;
+
+               const a11 = ae[ 0 ], a12 = ae[ 4 ], a13 = ae[ 8 ], a14 = ae[ 12 ];
+               const a21 = ae[ 1 ], a22 = ae[ 5 ], a23 = ae[ 9 ], a24 = ae[ 13 ];
+               const a31 = ae[ 2 ], a32 = ae[ 6 ], a33 = ae[ 10 ], a34 = ae[ 14 ];
+               const a41 = ae[ 3 ], a42 = ae[ 7 ], a43 = ae[ 11 ], a44 = ae[ 15 ];
+
+               const b11 = be[ 0 ], b12 = be[ 4 ], b13 = be[ 8 ], b14 = be[ 12 ];
+               const b21 = be[ 1 ], b22 = be[ 5 ], b23 = be[ 9 ], b24 = be[ 13 ];
+               const b31 = be[ 2 ], b32 = be[ 6 ], b33 = be[ 10 ], b34 = be[ 14 ];
+               const b41 = be[ 3 ], b42 = be[ 7 ], b43 = be[ 11 ], b44 = be[ 15 ];
+
+               te[ 0 ] = a11 * b11 + a12 * b21 + a13 * b31 + a14 * b41;
+               te[ 4 ] = a11 * b12 + a12 * b22 + a13 * b32 + a14 * b42;
+               te[ 8 ] = a11 * b13 + a12 * b23 + a13 * b33 + a14 * b43;
+               te[ 12 ] = a11 * b14 + a12 * b24 + a13 * b34 + a14 * b44;
+
+               te[ 1 ] = a21 * b11 + a22 * b21 + a23 * b31 + a24 * b41;
+               te[ 5 ] = a21 * b12 + a22 * b22 + a23 * b32 + a24 * b42;
+               te[ 9 ] = a21 * b13 + a22 * b23 + a23 * b33 + a24 * b43;
+               te[ 13 ] = a21 * b14 + a22 * b24 + a23 * b34 + a24 * b44;
+
+               te[ 2 ] = a31 * b11 + a32 * b21 + a33 * b31 + a34 * b41;
+               te[ 6 ] = a31 * b12 + a32 * b22 + a33 * b32 + a34 * b42;
+               te[ 10 ] = a31 * b13 + a32 * b23 + a33 * b33 + a34 * b43;
+               te[ 14 ] = a31 * b14 + a32 * b24 + a33 * b34 + a34 * b44;
+
+               te[ 3 ] = a41 * b11 + a42 * b21 + a43 * b31 + a44 * b41;
+               te[ 7 ] = a41 * b12 + a42 * b22 + a43 * b32 + a44 * b42;
+               te[ 11 ] = a41 * b13 + a42 * b23 + a43 * b33 + a44 * b43;
+               te[ 15 ] = a41 * b14 + a42 * b24 + a43 * b34 + a44 * b44;
+
+               return this;
+
+       }
+
+       multiplyScalar( s ) {
+
+               const te = this.elements;
+
+               te[ 0 ] *= s; te[ 4 ] *= s; te[ 8 ] *= s; te[ 12 ] *= s;
+               te[ 1 ] *= s; te[ 5 ] *= s; te[ 9 ] *= s; te[ 13 ] *= s;
+               te[ 2 ] *= s; te[ 6 ] *= s; te[ 10 ] *= s; te[ 14 ] *= s;
+               te[ 3 ] *= s; te[ 7 ] *= s; te[ 11 ] *= s; te[ 15 ] *= s;
+
+               return this;
+
+       }
+
+       determinant() {
+
+               const te = this.elements;
+
+               const n11 = te[ 0 ], n12 = te[ 4 ], n13 = te[ 8 ], n14 = te[ 12 ];
+               const n21 = te[ 1 ], n22 = te[ 5 ], n23 = te[ 9 ], n24 = te[ 13 ];
+               const n31 = te[ 2 ], n32 = te[ 6 ], n33 = te[ 10 ], n34 = te[ 14 ];
+               const n41 = te[ 3 ], n42 = te[ 7 ], n43 = te[ 11 ], n44 = te[ 15 ];
+
+               //TODO: make this more efficient
+               //( based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm )
+
+               return (
+                       n41 * (
+                               + n14 * n23 * n32
+                                - n13 * n24 * n32
+                                - n14 * n22 * n33
+                                + n12 * n24 * n33
+                                + n13 * n22 * n34
+                                - n12 * n23 * n34
+                       ) +
+                       n42 * (
+                               + n11 * n23 * n34
+                                - n11 * n24 * n33
+                                + n14 * n21 * n33
+                                - n13 * n21 * n34
+                                + n13 * n24 * n31
+                                - n14 * n23 * n31
+                       ) +
+                       n43 * (
+                               + n11 * n24 * n32
+                                - n11 * n22 * n34
+                                - n14 * n21 * n32
+                                + n12 * n21 * n34
+                                + n14 * n22 * n31
+                                - n12 * n24 * n31
+                       ) +
+                       n44 * (
+                               - n13 * n22 * n31
+                                - n11 * n23 * n32
+                                + n11 * n22 * n33
+                                + n13 * n21 * n32
+                                - n12 * n21 * n33
+                                + n12 * n23 * n31
+                       )
+
+               );
+
+       }
+
+       transpose() {
+
+               const te = this.elements;
+               let tmp;
+
+               tmp = te[ 1 ]; te[ 1 ] = te[ 4 ]; te[ 4 ] = tmp;
+               tmp = te[ 2 ]; te[ 2 ] = te[ 8 ]; te[ 8 ] = tmp;
+               tmp = te[ 6 ]; te[ 6 ] = te[ 9 ]; te[ 9 ] = tmp;
+
+               tmp = te[ 3 ]; te[ 3 ] = te[ 12 ]; te[ 12 ] = tmp;
+               tmp = te[ 7 ]; te[ 7 ] = te[ 13 ]; te[ 13 ] = tmp;
+               tmp = te[ 11 ]; te[ 11 ] = te[ 14 ]; te[ 14 ] = tmp;
+
+               return this;
+
+       }
+
+       setPosition( x, y, z ) {
+
+               const te = this.elements;
+
+               if ( x.isVector3 ) {
+
+                       te[ 12 ] = x.x;
+                       te[ 13 ] = x.y;
+                       te[ 14 ] = x.z;
+
+               } else {
+
+                       te[ 12 ] = x;
+                       te[ 13 ] = y;
+                       te[ 14 ] = z;
+
+               }
+
+               return this;
+
+       }
+
+       invert() {
+
+               // based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm
+               const te = this.elements,
+
+                       n11 = te[ 0 ], n21 = te[ 1 ], n31 = te[ 2 ], n41 = te[ 3 ],
+                       n12 = te[ 4 ], n22 = te[ 5 ], n32 = te[ 6 ], n42 = te[ 7 ],
+                       n13 = te[ 8 ], n23 = te[ 9 ], n33 = te[ 10 ], n43 = te[ 11 ],
+                       n14 = te[ 12 ], n24 = te[ 13 ], n34 = te[ 14 ], n44 = te[ 15 ],
+
+                       t11 = n23 * n34 * n42 - n24 * n33 * n42 + n24 * n32 * n43 - n22 * n34 * n43 - n23 * n32 * n44 + n22 * n33 * n44,
+                       t12 = n14 * n33 * n42 - n13 * n34 * n42 - n14 * n32 * n43 + n12 * n34 * n43 + n13 * n32 * n44 - n12 * n33 * n44,
+                       t13 = n13 * n24 * n42 - n14 * n23 * n42 + n14 * n22 * n43 - n12 * n24 * n43 - n13 * n22 * n44 + n12 * n23 * n44,
+                       t14 = n14 * n23 * n32 - n13 * n24 * n32 - n14 * n22 * n33 + n12 * n24 * n33 + n13 * n22 * n34 - n12 * n23 * n34;
+
+               const det = n11 * t11 + n21 * t12 + n31 * t13 + n41 * t14;
+
+               if ( det === 0 ) return this.set( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 );
+
+               const detInv = 1 / det;
+
+               te[ 0 ] = t11 * detInv;
+               te[ 1 ] = ( n24 * n33 * n41 - n23 * n34 * n41 - n24 * n31 * n43 + n21 * n34 * n43 + n23 * n31 * n44 - n21 * n33 * n44 ) * detInv;
+               te[ 2 ] = ( n22 * n34 * n41 - n24 * n32 * n41 + n24 * n31 * n42 - n21 * n34 * n42 - n22 * n31 * n44 + n21 * n32 * n44 ) * detInv;
+               te[ 3 ] = ( n23 * n32 * n41 - n22 * n33 * n41 - n23 * n31 * n42 + n21 * n33 * n42 + n22 * n31 * n43 - n21 * n32 * n43 ) * detInv;
+
+               te[ 4 ] = t12 * detInv;
+               te[ 5 ] = ( n13 * n34 * n41 - n14 * n33 * n41 + n14 * n31 * n43 - n11 * n34 * n43 - n13 * n31 * n44 + n11 * n33 * n44 ) * detInv;
+               te[ 6 ] = ( n14 * n32 * n41 - n12 * n34 * n41 - n14 * n31 * n42 + n11 * n34 * n42 + n12 * n31 * n44 - n11 * n32 * n44 ) * detInv;
+               te[ 7 ] = ( n12 * n33 * n41 - n13 * n32 * n41 + n13 * n31 * n42 - n11 * n33 * n42 - n12 * n31 * n43 + n11 * n32 * n43 ) * detInv;
+
+               te[ 8 ] = t13 * detInv;
+               te[ 9 ] = ( n14 * n23 * n41 - n13 * n24 * n41 - n14 * n21 * n43 + n11 * n24 * n43 + n13 * n21 * n44 - n11 * n23 * n44 ) * detInv;
+               te[ 10 ] = ( n12 * n24 * n41 - n14 * n22 * n41 + n14 * n21 * n42 - n11 * n24 * n42 - n12 * n21 * n44 + n11 * n22 * n44 ) * detInv;
+               te[ 11 ] = ( n13 * n22 * n41 - n12 * n23 * n41 - n13 * n21 * n42 + n11 * n23 * n42 + n12 * n21 * n43 - n11 * n22 * n43 ) * detInv;
+
+               te[ 12 ] = t14 * detInv;
+               te[ 13 ] = ( n13 * n24 * n31 - n14 * n23 * n31 + n14 * n21 * n33 - n11 * n24 * n33 - n13 * n21 * n34 + n11 * n23 * n34 ) * detInv;
+               te[ 14 ] = ( n14 * n22 * n31 - n12 * n24 * n31 - n14 * n21 * n32 + n11 * n24 * n32 + n12 * n21 * n34 - n11 * n22 * n34 ) * detInv;
+               te[ 15 ] = ( n12 * n23 * n31 - n13 * n22 * n31 + n13 * n21 * n32 - n11 * n23 * n32 - n12 * n21 * n33 + n11 * n22 * n33 ) * detInv;
+
+               return this;
+
+       }
+
+       scale( v ) {
+
+               const te = this.elements;
+               const x = v.x, y = v.y, z = v.z;
+
+               te[ 0 ] *= x; te[ 4 ] *= y; te[ 8 ] *= z;
+               te[ 1 ] *= x; te[ 5 ] *= y; te[ 9 ] *= z;
+               te[ 2 ] *= x; te[ 6 ] *= y; te[ 10 ] *= z;
+               te[ 3 ] *= x; te[ 7 ] *= y; te[ 11 ] *= z;
+
+               return this;
+
+       }
+
+       getMaxScaleOnAxis() {
+
+               const te = this.elements;
+
+               const scaleXSq = te[ 0 ] * te[ 0 ] + te[ 1 ] * te[ 1 ] + te[ 2 ] * te[ 2 ];
+               const scaleYSq = te[ 4 ] * te[ 4 ] + te[ 5 ] * te[ 5 ] + te[ 6 ] * te[ 6 ];
+               const scaleZSq = te[ 8 ] * te[ 8 ] + te[ 9 ] * te[ 9 ] + te[ 10 ] * te[ 10 ];
+
+               return Math.sqrt( Math.max( scaleXSq, scaleYSq, scaleZSq ) );
+
+       }
+
+       makeTranslation( x, y, z ) {
+
+               this.set(
+
+                       1, 0, 0, x,
+                       0, 1, 0, y,
+                       0, 0, 1, z,
+                       0, 0, 0, 1
+
+               );
+
+               return this;
+
+       }
+
+       makeRotationX( theta ) {
+
+               const c = Math.cos( theta ), s = Math.sin( theta );
+
+               this.set(
+
+                       1, 0, 0, 0,
+                       0, c, - s, 0,
+                       0, s, c, 0,
+                       0, 0, 0, 1
+
+               );
+
+               return this;
+
+       }
+
+       makeRotationY( theta ) {
+
+               const c = Math.cos( theta ), s = Math.sin( theta );
+
+               this.set(
+
+                        c, 0, s, 0,
+                        0, 1, 0, 0,
+                       - s, 0, c, 0,
+                        0, 0, 0, 1
+
+               );
+
+               return this;
+
+       }
+
+       makeRotationZ( theta ) {
+
+               const c = Math.cos( theta ), s = Math.sin( theta );
+
+               this.set(
+
+                       c, - s, 0, 0,
+                       s, c, 0, 0,
+                       0, 0, 1, 0,
+                       0, 0, 0, 1
+
+               );
+
+               return this;
+
+       }
+
+       makeRotationAxis( axis, angle ) {
+
+               // Based on http://www.gamedev.net/reference/articles/article1199.asp
+
+               const c = Math.cos( angle );
+               const s = Math.sin( angle );
+               const t = 1 - c;
+               const x = axis.x, y = axis.y, z = axis.z;
+               const tx = t * x, ty = t * y;
+
+               this.set(
+
+                       tx * x + c, tx * y - s * z, tx * z + s * y, 0,
+                       tx * y + s * z, ty * y + c, ty * z - s * x, 0,
+                       tx * z - s * y, ty * z + s * x, t * z * z + c, 0,
+                       0, 0, 0, 1
+
+               );
+
+               return this;
+
+       }
+
+       makeScale( x, y, z ) {
+
+               this.set(
+
+                       x, 0, 0, 0,
+                       0, y, 0, 0,
+                       0, 0, z, 0,
+                       0, 0, 0, 1
+
+               );
+
+               return this;
+
+       }
+
+       makeShear( x, y, z ) {
+
+               this.set(
+
+                       1, y, z, 0,
+                       x, 1, z, 0,
+                       x, y, 1, 0,
+                       0, 0, 0, 1
+
+               );
+
+               return this;
+
+       }
+
+       compose( position, quaternion, scale ) {
+
+               const te = this.elements;
+
+               const x = quaternion._x, y = quaternion._y, z = quaternion._z, w = quaternion._w;
+               const x2 = x + x,       y2 = y + y, z2 = z + z;
+               const xx = x * x2, xy = x * y2, xz = x * z2;
+               const yy = y * y2, yz = y * z2, zz = z * z2;
+               const wx = w * x2, wy = w * y2, wz = w * z2;
+
+               const sx = scale.x, sy = scale.y, sz = scale.z;
+
+               te[ 0 ] = ( 1 - ( yy + zz ) ) * sx;
+               te[ 1 ] = ( xy + wz ) * sx;
+               te[ 2 ] = ( xz - wy ) * sx;
+               te[ 3 ] = 0;
+
+               te[ 4 ] = ( xy - wz ) * sy;
+               te[ 5 ] = ( 1 - ( xx + zz ) ) * sy;
+               te[ 6 ] = ( yz + wx ) * sy;
+               te[ 7 ] = 0;
+
+               te[ 8 ] = ( xz + wy ) * sz;
+               te[ 9 ] = ( yz - wx ) * sz;
+               te[ 10 ] = ( 1 - ( xx + yy ) ) * sz;
+               te[ 11 ] = 0;
+
+               te[ 12 ] = position.x;
+               te[ 13 ] = position.y;
+               te[ 14 ] = position.z;
+               te[ 15 ] = 1;
+
+               return this;
+
+       }
+
+       decompose( position, quaternion, scale ) {
+
+               const te = this.elements;
+
+               let sx = _v1$1.set( te[ 0 ], te[ 1 ], te[ 2 ] ).length();
+               const sy = _v1$1.set( te[ 4 ], te[ 5 ], te[ 6 ] ).length();
+               const sz = _v1$1.set( te[ 8 ], te[ 9 ], te[ 10 ] ).length();
+
+               // if determine is negative, we need to invert one scale
+               const det = this.determinant();
+               if ( det < 0 ) sx = - sx;
+
+               position.x = te[ 12 ];
+               position.y = te[ 13 ];
+               position.z = te[ 14 ];
+
+               // scale the rotation part
+               _m1.copy( this );
+
+               const invSX = 1 / sx;
+               const invSY = 1 / sy;
+               const invSZ = 1 / sz;
+
+               _m1.elements[ 0 ] *= invSX;
+               _m1.elements[ 1 ] *= invSX;
+               _m1.elements[ 2 ] *= invSX;
+
+               _m1.elements[ 4 ] *= invSY;
+               _m1.elements[ 5 ] *= invSY;
+               _m1.elements[ 6 ] *= invSY;
+
+               _m1.elements[ 8 ] *= invSZ;
+               _m1.elements[ 9 ] *= invSZ;
+               _m1.elements[ 10 ] *= invSZ;
+
+               quaternion.setFromRotationMatrix( _m1 );
+
+               scale.x = sx;
+               scale.y = sy;
+               scale.z = sz;
+
+               return this;
+
+       }
+
+       makePerspective( left, right, top, bottom, near, far ) {
+
+               if ( far === undefined ) {
+
+                       console.warn( 'THREE.Matrix4: .makePerspective() has been redefined and has a new signature. Please check the docs.' );
+
+               }
+
+               const te = this.elements;
+               const x = 2 * near / ( right - left );
+               const y = 2 * near / ( top - bottom );
+
+               const a = ( right + left ) / ( right - left );
+               const b = ( top + bottom ) / ( top - bottom );
+               const c = - ( far + near ) / ( far - near );
+               const d = - 2 * far * near / ( far - near );
+
+               te[ 0 ] = x;    te[ 4 ] = 0;    te[ 8 ] = a;    te[ 12 ] = 0;
+               te[ 1 ] = 0;    te[ 5 ] = y;    te[ 9 ] = b;    te[ 13 ] = 0;
+               te[ 2 ] = 0;    te[ 6 ] = 0;    te[ 10 ] = c;   te[ 14 ] = d;
+               te[ 3 ] = 0;    te[ 7 ] = 0;    te[ 11 ] = - 1; te[ 15 ] = 0;
+
+               return this;
+
+       }
+
+       makeOrthographic( left, right, top, bottom, near, far ) {
+
+               const te = this.elements;
+               const w = 1.0 / ( right - left );
+               const h = 1.0 / ( top - bottom );
+               const p = 1.0 / ( far - near );
+
+               const x = ( right + left ) * w;
+               const y = ( top + bottom ) * h;
+               const z = ( far + near ) * p;
+
+               te[ 0 ] = 2 * w;        te[ 4 ] = 0;    te[ 8 ] = 0;    te[ 12 ] = - x;
+               te[ 1 ] = 0;    te[ 5 ] = 2 * h;        te[ 9 ] = 0;    te[ 13 ] = - y;
+               te[ 2 ] = 0;    te[ 6 ] = 0;    te[ 10 ] = - 2 * p;     te[ 14 ] = - z;
+               te[ 3 ] = 0;    te[ 7 ] = 0;    te[ 11 ] = 0;   te[ 15 ] = 1;
+
+               return this;
+
+       }
+
+       equals( matrix ) {
+
+               const te = this.elements;
+               const me = matrix.elements;
+
+               for ( let i = 0; i < 16; i ++ ) {
+
+                       if ( te[ i ] !== me[ i ] ) return false;
+
+               }
+
+               return true;
+
+       }
+
+       fromArray( array, offset = 0 ) {
+
+               for ( let i = 0; i < 16; i ++ ) {
+
+                       this.elements[ i ] = array[ i + offset ];
+
+               }
+
+               return this;
+
+       }
+
+       toArray( array = [], offset = 0 ) {
+
+               const te = this.elements;
+
+               array[ offset ] = te[ 0 ];
+               array[ offset + 1 ] = te[ 1 ];
+               array[ offset + 2 ] = te[ 2 ];
+               array[ offset + 3 ] = te[ 3 ];
+
+               array[ offset + 4 ] = te[ 4 ];
+               array[ offset + 5 ] = te[ 5 ];
+               array[ offset + 6 ] = te[ 6 ];
+               array[ offset + 7 ] = te[ 7 ];
+
+               array[ offset + 8 ] = te[ 8 ];
+               array[ offset + 9 ] = te[ 9 ];
+               array[ offset + 10 ] = te[ 10 ];
+               array[ offset + 11 ] = te[ 11 ];
+
+               array[ offset + 12 ] = te[ 12 ];
+               array[ offset + 13 ] = te[ 13 ];
+               array[ offset + 14 ] = te[ 14 ];
+               array[ offset + 15 ] = te[ 15 ];
+
+               return array;
+
+       }
+
+}
+
+const _v1$1 = /*@__PURE__*/ new Vector3();
+const _m1 = /*@__PURE__*/ new Matrix4();
+const _zero = /*@__PURE__*/ new Vector3( 0, 0, 0 );
+const _one = /*@__PURE__*/ new Vector3( 1, 1, 1 );
+const _x = /*@__PURE__*/ new Vector3();
+const _y = /*@__PURE__*/ new Vector3();
+const _z = /*@__PURE__*/ new Vector3();
+
+class Euler {
+
+       constructor( x = 0, y = 0, z = 0, order = Euler.DefaultOrder ) {
+
+               Object.defineProperty( this, 'isEuler', { value: true } );
+
+               this._x = x;
+               this._y = y;
+               this._z = z;
+               this._order = order;
+
+       }
+
+       get x() {
+
+               return this._x;
+
+       }
+
+       set x( value ) {
+
+               this._x = value;
+               this._onChangeCallback();
+
+       }
+
+       get y() {
+
+               return this._y;
+
+       }
+
+       set y( value ) {
+
+               this._y = value;
+               this._onChangeCallback();
+
+       }
+
+       get z() {
+
+               return this._z;
+
+       }
+
+       set z( value ) {
+
+               this._z = value;
+               this._onChangeCallback();
+
+       }
+
+       get order() {
+
+               return this._order;
+
+       }
+
+       set order( value ) {
+
+               this._order = value;
+               this._onChangeCallback();
+
+       }
+
+       set( x, y, z, order ) {
+
+               this._x = x;
+               this._y = y;
+               this._z = z;
+               this._order = order || this._order;
+
+               this._onChangeCallback();
+
+               return this;
+
+       }
+
+       clone() {
+
+               return new this.constructor( this._x, this._y, this._z, this._order );
+
+       }
+
+       copy( euler ) {
+
+               this._x = euler._x;
+               this._y = euler._y;
+               this._z = euler._z;
+               this._order = euler._order;
+
+               this._onChangeCallback();
+
+               return this;
+
+       }
+
+       setFromRotationMatrix( m, order, update ) {
+
+               const clamp = MathUtils.clamp;
+
+               // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
+
+               const te = m.elements;
+               const m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ];
+               const m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ];
+               const m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ];
+
+               order = order || this._order;
+
+               switch ( order ) {
+
+                       case 'XYZ':
+
+                               this._y = Math.asin( clamp( m13, - 1, 1 ) );
+
+                               if ( Math.abs( m13 ) < 0.9999999 ) {
+
+                                       this._x = Math.atan2( - m23, m33 );
+                                       this._z = Math.atan2( - m12, m11 );
+
+                               } else {
+
+                                       this._x = Math.atan2( m32, m22 );
+                                       this._z = 0;
+
+                               }
+
+                               break;
+
+                       case 'YXZ':
+
+                               this._x = Math.asin( - clamp( m23, - 1, 1 ) );
+
+                               if ( Math.abs( m23 ) < 0.9999999 ) {
+
+                                       this._y = Math.atan2( m13, m33 );
+                                       this._z = Math.atan2( m21, m22 );
+
+                               } else {
+
+                                       this._y = Math.atan2( - m31, m11 );
+                                       this._z = 0;
+
+                               }
+
+                               break;
+
+                       case 'ZXY':
+
+                               this._x = Math.asin( clamp( m32, - 1, 1 ) );
+
+                               if ( Math.abs( m32 ) < 0.9999999 ) {
+
+                                       this._y = Math.atan2( - m31, m33 );
+                                       this._z = Math.atan2( - m12, m22 );
+
+                               } else {
+
+                                       this._y = 0;
+                                       this._z = Math.atan2( m21, m11 );
+
+                               }
+
+                               break;
+
+                       case 'ZYX':
+
+                               this._y = Math.asin( - clamp( m31, - 1, 1 ) );
+
+                               if ( Math.abs( m31 ) < 0.9999999 ) {
+
+                                       this._x = Math.atan2( m32, m33 );
+                                       this._z = Math.atan2( m21, m11 );
+
+                               } else {
+
+                                       this._x = 0;
+                                       this._z = Math.atan2( - m12, m22 );
+
+                               }
+
+                               break;
+
+                       case 'YZX':
+
+                               this._z = Math.asin( clamp( m21, - 1, 1 ) );
+
+                               if ( Math.abs( m21 ) < 0.9999999 ) {
+
+                                       this._x = Math.atan2( - m23, m22 );
+                                       this._y = Math.atan2( - m31, m11 );
+
+                               } else {
+
+                                       this._x = 0;
+                                       this._y = Math.atan2( m13, m33 );
+
+                               }
+
+                               break;
+
+                       case 'XZY':
+
+                               this._z = Math.asin( - clamp( m12, - 1, 1 ) );
+
+                               if ( Math.abs( m12 ) < 0.9999999 ) {
+
+                                       this._x = Math.atan2( m32, m22 );
+                                       this._y = Math.atan2( m13, m11 );
+
+                               } else {
+
+                                       this._x = Math.atan2( - m23, m33 );
+                                       this._y = 0;
+
+                               }
+
+                               break;
+
+                       default:
+
+                               console.warn( 'THREE.Euler: .setFromRotationMatrix() encountered an unknown order: ' + order );
+
+               }
+
+               this._order = order;
+
+               if ( update !== false ) this._onChangeCallback();
+
+               return this;
+
+       }
+
+       setFromQuaternion( q, order, update ) {
+
+               _matrix.makeRotationFromQuaternion( q );
+
+               return this.setFromRotationMatrix( _matrix, order, update );
+
+       }
+
+       setFromVector3( v, order ) {
+
+               return this.set( v.x, v.y, v.z, order || this._order );
+
+       }
+
+       reorder( newOrder ) {
+
+               // WARNING: this discards revolution information -bhouston
+
+               _quaternion$1.setFromEuler( this );
+
+               return this.setFromQuaternion( _quaternion$1, newOrder );
+
+       }
+
+       equals( euler ) {
+
+               return ( euler._x === this._x ) && ( euler._y === this._y ) && ( euler._z === this._z ) && ( euler._order === this._order );
+
+       }
+
+       fromArray( array ) {
+
+               this._x = array[ 0 ];
+               this._y = array[ 1 ];
+               this._z = array[ 2 ];
+               if ( array[ 3 ] !== undefined ) this._order = array[ 3 ];
+
+               this._onChangeCallback();
+
+               return this;
+
+       }
+
+       toArray( array = [], offset = 0 ) {
+
+               array[ offset ] = this._x;
+               array[ offset + 1 ] = this._y;
+               array[ offset + 2 ] = this._z;
+               array[ offset + 3 ] = this._order;
+
+               return array;
+
+       }
+
+       toVector3( optionalResult ) {
+
+               if ( optionalResult ) {
+
+                       return optionalResult.set( this._x, this._y, this._z );
+
+               } else {
+
+                       return new Vector3( this._x, this._y, this._z );
+
+               }
+
+       }
+
+       _onChange( callback ) {
+
+               this._onChangeCallback = callback;
+
+               return this;
+
+       }
+
+       _onChangeCallback() {}
+
+}
+
+Euler.DefaultOrder = 'XYZ';
+Euler.RotationOrders = [ 'XYZ', 'YZX', 'ZXY', 'XZY', 'YXZ', 'ZYX' ];
+
+const _matrix = /*@__PURE__*/ new Matrix4();
+const _quaternion$1 = /*@__PURE__*/ new Quaternion();
+
+class Layers {
+
+       constructor() {
+
+               this.mask = 1 | 0;
+
+       }
+
+       set( channel ) {
+
+               this.mask = 1 << channel | 0;
+
+       }
+
+       enable( channel ) {
+
+               this.mask |= 1 << channel | 0;
+
+       }
+
+       enableAll() {
+
+               this.mask = 0xffffffff | 0;
+
+       }
+
+       toggle( channel ) {
+
+               this.mask ^= 1 << channel | 0;
+
+       }
+
+       disable( channel ) {
+
+               this.mask &= ~ ( 1 << channel | 0 );
+
+       }
+
+       disableAll() {
+
+               this.mask = 0;
+
+       }
+
+       test( layers ) {
+
+               return ( this.mask & layers.mask ) !== 0;
+
+       }
+
+}
+
+let _object3DId = 0;
+
+const _v1$2 = new Vector3();
+const _q1 = new Quaternion();
+const _m1$1 = new Matrix4();
+const _target = new Vector3();
+
+const _position = new Vector3();
+const _scale = new Vector3();
+const _quaternion$2 = new Quaternion();
+
+const _xAxis = new Vector3( 1, 0, 0 );
+const _yAxis = new Vector3( 0, 1, 0 );
+const _zAxis = new Vector3( 0, 0, 1 );
+
+const _addedEvent = { type: 'added' };
+const _removedEvent = { type: 'removed' };
+
+function Object3D() {
+
+       Object.defineProperty( this, 'id', { value: _object3DId ++ } );
+
+       this.uuid = MathUtils.generateUUID();
+
+       this.name = '';
+       this.type = 'Object3D';
+
+       this.parent = null;
+       this.children = [];
+
+       this.up = Object3D.DefaultUp.clone();
+
+       const position = new Vector3();
+       const rotation = new Euler();
+       const quaternion = new Quaternion();
+       const scale = new Vector3( 1, 1, 1 );
+
+       function onRotationChange() {
+
+               quaternion.setFromEuler( rotation, false );
+
+       }
+
+       function onQuaternionChange() {
+
+               rotation.setFromQuaternion( quaternion, undefined, false );
+
+       }
+
+       rotation._onChange( onRotationChange );
+       quaternion._onChange( onQuaternionChange );
+
+       Object.defineProperties( this, {
+               position: {
+                       configurable: true,
+                       enumerable: true,
+                       value: position
+               },
+               rotation: {
+                       configurable: true,
+                       enumerable: true,
+                       value: rotation
+               },
+               quaternion: {
+                       configurable: true,
+                       enumerable: true,
+                       value: quaternion
+               },
+               scale: {
+                       configurable: true,
+                       enumerable: true,
+                       value: scale
+               },
+               modelViewMatrix: {
+                       value: new Matrix4()
+               },
+               normalMatrix: {
+                       value: new Matrix3()
+               }
+       } );
+
+       this.matrix = new Matrix4();
+       this.matrixWorld = new Matrix4();
+
+       this.matrixAutoUpdate = Object3D.DefaultMatrixAutoUpdate;
+       this.matrixWorldNeedsUpdate = false;
+
+       this.layers = new Layers();
+       this.visible = true;
+
+       this.castShadow = false;
+       this.receiveShadow = false;
+
+       this.frustumCulled = true;
+       this.renderOrder = 0;
+
+       this.animations = [];
+
+       this.userData = {};
+
+}
+
+Object3D.DefaultUp = new Vector3( 0, 1, 0 );
+Object3D.DefaultMatrixAutoUpdate = true;
+
+Object3D.prototype = Object.assign( Object.create( EventDispatcher.prototype ), {
+
+       constructor: Object3D,
+
+       isObject3D: true,
+
+       onBeforeRender: function () {},
+       onAfterRender: function () {},
+
+       applyMatrix4: function ( matrix ) {
+
+               if ( this.matrixAutoUpdate ) this.updateMatrix();
+
+               this.matrix.premultiply( matrix );
+
+               this.matrix.decompose( this.position, this.quaternion, this.scale );
+
+       },
+
+       applyQuaternion: function ( q ) {
+
+               this.quaternion.premultiply( q );
+
+               return this;
+
+       },
+
+       setRotationFromAxisAngle: function ( axis, angle ) {
+
+               // assumes axis is normalized
+
+               this.quaternion.setFromAxisAngle( axis, angle );
+
+       },
+
+       setRotationFromEuler: function ( euler ) {
+
+               this.quaternion.setFromEuler( euler, true );
+
+       },
+
+       setRotationFromMatrix: function ( m ) {
+
+               // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
+
+               this.quaternion.setFromRotationMatrix( m );
+
+       },
+
+       setRotationFromQuaternion: function ( q ) {
+
+               // assumes q is normalized
+
+               this.quaternion.copy( q );
+
+       },
+
+       rotateOnAxis: function ( axis, angle ) {
+
+               // rotate object on axis in object space
+               // axis is assumed to be normalized
+
+               _q1.setFromAxisAngle( axis, angle );
+
+               this.quaternion.multiply( _q1 );
+
+               return this;
+
+       },
+
+       rotateOnWorldAxis: function ( axis, angle ) {
+
+               // rotate object on axis in world space
+               // axis is assumed to be normalized
+               // method assumes no rotated parent
+
+               _q1.setFromAxisAngle( axis, angle );
+
+               this.quaternion.premultiply( _q1 );
+
+               return this;
+
+       },
+
+       rotateX: function ( angle ) {
+
+               return this.rotateOnAxis( _xAxis, angle );
+
+       },
+
+       rotateY: function ( angle ) {
+
+               return this.rotateOnAxis( _yAxis, angle );
+
+       },
+
+       rotateZ: function ( angle ) {
+
+               return this.rotateOnAxis( _zAxis, angle );
+
+       },
+
+       translateOnAxis: function ( axis, distance ) {
+
+               // translate object by distance along axis in object space
+               // axis is assumed to be normalized
+
+               _v1$2.copy( axis ).applyQuaternion( this.quaternion );
+
+               this.position.add( _v1$2.multiplyScalar( distance ) );
+
+               return this;
+
+       },
+
+       translateX: function ( distance ) {
+
+               return this.translateOnAxis( _xAxis, distance );
+
+       },
+
+       translateY: function ( distance ) {
+
+               return this.translateOnAxis( _yAxis, distance );
+
+       },
+
+       translateZ: function ( distance ) {
+
+               return this.translateOnAxis( _zAxis, distance );
+
+       },
+
+       localToWorld: function ( vector ) {
+
+               return vector.applyMatrix4( this.matrixWorld );
+
+       },
+
+       worldToLocal: function ( vector ) {
+
+               return vector.applyMatrix4( _m1$1.copy( this.matrixWorld ).invert() );
+
+       },
+
+       lookAt: function ( x, y, z ) {
+
+               // This method does not support objects having non-uniformly-scaled parent(s)
+
+               if ( x.isVector3 ) {
+
+                       _target.copy( x );
+
+               } else {
+
+                       _target.set( x, y, z );
+
+               }
+
+               const parent = this.parent;
+
+               this.updateWorldMatrix( true, false );
+
+               _position.setFromMatrixPosition( this.matrixWorld );
+
+               if ( this.isCamera || this.isLight ) {
+
+                       _m1$1.lookAt( _position, _target, this.up );
+
+               } else {
+
+                       _m1$1.lookAt( _target, _position, this.up );
+
+               }
+
+               this.quaternion.setFromRotationMatrix( _m1$1 );
+
+               if ( parent ) {
+
+                       _m1$1.extractRotation( parent.matrixWorld );
+                       _q1.setFromRotationMatrix( _m1$1 );
+                       this.quaternion.premultiply( _q1.invert() );
+
+               }
+
+       },
+
+       add: function ( object ) {
+
+               if ( arguments.length > 1 ) {
+
+                       for ( let i = 0; i < arguments.length; i ++ ) {
+
+                               this.add( arguments[ i ] );
+
+                       }
+
+                       return this;
+
+               }
+
+               if ( object === this ) {
+
+                       console.error( 'THREE.Object3D.add: object can\'t be added as a child of itself.', object );
+                       return this;
+
+               }
+
+               if ( object && object.isObject3D ) {
+
+                       if ( object.parent !== null ) {
+
+                               object.parent.remove( object );
+
+                       }
+
+                       object.parent = this;
+                       this.children.push( object );
+
+                       object.dispatchEvent( _addedEvent );
+
+               } else {
+
+                       console.error( 'THREE.Object3D.add: object not an instance of THREE.Object3D.', object );
+
+               }
+
+               return this;
+
+       },
+
+       remove: function ( object ) {
+
+               if ( arguments.length > 1 ) {
+
+                       for ( let i = 0; i < arguments.length; i ++ ) {
+
+                               this.remove( arguments[ i ] );
+
+                       }
+
+                       return this;
+
+               }
+
+               const index = this.children.indexOf( object );
+
+               if ( index !== - 1 ) {
+
+                       object.parent = null;
+                       this.children.splice( index, 1 );
+
+                       object.dispatchEvent( _removedEvent );
+
+               }
+
+               return this;
+
+       },
+
+       clear: function () {
+
+               for ( let i = 0; i < this.children.length; i ++ ) {
+
+                       const object = this.children[ i ];
+
+                       object.parent = null;
+
+                       object.dispatchEvent( _removedEvent );
+
+               }
+
+               this.children.length = 0;
+
+               return this;
+
+
+       },
+
+       attach: function ( object ) {
+
+               // adds object as a child of this, while maintaining the object's world transform
+
+               this.updateWorldMatrix( true, false );
+
+               _m1$1.copy( this.matrixWorld ).invert();
+
+               if ( object.parent !== null ) {
+
+                       object.parent.updateWorldMatrix( true, false );
+
+                       _m1$1.multiply( object.parent.matrixWorld );
+
+               }
+
+               object.applyMatrix4( _m1$1 );
+
+               object.updateWorldMatrix( false, false );
+
+               this.add( object );
+
+               return this;
+
+       },
+
+       getObjectById: function ( id ) {
+
+               return this.getObjectByProperty( 'id', id );
+
+       },
+
+       getObjectByName: function ( name ) {
+
+               return this.getObjectByProperty( 'name', name );
+
+       },
+
+       getObjectByProperty: function ( name, value ) {
+
+               if ( this[ name ] === value ) return this;
+
+               for ( let i = 0, l = this.children.length; i < l; i ++ ) {
+
+                       const child = this.children[ i ];
+                       const object = child.getObjectByProperty( name, value );
+
+                       if ( object !== undefined ) {
+
+                               return object;
+
+                       }
+
+               }
+
+               return undefined;
+
+       },
+
+       getWorldPosition: function ( target ) {
+
+               if ( target === undefined ) {
+
+                       console.warn( 'THREE.Object3D: .getWorldPosition() target is now required' );
+                       target = new Vector3();
+
+               }
+
+               this.updateWorldMatrix( true, false );
+
+               return target.setFromMatrixPosition( this.matrixWorld );
+
+       },
+
+       getWorldQuaternion: function ( target ) {
+
+               if ( target === undefined ) {
+
+                       console.warn( 'THREE.Object3D: .getWorldQuaternion() target is now required' );
+                       target = new Quaternion();
+
+               }
+
+               this.updateWorldMatrix( true, false );
+
+               this.matrixWorld.decompose( _position, target, _scale );
+
+               return target;
+
+       },
+
+       getWorldScale: function ( target ) {
+
+               if ( target === undefined ) {
+
+                       console.warn( 'THREE.Object3D: .getWorldScale() target is now required' );
+                       target = new Vector3();
+
+               }
+
+               this.updateWorldMatrix( true, false );
+
+               this.matrixWorld.decompose( _position, _quaternion$2, target );
+
+               return target;
+
+       },
+
+       getWorldDirection: function ( target ) {
+
+               if ( target === undefined ) {
+
+                       console.warn( 'THREE.Object3D: .getWorldDirection() target is now required' );
+                       target = new Vector3();
+
+               }
+
+               this.updateWorldMatrix( true, false );
+
+               const e = this.matrixWorld.elements;
+
+               return target.set( e[ 8 ], e[ 9 ], e[ 10 ] ).normalize();
+
+       },
+
+       raycast: function () {},
+
+       traverse: function ( callback ) {
+
+               callback( this );
+
+               const children = this.children;
+
+               for ( let i = 0, l = children.length; i < l; i ++ ) {
+
+                       children[ i ].traverse( callback );
+
+               }
+
+       },
+
+       traverseVisible: function ( callback ) {
+
+               if ( this.visible === false ) return;
+
+               callback( this );
+
+               const children = this.children;
+
+               for ( let i = 0, l = children.length; i < l; i ++ ) {
+
+                       children[ i ].traverseVisible( callback );
+
+               }
+
+       },
+
+       traverseAncestors: function ( callback ) {
+
+               const parent = this.parent;
+
+               if ( parent !== null ) {
+
+                       callback( parent );
+
+                       parent.traverseAncestors( callback );
+
+               }
+
+       },
+
+       updateMatrix: function () {
+
+               this.matrix.compose( this.position, this.quaternion, this.scale );
+
+               this.matrixWorldNeedsUpdate = true;
+
+       },
+
+       updateMatrixWorld: function ( force ) {
+
+               if ( this.matrixAutoUpdate ) this.updateMatrix();
+
+               if ( this.matrixWorldNeedsUpdate || force ) {
+
+                       if ( this.parent === null ) {
+
+                               this.matrixWorld.copy( this.matrix );
+
+                       } else {
+
+                               this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix );
+
+                       }
+
+                       this.matrixWorldNeedsUpdate = false;
+
+                       force = true;
+
+               }
+
+               // update children
+
+               const children = this.children;
+
+               for ( let i = 0, l = children.length; i < l; i ++ ) {
+
+                       children[ i ].updateMatrixWorld( force );
+
+               }
+
+       },
+
+       updateWorldMatrix: function ( updateParents, updateChildren ) {
+
+               const parent = this.parent;
+
+               if ( updateParents === true && parent !== null ) {
+
+                       parent.updateWorldMatrix( true, false );
+
+               }
+
+               if ( this.matrixAutoUpdate ) this.updateMatrix();
+
+               if ( this.parent === null ) {
+
+                       this.matrixWorld.copy( this.matrix );
+
+               } else {
+
+                       this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix );
+
+               }
+
+               // update children
+
+               if ( updateChildren === true ) {
+
+                       const children = this.children;
+
+                       for ( let i = 0, l = children.length; i < l; i ++ ) {
+
+                               children[ i ].updateWorldMatrix( false, true );
+
+                       }
+
+               }
+
+       },
+
+       toJSON: function ( meta ) {
+
+               // meta is a string when called from JSON.stringify
+               const isRootObject = ( meta === undefined || typeof meta === 'string' );
+
+               const output = {};
+
+               // meta is a hash used to collect geometries, materials.
+               // not providing it implies that this is the root object
+               // being serialized.
+               if ( isRootObject ) {
+
+                       // initialize meta obj
+                       meta = {
+                               geometries: {},
+                               materials: {},
+                               textures: {},
+                               images: {},
+                               shapes: {},
+                               skeletons: {},
+                               animations: {}
+                       };
+
+                       output.metadata = {
+                               version: 4.5,
+                               type: 'Object',
+                               generator: 'Object3D.toJSON'
+                       };
+
+               }
+
+               // standard Object3D serialization
+
+               const object = {};
+
+               object.uuid = this.uuid;
+               object.type = this.type;
+
+               if ( this.name !== '' ) object.name = this.name;
+               if ( this.castShadow === true ) object.castShadow = true;
+               if ( this.receiveShadow === true ) object.receiveShadow = true;
+               if ( this.visible === false ) object.visible = false;
+               if ( this.frustumCulled === false ) object.frustumCulled = false;
+               if ( this.renderOrder !== 0 ) object.renderOrder = this.renderOrder;
+               if ( JSON.stringify( this.userData ) !== '{}' ) object.userData = this.userData;
+
+               object.layers = this.layers.mask;
+               object.matrix = this.matrix.toArray();
+
+               if ( this.matrixAutoUpdate === false ) object.matrixAutoUpdate = false;
+
+               // object specific properties
+
+               if ( this.isInstancedMesh ) {
+
+                       object.type = 'InstancedMesh';
+                       object.count = this.count;
+                       object.instanceMatrix = this.instanceMatrix.toJSON();
+
+               }
+
+               //
+
+               function serialize( library, element ) {
+
+                       if ( library[ element.uuid ] === undefined ) {
+
+                               library[ element.uuid ] = element.toJSON( meta );
+
+                       }
+
+                       return element.uuid;
+
+               }
+
+               if ( this.isMesh || this.isLine || this.isPoints ) {
+
+                       object.geometry = serialize( meta.geometries, this.geometry );
+
+                       const parameters = this.geometry.parameters;
+
+                       if ( parameters !== undefined && parameters.shapes !== undefined ) {
+
+                               const shapes = parameters.shapes;
+
+                               if ( Array.isArray( shapes ) ) {
+
+                                       for ( let i = 0, l = shapes.length; i < l; i ++ ) {
+
+                                               const shape = shapes[ i ];
+
+                                               serialize( meta.shapes, shape );
+
+                                       }
+
+                               } else {
+
+                                       serialize( meta.shapes, shapes );
+
+                               }
+
+                       }
+
+               }
+
+               if ( this.isSkinnedMesh ) {
+
+                       object.bindMode = this.bindMode;
+                       object.bindMatrix = this.bindMatrix.toArray();
+
+                       if ( this.skeleton !== undefined ) {
+
+                               serialize( meta.skeletons, this.skeleton );
+
+                               object.skeleton = this.skeleton.uuid;
+
+                       }
+
+               }
+
+               if ( this.material !== undefined ) {
+
+                       if ( Array.isArray( this.material ) ) {
+
+                               const uuids = [];
+
+                               for ( let i = 0, l = this.material.length; i < l; i ++ ) {
+
+                                       uuids.push( serialize( meta.materials, this.material[ i ] ) );
+
+                               }
+
+                               object.material = uuids;
+
+                       } else {
+
+                               object.material = serialize( meta.materials, this.material );
+
+                       }
+
+               }
+
+               //
+
+               if ( this.children.length > 0 ) {
+
+                       object.children = [];
+
+                       for ( let i = 0; i < this.children.length; i ++ ) {
+
+                               object.children.push( this.children[ i ].toJSON( meta ).object );
+
+                       }
+
+               }
+
+               //
+
+               if ( this.animations.length > 0 ) {
+
+                       object.animations = [];
+
+                       for ( let i = 0; i < this.animations.length; i ++ ) {
+
+                               const animation = this.animations[ i ];
+
+                               object.animations.push( serialize( meta.animations, animation ) );
+
+                       }
+
+               }
+
+               if ( isRootObject ) {
+
+                       const geometries = extractFromCache( meta.geometries );
+                       const materials = extractFromCache( meta.materials );
+                       const textures = extractFromCache( meta.textures );
+                       const images = extractFromCache( meta.images );
+                       const shapes = extractFromCache( meta.shapes );
+                       const skeletons = extractFromCache( meta.skeletons );
+                       const animations = extractFromCache( meta.animations );
+
+                       if ( geometries.length > 0 ) output.geometries = geometries;
+                       if ( materials.length > 0 ) output.materials = materials;
+                       if ( textures.length > 0 ) output.textures = textures;
+                       if ( images.length > 0 ) output.images = images;
+                       if ( shapes.length > 0 ) output.shapes = shapes;
+                       if ( skeletons.length > 0 ) output.skeletons = skeletons;
+                       if ( animations.length > 0 ) output.animations = animations;
+
+               }
+
+               output.object = object;
+
+               return output;
+
+               // extract data from the cache hash
+               // remove metadata on each item
+               // and return as array
+               function extractFromCache( cache ) {
+
+                       const values = [];
+                       for ( const key in cache ) {
+
+                               const data = cache[ key ];
+                               delete data.metadata;
+                               values.push( data );
+
+                       }
+
+                       return values;
+
+               }
+
+       },
+
+       clone: function ( recursive ) {
+
+               return new this.constructor().copy( this, recursive );
+
+       },
+
+       copy: function ( source, recursive = true ) {
+
+               this.name = source.name;
+
+               this.up.copy( source.up );
+
+               this.position.copy( source.position );
+               this.rotation.order = source.rotation.order;
+               this.quaternion.copy( source.quaternion );
+               this.scale.copy( source.scale );
+
+               this.matrix.copy( source.matrix );
+               this.matrixWorld.copy( source.matrixWorld );
+
+               this.matrixAutoUpdate = source.matrixAutoUpdate;
+               this.matrixWorldNeedsUpdate = source.matrixWorldNeedsUpdate;
+
+               this.layers.mask = source.layers.mask;
+               this.visible = source.visible;
+
+               this.castShadow = source.castShadow;
+               this.receiveShadow = source.receiveShadow;
+
+               this.frustumCulled = source.frustumCulled;
+               this.renderOrder = source.renderOrder;
+
+               this.userData = JSON.parse( JSON.stringify( source.userData ) );
+
+               if ( recursive === true ) {
+
+                       for ( let i = 0; i < source.children.length; i ++ ) {
+
+                               const child = source.children[ i ];
+                               this.add( child.clone() );
+
+                       }
+
+               }
+
+               return this;
+
+       }
+
+} );
+
+const _vector1 = /*@__PURE__*/ new Vector3();
+const _vector2 = /*@__PURE__*/ new Vector3();
+const _normalMatrix = /*@__PURE__*/ new Matrix3();
+
+class Plane {
+
+       constructor( normal, constant ) {
+
+               Object.defineProperty( this, 'isPlane', { value: true } );
+
+               // normal is assumed to be normalized
+
+               this.normal = ( normal !== undefined ) ? normal : new Vector3( 1, 0, 0 );
+               this.constant = ( constant !== undefined ) ? constant : 0;
+
+       }
+
+       set( normal, constant ) {
+
+               this.normal.copy( normal );
+               this.constant = constant;
+
+               return this;
+
+       }
+
+       setComponents( x, y, z, w ) {
+
+               this.normal.set( x, y, z );
+               this.constant = w;
+
+               return this;
+
+       }
+
+       setFromNormalAndCoplanarPoint( normal, point ) {
+
+               this.normal.copy( normal );
+               this.constant = - point.dot( this.normal );
+
+               return this;
+
+       }
+
+       setFromCoplanarPoints( a, b, c ) {
+
+               const normal = _vector1.subVectors( c, b ).cross( _vector2.subVectors( a, b ) ).normalize();
+
+               // Q: should an error be thrown if normal is zero (e.g. degenerate plane)?
+
+               this.setFromNormalAndCoplanarPoint( normal, a );
+
+               return this;
+
+       }
+
+       clone() {
+
+               return new this.constructor().copy( this );
+
+       }
+
+       copy( plane ) {
+
+               this.normal.copy( plane.normal );
+               this.constant = plane.constant;
+
+               return this;
+
+       }
+
+       normalize() {
+
+               // Note: will lead to a divide by zero if the plane is invalid.
+
+               const inverseNormalLength = 1.0 / this.normal.length();
+               this.normal.multiplyScalar( inverseNormalLength );
+               this.constant *= inverseNormalLength;
+
+               return this;
+
+       }
+
+       negate() {
+
+               this.constant *= - 1;
+               this.normal.negate();
+
+               return this;
+
+       }
+
+       distanceToPoint( point ) {
+
+               return this.normal.dot( point ) + this.constant;
+
+       }
+
+       distanceToSphere( sphere ) {
+
+               return this.distanceToPoint( sphere.center ) - sphere.radius;
+
+       }
+
+       projectPoint( point, target ) {
+
+               if ( target === undefined ) {
+
+                       console.warn( 'THREE.Plane: .projectPoint() target is now required' );
+                       target = new Vector3();
+
+               }
+
+               return target.copy( this.normal ).multiplyScalar( - this.distanceToPoint( point ) ).add( point );
+
+       }
+
+       intersectLine( line, target ) {
+
+               if ( target === undefined ) {
+
+                       console.warn( 'THREE.Plane: .intersectLine() target is now required' );
+                       target = new Vector3();
+
+               }
+
+               const direction = line.delta( _vector1 );
+
+               const denominator = this.normal.dot( direction );
+
+               if ( denominator === 0 ) {
+
+                       // line is coplanar, return origin
+                       if ( this.distanceToPoint( line.start ) === 0 ) {
+
+                               return target.copy( line.start );
+
+                       }
+
+                       // Unsure if this is the correct method to handle this case.
+                       return undefined;
+
+               }
+
+               const t = - ( line.start.dot( this.normal ) + this.constant ) / denominator;
+
+               if ( t < 0 || t > 1 ) {
+
+                       return undefined;
+
+               }
+
+               return target.copy( direction ).multiplyScalar( t ).add( line.start );
+
+       }
+
+       intersectsLine( line ) {
+
+               // Note: this tests if a line intersects the plane, not whether it (or its end-points) are coplanar with it.
+
+               const startSign = this.distanceToPoint( line.start );
+               const endSign = this.distanceToPoint( line.end );
+
+               return ( startSign < 0 && endSign > 0 ) || ( endSign < 0 && startSign > 0 );
+
+       }
+
+       intersectsBox( box ) {
+
+               return box.intersectsPlane( this );
+
+       }
+
+       intersectsSphere( sphere ) {
+
+               return sphere.intersectsPlane( this );
+
+       }
+
+       coplanarPoint( target ) {
+
+               if ( target === undefined ) {
+
+                       console.warn( 'THREE.Plane: .coplanarPoint() target is now required' );
+                       target = new Vector3();
+
+               }
+
+               return target.copy( this.normal ).multiplyScalar( - this.constant );
+
+       }
+
+       applyMatrix4( matrix, optionalNormalMatrix ) {
+
+               const normalMatrix = optionalNormalMatrix || _normalMatrix.getNormalMatrix( matrix );
+
+               const referencePoint = this.coplanarPoint( _vector1 ).applyMatrix4( matrix );
+
+               const normal = this.normal.applyMatrix3( normalMatrix ).normalize();
+
+               this.constant = - referencePoint.dot( normal );
+
+               return this;
+
+       }
+
+       translate( offset ) {
+
+               this.constant -= offset.dot( this.normal );
+
+               return this;
+
+       }
+
+       equals( plane ) {
+
+               return plane.normal.equals( this.normal ) && ( plane.constant === this.constant );
+
+       }
+
+}
+
+const _v0$1 = /*@__PURE__*/ new Vector3();
+const _v1$3 = /*@__PURE__*/ new Vector3();
+const _v2$1 = /*@__PURE__*/ new Vector3();
+const _v3 = /*@__PURE__*/ new Vector3();
+
+const _vab = /*@__PURE__*/ new Vector3();
+const _vac = /*@__PURE__*/ new Vector3();
+const _vbc = /*@__PURE__*/ new Vector3();
+const _vap = /*@__PURE__*/ new Vector3();
+const _vbp = /*@__PURE__*/ new Vector3();
+const _vcp = /*@__PURE__*/ new Vector3();
+
+class Triangle {
+
+       constructor( a, b, c ) {
+
+               this.a = ( a !== undefined ) ? a : new Vector3();
+               this.b = ( b !== undefined ) ? b : new Vector3();
+               this.c = ( c !== undefined ) ? c : new Vector3();
+
+       }
+
+       static getNormal( a, b, c, target ) {
+
+               if ( target === undefined ) {
+
+                       console.warn( 'THREE.Triangle: .getNormal() target is now required' );
+                       target = new Vector3();
+
+               }
+
+               target.subVectors( c, b );
+               _v0$1.subVectors( a, b );
+               target.cross( _v0$1 );
+
+               const targetLengthSq = target.lengthSq();
+               if ( targetLengthSq > 0 ) {
+
+                       return target.multiplyScalar( 1 / Math.sqrt( targetLengthSq ) );
+
+               }
+
+               return target.set( 0, 0, 0 );
+
+       }
+
+       // static/instance method to calculate barycentric coordinates
+       // based on: http://www.blackpawn.com/texts/pointinpoly/default.html
+       static getBarycoord( point, a, b, c, target ) {
+
+               _v0$1.subVectors( c, a );
+               _v1$3.subVectors( b, a );
+               _v2$1.subVectors( point, a );
+
+               const dot00 = _v0$1.dot( _v0$1 );
+               const dot01 = _v0$1.dot( _v1$3 );
+               const dot02 = _v0$1.dot( _v2$1 );
+               const dot11 = _v1$3.dot( _v1$3 );
+               const dot12 = _v1$3.dot( _v2$1 );
+
+               const denom = ( dot00 * dot11 - dot01 * dot01 );
+
+               if ( target === undefined ) {
+
+                       console.warn( 'THREE.Triangle: .getBarycoord() target is now required' );
+                       target = new Vector3();
+
+               }
+
+               // collinear or singular triangle
+               if ( denom === 0 ) {
+
+                       // arbitrary location outside of triangle?
+                       // not sure if this is the best idea, maybe should be returning undefined
+                       return target.set( - 2, - 1, - 1 );
+
+               }
+
+               const invDenom = 1 / denom;
+               const u = ( dot11 * dot02 - dot01 * dot12 ) * invDenom;
+               const v = ( dot00 * dot12 - dot01 * dot02 ) * invDenom;
+
+               // barycentric coordinates must always sum to 1
+               return target.set( 1 - u - v, v, u );
+
+       }
+
+       static containsPoint( point, a, b, c ) {
+
+               this.getBarycoord( point, a, b, c, _v3 );
+
+               return ( _v3.x >= 0 ) && ( _v3.y >= 0 ) && ( ( _v3.x + _v3.y ) <= 1 );
+
+       }
+
+       static getUV( point, p1, p2, p3, uv1, uv2, uv3, target ) {
+
+               this.getBarycoord( point, p1, p2, p3, _v3 );
+
+               target.set( 0, 0 );
+               target.addScaledVector( uv1, _v3.x );
+               target.addScaledVector( uv2, _v3.y );
+               target.addScaledVector( uv3, _v3.z );
+
+               return target;
+
+       }
+
+       static isFrontFacing( a, b, c, direction ) {
+
+               _v0$1.subVectors( c, b );
+               _v1$3.subVectors( a, b );
+
+               // strictly front facing
+               return ( _v0$1.cross( _v1$3 ).dot( direction ) < 0 ) ? true : false;
+
+       }
+
+       set( a, b, c ) {
+
+               this.a.copy( a );
+               this.b.copy( b );
+               this.c.copy( c );
+
+               return this;
+
+       }
+
+       setFromPointsAndIndices( points, i0, i1, i2 ) {
+
+               this.a.copy( points[ i0 ] );
+               this.b.copy( points[ i1 ] );
+               this.c.copy( points[ i2 ] );
+
+               return this;
+
+       }
+
+       clone() {
+
+               return new this.constructor().copy( this );
+
+       }
+
+       copy( triangle ) {
+
+               this.a.copy( triangle.a );
+               this.b.copy( triangle.b );
+               this.c.copy( triangle.c );
+
+               return this;
+
+       }
+
+       getArea() {
+
+               _v0$1.subVectors( this.c, this.b );
+               _v1$3.subVectors( this.a, this.b );
+
+               return _v0$1.cross( _v1$3 ).length() * 0.5;
+
+       }
+
+       getMidpoint( target ) {
+
+               if ( target === undefined ) {
+
+                       console.warn( 'THREE.Triangle: .getMidpoint() target is now required' );
+                       target = new Vector3();
+
+               }
+
+               return target.addVectors( this.a, this.b ).add( this.c ).multiplyScalar( 1 / 3 );
+
+       }
+
+       getNormal( target ) {
+
+               return Triangle.getNormal( this.a, this.b, this.c, target );
+
+       }
+
+       getPlane( target ) {
+
+               if ( target === undefined ) {
+
+                       console.warn( 'THREE.Triangle: .getPlane() target is now required' );
+                       target = new Plane();
+
+               }
+
+               return target.setFromCoplanarPoints( this.a, this.b, this.c );
+
+       }
+
+       getBarycoord( point, target ) {
+
+               return Triangle.getBarycoord( point, this.a, this.b, this.c, target );
+
+       }
+
+       getUV( point, uv1, uv2, uv3, target ) {
+
+               return Triangle.getUV( point, this.a, this.b, this.c, uv1, uv2, uv3, target );
+
+       }
+
+       containsPoint( point ) {
+
+               return Triangle.containsPoint( point, this.a, this.b, this.c );
+
+       }
+
+       isFrontFacing( direction ) {
+
+               return Triangle.isFrontFacing( this.a, this.b, this.c, direction );
+
+       }
+
+       intersectsBox( box ) {
+
+               return box.intersectsTriangle( this );
+
+       }
+
+       closestPointToPoint( p, target ) {
+
+               if ( target === undefined ) {
+
+                       console.warn( 'THREE.Triangle: .closestPointToPoint() target is now required' );
+                       target = new Vector3();
+
+               }
+
+               const a = this.a, b = this.b, c = this.c;
+               let v, w;
+
+               // algorithm thanks to Real-Time Collision Detection by Christer Ericson,
+               // published by Morgan Kaufmann Publishers, (c) 2005 Elsevier Inc.,
+               // under the accompanying license; see chapter 5.1.5 for detailed explanation.
+               // basically, we're distinguishing which of the voronoi regions of the triangle
+               // the point lies in with the minimum amount of redundant computation.
+
+               _vab.subVectors( b, a );
+               _vac.subVectors( c, a );
+               _vap.subVectors( p, a );
+               const d1 = _vab.dot( _vap );
+               const d2 = _vac.dot( _vap );
+               if ( d1 <= 0 && d2 <= 0 ) {
+
+                       // vertex region of A; barycentric coords (1, 0, 0)
+                       return target.copy( a );
+
+               }
+
+               _vbp.subVectors( p, b );
+               const d3 = _vab.dot( _vbp );
+               const d4 = _vac.dot( _vbp );
+               if ( d3 >= 0 && d4 <= d3 ) {
+
+                       // vertex region of B; barycentric coords (0, 1, 0)
+                       return target.copy( b );
+
+               }
+
+               const vc = d1 * d4 - d3 * d2;
+               if ( vc <= 0 && d1 >= 0 && d3 <= 0 ) {
+
+                       v = d1 / ( d1 - d3 );
+                       // edge region of AB; barycentric coords (1-v, v, 0)
+                       return target.copy( a ).addScaledVector( _vab, v );
+
+               }
+
+               _vcp.subVectors( p, c );
+               const d5 = _vab.dot( _vcp );
+               const d6 = _vac.dot( _vcp );
+               if ( d6 >= 0 && d5 <= d6 ) {
+
+                       // vertex region of C; barycentric coords (0, 0, 1)
+                       return target.copy( c );
+
+               }
+
+               const vb = d5 * d2 - d1 * d6;
+               if ( vb <= 0 && d2 >= 0 && d6 <= 0 ) {
+
+                       w = d2 / ( d2 - d6 );
+                       // edge region of AC; barycentric coords (1-w, 0, w)
+                       return target.copy( a ).addScaledVector( _vac, w );
+
+               }
+
+               const va = d3 * d6 - d5 * d4;
+               if ( va <= 0 && ( d4 - d3 ) >= 0 && ( d5 - d6 ) >= 0 ) {
+
+                       _vbc.subVectors( c, b );
+                       w = ( d4 - d3 ) / ( ( d4 - d3 ) + ( d5 - d6 ) );
+                       // edge region of BC; barycentric coords (0, 1-w, w)
+                       return target.copy( b ).addScaledVector( _vbc, w ); // edge region of BC
+
+               }
+
+               // face region
+               const denom = 1 / ( va + vb + vc );
+               // u = va * denom
+               v = vb * denom;
+               w = vc * denom;
+
+               return target.copy( a ).addScaledVector( _vab, v ).addScaledVector( _vac, w );
+
+       }
+
+       equals( triangle ) {
+
+               return triangle.a.equals( this.a ) && triangle.b.equals( this.b ) && triangle.c.equals( this.c );
+
+       }
+
+}
+
+const _colorKeywords = { 'aliceblue': 0xF0F8FF, 'antiquewhite': 0xFAEBD7, 'aqua': 0x00FFFF, 'aquamarine': 0x7FFFD4, 'azure': 0xF0FFFF,
+       'beige': 0xF5F5DC, 'bisque': 0xFFE4C4, 'black': 0x000000, 'blanchedalmond': 0xFFEBCD, 'blue': 0x0000FF, 'blueviolet': 0x8A2BE2,
+       'brown': 0xA52A2A, 'burlywood': 0xDEB887, 'cadetblue': 0x5F9EA0, 'chartreuse': 0x7FFF00, 'chocolate': 0xD2691E, 'coral': 0xFF7F50,
+       'cornflowerblue': 0x6495ED, 'cornsilk': 0xFFF8DC, 'crimson': 0xDC143C, 'cyan': 0x00FFFF, 'darkblue': 0x00008B, 'darkcyan': 0x008B8B,
+       'darkgoldenrod': 0xB8860B, 'darkgray': 0xA9A9A9, 'darkgreen': 0x006400, 'darkgrey': 0xA9A9A9, 'darkkhaki': 0xBDB76B, 'darkmagenta': 0x8B008B,
+       'darkolivegreen': 0x556B2F, 'darkorange': 0xFF8C00, 'darkorchid': 0x9932CC, 'darkred': 0x8B0000, 'darksalmon': 0xE9967A, 'darkseagreen': 0x8FBC8F,
+       'darkslateblue': 0x483D8B, 'darkslategray': 0x2F4F4F, 'darkslategrey': 0x2F4F4F, 'darkturquoise': 0x00CED1, 'darkviolet': 0x9400D3,
+       'deeppink': 0xFF1493, 'deepskyblue': 0x00BFFF, 'dimgray': 0x696969, 'dimgrey': 0x696969, 'dodgerblue': 0x1E90FF, 'firebrick': 0xB22222,
+       'floralwhite': 0xFFFAF0, 'forestgreen': 0x228B22, 'fuchsia': 0xFF00FF, 'gainsboro': 0xDCDCDC, 'ghostwhite': 0xF8F8FF, 'gold': 0xFFD700,
+       'goldenrod': 0xDAA520, 'gray': 0x808080, 'green': 0x008000, 'greenyellow': 0xADFF2F, 'grey': 0x808080, 'honeydew': 0xF0FFF0, 'hotpink': 0xFF69B4,
+       'indianred': 0xCD5C5C, 'indigo': 0x4B0082, 'ivory': 0xFFFFF0, 'khaki': 0xF0E68C, 'lavender': 0xE6E6FA, 'lavenderblush': 0xFFF0F5, 'lawngreen': 0x7CFC00,
+       'lemonchiffon': 0xFFFACD, 'lightblue': 0xADD8E6, 'lightcoral': 0xF08080, 'lightcyan': 0xE0FFFF, 'lightgoldenrodyellow': 0xFAFAD2, 'lightgray': 0xD3D3D3,
+       'lightgreen': 0x90EE90, 'lightgrey': 0xD3D3D3, 'lightpink': 0xFFB6C1, 'lightsalmon': 0xFFA07A, 'lightseagreen': 0x20B2AA, 'lightskyblue': 0x87CEFA,
+       'lightslategray': 0x778899, 'lightslategrey': 0x778899, 'lightsteelblue': 0xB0C4DE, 'lightyellow': 0xFFFFE0, 'lime': 0x00FF00, 'limegreen': 0x32CD32,
+       'linen': 0xFAF0E6, 'magenta': 0xFF00FF, 'maroon': 0x800000, 'mediumaquamarine': 0x66CDAA, 'mediumblue': 0x0000CD, 'mediumorchid': 0xBA55D3,
+       'mediumpurple': 0x9370DB, 'mediumseagreen': 0x3CB371, 'mediumslateblue': 0x7B68EE, 'mediumspringgreen': 0x00FA9A, 'mediumturquoise': 0x48D1CC,
+       'mediumvioletred': 0xC71585, 'midnightblue': 0x191970, 'mintcream': 0xF5FFFA, 'mistyrose': 0xFFE4E1, 'moccasin': 0xFFE4B5, 'navajowhite': 0xFFDEAD,
+       'navy': 0x000080, 'oldlace': 0xFDF5E6, 'olive': 0x808000, 'olivedrab': 0x6B8E23, 'orange': 0xFFA500, 'orangered': 0xFF4500, 'orchid': 0xDA70D6,
+       'palegoldenrod': 0xEEE8AA, 'palegreen': 0x98FB98, 'paleturquoise': 0xAFEEEE, 'palevioletred': 0xDB7093, 'papayawhip': 0xFFEFD5, 'peachpuff': 0xFFDAB9,
+       'peru': 0xCD853F, 'pink': 0xFFC0CB, 'plum': 0xDDA0DD, 'powderblue': 0xB0E0E6, 'purple': 0x800080, 'rebeccapurple': 0x663399, 'red': 0xFF0000, 'rosybrown': 0xBC8F8F,
+       'royalblue': 0x4169E1, 'saddlebrown': 0x8B4513, 'salmon': 0xFA8072, 'sandybrown': 0xF4A460, 'seagreen': 0x2E8B57, 'seashell': 0xFFF5EE,
+       'sienna': 0xA0522D, 'silver': 0xC0C0C0, 'skyblue': 0x87CEEB, 'slateblue': 0x6A5ACD, 'slategray': 0x708090, 'slategrey': 0x708090, 'snow': 0xFFFAFA,
+       'springgreen': 0x00FF7F, 'steelblue': 0x4682B4, 'tan': 0xD2B48C, 'teal': 0x008080, 'thistle': 0xD8BFD8, 'tomato': 0xFF6347, 'turquoise': 0x40E0D0,
+       'violet': 0xEE82EE, 'wheat': 0xF5DEB3, 'white': 0xFFFFFF, 'whitesmoke': 0xF5F5F5, 'yellow': 0xFFFF00, 'yellowgreen': 0x9ACD32 };
+
+const _hslA = { h: 0, s: 0, l: 0 };
+const _hslB = { h: 0, s: 0, l: 0 };
+
+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 ) * 6 * ( 2 / 3 - t );
+       return p;
+
+}
+
+function SRGBToLinear( c ) {
+
+       return ( c < 0.04045 ) ? c * 0.0773993808 : Math.pow( c * 0.9478672986 + 0.0521327014, 2.4 );
+
+}
+
+function LinearToSRGB( c ) {
+
+       return ( c < 0.0031308 ) ? c * 12.92 : 1.055 * ( Math.pow( c, 0.41666 ) ) - 0.055;
+
+}
+
+class Color {
+
+       constructor( r, g, b ) {
+
+               Object.defineProperty( this, 'isColor', { value: true } );
+
+               if ( g === undefined && b === undefined ) {
+
+                       // r is THREE.Color, hex or string
+                       return this.set( r );
+
+               }
+
+               return this.setRGB( r, g, b );
+
+       }
+
+       set( value ) {
+
+               if ( value && value.isColor ) {
+
+                       this.copy( value );
+
+               } else if ( typeof value === 'number' ) {
+
+                       this.setHex( value );
+
+               } else if ( typeof value === 'string' ) {
+
+                       this.setStyle( value );
+
+               }
+
+               return this;
+
+       }
+
+       setScalar( scalar ) {
+
+               this.r = scalar;
+               this.g = scalar;
+               this.b = scalar;
+
+               return this;
+
+       }
+
+       setHex( hex ) {
+
+               hex = Math.floor( hex );
+
+               this.r = ( hex >> 16 & 255 ) / 255;
+               this.g = ( hex >> 8 & 255 ) / 255;
+               this.b = ( hex & 255 ) / 255;
+
+               return this;
+
+       }
+
+       setRGB( r, g, b ) {
+
+               this.r = r;
+               this.g = g;
+               this.b = b;
+
+               return this;
+
+       }
+
+       setHSL( h, s, l ) {
+
+               // h,s,l ranges are in 0.0 - 1.0
+               h = MathUtils.euclideanModulo( h, 1 );
+               s = MathUtils.clamp( s, 0, 1 );
+               l = MathUtils.clamp( l, 0, 1 );
+
+               if ( s === 0 ) {
+
+                       this.r = this.g = this.b = l;
+
+               } else {
+
+                       const p = l <= 0.5 ? l * ( 1 + s ) : l + s - ( l * s );
+                       const q = ( 2 * l ) - p;
+
+                       this.r = hue2rgb( q, p, h + 1 / 3 );
+                       this.g = hue2rgb( q, p, h );
+                       this.b = hue2rgb( q, p, h - 1 / 3 );
+
+               }
+
+               return this;
+
+       }
+
+       setStyle( style ) {
+
+               function handleAlpha( string ) {
+
+                       if ( string === undefined ) return;
+
+                       if ( parseFloat( string ) < 1 ) {
+
+                               console.warn( 'THREE.Color: Alpha component of ' + style + ' will be ignored.' );
+
+                       }
+
+               }
+
+
+               let m;
+
+               if ( m = /^((?:rgb|hsl)a?)\(([^\)]*)\)/.exec( style ) ) {
+
+                       // rgb / hsl
+
+                       let color;
+                       const name = m[ 1 ];
+                       const components = m[ 2 ];
+
+                       switch ( name ) {
+
+                               case 'rgb':
+                               case 'rgba':
+
+                                       if ( color = /^\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) {
+
+                                               // rgb(255,0,0) rgba(255,0,0,0.5)
+                                               this.r = Math.min( 255, parseInt( color[ 1 ], 10 ) ) / 255;
+                                               this.g = Math.min( 255, parseInt( color[ 2 ], 10 ) ) / 255;
+                                               this.b = Math.min( 255, parseInt( color[ 3 ], 10 ) ) / 255;
+
+                                               handleAlpha( color[ 4 ] );
+
+                                               return this;
+
+                                       }
+
+                                       if ( color = /^\s*(\d+)\%\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) {
+
+                                               // rgb(100%,0%,0%) rgba(100%,0%,0%,0.5)
+                                               this.r = Math.min( 100, parseInt( color[ 1 ], 10 ) ) / 100;
+                                               this.g = Math.min( 100, parseInt( color[ 2 ], 10 ) ) / 100;
+                                               this.b = Math.min( 100, parseInt( color[ 3 ], 10 ) ) / 100;
+
+                                               handleAlpha( color[ 4 ] );
+
+                                               return this;
+
+                                       }
+
+                                       break;
+
+                               case 'hsl':
+                               case 'hsla':
+
+                                       if ( color = /^\s*(\d*\.?\d+)\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) {
+
+                                               // hsl(120,50%,50%) hsla(120,50%,50%,0.5)
+                                               const h = parseFloat( color[ 1 ] ) / 360;
+                                               const s = parseInt( color[ 2 ], 10 ) / 100;
+                                               const l = parseInt( color[ 3 ], 10 ) / 100;
+
+                                               handleAlpha( color[ 4 ] );
+
+                                               return this.setHSL( h, s, l );
+
+                                       }
+
+                                       break;
+
+                       }
+
+               } else if ( m = /^\#([A-Fa-f\d]+)$/.exec( style ) ) {
+
+                       // hex color
+
+                       const hex = m[ 1 ];
+                       const size = hex.length;
+
+                       if ( size === 3 ) {
+
+                               // #ff0
+                               this.r = parseInt( hex.charAt( 0 ) + hex.charAt( 0 ), 16 ) / 255;
+                               this.g = parseInt( hex.charAt( 1 ) + hex.charAt( 1 ), 16 ) / 255;
+                               this.b = parseInt( hex.charAt( 2 ) + hex.charAt( 2 ), 16 ) / 255;
+
+                               return this;
+
+                       } else if ( size === 6 ) {
+
+                               // #ff0000
+                               this.r = parseInt( hex.charAt( 0 ) + hex.charAt( 1 ), 16 ) / 255;
+                               this.g = parseInt( hex.charAt( 2 ) + hex.charAt( 3 ), 16 ) / 255;
+                               this.b = parseInt( hex.charAt( 4 ) + hex.charAt( 5 ), 16 ) / 255;
+
+                               return this;
+
+                       }
+
+               }
+
+               if ( style && style.length > 0 ) {
+
+                       return this.setColorName( style );
+
+               }
+
+               return this;
+
+       }
+
+       setColorName( style ) {
+
+               // color keywords
+               const hex = _colorKeywords[ style ];
+
+               if ( hex !== undefined ) {
+
+                       // red
+                       this.setHex( hex );
+
+               } else {
+
+                       // unknown color
+                       console.warn( 'THREE.Color: Unknown color ' + style );
+
+               }
+
+               return this;
+
+       }
+
+       clone() {
+
+               return new this.constructor( this.r, this.g, this.b );
+
+       }
+
+       copy( color ) {
+
+               this.r = color.r;
+               this.g = color.g;
+               this.b = color.b;
+
+               return this;
+
+       }
+
+       copyGammaToLinear( color, gammaFactor = 2.0 ) {
+
+               this.r = Math.pow( color.r, gammaFactor );
+               this.g = Math.pow( color.g, gammaFactor );
+               this.b = Math.pow( color.b, gammaFactor );
+
+               return this;
+
+       }
+
+       copyLinearToGamma( color, gammaFactor = 2.0 ) {
+
+               const safeInverse = ( gammaFactor > 0 ) ? ( 1.0 / gammaFactor ) : 1.0;
+
+               this.r = Math.pow( color.r, safeInverse );
+               this.g = Math.pow( color.g, safeInverse );
+               this.b = Math.pow( color.b, safeInverse );
+
+               return this;
+
+       }
+
+       convertGammaToLinear( gammaFactor ) {
+
+               this.copyGammaToLinear( this, gammaFactor );
+
+               return this;
+
+       }
+
+       convertLinearToGamma( gammaFactor ) {
+
+               this.copyLinearToGamma( this, gammaFactor );
+
+               return this;
+
+       }
+
+       copySRGBToLinear( color ) {
+
+               this.r = SRGBToLinear( color.r );
+               this.g = SRGBToLinear( color.g );
+               this.b = SRGBToLinear( color.b );
+
+               return this;
+
+       }
+
+       copyLinearToSRGB( color ) {
+
+               this.r = LinearToSRGB( color.r );
+               this.g = LinearToSRGB( color.g );
+               this.b = LinearToSRGB( color.b );
+
+               return this;
+
+       }
+
+       convertSRGBToLinear() {
+
+               this.copySRGBToLinear( this );
+
+               return this;
+
+       }
+
+       convertLinearToSRGB() {
+
+               this.copyLinearToSRGB( this );
+
+               return this;
+
+       }
+
+       getHex() {
+
+               return ( this.r * 255 ) << 16 ^ ( this.g * 255 ) << 8 ^ ( this.b * 255 ) << 0;
+
+       }
+
+       getHexString() {
+
+               return ( '000000' + this.getHex().toString( 16 ) ).slice( - 6 );
+
+       }
+
+       getHSL( target ) {
+
+               // h,s,l ranges are in 0.0 - 1.0
+
+               if ( target === undefined ) {
+
+                       console.warn( 'THREE.Color: .getHSL() target is now required' );
+                       target = { h: 0, s: 0, l: 0 };
+
+               }
+
+               const r = this.r, g = this.g, b = this.b;
+
+               const max = Math.max( r, g, b );
+               const min = Math.min( r, g, b );
+
+               let hue, saturation;
+               const lightness = ( min + max ) / 2.0;
+
+               if ( min === max ) {
+
+                       hue = 0;
+                       saturation = 0;
+
+               } else {
+
+                       const delta = max - min;
+
+                       saturation = lightness <= 0.5 ? delta / ( max + min ) : delta / ( 2 - max - min );
+
+                       switch ( max ) {
+
+                               case r: hue = ( g - b ) / delta + ( g < b ? 6 : 0 ); break;
+                               case g: hue = ( b - r ) / delta + 2; break;
+                               case b: hue = ( r - g ) / delta + 4; break;
+
+                       }
+
+                       hue /= 6;
+
+               }
+
+               target.h = hue;
+               target.s = saturation;
+               target.l = lightness;
+
+               return target;
+
+       }
+
+       getStyle() {
+
+               return 'rgb(' + ( ( this.r * 255 ) | 0 ) + ',' + ( ( this.g * 255 ) | 0 ) + ',' + ( ( this.b * 255 ) | 0 ) + ')';
+
+       }
+
+       offsetHSL( h, s, l ) {
+
+               this.getHSL( _hslA );
+
+               _hslA.h += h; _hslA.s += s; _hslA.l += l;
+
+               this.setHSL( _hslA.h, _hslA.s, _hslA.l );
+
+               return this;
+
+       }
+
+       add( color ) {
+
+               this.r += color.r;
+               this.g += color.g;
+               this.b += color.b;
+
+               return this;
+
+       }
+
+       addColors( color1, color2 ) {
+
+               this.r = color1.r + color2.r;
+               this.g = color1.g + color2.g;
+               this.b = color1.b + color2.b;
+
+               return this;
+
+       }
+
+       addScalar( s ) {
+
+               this.r += s;
+               this.g += s;
+               this.b += s;
+
+               return this;
+
+       }
+
+       sub( color ) {
+
+               this.r = Math.max( 0, this.r - color.r );
+               this.g = Math.max( 0, this.g - color.g );
+               this.b = Math.max( 0, this.b - color.b );
+
+               return this;
+
+       }
+
+       multiply( color ) {
+
+               this.r *= color.r;
+               this.g *= color.g;
+               this.b *= color.b;
+
+               return this;
+
+       }
+
+       multiplyScalar( s ) {
+
+               this.r *= s;
+               this.g *= s;
+               this.b *= s;
+
+               return this;
+
+       }
+
+       lerp( color, alpha ) {
+
+               this.r += ( color.r - this.r ) * alpha;
+               this.g += ( color.g - this.g ) * alpha;
+               this.b += ( color.b - this.b ) * alpha;
+
+               return this;
+
+       }
+
+       lerpColors( color1, color2, alpha ) {
+
+               this.r = color1.r + ( color2.r - color1.r ) * alpha;
+               this.g = color1.g + ( color2.g - color1.g ) * alpha;
+               this.b = color1.b + ( color2.b - color1.b ) * alpha;
+
+               return this;
+
+       }
+
+       lerpHSL( color, alpha ) {
+
+               this.getHSL( _hslA );
+               color.getHSL( _hslB );
+
+               const h = MathUtils.lerp( _hslA.h, _hslB.h, alpha );
+               const s = MathUtils.lerp( _hslA.s, _hslB.s, alpha );
+               const l = MathUtils.lerp( _hslA.l, _hslB.l, alpha );
+
+               this.setHSL( h, s, l );
+
+               return this;
+
+       }
+
+       equals( c ) {
+
+               return ( c.r === this.r ) && ( c.g === this.g ) && ( c.b === this.b );
+
+       }
+
+       fromArray( array, offset = 0 ) {
+
+               this.r = array[ offset ];
+               this.g = array[ offset + 1 ];
+               this.b = array[ offset + 2 ];
+
+               return this;
+
+       }
+
+       toArray( array = [], offset = 0 ) {
+
+               array[ offset ] = this.r;
+               array[ offset + 1 ] = this.g;
+               array[ offset + 2 ] = this.b;
+
+               return array;
+
+       }
+
+       fromBufferAttribute( attribute, index ) {
+
+               this.r = attribute.getX( index );
+               this.g = attribute.getY( index );
+               this.b = attribute.getZ( index );
+
+               if ( attribute.normalized === true ) {
+
+                       // assuming Uint8Array
+
+                       this.r /= 255;
+                       this.g /= 255;
+                       this.b /= 255;
+
+               }
+
+               return this;
+
+       }
+
+       toJSON() {
+
+               return this.getHex();
+
+       }
+
+}
+
+Color.NAMES = _colorKeywords;
+Color.prototype.r = 1;
+Color.prototype.g = 1;
+Color.prototype.b = 1;
+
+class Face3 {
+
+       constructor( a, b, c, normal, color, materialIndex = 0 ) {
+
+               this.a = a;
+               this.b = b;
+               this.c = c;
+
+               this.normal = ( normal && normal.isVector3 ) ? normal : new Vector3();
+               this.vertexNormals = Array.isArray( normal ) ? normal : [];
+
+               this.color = ( color && color.isColor ) ? color : new Color();
+               this.vertexColors = Array.isArray( color ) ? color : [];
+
+               this.materialIndex = materialIndex;
+
+       }
+
+       clone() {
+
+               return new this.constructor().copy( this );
+
+       }
+
+       copy( source ) {
+
+               this.a = source.a;
+               this.b = source.b;
+               this.c = source.c;
+
+               this.normal.copy( source.normal );
+               this.color.copy( source.color );
+
+               this.materialIndex = source.materialIndex;
+
+               for ( let i = 0, il = source.vertexNormals.length; i < il; i ++ ) {
+
+                       this.vertexNormals[ i ] = source.vertexNormals[ i ].clone();
+
+               }
+
+               for ( let i = 0, il = source.vertexColors.length; i < il; i ++ ) {
+
+                       this.vertexColors[ i ] = source.vertexColors[ i ].clone();
+
+               }
+
+               return this;
+
+       }
+
+}
+
+let materialId = 0;
+
+function Material() {
+
+       Object.defineProperty( this, 'id', { value: materialId ++ } );
+
+       this.uuid = MathUtils.generateUUID();
+
+       this.name = '';
+       this.type = 'Material';
+
+       this.fog = true;
+
+       this.blending = NormalBlending;
+       this.side = FrontSide;
+       this.flatShading = false;
+       this.vertexColors = false;
+
+       this.opacity = 1;
+       this.transparent = false;
+
+       this.blendSrc = SrcAlphaFactor;
+       this.blendDst = OneMinusSrcAlphaFactor;
+       this.blendEquation = AddEquation;
+       this.blendSrcAlpha = null;
+       this.blendDstAlpha = null;
+       this.blendEquationAlpha = null;
+
+       this.depthFunc = LessEqualDepth;
+       this.depthTest = true;
+       this.depthWrite = true;
+
+       this.stencilWriteMask = 0xff;
+       this.stencilFunc = AlwaysStencilFunc;
+       this.stencilRef = 0;
+       this.stencilFuncMask = 0xff;
+       this.stencilFail = KeepStencilOp;
+       this.stencilZFail = KeepStencilOp;
+       this.stencilZPass = KeepStencilOp;
+       this.stencilWrite = false;
+
+       this.clippingPlanes = null;
+       this.clipIntersection = false;
+       this.clipShadows = false;
+
+       this.shadowSide = null;
+
+       this.colorWrite = true;
+
+       this.precision = null; // override the renderer's default precision for this material
+
+       this.polygonOffset = false;
+       this.polygonOffsetFactor = 0;
+       this.polygonOffsetUnits = 0;
+
+       this.dithering = false;
+
+       this.alphaTest = 0;
+       this.premultipliedAlpha = false;
+
+       this.visible = true;
+
+       this.toneMapped = true;
+
+       this.userData = {};
+
+       this.version = 0;
+
+}
+
+Material.prototype = Object.assign( Object.create( EventDispatcher.prototype ), {
+
+       constructor: Material,
+
+       isMaterial: true,
+
+       onBeforeCompile: function ( /* shaderobject, renderer */ ) {},
+
+       customProgramCacheKey: function () {
+
+               return this.onBeforeCompile.toString();
+
+       },
+
+       setValues: function ( values ) {
+
+               if ( values === undefined ) return;
+
+               for ( const key in values ) {
+
+                       const newValue = values[ key ];
+
+                       if ( newValue === undefined ) {
+
+                               console.warn( 'THREE.Material: \'' + key + '\' parameter is undefined.' );
+                               continue;
+
+                       }
+
+                       // for backward compatability if shading is set in the constructor
+                       if ( key === 'shading' ) {
+
+                               console.warn( 'THREE.' + this.type + ': .shading has been removed. Use the boolean .flatShading instead.' );
+                               this.flatShading = ( newValue === FlatShading ) ? true : false;
+                               continue;
+
+                       }
+
+                       const currentValue = this[ key ];
+
+                       if ( currentValue === undefined ) {
+
+                               console.warn( 'THREE.' + this.type + ': \'' + key + '\' is not a property of this material.' );
+                               continue;
+
+                       }
+
+                       if ( currentValue && currentValue.isColor ) {
+
+                               currentValue.set( newValue );
+
+                       } else if ( ( currentValue && currentValue.isVector3 ) && ( newValue && newValue.isVector3 ) ) {
+
+                               currentValue.copy( newValue );
+
+                       } else {
+
+                               this[ key ] = newValue;
+
+                       }
+
+               }
+
+       },
+
+       toJSON: function ( meta ) {
+
+               const isRoot = ( meta === undefined || typeof meta === 'string' );
+
+               if ( isRoot ) {
+
+                       meta = {
+                               textures: {},
+                               images: {}
+                       };
+
+               }
+
+               const data = {
+                       metadata: {
+                               version: 4.5,
+                               type: 'Material',
+                               generator: 'Material.toJSON'
+                       }
+               };
+
+               // standard Material serialization
+               data.uuid = this.uuid;
+               data.type = this.type;
+
+               if ( this.name !== '' ) data.name = this.name;
+
+               if ( this.color && this.color.isColor ) data.color = this.color.getHex();
+
+               if ( this.roughness !== undefined ) data.roughness = this.roughness;
+               if ( this.metalness !== undefined ) data.metalness = this.metalness;
+
+               if ( this.sheen && this.sheen.isColor ) data.sheen = this.sheen.getHex();
+               if ( this.emissive && this.emissive.isColor ) data.emissive = this.emissive.getHex();
+               if ( this.emissiveIntensity && this.emissiveIntensity !== 1 ) data.emissiveIntensity = this.emissiveIntensity;
+
+               if ( this.specular && this.specular.isColor ) data.specular = this.specular.getHex();
+               if ( this.shininess !== undefined ) data.shininess = this.shininess;
+               if ( this.clearcoat !== undefined ) data.clearcoat = this.clearcoat;
+               if ( this.clearcoatRoughness !== undefined ) data.clearcoatRoughness = this.clearcoatRoughness;
+
+               if ( this.clearcoatMap && this.clearcoatMap.isTexture ) {
+
+                       data.clearcoatMap = this.clearcoatMap.toJSON( meta ).uuid;
+
+               }
+
+               if ( this.clearcoatRoughnessMap && this.clearcoatRoughnessMap.isTexture ) {
+
+                       data.clearcoatRoughnessMap = this.clearcoatRoughnessMap.toJSON( meta ).uuid;
+
+               }
+
+               if ( this.clearcoatNormalMap && this.clearcoatNormalMap.isTexture ) {
+
+                       data.clearcoatNormalMap = this.clearcoatNormalMap.toJSON( meta ).uuid;
+                       data.clearcoatNormalScale = this.clearcoatNormalScale.toArray();
+
+               }
+
+               if ( this.map && this.map.isTexture ) data.map = this.map.toJSON( meta ).uuid;
+               if ( this.matcap && this.matcap.isTexture ) data.matcap = this.matcap.toJSON( meta ).uuid;
+               if ( this.alphaMap && this.alphaMap.isTexture ) data.alphaMap = this.alphaMap.toJSON( meta ).uuid;
+               if ( this.lightMap && this.lightMap.isTexture ) data.lightMap = this.lightMap.toJSON( meta ).uuid;
+
+               if ( this.aoMap && this.aoMap.isTexture ) {
+
+                       data.aoMap = this.aoMap.toJSON( meta ).uuid;
+                       data.aoMapIntensity = this.aoMapIntensity;
+
+               }
+
+               if ( this.bumpMap && this.bumpMap.isTexture ) {
+
+                       data.bumpMap = this.bumpMap.toJSON( meta ).uuid;
+                       data.bumpScale = this.bumpScale;
+
+               }
+
+               if ( this.normalMap && this.normalMap.isTexture ) {
+
+                       data.normalMap = this.normalMap.toJSON( meta ).uuid;
+                       data.normalMapType = this.normalMapType;
+                       data.normalScale = this.normalScale.toArray();
+
+               }
+
+               if ( this.displacementMap && this.displacementMap.isTexture ) {
+
+                       data.displacementMap = this.displacementMap.toJSON( meta ).uuid;
+                       data.displacementScale = this.displacementScale;
+                       data.displacementBias = this.displacementBias;
+
+               }
+
+               if ( this.roughnessMap && this.roughnessMap.isTexture ) data.roughnessMap = this.roughnessMap.toJSON( meta ).uuid;
+               if ( this.metalnessMap && this.metalnessMap.isTexture ) data.metalnessMap = this.metalnessMap.toJSON( meta ).uuid;
+
+               if ( this.emissiveMap && this.emissiveMap.isTexture ) data.emissiveMap = this.emissiveMap.toJSON( meta ).uuid;
+               if ( this.specularMap && this.specularMap.isTexture ) data.specularMap = this.specularMap.toJSON( meta ).uuid;
+
+               if ( this.envMap && this.envMap.isTexture ) {
+
+                       data.envMap = this.envMap.toJSON( meta ).uuid;
+                       data.reflectivity = this.reflectivity; // Scale behind envMap
+                       data.refractionRatio = this.refractionRatio;
+
+                       if ( this.combine !== undefined ) data.combine = this.combine;
+                       if ( this.envMapIntensity !== undefined ) data.envMapIntensity = this.envMapIntensity;
+
+               }
+
+               if ( this.gradientMap && this.gradientMap.isTexture ) {
+
+                       data.gradientMap = this.gradientMap.toJSON( meta ).uuid;
+
+               }
+
+               if ( this.size !== undefined ) data.size = this.size;
+               if ( this.sizeAttenuation !== undefined ) data.sizeAttenuation = this.sizeAttenuation;
+
+               if ( this.blending !== NormalBlending ) data.blending = this.blending;
+               if ( this.flatShading === true ) data.flatShading = this.flatShading;
+               if ( this.side !== FrontSide ) data.side = this.side;
+               if ( this.vertexColors ) data.vertexColors = true;
+
+               if ( this.opacity < 1 ) data.opacity = this.opacity;
+               if ( this.transparent === true ) data.transparent = this.transparent;
+
+               data.depthFunc = this.depthFunc;
+               data.depthTest = this.depthTest;
+               data.depthWrite = this.depthWrite;
+
+               data.stencilWrite = this.stencilWrite;
+               data.stencilWriteMask = this.stencilWriteMask;
+               data.stencilFunc = this.stencilFunc;
+               data.stencilRef = this.stencilRef;
+               data.stencilFuncMask = this.stencilFuncMask;
+               data.stencilFail = this.stencilFail;
+               data.stencilZFail = this.stencilZFail;
+               data.stencilZPass = this.stencilZPass;
+
+               // rotation (SpriteMaterial)
+               if ( this.rotation && this.rotation !== 0 ) data.rotation = this.rotation;
+
+               if ( this.polygonOffset === true ) data.polygonOffset = true;
+               if ( this.polygonOffsetFactor !== 0 ) data.polygonOffsetFactor = this.polygonOffsetFactor;
+               if ( this.polygonOffsetUnits !== 0 ) data.polygonOffsetUnits = this.polygonOffsetUnits;
+
+               if ( this.linewidth && this.linewidth !== 1 ) data.linewidth = this.linewidth;
+               if ( this.dashSize !== undefined ) data.dashSize = this.dashSize;
+               if ( this.gapSize !== undefined ) data.gapSize = this.gapSize;
+               if ( this.scale !== undefined ) data.scale = this.scale;
+
+               if ( this.dithering === true ) data.dithering = true;
+
+               if ( this.alphaTest > 0 ) data.alphaTest = this.alphaTest;
+               if ( this.premultipliedAlpha === true ) data.premultipliedAlpha = this.premultipliedAlpha;
+
+               if ( this.wireframe === true ) data.wireframe = this.wireframe;
+               if ( this.wireframeLinewidth > 1 ) data.wireframeLinewidth = this.wireframeLinewidth;
+               if ( this.wireframeLinecap !== 'round' ) data.wireframeLinecap = this.wireframeLinecap;
+               if ( this.wireframeLinejoin !== 'round' ) data.wireframeLinejoin = this.wireframeLinejoin;
+
+               if ( this.morphTargets === true ) data.morphTargets = true;
+               if ( this.morphNormals === true ) data.morphNormals = true;
+               if ( this.skinning === true ) data.skinning = true;
+
+               if ( this.visible === false ) data.visible = false;
+
+               if ( this.toneMapped === false ) data.toneMapped = false;
+
+               if ( JSON.stringify( this.userData ) !== '{}' ) data.userData = this.userData;
+
+               // TODO: Copied from Object3D.toJSON
+
+               function extractFromCache( cache ) {
+
+                       const values = [];
+
+                       for ( const key in cache ) {
+
+                               const data = cache[ key ];
+                               delete data.metadata;
+                               values.push( data );
+
+                       }
+
+                       return values;
+
+               }
+
+               if ( isRoot ) {
+
+                       const textures = extractFromCache( meta.textures );
+                       const images = extractFromCache( meta.images );
+
+                       if ( textures.length > 0 ) data.textures = textures;
+                       if ( images.length > 0 ) data.images = images;
+
+               }
+
+               return data;
+
+       },
+
+       clone: function () {
+
+               return new this.constructor().copy( this );
+
+       },
+
+       copy: function ( source ) {
+
+               this.name = source.name;
+
+               this.fog = source.fog;
+
+               this.blending = source.blending;
+               this.side = source.side;
+               this.flatShading = source.flatShading;
+               this.vertexColors = source.vertexColors;
+
+               this.opacity = source.opacity;
+               this.transparent = source.transparent;
+
+               this.blendSrc = source.blendSrc;
+               this.blendDst = source.blendDst;
+               this.blendEquation = source.blendEquation;
+               this.blendSrcAlpha = source.blendSrcAlpha;
+               this.blendDstAlpha = source.blendDstAlpha;
+               this.blendEquationAlpha = source.blendEquationAlpha;
+
+               this.depthFunc = source.depthFunc;
+               this.depthTest = source.depthTest;
+               this.depthWrite = source.depthWrite;
+
+               this.stencilWriteMask = source.stencilWriteMask;
+               this.stencilFunc = source.stencilFunc;
+               this.stencilRef = source.stencilRef;
+               this.stencilFuncMask = source.stencilFuncMask;
+               this.stencilFail = source.stencilFail;
+               this.stencilZFail = source.stencilZFail;
+               this.stencilZPass = source.stencilZPass;
+               this.stencilWrite = source.stencilWrite;
+
+               const srcPlanes = source.clippingPlanes;
+               let dstPlanes = null;
+
+               if ( srcPlanes !== null ) {
+
+                       const n = srcPlanes.length;
+                       dstPlanes = new Array( n );
+
+                       for ( let i = 0; i !== n; ++ i ) {
+
+                               dstPlanes[ i ] = srcPlanes[ i ].clone();
+
+                       }
+
+               }
+
+               this.clippingPlanes = dstPlanes;
+               this.clipIntersection = source.clipIntersection;
+               this.clipShadows = source.clipShadows;
+
+               this.shadowSide = source.shadowSide;
+
+               this.colorWrite = source.colorWrite;
+
+               this.precision = source.precision;
+
+               this.polygonOffset = source.polygonOffset;
+               this.polygonOffsetFactor = source.polygonOffsetFactor;
+               this.polygonOffsetUnits = source.polygonOffsetUnits;
+
+               this.dithering = source.dithering;
+
+               this.alphaTest = source.alphaTest;
+               this.premultipliedAlpha = source.premultipliedAlpha;
+
+               this.visible = source.visible;
+
+               this.toneMapped = source.toneMapped;
+
+               this.userData = JSON.parse( JSON.stringify( source.userData ) );
+
+               return this;
+
+       },
+
+       dispose: function () {
+
+               this.dispatchEvent( { type: 'dispose' } );
+
+       }
+
+} );
+
+Object.defineProperty( Material.prototype, 'needsUpdate', {
+
+       set: function ( value ) {
+
+               if ( value === true ) this.version ++;
+
+       }
+
+} );
+
+/**
+ * parameters = {
+ *  color: <hex>,
+ *  opacity: <float>,
+ *  map: new THREE.Texture( <Image> ),
+ *
+ *  lightMap: new THREE.Texture( <Image> ),
+ *  lightMapIntensity: <float>
+ *
+ *  aoMap: new THREE.Texture( <Image> ),
+ *  aoMapIntensity: <float>
+ *
+ *  specularMap: new THREE.Texture( <Image> ),
+ *
+ *  alphaMap: new THREE.Texture( <Image> ),
+ *
+ *  envMap: new THREE.CubeTexture( [posx, negx, posy, negy, posz, negz] ),
+ *  combine: THREE.Multiply,
+ *  reflectivity: <float>,
+ *  refractionRatio: <float>,
+ *
+ *  depthTest: <bool>,
+ *  depthWrite: <bool>,
+ *
+ *  wireframe: <boolean>,
+ *  wireframeLinewidth: <float>,
+ *
+ *  skinning: <bool>,
+ *  morphTargets: <bool>
+ * }
+ */
+
+function MeshBasicMaterial( parameters ) {
+
+       Material.call( this );
+
+       this.type = 'MeshBasicMaterial';
+
+       this.color = new Color( 0xffffff ); // emissive
+
+       this.map = null;
+
+       this.lightMap = null;
+       this.lightMapIntensity = 1.0;
+
+       this.aoMap = null;
+       this.aoMapIntensity = 1.0;
+
+       this.specularMap = null;
+
+       this.alphaMap = null;
+
+       this.envMap = null;
+       this.combine = MultiplyOperation;
+       this.reflectivity = 1;
+       this.refractionRatio = 0.98;
+
+       this.wireframe = false;
+       this.wireframeLinewidth = 1;
+       this.wireframeLinecap = 'round';
+       this.wireframeLinejoin = 'round';
+
+       this.skinning = false;
+       this.morphTargets = false;
+
+       this.setValues( parameters );
+
+}
+
+MeshBasicMaterial.prototype = Object.create( Material.prototype );
+MeshBasicMaterial.prototype.constructor = MeshBasicMaterial;
+
+MeshBasicMaterial.prototype.isMeshBasicMaterial = true;
+
+MeshBasicMaterial.prototype.copy = function ( source ) {
+
+       Material.prototype.copy.call( this, source );
+
+       this.color.copy( source.color );
+
+       this.map = source.map;
+
+       this.lightMap = source.lightMap;
+       this.lightMapIntensity = source.lightMapIntensity;
+
+       this.aoMap = source.aoMap;
+       this.aoMapIntensity = source.aoMapIntensity;
+
+       this.specularMap = source.specularMap;
+
+       this.alphaMap = source.alphaMap;
+
+       this.envMap = source.envMap;
+       this.combine = source.combine;
+       this.reflectivity = source.reflectivity;
+       this.refractionRatio = source.refractionRatio;
+
+       this.wireframe = source.wireframe;
+       this.wireframeLinewidth = source.wireframeLinewidth;
+       this.wireframeLinecap = source.wireframeLinecap;
+       this.wireframeLinejoin = source.wireframeLinejoin;
+
+       this.skinning = source.skinning;
+       this.morphTargets = source.morphTargets;
+
+       return this;
+
+};
+
+const _vector$3 = new Vector3();
+const _vector2$1 = new Vector2();
+
+function BufferAttribute( array, itemSize, normalized ) {
+
+       if ( Array.isArray( array ) ) {
+
+               throw new TypeError( 'THREE.BufferAttribute: array should be a Typed Array.' );
+
+       }
+
+       this.name = '';
+
+       this.array = array;
+       this.itemSize = itemSize;
+       this.count = array !== undefined ? array.length / itemSize : 0;
+       this.normalized = normalized === true;
+
+       this.usage = StaticDrawUsage;
+       this.updateRange = { offset: 0, count: - 1 };
+
+       this.version = 0;
+
+}
+
+Object.defineProperty( BufferAttribute.prototype, 'needsUpdate', {
+
+       set: function ( value ) {
+
+               if ( value === true ) this.version ++;
+
+       }
+
+} );
+
+Object.assign( BufferAttribute.prototype, {
+
+       isBufferAttribute: true,
+
+       onUploadCallback: function () {},
+
+       setUsage: function ( value ) {
+
+               this.usage = value;
+
+               return this;
+
+       },
+
+       copy: function ( source ) {
+
+               this.name = source.name;
+               this.array = new source.array.constructor( source.array );
+               this.itemSize = source.itemSize;
+               this.count = source.count;
+               this.normalized = source.normalized;
+
+               this.usage = source.usage;
+
+               return this;
+
+       },
+
+       copyAt: function ( index1, attribute, index2 ) {
+
+               index1 *= this.itemSize;
+               index2 *= attribute.itemSize;
+
+               for ( let i = 0, l = this.itemSize; i < l; i ++ ) {
+
+                       this.array[ index1 + i ] = attribute.array[ index2 + i ];
+
+               }
+
+               return this;
+
+       },
+
+       copyArray: function ( array ) {
+
+               this.array.set( array );
+
+               return this;
+
+       },
+
+       copyColorsArray: function ( colors ) {
+
+               const array = this.array;
+               let offset = 0;
+
+               for ( let i = 0, l = colors.length; i < l; i ++ ) {
+
+                       let color = colors[ i ];
+
+                       if ( color === undefined ) {
+
+                               console.warn( 'THREE.BufferAttribute.copyColorsArray(): color is undefined', i );
+                               color = new Color();
+
+                       }
+
+                       array[ offset ++ ] = color.r;
+                       array[ offset ++ ] = color.g;
+                       array[ offset ++ ] = color.b;
+
+               }
+
+               return this;
+
+       },
+
+       copyVector2sArray: function ( vectors ) {
+
+               const array = this.array;
+               let offset = 0;
+
+               for ( let i = 0, l = vectors.length; i < l; i ++ ) {
+
+                       let vector = vectors[ i ];
+
+                       if ( vector === undefined ) {
+
+                               console.warn( 'THREE.BufferAttribute.copyVector2sArray(): vector is undefined', i );
+                               vector = new Vector2();
+
+                       }
+
+                       array[ offset ++ ] = vector.x;
+                       array[ offset ++ ] = vector.y;
+
+               }
+
+               return this;
+
+       },
+
+       copyVector3sArray: function ( vectors ) {
+
+               const array = this.array;
+               let offset = 0;
+
+               for ( let i = 0, l = vectors.length; i < l; i ++ ) {
+
+                       let vector = vectors[ i ];
+
+                       if ( vector === undefined ) {
+
+                               console.warn( 'THREE.BufferAttribute.copyVector3sArray(): vector is undefined', i );
+                               vector = new Vector3();
+
+                       }
+
+                       array[ offset ++ ] = vector.x;
+                       array[ offset ++ ] = vector.y;
+                       array[ offset ++ ] = vector.z;
+
+               }
+
+               return this;
+
+       },
+
+       copyVector4sArray: function ( vectors ) {
+
+               const array = this.array;
+               let offset = 0;
+
+               for ( let i = 0, l = vectors.length; i < l; i ++ ) {
+
+                       let vector = vectors[ i ];
+
+                       if ( vector === undefined ) {
+
+                               console.warn( 'THREE.BufferAttribute.copyVector4sArray(): vector is undefined', i );
+                               vector = new Vector4();
+
+                       }
+
+                       array[ offset ++ ] = vector.x;
+                       array[ offset ++ ] = vector.y;
+                       array[ offset ++ ] = vector.z;
+                       array[ offset ++ ] = vector.w;
+
+               }
+
+               return this;
+
+       },
+
+       applyMatrix3: function ( m ) {
+
+               if ( this.itemSize === 2 ) {
+
+                       for ( let i = 0, l = this.count; i < l; i ++ ) {
+
+                               _vector2$1.fromBufferAttribute( this, i );
+                               _vector2$1.applyMatrix3( m );
+
+                               this.setXY( i, _vector2$1.x, _vector2$1.y );
+
+                       }
+
+               } else if ( this.itemSize === 3 ) {
+
+                       for ( let i = 0, l = this.count; i < l; i ++ ) {
+
+                               _vector$3.fromBufferAttribute( this, i );
+                               _vector$3.applyMatrix3( m );
+
+                               this.setXYZ( i, _vector$3.x, _vector$3.y, _vector$3.z );
+
+                       }
+
+               }
+
+               return this;
+
+       },
+
+       applyMatrix4: function ( m ) {
+
+               for ( let i = 0, l = this.count; i < l; i ++ ) {
+
+                       _vector$3.x = this.getX( i );
+                       _vector$3.y = this.getY( i );
+                       _vector$3.z = this.getZ( i );
+
+                       _vector$3.applyMatrix4( m );
+
+                       this.setXYZ( i, _vector$3.x, _vector$3.y, _vector$3.z );
+
+               }
+
+               return this;
+
+       },
+
+       applyNormalMatrix: function ( m ) {
+
+               for ( let i = 0, l = this.count; i < l; i ++ ) {
+
+                       _vector$3.x = this.getX( i );
+                       _vector$3.y = this.getY( i );
+                       _vector$3.z = this.getZ( i );
+
+                       _vector$3.applyNormalMatrix( m );
+
+                       this.setXYZ( i, _vector$3.x, _vector$3.y, _vector$3.z );
+
+               }
+
+               return this;
+
+       },
+
+       transformDirection: function ( m ) {
+
+               for ( let i = 0, l = this.count; i < l; i ++ ) {
+
+                       _vector$3.x = this.getX( i );
+                       _vector$3.y = this.getY( i );
+                       _vector$3.z = this.getZ( i );
+
+                       _vector$3.transformDirection( m );
+
+                       this.setXYZ( i, _vector$3.x, _vector$3.y, _vector$3.z );
+
+               }
+
+               return this;
+
+       },
+
+       set: function ( value, offset = 0 ) {
+
+               this.array.set( value, offset );
+
+               return this;
+
+       },
+
+       getX: function ( index ) {
+
+               return this.array[ index * this.itemSize ];
+
+       },
+
+       setX: function ( index, x ) {
+
+               this.array[ index * this.itemSize ] = x;
+
+               return this;
+
+       },
+
+       getY: function ( index ) {
+
+               return this.array[ index * this.itemSize + 1 ];
+
+       },
+
+       setY: function ( index, y ) {
+
+               this.array[ index * this.itemSize + 1 ] = y;
+
+               return this;
+
+       },
+
+       getZ: function ( index ) {
+
+               return this.array[ index * this.itemSize + 2 ];
+
+       },
+
+       setZ: function ( index, z ) {
+
+               this.array[ index * this.itemSize + 2 ] = z;
+
+               return this;
+
+       },
+
+       getW: function ( index ) {
+
+               return this.array[ index * this.itemSize + 3 ];
+
+       },
+
+       setW: function ( index, w ) {
+
+               this.array[ index * this.itemSize + 3 ] = w;
+
+               return this;
+
+       },
+
+       setXY: function ( index, x, y ) {
+
+               index *= this.itemSize;
+
+               this.array[ index + 0 ] = x;
+               this.array[ index + 1 ] = y;
+
+               return this;
+
+       },
+
+       setXYZ: function ( index, x, y, z ) {
+
+               index *= this.itemSize;
+
+               this.array[ index + 0 ] = x;
+               this.array[ index + 1 ] = y;
+               this.array[ index + 2 ] = z;
+
+               return this;
+
+       },
+
+       setXYZW: function ( index, x, y, z, w ) {
+
+               index *= this.itemSize;
+
+               this.array[ index + 0 ] = x;
+               this.array[ index + 1 ] = y;
+               this.array[ index + 2 ] = z;
+               this.array[ index + 3 ] = w;
+
+               return this;
+
+       },
+
+       onUpload: function ( callback ) {
+
+               this.onUploadCallback = callback;
+
+               return this;
+
+       },
+
+       clone: function () {
+
+               return new this.constructor( this.array, this.itemSize ).copy( this );
+
+       },
+
+       toJSON: function () {
+
+               return {
+                       itemSize: this.itemSize,
+                       type: this.array.constructor.name,
+                       array: Array.prototype.slice.call( this.array ),
+                       normalized: this.normalized
+               };
+
+       }
+
+} );
+
+//
+
+function Int8BufferAttribute( array, itemSize, normalized ) {
+
+       BufferAttribute.call( this, new Int8Array( array ), itemSize, normalized );
+
+}
+
+Int8BufferAttribute.prototype = Object.create( BufferAttribute.prototype );
+Int8BufferAttribute.prototype.constructor = Int8BufferAttribute;
+
+
+function Uint8BufferAttribute( array, itemSize, normalized ) {
+
+       BufferAttribute.call( this, new Uint8Array( array ), itemSize, normalized );
+
+}
+
+Uint8BufferAttribute.prototype = Object.create( BufferAttribute.prototype );
+Uint8BufferAttribute.prototype.constructor = Uint8BufferAttribute;
+
+
+function Uint8ClampedBufferAttribute( array, itemSize, normalized ) {
+
+       BufferAttribute.call( this, new Uint8ClampedArray( array ), itemSize, normalized );
+
+}
+
+Uint8ClampedBufferAttribute.prototype = Object.create( BufferAttribute.prototype );
+Uint8ClampedBufferAttribute.prototype.constructor = Uint8ClampedBufferAttribute;
+
+
+function Int16BufferAttribute( array, itemSize, normalized ) {
+
+       BufferAttribute.call( this, new Int16Array( array ), itemSize, normalized );
+
+}
+
+Int16BufferAttribute.prototype = Object.create( BufferAttribute.prototype );
+Int16BufferAttribute.prototype.constructor = Int16BufferAttribute;
+
+
+function Uint16BufferAttribute( array, itemSize, normalized ) {
+
+       BufferAttribute.call( this, new Uint16Array( array ), itemSize, normalized );
+
+}
+
+Uint16BufferAttribute.prototype = Object.create( BufferAttribute.prototype );
+Uint16BufferAttribute.prototype.constructor = Uint16BufferAttribute;
+
+
+function Int32BufferAttribute( array, itemSize, normalized ) {
+
+       BufferAttribute.call( this, new Int32Array( array ), itemSize, normalized );
+
+}
+
+Int32BufferAttribute.prototype = Object.create( BufferAttribute.prototype );
+Int32BufferAttribute.prototype.constructor = Int32BufferAttribute;
+
+
+function Uint32BufferAttribute( array, itemSize, normalized ) {
+
+       BufferAttribute.call( this, new Uint32Array( array ), itemSize, normalized );
+
+}
+
+Uint32BufferAttribute.prototype = Object.create( BufferAttribute.prototype );
+Uint32BufferAttribute.prototype.constructor = Uint32BufferAttribute;
+
+function Float16BufferAttribute( array, itemSize, normalized ) {
+
+       BufferAttribute.call( this, new Uint16Array( array ), itemSize, normalized );
+
+}
+
+Float16BufferAttribute.prototype = Object.create( BufferAttribute.prototype );
+Float16BufferAttribute.prototype.constructor = Float16BufferAttribute;
+Float16BufferAttribute.prototype.isFloat16BufferAttribute = true;
+
+function Float32BufferAttribute( array, itemSize, normalized ) {
+
+       BufferAttribute.call( this, new Float32Array( array ), itemSize, normalized );
+
+}
+
+Float32BufferAttribute.prototype = Object.create( BufferAttribute.prototype );
+Float32BufferAttribute.prototype.constructor = Float32BufferAttribute;
+
+
+function Float64BufferAttribute( array, itemSize, normalized ) {
+
+       BufferAttribute.call( this, new Float64Array( array ), itemSize, normalized );
+
+}
+
+Float64BufferAttribute.prototype = Object.create( BufferAttribute.prototype );
+Float64BufferAttribute.prototype.constructor = Float64BufferAttribute;
+
+function arrayMax( array ) {
+
+       if ( array.length === 0 ) return - Infinity;
+
+       let max = array[ 0 ];
+
+       for ( let i = 1, l = array.length; i < l; ++ i ) {
+
+               if ( array[ i ] > max ) max = array[ i ];
+
+       }
+
+       return max;
+
+}
+
+const TYPED_ARRAYS = {
+       Int8Array: Int8Array,
+       Uint8Array: Uint8Array,
+       // Workaround for IE11 pre KB2929437. See #11440
+       Uint8ClampedArray: typeof Uint8ClampedArray !== 'undefined' ? Uint8ClampedArray : Uint8Array,
+       Int16Array: Int16Array,
+       Uint16Array: Uint16Array,
+       Int32Array: Int32Array,
+       Uint32Array: Uint32Array,
+       Float32Array: Float32Array,
+       Float64Array: Float64Array
+};
+
+function getTypedArray( type, buffer ) {
+
+       return new TYPED_ARRAYS[ type ]( buffer );
+
+}
+
+let _id = 0;
+
+const _m1$2 = new Matrix4();
+const _obj = new Object3D();
+const _offset = new Vector3();
+const _box$2 = new Box3();
+const _boxMorphTargets = new Box3();
+const _vector$4 = new Vector3();
+
+function BufferGeometry() {
+
+       Object.defineProperty( this, 'id', { value: _id ++ } );
+
+       this.uuid = MathUtils.generateUUID();
+
+       this.name = '';
+       this.type = 'BufferGeometry';
+
+       this.index = null;
+       this.attributes = {};
+
+       this.morphAttributes = {};
+       this.morphTargetsRelative = false;
+
+       this.groups = [];
+
+       this.boundingBox = null;
+       this.boundingSphere = null;
+
+       this.drawRange = { start: 0, count: Infinity };
+
+       this.userData = {};
+
+}
+
+BufferGeometry.prototype = Object.assign( Object.create( EventDispatcher.prototype ), {
+
+       constructor: BufferGeometry,
+
+       isBufferGeometry: true,
+
+       getIndex: function () {
+
+               return this.index;
+
+       },
+
+       setIndex: function ( index ) {
+
+               if ( Array.isArray( index ) ) {
+
+                       this.index = new ( arrayMax( index ) > 65535 ? Uint32BufferAttribute : Uint16BufferAttribute )( index, 1 );
+
+               } else {
+
+                       this.index = index;
+
+               }
+
+               return this;
+
+       },
+
+       getAttribute: function ( name ) {
+
+               return this.attributes[ name ];
+
+       },
+
+       setAttribute: function ( name, attribute ) {
+
+               this.attributes[ name ] = attribute;
+
+               return this;
+
+       },
+
+       deleteAttribute: function ( name ) {
+
+               delete this.attributes[ name ];
+
+               return this;
+
+       },
+
+       hasAttribute: function ( name ) {
+
+               return this.attributes[ name ] !== undefined;
+
+       },
+
+       addGroup: function ( start, count, materialIndex = 0 ) {
+
+               this.groups.push( {
+
+                       start: start,
+                       count: count,
+                       materialIndex: materialIndex
+
+               } );
+
+       },
+
+       clearGroups: function () {
+
+               this.groups = [];
+
+       },
+
+       setDrawRange: function ( start, count ) {
+
+               this.drawRange.start = start;
+               this.drawRange.count = count;
+
+       },
+
+       applyMatrix4: function ( matrix ) {
+
+               const position = this.attributes.position;
+
+               if ( position !== undefined ) {
+
+                       position.applyMatrix4( matrix );
+
+                       position.needsUpdate = true;
+
+               }
+
+               const normal = this.attributes.normal;
+
+               if ( normal !== undefined ) {
+
+                       const normalMatrix = new Matrix3().getNormalMatrix( matrix );
+
+                       normal.applyNormalMatrix( normalMatrix );
+
+                       normal.needsUpdate = true;
+
+               }
+
+               const tangent = this.attributes.tangent;
+
+               if ( tangent !== undefined ) {
+
+                       tangent.transformDirection( matrix );
+
+                       tangent.needsUpdate = true;
+
+               }
+
+               if ( this.boundingBox !== null ) {
+
+                       this.computeBoundingBox();
+
+               }
+
+               if ( this.boundingSphere !== null ) {
+
+                       this.computeBoundingSphere();
+
+               }
+
+               return this;
+
+       },
+
+       rotateX: function ( angle ) {
+
+               // rotate geometry around world x-axis
+
+               _m1$2.makeRotationX( angle );
+
+               this.applyMatrix4( _m1$2 );
+
+               return this;
+
+       },
+
+       rotateY: function ( angle ) {
+
+               // rotate geometry around world y-axis
+
+               _m1$2.makeRotationY( angle );
+
+               this.applyMatrix4( _m1$2 );
+
+               return this;
+
+       },
+
+       rotateZ: function ( angle ) {
+
+               // rotate geometry around world z-axis
+
+               _m1$2.makeRotationZ( angle );
+
+               this.applyMatrix4( _m1$2 );
+
+               return this;
+
+       },
+
+       translate: function ( x, y, z ) {
+
+               // translate geometry
+
+               _m1$2.makeTranslation( x, y, z );
+
+               this.applyMatrix4( _m1$2 );
+
+               return this;
+
+       },
+
+       scale: function ( x, y, z ) {
+
+               // scale geometry
+
+               _m1$2.makeScale( x, y, z );
+
+               this.applyMatrix4( _m1$2 );
+
+               return this;
+
+       },
+
+       lookAt: function ( vector ) {
+
+               _obj.lookAt( vector );
+
+               _obj.updateMatrix();
+
+               this.applyMatrix4( _obj.matrix );
+
+               return this;
+
+       },
+
+       center: function () {
+
+               this.computeBoundingBox();
+
+               this.boundingBox.getCenter( _offset ).negate();
+
+               this.translate( _offset.x, _offset.y, _offset.z );
+
+               return this;
+
+       },
+
+       setFromPoints: function ( points ) {
+
+               const position = [];
+
+               for ( let i = 0, l = points.length; i < l; i ++ ) {
+
+                       const point = points[ i ];
+                       position.push( point.x, point.y, point.z || 0 );
+
+               }
+
+               this.setAttribute( 'position', new Float32BufferAttribute( position, 3 ) );
+
+               return this;
+
+       },
+
+       computeBoundingBox: function () {
+
+               if ( this.boundingBox === null ) {
+
+                       this.boundingBox = new Box3();
+
+               }
+
+               const position = this.attributes.position;
+               const morphAttributesPosition = this.morphAttributes.position;
+
+               if ( position && position.isGLBufferAttribute ) {
+
+                       console.error( 'THREE.BufferGeometry.computeBoundingBox(): GLBufferAttribute requires a manual bounding box. Alternatively set "mesh.frustumCulled" to "false".', this );
+
+                       this.boundingBox.set(
+                               new Vector3( - Infinity, - Infinity, - Infinity ),
+                               new Vector3( + Infinity, + Infinity, + Infinity )
+                       );
+
+                       return;
+
+               }
+
+               if ( position !== undefined ) {
+
+                       this.boundingBox.setFromBufferAttribute( position );
+
+                       // process morph attributes if present
+
+                       if ( morphAttributesPosition ) {
+
+                               for ( let i = 0, il = morphAttributesPosition.length; i < il; i ++ ) {
+
+                                       const morphAttribute = morphAttributesPosition[ i ];
+                                       _box$2.setFromBufferAttribute( morphAttribute );
+
+                                       if ( this.morphTargetsRelative ) {
+
+                                               _vector$4.addVectors( this.boundingBox.min, _box$2.min );
+                                               this.boundingBox.expandByPoint( _vector$4 );
+
+                                               _vector$4.addVectors( this.boundingBox.max, _box$2.max );
+                                               this.boundingBox.expandByPoint( _vector$4 );
+
+                                       } else {
+
+                                               this.boundingBox.expandByPoint( _box$2.min );
+                                               this.boundingBox.expandByPoint( _box$2.max );
+
+                                       }
+
+                               }
+
+                       }
+
+               } else {
+
+                       this.boundingBox.makeEmpty();
+
+               }
+
+               if ( isNaN( this.boundingBox.min.x ) || isNaN( this.boundingBox.min.y ) || isNaN( this.boundingBox.min.z ) ) {
+
+                       console.error( 'THREE.BufferGeometry.computeBoundingBox(): Computed min/max have NaN values. The "position" attribute is likely to have NaN values.', this );
+
+               }
+
+       },
+
+       computeBoundingSphere: function () {
+
+               if ( this.boundingSphere === null ) {
+
+                       this.boundingSphere = new Sphere();
+
+               }
+
+               const position = this.attributes.position;
+               const morphAttributesPosition = this.morphAttributes.position;
+
+               if ( position && position.isGLBufferAttribute ) {
+
+                       console.error( 'THREE.BufferGeometry.computeBoundingSphere(): GLBufferAttribute requires a manual bounding sphere. Alternatively set "mesh.frustumCulled" to "false".', this );
+
+                       this.boundingSphere.set( new Vector3(), Infinity );
+
+                       return;
+
+               }
+
+               if ( position ) {
+
+                       // first, find the center of the bounding sphere
+
+                       const center = this.boundingSphere.center;
+
+                       _box$2.setFromBufferAttribute( position );
+
+                       // process morph attributes if present
+
+                       if ( morphAttributesPosition ) {
+
+                               for ( let i = 0, il = morphAttributesPosition.length; i < il; i ++ ) {
+
+                                       const morphAttribute = morphAttributesPosition[ i ];
+                                       _boxMorphTargets.setFromBufferAttribute( morphAttribute );
+
+                                       if ( this.morphTargetsRelative ) {
+
+                                               _vector$4.addVectors( _box$2.min, _boxMorphTargets.min );
+                                               _box$2.expandByPoint( _vector$4 );
+
+                                               _vector$4.addVectors( _box$2.max, _boxMorphTargets.max );
+                                               _box$2.expandByPoint( _vector$4 );
+
+                                       } else {
+
+                                               _box$2.expandByPoint( _boxMorphTargets.min );
+                                               _box$2.expandByPoint( _boxMorphTargets.max );
+
+                                       }
+
+                               }
+
+                       }
+
+                       _box$2.getCenter( center );
+
+                       // second, try to find a boundingSphere with a radius smaller than the
+                       // boundingSphere of the boundingBox: sqrt(3) smaller in the best case
+
+                       let maxRadiusSq = 0;
+
+                       for ( let i = 0, il = position.count; i < il; i ++ ) {
+
+                               _vector$4.fromBufferAttribute( position, i );
+
+                               maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( _vector$4 ) );
+
+                       }
+
+                       // process morph attributes if present
+
+                       if ( morphAttributesPosition ) {
+
+                               for ( let i = 0, il = morphAttributesPosition.length; i < il; i ++ ) {
+
+                                       const morphAttribute = morphAttributesPosition[ i ];
+                                       const morphTargetsRelative = this.morphTargetsRelative;
+
+                                       for ( let j = 0, jl = morphAttribute.count; j < jl; j ++ ) {
+
+                                               _vector$4.fromBufferAttribute( morphAttribute, j );
+
+                                               if ( morphTargetsRelative ) {
+
+                                                       _offset.fromBufferAttribute( position, j );
+                                                       _vector$4.add( _offset );
+
+                                               }
+
+                                               maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( _vector$4 ) );
+
+                                       }
+
+                               }
+
+                       }
+
+                       this.boundingSphere.radius = Math.sqrt( maxRadiusSq );
+
+                       if ( isNaN( this.boundingSphere.radius ) ) {
+
+                               console.error( 'THREE.BufferGeometry.computeBoundingSphere(): Computed radius is NaN. The "position" attribute is likely to have NaN values.', this );
+
+                       }
+
+               }
+
+       },
+
+       computeFaceNormals: function () {
+
+               // backwards compatibility
+
+       },
+
+       computeTangents: function () {
+
+               const index = this.index;
+               const attributes = this.attributes;
+
+               // based on http://www.terathon.com/code/tangent.html
+               // (per vertex tangents)
+
+               if ( index === null ||
+                        attributes.position === undefined ||
+                        attributes.normal === undefined ||
+                        attributes.uv === undefined ) {
+
+                       console.error( 'THREE.BufferGeometry: .computeTangents() failed. Missing required attributes (index, position, normal or uv)' );
+                       return;
+
+               }
+
+               const indices = index.array;
+               const positions = attributes.position.array;
+               const normals = attributes.normal.array;
+               const uvs = attributes.uv.array;
+
+               const nVertices = positions.length / 3;
+
+               if ( attributes.tangent === undefined ) {
+
+                       this.setAttribute( 'tangent', new BufferAttribute( new Float32Array( 4 * nVertices ), 4 ) );
+
+               }
+
+               const tangents = attributes.tangent.array;
+
+               const tan1 = [], tan2 = [];
+
+               for ( let i = 0; i < nVertices; i ++ ) {
+
+                       tan1[ i ] = new Vector3();
+                       tan2[ i ] = new Vector3();
+
+               }
+
+               const vA = new Vector3(),
+                       vB = new Vector3(),
+                       vC = new Vector3(),
+
+                       uvA = new Vector2(),
+                       uvB = new Vector2(),
+                       uvC = new Vector2(),
+
+                       sdir = new Vector3(),
+                       tdir = new Vector3();
+
+               function handleTriangle( a, b, c ) {
+
+                       vA.fromArray( positions, a * 3 );
+                       vB.fromArray( positions, b * 3 );
+                       vC.fromArray( positions, c * 3 );
+
+                       uvA.fromArray( uvs, a * 2 );
+                       uvB.fromArray( uvs, b * 2 );
+                       uvC.fromArray( uvs, c * 2 );
+
+                       vB.sub( vA );
+                       vC.sub( vA );
+
+                       uvB.sub( uvA );
+                       uvC.sub( uvA );
+
+                       const r = 1.0 / ( uvB.x * uvC.y - uvC.x * uvB.y );
+
+                       // silently ignore degenerate uv triangles having coincident or colinear vertices
+
+                       if ( ! isFinite( r ) ) return;
+
+                       sdir.copy( vB ).multiplyScalar( uvC.y ).addScaledVector( vC, - uvB.y ).multiplyScalar( r );
+                       tdir.copy( vC ).multiplyScalar( uvB.x ).addScaledVector( vB, - uvC.x ).multiplyScalar( r );
+
+                       tan1[ a ].add( sdir );
+                       tan1[ b ].add( sdir );
+                       tan1[ c ].add( sdir );
+
+                       tan2[ a ].add( tdir );
+                       tan2[ b ].add( tdir );
+                       tan2[ c ].add( tdir );
+
+               }
+
+               let groups = this.groups;
+
+               if ( groups.length === 0 ) {
+
+                       groups = [ {
+                               start: 0,
+                               count: indices.length
+                       } ];
+
+               }
+
+               for ( let i = 0, il = groups.length; i < il; ++ i ) {
+
+                       const group = groups[ i ];
+
+                       const start = group.start;
+                       const count = group.count;
+
+                       for ( let j = start, jl = start + count; j < jl; j += 3 ) {
+
+                               handleTriangle(
+                                       indices[ j + 0 ],
+                                       indices[ j + 1 ],
+                                       indices[ j + 2 ]
+                               );
+
+                       }
+
+               }
+
+               const tmp = new Vector3(), tmp2 = new Vector3();
+               const n = new Vector3(), n2 = new Vector3();
+
+               function handleVertex( v ) {
+
+                       n.fromArray( normals, v * 3 );
+                       n2.copy( n );
+
+                       const t = tan1[ v ];
+
+                       // Gram-Schmidt orthogonalize
+
+                       tmp.copy( t );
+                       tmp.sub( n.multiplyScalar( n.dot( t ) ) ).normalize();
+
+                       // Calculate handedness
+
+                       tmp2.crossVectors( n2, t );
+                       const test = tmp2.dot( tan2[ v ] );
+                       const w = ( test < 0.0 ) ? - 1.0 : 1.0;
+
+                       tangents[ v * 4 ] = tmp.x;
+                       tangents[ v * 4 + 1 ] = tmp.y;
+                       tangents[ v * 4 + 2 ] = tmp.z;
+                       tangents[ v * 4 + 3 ] = w;
+
+               }
+
+               for ( let i = 0, il = groups.length; i < il; ++ i ) {
+
+                       const group = groups[ i ];
+
+                       const start = group.start;
+                       const count = group.count;
+
+                       for ( let j = start, jl = start + count; j < jl; j += 3 ) {
+
+                               handleVertex( indices[ j + 0 ] );
+                               handleVertex( indices[ j + 1 ] );
+                               handleVertex( indices[ j + 2 ] );
+
+                       }
+
+               }
+
+       },
+
+       computeVertexNormals: function () {
+
+               const index = this.index;
+               const positionAttribute = this.getAttribute( 'position' );
+
+               if ( positionAttribute !== undefined ) {
+
+                       let normalAttribute = this.getAttribute( 'normal' );
+
+                       if ( normalAttribute === undefined ) {
+
+                               normalAttribute = new BufferAttribute( new Float32Array( positionAttribute.count * 3 ), 3 );
+                               this.setAttribute( 'normal', normalAttribute );
+
+                       } else {
+
+                               // reset existing normals to zero
+
+                               for ( let i = 0, il = normalAttribute.count; i < il; i ++ ) {
+
+                                       normalAttribute.setXYZ( i, 0, 0, 0 );
+
+                               }
+
+                       }
+
+                       const pA = new Vector3(), pB = new Vector3(), pC = new Vector3();
+                       const nA = new Vector3(), nB = new Vector3(), nC = new Vector3();
+                       const cb = new Vector3(), ab = new Vector3();
+
+                       // indexed elements
+
+                       if ( index ) {
+
+                               for ( let i = 0, il = index.count; i < il; i += 3 ) {
+
+                                       const vA = index.getX( i + 0 );
+                                       const vB = index.getX( i + 1 );
+                                       const vC = index.getX( i + 2 );
+
+                                       pA.fromBufferAttribute( positionAttribute, vA );
+                                       pB.fromBufferAttribute( positionAttribute, vB );
+                                       pC.fromBufferAttribute( positionAttribute, vC );
+
+                                       cb.subVectors( pC, pB );
+                                       ab.subVectors( pA, pB );
+                                       cb.cross( ab );
+
+                                       nA.fromBufferAttribute( normalAttribute, vA );
+                                       nB.fromBufferAttribute( normalAttribute, vB );
+                                       nC.fromBufferAttribute( normalAttribute, vC );
+
+                                       nA.add( cb );
+                                       nB.add( cb );
+                                       nC.add( cb );
+
+                                       normalAttribute.setXYZ( vA, nA.x, nA.y, nA.z );
+                                       normalAttribute.setXYZ( vB, nB.x, nB.y, nB.z );
+                                       normalAttribute.setXYZ( vC, nC.x, nC.y, nC.z );
+
+                               }
+
+                       } else {
+
+                               // non-indexed elements (unconnected triangle soup)
+
+                               for ( let i = 0, il = positionAttribute.count; i < il; i += 3 ) {
+
+                                       pA.fromBufferAttribute( positionAttribute, i + 0 );
+                                       pB.fromBufferAttribute( positionAttribute, i + 1 );
+                                       pC.fromBufferAttribute( positionAttribute, i + 2 );
+
+                                       cb.subVectors( pC, pB );
+                                       ab.subVectors( pA, pB );
+                                       cb.cross( ab );
+
+                                       normalAttribute.setXYZ( i + 0, cb.x, cb.y, cb.z );
+                                       normalAttribute.setXYZ( i + 1, cb.x, cb.y, cb.z );
+                                       normalAttribute.setXYZ( i + 2, cb.x, cb.y, cb.z );
+
+                               }
+
+                       }
+
+                       this.normalizeNormals();
+
+                       normalAttribute.needsUpdate = true;
+
+               }
+
+       },
+
+       merge: function ( geometry, offset ) {
+
+               if ( ! ( geometry && geometry.isBufferGeometry ) ) {
+
+                       console.error( 'THREE.BufferGeometry.merge(): geometry not an instance of THREE.BufferGeometry.', geometry );
+                       return;
+
+               }
+
+               if ( offset === undefined ) {
+
+                       offset = 0;
+
+                       console.warn(
+                               'THREE.BufferGeometry.merge(): Overwriting original geometry, starting at offset=0. '
+                               + 'Use BufferGeometryUtils.mergeBufferGeometries() for lossless merge.'
+                       );
+
+               }
+
+               const attributes = this.attributes;
+
+               for ( const key in attributes ) {
+
+                       if ( geometry.attributes[ key ] === undefined ) continue;
+
+                       const attribute1 = attributes[ key ];
+                       const attributeArray1 = attribute1.array;
+
+                       const attribute2 = geometry.attributes[ key ];
+                       const attributeArray2 = attribute2.array;
+
+                       const attributeOffset = attribute2.itemSize * offset;
+                       const length = Math.min( attributeArray2.length, attributeArray1.length - attributeOffset );
+
+                       for ( let i = 0, j = attributeOffset; i < length; i ++, j ++ ) {
+
+                               attributeArray1[ j ] = attributeArray2[ i ];
+
+                       }
+
+               }
+
+               return this;
+
+       },
+
+       normalizeNormals: function () {
+
+               const normals = this.attributes.normal;
+
+               for ( let i = 0, il = normals.count; i < il; i ++ ) {
+
+                       _vector$4.fromBufferAttribute( normals, i );
+
+                       _vector$4.normalize();
+
+                       normals.setXYZ( i, _vector$4.x, _vector$4.y, _vector$4.z );
+
+               }
+
+       },
+
+       toNonIndexed: function () {
+
+               function convertBufferAttribute( attribute, indices ) {
+
+                       const array = attribute.array;
+                       const itemSize = attribute.itemSize;
+                       const normalized = attribute.normalized;
+
+                       const array2 = new array.constructor( indices.length * itemSize );
+
+                       let index = 0, index2 = 0;
+
+                       for ( let i = 0, l = indices.length; i < l; i ++ ) {
+
+                               index = indices[ i ] * itemSize;
+
+                               for ( let j = 0; j < itemSize; j ++ ) {
+
+                                       array2[ index2 ++ ] = array[ index ++ ];
+
+                               }
+
+                       }
+
+                       return new BufferAttribute( array2, itemSize, normalized );
+
+               }
+
+               //
+
+               if ( this.index === null ) {
+
+                       console.warn( 'THREE.BufferGeometry.toNonIndexed(): BufferGeometry is already non-indexed.' );
+                       return this;
+
+               }
+
+               const geometry2 = new BufferGeometry();
+
+               const indices = this.index.array;
+               const attributes = this.attributes;
+
+               // attributes
+
+               for ( const name in attributes ) {
+
+                       const attribute = attributes[ name ];
+
+                       const newAttribute = convertBufferAttribute( attribute, indices );
+
+                       geometry2.setAttribute( name, newAttribute );
+
+               }
+
+               // morph attributes
+
+               const morphAttributes = this.morphAttributes;
+
+               for ( const name in morphAttributes ) {
+
+                       const morphArray = [];
+                       const morphAttribute = morphAttributes[ name ]; // morphAttribute: array of Float32BufferAttributes
+
+                       for ( let i = 0, il = morphAttribute.length; i < il; i ++ ) {
+
+                               const attribute = morphAttribute[ i ];
+
+                               const newAttribute = convertBufferAttribute( attribute, indices );
+
+                               morphArray.push( newAttribute );
+
+                       }
+
+                       geometry2.morphAttributes[ name ] = morphArray;
+
+               }
+
+               geometry2.morphTargetsRelative = this.morphTargetsRelative;
+
+               // groups
+
+               const groups = this.groups;
+
+               for ( let i = 0, l = groups.length; i < l; i ++ ) {
+
+                       const group = groups[ i ];
+                       geometry2.addGroup( group.start, group.count, group.materialIndex );
+
+               }
+
+               return geometry2;
+
+       },
+
+       toJSON: function () {
+
+               const data = {
+                       metadata: {
+                               version: 4.5,
+                               type: 'BufferGeometry',
+                               generator: 'BufferGeometry.toJSON'
+                       }
+               };
+
+               // standard BufferGeometry serialization
+
+               data.uuid = this.uuid;
+               data.type = this.type;
+               if ( this.name !== '' ) data.name = this.name;
+               if ( Object.keys( this.userData ).length > 0 ) data.userData = this.userData;
+
+               if ( this.parameters !== undefined ) {
+
+                       const parameters = this.parameters;
+
+                       for ( const key in parameters ) {
+
+                               if ( parameters[ key ] !== undefined ) data[ key ] = parameters[ key ];
+
+                       }
+
+                       return data;
+
+               }
+
+               data.data = { attributes: {} };
+
+               const index = this.index;
+
+               if ( index !== null ) {
+
+                       data.data.index = {
+                               type: index.array.constructor.name,
+                               array: Array.prototype.slice.call( index.array )
+                       };
+
+               }
+
+               const attributes = this.attributes;
+
+               for ( const key in attributes ) {
+
+                       const attribute = attributes[ key ];
+
+                       const attributeData = attribute.toJSON( data.data );
+
+                       if ( attribute.name !== '' ) attributeData.name = attribute.name;
+
+                       data.data.attributes[ key ] = attributeData;
+
+               }
+
+               const morphAttributes = {};
+               let hasMorphAttributes = false;
+
+               for ( const key in this.morphAttributes ) {
+
+                       const attributeArray = this.morphAttributes[ key ];
+
+                       const array = [];
+
+                       for ( let i = 0, il = attributeArray.length; i < il; i ++ ) {
+
+                               const attribute = attributeArray[ i ];
+
+                               const attributeData = attribute.toJSON( data.data );
+
+                               if ( attribute.name !== '' ) attributeData.name = attribute.name;
+
+                               array.push( attributeData );
+
+                       }
+
+                       if ( array.length > 0 ) {
+
+                               morphAttributes[ key ] = array;
+
+                               hasMorphAttributes = true;
+
+                       }
+
+               }
+
+               if ( hasMorphAttributes ) {
+
+                       data.data.morphAttributes = morphAttributes;
+                       data.data.morphTargetsRelative = this.morphTargetsRelative;
+
+               }
+
+               const groups = this.groups;
+
+               if ( groups.length > 0 ) {
+
+                       data.data.groups = JSON.parse( JSON.stringify( groups ) );
+
+               }
+
+               const boundingSphere = this.boundingSphere;
+
+               if ( boundingSphere !== null ) {
+
+                       data.data.boundingSphere = {
+                               center: boundingSphere.center.toArray(),
+                               radius: boundingSphere.radius
+                       };
+
+               }
+
+               return data;
+
+       },
+
+       clone: function () {
+
+               /*
+                // Handle primitives
+
+                const parameters = this.parameters;
+
+                if ( parameters !== undefined ) {
+
+                const values = [];
+
+                for ( const key in parameters ) {
+
+                values.push( parameters[ key ] );
+
+                }
+
+                const geometry = Object.create( this.constructor.prototype );
+                this.constructor.apply( geometry, values );
+                return geometry;
+
+                }
+
+                return new this.constructor().copy( this );
+                */
+
+               return new BufferGeometry().copy( this );
+
+       },
+
+       copy: function ( source ) {
+
+               // reset
+
+               this.index = null;
+               this.attributes = {};
+               this.morphAttributes = {};
+               this.groups = [];
+               this.boundingBox = null;
+               this.boundingSphere = null;
+
+               // used for storing cloned, shared data
+
+               const data = {};
+
+               // name
+
+               this.name = source.name;
+
+               // index
+
+               const index = source.index;
+
+               if ( index !== null ) {
+
+                       this.setIndex( index.clone( data ) );
+
+               }
+
+               // attributes
+
+               const attributes = source.attributes;
+
+               for ( const name in attributes ) {
+
+                       const attribute = attributes[ name ];
+                       this.setAttribute( name, attribute.clone( data ) );
+
+               }
+
+               // morph attributes
+
+               const morphAttributes = source.morphAttributes;
+
+               for ( const name in morphAttributes ) {
+
+                       const array = [];
+                       const morphAttribute = morphAttributes[ name ]; // morphAttribute: array of Float32BufferAttributes
+
+                       for ( let i = 0, l = morphAttribute.length; i < l; i ++ ) {
+
+                               array.push( morphAttribute[ i ].clone( data ) );
+
+                       }
+
+                       this.morphAttributes[ name ] = array;
+
+               }
+
+               this.morphTargetsRelative = source.morphTargetsRelative;
+
+               // groups
+
+               const groups = source.groups;
+
+               for ( let i = 0, l = groups.length; i < l; i ++ ) {
+
+                       const group = groups[ i ];
+                       this.addGroup( group.start, group.count, group.materialIndex );
+
+               }
+
+               // bounding box
+
+               const boundingBox = source.boundingBox;
+
+               if ( boundingBox !== null ) {
+
+                       this.boundingBox = boundingBox.clone();
+
+               }
+
+               // bounding sphere
+
+               const boundingSphere = source.boundingSphere;
+
+               if ( boundingSphere !== null ) {
+
+                       this.boundingSphere = boundingSphere.clone();
+
+               }
+
+               // draw range
+
+               this.drawRange.start = source.drawRange.start;
+               this.drawRange.count = source.drawRange.count;
+
+               // user data
+
+               this.userData = source.userData;
+
+               return this;
+
+       },
+
+       dispose: function () {
+
+               this.dispatchEvent( { type: 'dispose' } );
+
+       }
+
+} );
+
+const _inverseMatrix = new Matrix4();
+const _ray = new Ray();
+const _sphere = new Sphere();
+
+const _vA = new Vector3();
+const _vB = new Vector3();
+const _vC = new Vector3();
+
+const _tempA = new Vector3();
+const _tempB = new Vector3();
+const _tempC = new Vector3();
+
+const _morphA = new Vector3();
+const _morphB = new Vector3();
+const _morphC = new Vector3();
+
+const _uvA = new Vector2();
+const _uvB = new Vector2();
+const _uvC = new Vector2();
+
+const _intersectionPoint = new Vector3();
+const _intersectionPointWorld = new Vector3();
+
+function Mesh( geometry = new BufferGeometry(), material = new MeshBasicMaterial() ) {
+
+       Object3D.call( this );
+
+       this.type = 'Mesh';
+
+       this.geometry = geometry;
+       this.material = material;
+
+       this.updateMorphTargets();
+
+}
+
+Mesh.prototype = Object.assign( Object.create( Object3D.prototype ), {
+
+       constructor: Mesh,
+
+       isMesh: true,
+
+       copy: function ( source ) {
+
+               Object3D.prototype.copy.call( this, source );
+
+               if ( source.morphTargetInfluences !== undefined ) {
+
+                       this.morphTargetInfluences = source.morphTargetInfluences.slice();
+
+               }
+
+               if ( source.morphTargetDictionary !== undefined ) {
+
+                       this.morphTargetDictionary = Object.assign( {}, source.morphTargetDictionary );
+
+               }
+
+               this.material = source.material;
+               this.geometry = source.geometry;
+
+               return this;
+
+       },
+
+       updateMorphTargets: function () {
+
+               const geometry = this.geometry;
+
+               if ( geometry.isBufferGeometry ) {
+
+                       const morphAttributes = geometry.morphAttributes;
+                       const keys = Object.keys( morphAttributes );
+
+                       if ( keys.length > 0 ) {
+
+                               const morphAttribute = morphAttributes[ keys[ 0 ] ];
+
+                               if ( morphAttribute !== undefined ) {
+
+                                       this.morphTargetInfluences = [];
+                                       this.morphTargetDictionary = {};
+
+                                       for ( let m = 0, ml = morphAttribute.length; m < ml; m ++ ) {
+
+                                               const name = morphAttribute[ m ].name || String( m );
+
+                                               this.morphTargetInfluences.push( 0 );
+                                               this.morphTargetDictionary[ name ] = m;
+
+                                       }
+
+                               }
+
+                       }
+
+               } else {
+
+                       const morphTargets = geometry.morphTargets;
+
+                       if ( morphTargets !== undefined && morphTargets.length > 0 ) {
+
+                               console.error( 'THREE.Mesh.updateMorphTargets() no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.' );
+
+                       }
+
+               }
+
+       },
+
+       raycast: function ( raycaster, intersects ) {
+
+               const geometry = this.geometry;
+               const material = this.material;
+               const matrixWorld = this.matrixWorld;
+
+               if ( material === undefined ) return;
+
+               // Checking boundingSphere distance to ray
+
+               if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();
+
+               _sphere.copy( geometry.boundingSphere );
+               _sphere.applyMatrix4( matrixWorld );
+
+               if ( raycaster.ray.intersectsSphere( _sphere ) === false ) return;
+
+               //
+
+               _inverseMatrix.copy( matrixWorld ).invert();
+               _ray.copy( raycaster.ray ).applyMatrix4( _inverseMatrix );
+
+               // Check boundingBox before continuing
+
+               if ( geometry.boundingBox !== null ) {
+
+                       if ( _ray.intersectsBox( geometry.boundingBox ) === false ) return;
+
+               }
+
+               let intersection;
+
+               if ( geometry.isBufferGeometry ) {
+
+                       const index = geometry.index;
+                       const position = geometry.attributes.position;
+                       const morphPosition = geometry.morphAttributes.position;
+                       const morphTargetsRelative = geometry.morphTargetsRelative;
+                       const uv = geometry.attributes.uv;
+                       const uv2 = geometry.attributes.uv2;
+                       const groups = geometry.groups;
+                       const drawRange = geometry.drawRange;
+
+                       if ( index !== null ) {
+
+                               // indexed buffer geometry
+
+                               if ( Array.isArray( material ) ) {
+
+                                       for ( let i = 0, il = groups.length; i < il; i ++ ) {
+
+                                               const group = groups[ i ];
+                                               const groupMaterial = material[ group.materialIndex ];
+
+                                               const start = Math.max( group.start, drawRange.start );
+                                               const end = Math.min( ( group.start + group.count ), ( drawRange.start + drawRange.count ) );
+
+                                               for ( let j = start, jl = end; j < jl; j += 3 ) {
+
+                                                       const a = index.getX( j );
+                                                       const b = index.getX( j + 1 );
+                                                       const c = index.getX( j + 2 );
+
+                                                       intersection = checkBufferGeometryIntersection( this, groupMaterial, raycaster, _ray, position, morphPosition, morphTargetsRelative, uv, uv2, a, b, c );
+
+                                                       if ( intersection ) {
+
+                                                               intersection.faceIndex = Math.floor( j / 3 ); // triangle number in indexed buffer semantics
+                                                               intersection.face.materialIndex = group.materialIndex;
+                                                               intersects.push( intersection );
+
+                                                       }
+
+                                               }
+
+                                       }
+
+                               } else {
+
+                                       const start = Math.max( 0, drawRange.start );
+                                       const end = Math.min( index.count, ( drawRange.start + drawRange.count ) );
+
+                                       for ( let i = start, il = end; i < il; i += 3 ) {
+
+                                               const a = index.getX( i );
+                                               const b = index.getX( i + 1 );
+                                               const c = index.getX( i + 2 );
+
+                                               intersection = checkBufferGeometryIntersection( this, material, raycaster, _ray, position, morphPosition, morphTargetsRelative, uv, uv2, a, b, c );
+
+                                               if ( intersection ) {
+
+                                                       intersection.faceIndex = Math.floor( i / 3 ); // triangle number in indexed buffer semantics
+                                                       intersects.push( intersection );
+
+                                               }
+
+                                       }
+
+                               }
+
+                       } else if ( position !== undefined ) {
+
+                               // non-indexed buffer geometry
+
+                               if ( Array.isArray( material ) ) {
+
+                                       for ( let i = 0, il = groups.length; i < il; i ++ ) {
+
+                                               const group = groups[ i ];
+                                               const groupMaterial = material[ group.materialIndex ];
+
+                                               const start = Math.max( group.start, drawRange.start );
+                                               const end = Math.min( ( group.start + group.count ), ( drawRange.start + drawRange.count ) );
+
+                                               for ( let j = start, jl = end; j < jl; j += 3 ) {
+
+                                                       const a = j;
+                                                       const b = j + 1;
+                                                       const c = j + 2;
+
+                                                       intersection = checkBufferGeometryIntersection( this, groupMaterial, raycaster, _ray, position, morphPosition, morphTargetsRelative, uv, uv2, a, b, c );
+
+                                                       if ( intersection ) {
+
+                                                               intersection.faceIndex = Math.floor( j / 3 ); // triangle number in non-indexed buffer semantics
+                                                               intersection.face.materialIndex = group.materialIndex;
+                                                               intersects.push( intersection );
+
+                                                       }
+
+                                               }
+
+                                       }
+
+                               } else {
+
+                                       const start = Math.max( 0, drawRange.start );
+                                       const end = Math.min( position.count, ( drawRange.start + drawRange.count ) );
+
+                                       for ( let i = start, il = end; i < il; i += 3 ) {
+
+                                               const a = i;
+                                               const b = i + 1;
+                                               const c = i + 2;
+
+                                               intersection = checkBufferGeometryIntersection( this, material, raycaster, _ray, position, morphPosition, morphTargetsRelative, uv, uv2, a, b, c );
+
+                                               if ( intersection ) {
+
+                                                       intersection.faceIndex = Math.floor( i / 3 ); // triangle number in non-indexed buffer semantics
+                                                       intersects.push( intersection );
+
+                                               }
+
+                                       }
+
+                               }
+
+                       }
+
+               } else if ( geometry.isGeometry ) {
+
+                       console.error( 'THREE.Mesh.raycast() no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.' );
+
+               }
+
+       }
+
+} );
+
+function checkIntersection( object, material, raycaster, ray, pA, pB, pC, point ) {
+
+       let intersect;
+
+       if ( material.side === BackSide ) {
+
+               intersect = ray.intersectTriangle( pC, pB, pA, true, point );
+
+       } else {
+
+               intersect = ray.intersectTriangle( pA, pB, pC, material.side !== DoubleSide, point );
+
+       }
+
+       if ( intersect === null ) return null;
+
+       _intersectionPointWorld.copy( point );
+       _intersectionPointWorld.applyMatrix4( object.matrixWorld );
+
+       const distance = raycaster.ray.origin.distanceTo( _intersectionPointWorld );
+
+       if ( distance < raycaster.near || distance > raycaster.far ) return null;
+
+       return {
+               distance: distance,
+               point: _intersectionPointWorld.clone(),
+               object: object
+       };
+
+}
+
+function checkBufferGeometryIntersection( object, material, raycaster, ray, position, morphPosition, morphTargetsRelative, uv, uv2, a, b, c ) {
+
+       _vA.fromBufferAttribute( position, a );
+       _vB.fromBufferAttribute( position, b );
+       _vC.fromBufferAttribute( position, c );
+
+       const morphInfluences = object.morphTargetInfluences;
+
+       if ( material.morphTargets && morphPosition && morphInfluences ) {
+
+               _morphA.set( 0, 0, 0 );
+               _morphB.set( 0, 0, 0 );
+               _morphC.set( 0, 0, 0 );
+
+               for ( let i = 0, il = morphPosition.length; i < il; i ++ ) {
+
+                       const influence = morphInfluences[ i ];
+                       const morphAttribute = morphPosition[ i ];
+
+                       if ( influence === 0 ) continue;
+
+                       _tempA.fromBufferAttribute( morphAttribute, a );
+                       _tempB.fromBufferAttribute( morphAttribute, b );
+                       _tempC.fromBufferAttribute( morphAttribute, c );
+
+                       if ( morphTargetsRelative ) {
+
+                               _morphA.addScaledVector( _tempA, influence );
+                               _morphB.addScaledVector( _tempB, influence );
+                               _morphC.addScaledVector( _tempC, influence );
+
+                       } else {
+
+                               _morphA.addScaledVector( _tempA.sub( _vA ), influence );
+                               _morphB.addScaledVector( _tempB.sub( _vB ), influence );
+                               _morphC.addScaledVector( _tempC.sub( _vC ), influence );
+
+                       }
+
+               }
+
+               _vA.add( _morphA );
+               _vB.add( _morphB );
+               _vC.add( _morphC );
+
+       }
+
+       if ( object.isSkinnedMesh ) {
+
+               object.boneTransform( a, _vA );
+               object.boneTransform( b, _vB );
+               object.boneTransform( c, _vC );
+
+       }
+
+       const intersection = checkIntersection( object, material, raycaster, ray, _vA, _vB, _vC, _intersectionPoint );
+
+       if ( intersection ) {
+
+               if ( uv ) {
+
+                       _uvA.fromBufferAttribute( uv, a );
+                       _uvB.fromBufferAttribute( uv, b );
+                       _uvC.fromBufferAttribute( uv, c );
+
+                       intersection.uv = Triangle.getUV( _intersectionPoint, _vA, _vB, _vC, _uvA, _uvB, _uvC, new Vector2() );
+
+               }
+
+               if ( uv2 ) {
+
+                       _uvA.fromBufferAttribute( uv2, a );
+                       _uvB.fromBufferAttribute( uv2, b );
+                       _uvC.fromBufferAttribute( uv2, c );
+
+                       intersection.uv2 = Triangle.getUV( _intersectionPoint, _vA, _vB, _vC, _uvA, _uvB, _uvC, new Vector2() );
+
+               }
+
+               const face = new Face3( a, b, c );
+               Triangle.getNormal( _vA, _vB, _vC, face.normal );
+
+               intersection.face = face;
+
+       }
+
+       return intersection;
+
+}
+
+class BoxGeometry extends BufferGeometry {
+
+       constructor( width = 1, height = 1, depth = 1, widthSegments = 1, heightSegments = 1, depthSegments = 1 ) {
+
+               super();
+
+               this.type = 'BoxGeometry';
+
+               this.parameters = {
+                       width: width,
+                       height: height,
+                       depth: depth,
+                       widthSegments: widthSegments,
+                       heightSegments: heightSegments,
+                       depthSegments: depthSegments
+               };
+
+               const scope = this;
+
+               // segments
+
+               widthSegments = Math.floor( widthSegments );
+               heightSegments = Math.floor( heightSegments );
+               depthSegments = Math.floor( depthSegments );
+
+               // buffers
+
+               const indices = [];
+               const vertices = [];
+               const normals = [];
+               const uvs = [];
+
+               // helper variables
+
+               let numberOfVertices = 0;
+               let groupStart = 0;
+
+               // build each side of the box geometry
+
+               buildPlane( 'z', 'y', 'x', - 1, - 1, depth, height, width, depthSegments, heightSegments, 0 ); // px
+               buildPlane( 'z', 'y', 'x', 1, - 1, depth, height, - width, depthSegments, heightSegments, 1 ); // nx
+               buildPlane( 'x', 'z', 'y', 1, 1, width, depth, height, widthSegments, depthSegments, 2 ); // py
+               buildPlane( 'x', 'z', 'y', 1, - 1, width, depth, - height, widthSegments, depthSegments, 3 ); // ny
+               buildPlane( 'x', 'y', 'z', 1, - 1, width, height, depth, widthSegments, heightSegments, 4 ); // pz
+               buildPlane( 'x', 'y', 'z', - 1, - 1, width, height, - depth, widthSegments, heightSegments, 5 ); // nz
+
+               // build geometry
+
+               this.setIndex( indices );
+               this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
+               this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );
+               this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );
+
+               function buildPlane( u, v, w, udir, vdir, width, height, depth, gridX, gridY, materialIndex ) {
+
+                       const segmentWidth = width / gridX;
+                       const segmentHeight = height / gridY;
+
+                       const widthHalf = width / 2;
+                       const heightHalf = height / 2;
+                       const depthHalf = depth / 2;
+
+                       const gridX1 = gridX + 1;
+                       const gridY1 = gridY + 1;
+
+                       let vertexCounter = 0;
+                       let groupCount = 0;
+
+                       const vector = new Vector3();
+
+                       // generate vertices, normals and uvs
+
+                       for ( let iy = 0; iy < gridY1; iy ++ ) {
+
+                               const y = iy * segmentHeight - heightHalf;
+
+                               for ( let ix = 0; ix < gridX1; ix ++ ) {
+
+                                       const x = ix * segmentWidth - widthHalf;
+
+                                       // set values to correct vector component
+
+                                       vector[ u ] = x * udir;
+                                       vector[ v ] = y * vdir;
+                                       vector[ w ] = depthHalf;
+
+                                       // now apply vector to vertex buffer
+
+                                       vertices.push( vector.x, vector.y, vector.z );
+
+                                       // set values to correct vector component
+
+                                       vector[ u ] = 0;
+                                       vector[ v ] = 0;
+                                       vector[ w ] = depth > 0 ? 1 : - 1;
+
+                                       // now apply vector to normal buffer
+
+                                       normals.push( vector.x, vector.y, vector.z );
+
+                                       // uvs
+
+                                       uvs.push( ix / gridX );
+                                       uvs.push( 1 - ( iy / gridY ) );
+
+                                       // counters
+
+                                       vertexCounter += 1;
+
+                               }
+
+                       }
+
+                       // indices
+
+                       // 1. you need three indices to draw a single face
+                       // 2. a single segment consists of two faces
+                       // 3. so we need to generate six (2*3) indices per segment
+
+                       for ( let iy = 0; iy < gridY; iy ++ ) {
+
+                               for ( let ix = 0; ix < gridX; ix ++ ) {
+
+                                       const a = numberOfVertices + ix + gridX1 * iy;
+                                       const b = numberOfVertices + ix + gridX1 * ( iy + 1 );
+                                       const c = numberOfVertices + ( ix + 1 ) + gridX1 * ( iy + 1 );
+                                       const d = numberOfVertices + ( ix + 1 ) + gridX1 * iy;
+
+                                       // faces
+
+                                       indices.push( a, b, d );
+                                       indices.push( b, c, d );
+
+                                       // increase counter
+
+                                       groupCount += 6;
+
+                               }
+
+                       }
+
+                       // add a group to the geometry. this will ensure multi material support
+
+                       scope.addGroup( groupStart, groupCount, materialIndex );
+
+                       // calculate new start value for groups
+
+                       groupStart += groupCount;
+
+                       // update total number of vertices
+
+                       numberOfVertices += vertexCounter;
+
+               }
+
+       }
+
+}
+
+/**
+ * Uniform Utilities
+ */
+
+function cloneUniforms( src ) {
+
+       const dst = {};
+
+       for ( const u in src ) {
+
+               dst[ u ] = {};
+
+               for ( const p in src[ u ] ) {
+
+                       const property = src[ u ][ p ];
+
+                       if ( property && ( property.isColor ||
+                               property.isMatrix3 || property.isMatrix4 ||
+                               property.isVector2 || property.isVector3 || property.isVector4 ||
+                               property.isTexture ) ) {
+
+                               dst[ u ][ p ] = property.clone();
+
+                       } else if ( Array.isArray( property ) ) {
+
+                               dst[ u ][ p ] = property.slice();
+
+                       } else {
+
+                               dst[ u ][ p ] = property;
+
+                       }
+
+               }
+
+       }
+
+       return dst;
+
+}
+
+function mergeUniforms( uniforms ) {
+
+       const merged = {};
+
+       for ( let u = 0; u < uniforms.length; u ++ ) {
+
+               const tmp = cloneUniforms( uniforms[ u ] );
+
+               for ( const p in tmp ) {
+
+                       merged[ p ] = tmp[ p ];
+
+               }
+
+       }
+
+       return merged;
+
+}
+
+// Legacy
+
+const UniformsUtils = { clone: cloneUniforms, merge: mergeUniforms };
+
+var default_vertex = "void main() {\n\tgl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n}";
+
+var default_fragment = "void main() {\n\tgl_FragColor = vec4( 1.0, 0.0, 0.0, 1.0 );\n}";
+
+/**
+ * parameters = {
+ *  defines: { "label" : "value" },
+ *  uniforms: { "parameter1": { value: 1.0 }, "parameter2": { value2: 2 } },
+ *
+ *  fragmentShader: <string>,
+ *  vertexShader: <string>,
+ *
+ *  wireframe: <boolean>,
+ *  wireframeLinewidth: <float>,
+ *
+ *  lights: <bool>,
+ *
+ *  skinning: <bool>,
+ *  morphTargets: <bool>,
+ *  morphNormals: <bool>
+ * }
+ */
+
+function ShaderMaterial( parameters ) {
+
+       Material.call( this );
+
+       this.type = 'ShaderMaterial';
+
+       this.defines = {};
+       this.uniforms = {};
+
+       this.vertexShader = default_vertex;
+       this.fragmentShader = default_fragment;
+
+       this.linewidth = 1;
+
+       this.wireframe = false;
+       this.wireframeLinewidth = 1;
+
+       this.fog = false; // set to use scene fog
+       this.lights = false; // set to use scene lights
+       this.clipping = false; // set to use user-defined clipping planes
+
+       this.skinning = false; // set to use skinning attribute streams
+       this.morphTargets = false; // set to use morph targets
+       this.morphNormals = false; // set to use morph normals
+
+       this.extensions = {
+               derivatives: false, // set to use derivatives
+               fragDepth: false, // set to use fragment depth values
+               drawBuffers: false, // set to use draw buffers
+               shaderTextureLOD: false // set to use shader texture LOD
+       };
+
+       // When rendered geometry doesn't include these attributes but the material does,
+       // use these default values in WebGL. This avoids errors when buffer data is missing.
+       this.defaultAttributeValues = {
+               'color': [ 1, 1, 1 ],
+               'uv': [ 0, 0 ],
+               'uv2': [ 0, 0 ]
+       };
+
+       this.index0AttributeName = undefined;
+       this.uniformsNeedUpdate = false;
+
+       this.glslVersion = null;
+
+       if ( parameters !== undefined ) {
+
+               if ( parameters.attributes !== undefined ) {
+
+                       console.error( 'THREE.ShaderMaterial: attributes should now be defined in THREE.BufferGeometry instead.' );
+
+               }
+
+               this.setValues( parameters );
+
+       }
+
+}
+
+ShaderMaterial.prototype = Object.create( Material.prototype );
+ShaderMaterial.prototype.constructor = ShaderMaterial;
+
+ShaderMaterial.prototype.isShaderMaterial = true;
+
+ShaderMaterial.prototype.copy = function ( source ) {
+
+       Material.prototype.copy.call( this, source );
+
+       this.fragmentShader = source.fragmentShader;
+       this.vertexShader = source.vertexShader;
+
+       this.uniforms = cloneUniforms( source.uniforms );
+
+       this.defines = Object.assign( {}, source.defines );
+
+       this.wireframe = source.wireframe;
+       this.wireframeLinewidth = source.wireframeLinewidth;
+
+       this.lights = source.lights;
+       this.clipping = source.clipping;
+
+       this.skinning = source.skinning;
+
+       this.morphTargets = source.morphTargets;
+       this.morphNormals = source.morphNormals;
+
+       this.extensions = Object.assign( {}, source.extensions );
+
+       this.glslVersion = source.glslVersion;
+
+       return this;
+
+};
+
+ShaderMaterial.prototype.toJSON = function ( meta ) {
+
+       const data = Material.prototype.toJSON.call( this, meta );
+
+       data.glslVersion = this.glslVersion;
+       data.uniforms = {};
+
+       for ( const name in this.uniforms ) {
+
+               const uniform = this.uniforms[ name ];
+               const value = uniform.value;
+
+               if ( value && value.isTexture ) {
+
+                       data.uniforms[ name ] = {
+                               type: 't',
+                               value: value.toJSON( meta ).uuid
+                       };
+
+               } else if ( value && value.isColor ) {
+
+                       data.uniforms[ name ] = {
+                               type: 'c',
+                               value: value.getHex()
+                       };
+
+               } else if ( value && value.isVector2 ) {
+
+                       data.uniforms[ name ] = {
+                               type: 'v2',
+                               value: value.toArray()
+                       };
+
+               } else if ( value && value.isVector3 ) {
+
+                       data.uniforms[ name ] = {
+                               type: 'v3',
+                               value: value.toArray()
+                       };
+
+               } else if ( value && value.isVector4 ) {
+
+                       data.uniforms[ name ] = {
+                               type: 'v4',
+                               value: value.toArray()
+                       };
+
+               } else if ( value && value.isMatrix3 ) {
+
+                       data.uniforms[ name ] = {
+                               type: 'm3',
+                               value: value.toArray()
+                       };
+
+               } else if ( value && value.isMatrix4 ) {
+
+                       data.uniforms[ name ] = {
+                               type: 'm4',
+                               value: value.toArray()
+                       };
+
+               } else {
+
+                       data.uniforms[ name ] = {
+                               value: value
+                       };
+
+                       // note: the array variants v2v, v3v, v4v, m4v and tv are not supported so far
+
+               }
+
+       }
+
+       if ( Object.keys( this.defines ).length > 0 ) data.defines = this.defines;
+
+       data.vertexShader = this.vertexShader;
+       data.fragmentShader = this.fragmentShader;
+
+       const extensions = {};
+
+       for ( const key in this.extensions ) {
+
+               if ( this.extensions[ key ] === true ) extensions[ key ] = true;
+
+       }
+
+       if ( Object.keys( extensions ).length > 0 ) data.extensions = extensions;
+
+       return data;
+
+};
+
+function Camera$1() {
+
+       Object3D.call( this );
+
+       this.type = 'Camera';
+
+       this.matrixWorldInverse = new Matrix4();
+
+       this.projectionMatrix = new Matrix4();
+       this.projectionMatrixInverse = new Matrix4();
+
+}
+
+Camera$1.prototype = Object.assign( Object.create( Object3D.prototype ), {
+
+       constructor: Camera$1,
+
+       isCamera: true,
+
+       copy: function ( source, recursive ) {
+
+               Object3D.prototype.copy.call( this, source, recursive );
+
+               this.matrixWorldInverse.copy( source.matrixWorldInverse );
+
+               this.projectionMatrix.copy( source.projectionMatrix );
+               this.projectionMatrixInverse.copy( source.projectionMatrixInverse );
+
+               return this;
+
+       },
+
+       getWorldDirection: function ( target ) {
+
+               if ( target === undefined ) {
+
+                       console.warn( 'THREE.Camera: .getWorldDirection() target is now required' );
+                       target = new Vector3();
+
+               }
+
+               this.updateWorldMatrix( true, false );
+
+               const e = this.matrixWorld.elements;
+
+               return target.set( - e[ 8 ], - e[ 9 ], - e[ 10 ] ).normalize();
+
+       },
+
+       updateMatrixWorld: function ( force ) {
+
+               Object3D.prototype.updateMatrixWorld.call( this, force );
+
+               this.matrixWorldInverse.copy( this.matrixWorld ).invert();
+
+       },
+
+       updateWorldMatrix: function ( updateParents, updateChildren ) {
+
+               Object3D.prototype.updateWorldMatrix.call( this, updateParents, updateChildren );
+
+               this.matrixWorldInverse.copy( this.matrixWorld ).invert();
+
+       },
+
+       clone: function () {
+
+               return new this.constructor().copy( this );
+
+       }
+
+} );
+
+function PerspectiveCamera( fov = 50, aspect = 1, near = 0.1, far = 2000 ) {
+
+       Camera$1.call( this );
+
+       this.type = 'PerspectiveCamera';
+
+       this.fov = fov;
+       this.zoom = 1;
+
+       this.near = near;
+       this.far = far;
+       this.focus = 10;
+
+       this.aspect = aspect;
+       this.view = null;
+
+       this.filmGauge = 35;    // width of the film (default in millimeters)
+       this.filmOffset = 0;    // horizontal film offset (same unit as gauge)
+
+       this.updateProjectionMatrix();
+
+}
+
+PerspectiveCamera.prototype = Object.assign( Object.create( Camera$1.prototype ), {
+
+       constructor: PerspectiveCamera,
+
+       isPerspectiveCamera: true,
+
+       copy: function ( source, recursive ) {
+
+               Camera$1.prototype.copy.call( this, source, recursive );
+
+               this.fov = source.fov;
+               this.zoom = source.zoom;
+
+               this.near = source.near;
+               this.far = source.far;
+               this.focus = source.focus;
+
+               this.aspect = source.aspect;
+               this.view = source.view === null ? null : Object.assign( {}, source.view );
+
+               this.filmGauge = source.filmGauge;
+               this.filmOffset = source.filmOffset;
+
+               return this;
+
+       },
+
+       /**
+        * Sets the FOV by focal length in respect to the current .filmGauge.
+        *
+        * The default film gauge is 35, so that the focal length can be specified for
+        * a 35mm (full frame) camera.
+        *
+        * Values for focal length and film gauge must have the same unit.
+        */
+       setFocalLength: function ( focalLength ) {
+
+               /** see {@link http://www.bobatkins.com/photography/technical/field_of_view.html} */
+               const vExtentSlope = 0.5 * this.getFilmHeight() / focalLength;
+
+               this.fov = MathUtils.RAD2DEG * 2 * Math.atan( vExtentSlope );
+               this.updateProjectionMatrix();
+
+       },
+
+       /**
+        * Calculates the focal length from the current .fov and .filmGauge.
+        */
+       getFocalLength: function () {
+
+               const vExtentSlope = Math.tan( MathUtils.DEG2RAD * 0.5 * this.fov );
+
+               return 0.5 * this.getFilmHeight() / vExtentSlope;
+
+       },
+
+       getEffectiveFOV: function () {
+
+               return MathUtils.RAD2DEG * 2 * Math.atan(
+                       Math.tan( MathUtils.DEG2RAD * 0.5 * this.fov ) / this.zoom );
+
+       },
+
+       getFilmWidth: function () {
+
+               // film not completely covered in portrait format (aspect < 1)
+               return this.filmGauge * Math.min( this.aspect, 1 );
+
+       },
+
+       getFilmHeight: function () {
+
+               // film not completely covered in landscape format (aspect > 1)
+               return this.filmGauge / Math.max( this.aspect, 1 );
+
+       },
+
+       /**
+        * Sets an offset in a larger frustum. This is useful for multi-window or
+        * multi-monitor/multi-machine setups.
+        *
+        * For example, if you have 3x2 monitors and each monitor is 1920x1080 and
+        * the monitors are in grid like this
+        *
+        *   +---+---+---+
+        *   | A | B | C |
+        *   +---+---+---+
+        *   | D | E | F |
+        *   +---+---+---+
+        *
+        * then for each monitor you would call it like this
+        *
+        *   const w = 1920;
+        *   const h = 1080;
+        *   const fullWidth = w * 3;
+        *   const fullHeight = h * 2;
+        *
+        *   --A--
+        *   camera.setViewOffset( fullWidth, fullHeight, w * 0, h * 0, w, h );
+        *   --B--
+        *   camera.setViewOffset( fullWidth, fullHeight, w * 1, h * 0, w, h );
+        *   --C--
+        *   camera.setViewOffset( fullWidth, fullHeight, w * 2, h * 0, w, h );
+        *   --D--
+        *   camera.setViewOffset( fullWidth, fullHeight, w * 0, h * 1, w, h );
+        *   --E--
+        *   camera.setViewOffset( fullWidth, fullHeight, w * 1, h * 1, w, h );
+        *   --F--
+        *   camera.setViewOffset( fullWidth, fullHeight, w * 2, h * 1, w, h );
+        *
+        *   Note there is no reason monitors have to be the same size or in a grid.
+        */
+       setViewOffset: function ( fullWidth, fullHeight, x, y, width, height ) {
+
+               this.aspect = fullWidth / fullHeight;
+
+               if ( this.view === null ) {
+
+                       this.view = {
+                               enabled: true,
+                               fullWidth: 1,
+                               fullHeight: 1,
+                               offsetX: 0,
+                               offsetY: 0,
+                               width: 1,
+                               height: 1
+                       };
+
+               }
+
+               this.view.enabled = true;
+               this.view.fullWidth = fullWidth;
+               this.view.fullHeight = fullHeight;
+               this.view.offsetX = x;
+               this.view.offsetY = y;
+               this.view.width = width;
+               this.view.height = height;
+
+               this.updateProjectionMatrix();
+
+       },
+
+       clearViewOffset: function () {
+
+               if ( this.view !== null ) {
+
+                       this.view.enabled = false;
+
+               }
+
+               this.updateProjectionMatrix();
+
+       },
+
+       updateProjectionMatrix: function () {
+
+               const near = this.near;
+               let top = near * Math.tan( MathUtils.DEG2RAD * 0.5 * this.fov ) / this.zoom;
+               let height = 2 * top;
+               let width = this.aspect * height;
+               let left = - 0.5 * width;
+               const view = this.view;
+
+               if ( this.view !== null && this.view.enabled ) {
+
+                       const fullWidth = view.fullWidth,
+                               fullHeight = view.fullHeight;
+
+                       left += view.offsetX * width / fullWidth;
+                       top -= view.offsetY * height / fullHeight;
+                       width *= view.width / fullWidth;
+                       height *= view.height / fullHeight;
+
+               }
+
+               const skew = this.filmOffset;
+               if ( skew !== 0 ) left += near * skew / this.getFilmWidth();
+
+               this.projectionMatrix.makePerspective( left, left + width, top, top - height, near, this.far );
+
+               this.projectionMatrixInverse.copy( this.projectionMatrix ).invert();
+
+       },
+
+       toJSON: function ( meta ) {
+
+               const data = Object3D.prototype.toJSON.call( this, meta );
+
+               data.object.fov = this.fov;
+               data.object.zoom = this.zoom;
+
+               data.object.near = this.near;
+               data.object.far = this.far;
+               data.object.focus = this.focus;
+
+               data.object.aspect = this.aspect;
+
+               if ( this.view !== null ) data.object.view = Object.assign( {}, this.view );
+
+               data.object.filmGauge = this.filmGauge;
+               data.object.filmOffset = this.filmOffset;
+
+               return data;
+
+       }
+
+} );
+
+const fov = 90, aspect = 1;
+
+function CubeCamera( near, far, renderTarget ) {
+
+       Object3D.call( this );
+
+       this.type = 'CubeCamera';
+
+       if ( renderTarget.isWebGLCubeRenderTarget !== true ) {
+
+               console.error( 'THREE.CubeCamera: The constructor now expects an instance of WebGLCubeRenderTarget as third parameter.' );
+               return;
+
+       }
+
+       this.renderTarget = renderTarget;
+
+       const cameraPX = new PerspectiveCamera( fov, aspect, near, far );
+       cameraPX.layers = this.layers;
+       cameraPX.up.set( 0, - 1, 0 );
+       cameraPX.lookAt( new Vector3( 1, 0, 0 ) );
+       this.add( cameraPX );
+
+       const cameraNX = new PerspectiveCamera( fov, aspect, near, far );
+       cameraNX.layers = this.layers;
+       cameraNX.up.set( 0, - 1, 0 );
+       cameraNX.lookAt( new Vector3( - 1, 0, 0 ) );
+       this.add( cameraNX );
+
+       const cameraPY = new PerspectiveCamera( fov, aspect, near, far );
+       cameraPY.layers = this.layers;
+       cameraPY.up.set( 0, 0, 1 );
+       cameraPY.lookAt( new Vector3( 0, 1, 0 ) );
+       this.add( cameraPY );
+
+       const cameraNY = new PerspectiveCamera( fov, aspect, near, far );
+       cameraNY.layers = this.layers;
+       cameraNY.up.set( 0, 0, - 1 );
+       cameraNY.lookAt( new Vector3( 0, - 1, 0 ) );
+       this.add( cameraNY );
+
+       const cameraPZ = new PerspectiveCamera( fov, aspect, near, far );
+       cameraPZ.layers = this.layers;
+       cameraPZ.up.set( 0, - 1, 0 );
+       cameraPZ.lookAt( new Vector3( 0, 0, 1 ) );
+       this.add( cameraPZ );
+
+       const cameraNZ = new PerspectiveCamera( fov, aspect, near, far );
+       cameraNZ.layers = this.layers;
+       cameraNZ.up.set( 0, - 1, 0 );
+       cameraNZ.lookAt( new Vector3( 0, 0, - 1 ) );
+       this.add( cameraNZ );
+
+       this.update = function ( renderer, scene ) {
+
+               if ( this.parent === null ) this.updateMatrixWorld();
+
+               const currentXrEnabled = renderer.xr.enabled;
+               const currentRenderTarget = renderer.getRenderTarget();
+
+               renderer.xr.enabled = false;
+
+               const generateMipmaps = renderTarget.texture.generateMipmaps;
+
+               renderTarget.texture.generateMipmaps = false;
+
+               renderer.setRenderTarget( renderTarget, 0 );
+               renderer.render( scene, cameraPX );
+
+               renderer.setRenderTarget( renderTarget, 1 );
+               renderer.render( scene, cameraNX );
+
+               renderer.setRenderTarget( renderTarget, 2 );
+               renderer.render( scene, cameraPY );
+
+               renderer.setRenderTarget( renderTarget, 3 );
+               renderer.render( scene, cameraNY );
+
+               renderer.setRenderTarget( renderTarget, 4 );
+               renderer.render( scene, cameraPZ );
+
+               renderTarget.texture.generateMipmaps = generateMipmaps;
+
+               renderer.setRenderTarget( renderTarget, 5 );
+               renderer.render( scene, cameraNZ );
+
+               renderer.setRenderTarget( currentRenderTarget );
+
+               renderer.xr.enabled = currentXrEnabled;
+
+       };
+
+}
+
+CubeCamera.prototype = Object.create( Object3D.prototype );
+CubeCamera.prototype.constructor = CubeCamera;
+
+function CubeTexture( images, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, encoding ) {
+
+       images = images !== undefined ? images : [];
+       mapping = mapping !== undefined ? mapping : CubeReflectionMapping;
+       format = format !== undefined ? format : RGBFormat;
+
+       Texture.call( this, images, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, encoding );
+
+       this.flipY = false;
+
+       // Why CubeTexture._needsFlipEnvMap is necessary:
+       //
+       // By convention -- likely based on the RenderMan spec from the 1990's -- cube maps are specified by WebGL (and three.js)
+       // in a coordinate system in which positive-x is to the right when looking up the positive-z axis -- in other words,
+       // in a left-handed coordinate system. By continuing this convention, preexisting cube maps continued to render correctly.
+
+       // three.js uses a right-handed coordinate system. So environment maps used in three.js appear to have px and nx swapped
+       // and the flag _needsFlipEnvMap controls this conversion. The flip is not required (and thus _needsFlipEnvMap is set to false)
+       // when using WebGLCubeRenderTarget.texture as a cube texture.
+
+       this._needsFlipEnvMap = true;
+
+}
+
+CubeTexture.prototype = Object.create( Texture.prototype );
+CubeTexture.prototype.constructor = CubeTexture;
+
+CubeTexture.prototype.isCubeTexture = true;
+
+Object.defineProperty( CubeTexture.prototype, 'images', {
+
+       get: function () {
+
+               return this.image;
+
+       },
+
+       set: function ( value ) {
+
+               this.image = value;
+
+       }
+
+} );
+
+class WebGLCubeRenderTarget extends WebGLRenderTarget {
+
+       constructor( size, options, dummy ) {
+
+               if ( Number.isInteger( options ) ) {
+
+                       console.warn( 'THREE.WebGLCubeRenderTarget: constructor signature is now WebGLCubeRenderTarget( size, options )' );
+
+                       options = dummy;
+
+               }
+
+               super( size, size, options );
+
+               Object.defineProperty( this, 'isWebGLCubeRenderTarget', { value: true } );
+
+               options = options || {};
+
+               this.texture = new CubeTexture( undefined, options.mapping, options.wrapS, options.wrapT, options.magFilter, options.minFilter, options.format, options.type, options.anisotropy, options.encoding );
+
+               this.texture._needsFlipEnvMap = false;
+
+       }
+
+       fromEquirectangularTexture( renderer, texture ) {
+
+               this.texture.type = texture.type;
+               this.texture.format = RGBAFormat; // see #18859
+               this.texture.encoding = texture.encoding;
+
+               this.texture.generateMipmaps = texture.generateMipmaps;
+               this.texture.minFilter = texture.minFilter;
+               this.texture.magFilter = texture.magFilter;
+
+               const shader = {
+
+                       uniforms: {
+                               tEquirect: { value: null },
+                       },
+
+                       vertexShader: /* glsl */`
+
+                               varying vec3 vWorldDirection;
+
+                               vec3 transformDirection( in vec3 dir, in mat4 matrix ) {
+
+                                       return normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );
+
+                               }
+
+                               void main() {
+
+                                       vWorldDirection = transformDirection( position, modelMatrix );
+
+                                       #include <begin_vertex>
+                                       #include <project_vertex>
+
+                               }
+                       `,
+
+                       fragmentShader: /* glsl */`
+
+                               uniform sampler2D tEquirect;
+
+                               varying vec3 vWorldDirection;
+
+                               #include <common>
+
+                               void main() {
+
+                                       vec3 direction = normalize( vWorldDirection );
+
+                                       vec2 sampleUV = equirectUv( direction );
+
+                                       gl_FragColor = texture2D( tEquirect, sampleUV );
+
+                               }
+                       `
+               };
+
+               const geometry = new BoxGeometry( 5, 5, 5 );
+
+               const material = new ShaderMaterial( {
+
+                       name: 'CubemapFromEquirect',
+
+                       uniforms: cloneUniforms( shader.uniforms ),
+                       vertexShader: shader.vertexShader,
+                       fragmentShader: shader.fragmentShader,
+                       side: BackSide,
+                       blending: NoBlending
+
+               } );
+
+               material.uniforms.tEquirect.value = texture;
+
+               const mesh = new Mesh( geometry, material );
+
+               const currentMinFilter = texture.minFilter;
+
+               // Avoid blurred poles
+               if ( texture.minFilter === LinearMipmapLinearFilter ) texture.minFilter = LinearFilter;
+
+               const camera = new CubeCamera( 1, 10, this );
+               camera.update( renderer, mesh );
+
+               texture.minFilter = currentMinFilter;
+
+               mesh.geometry.dispose();
+               mesh.material.dispose();
+
+               return this;
+
+       }
+
+       clear( renderer, color, depth, stencil ) {
+
+               const currentRenderTarget = renderer.getRenderTarget();
+
+               for ( let i = 0; i < 6; i ++ ) {
+
+                       renderer.setRenderTarget( this, i );
+
+                       renderer.clear( color, depth, stencil );
+
+               }
+
+               renderer.setRenderTarget( currentRenderTarget );
+
+       }
+
+}
+
+function DataTexture( data, width, height, format, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, encoding ) {
+
+       Texture.call( this, null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, encoding );
+
+       this.image = { data: data || null, width: width || 1, height: height || 1 };
+
+       this.magFilter = magFilter !== undefined ? magFilter : NearestFilter;
+       this.minFilter = minFilter !== undefined ? minFilter : NearestFilter;
+
+       this.generateMipmaps = false;
+       this.flipY = false;
+       this.unpackAlignment = 1;
+
+       this.needsUpdate = true;
+
+}
+
+DataTexture.prototype = Object.create( Texture.prototype );
+DataTexture.prototype.constructor = DataTexture;
+
+DataTexture.prototype.isDataTexture = true;
+
+const _sphere$1 = /*@__PURE__*/ new Sphere();
+const _vector$5 = /*@__PURE__*/ new Vector3();
+
+class Frustum {
+
+       constructor( p0, p1, p2, p3, p4, p5 ) {
+
+               this.planes = [
+
+                       ( p0 !== undefined ) ? p0 : new Plane(),
+                       ( p1 !== undefined ) ? p1 : new Plane(),
+                       ( p2 !== undefined ) ? p2 : new Plane(),
+                       ( p3 !== undefined ) ? p3 : new Plane(),
+                       ( p4 !== undefined ) ? p4 : new Plane(),
+                       ( p5 !== undefined ) ? p5 : new Plane()
+
+               ];
+
+       }
+
+       set( p0, p1, p2, p3, p4, p5 ) {
+
+               const planes = this.planes;
+
+               planes[ 0 ].copy( p0 );
+               planes[ 1 ].copy( p1 );
+               planes[ 2 ].copy( p2 );
+               planes[ 3 ].copy( p3 );
+               planes[ 4 ].copy( p4 );
+               planes[ 5 ].copy( p5 );
+
+               return this;
+
+       }
+
+       clone() {
+
+               return new this.constructor().copy( this );
+
+       }
+
+       copy( frustum ) {
+
+               const planes = this.planes;
+
+               for ( let i = 0; i < 6; i ++ ) {
+
+                       planes[ i ].copy( frustum.planes[ i ] );
+
+               }
+
+               return this;
+
+       }
+
+       setFromProjectionMatrix( m ) {
+
+               const planes = this.planes;
+               const me = m.elements;
+               const me0 = me[ 0 ], me1 = me[ 1 ], me2 = me[ 2 ], me3 = me[ 3 ];
+               const me4 = me[ 4 ], me5 = me[ 5 ], me6 = me[ 6 ], me7 = me[ 7 ];
+               const me8 = me[ 8 ], me9 = me[ 9 ], me10 = me[ 10 ], me11 = me[ 11 ];
+               const me12 = me[ 12 ], me13 = me[ 13 ], me14 = me[ 14 ], me15 = me[ 15 ];
+
+               planes[ 0 ].setComponents( me3 - me0, me7 - me4, me11 - me8, me15 - me12 ).normalize();
+               planes[ 1 ].setComponents( me3 + me0, me7 + me4, me11 + me8, me15 + me12 ).normalize();
+               planes[ 2 ].setComponents( me3 + me1, me7 + me5, me11 + me9, me15 + me13 ).normalize();
+               planes[ 3 ].setComponents( me3 - me1, me7 - me5, me11 - me9, me15 - me13 ).normalize();
+               planes[ 4 ].setComponents( me3 - me2, me7 - me6, me11 - me10, me15 - me14 ).normalize();
+               planes[ 5 ].setComponents( me3 + me2, me7 + me6, me11 + me10, me15 + me14 ).normalize();
+
+               return this;
+
+       }
+
+       intersectsObject( object ) {
+
+               const geometry = object.geometry;
+
+               if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();
+
+               _sphere$1.copy( geometry.boundingSphere ).applyMatrix4( object.matrixWorld );
+
+               return this.intersectsSphere( _sphere$1 );
+
+       }
+
+       intersectsSprite( sprite ) {
+
+               _sphere$1.center.set( 0, 0, 0 );
+               _sphere$1.radius = 0.7071067811865476;
+               _sphere$1.applyMatrix4( sprite.matrixWorld );
+
+               return this.intersectsSphere( _sphere$1 );
+
+       }
+
+       intersectsSphere( sphere ) {
+
+               const planes = this.planes;
+               const center = sphere.center;
+               const negRadius = - sphere.radius;
+
+               for ( let i = 0; i < 6; i ++ ) {
+
+                       const distance = planes[ i ].distanceToPoint( center );
+
+                       if ( distance < negRadius ) {
+
+                               return false;
+
+                       }
+
+               }
+
+               return true;
+
+       }
+
+       intersectsBox( box ) {
+
+               const planes = this.planes;
+
+               for ( let i = 0; i < 6; i ++ ) {
+
+                       const plane = planes[ i ];
+
+                       // corner at max distance
+
+                       _vector$5.x = plane.normal.x > 0 ? box.max.x : box.min.x;
+                       _vector$5.y = plane.normal.y > 0 ? box.max.y : box.min.y;
+                       _vector$5.z = plane.normal.z > 0 ? box.max.z : box.min.z;
+
+                       if ( plane.distanceToPoint( _vector$5 ) < 0 ) {
+
+                               return false;
+
+                       }
+
+               }
+
+               return true;
+
+       }
+
+       containsPoint( point ) {
+
+               const planes = this.planes;
+
+               for ( let i = 0; i < 6; i ++ ) {
+
+                       if ( planes[ i ].distanceToPoint( point ) < 0 ) {
+
+                               return false;
+
+                       }
+
+               }
+
+               return true;
+
+       }
+
+}
+
+function WebGLAnimation() {
+
+       let context = null;
+       let isAnimating = false;
+       let animationLoop = null;
+       let requestId = null;
+
+       function onAnimationFrame( time, frame ) {
+
+               animationLoop( time, frame );
+
+               requestId = context.requestAnimationFrame( onAnimationFrame );
+
+       }
+
+       return {
+
+               start: function () {
+
+                       if ( isAnimating === true ) return;
+                       if ( animationLoop === null ) return;
+
+                       requestId = context.requestAnimationFrame( onAnimationFrame );
+
+                       isAnimating = true;
+
+               },
+
+               stop: function () {
+
+                       context.cancelAnimationFrame( requestId );
+
+                       isAnimating = false;
+
+               },
+
+               setAnimationLoop: function ( callback ) {
+
+                       animationLoop = callback;
+
+               },
+
+               setContext: function ( value ) {
+
+                       context = value;
+
+               }
+
+       };
+
+}
+
+function WebGLAttributes( gl, capabilities ) {
+
+       const isWebGL2 = capabilities.isWebGL2;
+
+       const buffers = new WeakMap();
+
+       function createBuffer( attribute, bufferType ) {
+
+               const array = attribute.array;
+               const usage = attribute.usage;
+
+               const buffer = gl.createBuffer();
+
+               gl.bindBuffer( bufferType, buffer );
+               gl.bufferData( bufferType, array, usage );
+
+               attribute.onUploadCallback();
+
+               let type = 5126;
+
+               if ( array instanceof Float32Array ) {
+
+                       type = 5126;
+
+               } else if ( array instanceof Float64Array ) {
+
+                       console.warn( 'THREE.WebGLAttributes: Unsupported data buffer format: Float64Array.' );
+
+               } else if ( array instanceof Uint16Array ) {
+
+                       if ( attribute.isFloat16BufferAttribute ) {
+
+                               if ( isWebGL2 ) {
+
+                                       type = 5131;
+
+                               } else {
+
+                                       console.warn( 'THREE.WebGLAttributes: Usage of Float16BufferAttribute requires WebGL2.' );
+
+                               }
+
+                       } else {
+
+                               type = 5123;
+
+                       }
+
+               } else if ( array instanceof Int16Array ) {
+
+                       type = 5122;
+
+               } else if ( array instanceof Uint32Array ) {
+
+                       type = 5125;
+
+               } else if ( array instanceof Int32Array ) {
+
+                       type = 5124;
+
+               } else if ( array instanceof Int8Array ) {
+
+                       type = 5120;
+
+               } else if ( array instanceof Uint8Array ) {
+
+                       type = 5121;
+
+               }
+
+               return {
+                       buffer: buffer,
+                       type: type,
+                       bytesPerElement: array.BYTES_PER_ELEMENT,
+                       version: attribute.version
+               };
+
+       }
+
+       function updateBuffer( buffer, attribute, bufferType ) {
+
+               const array = attribute.array;
+               const updateRange = attribute.updateRange;
+
+               gl.bindBuffer( bufferType, buffer );
+
+               if ( updateRange.count === - 1 ) {
+
+                       // Not using update ranges
+
+                       gl.bufferSubData( bufferType, 0, array );
+
+               } else {
+
+                       if ( isWebGL2 ) {
+
+                               gl.bufferSubData( bufferType, updateRange.offset * array.BYTES_PER_ELEMENT,
+                                       array, updateRange.offset, updateRange.count );
+
+                       } else {
+
+                               gl.bufferSubData( bufferType, updateRange.offset * array.BYTES_PER_ELEMENT,
+                                       array.subarray( updateRange.offset, updateRange.offset + updateRange.count ) );
+
+                       }
+
+                       updateRange.count = - 1; // reset range
+
+               }
+
+       }
+
+       //
+
+       function get( attribute ) {
+
+               if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data;
+
+               return buffers.get( attribute );
+
+       }
+
+       function remove( attribute ) {
+
+               if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data;
+
+               const data = buffers.get( attribute );
+
+               if ( data ) {
+
+                       gl.deleteBuffer( data.buffer );
+
+                       buffers.delete( attribute );
+
+               }
+
+       }
+
+       function update( attribute, bufferType ) {
+
+               if ( attribute.isGLBufferAttribute ) {
+
+                       const cached = buffers.get( attribute );
+
+                       if ( ! cached || cached.version < attribute.version ) {
+
+                               buffers.set( attribute, {
+                                       buffer: attribute.buffer,
+                                       type: attribute.type,
+                                       bytesPerElement: attribute.elementSize,
+                                       version: attribute.version
+                               } );
+
+                       }
+
+                       return;
+
+               }
+
+               if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data;
+
+               const data = buffers.get( attribute );
+
+               if ( data === undefined ) {
+
+                       buffers.set( attribute, createBuffer( attribute, bufferType ) );
+
+               } else if ( data.version < attribute.version ) {
+
+                       updateBuffer( data.buffer, attribute, bufferType );
+
+                       data.version = attribute.version;
+
+               }
+
+       }
+
+       return {
+
+               get: get,
+               remove: remove,
+               update: update
+
+       };
+
+}
+
+class PlaneGeometry extends BufferGeometry {
+
+       constructor( width = 1, height = 1, widthSegments = 1, heightSegments = 1 ) {
+
+               super();
+               this.type = 'PlaneGeometry';
+
+               this.parameters = {
+                       width: width,
+                       height: height,
+                       widthSegments: widthSegments,
+                       heightSegments: heightSegments
+               };
+
+               const width_half = width / 2;
+               const height_half = height / 2;
+
+               const gridX = Math.floor( widthSegments );
+               const gridY = Math.floor( heightSegments );
+
+               const gridX1 = gridX + 1;
+               const gridY1 = gridY + 1;
+
+               const segment_width = width / gridX;
+               const segment_height = height / gridY;
+
+               //
+
+               const indices = [];
+               const vertices = [];
+               const normals = [];
+               const uvs = [];
+
+               for ( let iy = 0; iy < gridY1; iy ++ ) {
+
+                       const y = iy * segment_height - height_half;
+
+                       for ( let ix = 0; ix < gridX1; ix ++ ) {
+
+                               const x = ix * segment_width - width_half;
+
+                               vertices.push( x, - y, 0 );
+
+                               normals.push( 0, 0, 1 );
+
+                               uvs.push( ix / gridX );
+                               uvs.push( 1 - ( iy / gridY ) );
+
+                       }
+
+               }
+
+               for ( let iy = 0; iy < gridY; iy ++ ) {
+
+                       for ( let ix = 0; ix < gridX; ix ++ ) {
+
+                               const a = ix + gridX1 * iy;
+                               const b = ix + gridX1 * ( iy + 1 );
+                               const c = ( ix + 1 ) + gridX1 * ( iy + 1 );
+                               const d = ( ix + 1 ) + gridX1 * iy;
+
+                               indices.push( a, b, d );
+                               indices.push( b, c, d );
+
+                       }
+
+               }
+
+               this.setIndex( indices );
+               this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
+               this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );
+               this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );
+
+       }
+
+}
+
+var alphamap_fragment = "#ifdef USE_ALPHAMAP\n\tdiffuseColor.a *= texture2D( alphaMap, vUv ).g;\n#endif";
+
+var alphamap_pars_fragment = "#ifdef USE_ALPHAMAP\n\tuniform sampler2D alphaMap;\n#endif";
+
+var alphatest_fragment = "#ifdef ALPHATEST\n\tif ( diffuseColor.a < ALPHATEST ) discard;\n#endif";
+
+var aomap_fragment = "#ifdef USE_AOMAP\n\tfloat ambientOcclusion = ( texture2D( aoMap, vUv2 ).r - 1.0 ) * aoMapIntensity + 1.0;\n\treflectedLight.indirectDiffuse *= ambientOcclusion;\n\t#if defined( USE_ENVMAP ) && defined( STANDARD )\n\t\tfloat dotNV = saturate( dot( geometry.normal, geometry.viewDir ) );\n\t\treflectedLight.indirectSpecular *= computeSpecularOcclusion( dotNV, ambientOcclusion, material.specularRoughness );\n\t#endif\n#endif";
+
+var aomap_pars_fragment = "#ifdef USE_AOMAP\n\tuniform sampler2D aoMap;\n\tuniform float aoMapIntensity;\n#endif";
+
+var begin_vertex = "vec3 transformed = vec3( position );";
+
+var beginnormal_vertex = "vec3 objectNormal = vec3( normal );\n#ifdef USE_TANGENT\n\tvec3 objectTangent = vec3( tangent.xyz );\n#endif";
+
+var bsdfs = "vec2 integrateSpecularBRDF( const in float dotNV, const in float roughness ) {\n\tconst vec4 c0 = vec4( - 1, - 0.0275, - 0.572, 0.022 );\n\tconst vec4 c1 = vec4( 1, 0.0425, 1.04, - 0.04 );\n\tvec4 r = roughness * c0 + c1;\n\tfloat a004 = min( r.x * r.x, exp2( - 9.28 * dotNV ) ) * r.x + r.y;\n\treturn vec2( -1.04, 1.04 ) * a004 + r.zw;\n}\nfloat punctualLightIntensityToIrradianceFactor( const in float lightDistance, const in float cutoffDistance, const in float decayExponent ) {\n#if defined ( PHYSICALLY_CORRECT_LIGHTS )\n\tfloat distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), 0.01 );\n\tif( cutoffDistance > 0.0 ) {\n\t\tdistanceFalloff *= pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) );\n\t}\n\treturn distanceFalloff;\n#else\n\tif( cutoffDistance > 0.0 && decayExponent > 0.0 ) {\n\t\treturn pow( saturate( -lightDistance / cutoffDistance + 1.0 ), decayExponent );\n\t}\n\treturn 1.0;\n#endif\n}\nvec3 BRDF_Diffuse_Lambert( const in vec3 diffuseColor ) {\n\treturn RECIPROCAL_PI * diffuseColor;\n}\nvec3 F_Schlick( const in vec3 specularColor, const in float dotLH ) {\n\tfloat fresnel = exp2( ( -5.55473 * dotLH - 6.98316 ) * dotLH );\n\treturn ( 1.0 - specularColor ) * fresnel + specularColor;\n}\nvec3 F_Schlick_RoughnessDependent( const in vec3 F0, const in float dotNV, const in float roughness ) {\n\tfloat fresnel = exp2( ( -5.55473 * dotNV - 6.98316 ) * dotNV );\n\tvec3 Fr = max( vec3( 1.0 - roughness ), F0 ) - F0;\n\treturn Fr * fresnel + F0;\n}\nfloat G_GGX_Smith( const in float alpha, const in float dotNL, const in float dotNV ) {\n\tfloat a2 = pow2( alpha );\n\tfloat gl = dotNL + sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) );\n\tfloat gv = dotNV + sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) );\n\treturn 1.0 / ( gl * gv );\n}\nfloat G_GGX_SmithCorrelated( const in float alpha, const in float dotNL, const in float dotNV ) {\n\tfloat a2 = pow2( alpha );\n\tfloat gv = dotNL * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) );\n\tfloat gl = dotNV * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) );\n\treturn 0.5 / max( gv + gl, EPSILON );\n}\nfloat D_GGX( const in float alpha, const in float dotNH ) {\n\tfloat a2 = pow2( alpha );\n\tfloat denom = pow2( dotNH ) * ( a2 - 1.0 ) + 1.0;\n\treturn RECIPROCAL_PI * a2 / pow2( denom );\n}\nvec3 BRDF_Specular_GGX( const in IncidentLight incidentLight, const in vec3 viewDir, const in vec3 normal, const in vec3 specularColor, const in float roughness ) {\n\tfloat alpha = pow2( roughness );\n\tvec3 halfDir = normalize( incidentLight.direction + viewDir );\n\tfloat dotNL = saturate( dot( normal, incidentLight.direction ) );\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat dotLH = saturate( dot( incidentLight.direction, halfDir ) );\n\tvec3 F = F_Schlick( specularColor, dotLH );\n\tfloat G = G_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n\tfloat D = D_GGX( alpha, dotNH );\n\treturn F * ( G * D );\n}\nvec2 LTC_Uv( const in vec3 N, const in vec3 V, const in float roughness ) {\n\tconst float LUT_SIZE = 64.0;\n\tconst float LUT_SCALE = ( LUT_SIZE - 1.0 ) / LUT_SIZE;\n\tconst float LUT_BIAS = 0.5 / LUT_SIZE;\n\tfloat dotNV = saturate( dot( N, V ) );\n\tvec2 uv = vec2( roughness, sqrt( 1.0 - dotNV ) );\n\tuv = uv * LUT_SCALE + LUT_BIAS;\n\treturn uv;\n}\nfloat LTC_ClippedSphereFormFactor( const in vec3 f ) {\n\tfloat l = length( f );\n\treturn max( ( l * l + f.z ) / ( l + 1.0 ), 0.0 );\n}\nvec3 LTC_EdgeVectorFormFactor( const in vec3 v1, const in vec3 v2 ) {\n\tfloat x = dot( v1, v2 );\n\tfloat y = abs( x );\n\tfloat a = 0.8543985 + ( 0.4965155 + 0.0145206 * y ) * y;\n\tfloat b = 3.4175940 + ( 4.1616724 + y ) * y;\n\tfloat v = a / b;\n\tfloat theta_sintheta = ( x > 0.0 ) ? v : 0.5 * inversesqrt( max( 1.0 - x * x, 1e-7 ) ) - v;\n\treturn cross( v1, v2 ) * theta_sintheta;\n}\nvec3 LTC_Evaluate( const in vec3 N, const in vec3 V, const in vec3 P, const in mat3 mInv, const in vec3 rectCoords[ 4 ] ) {\n\tvec3 v1 = rectCoords[ 1 ] - rectCoords[ 0 ];\n\tvec3 v2 = rectCoords[ 3 ] - rectCoords[ 0 ];\n\tvec3 lightNormal = cross( v1, v2 );\n\tif( dot( lightNormal, P - rectCoords[ 0 ] ) < 0.0 ) return vec3( 0.0 );\n\tvec3 T1, T2;\n\tT1 = normalize( V - N * dot( V, N ) );\n\tT2 = - cross( N, T1 );\n\tmat3 mat = mInv * transposeMat3( mat3( T1, T2, N ) );\n\tvec3 coords[ 4 ];\n\tcoords[ 0 ] = mat * ( rectCoords[ 0 ] - P );\n\tcoords[ 1 ] = mat * ( rectCoords[ 1 ] - P );\n\tcoords[ 2 ] = mat * ( rectCoords[ 2 ] - P );\n\tcoords[ 3 ] = mat * ( rectCoords[ 3 ] - P );\n\tcoords[ 0 ] = normalize( coords[ 0 ] );\n\tcoords[ 1 ] = normalize( coords[ 1 ] );\n\tcoords[ 2 ] = normalize( coords[ 2 ] );\n\tcoords[ 3 ] = normalize( coords[ 3 ] );\n\tvec3 vectorFormFactor = vec3( 0.0 );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 0 ], coords[ 1 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 1 ], coords[ 2 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 2 ], coords[ 3 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 3 ], coords[ 0 ] );\n\tfloat result = LTC_ClippedSphereFormFactor( vectorFormFactor );\n\treturn vec3( result );\n}\nvec3 BRDF_Specular_GGX_Environment( const in vec3 viewDir, const in vec3 normal, const in vec3 specularColor, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tvec2 brdf = integrateSpecularBRDF( dotNV, roughness );\n\treturn specularColor * brdf.x + brdf.y;\n}\nvoid BRDF_Specular_Multiscattering_Environment( const in GeometricContext geometry, const in vec3 specularColor, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n\tfloat dotNV = saturate( dot( geometry.normal, geometry.viewDir ) );\n\tvec3 F = F_Schlick_RoughnessDependent( specularColor, dotNV, roughness );\n\tvec2 brdf = integrateSpecularBRDF( dotNV, roughness );\n\tvec3 FssEss = F * brdf.x + brdf.y;\n\tfloat Ess = brdf.x + brdf.y;\n\tfloat Ems = 1.0 - Ess;\n\tvec3 Favg = specularColor + ( 1.0 - specularColor ) * 0.047619;\tvec3 Fms = FssEss * Favg / ( 1.0 - Ems * Favg );\n\tsingleScatter += FssEss;\n\tmultiScatter += Fms * Ems;\n}\nfloat G_BlinnPhong_Implicit( ) {\n\treturn 0.25;\n}\nfloat D_BlinnPhong( const in float shininess, const in float dotNH ) {\n\treturn RECIPROCAL_PI * ( shininess * 0.5 + 1.0 ) * pow( dotNH, shininess );\n}\nvec3 BRDF_Specular_BlinnPhong( const in IncidentLight incidentLight, const in GeometricContext geometry, const in vec3 specularColor, const in float shininess ) {\n\tvec3 halfDir = normalize( incidentLight.direction + geometry.viewDir );\n\tfloat dotNH = saturate( dot( geometry.normal, halfDir ) );\n\tfloat dotLH = saturate( dot( incidentLight.direction, halfDir ) );\n\tvec3 F = F_Schlick( specularColor, dotLH );\n\tfloat G = G_BlinnPhong_Implicit( );\n\tfloat D = D_BlinnPhong( shininess, dotNH );\n\treturn F * ( G * D );\n}\nfloat GGXRoughnessToBlinnExponent( const in float ggxRoughness ) {\n\treturn ( 2.0 / pow2( ggxRoughness + 0.0001 ) - 2.0 );\n}\nfloat BlinnExponentToGGXRoughness( const in float blinnExponent ) {\n\treturn sqrt( 2.0 / ( blinnExponent + 2.0 ) );\n}\n#if defined( USE_SHEEN )\nfloat D_Charlie(float roughness, float NoH) {\n\tfloat invAlpha = 1.0 / roughness;\n\tfloat cos2h = NoH * NoH;\n\tfloat sin2h = max(1.0 - cos2h, 0.0078125);\treturn (2.0 + invAlpha) * pow(sin2h, invAlpha * 0.5) / (2.0 * PI);\n}\nfloat V_Neubelt(float NoV, float NoL) {\n\treturn saturate(1.0 / (4.0 * (NoL + NoV - NoL * NoV)));\n}\nvec3 BRDF_Specular_Sheen( const in float roughness, const in vec3 L, const in GeometricContext geometry, vec3 specularColor ) {\n\tvec3 N = geometry.normal;\n\tvec3 V = geometry.viewDir;\n\tvec3 H = normalize( V + L );\n\tfloat dotNH = saturate( dot( N, H ) );\n\treturn specularColor * D_Charlie( roughness, dotNH ) * V_Neubelt( dot(N, V), dot(N, L) );\n}\n#endif";
+
+var bumpmap_pars_fragment = "#ifdef USE_BUMPMAP\n\tuniform sampler2D bumpMap;\n\tuniform float bumpScale;\n\tvec2 dHdxy_fwd() {\n\t\tvec2 dSTdx = dFdx( vUv );\n\t\tvec2 dSTdy = dFdy( vUv );\n\t\tfloat Hll = bumpScale * texture2D( bumpMap, vUv ).x;\n\t\tfloat dBx = bumpScale * texture2D( bumpMap, vUv + dSTdx ).x - Hll;\n\t\tfloat dBy = bumpScale * texture2D( bumpMap, vUv + dSTdy ).x - Hll;\n\t\treturn vec2( dBx, dBy );\n\t}\n\tvec3 perturbNormalArb( vec3 surf_pos, vec3 surf_norm, vec2 dHdxy ) {\n\t\tvec3 vSigmaX = vec3( dFdx( surf_pos.x ), dFdx( surf_pos.y ), dFdx( surf_pos.z ) );\n\t\tvec3 vSigmaY = vec3( dFdy( surf_pos.x ), dFdy( surf_pos.y ), dFdy( surf_pos.z ) );\n\t\tvec3 vN = surf_norm;\n\t\tvec3 R1 = cross( vSigmaY, vN );\n\t\tvec3 R2 = cross( vN, vSigmaX );\n\t\tfloat fDet = dot( vSigmaX, R1 );\n\t\tfDet *= ( float( gl_FrontFacing ) * 2.0 - 1.0 );\n\t\tvec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 );\n\t\treturn normalize( abs( fDet ) * surf_norm - vGrad );\n\t}\n#endif";
+
+var clipping_planes_fragment = "#if NUM_CLIPPING_PLANES > 0\n\tvec4 plane;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < UNION_CLIPPING_PLANES; i ++ ) {\n\t\tplane = clippingPlanes[ i ];\n\t\tif ( dot( vClipPosition, plane.xyz ) > plane.w ) discard;\n\t}\n\t#pragma unroll_loop_end\n\t#if UNION_CLIPPING_PLANES < NUM_CLIPPING_PLANES\n\t\tbool clipped = true;\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = UNION_CLIPPING_PLANES; i < NUM_CLIPPING_PLANES; i ++ ) {\n\t\t\tplane = clippingPlanes[ i ];\n\t\t\tclipped = ( dot( vClipPosition, plane.xyz ) > plane.w ) && clipped;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t\tif ( clipped ) discard;\n\t#endif\n#endif";
+
+var clipping_planes_pars_fragment = "#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n\tuniform vec4 clippingPlanes[ NUM_CLIPPING_PLANES ];\n#endif";
+
+var clipping_planes_pars_vertex = "#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n#endif";
+
+var clipping_planes_vertex = "#if NUM_CLIPPING_PLANES > 0\n\tvClipPosition = - mvPosition.xyz;\n#endif";
+
+var color_fragment = "#ifdef USE_COLOR\n\tdiffuseColor.rgb *= vColor;\n#endif";
+
+var color_pars_fragment = "#ifdef USE_COLOR\n\tvarying vec3 vColor;\n#endif";
+
+var color_pars_vertex = "#if defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR )\n\tvarying vec3 vColor;\n#endif";
+
+var color_vertex = "#if defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR )\n\tvColor = vec3( 1.0 );\n#endif\n#ifdef USE_COLOR\n\tvColor.xyz *= color.xyz;\n#endif\n#ifdef USE_INSTANCING_COLOR\n\tvColor.xyz *= instanceColor.xyz;\n#endif";
+
+var common$1 = "#define PI 3.141592653589793\n#define PI2 6.283185307179586\n#define PI_HALF 1.5707963267948966\n#define RECIPROCAL_PI 0.3183098861837907\n#define RECIPROCAL_PI2 0.15915494309189535\n#define EPSILON 1e-6\n#ifndef saturate\n#define saturate(a) clamp( a, 0.0, 1.0 )\n#endif\n#define whiteComplement(a) ( 1.0 - saturate( a ) )\nfloat pow2( const in float x ) { return x*x; }\nfloat pow3( const in float x ) { return x*x*x; }\nfloat pow4( const in float x ) { float x2 = x*x; return x2*x2; }\nfloat average( const in vec3 color ) { return dot( color, vec3( 0.3333 ) ); }\nhighp float rand( const in vec2 uv ) {\n\tconst highp float a = 12.9898, b = 78.233, c = 43758.5453;\n\thighp float dt = dot( uv.xy, vec2( a,b ) ), sn = mod( dt, PI );\n\treturn fract(sin(sn) * c);\n}\n#ifdef HIGH_PRECISION\n\tfloat precisionSafeLength( vec3 v ) { return length( v ); }\n#else\n\tfloat max3( vec3 v ) { return max( max( v.x, v.y ), v.z ); }\n\tfloat precisionSafeLength( vec3 v ) {\n\t\tfloat maxComponent = max3( abs( v ) );\n\t\treturn length( v / maxComponent ) * maxComponent;\n\t}\n#endif\nstruct IncidentLight {\n\tvec3 color;\n\tvec3 direction;\n\tbool visible;\n};\nstruct ReflectedLight {\n\tvec3 directDiffuse;\n\tvec3 directSpecular;\n\tvec3 indirectDiffuse;\n\tvec3 indirectSpecular;\n};\nstruct GeometricContext {\n\tvec3 position;\n\tvec3 normal;\n\tvec3 viewDir;\n#ifdef CLEARCOAT\n\tvec3 clearcoatNormal;\n#endif\n};\nvec3 transformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );\n}\nvec3 inverseTransformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( vec4( dir, 0.0 ) * matrix ).xyz );\n}\nvec3 projectOnPlane(in vec3 point, in vec3 pointOnPlane, in vec3 planeNormal ) {\n\tfloat distance = dot( planeNormal, point - pointOnPlane );\n\treturn - distance * planeNormal + point;\n}\nfloat sideOfPlane( in vec3 point, in vec3 pointOnPlane, in vec3 planeNormal ) {\n\treturn sign( dot( point - pointOnPlane, planeNormal ) );\n}\nvec3 linePlaneIntersect( in vec3 pointOnLine, in vec3 lineDirection, in vec3 pointOnPlane, in vec3 planeNormal ) {\n\treturn lineDirection * ( dot( planeNormal, pointOnPlane - pointOnLine ) / dot( planeNormal, lineDirection ) ) + pointOnLine;\n}\nmat3 transposeMat3( const in mat3 m ) {\n\tmat3 tmp;\n\ttmp[ 0 ] = vec3( m[ 0 ].x, m[ 1 ].x, m[ 2 ].x );\n\ttmp[ 1 ] = vec3( m[ 0 ].y, m[ 1 ].y, m[ 2 ].y );\n\ttmp[ 2 ] = vec3( m[ 0 ].z, m[ 1 ].z, m[ 2 ].z );\n\treturn tmp;\n}\nfloat linearToRelativeLuminance( const in vec3 color ) {\n\tvec3 weights = vec3( 0.2126, 0.7152, 0.0722 );\n\treturn dot( weights, color.rgb );\n}\nbool isPerspectiveMatrix( mat4 m ) {\n\treturn m[ 2 ][ 3 ] == - 1.0;\n}\nvec2 equirectUv( in vec3 dir ) {\n\tfloat u = atan( dir.z, dir.x ) * RECIPROCAL_PI2 + 0.5;\n\tfloat v = asin( clamp( dir.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\n\treturn vec2( u, v );\n}";
+
+var cube_uv_reflection_fragment = "#ifdef ENVMAP_TYPE_CUBE_UV\n\t#define cubeUV_maxMipLevel 8.0\n\t#define cubeUV_minMipLevel 4.0\n\t#define cubeUV_maxTileSize 256.0\n\t#define cubeUV_minTileSize 16.0\n\tfloat getFace( vec3 direction ) {\n\t\tvec3 absDirection = abs( direction );\n\t\tfloat face = - 1.0;\n\t\tif ( absDirection.x > absDirection.z ) {\n\t\t\tif ( absDirection.x > absDirection.y )\n\t\t\t\tface = direction.x > 0.0 ? 0.0 : 3.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t} else {\n\t\t\tif ( absDirection.z > absDirection.y )\n\t\t\t\tface = direction.z > 0.0 ? 2.0 : 5.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t}\n\t\treturn face;\n\t}\n\tvec2 getUV( vec3 direction, float face ) {\n\t\tvec2 uv;\n\t\tif ( face == 0.0 ) {\n\t\t\tuv = vec2( direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 1.0 ) {\n\t\t\tuv = vec2( - direction.x, - direction.z ) / abs( direction.y );\n\t\t} else if ( face == 2.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.y ) / abs( direction.z );\n\t\t} else if ( face == 3.0 ) {\n\t\t\tuv = vec2( - direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 4.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.z ) / abs( direction.y );\n\t\t} else {\n\t\t\tuv = vec2( direction.x, direction.y ) / abs( direction.z );\n\t\t}\n\t\treturn 0.5 * ( uv + 1.0 );\n\t}\n\tvec3 bilinearCubeUV( sampler2D envMap, vec3 direction, float mipInt ) {\n\t\tfloat face = getFace( direction );\n\t\tfloat filterInt = max( cubeUV_minMipLevel - mipInt, 0.0 );\n\t\tmipInt = max( mipInt, cubeUV_minMipLevel );\n\t\tfloat faceSize = exp2( mipInt );\n\t\tfloat texelSize = 1.0 / ( 3.0 * cubeUV_maxTileSize );\n\t\tvec2 uv = getUV( direction, face ) * ( faceSize - 1.0 );\n\t\tvec2 f = fract( uv );\n\t\tuv += 0.5 - f;\n\t\tif ( face > 2.0 ) {\n\t\t\tuv.y += faceSize;\n\t\t\tface -= 3.0;\n\t\t}\n\t\tuv.x += face * faceSize;\n\t\tif ( mipInt < cubeUV_maxMipLevel ) {\n\t\t\tuv.y += 2.0 * cubeUV_maxTileSize;\n\t\t}\n\t\tuv.y += filterInt * 2.0 * cubeUV_minTileSize;\n\t\tuv.x += 3.0 * max( 0.0, cubeUV_maxTileSize - 2.0 * faceSize );\n\t\tuv *= texelSize;\n\t\tvec3 tl = envMapTexelToLinear( texture2D( envMap, uv ) ).rgb;\n\t\tuv.x += texelSize;\n\t\tvec3 tr = envMapTexelToLinear( texture2D( envMap, uv ) ).rgb;\n\t\tuv.y += texelSize;\n\t\tvec3 br = envMapTexelToLinear( texture2D( envMap, uv ) ).rgb;\n\t\tuv.x -= texelSize;\n\t\tvec3 bl = envMapTexelToLinear( texture2D( envMap, uv ) ).rgb;\n\t\tvec3 tm = mix( tl, tr, f.x );\n\t\tvec3 bm = mix( bl, br, f.x );\n\t\treturn mix( tm, bm, f.y );\n\t}\n\t#define r0 1.0\n\t#define v0 0.339\n\t#define m0 - 2.0\n\t#define r1 0.8\n\t#define v1 0.276\n\t#define m1 - 1.0\n\t#define r4 0.4\n\t#define v4 0.046\n\t#define m4 2.0\n\t#define r5 0.305\n\t#define v5 0.016\n\t#define m5 3.0\n\t#define r6 0.21\n\t#define v6 0.0038\n\t#define m6 4.0\n\tfloat roughnessToMip( float roughness ) {\n\t\tfloat mip = 0.0;\n\t\tif ( roughness >= r1 ) {\n\t\t\tmip = ( r0 - roughness ) * ( m1 - m0 ) / ( r0 - r1 ) + m0;\n\t\t} else if ( roughness >= r4 ) {\n\t\t\tmip = ( r1 - roughness ) * ( m4 - m1 ) / ( r1 - r4 ) + m1;\n\t\t} else if ( roughness >= r5 ) {\n\t\t\tmip = ( r4 - roughness ) * ( m5 - m4 ) / ( r4 - r5 ) + m4;\n\t\t} else if ( roughness >= r6 ) {\n\t\t\tmip = ( r5 - roughness ) * ( m6 - m5 ) / ( r5 - r6 ) + m5;\n\t\t} else {\n\t\t\tmip = - 2.0 * log2( 1.16 * roughness );\t\t}\n\t\treturn mip;\n\t}\n\tvec4 textureCubeUV( sampler2D envMap, vec3 sampleDir, float roughness ) {\n\t\tfloat mip = clamp( roughnessToMip( roughness ), m0, cubeUV_maxMipLevel );\n\t\tfloat mipF = fract( mip );\n\t\tfloat mipInt = floor( mip );\n\t\tvec3 color0 = bilinearCubeUV( envMap, sampleDir, mipInt );\n\t\tif ( mipF == 0.0 ) {\n\t\t\treturn vec4( color0, 1.0 );\n\t\t} else {\n\t\t\tvec3 color1 = bilinearCubeUV( envMap, sampleDir, mipInt + 1.0 );\n\t\t\treturn vec4( mix( color0, color1, mipF ), 1.0 );\n\t\t}\n\t}\n#endif";
+
+var defaultnormal_vertex = "vec3 transformedNormal = objectNormal;\n#ifdef USE_INSTANCING\n\tmat3 m = mat3( instanceMatrix );\n\ttransformedNormal /= vec3( dot( m[ 0 ], m[ 0 ] ), dot( m[ 1 ], m[ 1 ] ), dot( m[ 2 ], m[ 2 ] ) );\n\ttransformedNormal = m * transformedNormal;\n#endif\ntransformedNormal = normalMatrix * transformedNormal;\n#ifdef FLIP_SIDED\n\ttransformedNormal = - transformedNormal;\n#endif\n#ifdef USE_TANGENT\n\tvec3 transformedTangent = ( modelViewMatrix * vec4( objectTangent, 0.0 ) ).xyz;\n\t#ifdef FLIP_SIDED\n\t\ttransformedTangent = - transformedTangent;\n\t#endif\n#endif";
+
+var displacementmap_pars_vertex = "#ifdef USE_DISPLACEMENTMAP\n\tuniform sampler2D displacementMap;\n\tuniform float displacementScale;\n\tuniform float displacementBias;\n#endif";
+
+var displacementmap_vertex = "#ifdef USE_DISPLACEMENTMAP\n\ttransformed += normalize( objectNormal ) * ( texture2D( displacementMap, vUv ).x * displacementScale + displacementBias );\n#endif";
+
+var emissivemap_fragment = "#ifdef USE_EMISSIVEMAP\n\tvec4 emissiveColor = texture2D( emissiveMap, vUv );\n\temissiveColor.rgb = emissiveMapTexelToLinear( emissiveColor ).rgb;\n\ttotalEmissiveRadiance *= emissiveColor.rgb;\n#endif";
+
+var emissivemap_pars_fragment = "#ifdef USE_EMISSIVEMAP\n\tuniform sampler2D emissiveMap;\n#endif";
+
+var encodings_fragment = "gl_FragColor = linearToOutputTexel( gl_FragColor );";
+
+var encodings_pars_fragment = "\nvec4 LinearToLinear( in vec4 value ) {\n\treturn value;\n}\nvec4 GammaToLinear( in vec4 value, in float gammaFactor ) {\n\treturn vec4( pow( value.rgb, vec3( gammaFactor ) ), value.a );\n}\nvec4 LinearToGamma( in vec4 value, in float gammaFactor ) {\n\treturn vec4( pow( value.rgb, vec3( 1.0 / gammaFactor ) ), value.a );\n}\nvec4 sRGBToLinear( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), value.rgb * 0.0773993808, vec3( lessThanEqual( value.rgb, vec3( 0.04045 ) ) ) ), value.a );\n}\nvec4 LinearTosRGB( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.a );\n}\nvec4 RGBEToLinear( in vec4 value ) {\n\treturn vec4( value.rgb * exp2( value.a * 255.0 - 128.0 ), 1.0 );\n}\nvec4 LinearToRGBE( in vec4 value ) {\n\tfloat maxComponent = max( max( value.r, value.g ), value.b );\n\tfloat fExp = clamp( ceil( log2( maxComponent ) ), -128.0, 127.0 );\n\treturn vec4( value.rgb / exp2( fExp ), ( fExp + 128.0 ) / 255.0 );\n}\nvec4 RGBMToLinear( in vec4 value, in float maxRange ) {\n\treturn vec4( value.rgb * value.a * maxRange, 1.0 );\n}\nvec4 LinearToRGBM( in vec4 value, in float maxRange ) {\n\tfloat maxRGB = max( value.r, max( value.g, value.b ) );\n\tfloat M = clamp( maxRGB / maxRange, 0.0, 1.0 );\n\tM = ceil( M * 255.0 ) / 255.0;\n\treturn vec4( value.rgb / ( M * maxRange ), M );\n}\nvec4 RGBDToLinear( in vec4 value, in float maxRange ) {\n\treturn vec4( value.rgb * ( ( maxRange / 255.0 ) / value.a ), 1.0 );\n}\nvec4 LinearToRGBD( in vec4 value, in float maxRange ) {\n\tfloat maxRGB = max( value.r, max( value.g, value.b ) );\n\tfloat D = max( maxRange / maxRGB, 1.0 );\n\tD = clamp( floor( D ) / 255.0, 0.0, 1.0 );\n\treturn vec4( value.rgb * ( D * ( 255.0 / maxRange ) ), D );\n}\nconst mat3 cLogLuvM = mat3( 0.2209, 0.3390, 0.4184, 0.1138, 0.6780, 0.7319, 0.0102, 0.1130, 0.2969 );\nvec4 LinearToLogLuv( in vec4 value ) {\n\tvec3 Xp_Y_XYZp = cLogLuvM * value.rgb;\n\tXp_Y_XYZp = max( Xp_Y_XYZp, vec3( 1e-6, 1e-6, 1e-6 ) );\n\tvec4 vResult;\n\tvResult.xy = Xp_Y_XYZp.xy / Xp_Y_XYZp.z;\n\tfloat Le = 2.0 * log2(Xp_Y_XYZp.y) + 127.0;\n\tvResult.w = fract( Le );\n\tvResult.z = ( Le - ( floor( vResult.w * 255.0 ) ) / 255.0 ) / 255.0;\n\treturn vResult;\n}\nconst mat3 cLogLuvInverseM = mat3( 6.0014, -2.7008, -1.7996, -1.3320, 3.1029, -5.7721, 0.3008, -1.0882, 5.6268 );\nvec4 LogLuvToLinear( in vec4 value ) {\n\tfloat Le = value.z * 255.0 + value.w;\n\tvec3 Xp_Y_XYZp;\n\tXp_Y_XYZp.y = exp2( ( Le - 127.0 ) / 2.0 );\n\tXp_Y_XYZp.z = Xp_Y_XYZp.y / value.y;\n\tXp_Y_XYZp.x = value.x * Xp_Y_XYZp.z;\n\tvec3 vRGB = cLogLuvInverseM * Xp_Y_XYZp.rgb;\n\treturn vec4( max( vRGB, 0.0 ), 1.0 );\n}";
+
+var envmap_fragment = "#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvec3 cameraToFrag;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToFrag = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToFrag = normalize( vWorldPosition - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvec3 reflectVec = reflect( cameraToFrag, worldNormal );\n\t\t#else\n\t\t\tvec3 reflectVec = refract( cameraToFrag, worldNormal, refractionRatio );\n\t\t#endif\n\t#else\n\t\tvec3 reflectVec = vReflect;\n\t#endif\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 envColor = textureCube( envMap, vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );\n\t#elif defined( ENVMAP_TYPE_CUBE_UV )\n\t\tvec4 envColor = textureCubeUV( envMap, reflectVec, 0.0 );\n\t#else\n\t\tvec4 envColor = vec4( 0.0 );\n\t#endif\n\t#ifndef ENVMAP_TYPE_CUBE_UV\n\t\tenvColor = envMapTexelToLinear( envColor );\n\t#endif\n\t#ifdef ENVMAP_BLENDING_MULTIPLY\n\t\toutgoingLight = mix( outgoingLight, outgoingLight * envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_MIX )\n\t\toutgoingLight = mix( outgoingLight, envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_ADD )\n\t\toutgoingLight += envColor.xyz * specularStrength * reflectivity;\n\t#endif\n#endif";
+
+var envmap_common_pars_fragment = "#ifdef USE_ENVMAP\n\tuniform float envMapIntensity;\n\tuniform float flipEnvMap;\n\tuniform int maxMipLevel;\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tuniform samplerCube envMap;\n\t#else\n\t\tuniform sampler2D envMap;\n\t#endif\n\t\n#endif";
+
+var envmap_pars_fragment = "#ifdef USE_ENVMAP\n\tuniform float reflectivity;\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\tvarying vec3 vWorldPosition;\n\t\tuniform float refractionRatio;\n\t#else\n\t\tvarying vec3 vReflect;\n\t#endif\n#endif";
+
+var envmap_pars_vertex = "#ifdef USE_ENVMAP\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) ||defined( PHONG )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\t\n\t\tvarying vec3 vWorldPosition;\n\t#else\n\t\tvarying vec3 vReflect;\n\t\tuniform float refractionRatio;\n\t#endif\n#endif";
+
+var envmap_vertex = "#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvWorldPosition = worldPosition.xyz;\n\t#else\n\t\tvec3 cameraToVertex;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToVertex = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToVertex = normalize( worldPosition.xyz - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvReflect = reflect( cameraToVertex, worldNormal );\n\t\t#else\n\t\t\tvReflect = refract( cameraToVertex, worldNormal, refractionRatio );\n\t\t#endif\n\t#endif\n#endif";
+
+var fog_vertex = "#ifdef USE_FOG\n\tfogDepth = - mvPosition.z;\n#endif";
+
+var fog_pars_vertex = "#ifdef USE_FOG\n\tvarying float fogDepth;\n#endif";
+
+var fog_fragment = "#ifdef USE_FOG\n\t#ifdef FOG_EXP2\n\t\tfloat fogFactor = 1.0 - exp( - fogDensity * fogDensity * fogDepth * fogDepth );\n\t#else\n\t\tfloat fogFactor = smoothstep( fogNear, fogFar, fogDepth );\n\t#endif\n\tgl_FragColor.rgb = mix( gl_FragColor.rgb, fogColor, fogFactor );\n#endif";
+
+var fog_pars_fragment = "#ifdef USE_FOG\n\tuniform vec3 fogColor;\n\tvarying float fogDepth;\n\t#ifdef FOG_EXP2\n\t\tuniform float fogDensity;\n\t#else\n\t\tuniform float fogNear;\n\t\tuniform float fogFar;\n\t#endif\n#endif";
+
+var gradientmap_pars_fragment = "#ifdef USE_GRADIENTMAP\n\tuniform sampler2D gradientMap;\n#endif\nvec3 getGradientIrradiance( vec3 normal, vec3 lightDirection ) {\n\tfloat dotNL = dot( normal, lightDirection );\n\tvec2 coord = vec2( dotNL * 0.5 + 0.5, 0.0 );\n\t#ifdef USE_GRADIENTMAP\n\t\treturn texture2D( gradientMap, coord ).rgb;\n\t#else\n\t\treturn ( coord.x < 0.7 ) ? vec3( 0.7 ) : vec3( 1.0 );\n\t#endif\n}";
+
+var lightmap_fragment = "#ifdef USE_LIGHTMAP\n\tvec4 lightMapTexel= texture2D( lightMap, vUv2 );\n\treflectedLight.indirectDiffuse += PI * lightMapTexelToLinear( lightMapTexel ).rgb * lightMapIntensity;\n#endif";
+
+var lightmap_pars_fragment = "#ifdef USE_LIGHTMAP\n\tuniform sampler2D lightMap;\n\tuniform float lightMapIntensity;\n#endif";
+
+var lights_lambert_vertex = "vec3 diffuse = vec3( 1.0 );\nGeometricContext geometry;\ngeometry.position = mvPosition.xyz;\ngeometry.normal = normalize( transformedNormal );\ngeometry.viewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( -mvPosition.xyz );\nGeometricContext backGeometry;\nbackGeometry.position = geometry.position;\nbackGeometry.normal = -geometry.normal;\nbackGeometry.viewDir = geometry.viewDir;\nvLightFront = vec3( 0.0 );\nvIndirectFront = vec3( 0.0 );\n#ifdef DOUBLE_SIDED\n\tvLightBack = vec3( 0.0 );\n\tvIndirectBack = vec3( 0.0 );\n#endif\nIncidentLight directLight;\nfloat dotNL;\nvec3 directLightColor_Diffuse;\nvIndirectFront += getAmbientLightIrradiance( ambientLightColor );\nvIndirectFront += getLightProbeIrradiance( lightProbe, geometry );\n#ifdef DOUBLE_SIDED\n\tvIndirectBack += getAmbientLightIrradiance( ambientLightColor );\n\tvIndirectBack += getLightProbeIrradiance( lightProbe, backGeometry );\n#endif\n#if NUM_POINT_LIGHTS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tgetPointDirectLightIrradiance( pointLights[ i ], geometry, directLight );\n\t\tdotNL = dot( geometry.normal, directLight.direction );\n\t\tdirectLightColor_Diffuse = PI * directLight.color;\n\t\tvLightFront += saturate( dotNL ) * directLightColor_Diffuse;\n\t\t#ifdef DOUBLE_SIDED\n\t\t\tvLightBack += saturate( -dotNL ) * directLightColor_Diffuse;\n\t\t#endif\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if NUM_SPOT_LIGHTS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tgetSpotDirectLightIrradiance( spotLights[ i ], geometry, directLight );\n\t\tdotNL = dot( geometry.normal, directLight.direction );\n\t\tdirectLightColor_Diffuse = PI * directLight.color;\n\t\tvLightFront += saturate( dotNL ) * directLightColor_Diffuse;\n\t\t#ifdef DOUBLE_SIDED\n\t\t\tvLightBack += saturate( -dotNL ) * directLightColor_Diffuse;\n\t\t#endif\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if NUM_DIR_LIGHTS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tgetDirectionalDirectLightIrradiance( directionalLights[ i ], geometry, directLight );\n\t\tdotNL = dot( geometry.normal, directLight.direction );\n\t\tdirectLightColor_Diffuse = PI * directLight.color;\n\t\tvLightFront += saturate( dotNL ) * directLightColor_Diffuse;\n\t\t#ifdef DOUBLE_SIDED\n\t\t\tvLightBack += saturate( -dotNL ) * directLightColor_Diffuse;\n\t\t#endif\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if NUM_HEMI_LIGHTS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\n\t\tvIndirectFront += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry );\n\t\t#ifdef DOUBLE_SIDED\n\t\t\tvIndirectBack += getHemisphereLightIrradiance( hemisphereLights[ i ], backGeometry );\n\t\t#endif\n\t}\n\t#pragma unroll_loop_end\n#endif";
+
+var lights_pars_begin = "uniform bool receiveShadow;\nuniform vec3 ambientLightColor;\nuniform vec3 lightProbe[ 9 ];\nvec3 shGetIrradianceAt( in vec3 normal, in vec3 shCoefficients[ 9 ] ) {\n\tfloat x = normal.x, y = normal.y, z = normal.z;\n\tvec3 result = shCoefficients[ 0 ] * 0.886227;\n\tresult += shCoefficients[ 1 ] * 2.0 * 0.511664 * y;\n\tresult += shCoefficients[ 2 ] * 2.0 * 0.511664 * z;\n\tresult += shCoefficients[ 3 ] * 2.0 * 0.511664 * x;\n\tresult += shCoefficients[ 4 ] * 2.0 * 0.429043 * x * y;\n\tresult += shCoefficients[ 5 ] * 2.0 * 0.429043 * y * z;\n\tresult += shCoefficients[ 6 ] * ( 0.743125 * z * z - 0.247708 );\n\tresult += shCoefficients[ 7 ] * 2.0 * 0.429043 * x * z;\n\tresult += shCoefficients[ 8 ] * 0.429043 * ( x * x - y * y );\n\treturn result;\n}\nvec3 getLightProbeIrradiance( const in vec3 lightProbe[ 9 ], const in GeometricContext geometry ) {\n\tvec3 worldNormal = inverseTransformDirection( geometry.normal, viewMatrix );\n\tvec3 irradiance = shGetIrradianceAt( worldNormal, lightProbe );\n\treturn irradiance;\n}\nvec3 getAmbientLightIrradiance( const in vec3 ambientLightColor ) {\n\tvec3 irradiance = ambientLightColor;\n\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\tirradiance *= PI;\n\t#endif\n\treturn irradiance;\n}\n#if NUM_DIR_LIGHTS > 0\n\tstruct DirectionalLight {\n\t\tvec3 direction;\n\t\tvec3 color;\n\t};\n\tuniform DirectionalLight directionalLights[ NUM_DIR_LIGHTS ];\n\tvoid getDirectionalDirectLightIrradiance( const in DirectionalLight directionalLight, const in GeometricContext geometry, out IncidentLight directLight ) {\n\t\tdirectLight.color = directionalLight.color;\n\t\tdirectLight.direction = directionalLight.direction;\n\t\tdirectLight.visible = true;\n\t}\n#endif\n#if NUM_POINT_LIGHTS > 0\n\tstruct PointLight {\n\t\tvec3 position;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t};\n\tuniform PointLight pointLights[ NUM_POINT_LIGHTS ];\n\tvoid getPointDirectLightIrradiance( const in PointLight pointLight, const in GeometricContext geometry, out IncidentLight directLight ) {\n\t\tvec3 lVector = pointLight.position - geometry.position;\n\t\tdirectLight.direction = normalize( lVector );\n\t\tfloat lightDistance = length( lVector );\n\t\tdirectLight.color = pointLight.color;\n\t\tdirectLight.color *= punctualLightIntensityToIrradianceFactor( lightDistance, pointLight.distance, pointLight.decay );\n\t\tdirectLight.visible = ( directLight.color != vec3( 0.0 ) );\n\t}\n#endif\n#if NUM_SPOT_LIGHTS > 0\n\tstruct SpotLight {\n\t\tvec3 position;\n\t\tvec3 direction;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t\tfloat coneCos;\n\t\tfloat penumbraCos;\n\t};\n\tuniform SpotLight spotLights[ NUM_SPOT_LIGHTS ];\n\tvoid getSpotDirectLightIrradiance( const in SpotLight spotLight, const in GeometricContext geometry, out IncidentLight directLight ) {\n\t\tvec3 lVector = spotLight.position - geometry.position;\n\t\tdirectLight.direction = normalize( lVector );\n\t\tfloat lightDistance = length( lVector );\n\t\tfloat angleCos = dot( directLight.direction, spotLight.direction );\n\t\tif ( angleCos > spotLight.coneCos ) {\n\t\t\tfloat spotEffect = smoothstep( spotLight.coneCos, spotLight.penumbraCos, angleCos );\n\t\t\tdirectLight.color = spotLight.color;\n\t\t\tdirectLight.color *= spotEffect * punctualLightIntensityToIrradianceFactor( lightDistance, spotLight.distance, spotLight.decay );\n\t\t\tdirectLight.visible = true;\n\t\t} else {\n\t\t\tdirectLight.color = vec3( 0.0 );\n\t\t\tdirectLight.visible = false;\n\t\t}\n\t}\n#endif\n#if NUM_RECT_AREA_LIGHTS > 0\n\tstruct RectAreaLight {\n\t\tvec3 color;\n\t\tvec3 position;\n\t\tvec3 halfWidth;\n\t\tvec3 halfHeight;\n\t};\n\tuniform sampler2D ltc_1;\tuniform sampler2D ltc_2;\n\tuniform RectAreaLight rectAreaLights[ NUM_RECT_AREA_LIGHTS ];\n#endif\n#if NUM_HEMI_LIGHTS > 0\n\tstruct HemisphereLight {\n\t\tvec3 direction;\n\t\tvec3 skyColor;\n\t\tvec3 groundColor;\n\t};\n\tuniform HemisphereLight hemisphereLights[ NUM_HEMI_LIGHTS ];\n\tvec3 getHemisphereLightIrradiance( const in HemisphereLight hemiLight, const in GeometricContext geometry ) {\n\t\tfloat dotNL = dot( geometry.normal, hemiLight.direction );\n\t\tfloat hemiDiffuseWeight = 0.5 * dotNL + 0.5;\n\t\tvec3 irradiance = mix( hemiLight.groundColor, hemiLight.skyColor, hemiDiffuseWeight );\n\t\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\t\tirradiance *= PI;\n\t\t#endif\n\t\treturn irradiance;\n\t}\n#endif";
+
+var envmap_physical_pars_fragment = "#if defined( USE_ENVMAP )\n\t#ifdef ENVMAP_MODE_REFRACTION\n\t\tuniform float refractionRatio;\n\t#endif\n\tvec3 getLightProbeIndirectIrradiance( const in GeometricContext geometry, const in int maxMIPLevel ) {\n\t\tvec3 worldNormal = inverseTransformDirection( geometry.normal, viewMatrix );\n\t\t#ifdef ENVMAP_TYPE_CUBE\n\t\t\tvec3 queryVec = vec3( flipEnvMap * worldNormal.x, worldNormal.yz );\n\t\t\t#ifdef TEXTURE_LOD_EXT\n\t\t\t\tvec4 envMapColor = textureCubeLodEXT( envMap, queryVec, float( maxMIPLevel ) );\n\t\t\t#else\n\t\t\t\tvec4 envMapColor = textureCube( envMap, queryVec, float( maxMIPLevel ) );\n\t\t\t#endif\n\t\t\tenvMapColor.rgb = envMapTexelToLinear( envMapColor ).rgb;\n\t\t#elif defined( ENVMAP_TYPE_CUBE_UV )\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, worldNormal, 1.0 );\n\t\t#else\n\t\t\tvec4 envMapColor = vec4( 0.0 );\n\t\t#endif\n\t\treturn PI * envMapColor.rgb * envMapIntensity;\n\t}\n\tfloat getSpecularMIPLevel( const in float roughness, const in int maxMIPLevel ) {\n\t\tfloat maxMIPLevelScalar = float( maxMIPLevel );\n\t\tfloat sigma = PI * roughness * roughness / ( 1.0 + roughness );\n\t\tfloat desiredMIPLevel = maxMIPLevelScalar + log2( sigma );\n\t\treturn clamp( desiredMIPLevel, 0.0, maxMIPLevelScalar );\n\t}\n\tvec3 getLightProbeIndirectRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness, const in int maxMIPLevel ) {\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvec3 reflectVec = reflect( -viewDir, normal );\n\t\t\treflectVec = normalize( mix( reflectVec, normal, roughness * roughness) );\n\t\t#else\n\t\t\tvec3 reflectVec = refract( -viewDir, normal, refractionRatio );\n\t\t#endif\n\t\treflectVec = inverseTransformDirection( reflectVec, viewMatrix );\n\t\tfloat specularMIPLevel = getSpecularMIPLevel( roughness, maxMIPLevel );\n\t\t#ifdef ENVMAP_TYPE_CUBE\n\t\t\tvec3 queryReflectVec = vec3( flipEnvMap * reflectVec.x, reflectVec.yz );\n\t\t\t#ifdef TEXTURE_LOD_EXT\n\t\t\t\tvec4 envMapColor = textureCubeLodEXT( envMap, queryReflectVec, specularMIPLevel );\n\t\t\t#else\n\t\t\t\tvec4 envMapColor = textureCube( envMap, queryReflectVec, specularMIPLevel );\n\t\t\t#endif\n\t\t\tenvMapColor.rgb = envMapTexelToLinear( envMapColor ).rgb;\n\t\t#elif defined( ENVMAP_TYPE_CUBE_UV )\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, reflectVec, roughness );\n\t\t#endif\n\t\treturn envMapColor.rgb * envMapIntensity;\n\t}\n#endif";
+
+var lights_toon_fragment = "ToonMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;";
+
+var lights_toon_pars_fragment = "varying vec3 vViewPosition;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\nstruct ToonMaterial {\n\tvec3 diffuseColor;\n};\nvoid RE_Direct_Toon( const in IncidentLight directLight, const in GeometricContext geometry, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\tvec3 irradiance = getGradientIrradiance( geometry.normal, directLight.direction ) * directLight.color;\n\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\tirradiance *= PI;\n\t#endif\n\treflectedLight.directDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Toon( const in vec3 irradiance, const in GeometricContext geometry, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_Toon\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Toon\n#define Material_LightProbeLOD( material )\t(0)";
+
+var lights_phong_fragment = "BlinnPhongMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularColor = specular;\nmaterial.specularShininess = shininess;\nmaterial.specularStrength = specularStrength;";
+
+var lights_phong_pars_fragment = "varying vec3 vViewPosition;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\nstruct BlinnPhongMaterial {\n\tvec3 diffuseColor;\n\tvec3 specularColor;\n\tfloat specularShininess;\n\tfloat specularStrength;\n};\nvoid RE_Direct_BlinnPhong( const in IncidentLight directLight, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometry.normal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\tirradiance *= PI;\n\t#endif\n\treflectedLight.directDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\n\treflectedLight.directSpecular += irradiance * BRDF_Specular_BlinnPhong( directLight, geometry, material.specularColor, material.specularShininess ) * material.specularStrength;\n}\nvoid RE_IndirectDiffuse_BlinnPhong( const in vec3 irradiance, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_BlinnPhong\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_BlinnPhong\n#define Material_LightProbeLOD( material )\t(0)";
+
+var lights_physical_fragment = "PhysicalMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb * ( 1.0 - metalnessFactor );\nvec3 dxy = max( abs( dFdx( geometryNormal ) ), abs( dFdy( geometryNormal ) ) );\nfloat geometryRoughness = max( max( dxy.x, dxy.y ), dxy.z );\nmaterial.specularRoughness = max( roughnessFactor, 0.0525 );material.specularRoughness += geometryRoughness;\nmaterial.specularRoughness = min( material.specularRoughness, 1.0 );\n#ifdef REFLECTIVITY\n\tmaterial.specularColor = mix( vec3( MAXIMUM_SPECULAR_COEFFICIENT * pow2( reflectivity ) ), diffuseColor.rgb, metalnessFactor );\n#else\n\tmaterial.specularColor = mix( vec3( DEFAULT_SPECULAR_COEFFICIENT ), diffuseColor.rgb, metalnessFactor );\n#endif\n#ifdef CLEARCOAT\n\tmaterial.clearcoat = clearcoat;\n\tmaterial.clearcoatRoughness = clearcoatRoughness;\n\t#ifdef USE_CLEARCOATMAP\n\t\tmaterial.clearcoat *= texture2D( clearcoatMap, vUv ).x;\n\t#endif\n\t#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\t\tmaterial.clearcoatRoughness *= texture2D( clearcoatRoughnessMap, vUv ).y;\n\t#endif\n\tmaterial.clearcoat = saturate( material.clearcoat );\tmaterial.clearcoatRoughness = max( material.clearcoatRoughness, 0.0525 );\n\tmaterial.clearcoatRoughness += geometryRoughness;\n\tmaterial.clearcoatRoughness = min( material.clearcoatRoughness, 1.0 );\n#endif\n#ifdef USE_SHEEN\n\tmaterial.sheenColor = sheen;\n#endif";
+
+var lights_physical_pars_fragment = "struct PhysicalMaterial {\n\tvec3 diffuseColor;\n\tfloat specularRoughness;\n\tvec3 specularColor;\n#ifdef CLEARCOAT\n\tfloat clearcoat;\n\tfloat clearcoatRoughness;\n#endif\n#ifdef USE_SHEEN\n\tvec3 sheenColor;\n#endif\n};\n#define MAXIMUM_SPECULAR_COEFFICIENT 0.16\n#define DEFAULT_SPECULAR_COEFFICIENT 0.04\nfloat clearcoatDHRApprox( const in float roughness, const in float dotNL ) {\n\treturn DEFAULT_SPECULAR_COEFFICIENT + ( 1.0 - DEFAULT_SPECULAR_COEFFICIENT ) * ( pow( 1.0 - dotNL, 5.0 ) * pow( 1.0 - roughness, 2.0 ) );\n}\n#if NUM_RECT_AREA_LIGHTS > 0\n\tvoid RE_Direct_RectArea_Physical( const in RectAreaLight rectAreaLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\t\tvec3 normal = geometry.normal;\n\t\tvec3 viewDir = geometry.viewDir;\n\t\tvec3 position = geometry.position;\n\t\tvec3 lightPos = rectAreaLight.position;\n\t\tvec3 halfWidth = rectAreaLight.halfWidth;\n\t\tvec3 halfHeight = rectAreaLight.halfHeight;\n\t\tvec3 lightColor = rectAreaLight.color;\n\t\tfloat roughness = material.specularRoughness;\n\t\tvec3 rectCoords[ 4 ];\n\t\trectCoords[ 0 ] = lightPos + halfWidth - halfHeight;\t\trectCoords[ 1 ] = lightPos - halfWidth - halfHeight;\n\t\trectCoords[ 2 ] = lightPos - halfWidth + halfHeight;\n\t\trectCoords[ 3 ] = lightPos + halfWidth + halfHeight;\n\t\tvec2 uv = LTC_Uv( normal, viewDir, roughness );\n\t\tvec4 t1 = texture2D( ltc_1, uv );\n\t\tvec4 t2 = texture2D( ltc_2, uv );\n\t\tmat3 mInv = mat3(\n\t\t\tvec3( t1.x, 0, t1.y ),\n\t\t\tvec3(    0, 1,    0 ),\n\t\t\tvec3( t1.z, 0, t1.w )\n\t\t);\n\t\tvec3 fresnel = ( material.specularColor * t2.x + ( vec3( 1.0 ) - material.specularColor ) * t2.y );\n\t\treflectedLight.directSpecular += lightColor * fresnel * LTC_Evaluate( normal, viewDir, position, mInv, rectCoords );\n\t\treflectedLight.directDiffuse += lightColor * material.diffuseColor * LTC_Evaluate( normal, viewDir, position, mat3( 1.0 ), rectCoords );\n\t}\n#endif\nvoid RE_Direct_Physical( const in IncidentLight directLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometry.normal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\tirradiance *= PI;\n\t#endif\n\t#ifdef CLEARCOAT\n\t\tfloat ccDotNL = saturate( dot( geometry.clearcoatNormal, directLight.direction ) );\n\t\tvec3 ccIrradiance = ccDotNL * directLight.color;\n\t\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\t\tccIrradiance *= PI;\n\t\t#endif\n\t\tfloat clearcoatDHR = material.clearcoat * clearcoatDHRApprox( material.clearcoatRoughness, ccDotNL );\n\t\treflectedLight.directSpecular += ccIrradiance * material.clearcoat * BRDF_Specular_GGX( directLight, geometry.viewDir, geometry.clearcoatNormal, vec3( DEFAULT_SPECULAR_COEFFICIENT ), material.clearcoatRoughness );\n\t#else\n\t\tfloat clearcoatDHR = 0.0;\n\t#endif\n\t#ifdef USE_SHEEN\n\t\treflectedLight.directSpecular += ( 1.0 - clearcoatDHR ) * irradiance * BRDF_Specular_Sheen(\n\t\t\tmaterial.specularRoughness,\n\t\t\tdirectLight.direction,\n\t\t\tgeometry,\n\t\t\tmaterial.sheenColor\n\t\t);\n\t#else\n\t\treflectedLight.directSpecular += ( 1.0 - clearcoatDHR ) * irradiance * BRDF_Specular_GGX( directLight, geometry.viewDir, geometry.normal, material.specularColor, material.specularRoughness);\n\t#endif\n\treflectedLight.directDiffuse += ( 1.0 - clearcoatDHR ) * irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Physical( const in vec3 irradiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 irradiance, const in vec3 clearcoatRadiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {\n\t#ifdef CLEARCOAT\n\t\tfloat ccDotNV = saturate( dot( geometry.clearcoatNormal, geometry.viewDir ) );\n\t\treflectedLight.indirectSpecular += clearcoatRadiance * material.clearcoat * BRDF_Specular_GGX_Environment( geometry.viewDir, geometry.clearcoatNormal, vec3( DEFAULT_SPECULAR_COEFFICIENT ), material.clearcoatRoughness );\n\t\tfloat ccDotNL = ccDotNV;\n\t\tfloat clearcoatDHR = material.clearcoat * clearcoatDHRApprox( material.clearcoatRoughness, ccDotNL );\n\t#else\n\t\tfloat clearcoatDHR = 0.0;\n\t#endif\n\tfloat clearcoatInv = 1.0 - clearcoatDHR;\n\tvec3 singleScattering = vec3( 0.0 );\n\tvec3 multiScattering = vec3( 0.0 );\n\tvec3 cosineWeightedIrradiance = irradiance * RECIPROCAL_PI;\n\tBRDF_Specular_Multiscattering_Environment( geometry, material.specularColor, material.specularRoughness, singleScattering, multiScattering );\n\tvec3 diffuse = material.diffuseColor * ( 1.0 - ( singleScattering + multiScattering ) );\n\treflectedLight.indirectSpecular += clearcoatInv * radiance * singleScattering;\n\treflectedLight.indirectSpecular += multiScattering * cosineWeightedIrradiance;\n\treflectedLight.indirectDiffuse += diffuse * cosineWeightedIrradiance;\n}\n#define RE_Direct\t\t\t\tRE_Direct_Physical\n#define RE_Direct_RectArea\t\tRE_Direct_RectArea_Physical\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Physical\n#define RE_IndirectSpecular\t\tRE_IndirectSpecular_Physical\nfloat computeSpecularOcclusion( const in float dotNV, const in float ambientOcclusion, const in float roughness ) {\n\treturn saturate( pow( dotNV + ambientOcclusion, exp2( - 16.0 * roughness - 1.0 ) ) - 1.0 + ambientOcclusion );\n}";
+
+var lights_fragment_begin = "\nGeometricContext geometry;\ngeometry.position = - vViewPosition;\ngeometry.normal = normal;\ngeometry.viewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( vViewPosition );\n#ifdef CLEARCOAT\n\tgeometry.clearcoatNormal = clearcoatNormal;\n#endif\nIncidentLight directLight;\n#if ( NUM_POINT_LIGHTS > 0 ) && defined( RE_Direct )\n\tPointLight pointLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tpointLight = pointLights[ i ];\n\t\tgetPointDirectLightIrradiance( pointLight, geometry, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_POINT_LIGHT_SHADOWS )\n\t\tpointLightShadow = pointLightShadows[ i ];\n\t\tdirectLight.color *= all( bvec2( directLight.visible, receiveShadow ) ) ? getPointShadow( pointShadowMap[ i ], pointLightShadow.shadowMapSize, pointLightShadow.shadowBias, pointLightShadow.shadowRadius, vPointShadowCoord[ i ], pointLightShadow.shadowCameraNear, pointLightShadow.shadowCameraFar ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_SPOT_LIGHTS > 0 ) && defined( RE_Direct )\n\tSpotLight spotLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tspotLight = spotLights[ i ];\n\t\tgetSpotDirectLightIrradiance( spotLight, geometry, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\tspotLightShadow = spotLightShadows[ i ];\n\t\tdirectLight.color *= all( bvec2( directLight.visible, receiveShadow ) ) ? getShadow( spotShadowMap[ i ], spotLightShadow.shadowMapSize, spotLightShadow.shadowBias, spotLightShadow.shadowRadius, vSpotShadowCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct )\n\tDirectionalLight directionalLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tdirectionalLight = directionalLights[ i ];\n\t\tgetDirectionalDirectLightIrradiance( directionalLight, geometry, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS )\n\t\tdirectionalLightShadow = directionalLightShadows[ i ];\n\t\tdirectLight.color *= all( bvec2( directLight.visible, receiveShadow ) ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_RECT_AREA_LIGHTS > 0 ) && defined( RE_Direct_RectArea )\n\tRectAreaLight rectAreaLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_RECT_AREA_LIGHTS; i ++ ) {\n\t\trectAreaLight = rectAreaLights[ i ];\n\t\tRE_Direct_RectArea( rectAreaLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if defined( RE_IndirectDiffuse )\n\tvec3 iblIrradiance = vec3( 0.0 );\n\tvec3 irradiance = getAmbientLightIrradiance( ambientLightColor );\n\tirradiance += getLightProbeIrradiance( lightProbe, geometry );\n\t#if ( NUM_HEMI_LIGHTS > 0 )\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\n\t\t\tirradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry );\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n#endif\n#if defined( RE_IndirectSpecular )\n\tvec3 radiance = vec3( 0.0 );\n\tvec3 clearcoatRadiance = vec3( 0.0 );\n#endif";
+
+var lights_fragment_maps = "#if defined( RE_IndirectDiffuse )\n\t#ifdef USE_LIGHTMAP\n\t\tvec4 lightMapTexel= texture2D( lightMap, vUv2 );\n\t\tvec3 lightMapIrradiance = lightMapTexelToLinear( lightMapTexel ).rgb * lightMapIntensity;\n\t\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\t\tlightMapIrradiance *= PI;\n\t\t#endif\n\t\tirradiance += lightMapIrradiance;\n\t#endif\n\t#if defined( USE_ENVMAP ) && defined( STANDARD ) && defined( ENVMAP_TYPE_CUBE_UV )\n\t\tiblIrradiance += getLightProbeIndirectIrradiance( geometry, maxMipLevel );\n\t#endif\n#endif\n#if defined( USE_ENVMAP ) && defined( RE_IndirectSpecular )\n\tradiance += getLightProbeIndirectRadiance( geometry.viewDir, geometry.normal, material.specularRoughness, maxMipLevel );\n\t#ifdef CLEARCOAT\n\t\tclearcoatRadiance += getLightProbeIndirectRadiance( geometry.viewDir, geometry.clearcoatNormal, material.clearcoatRoughness, maxMipLevel );\n\t#endif\n#endif";
+
+var lights_fragment_end = "#if defined( RE_IndirectDiffuse )\n\tRE_IndirectDiffuse( irradiance, geometry, material, reflectedLight );\n#endif\n#if defined( RE_IndirectSpecular )\n\tRE_IndirectSpecular( radiance, iblIrradiance, clearcoatRadiance, geometry, material, reflectedLight );\n#endif";
+
+var logdepthbuf_fragment = "#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\n\tgl_FragDepthEXT = vIsPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\n#endif";
+
+var logdepthbuf_pars_fragment = "#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\n\tuniform float logDepthBufFC;\n\tvarying float vFragDepth;\n\tvarying float vIsPerspective;\n#endif";
+
+var logdepthbuf_pars_vertex = "#ifdef USE_LOGDEPTHBUF\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvarying float vFragDepth;\n\t\tvarying float vIsPerspective;\n\t#else\n\t\tuniform float logDepthBufFC;\n\t#endif\n#endif";
+
+var logdepthbuf_vertex = "#ifdef USE_LOGDEPTHBUF\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvFragDepth = 1.0 + gl_Position.w;\n\t\tvIsPerspective = float( isPerspectiveMatrix( projectionMatrix ) );\n\t#else\n\t\tif ( isPerspectiveMatrix( projectionMatrix ) ) {\n\t\t\tgl_Position.z = log2( max( EPSILON, gl_Position.w + 1.0 ) ) * logDepthBufFC - 1.0;\n\t\t\tgl_Position.z *= gl_Position.w;\n\t\t}\n\t#endif\n#endif";
+
+var map_fragment = "#ifdef USE_MAP\n\tvec4 texelColor = texture2D( map, vUv );\n\ttexelColor = mapTexelToLinear( texelColor );\n\tdiffuseColor *= texelColor;\n#endif";
+
+var map_pars_fragment = "#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif";
+
+var map_particle_fragment = "#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\tvec2 uv = ( uvTransform * vec3( gl_PointCoord.x, 1.0 - gl_PointCoord.y, 1 ) ).xy;\n#endif\n#ifdef USE_MAP\n\tvec4 mapTexel = texture2D( map, uv );\n\tdiffuseColor *= mapTexelToLinear( mapTexel );\n#endif\n#ifdef USE_ALPHAMAP\n\tdiffuseColor.a *= texture2D( alphaMap, uv ).g;\n#endif";
+
+var map_particle_pars_fragment = "#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\tuniform mat3 uvTransform;\n#endif\n#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif\n#ifdef USE_ALPHAMAP\n\tuniform sampler2D alphaMap;\n#endif";
+
+var metalnessmap_fragment = "float metalnessFactor = metalness;\n#ifdef USE_METALNESSMAP\n\tvec4 texelMetalness = texture2D( metalnessMap, vUv );\n\tmetalnessFactor *= texelMetalness.b;\n#endif";
+
+var metalnessmap_pars_fragment = "#ifdef USE_METALNESSMAP\n\tuniform sampler2D metalnessMap;\n#endif";
+
+var morphnormal_vertex = "#ifdef USE_MORPHNORMALS\n\tobjectNormal *= morphTargetBaseInfluence;\n\tobjectNormal += morphNormal0 * morphTargetInfluences[ 0 ];\n\tobjectNormal += morphNormal1 * morphTargetInfluences[ 1 ];\n\tobjectNormal += morphNormal2 * morphTargetInfluences[ 2 ];\n\tobjectNormal += morphNormal3 * morphTargetInfluences[ 3 ];\n#endif";
+
+var morphtarget_pars_vertex = "#ifdef USE_MORPHTARGETS\n\tuniform float morphTargetBaseInfluence;\n\t#ifndef USE_MORPHNORMALS\n\t\tuniform float morphTargetInfluences[ 8 ];\n\t#else\n\t\tuniform float morphTargetInfluences[ 4 ];\n\t#endif\n#endif";
+
+var morphtarget_vertex = "#ifdef USE_MORPHTARGETS\n\ttransformed *= morphTargetBaseInfluence;\n\ttransformed += morphTarget0 * morphTargetInfluences[ 0 ];\n\ttransformed += morphTarget1 * morphTargetInfluences[ 1 ];\n\ttransformed += morphTarget2 * morphTargetInfluences[ 2 ];\n\ttransformed += morphTarget3 * morphTargetInfluences[ 3 ];\n\t#ifndef USE_MORPHNORMALS\n\t\ttransformed += morphTarget4 * morphTargetInfluences[ 4 ];\n\t\ttransformed += morphTarget5 * morphTargetInfluences[ 5 ];\n\t\ttransformed += morphTarget6 * morphTargetInfluences[ 6 ];\n\t\ttransformed += morphTarget7 * morphTargetInfluences[ 7 ];\n\t#endif\n#endif";
+
+var normal_fragment_begin = "#ifdef FLAT_SHADED\n\tvec3 fdx = vec3( dFdx( vViewPosition.x ), dFdx( vViewPosition.y ), dFdx( vViewPosition.z ) );\n\tvec3 fdy = vec3( dFdy( vViewPosition.x ), dFdy( vViewPosition.y ), dFdy( vViewPosition.z ) );\n\tvec3 normal = normalize( cross( fdx, fdy ) );\n#else\n\tvec3 normal = normalize( vNormal );\n\t#ifdef DOUBLE_SIDED\n\t\tnormal = normal * ( float( gl_FrontFacing ) * 2.0 - 1.0 );\n\t#endif\n\t#ifdef USE_TANGENT\n\t\tvec3 tangent = normalize( vTangent );\n\t\tvec3 bitangent = normalize( vBitangent );\n\t\t#ifdef DOUBLE_SIDED\n\t\t\ttangent = tangent * ( float( gl_FrontFacing ) * 2.0 - 1.0 );\n\t\t\tbitangent = bitangent * ( float( gl_FrontFacing ) * 2.0 - 1.0 );\n\t\t#endif\n\t\t#if defined( TANGENTSPACE_NORMALMAP ) || defined( USE_CLEARCOAT_NORMALMAP )\n\t\t\tmat3 vTBN = mat3( tangent, bitangent, normal );\n\t\t#endif\n\t#endif\n#endif\nvec3 geometryNormal = normal;";
+
+var normal_fragment_maps = "#ifdef OBJECTSPACE_NORMALMAP\n\tnormal = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;\n\t#ifdef FLIP_SIDED\n\t\tnormal = - normal;\n\t#endif\n\t#ifdef DOUBLE_SIDED\n\t\tnormal = normal * ( float( gl_FrontFacing ) * 2.0 - 1.0 );\n\t#endif\n\tnormal = normalize( normalMatrix * normal );\n#elif defined( TANGENTSPACE_NORMALMAP )\n\tvec3 mapN = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;\n\tmapN.xy *= normalScale;\n\t#ifdef USE_TANGENT\n\t\tnormal = normalize( vTBN * mapN );\n\t#else\n\t\tnormal = perturbNormal2Arb( -vViewPosition, normal, mapN );\n\t#endif\n#elif defined( USE_BUMPMAP )\n\tnormal = perturbNormalArb( -vViewPosition, normal, dHdxy_fwd() );\n#endif";
+
+var normalmap_pars_fragment = "#ifdef USE_NORMALMAP\n\tuniform sampler2D normalMap;\n\tuniform vec2 normalScale;\n#endif\n#ifdef OBJECTSPACE_NORMALMAP\n\tuniform mat3 normalMatrix;\n#endif\n#if ! defined ( USE_TANGENT ) && ( defined ( TANGENTSPACE_NORMALMAP ) || defined ( USE_CLEARCOAT_NORMALMAP ) )\n\tvec3 perturbNormal2Arb( vec3 eye_pos, vec3 surf_norm, vec3 mapN ) {\n\t\tvec3 q0 = vec3( dFdx( eye_pos.x ), dFdx( eye_pos.y ), dFdx( eye_pos.z ) );\n\t\tvec3 q1 = vec3( dFdy( eye_pos.x ), dFdy( eye_pos.y ), dFdy( eye_pos.z ) );\n\t\tvec2 st0 = dFdx( vUv.st );\n\t\tvec2 st1 = dFdy( vUv.st );\n\t\tfloat scale = sign( st1.t * st0.s - st0.t * st1.s );\n\t\tvec3 S = normalize( ( q0 * st1.t - q1 * st0.t ) * scale );\n\t\tvec3 T = normalize( ( - q0 * st1.s + q1 * st0.s ) * scale );\n\t\tvec3 N = normalize( surf_norm );\n\t\tmat3 tsn = mat3( S, T, N );\n\t\tmapN.xy *= ( float( gl_FrontFacing ) * 2.0 - 1.0 );\n\t\treturn normalize( tsn * mapN );\n\t}\n#endif";
+
+var clearcoat_normal_fragment_begin = "#ifdef CLEARCOAT\n\tvec3 clearcoatNormal = geometryNormal;\n#endif";
+
+var clearcoat_normal_fragment_maps = "#ifdef USE_CLEARCOAT_NORMALMAP\n\tvec3 clearcoatMapN = texture2D( clearcoatNormalMap, vUv ).xyz * 2.0 - 1.0;\n\tclearcoatMapN.xy *= clearcoatNormalScale;\n\t#ifdef USE_TANGENT\n\t\tclearcoatNormal = normalize( vTBN * clearcoatMapN );\n\t#else\n\t\tclearcoatNormal = perturbNormal2Arb( - vViewPosition, clearcoatNormal, clearcoatMapN );\n\t#endif\n#endif";
+
+var clearcoat_pars_fragment = "#ifdef USE_CLEARCOATMAP\n\tuniform sampler2D clearcoatMap;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tuniform sampler2D clearcoatRoughnessMap;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tuniform sampler2D clearcoatNormalMap;\n\tuniform vec2 clearcoatNormalScale;\n#endif";
+
+var packing = "vec3 packNormalToRGB( const in vec3 normal ) {\n\treturn normalize( normal ) * 0.5 + 0.5;\n}\nvec3 unpackRGBToNormal( const in vec3 rgb ) {\n\treturn 2.0 * rgb.xyz - 1.0;\n}\nconst float PackUpscale = 256. / 255.;const float UnpackDownscale = 255. / 256.;\nconst vec3 PackFactors = vec3( 256. * 256. * 256., 256. * 256., 256. );\nconst vec4 UnpackFactors = UnpackDownscale / vec4( PackFactors, 1. );\nconst float ShiftRight8 = 1. / 256.;\nvec4 packDepthToRGBA( const in float v ) {\n\tvec4 r = vec4( fract( v * PackFactors ), v );\n\tr.yzw -= r.xyz * ShiftRight8;\treturn r * PackUpscale;\n}\nfloat unpackRGBAToDepth( const in vec4 v ) {\n\treturn dot( v, UnpackFactors );\n}\nvec4 pack2HalfToRGBA( vec2 v ) {\n\tvec4 r = vec4( v.x, fract( v.x * 255.0 ), v.y, fract( v.y * 255.0 ));\n\treturn vec4( r.x - r.y / 255.0, r.y, r.z - r.w / 255.0, r.w);\n}\nvec2 unpackRGBATo2Half( vec4 v ) {\n\treturn vec2( v.x + ( v.y / 255.0 ), v.z + ( v.w / 255.0 ) );\n}\nfloat viewZToOrthographicDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( viewZ + near ) / ( near - far );\n}\nfloat orthographicDepthToViewZ( const in float linearClipZ, const in float near, const in float far ) {\n\treturn linearClipZ * ( near - far ) - near;\n}\nfloat viewZToPerspectiveDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn (( near + viewZ ) * far ) / (( far - near ) * viewZ );\n}\nfloat perspectiveDepthToViewZ( const in float invClipZ, const in float near, const in float far ) {\n\treturn ( near * far ) / ( ( far - near ) * invClipZ - far );\n}";
+
+var premultiplied_alpha_fragment = "#ifdef PREMULTIPLIED_ALPHA\n\tgl_FragColor.rgb *= gl_FragColor.a;\n#endif";
+
+var project_vertex = "vec4 mvPosition = vec4( transformed, 1.0 );\n#ifdef USE_INSTANCING\n\tmvPosition = instanceMatrix * mvPosition;\n#endif\nmvPosition = modelViewMatrix * mvPosition;\ngl_Position = projectionMatrix * mvPosition;";
+
+var dithering_fragment = "#ifdef DITHERING\n\tgl_FragColor.rgb = dithering( gl_FragColor.rgb );\n#endif";
+
+var dithering_pars_fragment = "#ifdef DITHERING\n\tvec3 dithering( vec3 color ) {\n\t\tfloat grid_position = rand( gl_FragCoord.xy );\n\t\tvec3 dither_shift_RGB = vec3( 0.25 / 255.0, -0.25 / 255.0, 0.25 / 255.0 );\n\t\tdither_shift_RGB = mix( 2.0 * dither_shift_RGB, -2.0 * dither_shift_RGB, grid_position );\n\t\treturn color + dither_shift_RGB;\n\t}\n#endif";
+
+var roughnessmap_fragment = "float roughnessFactor = roughness;\n#ifdef USE_ROUGHNESSMAP\n\tvec4 texelRoughness = texture2D( roughnessMap, vUv );\n\troughnessFactor *= texelRoughness.g;\n#endif";
+
+var roughnessmap_pars_fragment = "#ifdef USE_ROUGHNESSMAP\n\tuniform sampler2D roughnessMap;\n#endif";
+
+var shadowmap_pars_fragment = "#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D directionalShadowMap[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D spotShadowMap[ NUM_SPOT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vSpotShadowCoord[ NUM_SPOT_LIGHT_SHADOWS ];\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D pointShadowMap[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n\tfloat texture2DCompare( sampler2D depths, vec2 uv, float compare ) {\n\t\treturn step( compare, unpackRGBAToDepth( texture2D( depths, uv ) ) );\n\t}\n\tvec2 texture2DDistribution( sampler2D shadow, vec2 uv ) {\n\t\treturn unpackRGBATo2Half( texture2D( shadow, uv ) );\n\t}\n\tfloat VSMShadow (sampler2D shadow, vec2 uv, float compare ){\n\t\tfloat occlusion = 1.0;\n\t\tvec2 distribution = texture2DDistribution( shadow, uv );\n\t\tfloat hard_shadow = step( compare , distribution.x );\n\t\tif (hard_shadow != 1.0 ) {\n\t\t\tfloat distance = compare - distribution.x ;\n\t\t\tfloat variance = max( 0.00000, distribution.y * distribution.y );\n\t\t\tfloat softness_probability = variance / (variance + distance * distance );\t\t\tsoftness_probability = clamp( ( softness_probability - 0.3 ) / ( 0.95 - 0.3 ), 0.0, 1.0 );\t\t\tocclusion = clamp( max( hard_shadow, softness_probability ), 0.0, 1.0 );\n\t\t}\n\t\treturn occlusion;\n\t}\n\tfloat getShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord ) {\n\t\tfloat shadow = 1.0;\n\t\tshadowCoord.xyz /= shadowCoord.w;\n\t\tshadowCoord.z += shadowBias;\n\t\tbvec4 inFrustumVec = bvec4 ( shadowCoord.x >= 0.0, shadowCoord.x <= 1.0, shadowCoord.y >= 0.0, shadowCoord.y <= 1.0 );\n\t\tbool inFrustum = all( inFrustumVec );\n\t\tbvec2 frustumTestVec = bvec2( inFrustum, shadowCoord.z <= 1.0 );\n\t\tbool frustumTest = all( frustumTestVec );\n\t\tif ( frustumTest ) {\n\t\t#if defined( SHADOWMAP_TYPE_PCF )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx0 = - texelSize.x * shadowRadius;\n\t\t\tfloat dy0 = - texelSize.y * shadowRadius;\n\t\t\tfloat dx1 = + texelSize.x * shadowRadius;\n\t\t\tfloat dy1 = + texelSize.y * shadowRadius;\n\t\t\tfloat dx2 = dx0 / 2.0;\n\t\t\tfloat dy2 = dy0 / 2.0;\n\t\t\tfloat dx3 = dx1 / 2.0;\n\t\t\tfloat dy3 = dy1 / 2.0;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy1 ), shadowCoord.z )\n\t\t\t) * ( 1.0 / 17.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_PCF_SOFT )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx = texelSize.x;\n\t\t\tfloat dy = texelSize.y;\n\t\t\tvec2 uv = shadowCoord.xy;\n\t\t\tvec2 f = fract( uv * shadowMapSize + 0.5 );\n\t\t\tuv -= f * texelSize;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, uv, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( dx, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( 0.0, dy ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + texelSize, shadowCoord.z ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, 0.0 ), shadowCoord.z ), \n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 0.0 ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, dy ), shadowCoord.z ), \n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, dy ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( 0.0, -dy ), shadowCoord.z ), \n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 0.0, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( dx, -dy ), shadowCoord.z ), \n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( mix( texture2DCompare( shadowMap, uv + vec2( -dx, -dy ), shadowCoord.z ), \n\t\t\t\t\t\t  texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t\t  f.x ),\n\t\t\t\t\t mix( texture2DCompare( shadowMap, uv + vec2( -dx, 2.0 * dy ), shadowCoord.z ), \n\t\t\t\t\t\t  texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t\t  f.x ),\n\t\t\t\t\t f.y )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_VSM )\n\t\t\tshadow = VSMShadow( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#else\n\t\t\tshadow = texture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#endif\n\t\t}\n\t\treturn shadow;\n\t}\n\tvec2 cubeToUV( vec3 v, float texelSizeY ) {\n\t\tvec3 absV = abs( v );\n\t\tfloat scaleToCube = 1.0 / max( absV.x, max( absV.y, absV.z ) );\n\t\tabsV *= scaleToCube;\n\t\tv *= scaleToCube * ( 1.0 - 2.0 * texelSizeY );\n\t\tvec2 planar = v.xy;\n\t\tfloat almostATexel = 1.5 * texelSizeY;\n\t\tfloat almostOne = 1.0 - almostATexel;\n\t\tif ( absV.z >= almostOne ) {\n\t\t\tif ( v.z > 0.0 )\n\t\t\t\tplanar.x = 4.0 - v.x;\n\t\t} else if ( absV.x >= almostOne ) {\n\t\t\tfloat signX = sign( v.x );\n\t\t\tplanar.x = v.z * signX + 2.0 * signX;\n\t\t} else if ( absV.y >= almostOne ) {\n\t\t\tfloat signY = sign( v.y );\n\t\t\tplanar.x = v.x + 2.0 * signY + 2.0;\n\t\t\tplanar.y = v.z * signY - 2.0;\n\t\t}\n\t\treturn vec2( 0.125, 0.25 ) * planar + vec2( 0.375, 0.75 );\n\t}\n\tfloat getPointShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord, float shadowCameraNear, float shadowCameraFar ) {\n\t\tvec2 texelSize = vec2( 1.0 ) / ( shadowMapSize * vec2( 4.0, 2.0 ) );\n\t\tvec3 lightToPosition = shadowCoord.xyz;\n\t\tfloat dp = ( length( lightToPosition ) - shadowCameraNear ) / ( shadowCameraFar - shadowCameraNear );\t\tdp += shadowBias;\n\t\tvec3 bd3D = normalize( lightToPosition );\n\t\t#if defined( SHADOWMAP_TYPE_PCF ) || defined( SHADOWMAP_TYPE_PCF_SOFT ) || defined( SHADOWMAP_TYPE_VSM )\n\t\t\tvec2 offset = vec2( - 1, 1 ) * shadowRadius * texelSize.y;\n\t\t\treturn (\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxx, texelSize.y ), dp )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#else\n\t\t\treturn texture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp );\n\t\t#endif\n\t}\n#endif";
+
+var shadowmap_pars_vertex = "#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform mat4 directionalShadowMatrix[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tuniform mat4 spotShadowMatrix[ NUM_SPOT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vSpotShadowCoord[ NUM_SPOT_LIGHT_SHADOWS ];\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform mat4 pointShadowMatrix[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n#endif";
+
+var shadowmap_vertex = "#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0 || NUM_SPOT_LIGHT_SHADOWS > 0 || NUM_POINT_LIGHT_SHADOWS > 0\n\t\tvec3 shadowWorldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\t\tvec4 shadowWorldPosition;\n\t#endif\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * directionalLightShadows[ i ].shadowNormalBias, 0 );\n\t\tvDirectionalShadowCoord[ i ] = directionalShadowMatrix[ i ] * shadowWorldPosition;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_SHADOWS; i ++ ) {\n\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * spotLightShadows[ i ].shadowNormalBias, 0 );\n\t\tvSpotShadowCoord[ i ] = spotShadowMatrix[ i ] * shadowWorldPosition;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * pointLightShadows[ i ].shadowNormalBias, 0 );\n\t\tvPointShadowCoord[ i ] = pointShadowMatrix[ i ] * shadowWorldPosition;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n#endif";
+
+var shadowmask_pars_fragment = "float getShadowMask() {\n\tfloat shadow = 1.0;\n\t#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\tdirectionalLight = directionalLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_SHADOWS; i ++ ) {\n\t\tspotLight = spotLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowBias, spotLight.shadowRadius, vSpotShadowCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\tpointLight = pointLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getPointShadow( pointShadowMap[ i ], pointLight.shadowMapSize, pointLight.shadowBias, pointLight.shadowRadius, vPointShadowCoord[ i ], pointLight.shadowCameraNear, pointLight.shadowCameraFar ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#endif\n\treturn shadow;\n}";
+
+var skinbase_vertex = "#ifdef USE_SKINNING\n\tmat4 boneMatX = getBoneMatrix( skinIndex.x );\n\tmat4 boneMatY = getBoneMatrix( skinIndex.y );\n\tmat4 boneMatZ = getBoneMatrix( skinIndex.z );\n\tmat4 boneMatW = getBoneMatrix( skinIndex.w );\n#endif";
+
+var skinning_pars_vertex = "#ifdef USE_SKINNING\n\tuniform mat4 bindMatrix;\n\tuniform mat4 bindMatrixInverse;\n\t#ifdef BONE_TEXTURE\n\t\tuniform highp sampler2D boneTexture;\n\t\tuniform int boneTextureSize;\n\t\tmat4 getBoneMatrix( const in float i ) {\n\t\t\tfloat j = i * 4.0;\n\t\t\tfloat x = mod( j, float( boneTextureSize ) );\n\t\t\tfloat y = floor( j / float( boneTextureSize ) );\n\t\t\tfloat dx = 1.0 / float( boneTextureSize );\n\t\t\tfloat dy = 1.0 / float( boneTextureSize );\n\t\t\ty = dy * ( y + 0.5 );\n\t\t\tvec4 v1 = texture2D( boneTexture, vec2( dx * ( x + 0.5 ), y ) );\n\t\t\tvec4 v2 = texture2D( boneTexture, vec2( dx * ( x + 1.5 ), y ) );\n\t\t\tvec4 v3 = texture2D( boneTexture, vec2( dx * ( x + 2.5 ), y ) );\n\t\t\tvec4 v4 = texture2D( boneTexture, vec2( dx * ( x + 3.5 ), y ) );\n\t\t\tmat4 bone = mat4( v1, v2, v3, v4 );\n\t\t\treturn bone;\n\t\t}\n\t#else\n\t\tuniform mat4 boneMatrices[ MAX_BONES ];\n\t\tmat4 getBoneMatrix( const in float i ) {\n\t\t\tmat4 bone = boneMatrices[ int(i) ];\n\t\t\treturn bone;\n\t\t}\n\t#endif\n#endif";
+
+var skinning_vertex = "#ifdef USE_SKINNING\n\tvec4 skinVertex = bindMatrix * vec4( transformed, 1.0 );\n\tvec4 skinned = vec4( 0.0 );\n\tskinned += boneMatX * skinVertex * skinWeight.x;\n\tskinned += boneMatY * skinVertex * skinWeight.y;\n\tskinned += boneMatZ * skinVertex * skinWeight.z;\n\tskinned += boneMatW * skinVertex * skinWeight.w;\n\ttransformed = ( bindMatrixInverse * skinned ).xyz;\n#endif";
+
+var skinnormal_vertex = "#ifdef USE_SKINNING\n\tmat4 skinMatrix = mat4( 0.0 );\n\tskinMatrix += skinWeight.x * boneMatX;\n\tskinMatrix += skinWeight.y * boneMatY;\n\tskinMatrix += skinWeight.z * boneMatZ;\n\tskinMatrix += skinWeight.w * boneMatW;\n\tskinMatrix = bindMatrixInverse * skinMatrix * bindMatrix;\n\tobjectNormal = vec4( skinMatrix * vec4( objectNormal, 0.0 ) ).xyz;\n\t#ifdef USE_TANGENT\n\t\tobjectTangent = vec4( skinMatrix * vec4( objectTangent, 0.0 ) ).xyz;\n\t#endif\n#endif";
+
+var specularmap_fragment = "float specularStrength;\n#ifdef USE_SPECULARMAP\n\tvec4 texelSpecular = texture2D( specularMap, vUv );\n\tspecularStrength = texelSpecular.r;\n#else\n\tspecularStrength = 1.0;\n#endif";
+
+var specularmap_pars_fragment = "#ifdef USE_SPECULARMAP\n\tuniform sampler2D specularMap;\n#endif";
+
+var tonemapping_fragment = "#if defined( TONE_MAPPING )\n\tgl_FragColor.rgb = toneMapping( gl_FragColor.rgb );\n#endif";
+
+var tonemapping_pars_fragment = "#ifndef saturate\n#define saturate(a) clamp( a, 0.0, 1.0 )\n#endif\nuniform float toneMappingExposure;\nvec3 LinearToneMapping( vec3 color ) {\n\treturn toneMappingExposure * color;\n}\nvec3 ReinhardToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\treturn saturate( color / ( vec3( 1.0 ) + color ) );\n}\nvec3 OptimizedCineonToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\tcolor = max( vec3( 0.0 ), color - 0.004 );\n\treturn pow( ( color * ( 6.2 * color + 0.5 ) ) / ( color * ( 6.2 * color + 1.7 ) + 0.06 ), vec3( 2.2 ) );\n}\nvec3 RRTAndODTFit( vec3 v ) {\n\tvec3 a = v * ( v + 0.0245786 ) - 0.000090537;\n\tvec3 b = v * ( 0.983729 * v + 0.4329510 ) + 0.238081;\n\treturn a / b;\n}\nvec3 ACESFilmicToneMapping( vec3 color ) {\n\tconst mat3 ACESInputMat = mat3(\n\t\tvec3( 0.59719, 0.07600, 0.02840 ),\t\tvec3( 0.35458, 0.90834, 0.13383 ),\n\t\tvec3( 0.04823, 0.01566, 0.83777 )\n\t);\n\tconst mat3 ACESOutputMat = mat3(\n\t\tvec3(  1.60475, -0.10208, -0.00327 ),\t\tvec3( -0.53108,  1.10813, -0.07276 ),\n\t\tvec3( -0.07367, -0.00605,  1.07602 )\n\t);\n\tcolor *= toneMappingExposure / 0.6;\n\tcolor = ACESInputMat * color;\n\tcolor = RRTAndODTFit( color );\n\tcolor = ACESOutputMat * color;\n\treturn saturate( color );\n}\nvec3 CustomToneMapping( vec3 color ) { return color; }";
+
+var transmissionmap_fragment = "#ifdef USE_TRANSMISSIONMAP\n\ttotalTransmission *= texture2D( transmissionMap, vUv ).r;\n#endif";
+
+var transmissionmap_pars_fragment = "#ifdef USE_TRANSMISSIONMAP\n\tuniform sampler2D transmissionMap;\n#endif";
+
+var uv_pars_fragment = "#if ( defined( USE_UV ) && ! defined( UVS_VERTEX_ONLY ) )\n\tvarying vec2 vUv;\n#endif";
+
+var uv_pars_vertex = "#ifdef USE_UV\n\t#ifdef UVS_VERTEX_ONLY\n\t\tvec2 vUv;\n\t#else\n\t\tvarying vec2 vUv;\n\t#endif\n\tuniform mat3 uvTransform;\n#endif";
+
+var uv_vertex = "#ifdef USE_UV\n\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n#endif";
+
+var uv2_pars_fragment = "#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\tvarying vec2 vUv2;\n#endif";
+
+var uv2_pars_vertex = "#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\tattribute vec2 uv2;\n\tvarying vec2 vUv2;\n\tuniform mat3 uv2Transform;\n#endif";
+
+var uv2_vertex = "#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\tvUv2 = ( uv2Transform * vec3( uv2, 1 ) ).xy;\n#endif";
+
+var worldpos_vertex = "#if defined( USE_ENVMAP ) || defined( DISTANCE ) || defined ( USE_SHADOWMAP )\n\tvec4 worldPosition = vec4( transformed, 1.0 );\n\t#ifdef USE_INSTANCING\n\t\tworldPosition = instanceMatrix * worldPosition;\n\t#endif\n\tworldPosition = modelMatrix * worldPosition;\n#endif";
+
+var background_frag = "uniform sampler2D t2D;\nvarying vec2 vUv;\nvoid main() {\n\tvec4 texColor = texture2D( t2D, vUv );\n\tgl_FragColor = mapTexelToLinear( texColor );\n\t#include <tonemapping_fragment>\n\t#include <encodings_fragment>\n}";
+
+var background_vert = "varying vec2 vUv;\nuniform mat3 uvTransform;\nvoid main() {\n\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n\tgl_Position = vec4( position.xy, 1.0, 1.0 );\n}";
+
+var cube_frag = "#include <envmap_common_pars_fragment>\nuniform float opacity;\nvarying vec3 vWorldDirection;\n#include <cube_uv_reflection_fragment>\nvoid main() {\n\tvec3 vReflect = vWorldDirection;\n\t#include <envmap_fragment>\n\tgl_FragColor = envColor;\n\tgl_FragColor.a *= opacity;\n\t#include <tonemapping_fragment>\n\t#include <encodings_fragment>\n}";
+
+var cube_vert = "varying vec3 vWorldDirection;\n#include <common>\nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include <begin_vertex>\n\t#include <project_vertex>\n\tgl_Position.z = gl_Position.w;\n}";
+
+var depth_frag = "#if DEPTH_PACKING == 3200\n\tuniform float opacity;\n#endif\n#include <common>\n#include <packing>\n#include <uv_pars_fragment>\n#include <map_pars_fragment>\n#include <alphamap_pars_fragment>\n#include <logdepthbuf_pars_fragment>\n#include <clipping_planes_pars_fragment>\nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\t#include <clipping_planes_fragment>\n\tvec4 diffuseColor = vec4( 1.0 );\n\t#if DEPTH_PACKING == 3200\n\t\tdiffuseColor.a = opacity;\n\t#endif\n\t#include <map_fragment>\n\t#include <alphamap_fragment>\n\t#include <alphatest_fragment>\n\t#include <logdepthbuf_fragment>\n\tfloat fragCoordZ = 0.5 * vHighPrecisionZW[0] / vHighPrecisionZW[1] + 0.5;\n\t#if DEPTH_PACKING == 3200\n\t\tgl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), opacity );\n\t#elif DEPTH_PACKING == 3201\n\t\tgl_FragColor = packDepthToRGBA( fragCoordZ );\n\t#endif\n}";
+
+var depth_vert = "#include <common>\n#include <uv_pars_vertex>\n#include <displacementmap_pars_vertex>\n#include <morphtarget_pars_vertex>\n#include <skinning_pars_vertex>\n#include <logdepthbuf_pars_vertex>\n#include <clipping_planes_pars_vertex>\nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\t#include <uv_vertex>\n\t#include <skinbase_vertex>\n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include <beginnormal_vertex>\n\t\t#include <morphnormal_vertex>\n\t\t#include <skinnormal_vertex>\n\t#endif\n\t#include <begin_vertex>\n\t#include <morphtarget_vertex>\n\t#include <skinning_vertex>\n\t#include <displacementmap_vertex>\n\t#include <project_vertex>\n\t#include <logdepthbuf_vertex>\n\t#include <clipping_planes_vertex>\n\tvHighPrecisionZW = gl_Position.zw;\n}";
+
+var distanceRGBA_frag = "#define DISTANCE\nuniform vec3 referencePosition;\nuniform float nearDistance;\nuniform float farDistance;\nvarying vec3 vWorldPosition;\n#include <common>\n#include <packing>\n#include <uv_pars_fragment>\n#include <map_pars_fragment>\n#include <alphamap_pars_fragment>\n#include <clipping_planes_pars_fragment>\nvoid main () {\n\t#include <clipping_planes_fragment>\n\tvec4 diffuseColor = vec4( 1.0 );\n\t#include <map_fragment>\n\t#include <alphamap_fragment>\n\t#include <alphatest_fragment>\n\tfloat dist = length( vWorldPosition - referencePosition );\n\tdist = ( dist - nearDistance ) / ( farDistance - nearDistance );\n\tdist = saturate( dist );\n\tgl_FragColor = packDepthToRGBA( dist );\n}";
+
+var distanceRGBA_vert = "#define DISTANCE\nvarying vec3 vWorldPosition;\n#include <common>\n#include <uv_pars_vertex>\n#include <displacementmap_pars_vertex>\n#include <morphtarget_pars_vertex>\n#include <skinning_pars_vertex>\n#include <clipping_planes_pars_vertex>\nvoid main() {\n\t#include <uv_vertex>\n\t#include <skinbase_vertex>\n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include <beginnormal_vertex>\n\t\t#include <morphnormal_vertex>\n\t\t#include <skinnormal_vertex>\n\t#endif\n\t#include <begin_vertex>\n\t#include <morphtarget_vertex>\n\t#include <skinning_vertex>\n\t#include <displacementmap_vertex>\n\t#include <project_vertex>\n\t#include <worldpos_vertex>\n\t#include <clipping_planes_vertex>\n\tvWorldPosition = worldPosition.xyz;\n}";
+
+var equirect_frag = "uniform sampler2D tEquirect;\nvarying vec3 vWorldDirection;\n#include <common>\nvoid main() {\n\tvec3 direction = normalize( vWorldDirection );\n\tvec2 sampleUV = equirectUv( direction );\n\tvec4 texColor = texture2D( tEquirect, sampleUV );\n\tgl_FragColor = mapTexelToLinear( texColor );\n\t#include <tonemapping_fragment>\n\t#include <encodings_fragment>\n}";
+
+var equirect_vert = "varying vec3 vWorldDirection;\n#include <common>\nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include <begin_vertex>\n\t#include <project_vertex>\n}";
+
+var linedashed_frag = "uniform vec3 diffuse;\nuniform float opacity;\nuniform float dashSize;\nuniform float totalSize;\nvarying float vLineDistance;\n#include <common>\n#include <color_pars_fragment>\n#include <fog_pars_fragment>\n#include <logdepthbuf_pars_fragment>\n#include <clipping_planes_pars_fragment>\nvoid main() {\n\t#include <clipping_planes_fragment>\n\tif ( mod( vLineDistance, totalSize ) > dashSize ) {\n\t\tdiscard;\n\t}\n\tvec3 outgoingLight = vec3( 0.0 );\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include <logdepthbuf_fragment>\n\t#include <color_fragment>\n\toutgoingLight = diffuseColor.rgb;\n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include <tonemapping_fragment>\n\t#include <encodings_fragment>\n\t#include <fog_fragment>\n\t#include <premultiplied_alpha_fragment>\n}";
+
+var linedashed_vert = "uniform float scale;\nattribute float lineDistance;\nvarying float vLineDistance;\n#include <common>\n#include <color_pars_vertex>\n#include <fog_pars_vertex>\n#include <morphtarget_pars_vertex>\n#include <logdepthbuf_pars_vertex>\n#include <clipping_planes_pars_vertex>\nvoid main() {\n\tvLineDistance = scale * lineDistance;\n\t#include <color_vertex>\n\t#include <begin_vertex>\n\t#include <morphtarget_vertex>\n\t#include <project_vertex>\n\t#include <logdepthbuf_vertex>\n\t#include <clipping_planes_vertex>\n\t#include <fog_vertex>\n}";
+
+var meshbasic_frag = "uniform vec3 diffuse;\nuniform float opacity;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include <common>\n#include <dithering_pars_fragment>\n#include <color_pars_fragment>\n#include <uv_pars_fragment>\n#include <uv2_pars_fragment>\n#include <map_pars_fragment>\n#include <alphamap_pars_fragment>\n#include <aomap_pars_fragment>\n#include <lightmap_pars_fragment>\n#include <envmap_common_pars_fragment>\n#include <envmap_pars_fragment>\n#include <cube_uv_reflection_fragment>\n#include <fog_pars_fragment>\n#include <specularmap_pars_fragment>\n#include <logdepthbuf_pars_fragment>\n#include <clipping_planes_pars_fragment>\nvoid main() {\n\t#include <clipping_planes_fragment>\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include <logdepthbuf_fragment>\n\t#include <map_fragment>\n\t#include <color_fragment>\n\t#include <alphamap_fragment>\n\t#include <alphatest_fragment>\n\t#include <specularmap_fragment>\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\t#ifdef USE_LIGHTMAP\n\t\n\t\tvec4 lightMapTexel= texture2D( lightMap, vUv2 );\n\t\treflectedLight.indirectDiffuse += lightMapTexelToLinear( lightMapTexel ).rgb * lightMapIntensity;\n\t#else\n\t\treflectedLight.indirectDiffuse += vec3( 1.0 );\n\t#endif\n\t#include <aomap_fragment>\n\treflectedLight.indirectDiffuse *= diffuseColor.rgb;\n\tvec3 outgoingLight = reflectedLight.indirectDiffuse;\n\t#include <envmap_fragment>\n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include <tonemapping_fragment>\n\t#include <encodings_fragment>\n\t#include <fog_fragment>\n\t#include <premultiplied_alpha_fragment>\n\t#include <dithering_fragment>\n}";
+
+var meshbasic_vert = "#include <common>\n#include <uv_pars_vertex>\n#include <uv2_pars_vertex>\n#include <envmap_pars_vertex>\n#include <color_pars_vertex>\n#include <fog_pars_vertex>\n#include <morphtarget_pars_vertex>\n#include <skinning_pars_vertex>\n#include <logdepthbuf_pars_vertex>\n#include <clipping_planes_pars_vertex>\nvoid main() {\n\t#include <uv_vertex>\n\t#include <uv2_vertex>\n\t#include <color_vertex>\n\t#include <skinbase_vertex>\n\t#ifdef USE_ENVMAP\n\t#include <beginnormal_vertex>\n\t#include <morphnormal_vertex>\n\t#include <skinnormal_vertex>\n\t#include <defaultnormal_vertex>\n\t#endif\n\t#include <begin_vertex>\n\t#include <morphtarget_vertex>\n\t#include <skinning_vertex>\n\t#include <project_vertex>\n\t#include <logdepthbuf_vertex>\n\t#include <worldpos_vertex>\n\t#include <clipping_planes_vertex>\n\t#include <envmap_vertex>\n\t#include <fog_vertex>\n}";
+
+var meshlambert_frag = "uniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\nvarying vec3 vLightFront;\nvarying vec3 vIndirectFront;\n#ifdef DOUBLE_SIDED\n\tvarying vec3 vLightBack;\n\tvarying vec3 vIndirectBack;\n#endif\n#include <common>\n#include <packing>\n#include <dithering_pars_fragment>\n#include <color_pars_fragment>\n#include <uv_pars_fragment>\n#include <uv2_pars_fragment>\n#include <map_pars_fragment>\n#include <alphamap_pars_fragment>\n#include <aomap_pars_fragment>\n#include <lightmap_pars_fragment>\n#include <emissivemap_pars_fragment>\n#include <envmap_common_pars_fragment>\n#include <envmap_pars_fragment>\n#include <cube_uv_reflection_fragment>\n#include <bsdfs>\n#include <lights_pars_begin>\n#include <fog_pars_fragment>\n#include <shadowmap_pars_fragment>\n#include <shadowmask_pars_fragment>\n#include <specularmap_pars_fragment>\n#include <logdepthbuf_pars_fragment>\n#include <clipping_planes_pars_fragment>\nvoid main() {\n\t#include <clipping_planes_fragment>\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include <logdepthbuf_fragment>\n\t#include <map_fragment>\n\t#include <color_fragment>\n\t#include <alphamap_fragment>\n\t#include <alphatest_fragment>\n\t#include <specularmap_fragment>\n\t#include <emissivemap_fragment>\n\t#ifdef DOUBLE_SIDED\n\t\treflectedLight.indirectDiffuse += ( gl_FrontFacing ) ? vIndirectFront : vIndirectBack;\n\t#else\n\t\treflectedLight.indirectDiffuse += vIndirectFront;\n\t#endif\n\t#include <lightmap_fragment>\n\treflectedLight.indirectDiffuse *= BRDF_Diffuse_Lambert( diffuseColor.rgb );\n\t#ifdef DOUBLE_SIDED\n\t\treflectedLight.directDiffuse = ( gl_FrontFacing ) ? vLightFront : vLightBack;\n\t#else\n\t\treflectedLight.directDiffuse = vLightFront;\n\t#endif\n\treflectedLight.directDiffuse *= BRDF_Diffuse_Lambert( diffuseColor.rgb ) * getShadowMask();\n\t#include <aomap_fragment>\n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include <envmap_fragment>\n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include <tonemapping_fragment>\n\t#include <encodings_fragment>\n\t#include <fog_fragment>\n\t#include <premultiplied_alpha_fragment>\n\t#include <dithering_fragment>\n}";
+
+var meshlambert_vert = "#define LAMBERT\nvarying vec3 vLightFront;\nvarying vec3 vIndirectFront;\n#ifdef DOUBLE_SIDED\n\tvarying vec3 vLightBack;\n\tvarying vec3 vIndirectBack;\n#endif\n#include <common>\n#include <uv_pars_vertex>\n#include <uv2_pars_vertex>\n#include <envmap_pars_vertex>\n#include <bsdfs>\n#include <lights_pars_begin>\n#include <color_pars_vertex>\n#include <fog_pars_vertex>\n#include <morphtarget_pars_vertex>\n#include <skinning_pars_vertex>\n#include <shadowmap_pars_vertex>\n#include <logdepthbuf_pars_vertex>\n#include <clipping_planes_pars_vertex>\nvoid main() {\n\t#include <uv_vertex>\n\t#include <uv2_vertex>\n\t#include <color_vertex>\n\t#include <beginnormal_vertex>\n\t#include <morphnormal_vertex>\n\t#include <skinbase_vertex>\n\t#include <skinnormal_vertex>\n\t#include <defaultnormal_vertex>\n\t#include <begin_vertex>\n\t#include <morphtarget_vertex>\n\t#include <skinning_vertex>\n\t#include <project_vertex>\n\t#include <logdepthbuf_vertex>\n\t#include <clipping_planes_vertex>\n\t#include <worldpos_vertex>\n\t#include <envmap_vertex>\n\t#include <lights_lambert_vertex>\n\t#include <shadowmap_vertex>\n\t#include <fog_vertex>\n}";
+
+var meshmatcap_frag = "#define MATCAP\nuniform vec3 diffuse;\nuniform float opacity;\nuniform sampler2D matcap;\nvarying vec3 vViewPosition;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include <common>\n#include <dithering_pars_fragment>\n#include <color_pars_fragment>\n#include <uv_pars_fragment>\n#include <map_pars_fragment>\n#include <alphamap_pars_fragment>\n#include <fog_pars_fragment>\n#include <bumpmap_pars_fragment>\n#include <normalmap_pars_fragment>\n#include <logdepthbuf_pars_fragment>\n#include <clipping_planes_pars_fragment>\nvoid main() {\n\t#include <clipping_planes_fragment>\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include <logdepthbuf_fragment>\n\t#include <map_fragment>\n\t#include <color_fragment>\n\t#include <alphamap_fragment>\n\t#include <alphatest_fragment>\n\t#include <normal_fragment_begin>\n\t#include <normal_fragment_maps>\n\tvec3 viewDir = normalize( vViewPosition );\n\tvec3 x = normalize( vec3( viewDir.z, 0.0, - viewDir.x ) );\n\tvec3 y = cross( viewDir, x );\n\tvec2 uv = vec2( dot( x, normal ), dot( y, normal ) ) * 0.495 + 0.5;\n\t#ifdef USE_MATCAP\n\t\tvec4 matcapColor = texture2D( matcap, uv );\n\t\tmatcapColor = matcapTexelToLinear( matcapColor );\n\t#else\n\t\tvec4 matcapColor = vec4( 1.0 );\n\t#endif\n\tvec3 outgoingLight = diffuseColor.rgb * matcapColor.rgb;\n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include <tonemapping_fragment>\n\t#include <encodings_fragment>\n\t#include <fog_fragment>\n\t#include <premultiplied_alpha_fragment>\n\t#include <dithering_fragment>\n}";
+
+var meshmatcap_vert = "#define MATCAP\nvarying vec3 vViewPosition;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include <common>\n#include <uv_pars_vertex>\n#include <color_pars_vertex>\n#include <displacementmap_pars_vertex>\n#include <fog_pars_vertex>\n#include <morphtarget_pars_vertex>\n#include <skinning_pars_vertex>\n#include <logdepthbuf_pars_vertex>\n#include <clipping_planes_pars_vertex>\nvoid main() {\n\t#include <uv_vertex>\n\t#include <color_vertex>\n\t#include <beginnormal_vertex>\n\t#include <morphnormal_vertex>\n\t#include <skinbase_vertex>\n\t#include <skinnormal_vertex>\n\t#include <defaultnormal_vertex>\n\t#ifndef FLAT_SHADED\n\t\tvNormal = normalize( transformedNormal );\n\t#endif\n\t#include <begin_vertex>\n\t#include <morphtarget_vertex>\n\t#include <skinning_vertex>\n\t#include <displacementmap_vertex>\n\t#include <project_vertex>\n\t#include <logdepthbuf_vertex>\n\t#include <clipping_planes_vertex>\n\t#include <fog_vertex>\n\tvViewPosition = - mvPosition.xyz;\n}";
+
+var meshtoon_frag = "#define TOON\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\n#include <common>\n#include <packing>\n#include <dithering_pars_fragment>\n#include <color_pars_fragment>\n#include <uv_pars_fragment>\n#include <uv2_pars_fragment>\n#include <map_pars_fragment>\n#include <alphamap_pars_fragment>\n#include <aomap_pars_fragment>\n#include <lightmap_pars_fragment>\n#include <emissivemap_pars_fragment>\n#include <gradientmap_pars_fragment>\n#include <fog_pars_fragment>\n#include <bsdfs>\n#include <lights_pars_begin>\n#include <lights_toon_pars_fragment>\n#include <shadowmap_pars_fragment>\n#include <bumpmap_pars_fragment>\n#include <normalmap_pars_fragment>\n#include <logdepthbuf_pars_fragment>\n#include <clipping_planes_pars_fragment>\nvoid main() {\n\t#include <clipping_planes_fragment>\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include <logdepthbuf_fragment>\n\t#include <map_fragment>\n\t#include <color_fragment>\n\t#include <alphamap_fragment>\n\t#include <alphatest_fragment>\n\t#include <normal_fragment_begin>\n\t#include <normal_fragment_maps>\n\t#include <emissivemap_fragment>\n\t#include <lights_toon_fragment>\n\t#include <lights_fragment_begin>\n\t#include <lights_fragment_maps>\n\t#include <lights_fragment_end>\n\t#include <aomap_fragment>\n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include <tonemapping_fragment>\n\t#include <encodings_fragment>\n\t#include <fog_fragment>\n\t#include <premultiplied_alpha_fragment>\n\t#include <dithering_fragment>\n}";
+
+var meshtoon_vert = "#define TOON\nvarying vec3 vViewPosition;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include <common>\n#include <uv_pars_vertex>\n#include <uv2_pars_vertex>\n#include <displacementmap_pars_vertex>\n#include <color_pars_vertex>\n#include <fog_pars_vertex>\n#include <morphtarget_pars_vertex>\n#include <skinning_pars_vertex>\n#include <shadowmap_pars_vertex>\n#include <logdepthbuf_pars_vertex>\n#include <clipping_planes_pars_vertex>\nvoid main() {\n\t#include <uv_vertex>\n\t#include <uv2_vertex>\n\t#include <color_vertex>\n\t#include <beginnormal_vertex>\n\t#include <morphnormal_vertex>\n\t#include <skinbase_vertex>\n\t#include <skinnormal_vertex>\n\t#include <defaultnormal_vertex>\n#ifndef FLAT_SHADED\n\tvNormal = normalize( transformedNormal );\n#endif\n\t#include <begin_vertex>\n\t#include <morphtarget_vertex>\n\t#include <skinning_vertex>\n\t#include <displacementmap_vertex>\n\t#include <project_vertex>\n\t#include <logdepthbuf_vertex>\n\t#include <clipping_planes_vertex>\n\tvViewPosition = - mvPosition.xyz;\n\t#include <worldpos_vertex>\n\t#include <shadowmap_vertex>\n\t#include <fog_vertex>\n}";
+
+var meshphong_frag = "#define PHONG\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform vec3 specular;\nuniform float shininess;\nuniform float opacity;\n#include <common>\n#include <packing>\n#include <dithering_pars_fragment>\n#include <color_pars_fragment>\n#include <uv_pars_fragment>\n#include <uv2_pars_fragment>\n#include <map_pars_fragment>\n#include <alphamap_pars_fragment>\n#include <aomap_pars_fragment>\n#include <lightmap_pars_fragment>\n#include <emissivemap_pars_fragment>\n#include <envmap_common_pars_fragment>\n#include <envmap_pars_fragment>\n#include <cube_uv_reflection_fragment>\n#include <fog_pars_fragment>\n#include <bsdfs>\n#include <lights_pars_begin>\n#include <lights_phong_pars_fragment>\n#include <shadowmap_pars_fragment>\n#include <bumpmap_pars_fragment>\n#include <normalmap_pars_fragment>\n#include <specularmap_pars_fragment>\n#include <logdepthbuf_pars_fragment>\n#include <clipping_planes_pars_fragment>\nvoid main() {\n\t#include <clipping_planes_fragment>\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include <logdepthbuf_fragment>\n\t#include <map_fragment>\n\t#include <color_fragment>\n\t#include <alphamap_fragment>\n\t#include <alphatest_fragment>\n\t#include <specularmap_fragment>\n\t#include <normal_fragment_begin>\n\t#include <normal_fragment_maps>\n\t#include <emissivemap_fragment>\n\t#include <lights_phong_fragment>\n\t#include <lights_fragment_begin>\n\t#include <lights_fragment_maps>\n\t#include <lights_fragment_end>\n\t#include <aomap_fragment>\n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;\n\t#include <envmap_fragment>\n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include <tonemapping_fragment>\n\t#include <encodings_fragment>\n\t#include <fog_fragment>\n\t#include <premultiplied_alpha_fragment>\n\t#include <dithering_fragment>\n}";
+
+var meshphong_vert = "#define PHONG\nvarying vec3 vViewPosition;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include <common>\n#include <uv_pars_vertex>\n#include <uv2_pars_vertex>\n#include <displacementmap_pars_vertex>\n#include <envmap_pars_vertex>\n#include <color_pars_vertex>\n#include <fog_pars_vertex>\n#include <morphtarget_pars_vertex>\n#include <skinning_pars_vertex>\n#include <shadowmap_pars_vertex>\n#include <logdepthbuf_pars_vertex>\n#include <clipping_planes_pars_vertex>\nvoid main() {\n\t#include <uv_vertex>\n\t#include <uv2_vertex>\n\t#include <color_vertex>\n\t#include <beginnormal_vertex>\n\t#include <morphnormal_vertex>\n\t#include <skinbase_vertex>\n\t#include <skinnormal_vertex>\n\t#include <defaultnormal_vertex>\n#ifndef FLAT_SHADED\n\tvNormal = normalize( transformedNormal );\n#endif\n\t#include <begin_vertex>\n\t#include <morphtarget_vertex>\n\t#include <skinning_vertex>\n\t#include <displacementmap_vertex>\n\t#include <project_vertex>\n\t#include <logdepthbuf_vertex>\n\t#include <clipping_planes_vertex>\n\tvViewPosition = - mvPosition.xyz;\n\t#include <worldpos_vertex>\n\t#include <envmap_vertex>\n\t#include <shadowmap_vertex>\n\t#include <fog_vertex>\n}";
+
+var meshphysical_frag = "#define STANDARD\n#ifdef PHYSICAL\n\t#define REFLECTIVITY\n\t#define CLEARCOAT\n\t#define TRANSMISSION\n#endif\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float roughness;\nuniform float metalness;\nuniform float opacity;\n#ifdef TRANSMISSION\n\tuniform float transmission;\n#endif\n#ifdef REFLECTIVITY\n\tuniform float reflectivity;\n#endif\n#ifdef CLEARCOAT\n\tuniform float clearcoat;\n\tuniform float clearcoatRoughness;\n#endif\n#ifdef USE_SHEEN\n\tuniform vec3 sheen;\n#endif\nvarying vec3 vViewPosition;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif\n#include <common>\n#include <packing>\n#include <dithering_pars_fragment>\n#include <color_pars_fragment>\n#include <uv_pars_fragment>\n#include <uv2_pars_fragment>\n#include <map_pars_fragment>\n#include <alphamap_pars_fragment>\n#include <aomap_pars_fragment>\n#include <lightmap_pars_fragment>\n#include <emissivemap_pars_fragment>\n#include <transmissionmap_pars_fragment>\n#include <bsdfs>\n#include <cube_uv_reflection_fragment>\n#include <envmap_common_pars_fragment>\n#include <envmap_physical_pars_fragment>\n#include <fog_pars_fragment>\n#include <lights_pars_begin>\n#include <lights_physical_pars_fragment>\n#include <shadowmap_pars_fragment>\n#include <bumpmap_pars_fragment>\n#include <normalmap_pars_fragment>\n#include <clearcoat_pars_fragment>\n#include <roughnessmap_pars_fragment>\n#include <metalnessmap_pars_fragment>\n#include <logdepthbuf_pars_fragment>\n#include <clipping_planes_pars_fragment>\nvoid main() {\n\t#include <clipping_planes_fragment>\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#ifdef TRANSMISSION\n\t\tfloat totalTransmission = transmission;\n\t#endif\n\t#include <logdepthbuf_fragment>\n\t#include <map_fragment>\n\t#include <color_fragment>\n\t#include <alphamap_fragment>\n\t#include <alphatest_fragment>\n\t#include <roughnessmap_fragment>\n\t#include <metalnessmap_fragment>\n\t#include <normal_fragment_begin>\n\t#include <normal_fragment_maps>\n\t#include <clearcoat_normal_fragment_begin>\n\t#include <clearcoat_normal_fragment_maps>\n\t#include <emissivemap_fragment>\n\t#include <transmissionmap_fragment>\n\t#include <lights_physical_fragment>\n\t#include <lights_fragment_begin>\n\t#include <lights_fragment_maps>\n\t#include <lights_fragment_end>\n\t#include <aomap_fragment>\n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;\n\t#ifdef TRANSMISSION\n\t\tdiffuseColor.a *= mix( saturate( 1. - totalTransmission + linearToRelativeLuminance( reflectedLight.directSpecular + reflectedLight.indirectSpecular ) ), 1.0, metalness );\n\t#endif\n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include <tonemapping_fragment>\n\t#include <encodings_fragment>\n\t#include <fog_fragment>\n\t#include <premultiplied_alpha_fragment>\n\t#include <dithering_fragment>\n}";
+
+var meshphysical_vert = "#define STANDARD\nvarying vec3 vViewPosition;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif\n#include <common>\n#include <uv_pars_vertex>\n#include <uv2_pars_vertex>\n#include <displacementmap_pars_vertex>\n#include <color_pars_vertex>\n#include <fog_pars_vertex>\n#include <morphtarget_pars_vertex>\n#include <skinning_pars_vertex>\n#include <shadowmap_pars_vertex>\n#include <logdepthbuf_pars_vertex>\n#include <clipping_planes_pars_vertex>\nvoid main() {\n\t#include <uv_vertex>\n\t#include <uv2_vertex>\n\t#include <color_vertex>\n\t#include <beginnormal_vertex>\n\t#include <morphnormal_vertex>\n\t#include <skinbase_vertex>\n\t#include <skinnormal_vertex>\n\t#include <defaultnormal_vertex>\n#ifndef FLAT_SHADED\n\tvNormal = normalize( transformedNormal );\n\t#ifdef USE_TANGENT\n\t\tvTangent = normalize( transformedTangent );\n\t\tvBitangent = normalize( cross( vNormal, vTangent ) * tangent.w );\n\t#endif\n#endif\n\t#include <begin_vertex>\n\t#include <morphtarget_vertex>\n\t#include <skinning_vertex>\n\t#include <displacementmap_vertex>\n\t#include <project_vertex>\n\t#include <logdepthbuf_vertex>\n\t#include <clipping_planes_vertex>\n\tvViewPosition = - mvPosition.xyz;\n\t#include <worldpos_vertex>\n\t#include <shadowmap_vertex>\n\t#include <fog_vertex>\n}";
+
+var normal_frag = "#define NORMAL\nuniform float opacity;\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( TANGENTSPACE_NORMALMAP )\n\tvarying vec3 vViewPosition;\n#endif\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif\n#include <packing>\n#include <uv_pars_fragment>\n#include <bumpmap_pars_fragment>\n#include <normalmap_pars_fragment>\n#include <logdepthbuf_pars_fragment>\n#include <clipping_planes_pars_fragment>\nvoid main() {\n\t#include <clipping_planes_fragment>\n\t#include <logdepthbuf_fragment>\n\t#include <normal_fragment_begin>\n\t#include <normal_fragment_maps>\n\tgl_FragColor = vec4( packNormalToRGB( normal ), opacity );\n}";
+
+var normal_vert = "#define NORMAL\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( TANGENTSPACE_NORMALMAP )\n\tvarying vec3 vViewPosition;\n#endif\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif\n#include <common>\n#include <uv_pars_vertex>\n#include <displacementmap_pars_vertex>\n#include <morphtarget_pars_vertex>\n#include <skinning_pars_vertex>\n#include <logdepthbuf_pars_vertex>\n#include <clipping_planes_pars_vertex>\nvoid main() {\n\t#include <uv_vertex>\n\t#include <beginnormal_vertex>\n\t#include <morphnormal_vertex>\n\t#include <skinbase_vertex>\n\t#include <skinnormal_vertex>\n\t#include <defaultnormal_vertex>\n#ifndef FLAT_SHADED\n\tvNormal = normalize( transformedNormal );\n\t#ifdef USE_TANGENT\n\t\tvTangent = normalize( transformedTangent );\n\t\tvBitangent = normalize( cross( vNormal, vTangent ) * tangent.w );\n\t#endif\n#endif\n\t#include <begin_vertex>\n\t#include <morphtarget_vertex>\n\t#include <skinning_vertex>\n\t#include <displacementmap_vertex>\n\t#include <project_vertex>\n\t#include <logdepthbuf_vertex>\n\t#include <clipping_planes_vertex>\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( TANGENTSPACE_NORMALMAP )\n\tvViewPosition = - mvPosition.xyz;\n#endif\n}";
+
+var points_frag = "uniform vec3 diffuse;\nuniform float opacity;\n#include <common>\n#include <color_pars_fragment>\n#include <map_particle_pars_fragment>\n#include <fog_pars_fragment>\n#include <logdepthbuf_pars_fragment>\n#include <clipping_planes_pars_fragment>\nvoid main() {\n\t#include <clipping_planes_fragment>\n\tvec3 outgoingLight = vec3( 0.0 );\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include <logdepthbuf_fragment>\n\t#include <map_particle_fragment>\n\t#include <color_fragment>\n\t#include <alphatest_fragment>\n\toutgoingLight = diffuseColor.rgb;\n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include <tonemapping_fragment>\n\t#include <encodings_fragment>\n\t#include <fog_fragment>\n\t#include <premultiplied_alpha_fragment>\n}";
+
+var points_vert = "uniform float size;\nuniform float scale;\n#include <common>\n#include <color_pars_vertex>\n#include <fog_pars_vertex>\n#include <morphtarget_pars_vertex>\n#include <logdepthbuf_pars_vertex>\n#include <clipping_planes_pars_vertex>\nvoid main() {\n\t#include <color_vertex>\n\t#include <begin_vertex>\n\t#include <morphtarget_vertex>\n\t#include <project_vertex>\n\tgl_PointSize = size;\n\t#ifdef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) gl_PointSize *= ( scale / - mvPosition.z );\n\t#endif\n\t#include <logdepthbuf_vertex>\n\t#include <clipping_planes_vertex>\n\t#include <worldpos_vertex>\n\t#include <fog_vertex>\n}";
+
+var shadow_frag = "uniform vec3 color;\nuniform float opacity;\n#include <common>\n#include <packing>\n#include <fog_pars_fragment>\n#include <bsdfs>\n#include <lights_pars_begin>\n#include <shadowmap_pars_fragment>\n#include <shadowmask_pars_fragment>\nvoid main() {\n\tgl_FragColor = vec4( color, opacity * ( 1.0 - getShadowMask() ) );\n\t#include <tonemapping_fragment>\n\t#include <encodings_fragment>\n\t#include <fog_fragment>\n}";
+
+var shadow_vert = "#include <common>\n#include <fog_pars_vertex>\n#include <shadowmap_pars_vertex>\nvoid main() {\n\t#include <begin_vertex>\n\t#include <project_vertex>\n\t#include <worldpos_vertex>\n\t#include <beginnormal_vertex>\n\t#include <morphnormal_vertex>\n\t#include <skinbase_vertex>\n\t#include <skinnormal_vertex>\n\t#include <defaultnormal_vertex>\n\t#include <shadowmap_vertex>\n\t#include <fog_vertex>\n}";
+
+var sprite_frag = "uniform vec3 diffuse;\nuniform float opacity;\n#include <common>\n#include <uv_pars_fragment>\n#include <map_pars_fragment>\n#include <alphamap_pars_fragment>\n#include <fog_pars_fragment>\n#include <logdepthbuf_pars_fragment>\n#include <clipping_planes_pars_fragment>\nvoid main() {\n\t#include <clipping_planes_fragment>\n\tvec3 outgoingLight = vec3( 0.0 );\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include <logdepthbuf_fragment>\n\t#include <map_fragment>\n\t#include <alphamap_fragment>\n\t#include <alphatest_fragment>\n\toutgoingLight = diffuseColor.rgb;\n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include <tonemapping_fragment>\n\t#include <encodings_fragment>\n\t#include <fog_fragment>\n}";
+
+var sprite_vert = "uniform float rotation;\nuniform vec2 center;\n#include <common>\n#include <uv_pars_vertex>\n#include <fog_pars_vertex>\n#include <logdepthbuf_pars_vertex>\n#include <clipping_planes_pars_vertex>\nvoid main() {\n\t#include <uv_vertex>\n\tvec4 mvPosition = modelViewMatrix * vec4( 0.0, 0.0, 0.0, 1.0 );\n\tvec2 scale;\n\tscale.x = length( vec3( modelMatrix[ 0 ].x, modelMatrix[ 0 ].y, modelMatrix[ 0 ].z ) );\n\tscale.y = length( vec3( modelMatrix[ 1 ].x, modelMatrix[ 1 ].y, modelMatrix[ 1 ].z ) );\n\t#ifndef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) scale *= - mvPosition.z;\n\t#endif\n\tvec2 alignedPosition = ( position.xy - ( center - vec2( 0.5 ) ) ) * scale;\n\tvec2 rotatedPosition;\n\trotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y;\n\trotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y;\n\tmvPosition.xy += rotatedPosition;\n\tgl_Position = projectionMatrix * mvPosition;\n\t#include <logdepthbuf_vertex>\n\t#include <clipping_planes_vertex>\n\t#include <fog_vertex>\n}";
+
+const ShaderChunk = {
+       alphamap_fragment: alphamap_fragment,
+       alphamap_pars_fragment: alphamap_pars_fragment,
+       alphatest_fragment: alphatest_fragment,
+       aomap_fragment: aomap_fragment,
+       aomap_pars_fragment: aomap_pars_fragment,
+       begin_vertex: begin_vertex,
+       beginnormal_vertex: beginnormal_vertex,
+       bsdfs: bsdfs,
+       bumpmap_pars_fragment: bumpmap_pars_fragment,
+       clipping_planes_fragment: clipping_planes_fragment,
+       clipping_planes_pars_fragment: clipping_planes_pars_fragment,
+       clipping_planes_pars_vertex: clipping_planes_pars_vertex,
+       clipping_planes_vertex: clipping_planes_vertex,
+       color_fragment: color_fragment,
+       color_pars_fragment: color_pars_fragment,
+       color_pars_vertex: color_pars_vertex,
+       color_vertex: color_vertex,
+       common: common$1,
+       cube_uv_reflection_fragment: cube_uv_reflection_fragment,
+       defaultnormal_vertex: defaultnormal_vertex,
+       displacementmap_pars_vertex: displacementmap_pars_vertex,
+       displacementmap_vertex: displacementmap_vertex,
+       emissivemap_fragment: emissivemap_fragment,
+       emissivemap_pars_fragment: emissivemap_pars_fragment,
+       encodings_fragment: encodings_fragment,
+       encodings_pars_fragment: encodings_pars_fragment,
+       envmap_fragment: envmap_fragment,
+       envmap_common_pars_fragment: envmap_common_pars_fragment,
+       envmap_pars_fragment: envmap_pars_fragment,
+       envmap_pars_vertex: envmap_pars_vertex,
+       envmap_physical_pars_fragment: envmap_physical_pars_fragment,
+       envmap_vertex: envmap_vertex,
+       fog_vertex: fog_vertex,
+       fog_pars_vertex: fog_pars_vertex,
+       fog_fragment: fog_fragment,
+       fog_pars_fragment: fog_pars_fragment,
+       gradientmap_pars_fragment: gradientmap_pars_fragment,
+       lightmap_fragment: lightmap_fragment,
+       lightmap_pars_fragment: lightmap_pars_fragment,
+       lights_lambert_vertex: lights_lambert_vertex,
+       lights_pars_begin: lights_pars_begin,
+       lights_toon_fragment: lights_toon_fragment,
+       lights_toon_pars_fragment: lights_toon_pars_fragment,
+       lights_phong_fragment: lights_phong_fragment,
+       lights_phong_pars_fragment: lights_phong_pars_fragment,
+       lights_physical_fragment: lights_physical_fragment,
+       lights_physical_pars_fragment: lights_physical_pars_fragment,
+       lights_fragment_begin: lights_fragment_begin,
+       lights_fragment_maps: lights_fragment_maps,
+       lights_fragment_end: lights_fragment_end,
+       logdepthbuf_fragment: logdepthbuf_fragment,
+       logdepthbuf_pars_fragment: logdepthbuf_pars_fragment,
+       logdepthbuf_pars_vertex: logdepthbuf_pars_vertex,
+       logdepthbuf_vertex: logdepthbuf_vertex,
+       map_fragment: map_fragment,
+       map_pars_fragment: map_pars_fragment,
+       map_particle_fragment: map_particle_fragment,
+       map_particle_pars_fragment: map_particle_pars_fragment,
+       metalnessmap_fragment: metalnessmap_fragment,
+       metalnessmap_pars_fragment: metalnessmap_pars_fragment,
+       morphnormal_vertex: morphnormal_vertex,
+       morphtarget_pars_vertex: morphtarget_pars_vertex,
+       morphtarget_vertex: morphtarget_vertex,
+       normal_fragment_begin: normal_fragment_begin,
+       normal_fragment_maps: normal_fragment_maps,
+       normalmap_pars_fragment: normalmap_pars_fragment,
+       clearcoat_normal_fragment_begin: clearcoat_normal_fragment_begin,
+       clearcoat_normal_fragment_maps: clearcoat_normal_fragment_maps,
+       clearcoat_pars_fragment: clearcoat_pars_fragment,
+       packing: packing,
+       premultiplied_alpha_fragment: premultiplied_alpha_fragment,
+       project_vertex: project_vertex,
+       dithering_fragment: dithering_fragment,
+       dithering_pars_fragment: dithering_pars_fragment,
+       roughnessmap_fragment: roughnessmap_fragment,
+       roughnessmap_pars_fragment: roughnessmap_pars_fragment,
+       shadowmap_pars_fragment: shadowmap_pars_fragment,
+       shadowmap_pars_vertex: shadowmap_pars_vertex,
+       shadowmap_vertex: shadowmap_vertex,
+       shadowmask_pars_fragment: shadowmask_pars_fragment,
+       skinbase_vertex: skinbase_vertex,
+       skinning_pars_vertex: skinning_pars_vertex,
+       skinning_vertex: skinning_vertex,
+       skinnormal_vertex: skinnormal_vertex,
+       specularmap_fragment: specularmap_fragment,
+       specularmap_pars_fragment: specularmap_pars_fragment,
+       tonemapping_fragment: tonemapping_fragment,
+       tonemapping_pars_fragment: tonemapping_pars_fragment,
+       transmissionmap_fragment: transmissionmap_fragment,
+       transmissionmap_pars_fragment: transmissionmap_pars_fragment,
+       uv_pars_fragment: uv_pars_fragment,
+       uv_pars_vertex: uv_pars_vertex,
+       uv_vertex: uv_vertex,
+       uv2_pars_fragment: uv2_pars_fragment,
+       uv2_pars_vertex: uv2_pars_vertex,
+       uv2_vertex: uv2_vertex,
+       worldpos_vertex: worldpos_vertex,
+
+       background_frag: background_frag,
+       background_vert: background_vert,
+       cube_frag: cube_frag,
+       cube_vert: cube_vert,
+       depth_frag: depth_frag,
+       depth_vert: depth_vert,
+       distanceRGBA_frag: distanceRGBA_frag,
+       distanceRGBA_vert: distanceRGBA_vert,
+       equirect_frag: equirect_frag,
+       equirect_vert: equirect_vert,
+       linedashed_frag: linedashed_frag,
+       linedashed_vert: linedashed_vert,
+       meshbasic_frag: meshbasic_frag,
+       meshbasic_vert: meshbasic_vert,
+       meshlambert_frag: meshlambert_frag,
+       meshlambert_vert: meshlambert_vert,
+       meshmatcap_frag: meshmatcap_frag,
+       meshmatcap_vert: meshmatcap_vert,
+       meshtoon_frag: meshtoon_frag,
+       meshtoon_vert: meshtoon_vert,
+       meshphong_frag: meshphong_frag,
+       meshphong_vert: meshphong_vert,
+       meshphysical_frag: meshphysical_frag,
+       meshphysical_vert: meshphysical_vert,
+       normal_frag: normal_frag,
+       normal_vert: normal_vert,
+       points_frag: points_frag,
+       points_vert: points_vert,
+       shadow_frag: shadow_frag,
+       shadow_vert: shadow_vert,
+       sprite_frag: sprite_frag,
+       sprite_vert: sprite_vert
+};
+
+/**
+ * Uniforms library for shared webgl shaders
+ */
+
+const UniformsLib = {
+
+       common: {
+
+               diffuse: { value: new Color( 0xeeeeee ) },
+               opacity: { value: 1.0 },
+
+               map: { value: null },
+               uvTransform: { value: new Matrix3() },
+               uv2Transform: { value: new Matrix3() },
+
+               alphaMap: { value: null },
+
+       },
+
+       specularmap: {
+
+               specularMap: { value: null },
+
+       },
+
+       envmap: {
+
+               envMap: { value: null },
+               flipEnvMap: { value: - 1 },
+               reflectivity: { value: 1.0 },
+               refractionRatio: { value: 0.98 },
+               maxMipLevel: { value: 0 }
+
+       },
+
+       aomap: {
+
+               aoMap: { value: null },
+               aoMapIntensity: { value: 1 }
+
+       },
+
+       lightmap: {
+
+               lightMap: { value: null },
+               lightMapIntensity: { value: 1 }
+
+       },
+
+       emissivemap: {
+
+               emissiveMap: { value: null }
+
+       },
+
+       bumpmap: {
+
+               bumpMap: { value: null },
+               bumpScale: { value: 1 }
+
+       },
+
+       normalmap: {
+
+               normalMap: { value: null },
+               normalScale: { value: new Vector2( 1, 1 ) }
+
+       },
+
+       displacementmap: {
+
+               displacementMap: { value: null },
+               displacementScale: { value: 1 },
+               displacementBias: { value: 0 }
+
+       },
+
+       roughnessmap: {
+
+               roughnessMap: { value: null }
+
+       },
+
+       metalnessmap: {
+
+               metalnessMap: { value: null }
+
+       },
+
+       gradientmap: {
+
+               gradientMap: { value: null }
+
+       },
+
+       fog: {
+
+               fogDensity: { value: 0.00025 },
+               fogNear: { value: 1 },
+               fogFar: { value: 2000 },
+               fogColor: { value: new Color( 0xffffff ) }
+
+       },
+
+       lights: {
+
+               ambientLightColor: { value: [] },
+
+               lightProbe: { value: [] },
+
+               directionalLights: { value: [], properties: {
+                       direction: {},
+                       color: {}
+               } },
+
+               directionalLightShadows: { value: [], properties: {
+                       shadowBias: {},
+                       shadowNormalBias: {},
+                       shadowRadius: {},
+                       shadowMapSize: {}
+               } },
+
+               directionalShadowMap: { value: [] },
+               directionalShadowMatrix: { value: [] },
+
+               spotLights: { value: [], properties: {
+                       color: {},
+                       position: {},
+                       direction: {},
+                       distance: {},
+                       coneCos: {},
+                       penumbraCos: {},
+                       decay: {}
+               } },
+
+               spotLightShadows: { value: [], properties: {
+                       shadowBias: {},
+                       shadowNormalBias: {},
+                       shadowRadius: {},
+                       shadowMapSize: {}
+               } },
+
+               spotShadowMap: { value: [] },
+               spotShadowMatrix: { value: [] },
+
+               pointLights: { value: [], properties: {
+                       color: {},
+                       position: {},
+                       decay: {},
+                       distance: {}
+               } },
+
+               pointLightShadows: { value: [], properties: {
+                       shadowBias: {},
+                       shadowNormalBias: {},
+                       shadowRadius: {},
+                       shadowMapSize: {},
+                       shadowCameraNear: {},
+                       shadowCameraFar: {}
+               } },
+
+               pointShadowMap: { value: [] },
+               pointShadowMatrix: { value: [] },
+
+               hemisphereLights: { value: [], properties: {
+                       direction: {},
+                       skyColor: {},
+                       groundColor: {}
+               } },
+
+               // TODO (abelnation): RectAreaLight BRDF data needs to be moved from example to main src
+               rectAreaLights: { value: [], properties: {
+                       color: {},
+                       position: {},
+                       width: {},
+                       height: {}
+               } },
+
+               ltc_1: { value: null },
+               ltc_2: { value: null }
+
+       },
+
+       points: {
+
+               diffuse: { value: new Color( 0xeeeeee ) },
+               opacity: { value: 1.0 },
+               size: { value: 1.0 },
+               scale: { value: 1.0 },
+               map: { value: null },
+               alphaMap: { value: null },
+               uvTransform: { value: new Matrix3() }
+
+       },
+
+       sprite: {
+
+               diffuse: { value: new Color( 0xeeeeee ) },
+               opacity: { value: 1.0 },
+               center: { value: new Vector2( 0.5, 0.5 ) },
+               rotation: { value: 0.0 },
+               map: { value: null },
+               alphaMap: { value: null },
+               uvTransform: { value: new Matrix3() }
+
+       }
+
+};
+
+const ShaderLib = {
+
+       basic: {
+
+               uniforms: mergeUniforms( [
+                       UniformsLib.common,
+                       UniformsLib.specularmap,
+                       UniformsLib.envmap,
+                       UniformsLib.aomap,
+                       UniformsLib.lightmap,
+                       UniformsLib.fog
+               ] ),
+
+               vertexShader: ShaderChunk.meshbasic_vert,
+               fragmentShader: ShaderChunk.meshbasic_frag
+
+       },
+
+       lambert: {
+
+               uniforms: mergeUniforms( [
+                       UniformsLib.common,
+                       UniformsLib.specularmap,
+                       UniformsLib.envmap,
+                       UniformsLib.aomap,
+                       UniformsLib.lightmap,
+                       UniformsLib.emissivemap,
+                       UniformsLib.fog,
+                       UniformsLib.lights,
+                       {
+                               emissive: { value: new Color( 0x000000 ) }
+                       }
+               ] ),
+
+               vertexShader: ShaderChunk.meshlambert_vert,
+               fragmentShader: ShaderChunk.meshlambert_frag
+
+       },
+
+       phong: {
+
+               uniforms: mergeUniforms( [
+                       UniformsLib.common,
+                       UniformsLib.specularmap,
+                       UniformsLib.envmap,
+                       UniformsLib.aomap,
+                       UniformsLib.lightmap,
+                       UniformsLib.emissivemap,
+                       UniformsLib.bumpmap,
+                       UniformsLib.normalmap,
+                       UniformsLib.displacementmap,
+                       UniformsLib.fog,
+                       UniformsLib.lights,
+                       {
+                               emissive: { value: new Color( 0x000000 ) },
+                               specular: { value: new Color( 0x111111 ) },
+                               shininess: { value: 30 }
+                       }
+               ] ),
+
+               vertexShader: ShaderChunk.meshphong_vert,
+               fragmentShader: ShaderChunk.meshphong_frag
+
+       },
+
+       standard: {
+
+               uniforms: mergeUniforms( [
+                       UniformsLib.common,
+                       UniformsLib.envmap,
+                       UniformsLib.aomap,
+                       UniformsLib.lightmap,
+                       UniformsLib.emissivemap,
+                       UniformsLib.bumpmap,
+                       UniformsLib.normalmap,
+                       UniformsLib.displacementmap,
+                       UniformsLib.roughnessmap,
+                       UniformsLib.metalnessmap,
+                       UniformsLib.fog,
+                       UniformsLib.lights,
+                       {
+                               emissive: { value: new Color( 0x000000 ) },
+                               roughness: { value: 1.0 },
+                               metalness: { value: 0.0 },
+                               envMapIntensity: { value: 1 } // temporary
+                       }
+               ] ),
+
+               vertexShader: ShaderChunk.meshphysical_vert,
+               fragmentShader: ShaderChunk.meshphysical_frag
+
+       },
+
+       toon: {
+
+               uniforms: mergeUniforms( [
+                       UniformsLib.common,
+                       UniformsLib.aomap,
+                       UniformsLib.lightmap,
+                       UniformsLib.emissivemap,
+                       UniformsLib.bumpmap,
+                       UniformsLib.normalmap,
+                       UniformsLib.displacementmap,
+                       UniformsLib.gradientmap,
+                       UniformsLib.fog,
+                       UniformsLib.lights,
+                       {
+                               emissive: { value: new Color( 0x000000 ) }
+                       }
+               ] ),
+
+               vertexShader: ShaderChunk.meshtoon_vert,
+               fragmentShader: ShaderChunk.meshtoon_frag
+
+       },
+
+       matcap: {
+
+               uniforms: mergeUniforms( [
+                       UniformsLib.common,
+                       UniformsLib.bumpmap,
+                       UniformsLib.normalmap,
+                       UniformsLib.displacementmap,
+                       UniformsLib.fog,
+                       {
+                               matcap: { value: null }
+                       }
+               ] ),
+
+               vertexShader: ShaderChunk.meshmatcap_vert,
+               fragmentShader: ShaderChunk.meshmatcap_frag
+
+       },
+
+       points: {
+
+               uniforms: mergeUniforms( [
+                       UniformsLib.points,
+                       UniformsLib.fog
+               ] ),
+
+               vertexShader: ShaderChunk.points_vert,
+               fragmentShader: ShaderChunk.points_frag
+
+       },
+
+       dashed: {
+
+               uniforms: mergeUniforms( [
+                       UniformsLib.common,
+                       UniformsLib.fog,
+                       {
+                               scale: { value: 1 },
+                               dashSize: { value: 1 },
+                               totalSize: { value: 2 }
+                       }
+               ] ),
+
+               vertexShader: ShaderChunk.linedashed_vert,
+               fragmentShader: ShaderChunk.linedashed_frag
+
+       },
+
+       depth: {
+
+               uniforms: mergeUniforms( [
+                       UniformsLib.common,
+                       UniformsLib.displacementmap
+               ] ),
+
+               vertexShader: ShaderChunk.depth_vert,
+               fragmentShader: ShaderChunk.depth_frag
+
+       },
+
+       normal: {
+
+               uniforms: mergeUniforms( [
+                       UniformsLib.common,
+                       UniformsLib.bumpmap,
+                       UniformsLib.normalmap,
+                       UniformsLib.displacementmap,
+                       {
+                               opacity: { value: 1.0 }
+                       }
+               ] ),
+
+               vertexShader: ShaderChunk.normal_vert,
+               fragmentShader: ShaderChunk.normal_frag
+
+       },
+
+       sprite: {
+
+               uniforms: mergeUniforms( [
+                       UniformsLib.sprite,
+                       UniformsLib.fog
+               ] ),
+
+               vertexShader: ShaderChunk.sprite_vert,
+               fragmentShader: ShaderChunk.sprite_frag
+
+       },
+
+       background: {
+
+               uniforms: {
+                       uvTransform: { value: new Matrix3() },
+                       t2D: { value: null },
+               },
+
+               vertexShader: ShaderChunk.background_vert,
+               fragmentShader: ShaderChunk.background_frag
+
+       },
+       /* -------------------------------------------------------------------------
+       //      Cube map shader
+        ------------------------------------------------------------------------- */
+
+       cube: {
+
+               uniforms: mergeUniforms( [
+                       UniformsLib.envmap,
+                       {
+                               opacity: { value: 1.0 }
+                       }
+               ] ),
+
+               vertexShader: ShaderChunk.cube_vert,
+               fragmentShader: ShaderChunk.cube_frag
+
+       },
+
+       equirect: {
+
+               uniforms: {
+                       tEquirect: { value: null },
+               },
+
+               vertexShader: ShaderChunk.equirect_vert,
+               fragmentShader: ShaderChunk.equirect_frag
+
+       },
+
+       distanceRGBA: {
+
+               uniforms: mergeUniforms( [
+                       UniformsLib.common,
+                       UniformsLib.displacementmap,
+                       {
+                               referencePosition: { value: new Vector3() },
+                               nearDistance: { value: 1 },
+                               farDistance: { value: 1000 }
+                       }
+               ] ),
+
+               vertexShader: ShaderChunk.distanceRGBA_vert,
+               fragmentShader: ShaderChunk.distanceRGBA_frag
+
+       },
+
+       shadow: {
+
+               uniforms: mergeUniforms( [
+                       UniformsLib.lights,
+                       UniformsLib.fog,
+                       {
+                               color: { value: new Color( 0x00000 ) },
+                               opacity: { value: 1.0 }
+                       },
+               ] ),
+
+               vertexShader: ShaderChunk.shadow_vert,
+               fragmentShader: ShaderChunk.shadow_frag
+
+       }
+
+};
+
+ShaderLib.physical = {
+
+       uniforms: mergeUniforms( [
+               ShaderLib.standard.uniforms,
+               {
+                       clearcoat: { value: 0 },
+                       clearcoatMap: { value: null },
+                       clearcoatRoughness: { value: 0 },
+                       clearcoatRoughnessMap: { value: null },
+                       clearcoatNormalScale: { value: new Vector2( 1, 1 ) },
+                       clearcoatNormalMap: { value: null },
+                       sheen: { value: new Color( 0x000000 ) },
+                       transmission: { value: 0 },
+                       transmissionMap: { value: null },
+               }
+       ] ),
+
+       vertexShader: ShaderChunk.meshphysical_vert,
+       fragmentShader: ShaderChunk.meshphysical_frag
+
+};
+
+function WebGLBackground( renderer, cubemaps, state, objects, premultipliedAlpha ) {
+
+       const clearColor = new Color( 0x000000 );
+       let clearAlpha = 0;
+
+       let planeMesh;
+       let boxMesh;
+
+       let currentBackground = null;
+       let currentBackgroundVersion = 0;
+       let currentTonemapping = null;
+
+       function render( renderList, scene, camera, forceClear ) {
+
+               let background = scene.isScene === true ? scene.background : null;
+
+               if ( background && background.isTexture ) {
+
+                       background = cubemaps.get( background );
+
+               }
+
+               // Ignore background in AR
+               // TODO: Reconsider this.
+
+               const xr = renderer.xr;
+               const session = xr.getSession && xr.getSession();
+
+               if ( session && session.environmentBlendMode === 'additive' ) {
+
+                       background = null;
+
+               }
+
+               if ( background === null ) {
+
+                       setClear( clearColor, clearAlpha );
+
+               } else if ( background && background.isColor ) {
+
+                       setClear( background, 1 );
+                       forceClear = true;
+
+               }
+
+               if ( renderer.autoClear || forceClear ) {
+
+                       renderer.clear( renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil );
+
+               }
+
+               if ( background && ( background.isCubeTexture || background.isWebGLCubeRenderTarget || background.mapping === CubeUVReflectionMapping ) ) {
+
+                       if ( boxMesh === undefined ) {
+
+                               boxMesh = new Mesh(
+                                       new BoxGeometry( 1, 1, 1 ),
+                                       new ShaderMaterial( {
+                                               name: 'BackgroundCubeMaterial',
+                                               uniforms: cloneUniforms( ShaderLib.cube.uniforms ),
+                                               vertexShader: ShaderLib.cube.vertexShader,
+                                               fragmentShader: ShaderLib.cube.fragmentShader,
+                                               side: BackSide,
+                                               depthTest: false,
+                                               depthWrite: false,
+                                               fog: false
+                                       } )
+                               );
+
+                               boxMesh.geometry.deleteAttribute( 'normal' );
+                               boxMesh.geometry.deleteAttribute( 'uv' );
+
+                               boxMesh.onBeforeRender = function ( renderer, scene, camera ) {
+
+                                       this.matrixWorld.copyPosition( camera.matrixWorld );
+
+                               };
+
+                               // enable code injection for non-built-in material
+                               Object.defineProperty( boxMesh.material, 'envMap', {
+
+                                       get: function () {
+
+                                               return this.uniforms.envMap.value;
+
+                                       }
+
+                               } );
+
+                               objects.update( boxMesh );
+
+                       }
+
+                       if ( background.isWebGLCubeRenderTarget ) {
+
+                               // TODO Deprecate
+
+                               background = background.texture;
+
+                       }
+
+                       boxMesh.material.uniforms.envMap.value = background;
+                       boxMesh.material.uniforms.flipEnvMap.value = ( background.isCubeTexture && background._needsFlipEnvMap ) ? - 1 : 1;
+
+                       if ( currentBackground !== background ||
+                               currentBackgroundVersion !== background.version ||
+                               currentTonemapping !== renderer.toneMapping ) {
+
+                               boxMesh.material.needsUpdate = true;
+
+                               currentBackground = background;
+                               currentBackgroundVersion = background.version;
+                               currentTonemapping = renderer.toneMapping;
+
+                       }
+
+                       // push to the pre-sorted opaque render list
+                       renderList.unshift( boxMesh, boxMesh.geometry, boxMesh.material, 0, 0, null );
+
+               } else if ( background && background.isTexture ) {
+
+                       if ( planeMesh === undefined ) {
+
+                               planeMesh = new Mesh(
+                                       new PlaneGeometry( 2, 2 ),
+                                       new ShaderMaterial( {
+                                               name: 'BackgroundMaterial',
+                                               uniforms: cloneUniforms( ShaderLib.background.uniforms ),
+                                               vertexShader: ShaderLib.background.vertexShader,
+                                               fragmentShader: ShaderLib.background.fragmentShader,
+                                               side: FrontSide,
+                                               depthTest: false,
+                                               depthWrite: false,
+                                               fog: false
+                                       } )
+                               );
+
+                               planeMesh.geometry.deleteAttribute( 'normal' );
+
+                               // enable code injection for non-built-in material
+                               Object.defineProperty( planeMesh.material, 'map', {
+
+                                       get: function () {
+
+                                               return this.uniforms.t2D.value;
+
+                                       }
+
+                               } );
+
+                               objects.update( planeMesh );
+
+                       }
+
+                       planeMesh.material.uniforms.t2D.value = background;
+
+                       if ( background.matrixAutoUpdate === true ) {
+
+                               background.updateMatrix();
+
+                       }
+
+                       planeMesh.material.uniforms.uvTransform.value.copy( background.matrix );
+
+                       if ( currentBackground !== background ||
+                               currentBackgroundVersion !== background.version ||
+                               currentTonemapping !== renderer.toneMapping ) {
+
+                               planeMesh.material.needsUpdate = true;
+
+                               currentBackground = background;
+                               currentBackgroundVersion = background.version;
+                               currentTonemapping = renderer.toneMapping;
+
+                       }
+
+
+                       // push to the pre-sorted opaque render list
+                       renderList.unshift( planeMesh, planeMesh.geometry, planeMesh.material, 0, 0, null );
+
+               }
+
+       }
+
+       function setClear( color, alpha ) {
+
+               state.buffers.color.setClear( color.r, color.g, color.b, alpha, premultipliedAlpha );
+
+       }
+
+       return {
+
+               getClearColor: function () {
+
+                       return clearColor;
+
+               },
+               setClearColor: function ( color, alpha = 1 ) {
+
+                       clearColor.set( color );
+                       clearAlpha = alpha;
+                       setClear( clearColor, clearAlpha );
+
+               },
+               getClearAlpha: function () {
+
+                       return clearAlpha;
+
+               },
+               setClearAlpha: function ( alpha ) {
+
+                       clearAlpha = alpha;
+                       setClear( clearColor, clearAlpha );
+
+               },
+               render: render
+
+       };
+
+}
+
+function WebGLBindingStates( gl, extensions, attributes, capabilities ) {
+
+       const maxVertexAttributes = gl.getParameter( 34921 );
+
+       const extension = capabilities.isWebGL2 ? null : extensions.get( 'OES_vertex_array_object' );
+       const vaoAvailable = capabilities.isWebGL2 || extension !== null;
+
+       const bindingStates = {};
+
+       const defaultState = createBindingState( null );
+       let currentState = defaultState;
+
+       function setup( object, material, program, geometry, index ) {
+
+               let updateBuffers = false;
+
+               if ( vaoAvailable ) {
+
+                       const state = getBindingState( geometry, program, material );
+
+                       if ( currentState !== state ) {
+
+                               currentState = state;
+                               bindVertexArrayObject( currentState.object );
+
+                       }
+
+                       updateBuffers = needsUpdate( geometry, index );
+
+                       if ( updateBuffers ) saveCache( geometry, index );
+
+               } else {
+
+                       const wireframe = ( material.wireframe === true );
+
+                       if ( currentState.geometry !== geometry.id ||
+                               currentState.program !== program.id ||
+                               currentState.wireframe !== wireframe ) {
+
+                               currentState.geometry = geometry.id;
+                               currentState.program = program.id;
+                               currentState.wireframe = wireframe;
+
+                               updateBuffers = true;
+
+                       }
+
+               }
+
+               if ( object.isInstancedMesh === true ) {
+
+                       updateBuffers = true;
+
+               }
+
+               if ( index !== null ) {
+
+                       attributes.update( index, 34963 );
+
+               }
+
+               if ( updateBuffers ) {
+
+                       setupVertexAttributes( object, material, program, geometry );
+
+                       if ( index !== null ) {
+
+                               gl.bindBuffer( 34963, attributes.get( index ).buffer );
+
+                       }
+
+               }
+
+       }
+
+       function createVertexArrayObject() {
+
+               if ( capabilities.isWebGL2 ) return gl.createVertexArray();
+
+               return extension.createVertexArrayOES();
+
+       }
+
+       function bindVertexArrayObject( vao ) {
+
+               if ( capabilities.isWebGL2 ) return gl.bindVertexArray( vao );
+
+               return extension.bindVertexArrayOES( vao );
+
+       }
+
+       function deleteVertexArrayObject( vao ) {
+
+               if ( capabilities.isWebGL2 ) return gl.deleteVertexArray( vao );
+
+               return extension.deleteVertexArrayOES( vao );
+
+       }
+
+       function getBindingState( geometry, program, material ) {
+
+               const wireframe = ( material.wireframe === true );
+
+               let programMap = bindingStates[ geometry.id ];
+
+               if ( programMap === undefined ) {
+
+                       programMap = {};
+                       bindingStates[ geometry.id ] = programMap;
+
+               }
+
+               let stateMap = programMap[ program.id ];
+
+               if ( stateMap === undefined ) {
+
+                       stateMap = {};
+                       programMap[ program.id ] = stateMap;
+
+               }
+
+               let state = stateMap[ wireframe ];
+
+               if ( state === undefined ) {
+
+                       state = createBindingState( createVertexArrayObject() );
+                       stateMap[ wireframe ] = state;
+
+               }
+
+               return state;
+
+       }
+
+       function createBindingState( vao ) {
+
+               const newAttributes = [];
+               const enabledAttributes = [];
+               const attributeDivisors = [];
+
+               for ( let i = 0; i < maxVertexAttributes; i ++ ) {
+
+                       newAttributes[ i ] = 0;
+                       enabledAttributes[ i ] = 0;
+                       attributeDivisors[ i ] = 0;
+
+               }
+
+               return {
+
+                       // for backward compatibility on non-VAO support browser
+                       geometry: null,
+                       program: null,
+                       wireframe: false,
+
+                       newAttributes: newAttributes,
+                       enabledAttributes: enabledAttributes,
+                       attributeDivisors: attributeDivisors,
+                       object: vao,
+                       attributes: {},
+                       index: null
+
+               };
+
+       }
+
+       function needsUpdate( geometry, index ) {
+
+               const cachedAttributes = currentState.attributes;
+               const geometryAttributes = geometry.attributes;
+
+               let attributesNum = 0;
+
+               for ( const key in geometryAttributes ) {
+
+                       const cachedAttribute = cachedAttributes[ key ];
+                       const geometryAttribute = geometryAttributes[ key ];
+
+                       if ( cachedAttribute === undefined ) return true;
+
+                       if ( cachedAttribute.attribute !== geometryAttribute ) return true;
+
+                       if ( cachedAttribute.data !== geometryAttribute.data ) return true;
+
+                       attributesNum ++;
+
+               }
+
+               if ( currentState.attributesNum !== attributesNum ) return true;
+
+               if ( currentState.index !== index ) return true;
+
+               return false;
+
+       }
+
+       function saveCache( geometry, index ) {
+
+               const cache = {};
+               const attributes = geometry.attributes;
+               let attributesNum = 0;
+
+               for ( const key in attributes ) {
+
+                       const attribute = attributes[ key ];
+
+                       const data = {};
+                       data.attribute = attribute;
+
+                       if ( attribute.data ) {
+
+                               data.data = attribute.data;
+
+                       }
+
+                       cache[ key ] = data;
+
+                       attributesNum ++;
+
+               }
+
+               currentState.attributes = cache;
+               currentState.attributesNum = attributesNum;
+
+               currentState.index = index;
+
+       }
+
+       function initAttributes() {
+
+               const newAttributes = currentState.newAttributes;
+
+               for ( let i = 0, il = newAttributes.length; i < il; i ++ ) {
+
+                       newAttributes[ i ] = 0;
+
+               }
+
+       }
+
+       function enableAttribute( attribute ) {
+
+               enableAttributeAndDivisor( attribute, 0 );
+
+       }
+
+       function enableAttributeAndDivisor( attribute, meshPerAttribute ) {
+
+               const newAttributes = currentState.newAttributes;
+               const enabledAttributes = currentState.enabledAttributes;
+               const attributeDivisors = currentState.attributeDivisors;
+
+               newAttributes[ attribute ] = 1;
+
+               if ( enabledAttributes[ attribute ] === 0 ) {
+
+                       gl.enableVertexAttribArray( attribute );
+                       enabledAttributes[ attribute ] = 1;
+
+               }
+
+               if ( attributeDivisors[ attribute ] !== meshPerAttribute ) {
+
+                       const extension = capabilities.isWebGL2 ? gl : extensions.get( 'ANGLE_instanced_arrays' );
+
+                       extension[ capabilities.isWebGL2 ? 'vertexAttribDivisor' : 'vertexAttribDivisorANGLE' ]( attribute, meshPerAttribute );
+                       attributeDivisors[ attribute ] = meshPerAttribute;
+
+               }
+
+       }
+
+       function disableUnusedAttributes() {
+
+               const newAttributes = currentState.newAttributes;
+               const enabledAttributes = currentState.enabledAttributes;
+
+               for ( let i = 0, il = enabledAttributes.length; i < il; i ++ ) {
+
+                       if ( enabledAttributes[ i ] !== newAttributes[ i ] ) {
+
+                               gl.disableVertexAttribArray( i );
+                               enabledAttributes[ i ] = 0;
+
+                       }
+
+               }
+
+       }
+
+       function vertexAttribPointer( index, size, type, normalized, stride, offset ) {
+
+               if ( capabilities.isWebGL2 === true && ( type === 5124 || type === 5125 ) ) {
+
+                       gl.vertexAttribIPointer( index, size, type, stride, offset );
+
+               } else {
+
+                       gl.vertexAttribPointer( index, size, type, normalized, stride, offset );
+
+               }
+
+       }
+
+       function setupVertexAttributes( object, material, program, geometry ) {
+
+               if ( capabilities.isWebGL2 === false && ( object.isInstancedMesh || geometry.isInstancedBufferGeometry ) ) {
+
+                       if ( extensions.get( 'ANGLE_instanced_arrays' ) === null ) return;
+
+               }
+
+               initAttributes();
+
+               const geometryAttributes = geometry.attributes;
+
+               const programAttributes = program.getAttributes();
+
+               const materialDefaultAttributeValues = material.defaultAttributeValues;
+
+               for ( const name in programAttributes ) {
+
+                       const programAttribute = programAttributes[ name ];
+
+                       if ( programAttribute >= 0 ) {
+
+                               const geometryAttribute = geometryAttributes[ name ];
+
+                               if ( geometryAttribute !== undefined ) {
+
+                                       const normalized = geometryAttribute.normalized;
+                                       const size = geometryAttribute.itemSize;
+
+                                       const attribute = attributes.get( geometryAttribute );
+
+                                       // TODO Attribute may not be available on context restore
+
+                                       if ( attribute === undefined ) continue;
+
+                                       const buffer = attribute.buffer;
+                                       const type = attribute.type;
+                                       const bytesPerElement = attribute.bytesPerElement;
+
+                                       if ( geometryAttribute.isInterleavedBufferAttribute ) {
+
+                                               const data = geometryAttribute.data;
+                                               const stride = data.stride;
+                                               const offset = geometryAttribute.offset;
+
+                                               if ( data && data.isInstancedInterleavedBuffer ) {
+
+                                                       enableAttributeAndDivisor( programAttribute, data.meshPerAttribute );
+
+                                                       if ( geometry._maxInstanceCount === undefined ) {
+
+                                                               geometry._maxInstanceCount = data.meshPerAttribute * data.count;
+
+                                                       }
+
+                                               } else {
+
+                                                       enableAttribute( programAttribute );
+
+                                               }
+
+                                               gl.bindBuffer( 34962, buffer );
+                                               vertexAttribPointer( programAttribute, size, type, normalized, stride * bytesPerElement, offset * bytesPerElement );
+
+                                       } else {
+
+                                               if ( geometryAttribute.isInstancedBufferAttribute ) {
+
+                                                       enableAttributeAndDivisor( programAttribute, geometryAttribute.meshPerAttribute );
+
+                                                       if ( geometry._maxInstanceCount === undefined ) {
+
+                                                               geometry._maxInstanceCount = geometryAttribute.meshPerAttribute * geometryAttribute.count;
+
+                                                       }
+
+                                               } else {
+
+                                                       enableAttribute( programAttribute );
+
+                                               }
+
+                                               gl.bindBuffer( 34962, buffer );
+                                               vertexAttribPointer( programAttribute, size, type, normalized, 0, 0 );
+
+                                       }
+
+                               } else if ( name === 'instanceMatrix' ) {
+
+                                       const attribute = attributes.get( object.instanceMatrix );
+
+                                       // TODO Attribute may not be available on context restore
+
+                                       if ( attribute === undefined ) continue;
+
+                                       const buffer = attribute.buffer;
+                                       const type = attribute.type;
+
+                                       enableAttributeAndDivisor( programAttribute + 0, 1 );
+                                       enableAttributeAndDivisor( programAttribute + 1, 1 );
+                                       enableAttributeAndDivisor( programAttribute + 2, 1 );
+                                       enableAttributeAndDivisor( programAttribute + 3, 1 );
+
+                                       gl.bindBuffer( 34962, buffer );
+
+                                       gl.vertexAttribPointer( programAttribute + 0, 4, type, false, 64, 0 );
+                                       gl.vertexAttribPointer( programAttribute + 1, 4, type, false, 64, 16 );
+                                       gl.vertexAttribPointer( programAttribute + 2, 4, type, false, 64, 32 );
+                                       gl.vertexAttribPointer( programAttribute + 3, 4, type, false, 64, 48 );
+
+                               } else if ( name === 'instanceColor' ) {
+
+                                       const attribute = attributes.get( object.instanceColor );
+
+                                       // TODO Attribute may not be available on context restore
+
+                                       if ( attribute === undefined ) continue;
+
+                                       const buffer = attribute.buffer;
+                                       const type = attribute.type;
+
+                                       enableAttributeAndDivisor( programAttribute, 1 );
+
+                                       gl.bindBuffer( 34962, buffer );
+
+                                       gl.vertexAttribPointer( programAttribute, 3, type, false, 12, 0 );
+
+                               } else if ( materialDefaultAttributeValues !== undefined ) {
+
+                                       const value = materialDefaultAttributeValues[ name ];
+
+                                       if ( value !== undefined ) {
+
+                                               switch ( value.length ) {
+
+                                                       case 2:
+                                                               gl.vertexAttrib2fv( programAttribute, value );
+                                                               break;
+
+                                                       case 3:
+                                                               gl.vertexAttrib3fv( programAttribute, value );
+                                                               break;
+
+                                                       case 4:
+                                                               gl.vertexAttrib4fv( programAttribute, value );
+                                                               break;
+
+                                                       default:
+                                                               gl.vertexAttrib1fv( programAttribute, value );
+
+                                               }
+
+                                       }
+
+                               }
+
+                       }
+
+               }
+
+               disableUnusedAttributes();
+
+       }
+
+       function dispose() {
+
+               reset();
+
+               for ( const geometryId in bindingStates ) {
+
+                       const programMap = bindingStates[ geometryId ];
+
+                       for ( const programId in programMap ) {
+
+                               const stateMap = programMap[ programId ];
+
+                               for ( const wireframe in stateMap ) {
+
+                                       deleteVertexArrayObject( stateMap[ wireframe ].object );
+
+                                       delete stateMap[ wireframe ];
+
+                               }
+
+                               delete programMap[ programId ];
+
+                       }
+
+                       delete bindingStates[ geometryId ];
+
+               }
+
+       }
+
+       function releaseStatesOfGeometry( geometry ) {
+
+               if ( bindingStates[ geometry.id ] === undefined ) return;
+
+               const programMap = bindingStates[ geometry.id ];
+
+               for ( const programId in programMap ) {
+
+                       const stateMap = programMap[ programId ];
+
+                       for ( const wireframe in stateMap ) {
+
+                               deleteVertexArrayObject( stateMap[ wireframe ].object );
+
+                               delete stateMap[ wireframe ];
+
+                       }
+
+                       delete programMap[ programId ];
+
+               }
+
+               delete bindingStates[ geometry.id ];
+
+       }
+
+       function releaseStatesOfProgram( program ) {
+
+               for ( const geometryId in bindingStates ) {
+
+                       const programMap = bindingStates[ geometryId ];
+
+                       if ( programMap[ program.id ] === undefined ) continue;
+
+                       const stateMap = programMap[ program.id ];
+
+                       for ( const wireframe in stateMap ) {
+
+                               deleteVertexArrayObject( stateMap[ wireframe ].object );
+
+                               delete stateMap[ wireframe ];
+
+                       }
+
+                       delete programMap[ program.id ];
+
+               }
+
+       }
+
+       function reset() {
+
+               resetDefaultState();
+
+               if ( currentState === defaultState ) return;
+
+               currentState = defaultState;
+               bindVertexArrayObject( currentState.object );
+
+       }
+
+       // for backward-compatilibity
+
+       function resetDefaultState() {
+
+               defaultState.geometry = null;
+               defaultState.program = null;
+               defaultState.wireframe = false;
+
+       }
+
+       return {
+
+               setup: setup,
+               reset: reset,
+               resetDefaultState: resetDefaultState,
+               dispose: dispose,
+               releaseStatesOfGeometry: releaseStatesOfGeometry,
+               releaseStatesOfProgram: releaseStatesOfProgram,
+
+               initAttributes: initAttributes,
+               enableAttribute: enableAttribute,
+               disableUnusedAttributes: disableUnusedAttributes
+
+       };
+
+}
+
+function WebGLBufferRenderer( gl, extensions, info, capabilities ) {
+
+       const isWebGL2 = capabilities.isWebGL2;
+
+       let mode;
+
+       function setMode( value ) {
+
+               mode = value;
+
+       }
+
+       function render( start, count ) {
+
+               gl.drawArrays( mode, start, count );
+
+               info.update( count, mode, 1 );
+
+       }
+
+       function renderInstances( start, count, primcount ) {
+
+               if ( primcount === 0 ) return;
+
+               let extension, methodName;
+
+               if ( isWebGL2 ) {
+
+                       extension = gl;
+                       methodName = 'drawArraysInstanced';
+
+               } else {
+
+                       extension = extensions.get( 'ANGLE_instanced_arrays' );
+                       methodName = 'drawArraysInstancedANGLE';
+
+                       if ( extension === null ) {
+
+                               console.error( 'THREE.WebGLBufferRenderer: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays.' );
+                               return;
+
+                       }
+
+               }
+
+               extension[ methodName ]( mode, start, count, primcount );
+
+               info.update( count, mode, primcount );
+
+       }
+
+       //
+
+       this.setMode = setMode;
+       this.render = render;
+       this.renderInstances = renderInstances;
+
+}
+
+function WebGLCapabilities( gl, extensions, parameters ) {
+
+       let maxAnisotropy;
+
+       function getMaxAnisotropy() {
+
+               if ( maxAnisotropy !== undefined ) return maxAnisotropy;
+
+               const extension = extensions.get( 'EXT_texture_filter_anisotropic' );
+
+               if ( extension !== null ) {
+
+                       maxAnisotropy = gl.getParameter( extension.MAX_TEXTURE_MAX_ANISOTROPY_EXT );
+
+               } else {
+
+                       maxAnisotropy = 0;
+
+               }
+
+               return maxAnisotropy;
+
+       }
+
+       function getMaxPrecision( precision ) {
+
+               if ( precision === 'highp' ) {
+
+                       if ( gl.getShaderPrecisionFormat( 35633, 36338 ).precision > 0 &&
+                               gl.getShaderPrecisionFormat( 35632, 36338 ).precision > 0 ) {
+
+                               return 'highp';
+
+                       }
+
+                       precision = 'mediump';
+
+               }
+
+               if ( precision === 'mediump' ) {
+
+                       if ( gl.getShaderPrecisionFormat( 35633, 36337 ).precision > 0 &&
+                               gl.getShaderPrecisionFormat( 35632, 36337 ).precision > 0 ) {
+
+                               return 'mediump';
+
+                       }
+
+               }
+
+               return 'lowp';
+
+       }
+
+       /* eslint-disable no-undef */
+       const isWebGL2 = ( typeof WebGL2RenderingContext !== 'undefined' && gl instanceof WebGL2RenderingContext ) ||
+               ( typeof WebGL2ComputeRenderingContext !== 'undefined' && gl instanceof WebGL2ComputeRenderingContext );
+       /* eslint-enable no-undef */
+
+       let precision = parameters.precision !== undefined ? parameters.precision : 'highp';
+       const maxPrecision = getMaxPrecision( precision );
+
+       if ( maxPrecision !== precision ) {
+
+               console.warn( 'THREE.WebGLRenderer:', precision, 'not supported, using', maxPrecision, 'instead.' );
+               precision = maxPrecision;
+
+       }
+
+       const logarithmicDepthBuffer = parameters.logarithmicDepthBuffer === true;
+
+       const maxTextures = gl.getParameter( 34930 );
+       const maxVertexTextures = gl.getParameter( 35660 );
+       const maxTextureSize = gl.getParameter( 3379 );
+       const maxCubemapSize = gl.getParameter( 34076 );
+
+       const maxAttributes = gl.getParameter( 34921 );
+       const maxVertexUniforms = gl.getParameter( 36347 );
+       const maxVaryings = gl.getParameter( 36348 );
+       const maxFragmentUniforms = gl.getParameter( 36349 );
+
+       const vertexTextures = maxVertexTextures > 0;
+       const floatFragmentTextures = isWebGL2 || !! extensions.get( 'OES_texture_float' );
+       const floatVertexTextures = vertexTextures && floatFragmentTextures;
+
+       const maxSamples = isWebGL2 ? gl.getParameter( 36183 ) : 0;
+
+       return {
+
+               isWebGL2: isWebGL2,
+
+               getMaxAnisotropy: getMaxAnisotropy,
+               getMaxPrecision: getMaxPrecision,
+
+               precision: precision,
+               logarithmicDepthBuffer: logarithmicDepthBuffer,
+
+               maxTextures: maxTextures,
+               maxVertexTextures: maxVertexTextures,
+               maxTextureSize: maxTextureSize,
+               maxCubemapSize: maxCubemapSize,
+
+               maxAttributes: maxAttributes,
+               maxVertexUniforms: maxVertexUniforms,
+               maxVaryings: maxVaryings,
+               maxFragmentUniforms: maxFragmentUniforms,
+
+               vertexTextures: vertexTextures,
+               floatFragmentTextures: floatFragmentTextures,
+               floatVertexTextures: floatVertexTextures,
+
+               maxSamples: maxSamples
+
+       };
+
+}
+
+function WebGLClipping( properties ) {
+
+       const scope = this;
+
+       let globalState = null,
+               numGlobalPlanes = 0,
+               localClippingEnabled = false,
+               renderingShadows = false;
+
+       const plane = new Plane(),
+               viewNormalMatrix = new Matrix3(),
+
+               uniform = { value: null, needsUpdate: false };
+
+       this.uniform = uniform;
+       this.numPlanes = 0;
+       this.numIntersection = 0;
+
+       this.init = function ( planes, enableLocalClipping, camera ) {
+
+               const enabled =
+                       planes.length !== 0 ||
+                       enableLocalClipping ||
+                       // enable state of previous frame - the clipping code has to
+                       // run another frame in order to reset the state:
+                       numGlobalPlanes !== 0 ||
+                       localClippingEnabled;
+
+               localClippingEnabled = enableLocalClipping;
+
+               globalState = projectPlanes( planes, camera, 0 );
+               numGlobalPlanes = planes.length;
+
+               return enabled;
+
+       };
+
+       this.beginShadows = function () {
+
+               renderingShadows = true;
+               projectPlanes( null );
+
+       };
+
+       this.endShadows = function () {
+
+               renderingShadows = false;
+               resetGlobalState();
+
+       };
+
+       this.setState = function ( material, camera, useCache ) {
+
+               const planes = material.clippingPlanes,
+                       clipIntersection = material.clipIntersection,
+                       clipShadows = material.clipShadows;
+
+               const materialProperties = properties.get( material );
+
+               if ( ! localClippingEnabled || planes === null || planes.length === 0 || renderingShadows && ! clipShadows ) {
+
+                       // there's no local clipping
+
+                       if ( renderingShadows ) {
+
+                               // there's no global clipping
+
+                               projectPlanes( null );
+
+                       } else {
+
+                               resetGlobalState();
+
+                       }
+
+               } else {
+
+                       const nGlobal = renderingShadows ? 0 : numGlobalPlanes,
+                               lGlobal = nGlobal * 4;
+
+                       let dstArray = materialProperties.clippingState || null;
+
+                       uniform.value = dstArray; // ensure unique state
+
+                       dstArray = projectPlanes( planes, camera, lGlobal, useCache );
+
+                       for ( let i = 0; i !== lGlobal; ++ i ) {
+
+                               dstArray[ i ] = globalState[ i ];
+
+                       }
+
+                       materialProperties.clippingState = dstArray;
+                       this.numIntersection = clipIntersection ? this.numPlanes : 0;
+                       this.numPlanes += nGlobal;
+
+               }
+
+
+       };
+
+       function resetGlobalState() {
+
+               if ( uniform.value !== globalState ) {
+
+                       uniform.value = globalState;
+                       uniform.needsUpdate = numGlobalPlanes > 0;
+
+               }
+
+               scope.numPlanes = numGlobalPlanes;
+               scope.numIntersection = 0;
+
+       }
+
+       function projectPlanes( planes, camera, dstOffset, skipTransform ) {
+
+               const nPlanes = planes !== null ? planes.length : 0;
+               let dstArray = null;
+
+               if ( nPlanes !== 0 ) {
+
+                       dstArray = uniform.value;
+
+                       if ( skipTransform !== true || dstArray === null ) {
+
+                               const flatSize = dstOffset + nPlanes * 4,
+                                       viewMatrix = camera.matrixWorldInverse;
+
+                               viewNormalMatrix.getNormalMatrix( viewMatrix );
+
+                               if ( dstArray === null || dstArray.length < flatSize ) {
+
+                                       dstArray = new Float32Array( flatSize );
+
+                               }
+
+                               for ( let i = 0, i4 = dstOffset; i !== nPlanes; ++ i, i4 += 4 ) {
+
+                                       plane.copy( planes[ i ] ).applyMatrix4( viewMatrix, viewNormalMatrix );
+
+                                       plane.normal.toArray( dstArray, i4 );
+                                       dstArray[ i4 + 3 ] = plane.constant;
+
+                               }
+
+                       }
+
+                       uniform.value = dstArray;
+                       uniform.needsUpdate = true;
+
+               }
+
+               scope.numPlanes = nPlanes;
+               scope.numIntersection = 0;
+
+               return dstArray;
+
+       }
+
+}
+
+function WebGLCubeMaps( renderer ) {
+
+       let cubemaps = new WeakMap();
+
+       function mapTextureMapping( texture, mapping ) {
+
+               if ( mapping === EquirectangularReflectionMapping ) {
+
+                       texture.mapping = CubeReflectionMapping;
+
+               } else if ( mapping === EquirectangularRefractionMapping ) {
+
+                       texture.mapping = CubeRefractionMapping;
+
+               }
+
+               return texture;
+
+       }
+
+       function get( texture ) {
+
+               if ( texture && texture.isTexture ) {
+
+                       const mapping = texture.mapping;
+
+                       if ( mapping === EquirectangularReflectionMapping || mapping === EquirectangularRefractionMapping ) {
+
+                               if ( cubemaps.has( texture ) ) {
+
+                                       const cubemap = cubemaps.get( texture ).texture;
+                                       return mapTextureMapping( cubemap, texture.mapping );
+
+                               } else {
+
+                                       const image = texture.image;
+
+                                       if ( image && image.height > 0 ) {
+
+                                               const currentRenderList = renderer.getRenderList();
+                                               const currentRenderTarget = renderer.getRenderTarget();
+
+                                               const renderTarget = new WebGLCubeRenderTarget( image.height / 2 );
+                                               renderTarget.fromEquirectangularTexture( renderer, texture );
+                                               cubemaps.set( texture, renderTarget );
+
+                                               renderer.setRenderTarget( currentRenderTarget );
+                                               renderer.setRenderList( currentRenderList );
+
+                                               texture.addEventListener( 'dispose', onTextureDispose );
+
+                                               return mapTextureMapping( renderTarget.texture, texture.mapping );
+
+                                       } else {
+
+                                               // image not yet ready. try the conversion next frame
+
+                                               return null;
+
+                                       }
+
+                               }
+
+                       }
+
+               }
+
+               return texture;
+
+       }
+
+       function onTextureDispose( event ) {
+
+               const texture = event.target;
+
+               texture.removeEventListener( 'dispose', onTextureDispose );
+
+               const cubemap = cubemaps.get( texture );
+
+               if ( cubemap !== undefined ) {
+
+                       cubemaps.delete( texture );
+                       cubemap.dispose();
+
+               }
+
+       }
+
+       function dispose() {
+
+               cubemaps = new WeakMap();
+
+       }
+
+       return {
+               get: get,
+               dispose: dispose
+       };
+
+}
+
+function WebGLExtensions( gl ) {
+
+       const extensions = {};
+
+       function getExtension( name ) {
+
+               if ( extensions[ name ] !== undefined ) {
+
+                       return extensions[ name ];
+
+               }
+
+               let extension;
+
+               switch ( name ) {
+
+                       case 'WEBGL_depth_texture':
+                               extension = gl.getExtension( 'WEBGL_depth_texture' ) || gl.getExtension( 'MOZ_WEBGL_depth_texture' ) || gl.getExtension( 'WEBKIT_WEBGL_depth_texture' );
+                               break;
+
+                       case 'EXT_texture_filter_anisotropic':
+                               extension = gl.getExtension( 'EXT_texture_filter_anisotropic' ) || gl.getExtension( 'MOZ_EXT_texture_filter_anisotropic' ) || gl.getExtension( 'WEBKIT_EXT_texture_filter_anisotropic' );
+                               break;
+
+                       case 'WEBGL_compressed_texture_s3tc':
+                               extension = gl.getExtension( 'WEBGL_compressed_texture_s3tc' ) || gl.getExtension( 'MOZ_WEBGL_compressed_texture_s3tc' ) || gl.getExtension( 'WEBKIT_WEBGL_compressed_texture_s3tc' );
+                               break;
+
+                       case 'WEBGL_compressed_texture_pvrtc':
+                               extension = gl.getExtension( 'WEBGL_compressed_texture_pvrtc' ) || gl.getExtension( 'WEBKIT_WEBGL_compressed_texture_pvrtc' );
+                               break;
+
+                       default:
+                               extension = gl.getExtension( name );
+
+               }
+
+               extensions[ name ] = extension;
+
+               return extension;
+
+       }
+
+       return {
+
+               has: function ( name ) {
+
+                       return getExtension( name ) !== null;
+
+               },
+
+               init: function ( capabilities ) {
+
+                       if ( capabilities.isWebGL2 ) {
+
+                               getExtension( 'EXT_color_buffer_float' );
+
+                       } else {
+
+                               getExtension( 'WEBGL_depth_texture' );
+                               getExtension( 'OES_texture_float' );
+                               getExtension( 'OES_texture_half_float' );
+                               getExtension( 'OES_texture_half_float_linear' );
+                               getExtension( 'OES_standard_derivatives' );
+                               getExtension( 'OES_element_index_uint' );
+                               getExtension( 'OES_vertex_array_object' );
+                               getExtension( 'ANGLE_instanced_arrays' );
+
+                       }
+
+                       getExtension( 'OES_texture_float_linear' );
+                       getExtension( 'EXT_color_buffer_half_float' );
+
+               },
+
+               get: function ( name ) {
+
+                       const extension = getExtension( name );
+
+                       if ( extension === null ) {
+
+                               console.warn( 'THREE.WebGLRenderer: ' + name + ' extension not supported.' );
+
+                       }
+
+                       return extension;
+
+               }
+
+       };
+
+}
+
+function WebGLGeometries( gl, attributes, info, bindingStates ) {
+
+       const geometries = {};
+       const wireframeAttributes = new WeakMap();
+
+       function onGeometryDispose( event ) {
+
+               const geometry = event.target;
+
+               if ( geometry.index !== null ) {
+
+                       attributes.remove( geometry.index );
+
+               }
+
+               for ( const name in geometry.attributes ) {
+
+                       attributes.remove( geometry.attributes[ name ] );
+
+               }
+
+               geometry.removeEventListener( 'dispose', onGeometryDispose );
+
+               delete geometries[ geometry.id ];
+
+               const attribute = wireframeAttributes.get( geometry );
+
+               if ( attribute ) {
+
+                       attributes.remove( attribute );
+                       wireframeAttributes.delete( geometry );
+
+               }
+
+               bindingStates.releaseStatesOfGeometry( geometry );
+
+               if ( geometry.isInstancedBufferGeometry === true ) {
+
+                       delete geometry._maxInstanceCount;
+
+               }
+
+               //
+
+               info.memory.geometries --;
+
+       }
+
+       function get( object, geometry ) {
+
+               if ( geometries[ geometry.id ] === true ) return geometry;
+
+               geometry.addEventListener( 'dispose', onGeometryDispose );
+
+               geometries[ geometry.id ] = true;
+
+               info.memory.geometries ++;
+
+               return geometry;
+
+       }
+
+       function update( geometry ) {
+
+               const geometryAttributes = geometry.attributes;
+
+               // Updating index buffer in VAO now. See WebGLBindingStates.
+
+               for ( const name in geometryAttributes ) {
+
+                       attributes.update( geometryAttributes[ name ], 34962 );
+
+               }
+
+               // morph targets
+
+               const morphAttributes = geometry.morphAttributes;
+
+               for ( const name in morphAttributes ) {
+
+                       const array = morphAttributes[ name ];
+
+                       for ( let i = 0, l = array.length; i < l; i ++ ) {
+
+                               attributes.update( array[ i ], 34962 );
+
+                       }
+
+               }
+
+       }
+
+       function updateWireframeAttribute( geometry ) {
+
+               const indices = [];
+
+               const geometryIndex = geometry.index;
+               const geometryPosition = geometry.attributes.position;
+               let version = 0;
+
+               if ( geometryIndex !== null ) {
+
+                       const array = geometryIndex.array;
+                       version = geometryIndex.version;
+
+                       for ( let i = 0, l = array.length; i < l; i += 3 ) {
+
+                               const a = array[ i + 0 ];
+                               const b = array[ i + 1 ];
+                               const c = array[ i + 2 ];
+
+                               indices.push( a, b, b, c, c, a );
+
+                       }
+
+               } else {
+
+                       const array = geometryPosition.array;
+                       version = geometryPosition.version;
+
+                       for ( let i = 0, l = ( array.length / 3 ) - 1; i < l; i += 3 ) {
+
+                               const a = i + 0;
+                               const b = i + 1;
+                               const c = i + 2;
+
+                               indices.push( a, b, b, c, c, a );
+
+                       }
+
+               }
+
+               const attribute = new ( arrayMax( indices ) > 65535 ? Uint32BufferAttribute : Uint16BufferAttribute )( indices, 1 );
+               attribute.version = version;
+
+               // Updating index buffer in VAO now. See WebGLBindingStates
+
+               //
+
+               const previousAttribute = wireframeAttributes.get( geometry );
+
+               if ( previousAttribute ) attributes.remove( previousAttribute );
+
+               //
+
+               wireframeAttributes.set( geometry, attribute );
+
+       }
+
+       function getWireframeAttribute( geometry ) {
+
+               const currentAttribute = wireframeAttributes.get( geometry );
+
+               if ( currentAttribute ) {
+
+                       const geometryIndex = geometry.index;
+
+                       if ( geometryIndex !== null ) {
+
+                               // if the attribute is obsolete, create a new one
+
+                               if ( currentAttribute.version < geometryIndex.version ) {
+
+                                       updateWireframeAttribute( geometry );
+
+                               }
+
+                       }
+
+               } else {
+
+                       updateWireframeAttribute( geometry );
+
+               }
+
+               return wireframeAttributes.get( geometry );
+
+       }
+
+       return {
+
+               get: get,
+               update: update,
+
+               getWireframeAttribute: getWireframeAttribute
+
+       };
+
+}
+
+function WebGLIndexedBufferRenderer( gl, extensions, info, capabilities ) {
+
+       const isWebGL2 = capabilities.isWebGL2;
+
+       let mode;
+
+       function setMode( value ) {
+
+               mode = value;
+
+       }
+
+       let type, bytesPerElement;
+
+       function setIndex( value ) {
+
+               type = value.type;
+               bytesPerElement = value.bytesPerElement;
+
+       }
+
+       function render( start, count ) {
+
+               gl.drawElements( mode, count, type, start * bytesPerElement );
+
+               info.update( count, mode, 1 );
+
+       }
+
+       function renderInstances( start, count, primcount ) {
+
+               if ( primcount === 0 ) return;
+
+               let extension, methodName;
+
+               if ( isWebGL2 ) {
+
+                       extension = gl;
+                       methodName = 'drawElementsInstanced';
+
+               } else {
+
+                       extension = extensions.get( 'ANGLE_instanced_arrays' );
+                       methodName = 'drawElementsInstancedANGLE';
+
+                       if ( extension === null ) {
+
+                               console.error( 'THREE.WebGLIndexedBufferRenderer: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays.' );
+                               return;
+
+                       }
+
+               }
+
+               extension[ methodName ]( mode, count, type, start * bytesPerElement, primcount );
+
+               info.update( count, mode, primcount );
+
+       }
+
+       //
+
+       this.setMode = setMode;
+       this.setIndex = setIndex;
+       this.render = render;
+       this.renderInstances = renderInstances;
+
+}
+
+function WebGLInfo( gl ) {
+
+       const memory = {
+               geometries: 0,
+               textures: 0
+       };
+
+       const render = {
+               frame: 0,
+               calls: 0,
+               triangles: 0,
+               points: 0,
+               lines: 0
+       };
+
+       function update( count, mode, instanceCount ) {
+
+               render.calls ++;
+
+               switch ( mode ) {
+
+                       case 4:
+                               render.triangles += instanceCount * ( count / 3 );
+                               break;
+
+                       case 1:
+                               render.lines += instanceCount * ( count / 2 );
+                               break;
+
+                       case 3:
+                               render.lines += instanceCount * ( count - 1 );
+                               break;
+
+                       case 2:
+                               render.lines += instanceCount * count;
+                               break;
+
+                       case 0:
+                               render.points += instanceCount * count;
+                               break;
+
+                       default:
+                               console.error( 'THREE.WebGLInfo: Unknown draw mode:', mode );
+                               break;
+
+               }
+
+       }
+
+       function reset() {
+
+               render.frame ++;
+               render.calls = 0;
+               render.triangles = 0;
+               render.points = 0;
+               render.lines = 0;
+
+       }
+
+       return {
+               memory: memory,
+               render: render,
+               programs: null,
+               autoReset: true,
+               reset: reset,
+               update: update
+       };
+
+}
+
+function numericalSort( a, b ) {
+
+       return a[ 0 ] - b[ 0 ];
+
+}
+
+function absNumericalSort( a, b ) {
+
+       return Math.abs( b[ 1 ] ) - Math.abs( a[ 1 ] );
+
+}
+
+function WebGLMorphtargets( gl ) {
+
+       const influencesList = {};
+       const morphInfluences = new Float32Array( 8 );
+
+       const workInfluences = [];
+
+       for ( let i = 0; i < 8; i ++ ) {
+
+               workInfluences[ i ] = [ i, 0 ];
+
+       }
+
+       function update( object, geometry, material, program ) {
+
+               const objectInfluences = object.morphTargetInfluences;
+
+               // When object doesn't have morph target influences defined, we treat it as a 0-length array
+               // This is important to make sure we set up morphTargetBaseInfluence / morphTargetInfluences
+
+               const length = objectInfluences === undefined ? 0 : objectInfluences.length;
+
+               let influences = influencesList[ geometry.id ];
+
+               if ( influences === undefined ) {
+
+                       // initialise list
+
+                       influences = [];
+
+                       for ( let i = 0; i < length; i ++ ) {
+
+                               influences[ i ] = [ i, 0 ];
+
+                       }
+
+                       influencesList[ geometry.id ] = influences;
+
+               }
+
+               // Collect influences
+
+               for ( let i = 0; i < length; i ++ ) {
+
+                       const influence = influences[ i ];
+
+                       influence[ 0 ] = i;
+                       influence[ 1 ] = objectInfluences[ i ];
+
+               }
+
+               influences.sort( absNumericalSort );
+
+               for ( let i = 0; i < 8; i ++ ) {
+
+                       if ( i < length && influences[ i ][ 1 ] ) {
+
+                               workInfluences[ i ][ 0 ] = influences[ i ][ 0 ];
+                               workInfluences[ i ][ 1 ] = influences[ i ][ 1 ];
+
+                       } else {
+
+                               workInfluences[ i ][ 0 ] = Number.MAX_SAFE_INTEGER;
+                               workInfluences[ i ][ 1 ] = 0;
+
+                       }
+
+               }
+
+               workInfluences.sort( numericalSort );
+
+               const morphTargets = material.morphTargets && geometry.morphAttributes.position;
+               const morphNormals = material.morphNormals && geometry.morphAttributes.normal;
+
+               let morphInfluencesSum = 0;
+
+               for ( let i = 0; i < 8; i ++ ) {
+
+                       const influence = workInfluences[ i ];
+                       const index = influence[ 0 ];
+                       const value = influence[ 1 ];
+
+                       if ( index !== Number.MAX_SAFE_INTEGER && value ) {
+
+                               if ( morphTargets && geometry.getAttribute( 'morphTarget' + i ) !== morphTargets[ index ] ) {
+
+                                       geometry.setAttribute( 'morphTarget' + i, morphTargets[ index ] );
+
+                               }
+
+                               if ( morphNormals && geometry.getAttribute( 'morphNormal' + i ) !== morphNormals[ index ] ) {
+
+                                       geometry.setAttribute( 'morphNormal' + i, morphNormals[ index ] );
+
+                               }
+
+                               morphInfluences[ i ] = value;
+                               morphInfluencesSum += value;
+
+                       } else {
+
+                               if ( morphTargets && geometry.hasAttribute( 'morphTarget' + i ) === true ) {
+
+                                       geometry.deleteAttribute( 'morphTarget' + i );
+
+                               }
+
+                               if ( morphNormals && geometry.hasAttribute( 'morphNormal' + i ) === true ) {
+
+                                       geometry.deleteAttribute( 'morphNormal' + i );
+
+                               }
+
+                               morphInfluences[ i ] = 0;
+
+                       }
+
+               }
+
+               // GLSL shader uses formula baseinfluence * base + sum(target * influence)
+               // This allows us to switch between absolute morphs and relative morphs without changing shader code
+               // When baseinfluence = 1 - sum(influence), the above is equivalent to sum((target - base) * influence)
+               const morphBaseInfluence = geometry.morphTargetsRelative ? 1 : 1 - morphInfluencesSum;
+
+               program.getUniforms().setValue( gl, 'morphTargetBaseInfluence', morphBaseInfluence );
+               program.getUniforms().setValue( gl, 'morphTargetInfluences', morphInfluences );
+
+       }
+
+       return {
+
+               update: update
+
+       };
+
+}
+
+function WebGLObjects( gl, geometries, attributes, info ) {
+
+       let updateMap = new WeakMap();
+
+       function update( object ) {
+
+               const frame = info.render.frame;
+
+               const geometry = object.geometry;
+               const buffergeometry = geometries.get( object, geometry );
+
+               // Update once per frame
+
+               if ( updateMap.get( buffergeometry ) !== frame ) {
+
+                       geometries.update( buffergeometry );
+
+                       updateMap.set( buffergeometry, frame );
+
+               }
+
+               if ( object.isInstancedMesh ) {
+
+                       if ( object.hasEventListener( 'dispose', onInstancedMeshDispose ) === false ) {
+
+                               object.addEventListener( 'dispose', onInstancedMeshDispose );
+
+                       }
+
+                       attributes.update( object.instanceMatrix, 34962 );
+
+                       if ( object.instanceColor !== null ) {
+
+                               attributes.update( object.instanceColor, 34962 );
+
+                       }
+
+               }
+
+               return buffergeometry;
+
+       }
+
+       function dispose() {
+
+               updateMap = new WeakMap();
+
+       }
+
+       function onInstancedMeshDispose( event ) {
+
+               const instancedMesh = event.target;
+
+               instancedMesh.removeEventListener( 'dispose', onInstancedMeshDispose );
+
+               attributes.remove( instancedMesh.instanceMatrix );
+
+               if ( instancedMesh.instanceColor !== null ) attributes.remove( instancedMesh.instanceColor );
+
+       }
+
+       return {
+
+               update: update,
+               dispose: dispose
+
+       };
+
+}
+
+function DataTexture2DArray( data = null, width = 1, height = 1, depth = 1 ) {
+
+       Texture.call( this, null );
+
+       this.image = { data, width, height, depth };
+
+       this.magFilter = NearestFilter;
+       this.minFilter = NearestFilter;
+
+       this.wrapR = ClampToEdgeWrapping;
+
+       this.generateMipmaps = false;
+       this.flipY = false;
+
+       this.needsUpdate = true;
+
+}
+
+DataTexture2DArray.prototype = Object.create( Texture.prototype );
+DataTexture2DArray.prototype.constructor = DataTexture2DArray;
+DataTexture2DArray.prototype.isDataTexture2DArray = true;
+
+function DataTexture3D( data = null, width = 1, height = 1, depth = 1 ) {
+
+       // We're going to add .setXXX() methods for setting properties later.
+       // Users can still set in DataTexture3D directly.
+       //
+       //      const texture = new THREE.DataTexture3D( data, width, height, depth );
+       //      texture.anisotropy = 16;
+       //
+       // See #14839
+
+       Texture.call( this, null );
+
+       this.image = { data, width, height, depth };
+
+       this.magFilter = NearestFilter;
+       this.minFilter = NearestFilter;
+
+       this.wrapR = ClampToEdgeWrapping;
+
+       this.generateMipmaps = false;
+       this.flipY = false;
+
+       this.needsUpdate = true;
+
+
+}
+
+DataTexture3D.prototype = Object.create( Texture.prototype );
+DataTexture3D.prototype.constructor = DataTexture3D;
+DataTexture3D.prototype.isDataTexture3D = true;
+
+/**
+ * Uniforms of a program.
+ * Those form a tree structure with a special top-level container for the root,
+ * which you get by calling 'new WebGLUniforms( gl, program )'.
+ *
+ *
+ * Properties of inner nodes including the top-level container:
+ *
+ * .seq - array of nested uniforms
+ * .map - nested uniforms by name
+ *
+ *
+ * Methods of all nodes except the top-level container:
+ *
+ * .setValue( gl, value, [textures] )
+ *
+ *             uploads a uniform value(s)
+ *     the 'textures' parameter is needed for sampler uniforms
+ *
+ *
+ * Static methods of the top-level container (textures factorizations):
+ *
+ * .upload( gl, seq, values, textures )
+ *
+ *             sets uniforms in 'seq' to 'values[id].value'
+ *
+ * .seqWithValue( seq, values ) : filteredSeq
+ *
+ *             filters 'seq' entries with corresponding entry in values
+ *
+ *
+ * Methods of the top-level container (textures factorizations):
+ *
+ * .setValue( gl, name, value, textures )
+ *
+ *             sets uniform with  name 'name' to 'value'
+ *
+ * .setOptional( gl, obj, prop )
+ *
+ *             like .set for an optional property of the object
+ *
+ */
+
+const emptyTexture = new Texture();
+const emptyTexture2dArray = new DataTexture2DArray();
+const emptyTexture3d = new DataTexture3D();
+const emptyCubeTexture = new CubeTexture();
+
+// --- Utilities ---
+
+// Array Caches (provide typed arrays for temporary by size)
+
+const arrayCacheF32 = [];
+const arrayCacheI32 = [];
+
+// Float32Array caches used for uploading Matrix uniforms
+
+const mat4array = new Float32Array( 16 );
+const mat3array = new Float32Array( 9 );
+const mat2array = new Float32Array( 4 );
+
+// Flattening for arrays of vectors and matrices
+
+function flatten( array, nBlocks, blockSize ) {
+
+       const firstElem = array[ 0 ];
+
+       if ( firstElem <= 0 || firstElem > 0 ) return array;
+       // unoptimized: ! isNaN( firstElem )
+       // see http://jacksondunstan.com/articles/983
+
+       const n = nBlocks * blockSize;
+       let r = arrayCacheF32[ n ];
+
+       if ( r === undefined ) {
+
+               r = new Float32Array( n );
+               arrayCacheF32[ n ] = r;
+
+       }
+
+       if ( nBlocks !== 0 ) {
+
+               firstElem.toArray( r, 0 );
+
+               for ( let i = 1, offset = 0; i !== nBlocks; ++ i ) {
+
+                       offset += blockSize;
+                       array[ i ].toArray( r, offset );
+
+               }
+
+       }
+
+       return r;
+
+}
+
+function arraysEqual( a, b ) {
+
+       if ( a.length !== b.length ) return false;
+
+       for ( let i = 0, l = a.length; i < l; i ++ ) {
+
+               if ( a[ i ] !== b[ i ] ) return false;
+
+       }
+
+       return true;
+
+}
+
+function copyArray( a, b ) {
+
+       for ( let i = 0, l = b.length; i < l; i ++ ) {
+
+               a[ i ] = b[ i ];
+
+       }
+
+}
+
+// Texture unit allocation
+
+function allocTexUnits( textures, n ) {
+
+       let r = arrayCacheI32[ n ];
+
+       if ( r === undefined ) {
+
+               r = new Int32Array( n );
+               arrayCacheI32[ n ] = r;
+
+       }
+
+       for ( let i = 0; i !== n; ++ i ) {
+
+               r[ i ] = textures.allocateTextureUnit();
+
+       }
+
+       return r;
+
+}
+
+// --- Setters ---
+
+// Note: Defining these methods externally, because they come in a bunch
+// and this way their names minify.
+
+// Single scalar
+
+function setValueV1f( gl, v ) {
+
+       const cache = this.cache;
+
+       if ( cache[ 0 ] === v ) return;
+
+       gl.uniform1f( this.addr, v );
+
+       cache[ 0 ] = v;
+
+}
+
+// Single float vector (from flat array or THREE.VectorN)
+
+function setValueV2f( gl, v ) {
+
+       const cache = this.cache;
+
+       if ( v.x !== undefined ) {
+
+               if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y ) {
+
+                       gl.uniform2f( this.addr, v.x, v.y );
+
+                       cache[ 0 ] = v.x;
+                       cache[ 1 ] = v.y;
+
+               }
+
+       } else {
+
+               if ( arraysEqual( cache, v ) ) return;
+
+               gl.uniform2fv( this.addr, v );
+
+               copyArray( cache, v );
+
+       }
+
+}
+
+function setValueV3f( gl, v ) {
+
+       const cache = this.cache;
+
+       if ( v.x !== undefined ) {
+
+               if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z ) {
+
+                       gl.uniform3f( this.addr, v.x, v.y, v.z );
+
+                       cache[ 0 ] = v.x;
+                       cache[ 1 ] = v.y;
+                       cache[ 2 ] = v.z;
+
+               }
+
+       } else if ( v.r !== undefined ) {
+
+               if ( cache[ 0 ] !== v.r || cache[ 1 ] !== v.g || cache[ 2 ] !== v.b ) {
+
+                       gl.uniform3f( this.addr, v.r, v.g, v.b );
+
+                       cache[ 0 ] = v.r;
+                       cache[ 1 ] = v.g;
+                       cache[ 2 ] = v.b;
+
+               }
+
+       } else {
+
+               if ( arraysEqual( cache, v ) ) return;
+
+               gl.uniform3fv( this.addr, v );
+
+               copyArray( cache, v );
+
+       }
+
+}
+
+function setValueV4f( gl, v ) {
+
+       const cache = this.cache;
+
+       if ( v.x !== undefined ) {
+
+               if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z || cache[ 3 ] !== v.w ) {
+
+                       gl.uniform4f( this.addr, v.x, v.y, v.z, v.w );
+
+                       cache[ 0 ] = v.x;
+                       cache[ 1 ] = v.y;
+                       cache[ 2 ] = v.z;
+                       cache[ 3 ] = v.w;
+
+               }
+
+       } else {
+
+               if ( arraysEqual( cache, v ) ) return;
+
+               gl.uniform4fv( this.addr, v );
+
+               copyArray( cache, v );
+
+       }
+
+}
+
+// Single matrix (from flat array or MatrixN)
+
+function setValueM2( gl, v ) {
+
+       const cache = this.cache;
+       const elements = v.elements;
+
+       if ( elements === undefined ) {
+
+               if ( arraysEqual( cache, v ) ) return;
+
+               gl.uniformMatrix2fv( this.addr, false, v );
+
+               copyArray( cache, v );
+
+       } else {
+
+               if ( arraysEqual( cache, elements ) ) return;
+
+               mat2array.set( elements );
+
+               gl.uniformMatrix2fv( this.addr, false, mat2array );
+
+               copyArray( cache, elements );
+
+       }
+
+}
+
+function setValueM3( gl, v ) {
+
+       const cache = this.cache;
+       const elements = v.elements;
+
+       if ( elements === undefined ) {
+
+               if ( arraysEqual( cache, v ) ) return;
+
+               gl.uniformMatrix3fv( this.addr, false, v );
+
+               copyArray( cache, v );
+
+       } else {
+
+               if ( arraysEqual( cache, elements ) ) return;
+
+               mat3array.set( elements );
+
+               gl.uniformMatrix3fv( this.addr, false, mat3array );
+
+               copyArray( cache, elements );
+
+       }
+
+}
+
+function setValueM4( gl, v ) {
+
+       const cache = this.cache;
+       const elements = v.elements;
+
+       if ( elements === undefined ) {
+
+               if ( arraysEqual( cache, v ) ) return;
+
+               gl.uniformMatrix4fv( this.addr, false, v );
+
+               copyArray( cache, v );
+
+       } else {
+
+               if ( arraysEqual( cache, elements ) ) return;
+
+               mat4array.set( elements );
+
+               gl.uniformMatrix4fv( this.addr, false, mat4array );
+
+               copyArray( cache, elements );
+
+       }
+
+}
+
+// Single texture (2D / Cube)
+
+function setValueT1( gl, v, textures ) {
+
+       const cache = this.cache;
+       const unit = textures.allocateTextureUnit();
+
+       if ( cache[ 0 ] !== unit ) {
+
+               gl.uniform1i( this.addr, unit );
+               cache[ 0 ] = unit;
+
+       }
+
+       textures.safeSetTexture2D( v || emptyTexture, unit );
+
+}
+
+function setValueT2DArray1( gl, v, textures ) {
+
+       const cache = this.cache;
+       const unit = textures.allocateTextureUnit();
+
+       if ( cache[ 0 ] !== unit ) {
+
+               gl.uniform1i( this.addr, unit );
+               cache[ 0 ] = unit;
+
+       }
+
+       textures.setTexture2DArray( v || emptyTexture2dArray, unit );
+
+}
+
+function setValueT3D1( gl, v, textures ) {
+
+       const cache = this.cache;
+       const unit = textures.allocateTextureUnit();
+
+       if ( cache[ 0 ] !== unit ) {
+
+               gl.uniform1i( this.addr, unit );
+               cache[ 0 ] = unit;
+
+       }
+
+       textures.setTexture3D( v || emptyTexture3d, unit );
+
+}
+
+function setValueT6( gl, v, textures ) {
+
+       const cache = this.cache;
+       const unit = textures.allocateTextureUnit();
+
+       if ( cache[ 0 ] !== unit ) {
+
+               gl.uniform1i( this.addr, unit );
+               cache[ 0 ] = unit;
+
+       }
+
+       textures.safeSetTextureCube( v || emptyCubeTexture, unit );
+
+}
+
+// Integer / Boolean vectors or arrays thereof (always flat arrays)
+
+function setValueV1i( gl, v ) {
+
+       const cache = this.cache;
+
+       if ( cache[ 0 ] === v ) return;
+
+       gl.uniform1i( this.addr, v );
+
+       cache[ 0 ] = v;
+
+}
+
+function setValueV2i( gl, v ) {
+
+       const cache = this.cache;
+
+       if ( arraysEqual( cache, v ) ) return;
+
+       gl.uniform2iv( this.addr, v );
+
+       copyArray( cache, v );
+
+}
+
+function setValueV3i( gl, v ) {
+
+       const cache = this.cache;
+
+       if ( arraysEqual( cache, v ) ) return;
+
+       gl.uniform3iv( this.addr, v );
+
+       copyArray( cache, v );
+
+}
+
+function setValueV4i( gl, v ) {
+
+       const cache = this.cache;
+
+       if ( arraysEqual( cache, v ) ) return;
+
+       gl.uniform4iv( this.addr, v );
+
+       copyArray( cache, v );
+
+}
+
+// uint
+
+function setValueV1ui( gl, v ) {
+
+       const cache = this.cache;
+
+       if ( cache[ 0 ] === v ) return;
+
+       gl.uniform1ui( this.addr, v );
+
+       cache[ 0 ] = v;
+
+}
+
+// Helper to pick the right setter for the singular case
+
+function getSingularSetter( type ) {
+
+       switch ( type ) {
+
+               case 0x1406: return setValueV1f; // FLOAT
+               case 0x8b50: return setValueV2f; // _VEC2
+               case 0x8b51: return setValueV3f; // _VEC3
+               case 0x8b52: return setValueV4f; // _VEC4
+
+               case 0x8b5a: return setValueM2; // _MAT2
+               case 0x8b5b: return setValueM3; // _MAT3
+               case 0x8b5c: return setValueM4; // _MAT4
+
+               case 0x1404: case 0x8b56: return setValueV1i; // INT, BOOL
+               case 0x8b53: case 0x8b57: return setValueV2i; // _VEC2
+               case 0x8b54: case 0x8b58: return setValueV3i; // _VEC3
+               case 0x8b55: case 0x8b59: return setValueV4i; // _VEC4
+
+               case 0x1405: return setValueV1ui; // UINT
+
+               case 0x8b5e: // SAMPLER_2D
+               case 0x8d66: // SAMPLER_EXTERNAL_OES
+               case 0x8dca: // INT_SAMPLER_2D
+               case 0x8dd2: // UNSIGNED_INT_SAMPLER_2D
+               case 0x8b62: // SAMPLER_2D_SHADOW
+                       return setValueT1;
+
+               case 0x8b5f: // SAMPLER_3D
+               case 0x8dcb: // INT_SAMPLER_3D
+               case 0x8dd3: // UNSIGNED_INT_SAMPLER_3D
+                       return setValueT3D1;
+
+               case 0x8b60: // SAMPLER_CUBE
+               case 0x8dcc: // INT_SAMPLER_CUBE
+               case 0x8dd4: // UNSIGNED_INT_SAMPLER_CUBE
+               case 0x8dc5: // SAMPLER_CUBE_SHADOW
+                       return setValueT6;
+
+               case 0x8dc1: // SAMPLER_2D_ARRAY
+               case 0x8dcf: // INT_SAMPLER_2D_ARRAY
+               case 0x8dd7: // UNSIGNED_INT_SAMPLER_2D_ARRAY
+               case 0x8dc4: // SAMPLER_2D_ARRAY_SHADOW
+                       return setValueT2DArray1;
+
+       }
+
+}
+
+// Array of scalars
+function setValueV1fArray( gl, v ) {
+
+       gl.uniform1fv( this.addr, v );
+
+}
+
+// Integer / Boolean vectors or arrays thereof (always flat arrays)
+function setValueV1iArray( gl, v ) {
+
+       gl.uniform1iv( this.addr, v );
+
+}
+
+function setValueV2iArray( gl, v ) {
+
+       gl.uniform2iv( this.addr, v );
+
+}
+
+function setValueV3iArray( gl, v ) {
+
+       gl.uniform3iv( this.addr, v );
+
+}
+
+function setValueV4iArray( gl, v ) {
+
+       gl.uniform4iv( this.addr, v );
+
+}
+
+
+// Array of vectors (flat or from THREE classes)
+
+function setValueV2fArray( gl, v ) {
+
+       const data = flatten( v, this.size, 2 );
+
+       gl.uniform2fv( this.addr, data );
+
+}
+
+function setValueV3fArray( gl, v ) {
+
+       const data = flatten( v, this.size, 3 );
+
+       gl.uniform3fv( this.addr, data );
+
+}
+
+function setValueV4fArray( gl, v ) {
+
+       const data = flatten( v, this.size, 4 );
+
+       gl.uniform4fv( this.addr, data );
+
+}
+
+// Array of matrices (flat or from THREE clases)
+
+function setValueM2Array( gl, v ) {
+
+       const data = flatten( v, this.size, 4 );
+
+       gl.uniformMatrix2fv( this.addr, false, data );
+
+}
+
+function setValueM3Array( gl, v ) {
+
+       const data = flatten( v, this.size, 9 );
+
+       gl.uniformMatrix3fv( this.addr, false, data );
+
+}
+
+function setValueM4Array( gl, v ) {
+
+       const data = flatten( v, this.size, 16 );
+
+       gl.uniformMatrix4fv( this.addr, false, data );
+
+}
+
+// Array of textures (2D / Cube)
+
+function setValueT1Array( gl, v, textures ) {
+
+       const n = v.length;
+
+       const units = allocTexUnits( textures, n );
+
+       gl.uniform1iv( this.addr, units );
+
+       for ( let i = 0; i !== n; ++ i ) {
+
+               textures.safeSetTexture2D( v[ i ] || emptyTexture, units[ i ] );
+
+       }
+
+}
+
+function setValueT6Array( gl, v, textures ) {
+
+       const n = v.length;
+
+       const units = allocTexUnits( textures, n );
+
+       gl.uniform1iv( this.addr, units );
+
+       for ( let i = 0; i !== n; ++ i ) {
+
+               textures.safeSetTextureCube( v[ i ] || emptyCubeTexture, units[ i ] );
+
+       }
+
+}
+
+// Helper to pick the right setter for a pure (bottom-level) array
+
+function getPureArraySetter( type ) {
+
+       switch ( type ) {
+
+               case 0x1406: return setValueV1fArray; // FLOAT
+               case 0x8b50: return setValueV2fArray; // _VEC2
+               case 0x8b51: return setValueV3fArray; // _VEC3
+               case 0x8b52: return setValueV4fArray; // _VEC4
+
+               case 0x8b5a: return setValueM2Array; // _MAT2
+               case 0x8b5b: return setValueM3Array; // _MAT3
+               case 0x8b5c: return setValueM4Array; // _MAT4
+
+               case 0x1404: case 0x8b56: return setValueV1iArray; // INT, BOOL
+               case 0x8b53: case 0x8b57: return setValueV2iArray; // _VEC2
+               case 0x8b54: case 0x8b58: return setValueV3iArray; // _VEC3
+               case 0x8b55: case 0x8b59: return setValueV4iArray; // _VEC4
+
+               case 0x8b5e: // SAMPLER_2D
+               case 0x8d66: // SAMPLER_EXTERNAL_OES
+               case 0x8dca: // INT_SAMPLER_2D
+               case 0x8dd2: // UNSIGNED_INT_SAMPLER_2D
+               case 0x8b62: // SAMPLER_2D_SHADOW
+                       return setValueT1Array;
+
+               case 0x8b60: // SAMPLER_CUBE
+               case 0x8dcc: // INT_SAMPLER_CUBE
+               case 0x8dd4: // UNSIGNED_INT_SAMPLER_CUBE
+               case 0x8dc5: // SAMPLER_CUBE_SHADOW
+                       return setValueT6Array;
+
+       }
+
+}
+
+// --- Uniform Classes ---
+
+function SingleUniform( id, activeInfo, addr ) {
+
+       this.id = id;
+       this.addr = addr;
+       this.cache = [];
+       this.setValue = getSingularSetter( activeInfo.type );
+
+       // this.path = activeInfo.name; // DEBUG
+
+}
+
+function PureArrayUniform( id, activeInfo, addr ) {
+
+       this.id = id;
+       this.addr = addr;
+       this.cache = [];
+       this.size = activeInfo.size;
+       this.setValue = getPureArraySetter( activeInfo.type );
+
+       // this.path = activeInfo.name; // DEBUG
+
+}
+
+PureArrayUniform.prototype.updateCache = function ( data ) {
+
+       const cache = this.cache;
+
+       if ( data instanceof Float32Array && cache.length !== data.length ) {
+
+               this.cache = new Float32Array( data.length );
+
+       }
+
+       copyArray( cache, data );
+
+};
+
+function StructuredUniform( id ) {
+
+       this.id = id;
+
+       this.seq = [];
+       this.map = {};
+
+}
+
+StructuredUniform.prototype.setValue = function ( gl, value, textures ) {
+
+       const seq = this.seq;
+
+       for ( let i = 0, n = seq.length; i !== n; ++ i ) {
+
+               const u = seq[ i ];
+               u.setValue( gl, value[ u.id ], textures );
+
+       }
+
+};
+
+// --- Top-level ---
+
+// Parser - builds up the property tree from the path strings
+
+const RePathPart = /(\w+)(\])?(\[|\.)?/g;
+
+// extracts
+//     - the identifier (member name or array index)
+//  - followed by an optional right bracket (found when array index)
+//  - followed by an optional left bracket or dot (type of subscript)
+//
+// Note: These portions can be read in a non-overlapping fashion and
+// allow straightforward parsing of the hierarchy that WebGL encodes
+// in the uniform names.
+
+function addUniform( container, uniformObject ) {
+
+       container.seq.push( uniformObject );
+       container.map[ uniformObject.id ] = uniformObject;
+
+}
+
+function parseUniform( activeInfo, addr, container ) {
+
+       const path = activeInfo.name,
+               pathLength = path.length;
+
+       // reset RegExp object, because of the early exit of a previous run
+       RePathPart.lastIndex = 0;
+
+       while ( true ) {
+
+               const match = RePathPart.exec( path ),
+                       matchEnd = RePathPart.lastIndex;
+
+               let id = match[ 1 ];
+               const idIsIndex = match[ 2 ] === ']',
+                       subscript = match[ 3 ];
+
+               if ( idIsIndex ) id = id | 0; // convert to integer
+
+               if ( subscript === undefined || subscript === '[' && matchEnd + 2 === pathLength ) {
+
+                       // bare name or "pure" bottom-level array "[0]" suffix
+
+                       addUniform( container, subscript === undefined ?
+                               new SingleUniform( id, activeInfo, addr ) :
+                               new PureArrayUniform( id, activeInfo, addr ) );
+
+                       break;
+
+               } else {
+
+                       // step into inner node / create it in case it doesn't exist
+
+                       const map = container.map;
+                       let next = map[ id ];
+
+                       if ( next === undefined ) {
+
+                               next = new StructuredUniform( id );
+                               addUniform( container, next );
+
+                       }
+
+                       container = next;
+
+               }
+
+       }
+
+}
+
+// Root Container
+
+function WebGLUniforms( gl, program ) {
+
+       this.seq = [];
+       this.map = {};
+
+       const n = gl.getProgramParameter( program, 35718 );
+
+       for ( let i = 0; i < n; ++ i ) {
+
+               const info = gl.getActiveUniform( program, i ),
+                       addr = gl.getUniformLocation( program, info.name );
+
+               parseUniform( info, addr, this );
+
+       }
+
+}
+
+WebGLUniforms.prototype.setValue = function ( gl, name, value, textures ) {
+
+       const u = this.map[ name ];
+
+       if ( u !== undefined ) u.setValue( gl, value, textures );
+
+};
+
+WebGLUniforms.prototype.setOptional = function ( gl, object, name ) {
+
+       const v = object[ name ];
+
+       if ( v !== undefined ) this.setValue( gl, name, v );
+
+};
+
+
+// Static interface
+
+WebGLUniforms.upload = function ( gl, seq, values, textures ) {
+
+       for ( let i = 0, n = seq.length; i !== n; ++ i ) {
+
+               const u = seq[ i ],
+                       v = values[ u.id ];
+
+               if ( v.needsUpdate !== false ) {
+
+                       // note: always updating when .needsUpdate is undefined
+                       u.setValue( gl, v.value, textures );
+
+               }
+
+       }
+
+};
+
+WebGLUniforms.seqWithValue = function ( seq, values ) {
+
+       const r = [];
+
+       for ( let i = 0, n = seq.length; i !== n; ++ i ) {
+
+               const u = seq[ i ];
+               if ( u.id in values ) r.push( u );
+
+       }
+
+       return r;
+
+};
+
+function WebGLShader( gl, type, string ) {
+
+       const shader = gl.createShader( type );
+
+       gl.shaderSource( shader, string );
+       gl.compileShader( shader );
+
+       return shader;
+
+}
+
+let programIdCount = 0;
+
+function addLineNumbers( string ) {
+
+       const lines = string.split( '\n' );
+
+       for ( let i = 0; i < lines.length; i ++ ) {
+
+               lines[ i ] = ( i + 1 ) + ': ' + lines[ i ];
+
+       }
+
+       return lines.join( '\n' );
+
+}
+
+function getEncodingComponents( encoding ) {
+
+       switch ( encoding ) {
+
+               case LinearEncoding:
+                       return [ 'Linear', '( value )' ];
+               case sRGBEncoding:
+                       return [ 'sRGB', '( value )' ];
+               case RGBEEncoding:
+                       return [ 'RGBE', '( value )' ];
+               case RGBM7Encoding:
+                       return [ 'RGBM', '( value, 7.0 )' ];
+               case RGBM16Encoding:
+                       return [ 'RGBM', '( value, 16.0 )' ];
+               case RGBDEncoding:
+                       return [ 'RGBD', '( value, 256.0 )' ];
+               case GammaEncoding:
+                       return [ 'Gamma', '( value, float( GAMMA_FACTOR ) )' ];
+               case LogLuvEncoding:
+                       return [ 'LogLuv', '( value )' ];
+               default:
+                       console.warn( 'THREE.WebGLProgram: Unsupported encoding:', encoding );
+                       return [ 'Linear', '( value )' ];
+
+       }
+
+}
+
+function getShaderErrors( gl, shader, type ) {
+
+       const status = gl.getShaderParameter( shader, 35713 );
+       const log = gl.getShaderInfoLog( shader ).trim();
+
+       if ( status && log === '' ) return '';
+
+       // --enable-privileged-webgl-extension
+       // console.log( '**' + type + '**', gl.getExtension( 'WEBGL_debug_shaders' ).getTranslatedShaderSource( shader ) );
+
+       const source = gl.getShaderSource( shader );
+
+       return 'THREE.WebGLShader: gl.getShaderInfoLog() ' + type + '\n' + log + addLineNumbers( source );
+
+}
+
+function getTexelDecodingFunction( functionName, encoding ) {
+
+       const components = getEncodingComponents( encoding );
+       return 'vec4 ' + functionName + '( vec4 value ) { return ' + components[ 0 ] + 'ToLinear' + components[ 1 ] + '; }';
+
+}
+
+function getTexelEncodingFunction( functionName, encoding ) {
+
+       const components = getEncodingComponents( encoding );
+       return 'vec4 ' + functionName + '( vec4 value ) { return LinearTo' + components[ 0 ] + components[ 1 ] + '; }';
+
+}
+
+function getToneMappingFunction( functionName, toneMapping ) {
+
+       let toneMappingName;
+
+       switch ( toneMapping ) {
+
+               case LinearToneMapping:
+                       toneMappingName = 'Linear';
+                       break;
+
+               case ReinhardToneMapping:
+                       toneMappingName = 'Reinhard';
+                       break;
+
+               case CineonToneMapping:
+                       toneMappingName = 'OptimizedCineon';
+                       break;
+
+               case ACESFilmicToneMapping:
+                       toneMappingName = 'ACESFilmic';
+                       break;
+
+               case CustomToneMapping:
+                       toneMappingName = 'Custom';
+                       break;
+
+               default:
+                       console.warn( 'THREE.WebGLProgram: Unsupported toneMapping:', toneMapping );
+                       toneMappingName = 'Linear';
+
+       }
+
+       return 'vec3 ' + functionName + '( vec3 color ) { return ' + toneMappingName + 'ToneMapping( color ); }';
+
+}
+
+function generateExtensions( parameters ) {
+
+       const chunks = [
+               ( parameters.extensionDerivatives || parameters.envMapCubeUV || parameters.bumpMap || parameters.tangentSpaceNormalMap || parameters.clearcoatNormalMap || parameters.flatShading || parameters.shaderID === 'physical' ) ? '#extension GL_OES_standard_derivatives : enable' : '',
+               ( parameters.extensionFragDepth || parameters.logarithmicDepthBuffer ) && parameters.rendererExtensionFragDepth ? '#extension GL_EXT_frag_depth : enable' : '',
+               ( parameters.extensionDrawBuffers && parameters.rendererExtensionDrawBuffers ) ? '#extension GL_EXT_draw_buffers : require' : '',
+               ( parameters.extensionShaderTextureLOD || parameters.envMap ) && parameters.rendererExtensionShaderTextureLod ? '#extension GL_EXT_shader_texture_lod : enable' : ''
+       ];
+
+       return chunks.filter( filterEmptyLine ).join( '\n' );
+
+}
+
+function generateDefines( defines ) {
+
+       const chunks = [];
+
+       for ( const name in defines ) {
+
+               const value = defines[ name ];
+
+               if ( value === false ) continue;
+
+               chunks.push( '#define ' + name + ' ' + value );
+
+       }
+
+       return chunks.join( '\n' );
+
+}
+
+function fetchAttributeLocations( gl, program ) {
+
+       const attributes = {};
+
+       const n = gl.getProgramParameter( program, 35721 );
+
+       for ( let i = 0; i < n; i ++ ) {
+
+               const info = gl.getActiveAttrib( program, i );
+               const name = info.name;
+
+               // console.log( 'THREE.WebGLProgram: ACTIVE VERTEX ATTRIBUTE:', name, i );
+
+               attributes[ name ] = gl.getAttribLocation( program, name );
+
+       }
+
+       return attributes;
+
+}
+
+function filterEmptyLine( string ) {
+
+       return string !== '';
+
+}
+
+function replaceLightNums( string, parameters ) {
+
+       return string
+               .replace( /NUM_DIR_LIGHTS/g, parameters.numDirLights )
+               .replace( /NUM_SPOT_LIGHTS/g, parameters.numSpotLights )
+               .replace( /NUM_RECT_AREA_LIGHTS/g, parameters.numRectAreaLights )
+               .replace( /NUM_POINT_LIGHTS/g, parameters.numPointLights )
+               .replace( /NUM_HEMI_LIGHTS/g, parameters.numHemiLights )
+               .replace( /NUM_DIR_LIGHT_SHADOWS/g, parameters.numDirLightShadows )
+               .replace( /NUM_SPOT_LIGHT_SHADOWS/g, parameters.numSpotLightShadows )
+               .replace( /NUM_POINT_LIGHT_SHADOWS/g, parameters.numPointLightShadows );
+
+}
+
+function replaceClippingPlaneNums( string, parameters ) {
+
+       return string
+               .replace( /NUM_CLIPPING_PLANES/g, parameters.numClippingPlanes )
+               .replace( /UNION_CLIPPING_PLANES/g, ( parameters.numClippingPlanes - parameters.numClipIntersection ) );
+
+}
+
+// Resolve Includes
+
+const includePattern = /^[ \t]*#include +<([\w\d./]+)>/gm;
+
+function resolveIncludes( string ) {
+
+       return string.replace( includePattern, includeReplacer );
+
+}
+
+function includeReplacer( match, include ) {
+
+       const string = ShaderChunk[ include ];
+
+       if ( string === undefined ) {
+
+               throw new Error( 'Can not resolve #include <' + include + '>' );
+
+       }
+
+       return resolveIncludes( string );
+
+}
+
+// Unroll Loops
+
+const deprecatedUnrollLoopPattern = /#pragma unroll_loop[\s]+?for \( int i \= (\d+)\; i < (\d+)\; i \+\+ \) \{([\s\S]+?)(?=\})\}/g;
+const unrollLoopPattern = /#pragma unroll_loop_start\s+for\s*\(\s*int\s+i\s*=\s*(\d+)\s*;\s*i\s*<\s*(\d+)\s*;\s*i\s*\+\+\s*\)\s*{([\s\S]+?)}\s+#pragma unroll_loop_end/g;
+
+function unrollLoops( string ) {
+
+       return string
+               .replace( unrollLoopPattern, loopReplacer )
+               .replace( deprecatedUnrollLoopPattern, deprecatedLoopReplacer );
+
+}
+
+function deprecatedLoopReplacer( match, start, end, snippet ) {
+
+       console.warn( 'WebGLProgram: #pragma unroll_loop shader syntax is deprecated. Please use #pragma unroll_loop_start syntax instead.' );
+       return loopReplacer( match, start, end, snippet );
+
+}
+
+function loopReplacer( match, start, end, snippet ) {
+
+       let string = '';
+
+       for ( let i = parseInt( start ); i < parseInt( end ); i ++ ) {
+
+               string += snippet
+                       .replace( /\[\s*i\s*\]/g, '[ ' + i + ' ]' )
+                       .replace( /UNROLLED_LOOP_INDEX/g, i );
+
+       }
+
+       return string;
+
+}
+
+//
+
+function generatePrecision( parameters ) {
+
+       let precisionstring = 'precision ' + parameters.precision + ' float;\nprecision ' + parameters.precision + ' int;';
+
+       if ( parameters.precision === 'highp' ) {
+
+               precisionstring += '\n#define HIGH_PRECISION';
+
+       } else if ( parameters.precision === 'mediump' ) {
+
+               precisionstring += '\n#define MEDIUM_PRECISION';
+
+       } else if ( parameters.precision === 'lowp' ) {
+
+               precisionstring += '\n#define LOW_PRECISION';
+
+       }
+
+       return precisionstring;
+
+}
+
+function generateShadowMapTypeDefine( parameters ) {
+
+       let shadowMapTypeDefine = 'SHADOWMAP_TYPE_BASIC';
+
+       if ( parameters.shadowMapType === PCFShadowMap ) {
+
+               shadowMapTypeDefine = 'SHADOWMAP_TYPE_PCF';
+
+       } else if ( parameters.shadowMapType === PCFSoftShadowMap ) {
+
+               shadowMapTypeDefine = 'SHADOWMAP_TYPE_PCF_SOFT';
+
+       } else if ( parameters.shadowMapType === VSMShadowMap ) {
+
+               shadowMapTypeDefine = 'SHADOWMAP_TYPE_VSM';
+
+       }
+
+       return shadowMapTypeDefine;
+
+}
+
+function generateEnvMapTypeDefine( parameters ) {
+
+       let envMapTypeDefine = 'ENVMAP_TYPE_CUBE';
+
+       if ( parameters.envMap ) {
+
+               switch ( parameters.envMapMode ) {
+
+                       case CubeReflectionMapping:
+                       case CubeRefractionMapping:
+                               envMapTypeDefine = 'ENVMAP_TYPE_CUBE';
+                               break;
+
+                       case CubeUVReflectionMapping:
+                       case CubeUVRefractionMapping:
+                               envMapTypeDefine = 'ENVMAP_TYPE_CUBE_UV';
+                               break;
+
+               }
+
+       }
+
+       return envMapTypeDefine;
+
+}
+
+function generateEnvMapModeDefine( parameters ) {
+
+       let envMapModeDefine = 'ENVMAP_MODE_REFLECTION';
+
+       if ( parameters.envMap ) {
+
+               switch ( parameters.envMapMode ) {
+
+                       case CubeRefractionMapping:
+                       case CubeUVRefractionMapping:
+
+                               envMapModeDefine = 'ENVMAP_MODE_REFRACTION';
+                               break;
+
+               }
+
+       }
+
+       return envMapModeDefine;
+
+}
+
+function generateEnvMapBlendingDefine( parameters ) {
+
+       let envMapBlendingDefine = 'ENVMAP_BLENDING_NONE';
+
+       if ( parameters.envMap ) {
+
+               switch ( parameters.combine ) {
+
+                       case MultiplyOperation:
+                               envMapBlendingDefine = 'ENVMAP_BLENDING_MULTIPLY';
+                               break;
+
+                       case MixOperation:
+                               envMapBlendingDefine = 'ENVMAP_BLENDING_MIX';
+                               break;
+
+                       case AddOperation:
+                               envMapBlendingDefine = 'ENVMAP_BLENDING_ADD';
+                               break;
+
+               }
+
+       }
+
+       return envMapBlendingDefine;
+
+}
+
+function WebGLProgram( renderer, cacheKey, parameters, bindingStates ) {
+
+       const gl = renderer.getContext();
+
+       const defines = parameters.defines;
+
+       let vertexShader = parameters.vertexShader;
+       let fragmentShader = parameters.fragmentShader;
+
+       const shadowMapTypeDefine = generateShadowMapTypeDefine( parameters );
+       const envMapTypeDefine = generateEnvMapTypeDefine( parameters );
+       const envMapModeDefine = generateEnvMapModeDefine( parameters );
+       const envMapBlendingDefine = generateEnvMapBlendingDefine( parameters );
+
+
+       const gammaFactorDefine = ( renderer.gammaFactor > 0 ) ? renderer.gammaFactor : 1.0;
+
+       const customExtensions = parameters.isWebGL2 ? '' : generateExtensions( parameters );
+
+       const customDefines = generateDefines( defines );
+
+       const program = gl.createProgram();
+
+       let prefixVertex, prefixFragment;
+       let versionString = parameters.glslVersion ? '#version ' + parameters.glslVersion + '\n' : '';
+
+       if ( parameters.isRawShaderMaterial ) {
+
+               prefixVertex = [
+
+                       customDefines
+
+               ].filter( filterEmptyLine ).join( '\n' );
+
+               if ( prefixVertex.length > 0 ) {
+
+                       prefixVertex += '\n';
+
+               }
+
+               prefixFragment = [
+
+                       customExtensions,
+                       customDefines
+
+               ].filter( filterEmptyLine ).join( '\n' );
+
+               if ( prefixFragment.length > 0 ) {
+
+                       prefixFragment += '\n';
+
+               }
+
+       } else {
+
+               prefixVertex = [
+
+                       generatePrecision( parameters ),
+
+                       '#define SHADER_NAME ' + parameters.shaderName,
+
+                       customDefines,
+
+                       parameters.instancing ? '#define USE_INSTANCING' : '',
+                       parameters.instancingColor ? '#define USE_INSTANCING_COLOR' : '',
+
+                       parameters.supportsVertexTextures ? '#define VERTEX_TEXTURES' : '',
+
+                       '#define GAMMA_FACTOR ' + gammaFactorDefine,
+
+                       '#define MAX_BONES ' + parameters.maxBones,
+                       ( parameters.useFog && parameters.fog ) ? '#define USE_FOG' : '',
+                       ( parameters.useFog && parameters.fogExp2 ) ? '#define FOG_EXP2' : '',
+
+                       parameters.map ? '#define USE_MAP' : '',
+                       parameters.envMap ? '#define USE_ENVMAP' : '',
+                       parameters.envMap ? '#define ' + envMapModeDefine : '',
+                       parameters.lightMap ? '#define USE_LIGHTMAP' : '',
+                       parameters.aoMap ? '#define USE_AOMAP' : '',
+                       parameters.emissiveMap ? '#define USE_EMISSIVEMAP' : '',
+                       parameters.bumpMap ? '#define USE_BUMPMAP' : '',
+                       parameters.normalMap ? '#define USE_NORMALMAP' : '',
+                       ( parameters.normalMap && parameters.objectSpaceNormalMap ) ? '#define OBJECTSPACE_NORMALMAP' : '',
+                       ( parameters.normalMap && parameters.tangentSpaceNormalMap ) ? '#define TANGENTSPACE_NORMALMAP' : '',
+
+                       parameters.clearcoatMap ? '#define USE_CLEARCOATMAP' : '',
+                       parameters.clearcoatRoughnessMap ? '#define USE_CLEARCOAT_ROUGHNESSMAP' : '',
+                       parameters.clearcoatNormalMap ? '#define USE_CLEARCOAT_NORMALMAP' : '',
+                       parameters.displacementMap && parameters.supportsVertexTextures ? '#define USE_DISPLACEMENTMAP' : '',
+                       parameters.specularMap ? '#define USE_SPECULARMAP' : '',
+                       parameters.roughnessMap ? '#define USE_ROUGHNESSMAP' : '',
+                       parameters.metalnessMap ? '#define USE_METALNESSMAP' : '',
+                       parameters.alphaMap ? '#define USE_ALPHAMAP' : '',
+                       parameters.transmissionMap ? '#define USE_TRANSMISSIONMAP' : '',
+
+                       parameters.vertexTangents ? '#define USE_TANGENT' : '',
+                       parameters.vertexColors ? '#define USE_COLOR' : '',
+                       parameters.vertexUvs ? '#define USE_UV' : '',
+                       parameters.uvsVertexOnly ? '#define UVS_VERTEX_ONLY' : '',
+
+                       parameters.flatShading ? '#define FLAT_SHADED' : '',
+
+                       parameters.skinning ? '#define USE_SKINNING' : '',
+                       parameters.useVertexTexture ? '#define BONE_TEXTURE' : '',
+
+                       parameters.morphTargets ? '#define USE_MORPHTARGETS' : '',
+                       parameters.morphNormals && parameters.flatShading === false ? '#define USE_MORPHNORMALS' : '',
+                       parameters.doubleSided ? '#define DOUBLE_SIDED' : '',
+                       parameters.flipSided ? '#define FLIP_SIDED' : '',
+
+                       parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '',
+                       parameters.shadowMapEnabled ? '#define ' + shadowMapTypeDefine : '',
+
+                       parameters.sizeAttenuation ? '#define USE_SIZEATTENUATION' : '',
+
+                       parameters.logarithmicDepthBuffer ? '#define USE_LOGDEPTHBUF' : '',
+                       ( parameters.logarithmicDepthBuffer && parameters.rendererExtensionFragDepth ) ? '#define USE_LOGDEPTHBUF_EXT' : '',
+
+                       'uniform mat4 modelMatrix;',
+                       'uniform mat4 modelViewMatrix;',
+                       'uniform mat4 projectionMatrix;',
+                       'uniform mat4 viewMatrix;',
+                       'uniform mat3 normalMatrix;',
+                       'uniform vec3 cameraPosition;',
+                       'uniform bool isOrthographic;',
+
+                       '#ifdef USE_INSTANCING',
+
+                       '       attribute mat4 instanceMatrix;',
+
+                       '#endif',
+
+                       '#ifdef USE_INSTANCING_COLOR',
+
+                       '       attribute vec3 instanceColor;',
+
+                       '#endif',
+
+                       'attribute vec3 position;',
+                       'attribute vec3 normal;',
+                       'attribute vec2 uv;',
+
+                       '#ifdef USE_TANGENT',
+
+                       '       attribute vec4 tangent;',
+
+                       '#endif',
+
+                       '#ifdef USE_COLOR',
+
+                       '       attribute vec3 color;',
+
+                       '#endif',
+
+                       '#ifdef USE_MORPHTARGETS',
+
+                       '       attribute vec3 morphTarget0;',
+                       '       attribute vec3 morphTarget1;',
+                       '       attribute vec3 morphTarget2;',
+                       '       attribute vec3 morphTarget3;',
+
+                       '       #ifdef USE_MORPHNORMALS',
+
+                       '               attribute vec3 morphNormal0;',
+                       '               attribute vec3 morphNormal1;',
+                       '               attribute vec3 morphNormal2;',
+                       '               attribute vec3 morphNormal3;',
+
+                       '       #else',
+
+                       '               attribute vec3 morphTarget4;',
+                       '               attribute vec3 morphTarget5;',
+                       '               attribute vec3 morphTarget6;',
+                       '               attribute vec3 morphTarget7;',
+
+                       '       #endif',
+
+                       '#endif',
+
+                       '#ifdef USE_SKINNING',
+
+                       '       attribute vec4 skinIndex;',
+                       '       attribute vec4 skinWeight;',
+
+                       '#endif',
+
+                       '\n'
+
+               ].filter( filterEmptyLine ).join( '\n' );
+
+               prefixFragment = [
+
+                       customExtensions,
+
+                       generatePrecision( parameters ),
+
+                       '#define SHADER_NAME ' + parameters.shaderName,
+
+                       customDefines,
+
+                       parameters.alphaTest ? '#define ALPHATEST ' + parameters.alphaTest + ( parameters.alphaTest % 1 ? '' : '.0' ) : '', // add '.0' if integer
+
+                       '#define GAMMA_FACTOR ' + gammaFactorDefine,
+
+                       ( parameters.useFog && parameters.fog ) ? '#define USE_FOG' : '',
+                       ( parameters.useFog && parameters.fogExp2 ) ? '#define FOG_EXP2' : '',
+
+                       parameters.map ? '#define USE_MAP' : '',
+                       parameters.matcap ? '#define USE_MATCAP' : '',
+                       parameters.envMap ? '#define USE_ENVMAP' : '',
+                       parameters.envMap ? '#define ' + envMapTypeDefine : '',
+                       parameters.envMap ? '#define ' + envMapModeDefine : '',
+                       parameters.envMap ? '#define ' + envMapBlendingDefine : '',
+                       parameters.lightMap ? '#define USE_LIGHTMAP' : '',
+                       parameters.aoMap ? '#define USE_AOMAP' : '',
+                       parameters.emissiveMap ? '#define USE_EMISSIVEMAP' : '',
+                       parameters.bumpMap ? '#define USE_BUMPMAP' : '',
+                       parameters.normalMap ? '#define USE_NORMALMAP' : '',
+                       ( parameters.normalMap && parameters.objectSpaceNormalMap ) ? '#define OBJECTSPACE_NORMALMAP' : '',
+                       ( parameters.normalMap && parameters.tangentSpaceNormalMap ) ? '#define TANGENTSPACE_NORMALMAP' : '',
+                       parameters.clearcoatMap ? '#define USE_CLEARCOATMAP' : '',
+                       parameters.clearcoatRoughnessMap ? '#define USE_CLEARCOAT_ROUGHNESSMAP' : '',
+                       parameters.clearcoatNormalMap ? '#define USE_CLEARCOAT_NORMALMAP' : '',
+                       parameters.specularMap ? '#define USE_SPECULARMAP' : '',
+                       parameters.roughnessMap ? '#define USE_ROUGHNESSMAP' : '',
+                       parameters.metalnessMap ? '#define USE_METALNESSMAP' : '',
+                       parameters.alphaMap ? '#define USE_ALPHAMAP' : '',
+
+                       parameters.sheen ? '#define USE_SHEEN' : '',
+                       parameters.transmissionMap ? '#define USE_TRANSMISSIONMAP' : '',
+
+                       parameters.vertexTangents ? '#define USE_TANGENT' : '',
+                       parameters.vertexColors || parameters.instancingColor ? '#define USE_COLOR' : '',
+                       parameters.vertexUvs ? '#define USE_UV' : '',
+                       parameters.uvsVertexOnly ? '#define UVS_VERTEX_ONLY' : '',
+
+                       parameters.gradientMap ? '#define USE_GRADIENTMAP' : '',
+
+                       parameters.flatShading ? '#define FLAT_SHADED' : '',
+
+                       parameters.doubleSided ? '#define DOUBLE_SIDED' : '',
+                       parameters.flipSided ? '#define FLIP_SIDED' : '',
+
+                       parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '',
+                       parameters.shadowMapEnabled ? '#define ' + shadowMapTypeDefine : '',
+
+                       parameters.premultipliedAlpha ? '#define PREMULTIPLIED_ALPHA' : '',
+
+                       parameters.physicallyCorrectLights ? '#define PHYSICALLY_CORRECT_LIGHTS' : '',
+
+                       parameters.logarithmicDepthBuffer ? '#define USE_LOGDEPTHBUF' : '',
+                       ( parameters.logarithmicDepthBuffer && parameters.rendererExtensionFragDepth ) ? '#define USE_LOGDEPTHBUF_EXT' : '',
+
+                       ( ( parameters.extensionShaderTextureLOD || parameters.envMap ) && parameters.rendererExtensionShaderTextureLod ) ? '#define TEXTURE_LOD_EXT' : '',
+
+                       'uniform mat4 viewMatrix;',
+                       'uniform vec3 cameraPosition;',
+                       'uniform bool isOrthographic;',
+
+                       ( parameters.toneMapping !== NoToneMapping ) ? '#define TONE_MAPPING' : '',
+                       ( parameters.toneMapping !== NoToneMapping ) ? ShaderChunk[ 'tonemapping_pars_fragment' ] : '', // this code is required here because it is used by the toneMapping() function defined below
+                       ( parameters.toneMapping !== NoToneMapping ) ? getToneMappingFunction( 'toneMapping', parameters.toneMapping ) : '',
+
+                       parameters.dithering ? '#define DITHERING' : '',
+
+                       ShaderChunk[ 'encodings_pars_fragment' ], // this code is required here because it is used by the various encoding/decoding function defined below
+                       parameters.map ? getTexelDecodingFunction( 'mapTexelToLinear', parameters.mapEncoding ) : '',
+                       parameters.matcap ? getTexelDecodingFunction( 'matcapTexelToLinear', parameters.matcapEncoding ) : '',
+                       parameters.envMap ? getTexelDecodingFunction( 'envMapTexelToLinear', parameters.envMapEncoding ) : '',
+                       parameters.emissiveMap ? getTexelDecodingFunction( 'emissiveMapTexelToLinear', parameters.emissiveMapEncoding ) : '',
+                       parameters.lightMap ? getTexelDecodingFunction( 'lightMapTexelToLinear', parameters.lightMapEncoding ) : '',
+                       getTexelEncodingFunction( 'linearToOutputTexel', parameters.outputEncoding ),
+
+                       parameters.depthPacking ? '#define DEPTH_PACKING ' + parameters.depthPacking : '',
+
+                       '\n'
+
+               ].filter( filterEmptyLine ).join( '\n' );
+
+       }
+
+       vertexShader = resolveIncludes( vertexShader );
+       vertexShader = replaceLightNums( vertexShader, parameters );
+       vertexShader = replaceClippingPlaneNums( vertexShader, parameters );
+
+       fragmentShader = resolveIncludes( fragmentShader );
+       fragmentShader = replaceLightNums( fragmentShader, parameters );
+       fragmentShader = replaceClippingPlaneNums( fragmentShader, parameters );
+
+       vertexShader = unrollLoops( vertexShader );
+       fragmentShader = unrollLoops( fragmentShader );
+
+       if ( parameters.isWebGL2 && parameters.isRawShaderMaterial !== true ) {
+
+               // GLSL 3.0 conversion for built-in materials and ShaderMaterial
+
+               versionString = '#version 300 es\n';
+
+               prefixVertex = [
+                       '#define attribute in',
+                       '#define varying out',
+                       '#define texture2D texture'
+               ].join( '\n' ) + '\n' + prefixVertex;
+
+               prefixFragment = [
+                       '#define varying in',
+                       ( parameters.glslVersion === GLSL3 ) ? '' : 'out highp vec4 pc_fragColor;',
+                       ( parameters.glslVersion === GLSL3 ) ? '' : '#define gl_FragColor pc_fragColor',
+                       '#define gl_FragDepthEXT gl_FragDepth',
+                       '#define texture2D texture',
+                       '#define textureCube texture',
+                       '#define texture2DProj textureProj',
+                       '#define texture2DLodEXT textureLod',
+                       '#define texture2DProjLodEXT textureProjLod',
+                       '#define textureCubeLodEXT textureLod',
+                       '#define texture2DGradEXT textureGrad',
+                       '#define texture2DProjGradEXT textureProjGrad',
+                       '#define textureCubeGradEXT textureGrad'
+               ].join( '\n' ) + '\n' + prefixFragment;
+
+       }
+
+       const vertexGlsl = versionString + prefixVertex + vertexShader;
+       const fragmentGlsl = versionString + prefixFragment + fragmentShader;
+
+       // console.log( '*VERTEX*', vertexGlsl );
+       // console.log( '*FRAGMENT*', fragmentGlsl );
+
+       const glVertexShader = WebGLShader( gl, 35633, vertexGlsl );
+       const glFragmentShader = WebGLShader( gl, 35632, fragmentGlsl );
+
+       gl.attachShader( program, glVertexShader );
+       gl.attachShader( program, glFragmentShader );
+
+       // Force a particular attribute to index 0.
+
+       if ( parameters.index0AttributeName !== undefined ) {
+
+               gl.bindAttribLocation( program, 0, parameters.index0AttributeName );
+
+       } else if ( parameters.morphTargets === true ) {
+
+               // programs with morphTargets displace position out of attribute 0
+               gl.bindAttribLocation( program, 0, 'position' );
+
+       }
+
+       gl.linkProgram( program );
+
+       // check for link errors
+       if ( renderer.debug.checkShaderErrors ) {
+
+               const programLog = gl.getProgramInfoLog( program ).trim();
+               const vertexLog = gl.getShaderInfoLog( glVertexShader ).trim();
+               const fragmentLog = gl.getShaderInfoLog( glFragmentShader ).trim();
+
+               let runnable = true;
+               let haveDiagnostics = true;
+
+               if ( gl.getProgramParameter( program, 35714 ) === false ) {
+
+                       runnable = false;
+
+                       const vertexErrors = getShaderErrors( gl, glVertexShader, 'vertex' );
+                       const fragmentErrors = getShaderErrors( gl, glFragmentShader, 'fragment' );
+
+                       console.error( 'THREE.WebGLProgram: shader error: ', gl.getError(), '35715', gl.getProgramParameter( program, 35715 ), 'gl.getProgramInfoLog', programLog, vertexErrors, fragmentErrors );
+
+               } else if ( programLog !== '' ) {
+
+                       console.warn( 'THREE.WebGLProgram: gl.getProgramInfoLog()', programLog );
+
+               } else if ( vertexLog === '' || fragmentLog === '' ) {
+
+                       haveDiagnostics = false;
+
+               }
+
+               if ( haveDiagnostics ) {
+
+                       this.diagnostics = {
+
+                               runnable: runnable,
+
+                               programLog: programLog,
+
+                               vertexShader: {
+
+                                       log: vertexLog,
+                                       prefix: prefixVertex
+
+                               },
+
+                               fragmentShader: {
+
+                                       log: fragmentLog,
+                                       prefix: prefixFragment
+
+                               }
+
+                       };
+
+               }
+
+       }
+
+       // Clean up
+
+       // Crashes in iOS9 and iOS10. #18402
+       // gl.detachShader( program, glVertexShader );
+       // gl.detachShader( program, glFragmentShader );
+
+       gl.deleteShader( glVertexShader );
+       gl.deleteShader( glFragmentShader );
+
+       // set up caching for uniform locations
+
+       let cachedUniforms;
+
+       this.getUniforms = function () {
+
+               if ( cachedUniforms === undefined ) {
+
+                       cachedUniforms = new WebGLUniforms( gl, program );
+
+               }
+
+               return cachedUniforms;
+
+       };
+
+       // set up caching for attribute locations
+
+       let cachedAttributes;
+
+       this.getAttributes = function () {
+
+               if ( cachedAttributes === undefined ) {
+
+                       cachedAttributes = fetchAttributeLocations( gl, program );
+
+               }
+
+               return cachedAttributes;
+
+       };
+
+       // free resource
+
+       this.destroy = function () {
+
+               bindingStates.releaseStatesOfProgram( this );
+
+               gl.deleteProgram( program );
+               this.program = undefined;
+
+       };
+
+       //
+
+       this.name = parameters.shaderName;
+       this.id = programIdCount ++;
+       this.cacheKey = cacheKey;
+       this.usedTimes = 1;
+       this.program = program;
+       this.vertexShader = glVertexShader;
+       this.fragmentShader = glFragmentShader;
+
+       return this;
+
+}
+
+function WebGLPrograms( renderer, cubemaps, extensions, capabilities, bindingStates, clipping ) {
+
+       const programs = [];
+
+       const isWebGL2 = capabilities.isWebGL2;
+       const logarithmicDepthBuffer = capabilities.logarithmicDepthBuffer;
+       const floatVertexTextures = capabilities.floatVertexTextures;
+       const maxVertexUniforms = capabilities.maxVertexUniforms;
+       const vertexTextures = capabilities.vertexTextures;
+
+       let precision = capabilities.precision;
+
+       const shaderIDs = {
+               MeshDepthMaterial: 'depth',
+               MeshDistanceMaterial: 'distanceRGBA',
+               MeshNormalMaterial: 'normal',
+               MeshBasicMaterial: 'basic',
+               MeshLambertMaterial: 'lambert',
+               MeshPhongMaterial: 'phong',
+               MeshToonMaterial: 'toon',
+               MeshStandardMaterial: 'physical',
+               MeshPhysicalMaterial: 'physical',
+               MeshMatcapMaterial: 'matcap',
+               LineBasicMaterial: 'basic',
+               LineDashedMaterial: 'dashed',
+               PointsMaterial: 'points',
+               ShadowMaterial: 'shadow',
+               SpriteMaterial: 'sprite'
+       };
+
+       const parameterNames = [
+               'precision', 'isWebGL2', 'supportsVertexTextures', 'outputEncoding', 'instancing', 'instancingColor',
+               'map', 'mapEncoding', 'matcap', 'matcapEncoding', 'envMap', 'envMapMode', 'envMapEncoding', 'envMapCubeUV',
+               'lightMap', 'lightMapEncoding', 'aoMap', 'emissiveMap', 'emissiveMapEncoding', 'bumpMap', 'normalMap', 'objectSpaceNormalMap', 'tangentSpaceNormalMap', 'clearcoatMap', 'clearcoatRoughnessMap', 'clearcoatNormalMap', 'displacementMap', 'specularMap',
+               'roughnessMap', 'metalnessMap', 'gradientMap',
+               'alphaMap', 'combine', 'vertexColors', 'vertexTangents', 'vertexUvs', 'uvsVertexOnly', 'fog', 'useFog', 'fogExp2',
+               'flatShading', 'sizeAttenuation', 'logarithmicDepthBuffer', 'skinning',
+               'maxBones', 'useVertexTexture', 'morphTargets', 'morphNormals',
+               'maxMorphTargets', 'maxMorphNormals', 'premultipliedAlpha',
+               'numDirLights', 'numPointLights', 'numSpotLights', 'numHemiLights', 'numRectAreaLights',
+               'numDirLightShadows', 'numPointLightShadows', 'numSpotLightShadows',
+               'shadowMapEnabled', 'shadowMapType', 'toneMapping', 'physicallyCorrectLights',
+               'alphaTest', 'doubleSided', 'flipSided', 'numClippingPlanes', 'numClipIntersection', 'depthPacking', 'dithering',
+               'sheen', 'transmissionMap'
+       ];
+
+       function getMaxBones( object ) {
+
+               const skeleton = object.skeleton;
+               const bones = skeleton.bones;
+
+               if ( floatVertexTextures ) {
+
+                       return 1024;
+
+               } else {
+
+                       // default for when object is not specified
+                       // ( for example when prebuilding shader to be used with multiple objects )
+                       //
+                       //  - leave some extra space for other uniforms
+                       //  - limit here is ANGLE's 254 max uniform vectors
+                       //    (up to 54 should be safe)
+
+                       const nVertexUniforms = maxVertexUniforms;
+                       const nVertexMatrices = Math.floor( ( nVertexUniforms - 20 ) / 4 );
+
+                       const maxBones = Math.min( nVertexMatrices, bones.length );
+
+                       if ( maxBones < bones.length ) {
+
+                               console.warn( 'THREE.WebGLRenderer: Skeleton has ' + bones.length + ' bones. This GPU supports ' + maxBones + '.' );
+                               return 0;
+
+                       }
+
+                       return maxBones;
+
+               }
+
+       }
+
+       function getTextureEncodingFromMap( map ) {
+
+               let encoding;
+
+               if ( map && map.isTexture ) {
+
+                       encoding = map.encoding;
+
+               } else if ( map && map.isWebGLRenderTarget ) {
+
+                       console.warn( 'THREE.WebGLPrograms.getTextureEncodingFromMap: don\'t use render targets as textures. Use their .texture property instead.' );
+                       encoding = map.texture.encoding;
+
+               } else {
+
+                       encoding = LinearEncoding;
+
+               }
+
+               return encoding;
+
+       }
+
+       function getParameters( material, lights, shadows, scene, object ) {
+
+               const fog = scene.fog;
+               const environment = material.isMeshStandardMaterial ? scene.environment : null;
+
+               const envMap = cubemaps.get( material.envMap || environment );
+
+               const shaderID = shaderIDs[ material.type ];
+
+               // heuristics to create shader parameters according to lights in the scene
+               // (not to blow over maxLights budget)
+
+               const maxBones = object.isSkinnedMesh ? getMaxBones( object ) : 0;
+
+               if ( material.precision !== null ) {
+
+                       precision = capabilities.getMaxPrecision( material.precision );
+
+                       if ( precision !== material.precision ) {
+
+                               console.warn( 'THREE.WebGLProgram.getParameters:', material.precision, 'not supported, using', precision, 'instead.' );
+
+                       }
+
+               }
+
+               let vertexShader, fragmentShader;
+
+               if ( shaderID ) {
+
+                       const shader = ShaderLib[ shaderID ];
+
+                       vertexShader = shader.vertexShader;
+                       fragmentShader = shader.fragmentShader;
+
+               } else {
+
+                       vertexShader = material.vertexShader;
+                       fragmentShader = material.fragmentShader;
+
+               }
+
+               const currentRenderTarget = renderer.getRenderTarget();
+
+               const parameters = {
+
+                       isWebGL2: isWebGL2,
+
+                       shaderID: shaderID,
+                       shaderName: material.type,
+
+                       vertexShader: vertexShader,
+                       fragmentShader: fragmentShader,
+                       defines: material.defines,
+
+                       isRawShaderMaterial: material.isRawShaderMaterial === true,
+                       glslVersion: material.glslVersion,
+
+                       precision: precision,
+
+                       instancing: object.isInstancedMesh === true,
+                       instancingColor: object.isInstancedMesh === true && object.instanceColor !== null,
+
+                       supportsVertexTextures: vertexTextures,
+                       outputEncoding: ( currentRenderTarget !== null ) ? getTextureEncodingFromMap( currentRenderTarget.texture ) : renderer.outputEncoding,
+                       map: !! material.map,
+                       mapEncoding: getTextureEncodingFromMap( material.map ),
+                       matcap: !! material.matcap,
+                       matcapEncoding: getTextureEncodingFromMap( material.matcap ),
+                       envMap: !! envMap,
+                       envMapMode: envMap && envMap.mapping,
+                       envMapEncoding: getTextureEncodingFromMap( envMap ),
+                       envMapCubeUV: ( !! envMap ) && ( ( envMap.mapping === CubeUVReflectionMapping ) || ( envMap.mapping === CubeUVRefractionMapping ) ),
+                       lightMap: !! material.lightMap,
+                       lightMapEncoding: getTextureEncodingFromMap( material.lightMap ),
+                       aoMap: !! material.aoMap,
+                       emissiveMap: !! material.emissiveMap,
+                       emissiveMapEncoding: getTextureEncodingFromMap( material.emissiveMap ),
+                       bumpMap: !! material.bumpMap,
+                       normalMap: !! material.normalMap,
+                       objectSpaceNormalMap: material.normalMapType === ObjectSpaceNormalMap,
+                       tangentSpaceNormalMap: material.normalMapType === TangentSpaceNormalMap,
+                       clearcoatMap: !! material.clearcoatMap,
+                       clearcoatRoughnessMap: !! material.clearcoatRoughnessMap,
+                       clearcoatNormalMap: !! material.clearcoatNormalMap,
+                       displacementMap: !! material.displacementMap,
+                       roughnessMap: !! material.roughnessMap,
+                       metalnessMap: !! material.metalnessMap,
+                       specularMap: !! material.specularMap,
+                       alphaMap: !! material.alphaMap,
+
+                       gradientMap: !! material.gradientMap,
+
+                       sheen: !! material.sheen,
+
+                       transmissionMap: !! material.transmissionMap,
+
+                       combine: material.combine,
+
+                       vertexTangents: ( material.normalMap && material.vertexTangents ),
+                       vertexColors: material.vertexColors,
+                       vertexUvs: !! material.map || !! material.bumpMap || !! material.normalMap || !! material.specularMap || !! material.alphaMap || !! material.emissiveMap || !! material.roughnessMap || !! material.metalnessMap || !! material.clearcoatMap || !! material.clearcoatRoughnessMap || !! material.clearcoatNormalMap || !! material.displacementMap || !! material.transmissionMap,
+                       uvsVertexOnly: ! ( !! material.map || !! material.bumpMap || !! material.normalMap || !! material.specularMap || !! material.alphaMap || !! material.emissiveMap || !! material.roughnessMap || !! material.metalnessMap || !! material.clearcoatNormalMap || !! material.transmissionMap ) && !! material.displacementMap,
+
+                       fog: !! fog,
+                       useFog: material.fog,
+                       fogExp2: ( fog && fog.isFogExp2 ),
+
+                       flatShading: material.flatShading,
+
+                       sizeAttenuation: material.sizeAttenuation,
+                       logarithmicDepthBuffer: logarithmicDepthBuffer,
+
+                       skinning: material.skinning && maxBones > 0,
+                       maxBones: maxBones,
+                       useVertexTexture: floatVertexTextures,
+
+                       morphTargets: material.morphTargets,
+                       morphNormals: material.morphNormals,
+                       maxMorphTargets: renderer.maxMorphTargets,
+                       maxMorphNormals: renderer.maxMorphNormals,
+
+                       numDirLights: lights.directional.length,
+                       numPointLights: lights.point.length,
+                       numSpotLights: lights.spot.length,
+                       numRectAreaLights: lights.rectArea.length,
+                       numHemiLights: lights.hemi.length,
+
+                       numDirLightShadows: lights.directionalShadowMap.length,
+                       numPointLightShadows: lights.pointShadowMap.length,
+                       numSpotLightShadows: lights.spotShadowMap.length,
+
+                       numClippingPlanes: clipping.numPlanes,
+                       numClipIntersection: clipping.numIntersection,
+
+                       dithering: material.dithering,
+
+                       shadowMapEnabled: renderer.shadowMap.enabled && shadows.length > 0,
+                       shadowMapType: renderer.shadowMap.type,
+
+                       toneMapping: material.toneMapped ? renderer.toneMapping : NoToneMapping,
+                       physicallyCorrectLights: renderer.physicallyCorrectLights,
+
+                       premultipliedAlpha: material.premultipliedAlpha,
+
+                       alphaTest: material.alphaTest,
+                       doubleSided: material.side === DoubleSide,
+                       flipSided: material.side === BackSide,
+
+                       depthPacking: ( material.depthPacking !== undefined ) ? material.depthPacking : false,
+
+                       index0AttributeName: material.index0AttributeName,
+
+                       extensionDerivatives: material.extensions && material.extensions.derivatives,
+                       extensionFragDepth: material.extensions && material.extensions.fragDepth,
+                       extensionDrawBuffers: material.extensions && material.extensions.drawBuffers,
+                       extensionShaderTextureLOD: material.extensions && material.extensions.shaderTextureLOD,
+
+                       rendererExtensionFragDepth: isWebGL2 || extensions.has( 'EXT_frag_depth' ),
+                       rendererExtensionDrawBuffers: isWebGL2 || extensions.has( 'WEBGL_draw_buffers' ),
+                       rendererExtensionShaderTextureLod: isWebGL2 || extensions.has( 'EXT_shader_texture_lod' ),
+
+                       customProgramCacheKey: material.customProgramCacheKey()
+
+               };
+
+               return parameters;
+
+       }
+
+       function getProgramCacheKey( parameters ) {
+
+               const array = [];
+
+               if ( parameters.shaderID ) {
+
+                       array.push( parameters.shaderID );
+
+               } else {
+
+                       array.push( parameters.fragmentShader );
+                       array.push( parameters.vertexShader );
+
+               }
+
+               if ( parameters.defines !== undefined ) {
+
+                       for ( const name in parameters.defines ) {
+
+                               array.push( name );
+                               array.push( parameters.defines[ name ] );
+
+                       }
+
+               }
+
+               if ( parameters.isRawShaderMaterial === false ) {
+
+                       for ( let i = 0; i < parameterNames.length; i ++ ) {
+
+                               array.push( parameters[ parameterNames[ i ] ] );
+
+                       }
+
+                       array.push( renderer.outputEncoding );
+                       array.push( renderer.gammaFactor );
+
+               }
+
+               array.push( parameters.customProgramCacheKey );
+
+               return array.join();
+
+       }
+
+       function getUniforms( material ) {
+
+               const shaderID = shaderIDs[ material.type ];
+               let uniforms;
+
+               if ( shaderID ) {
+
+                       const shader = ShaderLib[ shaderID ];
+                       uniforms = UniformsUtils.clone( shader.uniforms );
+
+               } else {
+
+                       uniforms = material.uniforms;
+
+               }
+
+               return uniforms;
+
+       }
+
+       function acquireProgram( parameters, cacheKey ) {
+
+               let program;
+
+               // Check if code has been already compiled
+               for ( let p = 0, pl = programs.length; p < pl; p ++ ) {
+
+                       const preexistingProgram = programs[ p ];
+
+                       if ( preexistingProgram.cacheKey === cacheKey ) {
+
+                               program = preexistingProgram;
+                               ++ program.usedTimes;
+
+                               break;
+
+                       }
+
+               }
+
+               if ( program === undefined ) {
+
+                       program = new WebGLProgram( renderer, cacheKey, parameters, bindingStates );
+                       programs.push( program );
+
+               }
+
+               return program;
+
+       }
+
+       function releaseProgram( program ) {
+
+               if ( -- program.usedTimes === 0 ) {
+
+                       // Remove from unordered set
+                       const i = programs.indexOf( program );
+                       programs[ i ] = programs[ programs.length - 1 ];
+                       programs.pop();
+
+                       // Free WebGL resources
+                       program.destroy();
+
+               }
+
+       }
+
+       return {
+               getParameters: getParameters,
+               getProgramCacheKey: getProgramCacheKey,
+               getUniforms: getUniforms,
+               acquireProgram: acquireProgram,
+               releaseProgram: releaseProgram,
+               // Exposed for resource monitoring & error feedback via renderer.info:
+               programs: programs
+       };
+
+}
+
+function WebGLProperties() {
+
+       let properties = new WeakMap();
+
+       function get( object ) {
+
+               let map = properties.get( object );
+
+               if ( map === undefined ) {
+
+                       map = {};
+                       properties.set( object, map );
+
+               }
+
+               return map;
+
+       }
+
+       function remove( object ) {
+
+               properties.delete( object );
+
+       }
+
+       function update( object, key, value ) {
+
+               properties.get( object )[ key ] = value;
+
+       }
+
+       function dispose() {
+
+               properties = new WeakMap();
+
+       }
+
+       return {
+               get: get,
+               remove: remove,
+               update: update,
+               dispose: dispose
+       };
+
+}
+
+function painterSortStable( a, b ) {
+
+       if ( a.groupOrder !== b.groupOrder ) {
+
+               return a.groupOrder - b.groupOrder;
+
+       } else if ( a.renderOrder !== b.renderOrder ) {
+
+               return a.renderOrder - b.renderOrder;
+
+       } else if ( a.program !== b.program ) {
+
+               return a.program.id - b.program.id;
+
+       } else if ( a.material.id !== b.material.id ) {
+
+               return a.material.id - b.material.id;
+
+       } else if ( a.z !== b.z ) {
+
+               return a.z - b.z;
+
+       } else {
+
+               return a.id - b.id;
+
+       }
+
+}
+
+function reversePainterSortStable( a, b ) {
+
+       if ( a.groupOrder !== b.groupOrder ) {
+
+               return a.groupOrder - b.groupOrder;
+
+       } else if ( a.renderOrder !== b.renderOrder ) {
+
+               return a.renderOrder - b.renderOrder;
+
+       } else if ( a.z !== b.z ) {
+
+               return b.z - a.z;
+
+       } else {
+
+               return a.id - b.id;
+
+       }
+
+}
+
+
+function WebGLRenderList( properties ) {
+
+       const renderItems = [];
+       let renderItemsIndex = 0;
+
+       const opaque = [];
+       const transparent = [];
+
+       const defaultProgram = { id: - 1 };
+
+       function init() {
+
+               renderItemsIndex = 0;
+
+               opaque.length = 0;
+               transparent.length = 0;
+
+       }
+
+       function getNextRenderItem( object, geometry, material, groupOrder, z, group ) {
+
+               let renderItem = renderItems[ renderItemsIndex ];
+               const materialProperties = properties.get( material );
+
+               if ( renderItem === undefined ) {
+
+                       renderItem = {
+                               id: object.id,
+                               object: object,
+                               geometry: geometry,
+                               material: material,
+                               program: materialProperties.program || defaultProgram,
+                               groupOrder: groupOrder,
+                               renderOrder: object.renderOrder,
+                               z: z,
+                               group: group
+                       };
+
+                       renderItems[ renderItemsIndex ] = renderItem;
+
+               } else {
+
+                       renderItem.id = object.id;
+                       renderItem.object = object;
+                       renderItem.geometry = geometry;
+                       renderItem.material = material;
+                       renderItem.program = materialProperties.program || defaultProgram;
+                       renderItem.groupOrder = groupOrder;
+                       renderItem.renderOrder = object.renderOrder;
+                       renderItem.z = z;
+                       renderItem.group = group;
+
+               }
+
+               renderItemsIndex ++;
+
+               return renderItem;
+
+       }
+
+       function push( object, geometry, material, groupOrder, z, group ) {
+
+               const renderItem = getNextRenderItem( object, geometry, material, groupOrder, z, group );
+
+               ( material.transparent === true ? transparent : opaque ).push( renderItem );
+
+       }
+
+       function unshift( object, geometry, material, groupOrder, z, group ) {
+
+               const renderItem = getNextRenderItem( object, geometry, material, groupOrder, z, group );
+
+               ( material.transparent === true ? transparent : opaque ).unshift( renderItem );
+
+       }
+
+       function sort( customOpaqueSort, customTransparentSort ) {
+
+               if ( opaque.length > 1 ) opaque.sort( customOpaqueSort || painterSortStable );
+               if ( transparent.length > 1 ) transparent.sort( customTransparentSort || reversePainterSortStable );
+
+       }
+
+       function finish() {
+
+               // Clear references from inactive renderItems in the list
+
+               for ( let i = renderItemsIndex, il = renderItems.length; i < il; i ++ ) {
+
+                       const renderItem = renderItems[ i ];
+
+                       if ( renderItem.id === null ) break;
+
+                       renderItem.id = null;
+                       renderItem.object = null;
+                       renderItem.geometry = null;
+                       renderItem.material = null;
+                       renderItem.program = null;
+                       renderItem.group = null;
+
+               }
+
+       }
+
+       return {
+
+               opaque: opaque,
+               transparent: transparent,
+
+               init: init,
+               push: push,
+               unshift: unshift,
+               finish: finish,
+
+               sort: sort
+       };
+
+}
+
+function WebGLRenderLists( properties ) {
+
+       let lists = new WeakMap();
+
+       function get( scene, camera ) {
+
+               const cameras = lists.get( scene );
+               let list;
+
+               if ( cameras === undefined ) {
+
+                       list = new WebGLRenderList( properties );
+                       lists.set( scene, new WeakMap() );
+                       lists.get( scene ).set( camera, list );
+
+               } else {
+
+                       list = cameras.get( camera );
+                       if ( list === undefined ) {
+
+                               list = new WebGLRenderList( properties );
+                               cameras.set( camera, list );
+
+                       }
+
+               }
+
+               return list;
+
+       }
+
+       function dispose() {
+
+               lists = new WeakMap();
+
+       }
+
+       return {
+               get: get,
+               dispose: dispose
+       };
+
+}
+
+function UniformsCache() {
+
+       const lights = {};
+
+       return {
+
+               get: function ( light ) {
+
+                       if ( lights[ light.id ] !== undefined ) {
+
+                               return lights[ light.id ];
+
+                       }
+
+                       let uniforms;
+
+                       switch ( light.type ) {
+
+                               case 'DirectionalLight':
+                                       uniforms = {
+                                               direction: new Vector3(),
+                                               color: new Color()
+                                       };
+                                       break;
+
+                               case 'SpotLight':
+                                       uniforms = {
+                                               position: new Vector3(),
+                                               direction: new Vector3(),
+                                               color: new Color(),
+                                               distance: 0,
+                                               coneCos: 0,
+                                               penumbraCos: 0,
+                                               decay: 0
+                                       };
+                                       break;
+
+                               case 'PointLight':
+                                       uniforms = {
+                                               position: new Vector3(),
+                                               color: new Color(),
+                                               distance: 0,
+                                               decay: 0
+                                       };
+                                       break;
+
+                               case 'HemisphereLight':
+                                       uniforms = {
+                                               direction: new Vector3(),
+                                               skyColor: new Color(),
+                                               groundColor: new Color()
+                                       };
+                                       break;
+
+                               case 'RectAreaLight':
+                                       uniforms = {
+                                               color: new Color(),
+                                               position: new Vector3(),
+                                               halfWidth: new Vector3(),
+                                               halfHeight: new Vector3()
+                                       };
+                                       break;
+
+                       }
+
+                       lights[ light.id ] = uniforms;
+
+                       return uniforms;
+
+               }
+
+       };
+
+}
+
+function ShadowUniformsCache() {
+
+       const lights = {};
+
+       return {
+
+               get: function ( light ) {
+
+                       if ( lights[ light.id ] !== undefined ) {
+
+                               return lights[ light.id ];
+
+                       }
+
+                       let uniforms;
+
+                       switch ( light.type ) {
+
+                               case 'DirectionalLight':
+                                       uniforms = {
+                                               shadowBias: 0,
+                                               shadowNormalBias: 0,
+                                               shadowRadius: 1,
+                                               shadowMapSize: new Vector2()
+                                       };
+                                       break;
+
+                               case 'SpotLight':
+                                       uniforms = {
+                                               shadowBias: 0,
+                                               shadowNormalBias: 0,
+                                               shadowRadius: 1,
+                                               shadowMapSize: new Vector2()
+                                       };
+                                       break;
+
+                               case 'PointLight':
+                                       uniforms = {
+                                               shadowBias: 0,
+                                               shadowNormalBias: 0,
+                                               shadowRadius: 1,
+                                               shadowMapSize: new Vector2(),
+                                               shadowCameraNear: 1,
+                                               shadowCameraFar: 1000
+                                       };
+                                       break;
+
+                               // TODO (abelnation): set RectAreaLight shadow uniforms
+
+                       }
+
+                       lights[ light.id ] = uniforms;
+
+                       return uniforms;
+
+               }
+
+       };
+
+}
+
+
+
+let nextVersion = 0;
+
+function shadowCastingLightsFirst( lightA, lightB ) {
+
+       return ( lightB.castShadow ? 1 : 0 ) - ( lightA.castShadow ? 1 : 0 );
+
+}
+
+function WebGLLights( extensions, capabilities ) {
+
+       const cache = new UniformsCache();
+
+       const shadowCache = ShadowUniformsCache();
+
+       const state = {
+
+               version: 0,
+
+               hash: {
+                       directionalLength: - 1,
+                       pointLength: - 1,
+                       spotLength: - 1,
+                       rectAreaLength: - 1,
+                       hemiLength: - 1,
+
+                       numDirectionalShadows: - 1,
+                       numPointShadows: - 1,
+                       numSpotShadows: - 1
+               },
+
+               ambient: [ 0, 0, 0 ],
+               probe: [],
+               directional: [],
+               directionalShadow: [],
+               directionalShadowMap: [],
+               directionalShadowMatrix: [],
+               spot: [],
+               spotShadow: [],
+               spotShadowMap: [],
+               spotShadowMatrix: [],
+               rectArea: [],
+               rectAreaLTC1: null,
+               rectAreaLTC2: null,
+               point: [],
+               pointShadow: [],
+               pointShadowMap: [],
+               pointShadowMatrix: [],
+               hemi: []
+
+       };
+
+       for ( let i = 0; i < 9; i ++ ) state.probe.push( new Vector3() );
+
+       const vector3 = new Vector3();
+       const matrix4 = new Matrix4();
+       const matrix42 = new Matrix4();
+
+       function setup( lights ) {
+
+               let r = 0, g = 0, b = 0;
+
+               for ( let i = 0; i < 9; i ++ ) state.probe[ i ].set( 0, 0, 0 );
+
+               let directionalLength = 0;
+               let pointLength = 0;
+               let spotLength = 0;
+               let rectAreaLength = 0;
+               let hemiLength = 0;
+
+               let numDirectionalShadows = 0;
+               let numPointShadows = 0;
+               let numSpotShadows = 0;
+
+               lights.sort( shadowCastingLightsFirst );
+
+               for ( let i = 0, l = lights.length; i < l; i ++ ) {
+
+                       const light = lights[ i ];
+
+                       const color = light.color;
+                       const intensity = light.intensity;
+                       const distance = light.distance;
+
+                       const shadowMap = ( light.shadow && light.shadow.map ) ? light.shadow.map.texture : null;
+
+                       if ( light.isAmbientLight ) {
+
+                               r += color.r * intensity;
+                               g += color.g * intensity;
+                               b += color.b * intensity;
+
+                       } else if ( light.isLightProbe ) {
+
+                               for ( let j = 0; j < 9; j ++ ) {
+
+                                       state.probe[ j ].addScaledVector( light.sh.coefficients[ j ], intensity );
+
+                               }
+
+                       } else if ( light.isDirectionalLight ) {
+
+                               const uniforms = cache.get( light );
+
+                               uniforms.color.copy( light.color ).multiplyScalar( light.intensity );
+
+                               if ( light.castShadow ) {
+
+                                       const shadow = light.shadow;
+
+                                       const shadowUniforms = shadowCache.get( light );
+
+                                       shadowUniforms.shadowBias = shadow.bias;
+                                       shadowUniforms.shadowNormalBias = shadow.normalBias;
+                                       shadowUniforms.shadowRadius = shadow.radius;
+                                       shadowUniforms.shadowMapSize = shadow.mapSize;
+
+                                       state.directionalShadow[ directionalLength ] = shadowUniforms;
+                                       state.directionalShadowMap[ directionalLength ] = shadowMap;
+                                       state.directionalShadowMatrix[ directionalLength ] = light.shadow.matrix;
+
+                                       numDirectionalShadows ++;
+
+                               }
+
+                               state.directional[ directionalLength ] = uniforms;
+
+                               directionalLength ++;
+
+                       } else if ( light.isSpotLight ) {
+
+                               const uniforms = cache.get( light );
+
+                               uniforms.position.setFromMatrixPosition( light.matrixWorld );
+
+                               uniforms.color.copy( color ).multiplyScalar( intensity );
+                               uniforms.distance = distance;
+
+                               uniforms.coneCos = Math.cos( light.angle );
+                               uniforms.penumbraCos = Math.cos( light.angle * ( 1 - light.penumbra ) );
+                               uniforms.decay = light.decay;
+
+                               if ( light.castShadow ) {
+
+                                       const shadow = light.shadow;
+
+                                       const shadowUniforms = shadowCache.get( light );
+
+                                       shadowUniforms.shadowBias = shadow.bias;
+                                       shadowUniforms.shadowNormalBias = shadow.normalBias;
+                                       shadowUniforms.shadowRadius = shadow.radius;
+                                       shadowUniforms.shadowMapSize = shadow.mapSize;
+
+                                       state.spotShadow[ spotLength ] = shadowUniforms;
+                                       state.spotShadowMap[ spotLength ] = shadowMap;
+                                       state.spotShadowMatrix[ spotLength ] = light.shadow.matrix;
+
+                                       numSpotShadows ++;
+
+                               }
+
+                               state.spot[ spotLength ] = uniforms;
+
+                               spotLength ++;
+
+                       } else if ( light.isRectAreaLight ) {
+
+                               const uniforms = cache.get( light );
+
+                               // (a) intensity is the total visible light emitted
+                               //uniforms.color.copy( color ).multiplyScalar( intensity / ( light.width * light.height * Math.PI ) );
+
+                               // (b) intensity is the brightness of the light
+                               uniforms.color.copy( color ).multiplyScalar( intensity );
+
+                               uniforms.halfWidth.set( light.width * 0.5, 0.0, 0.0 );
+                               uniforms.halfHeight.set( 0.0, light.height * 0.5, 0.0 );
+
+                               state.rectArea[ rectAreaLength ] = uniforms;
+
+                               rectAreaLength ++;
+
+                       } else if ( light.isPointLight ) {
+
+                               const uniforms = cache.get( light );
+
+                               uniforms.color.copy( light.color ).multiplyScalar( light.intensity );
+                               uniforms.distance = light.distance;
+                               uniforms.decay = light.decay;
+
+                               if ( light.castShadow ) {
+
+                                       const shadow = light.shadow;
+
+                                       const shadowUniforms = shadowCache.get( light );
+
+                                       shadowUniforms.shadowBias = shadow.bias;
+                                       shadowUniforms.shadowNormalBias = shadow.normalBias;
+                                       shadowUniforms.shadowRadius = shadow.radius;
+                                       shadowUniforms.shadowMapSize = shadow.mapSize;
+                                       shadowUniforms.shadowCameraNear = shadow.camera.near;
+                                       shadowUniforms.shadowCameraFar = shadow.camera.far;
+
+                                       state.pointShadow[ pointLength ] = shadowUniforms;
+                                       state.pointShadowMap[ pointLength ] = shadowMap;
+                                       state.pointShadowMatrix[ pointLength ] = light.shadow.matrix;
+
+                                       numPointShadows ++;
+
+                               }
+
+                               state.point[ pointLength ] = uniforms;
+
+                               pointLength ++;
+
+                       } else if ( light.isHemisphereLight ) {
+
+                               const uniforms = cache.get( light );
+
+                               uniforms.skyColor.copy( light.color ).multiplyScalar( intensity );
+                               uniforms.groundColor.copy( light.groundColor ).multiplyScalar( intensity );
+
+                               state.hemi[ hemiLength ] = uniforms;
+
+                               hemiLength ++;
+
+                       }
+
+               }
+
+               if ( rectAreaLength > 0 ) {
+
+                       if ( capabilities.isWebGL2 ) {
+
+                               // WebGL 2
+
+                               state.rectAreaLTC1 = UniformsLib.LTC_FLOAT_1;
+                               state.rectAreaLTC2 = UniformsLib.LTC_FLOAT_2;
+
+                       } else {
+
+                               // WebGL 1
+
+                               if ( extensions.has( 'OES_texture_float_linear' ) === true ) {
+
+                                       state.rectAreaLTC1 = UniformsLib.LTC_FLOAT_1;
+                                       state.rectAreaLTC2 = UniformsLib.LTC_FLOAT_2;
+
+                               } else if ( extensions.has( 'OES_texture_half_float_linear' ) === true ) {
+
+                                       state.rectAreaLTC1 = UniformsLib.LTC_HALF_1;
+                                       state.rectAreaLTC2 = UniformsLib.LTC_HALF_2;
+
+                               } else {
+
+                                       console.error( 'THREE.WebGLRenderer: Unable to use RectAreaLight. Missing WebGL extensions.' );
+
+                               }
+
+                       }
+
+               }
+
+               state.ambient[ 0 ] = r;
+               state.ambient[ 1 ] = g;
+               state.ambient[ 2 ] = b;
+
+               const hash = state.hash;
+
+               if ( hash.directionalLength !== directionalLength ||
+                       hash.pointLength !== pointLength ||
+                       hash.spotLength !== spotLength ||
+                       hash.rectAreaLength !== rectAreaLength ||
+                       hash.hemiLength !== hemiLength ||
+                       hash.numDirectionalShadows !== numDirectionalShadows ||
+                       hash.numPointShadows !== numPointShadows ||
+                       hash.numSpotShadows !== numSpotShadows ) {
+
+                       state.directional.length = directionalLength;
+                       state.spot.length = spotLength;
+                       state.rectArea.length = rectAreaLength;
+                       state.point.length = pointLength;
+                       state.hemi.length = hemiLength;
+
+                       state.directionalShadow.length = numDirectionalShadows;
+                       state.directionalShadowMap.length = numDirectionalShadows;
+                       state.pointShadow.length = numPointShadows;
+                       state.pointShadowMap.length = numPointShadows;
+                       state.spotShadow.length = numSpotShadows;
+                       state.spotShadowMap.length = numSpotShadows;
+                       state.directionalShadowMatrix.length = numDirectionalShadows;
+                       state.pointShadowMatrix.length = numPointShadows;
+                       state.spotShadowMatrix.length = numSpotShadows;
+
+                       hash.directionalLength = directionalLength;
+                       hash.pointLength = pointLength;
+                       hash.spotLength = spotLength;
+                       hash.rectAreaLength = rectAreaLength;
+                       hash.hemiLength = hemiLength;
+
+                       hash.numDirectionalShadows = numDirectionalShadows;
+                       hash.numPointShadows = numPointShadows;
+                       hash.numSpotShadows = numSpotShadows;
+
+                       state.version = nextVersion ++;
+
+               }
+
+       }
+
+       function setupView( lights, camera ) {
+
+               let directionalLength = 0;
+               let pointLength = 0;
+               let spotLength = 0;
+               let rectAreaLength = 0;
+               let hemiLength = 0;
+
+               const viewMatrix = camera.matrixWorldInverse;
+
+               for ( let i = 0, l = lights.length; i < l; i ++ ) {
+
+                       const light = lights[ i ];
+
+                       if ( light.isDirectionalLight ) {
+
+                               const uniforms = state.directional[ directionalLength ];
+
+                               uniforms.direction.setFromMatrixPosition( light.matrixWorld );
+                               vector3.setFromMatrixPosition( light.target.matrixWorld );
+                               uniforms.direction.sub( vector3 );
+                               uniforms.direction.transformDirection( viewMatrix );
+
+                               directionalLength ++;
+
+                       } else if ( light.isSpotLight ) {
+
+                               const uniforms = state.spot[ spotLength ];
+
+                               uniforms.position.setFromMatrixPosition( light.matrixWorld );
+                               uniforms.position.applyMatrix4( viewMatrix );
+
+                               uniforms.direction.setFromMatrixPosition( light.matrixWorld );
+                               vector3.setFromMatrixPosition( light.target.matrixWorld );
+                               uniforms.direction.sub( vector3 );
+                               uniforms.direction.transformDirection( viewMatrix );
+
+                               spotLength ++;
+
+                       } else if ( light.isRectAreaLight ) {
+
+                               const uniforms = state.rectArea[ rectAreaLength ];
+
+                               uniforms.position.setFromMatrixPosition( light.matrixWorld );
+                               uniforms.position.applyMatrix4( viewMatrix );
+
+                               // extract local rotation of light to derive width/height half vectors
+                               matrix42.identity();
+                               matrix4.copy( light.matrixWorld );
+                               matrix4.premultiply( viewMatrix );
+                               matrix42.extractRotation( matrix4 );
+
+                               uniforms.halfWidth.set( light.width * 0.5, 0.0, 0.0 );
+                               uniforms.halfHeight.set( 0.0, light.height * 0.5, 0.0 );
+
+                               uniforms.halfWidth.applyMatrix4( matrix42 );
+                               uniforms.halfHeight.applyMatrix4( matrix42 );
+
+                               rectAreaLength ++;
+
+                       } else if ( light.isPointLight ) {
+
+                               const uniforms = state.point[ pointLength ];
+
+                               uniforms.position.setFromMatrixPosition( light.matrixWorld );
+                               uniforms.position.applyMatrix4( viewMatrix );
+
+                               pointLength ++;
+
+                       } else if ( light.isHemisphereLight ) {
+
+                               const uniforms = state.hemi[ hemiLength ];
+
+                               uniforms.direction.setFromMatrixPosition( light.matrixWorld );
+                               uniforms.direction.transformDirection( viewMatrix );
+                               uniforms.direction.normalize();
+
+                               hemiLength ++;
+
+                       }
+
+               }
+
+       }
+
+       return {
+               setup: setup,
+               setupView: setupView,
+               state: state
+       };
+
+}
+
+function WebGLRenderState( extensions, capabilities ) {
+
+       const lights = new WebGLLights( extensions, capabilities );
+
+       const lightsArray = [];
+       const shadowsArray = [];
+
+       function init() {
+
+               lightsArray.length = 0;
+               shadowsArray.length = 0;
+
+       }
+
+       function pushLight( light ) {
+
+               lightsArray.push( light );
+
+       }
+
+       function pushShadow( shadowLight ) {
+
+               shadowsArray.push( shadowLight );
+
+       }
+
+       function setupLights() {
+
+               lights.setup( lightsArray );
+
+       }
+
+       function setupLightsView( camera ) {
+
+               lights.setupView( lightsArray, camera );
+
+       }
+
+       const state = {
+               lightsArray: lightsArray,
+               shadowsArray: shadowsArray,
+
+               lights: lights
+       };
+
+       return {
+               init: init,
+               state: state,
+               setupLights: setupLights,
+               setupLightsView: setupLightsView,
+
+               pushLight: pushLight,
+               pushShadow: pushShadow
+       };
+
+}
+
+function WebGLRenderStates( extensions, capabilities ) {
+
+       let renderStates = new WeakMap();
+
+       function get( scene, renderCallDepth = 0 ) {
+
+               let renderState;
+
+               if ( renderStates.has( scene ) === false ) {
+
+                       renderState = new WebGLRenderState( extensions, capabilities );
+                       renderStates.set( scene, [] );
+                       renderStates.get( scene ).push( renderState );
+
+               } else {
+
+                       if ( renderCallDepth >= renderStates.get( scene ).length ) {
+
+                               renderState = new WebGLRenderState( extensions, capabilities );
+                               renderStates.get( scene ).push( renderState );
+
+                       } else {
+
+                               renderState = renderStates.get( scene )[ renderCallDepth ];
+
+                       }
+
+               }
+
+               return renderState;
+
+       }
+
+       function dispose() {
+
+               renderStates = new WeakMap();
+
+       }
+
+       return {
+               get: get,
+               dispose: dispose
+       };
+
+}
+
+/**
+ * parameters = {
+ *
+ *  opacity: <float>,
+ *
+ *  map: new THREE.Texture( <Image> ),
+ *
+ *  alphaMap: new THREE.Texture( <Image> ),
+ *
+ *  displacementMap: new THREE.Texture( <Image> ),
+ *  displacementScale: <float>,
+ *  displacementBias: <float>,
+ *
+ *  wireframe: <boolean>,
+ *  wireframeLinewidth: <float>
+ * }
+ */
+
+function MeshDepthMaterial( parameters ) {
+
+       Material.call( this );
+
+       this.type = 'MeshDepthMaterial';
+
+       this.depthPacking = BasicDepthPacking;
+
+       this.skinning = false;
+       this.morphTargets = false;
+
+       this.map = null;
+
+       this.alphaMap = null;
+
+       this.displacementMap = null;
+       this.displacementScale = 1;
+       this.displacementBias = 0;
+
+       this.wireframe = false;
+       this.wireframeLinewidth = 1;
+
+       this.fog = false;
+
+       this.setValues( parameters );
+
+}
+
+MeshDepthMaterial.prototype = Object.create( Material.prototype );
+MeshDepthMaterial.prototype.constructor = MeshDepthMaterial;
+
+MeshDepthMaterial.prototype.isMeshDepthMaterial = true;
+
+MeshDepthMaterial.prototype.copy = function ( source ) {
+
+       Material.prototype.copy.call( this, source );
+
+       this.depthPacking = source.depthPacking;
+
+       this.skinning = source.skinning;
+       this.morphTargets = source.morphTargets;
+
+       this.map = source.map;
+
+       this.alphaMap = source.alphaMap;
+
+       this.displacementMap = source.displacementMap;
+       this.displacementScale = source.displacementScale;
+       this.displacementBias = source.displacementBias;
+
+       this.wireframe = source.wireframe;
+       this.wireframeLinewidth = source.wireframeLinewidth;
+
+       return this;
+
+};
+
+/**
+ * parameters = {
+ *
+ *  referencePosition: <float>,
+ *  nearDistance: <float>,
+ *  farDistance: <float>,
+ *
+ *  skinning: <bool>,
+ *  morphTargets: <bool>,
+ *
+ *  map: new THREE.Texture( <Image> ),
+ *
+ *  alphaMap: new THREE.Texture( <Image> ),
+ *
+ *  displacementMap: new THREE.Texture( <Image> ),
+ *  displacementScale: <float>,
+ *  displacementBias: <float>
+ *
+ * }
+ */
+
+function MeshDistanceMaterial( parameters ) {
+
+       Material.call( this );
+
+       this.type = 'MeshDistanceMaterial';
+
+       this.referencePosition = new Vector3();
+       this.nearDistance = 1;
+       this.farDistance = 1000;
+
+       this.skinning = false;
+       this.morphTargets = false;
+
+       this.map = null;
+
+       this.alphaMap = null;
+
+       this.displacementMap = null;
+       this.displacementScale = 1;
+       this.displacementBias = 0;
+
+       this.fog = false;
+
+       this.setValues( parameters );
+
+}
+
+MeshDistanceMaterial.prototype = Object.create( Material.prototype );
+MeshDistanceMaterial.prototype.constructor = MeshDistanceMaterial;
+
+MeshDistanceMaterial.prototype.isMeshDistanceMaterial = true;
+
+MeshDistanceMaterial.prototype.copy = function ( source ) {
+
+       Material.prototype.copy.call( this, source );
+
+       this.referencePosition.copy( source.referencePosition );
+       this.nearDistance = source.nearDistance;
+       this.farDistance = source.farDistance;
+
+       this.skinning = source.skinning;
+       this.morphTargets = source.morphTargets;
+
+       this.map = source.map;
+
+       this.alphaMap = source.alphaMap;
+
+       this.displacementMap = source.displacementMap;
+       this.displacementScale = source.displacementScale;
+       this.displacementBias = source.displacementBias;
+
+       return this;
+
+};
+
+var vsm_frag = "uniform sampler2D shadow_pass;\nuniform vec2 resolution;\nuniform float radius;\n#include <packing>\nvoid main() {\n\tfloat mean = 0.0;\n\tfloat squared_mean = 0.0;\n\tfloat depth = unpackRGBAToDepth( texture2D( shadow_pass, ( gl_FragCoord.xy ) / resolution ) );\n\tfor ( float i = -1.0; i < 1.0 ; i += SAMPLE_RATE) {\n\t\t#ifdef HORIZONTAL_PASS\n\t\t\tvec2 distribution = unpackRGBATo2Half( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( i, 0.0 ) * radius ) / resolution ) );\n\t\t\tmean += distribution.x;\n\t\t\tsquared_mean += distribution.y * distribution.y + distribution.x * distribution.x;\n\t\t#else\n\t\t\tfloat depth = unpackRGBAToDepth( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( 0.0, i ) * radius ) / resolution ) );\n\t\t\tmean += depth;\n\t\t\tsquared_mean += depth * depth;\n\t\t#endif\n\t}\n\tmean = mean * HALF_SAMPLE_RATE;\n\tsquared_mean = squared_mean * HALF_SAMPLE_RATE;\n\tfloat std_dev = sqrt( squared_mean - mean * mean );\n\tgl_FragColor = pack2HalfToRGBA( vec2( mean, std_dev ) );\n}";
+
+var vsm_vert = "void main() {\n\tgl_Position = vec4( position, 1.0 );\n}";
+
+function WebGLShadowMap( _renderer, _objects, maxTextureSize ) {
+
+       let _frustum = new Frustum();
+
+       const _shadowMapSize = new Vector2(),
+               _viewportSize = new Vector2(),
+
+               _viewport = new Vector4(),
+
+               _depthMaterials = [],
+               _distanceMaterials = [],
+
+               _materialCache = {};
+
+       const shadowSide = { 0: BackSide, 1: FrontSide, 2: DoubleSide };
+
+       const shadowMaterialVertical = new ShaderMaterial( {
+
+               defines: {
+                       SAMPLE_RATE: 2.0 / 8.0,
+                       HALF_SAMPLE_RATE: 1.0 / 8.0
+               },
+
+               uniforms: {
+                       shadow_pass: { value: null },
+                       resolution: { value: new Vector2() },
+                       radius: { value: 4.0 }
+               },
+
+               vertexShader: vsm_vert,
+
+               fragmentShader: vsm_frag
+
+       } );
+
+       const shadowMaterialHorizontal = shadowMaterialVertical.clone();
+       shadowMaterialHorizontal.defines.HORIZONTAL_PASS = 1;
+
+       const fullScreenTri = new BufferGeometry();
+       fullScreenTri.setAttribute(
+               'position',
+               new BufferAttribute(
+                       new Float32Array( [ - 1, - 1, 0.5, 3, - 1, 0.5, - 1, 3, 0.5 ] ),
+                       3
+               )
+       );
+
+       const fullScreenMesh = new Mesh( fullScreenTri, shadowMaterialVertical );
+
+       const scope = this;
+
+       this.enabled = false;
+
+       this.autoUpdate = true;
+       this.needsUpdate = false;
+
+       this.type = PCFShadowMap;
+
+       this.render = function ( lights, scene, camera ) {
+
+               if ( scope.enabled === false ) return;
+               if ( scope.autoUpdate === false && scope.needsUpdate === false ) return;
+
+               if ( lights.length === 0 ) return;
+
+               const currentRenderTarget = _renderer.getRenderTarget();
+               const activeCubeFace = _renderer.getActiveCubeFace();
+               const activeMipmapLevel = _renderer.getActiveMipmapLevel();
+
+               const _state = _renderer.state;
+
+               // Set GL state for depth map.
+               _state.setBlending( NoBlending );
+               _state.buffers.color.setClear( 1, 1, 1, 1 );
+               _state.buffers.depth.setTest( true );
+               _state.setScissorTest( false );
+
+               // render depth map
+
+               for ( let i = 0, il = lights.length; i < il; i ++ ) {
+
+                       const light = lights[ i ];
+                       const shadow = light.shadow;
+
+                       if ( shadow === undefined ) {
+
+                               console.warn( 'THREE.WebGLShadowMap:', light, 'has no shadow.' );
+                               continue;
+
+                       }
+
+                       if ( shadow.autoUpdate === false && shadow.needsUpdate === false ) continue;
+
+                       _shadowMapSize.copy( shadow.mapSize );
+
+                       const shadowFrameExtents = shadow.getFrameExtents();
+
+                       _shadowMapSize.multiply( shadowFrameExtents );
+
+                       _viewportSize.copy( shadow.mapSize );
+
+                       if ( _shadowMapSize.x > maxTextureSize || _shadowMapSize.y > maxTextureSize ) {
+
+                               if ( _shadowMapSize.x > maxTextureSize ) {
+
+                                       _viewportSize.x = Math.floor( maxTextureSize / shadowFrameExtents.x );
+                                       _shadowMapSize.x = _viewportSize.x * shadowFrameExtents.x;
+                                       shadow.mapSize.x = _viewportSize.x;
+
+                               }
+
+                               if ( _shadowMapSize.y > maxTextureSize ) {
+
+                                       _viewportSize.y = Math.floor( maxTextureSize / shadowFrameExtents.y );
+                                       _shadowMapSize.y = _viewportSize.y * shadowFrameExtents.y;
+                                       shadow.mapSize.y = _viewportSize.y;
+
+                               }
+
+                       }
+
+                       if ( shadow.map === null && ! shadow.isPointLightShadow && this.type === VSMShadowMap ) {
+
+                               const pars = { minFilter: LinearFilter, magFilter: LinearFilter, format: RGBAFormat };
+
+                               shadow.map = new WebGLRenderTarget( _shadowMapSize.x, _shadowMapSize.y, pars );
+                               shadow.map.texture.name = light.name + '.shadowMap';
+
+                               shadow.mapPass = new WebGLRenderTarget( _shadowMapSize.x, _shadowMapSize.y, pars );
+
+                               shadow.camera.updateProjectionMatrix();
+
+                       }
+
+                       if ( shadow.map === null ) {
+
+                               const pars = { minFilter: NearestFilter, magFilter: NearestFilter, format: RGBAFormat };
+
+                               shadow.map = new WebGLRenderTarget( _shadowMapSize.x, _shadowMapSize.y, pars );
+                               shadow.map.texture.name = light.name + '.shadowMap';
+
+                               shadow.camera.updateProjectionMatrix();
+
+                       }
+
+                       _renderer.setRenderTarget( shadow.map );
+                       _renderer.clear();
+
+                       const viewportCount = shadow.getViewportCount();
+
+                       for ( let vp = 0; vp < viewportCount; vp ++ ) {
+
+                               const viewport = shadow.getViewport( vp );
+
+                               _viewport.set(
+                                       _viewportSize.x * viewport.x,
+                                       _viewportSize.y * viewport.y,
+                                       _viewportSize.x * viewport.z,
+                                       _viewportSize.y * viewport.w
+                               );
+
+                               _state.viewport( _viewport );
+
+                               shadow.updateMatrices( light, vp );
+
+                               _frustum = shadow.getFrustum();
+
+                               renderObject( scene, camera, shadow.camera, light, this.type );
+
+                       }
+
+                       // do blur pass for VSM
+
+                       if ( ! shadow.isPointLightShadow && this.type === VSMShadowMap ) {
+
+                               VSMPass( shadow, camera );
+
+                       }
+
+                       shadow.needsUpdate = false;
+
+               }
+
+               scope.needsUpdate = false;
+
+               _renderer.setRenderTarget( currentRenderTarget, activeCubeFace, activeMipmapLevel );
+
+       };
+
+       function VSMPass( shadow, camera ) {
+
+               const geometry = _objects.update( fullScreenMesh );
+
+               // vertical pass
+
+               shadowMaterialVertical.uniforms.shadow_pass.value = shadow.map.texture;
+               shadowMaterialVertical.uniforms.resolution.value = shadow.mapSize;
+               shadowMaterialVertical.uniforms.radius.value = shadow.radius;
+               _renderer.setRenderTarget( shadow.mapPass );
+               _renderer.clear();
+               _renderer.renderBufferDirect( camera, null, geometry, shadowMaterialVertical, fullScreenMesh, null );
+
+               // horizontal pass
+
+               shadowMaterialHorizontal.uniforms.shadow_pass.value = shadow.mapPass.texture;
+               shadowMaterialHorizontal.uniforms.resolution.value = shadow.mapSize;
+               shadowMaterialHorizontal.uniforms.radius.value = shadow.radius;
+               _renderer.setRenderTarget( shadow.map );
+               _renderer.clear();
+               _renderer.renderBufferDirect( camera, null, geometry, shadowMaterialHorizontal, fullScreenMesh, null );
+
+       }
+
+       function getDepthMaterialVariant( useMorphing, useSkinning, useInstancing ) {
+
+               const index = useMorphing << 0 | useSkinning << 1 | useInstancing << 2;
+
+               let material = _depthMaterials[ index ];
+
+               if ( material === undefined ) {
+
+                       material = new MeshDepthMaterial( {
+
+                               depthPacking: RGBADepthPacking,
+
+                               morphTargets: useMorphing,
+                               skinning: useSkinning
+
+                       } );
+
+                       _depthMaterials[ index ] = material;
+
+               }
+
+               return material;
+
+       }
+
+       function getDistanceMaterialVariant( useMorphing, useSkinning, useInstancing ) {
+
+               const index = useMorphing << 0 | useSkinning << 1 | useInstancing << 2;
+
+               let material = _distanceMaterials[ index ];
+
+               if ( material === undefined ) {
+
+                       material = new MeshDistanceMaterial( {
+
+                               morphTargets: useMorphing,
+                               skinning: useSkinning
+
+                       } );
+
+                       _distanceMaterials[ index ] = material;
+
+               }
+
+               return material;
+
+       }
+
+       function getDepthMaterial( object, geometry, material, light, shadowCameraNear, shadowCameraFar, type ) {
+
+               let result = null;
+
+               let getMaterialVariant = getDepthMaterialVariant;
+               let customMaterial = object.customDepthMaterial;
+
+               if ( light.isPointLight === true ) {
+
+                       getMaterialVariant = getDistanceMaterialVariant;
+                       customMaterial = object.customDistanceMaterial;
+
+               }
+
+               if ( customMaterial === undefined ) {
+
+                       let useMorphing = false;
+
+                       if ( material.morphTargets === true ) {
+
+                               useMorphing = geometry.morphAttributes && geometry.morphAttributes.position && geometry.morphAttributes.position.length > 0;
+
+                       }
+
+                       let useSkinning = false;
+
+                       if ( object.isSkinnedMesh === true ) {
+
+                               if ( material.skinning === true ) {
+
+                                       useSkinning = true;
+
+                               } else {
+
+                                       console.warn( 'THREE.WebGLShadowMap: THREE.SkinnedMesh with material.skinning set to false:', object );
+
+                               }
+
+                       }
+
+                       const useInstancing = object.isInstancedMesh === true;
+
+                       result = getMaterialVariant( useMorphing, useSkinning, useInstancing );
+
+               } else {
+
+                       result = customMaterial;
+
+               }
+
+               if ( _renderer.localClippingEnabled &&
+                               material.clipShadows === true &&
+                               material.clippingPlanes.length !== 0 ) {
+
+                       // in this case we need a unique material instance reflecting the
+                       // appropriate state
+
+                       const keyA = result.uuid, keyB = material.uuid;
+
+                       let materialsForVariant = _materialCache[ keyA ];
+
+                       if ( materialsForVariant === undefined ) {
+
+                               materialsForVariant = {};
+                               _materialCache[ keyA ] = materialsForVariant;
+
+                       }
+
+                       let cachedMaterial = materialsForVariant[ keyB ];
+
+                       if ( cachedMaterial === undefined ) {
+
+                               cachedMaterial = result.clone();
+                               materialsForVariant[ keyB ] = cachedMaterial;
+
+                       }
+
+                       result = cachedMaterial;
+
+               }
+
+               result.visible = material.visible;
+               result.wireframe = material.wireframe;
+
+               if ( type === VSMShadowMap ) {
+
+                       result.side = ( material.shadowSide !== null ) ? material.shadowSide : material.side;
+
+               } else {
+
+                       result.side = ( material.shadowSide !== null ) ? material.shadowSide : shadowSide[ material.side ];
+
+               }
+
+               result.clipShadows = material.clipShadows;
+               result.clippingPlanes = material.clippingPlanes;
+               result.clipIntersection = material.clipIntersection;
+
+               result.wireframeLinewidth = material.wireframeLinewidth;
+               result.linewidth = material.linewidth;
+
+               if ( light.isPointLight === true && result.isMeshDistanceMaterial === true ) {
+
+                       result.referencePosition.setFromMatrixPosition( light.matrixWorld );
+                       result.nearDistance = shadowCameraNear;
+                       result.farDistance = shadowCameraFar;
+
+               }
+
+               return result;
+
+       }
+
+       function renderObject( object, camera, shadowCamera, light, type ) {
+
+               if ( object.visible === false ) return;
+
+               const visible = object.layers.test( camera.layers );
+
+               if ( visible && ( object.isMesh || object.isLine || object.isPoints ) ) {
+
+                       if ( ( object.castShadow || ( object.receiveShadow && type === VSMShadowMap ) ) && ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) ) {
+
+                               object.modelViewMatrix.multiplyMatrices( shadowCamera.matrixWorldInverse, object.matrixWorld );
+
+                               const geometry = _objects.update( object );
+                               const material = object.material;
+
+                               if ( Array.isArray( material ) ) {
+
+                                       const groups = geometry.groups;
+
+                                       for ( let k = 0, kl = groups.length; k < kl; k ++ ) {
+
+                                               const group = groups[ k ];
+                                               const groupMaterial = material[ group.materialIndex ];
+
+                                               if ( groupMaterial && groupMaterial.visible ) {
+
+                                                       const depthMaterial = getDepthMaterial( object, geometry, groupMaterial, light, shadowCamera.near, shadowCamera.far, type );
+
+                                                       _renderer.renderBufferDirect( shadowCamera, null, geometry, depthMaterial, object, group );
+
+                                               }
+
+                                       }
+
+                               } else if ( material.visible ) {
+
+                                       const depthMaterial = getDepthMaterial( object, geometry, material, light, shadowCamera.near, shadowCamera.far, type );
+
+                                       _renderer.renderBufferDirect( shadowCamera, null, geometry, depthMaterial, object, null );
+
+                               }
+
+                       }
+
+               }
+
+               const children = object.children;
+
+               for ( let i = 0, l = children.length; i < l; i ++ ) {
+
+                       renderObject( children[ i ], camera, shadowCamera, light, type );
+
+               }
+
+       }
+
+}
+
+function WebGLState( gl, extensions, capabilities ) {
+
+       const isWebGL2 = capabilities.isWebGL2;
+
+       function ColorBuffer() {
+
+               let locked = false;
+
+               const color = new Vector4();
+               let currentColorMask = null;
+               const currentColorClear = new Vector4( 0, 0, 0, 0 );
+
+               return {
+
+                       setMask: function ( colorMask ) {
+
+                               if ( currentColorMask !== colorMask && ! locked ) {
+
+                                       gl.colorMask( colorMask, colorMask, colorMask, colorMask );
+                                       currentColorMask = colorMask;
+
+                               }
+
+                       },
+
+                       setLocked: function ( lock ) {
+
+                               locked = lock;
+
+                       },
+
+                       setClear: function ( r, g, b, a, premultipliedAlpha ) {
+
+                               if ( premultipliedAlpha === true ) {
+
+                                       r *= a; g *= a; b *= a;
+
+                               }
+
+                               color.set( r, g, b, a );
+
+                               if ( currentColorClear.equals( color ) === false ) {
+
+                                       gl.clearColor( r, g, b, a );
+                                       currentColorClear.copy( color );
+
+                               }
+
+                       },
+
+                       reset: function () {
+
+                               locked = false;
+
+                               currentColorMask = null;
+                               currentColorClear.set( - 1, 0, 0, 0 ); // set to invalid state
+
+                       }
+
+               };
+
+       }
+
+       function DepthBuffer() {
+
+               let locked = false;
+
+               let currentDepthMask = null;
+               let currentDepthFunc = null;
+               let currentDepthClear = null;
+
+               return {
+
+                       setTest: function ( depthTest ) {
+
+                               if ( depthTest ) {
+
+                                       enable( 2929 );
+
+                               } else {
+
+                                       disable( 2929 );
+
+                               }
+
+                       },
+
+                       setMask: function ( depthMask ) {
+
+                               if ( currentDepthMask !== depthMask && ! locked ) {
+
+                                       gl.depthMask( depthMask );
+                                       currentDepthMask = depthMask;
+
+                               }
+
+                       },
+
+                       setFunc: function ( depthFunc ) {
+
+                               if ( currentDepthFunc !== depthFunc ) {
+
+                                       if ( depthFunc ) {
+
+                                               switch ( depthFunc ) {
+
+                                                       case NeverDepth:
+
+                                                               gl.depthFunc( 512 );
+                                                               break;
+
+                                                       case AlwaysDepth:
+
+                                                               gl.depthFunc( 519 );
+                                                               break;
+
+                                                       case LessDepth:
+
+                                                               gl.depthFunc( 513 );
+                                                               break;
+
+                                                       case LessEqualDepth:
+
+                                                               gl.depthFunc( 515 );
+                                                               break;
+
+                                                       case EqualDepth:
+
+                                                               gl.depthFunc( 514 );
+                                                               break;
+
+                                                       case GreaterEqualDepth:
+
+                                                               gl.depthFunc( 518 );
+                                                               break;
+
+                                                       case GreaterDepth:
+
+                                                               gl.depthFunc( 516 );
+                                                               break;
+
+                                                       case NotEqualDepth:
+
+                                                               gl.depthFunc( 517 );
+                                                               break;
+
+                                                       default:
+
+                                                               gl.depthFunc( 515 );
+
+                                               }
+
+                                       } else {
+
+                                               gl.depthFunc( 515 );
+
+                                       }
+
+                                       currentDepthFunc = depthFunc;
+
+                               }
+
+                       },
+
+                       setLocked: function ( lock ) {
+
+                               locked = lock;
+
+                       },
+
+                       setClear: function ( depth ) {
+
+                               if ( currentDepthClear !== depth ) {
+
+                                       gl.clearDepth( depth );
+                                       currentDepthClear = depth;
+
+                               }
+
+                       },
+
+                       reset: function () {
+
+                               locked = false;
+
+                               currentDepthMask = null;
+                               currentDepthFunc = null;
+                               currentDepthClear = null;
+
+                       }
+
+               };
+
+       }
+
+       function StencilBuffer() {
+
+               let locked = false;
+
+               let currentStencilMask = null;
+               let currentStencilFunc = null;
+               let currentStencilRef = null;
+               let currentStencilFuncMask = null;
+               let currentStencilFail = null;
+               let currentStencilZFail = null;
+               let currentStencilZPass = null;
+               let currentStencilClear = null;
+
+               return {
+
+                       setTest: function ( stencilTest ) {
+
+                               if ( ! locked ) {
+
+                                       if ( stencilTest ) {
+
+                                               enable( 2960 );
+
+                                       } else {
+
+                                               disable( 2960 );
+
+                                       }
+
+                               }
+
+                       },
+
+                       setMask: function ( stencilMask ) {
+
+                               if ( currentStencilMask !== stencilMask && ! locked ) {
+
+                                       gl.stencilMask( stencilMask );
+                                       currentStencilMask = stencilMask;
+
+                               }
+
+                       },
+
+                       setFunc: function ( stencilFunc, stencilRef, stencilMask ) {
+
+                               if ( currentStencilFunc !== stencilFunc ||
+                                    currentStencilRef !== stencilRef ||
+                                    currentStencilFuncMask !== stencilMask ) {
+
+                                       gl.stencilFunc( stencilFunc, stencilRef, stencilMask );
+
+                                       currentStencilFunc = stencilFunc;
+                                       currentStencilRef = stencilRef;
+                                       currentStencilFuncMask = stencilMask;
+
+                               }
+
+                       },
+
+                       setOp: function ( stencilFail, stencilZFail, stencilZPass ) {
+
+                               if ( currentStencilFail !== stencilFail ||
+                                    currentStencilZFail !== stencilZFail ||
+                                    currentStencilZPass !== stencilZPass ) {
+
+                                       gl.stencilOp( stencilFail, stencilZFail, stencilZPass );
+
+                                       currentStencilFail = stencilFail;
+                                       currentStencilZFail = stencilZFail;
+                                       currentStencilZPass = stencilZPass;
+
+                               }
+
+                       },
+
+                       setLocked: function ( lock ) {
+
+                               locked = lock;
+
+                       },
+
+                       setClear: function ( stencil ) {
+
+                               if ( currentStencilClear !== stencil ) {
+
+                                       gl.clearStencil( stencil );
+                                       currentStencilClear = stencil;
+
+                               }
+
+                       },
+
+                       reset: function () {
+
+                               locked = false;
+
+                               currentStencilMask = null;
+                               currentStencilFunc = null;
+                               currentStencilRef = null;
+                               currentStencilFuncMask = null;
+                               currentStencilFail = null;
+                               currentStencilZFail = null;
+                               currentStencilZPass = null;
+                               currentStencilClear = null;
+
+                       }
+
+               };
+
+       }
+
+       //
+
+       const colorBuffer = new ColorBuffer();
+       const depthBuffer = new DepthBuffer();
+       const stencilBuffer = new StencilBuffer();
+
+       let enabledCapabilities = {};
+
+       let currentProgram = null;
+
+       let currentBlendingEnabled = null;
+       let currentBlending = null;
+       let currentBlendEquation = null;
+       let currentBlendSrc = null;
+       let currentBlendDst = null;
+       let currentBlendEquationAlpha = null;
+       let currentBlendSrcAlpha = null;
+       let currentBlendDstAlpha = null;
+       let currentPremultipledAlpha = false;
+
+       let currentFlipSided = null;
+       let currentCullFace = null;
+
+       let currentLineWidth = null;
+
+       let currentPolygonOffsetFactor = null;
+       let currentPolygonOffsetUnits = null;
+
+       const maxTextures = gl.getParameter( 35661 );
+
+       let lineWidthAvailable = false;
+       let version = 0;
+       const glVersion = gl.getParameter( 7938 );
+
+       if ( glVersion.indexOf( 'WebGL' ) !== - 1 ) {
+
+               version = parseFloat( /^WebGL (\d)/.exec( glVersion )[ 1 ] );
+               lineWidthAvailable = ( version >= 1.0 );
+
+       } else if ( glVersion.indexOf( 'OpenGL ES' ) !== - 1 ) {
+
+               version = parseFloat( /^OpenGL ES (\d)/.exec( glVersion )[ 1 ] );
+               lineWidthAvailable = ( version >= 2.0 );
+
+       }
+
+       let currentTextureSlot = null;
+       let currentBoundTextures = {};
+
+       const currentScissor = new Vector4();
+       const currentViewport = new Vector4();
+
+       function createTexture( type, target, count ) {
+
+               const data = new Uint8Array( 4 ); // 4 is required to match default unpack alignment of 4.
+               const texture = gl.createTexture();
+
+               gl.bindTexture( type, texture );
+               gl.texParameteri( type, 10241, 9728 );
+               gl.texParameteri( type, 10240, 9728 );
+
+               for ( let i = 0; i < count; i ++ ) {
+
+                       gl.texImage2D( target + i, 0, 6408, 1, 1, 0, 6408, 5121, data );
+
+               }
+
+               return texture;
+
+       }
+
+       const emptyTextures = {};
+       emptyTextures[ 3553 ] = createTexture( 3553, 3553, 1 );
+       emptyTextures[ 34067 ] = createTexture( 34067, 34069, 6 );
+
+       // init
+
+       colorBuffer.setClear( 0, 0, 0, 1 );
+       depthBuffer.setClear( 1 );
+       stencilBuffer.setClear( 0 );
+
+       enable( 2929 );
+       depthBuffer.setFunc( LessEqualDepth );
+
+       setFlipSided( false );
+       setCullFace( CullFaceBack );
+       enable( 2884 );
+
+       setBlending( NoBlending );
+
+       //
+
+       function enable( id ) {
+
+               if ( enabledCapabilities[ id ] !== true ) {
+
+                       gl.enable( id );
+                       enabledCapabilities[ id ] = true;
+
+               }
+
+       }
+
+       function disable( id ) {
+
+               if ( enabledCapabilities[ id ] !== false ) {
+
+                       gl.disable( id );
+                       enabledCapabilities[ id ] = false;
+
+               }
+
+       }
+
+       function useProgram( program ) {
+
+               if ( currentProgram !== program ) {
+
+                       gl.useProgram( program );
+
+                       currentProgram = program;
+
+                       return true;
+
+               }
+
+               return false;
+
+       }
+
+       const equationToGL = {
+               [ AddEquation ]: 32774,
+               [ SubtractEquation ]: 32778,
+               [ ReverseSubtractEquation ]: 32779
+       };
+
+       if ( isWebGL2 ) {
+
+               equationToGL[ MinEquation ] = 32775;
+               equationToGL[ MaxEquation ] = 32776;
+
+       } else {
+
+               const extension = extensions.get( 'EXT_blend_minmax' );
+
+               if ( extension !== null ) {
+
+                       equationToGL[ MinEquation ] = extension.MIN_EXT;
+                       equationToGL[ MaxEquation ] = extension.MAX_EXT;
+
+               }
+
+       }
+
+       const factorToGL = {
+               [ ZeroFactor ]: 0,
+               [ OneFactor ]: 1,
+               [ SrcColorFactor ]: 768,
+               [ SrcAlphaFactor ]: 770,
+               [ SrcAlphaSaturateFactor ]: 776,
+               [ DstColorFactor ]: 774,
+               [ DstAlphaFactor ]: 772,
+               [ OneMinusSrcColorFactor ]: 769,
+               [ OneMinusSrcAlphaFactor ]: 771,
+               [ OneMinusDstColorFactor ]: 775,
+               [ OneMinusDstAlphaFactor ]: 773
+       };
+
+       function setBlending( blending, blendEquation, blendSrc, blendDst, blendEquationAlpha, blendSrcAlpha, blendDstAlpha, premultipliedAlpha ) {
+
+               if ( blending === NoBlending ) {
+
+                       if ( currentBlendingEnabled ) {
+
+                               disable( 3042 );
+                               currentBlendingEnabled = false;
+
+                       }
+
+                       return;
+
+               }
+
+               if ( ! currentBlendingEnabled ) {
+
+                       enable( 3042 );
+                       currentBlendingEnabled = true;
+
+               }
+
+               if ( blending !== CustomBlending ) {
+
+                       if ( blending !== currentBlending || premultipliedAlpha !== currentPremultipledAlpha ) {
+
+                               if ( currentBlendEquation !== AddEquation || currentBlendEquationAlpha !== AddEquation ) {
+
+                                       gl.blendEquation( 32774 );
+
+                                       currentBlendEquation = AddEquation;
+                                       currentBlendEquationAlpha = AddEquation;
+
+                               }
+
+                               if ( premultipliedAlpha ) {
+
+                                       switch ( blending ) {
+
+                                               case NormalBlending:
+                                                       gl.blendFuncSeparate( 1, 771, 1, 771 );
+                                                       break;
+
+                                               case AdditiveBlending:
+                                                       gl.blendFunc( 1, 1 );
+                                                       break;
+
+                                               case SubtractiveBlending:
+                                                       gl.blendFuncSeparate( 0, 0, 769, 771 );
+                                                       break;
+
+                                               case MultiplyBlending:
+                                                       gl.blendFuncSeparate( 0, 768, 0, 770 );
+                                                       break;
+
+                                               default:
+                                                       console.error( 'THREE.WebGLState: Invalid blending: ', blending );
+                                                       break;
+
+                                       }
+
+                               } else {
+
+                                       switch ( blending ) {
+
+                                               case NormalBlending:
+                                                       gl.blendFuncSeparate( 770, 771, 1, 771 );
+                                                       break;
+
+                                               case AdditiveBlending:
+                                                       gl.blendFunc( 770, 1 );
+                                                       break;
+
+                                               case SubtractiveBlending:
+                                                       gl.blendFunc( 0, 769 );
+                                                       break;
+
+                                               case MultiplyBlending:
+                                                       gl.blendFunc( 0, 768 );
+                                                       break;
+
+                                               default:
+                                                       console.error( 'THREE.WebGLState: Invalid blending: ', blending );
+                                                       break;
+
+                                       }
+
+                               }
+
+                               currentBlendSrc = null;
+                               currentBlendDst = null;
+                               currentBlendSrcAlpha = null;
+                               currentBlendDstAlpha = null;
+
+                               currentBlending = blending;
+                               currentPremultipledAlpha = premultipliedAlpha;
+
+                       }
+
+                       return;
+
+               }
+
+               // custom blending
+
+               blendEquationAlpha = blendEquationAlpha || blendEquation;
+               blendSrcAlpha = blendSrcAlpha || blendSrc;
+               blendDstAlpha = blendDstAlpha || blendDst;
+
+               if ( blendEquation !== currentBlendEquation || blendEquationAlpha !== currentBlendEquationAlpha ) {
+
+                       gl.blendEquationSeparate( equationToGL[ blendEquation ], equationToGL[ blendEquationAlpha ] );
+
+                       currentBlendEquation = blendEquation;
+                       currentBlendEquationAlpha = blendEquationAlpha;
+
+               }
+
+               if ( blendSrc !== currentBlendSrc || blendDst !== currentBlendDst || blendSrcAlpha !== currentBlendSrcAlpha || blendDstAlpha !== currentBlendDstAlpha ) {
+
+                       gl.blendFuncSeparate( factorToGL[ blendSrc ], factorToGL[ blendDst ], factorToGL[ blendSrcAlpha ], factorToGL[ blendDstAlpha ] );
+
+                       currentBlendSrc = blendSrc;
+                       currentBlendDst = blendDst;
+                       currentBlendSrcAlpha = blendSrcAlpha;
+                       currentBlendDstAlpha = blendDstAlpha;
+
+               }
+
+               currentBlending = blending;
+               currentPremultipledAlpha = null;
+
+       }
+
+       function setMaterial( material, frontFaceCW ) {
+
+               material.side === DoubleSide
+                       ? disable( 2884 )
+                       : enable( 2884 );
+
+               let flipSided = ( material.side === BackSide );
+               if ( frontFaceCW ) flipSided = ! flipSided;
+
+               setFlipSided( flipSided );
+
+               ( material.blending === NormalBlending && material.transparent === false )
+                       ? setBlending( NoBlending )
+                       : setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst, material.blendEquationAlpha, material.blendSrcAlpha, material.blendDstAlpha, material.premultipliedAlpha );
+
+               depthBuffer.setFunc( material.depthFunc );
+               depthBuffer.setTest( material.depthTest );
+               depthBuffer.setMask( material.depthWrite );
+               colorBuffer.setMask( material.colorWrite );
+
+               const stencilWrite = material.stencilWrite;
+               stencilBuffer.setTest( stencilWrite );
+               if ( stencilWrite ) {
+
+                       stencilBuffer.setMask( material.stencilWriteMask );
+                       stencilBuffer.setFunc( material.stencilFunc, material.stencilRef, material.stencilFuncMask );
+                       stencilBuffer.setOp( material.stencilFail, material.stencilZFail, material.stencilZPass );
+
+               }
+
+               setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits );
+
+       }
+
+       //
+
+       function setFlipSided( flipSided ) {
+
+               if ( currentFlipSided !== flipSided ) {
+
+                       if ( flipSided ) {
+
+                               gl.frontFace( 2304 );
+
+                       } else {
+
+                               gl.frontFace( 2305 );
+
+                       }
+
+                       currentFlipSided = flipSided;
+
+               }
+
+       }
+
+       function setCullFace( cullFace ) {
+
+               if ( cullFace !== CullFaceNone ) {
+
+                       enable( 2884 );
+
+                       if ( cullFace !== currentCullFace ) {
+
+                               if ( cullFace === CullFaceBack ) {
+
+                                       gl.cullFace( 1029 );
+
+                               } else if ( cullFace === CullFaceFront ) {
+
+                                       gl.cullFace( 1028 );
+
+                               } else {
+
+                                       gl.cullFace( 1032 );
+
+                               }
+
+                       }
+
+               } else {
+
+                       disable( 2884 );
+
+               }
+
+               currentCullFace = cullFace;
+
+       }
+
+       function setLineWidth( width ) {
+
+               if ( width !== currentLineWidth ) {
+
+                       if ( lineWidthAvailable ) gl.lineWidth( width );
+
+                       currentLineWidth = width;
+
+               }
+
+       }
+
+       function setPolygonOffset( polygonOffset, factor, units ) {
+
+               if ( polygonOffset ) {
+
+                       enable( 32823 );
+
+                       if ( currentPolygonOffsetFactor !== factor || currentPolygonOffsetUnits !== units ) {
+
+                               gl.polygonOffset( factor, units );
+
+                               currentPolygonOffsetFactor = factor;
+                               currentPolygonOffsetUnits = units;
+
+                       }
+
+               } else {
+
+                       disable( 32823 );
+
+               }
+
+       }
+
+       function setScissorTest( scissorTest ) {
+
+               if ( scissorTest ) {
+
+                       enable( 3089 );
+
+               } else {
+
+                       disable( 3089 );
+
+               }
+
+       }
+
+       // texture
+
+       function activeTexture( webglSlot ) {
+
+               if ( webglSlot === undefined ) webglSlot = 33984 + maxTextures - 1;
+
+               if ( currentTextureSlot !== webglSlot ) {
+
+                       gl.activeTexture( webglSlot );
+                       currentTextureSlot = webglSlot;
+
+               }
+
+       }
+
+       function bindTexture( webglType, webglTexture ) {
+
+               if ( currentTextureSlot === null ) {
+
+                       activeTexture();
+
+               }
+
+               let boundTexture = currentBoundTextures[ currentTextureSlot ];
+
+               if ( boundTexture === undefined ) {
+
+                       boundTexture = { type: undefined, texture: undefined };
+                       currentBoundTextures[ currentTextureSlot ] = boundTexture;
+
+               }
+
+               if ( boundTexture.type !== webglType || boundTexture.texture !== webglTexture ) {
+
+                       gl.bindTexture( webglType, webglTexture || emptyTextures[ webglType ] );
+
+                       boundTexture.type = webglType;
+                       boundTexture.texture = webglTexture;
+
+               }
+
+       }
+
+       function unbindTexture() {
+
+               const boundTexture = currentBoundTextures[ currentTextureSlot ];
+
+               if ( boundTexture !== undefined && boundTexture.type !== undefined ) {
+
+                       gl.bindTexture( boundTexture.type, null );
+
+                       boundTexture.type = undefined;
+                       boundTexture.texture = undefined;
+
+               }
+
+       }
+
+       function compressedTexImage2D() {
+
+               try {
+
+                       gl.compressedTexImage2D.apply( gl, arguments );
+
+               } catch ( error ) {
+
+                       console.error( 'THREE.WebGLState:', error );
+
+               }
+
+       }
+
+       function texImage2D() {
+
+               try {
+
+                       gl.texImage2D.apply( gl, arguments );
+
+               } catch ( error ) {
+
+                       console.error( 'THREE.WebGLState:', error );
+
+               }
+
+       }
+
+       function texImage3D() {
+
+               try {
+
+                       gl.texImage3D.apply( gl, arguments );
+
+               } catch ( error ) {
+
+                       console.error( 'THREE.WebGLState:', error );
+
+               }
+
+       }
+
+       //
+
+       function scissor( scissor ) {
+
+               if ( currentScissor.equals( scissor ) === false ) {
+
+                       gl.scissor( scissor.x, scissor.y, scissor.z, scissor.w );
+                       currentScissor.copy( scissor );
+
+               }
+
+       }
+
+       function viewport( viewport ) {
+
+               if ( currentViewport.equals( viewport ) === false ) {
+
+                       gl.viewport( viewport.x, viewport.y, viewport.z, viewport.w );
+                       currentViewport.copy( viewport );
+
+               }
+
+       }
+
+       //
+
+       function reset() {
+
+               enabledCapabilities = {};
+
+               currentTextureSlot = null;
+               currentBoundTextures = {};
+
+               currentProgram = null;
+
+               currentBlendingEnabled = null;
+               currentBlending = null;
+               currentBlendEquation = null;
+               currentBlendSrc = null;
+               currentBlendDst = null;
+               currentBlendEquationAlpha = null;
+               currentBlendSrcAlpha = null;
+               currentBlendDstAlpha = null;
+               currentPremultipledAlpha = false;
+
+               currentFlipSided = null;
+               currentCullFace = null;
+
+               currentLineWidth = null;
+
+               currentPolygonOffsetFactor = null;
+               currentPolygonOffsetUnits = null;
+
+               colorBuffer.reset();
+               depthBuffer.reset();
+               stencilBuffer.reset();
+
+       }
+
+       return {
+
+               buffers: {
+                       color: colorBuffer,
+                       depth: depthBuffer,
+                       stencil: stencilBuffer
+               },
+
+               enable: enable,
+               disable: disable,
+
+               useProgram: useProgram,
+
+               setBlending: setBlending,
+               setMaterial: setMaterial,
+
+               setFlipSided: setFlipSided,
+               setCullFace: setCullFace,
+
+               setLineWidth: setLineWidth,
+               setPolygonOffset: setPolygonOffset,
+
+               setScissorTest: setScissorTest,
+
+               activeTexture: activeTexture,
+               bindTexture: bindTexture,
+               unbindTexture: unbindTexture,
+               compressedTexImage2D: compressedTexImage2D,
+               texImage2D: texImage2D,
+               texImage3D: texImage3D,
+
+               scissor: scissor,
+               viewport: viewport,
+
+               reset: reset
+
+       };
+
+}
+
+function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, info ) {
+
+       const isWebGL2 = capabilities.isWebGL2;
+       const maxTextures = capabilities.maxTextures;
+       const maxCubemapSize = capabilities.maxCubemapSize;
+       const maxTextureSize = capabilities.maxTextureSize;
+       const maxSamples = capabilities.maxSamples;
+
+       const _videoTextures = new WeakMap();
+       let _canvas;
+
+       // cordova iOS (as of 5.0) still uses UIWebView, which provides OffscreenCanvas,
+       // also OffscreenCanvas.getContext("webgl"), but not OffscreenCanvas.getContext("2d")!
+       // Some implementations may only implement OffscreenCanvas partially (e.g. lacking 2d).
+
+       let useOffscreenCanvas = false;
+
+       try {
+
+               useOffscreenCanvas = typeof OffscreenCanvas !== 'undefined'
+                       && ( new OffscreenCanvas( 1, 1 ).getContext( '2d' ) ) !== null;
+
+       } catch ( err ) {
+
+               // Ignore any errors
+
+       }
+
+       function createCanvas( width, height ) {
+
+               // Use OffscreenCanvas when available. Specially needed in web workers
+
+               return useOffscreenCanvas ?
+                       new OffscreenCanvas( width, height ) :
+                       document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' );
+
+       }
+
+       function resizeImage( image, needsPowerOfTwo, needsNewCanvas, maxSize ) {
+
+               let scale = 1;
+
+               // handle case if texture exceeds max size
+
+               if ( image.width > maxSize || image.height > maxSize ) {
+
+                       scale = maxSize / Math.max( image.width, image.height );
+
+               }
+
+               // only perform resize if necessary
+
+               if ( scale < 1 || needsPowerOfTwo === true ) {
+
+                       // only perform resize for certain image types
+
+                       if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) ||
+                               ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) ||
+                               ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) {
+
+                               const floor = needsPowerOfTwo ? MathUtils.floorPowerOfTwo : Math.floor;
+
+                               const width = floor( scale * image.width );
+                               const height = floor( scale * image.height );
+
+                               if ( _canvas === undefined ) _canvas = createCanvas( width, height );
+
+                               // cube textures can't reuse the same canvas
+
+                               const canvas = needsNewCanvas ? createCanvas( width, height ) : _canvas;
+
+                               canvas.width = width;
+                               canvas.height = height;
+
+                               const context = canvas.getContext( '2d' );
+                               context.drawImage( image, 0, 0, width, height );
+
+                               console.warn( 'THREE.WebGLRenderer: Texture has been resized from (' + image.width + 'x' + image.height + ') to (' + width + 'x' + height + ').' );
+
+                               return canvas;
+
+                       } else {
+
+                               if ( 'data' in image ) {
+
+                                       console.warn( 'THREE.WebGLRenderer: Image in DataTexture is too big (' + image.width + 'x' + image.height + ').' );
+
+                               }
+
+                               return image;
+
+                       }
+
+               }
+
+               return image;
+
+       }
+
+       function isPowerOfTwo( image ) {
+
+               return MathUtils.isPowerOfTwo( image.width ) && MathUtils.isPowerOfTwo( image.height );
+
+       }
+
+       function textureNeedsPowerOfTwo( texture ) {
+
+               if ( isWebGL2 ) return false;
+
+               return ( texture.wrapS !== ClampToEdgeWrapping || texture.wrapT !== ClampToEdgeWrapping ) ||
+                       ( texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter );
+
+       }
+
+       function textureNeedsGenerateMipmaps( texture, supportsMips ) {
+
+               return texture.generateMipmaps && supportsMips &&
+                       texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter;
+
+       }
+
+       function generateMipmap( target, texture, width, height ) {
+
+               _gl.generateMipmap( target );
+
+               const textureProperties = properties.get( texture );
+
+               // Note: Math.log( x ) * Math.LOG2E used instead of Math.log2( x ) which is not supported by IE11
+               textureProperties.__maxMipLevel = Math.log( Math.max( width, height ) ) * Math.LOG2E;
+
+       }
+
+       function getInternalFormat( internalFormatName, glFormat, glType ) {
+
+               if ( isWebGL2 === false ) return glFormat;
+
+               if ( internalFormatName !== null ) {
+
+                       if ( _gl[ internalFormatName ] !== undefined ) return _gl[ internalFormatName ];
+
+                       console.warn( 'THREE.WebGLRenderer: Attempt to use non-existing WebGL internal format \'' + internalFormatName + '\'' );
+
+               }
+
+               let internalFormat = glFormat;
+
+               if ( glFormat === 6403 ) {
+
+                       if ( glType === 5126 ) internalFormat = 33326;
+                       if ( glType === 5131 ) internalFormat = 33325;
+                       if ( glType === 5121 ) internalFormat = 33321;
+
+               }
+
+               if ( glFormat === 6407 ) {
+
+                       if ( glType === 5126 ) internalFormat = 34837;
+                       if ( glType === 5131 ) internalFormat = 34843;
+                       if ( glType === 5121 ) internalFormat = 32849;
+
+               }
+
+               if ( glFormat === 6408 ) {
+
+                       if ( glType === 5126 ) internalFormat = 34836;
+                       if ( glType === 5131 ) internalFormat = 34842;
+                       if ( glType === 5121 ) internalFormat = 32856;
+
+               }
+
+               if ( internalFormat === 33325 || internalFormat === 33326 ||
+                       internalFormat === 34842 || internalFormat === 34836 ) {
+
+                       extensions.get( 'EXT_color_buffer_float' );
+
+               }
+
+               return internalFormat;
+
+       }
+
+       // Fallback filters for non-power-of-2 textures
+
+       function filterFallback( f ) {
+
+               if ( f === NearestFilter || f === NearestMipmapNearestFilter || f === NearestMipmapLinearFilter ) {
+
+                       return 9728;
+
+               }
+
+               return 9729;
+
+       }
+
+       //
+
+       function onTextureDispose( event ) {
+
+               const texture = event.target;
+
+               texture.removeEventListener( 'dispose', onTextureDispose );
+
+               deallocateTexture( texture );
+
+               if ( texture.isVideoTexture ) {
+
+                       _videoTextures.delete( texture );
+
+               }
+
+               info.memory.textures --;
+
+       }
+
+       function onRenderTargetDispose( event ) {
+
+               const renderTarget = event.target;
+
+               renderTarget.removeEventListener( 'dispose', onRenderTargetDispose );
+
+               deallocateRenderTarget( renderTarget );
+
+               info.memory.textures --;
+
+       }
+
+       //
+
+       function deallocateTexture( texture ) {
+
+               const textureProperties = properties.get( texture );
+
+               if ( textureProperties.__webglInit === undefined ) return;
+
+               _gl.deleteTexture( textureProperties.__webglTexture );
+
+               properties.remove( texture );
+
+       }
+
+       function deallocateRenderTarget( renderTarget ) {
+
+               const renderTargetProperties = properties.get( renderTarget );
+               const textureProperties = properties.get( renderTarget.texture );
+
+               if ( ! renderTarget ) return;
+
+               if ( textureProperties.__webglTexture !== undefined ) {
+
+                       _gl.deleteTexture( textureProperties.__webglTexture );
+
+               }
+
+               if ( renderTarget.depthTexture ) {
+
+                       renderTarget.depthTexture.dispose();
+
+               }
+
+               if ( renderTarget.isWebGLCubeRenderTarget ) {
+
+                       for ( let i = 0; i < 6; i ++ ) {
+
+                               _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer[ i ] );
+                               if ( renderTargetProperties.__webglDepthbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthbuffer[ i ] );
+
+                       }
+
+               } else {
+
+                       _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer );
+                       if ( renderTargetProperties.__webglDepthbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthbuffer );
+                       if ( renderTargetProperties.__webglMultisampledFramebuffer ) _gl.deleteFramebuffer( renderTargetProperties.__webglMultisampledFramebuffer );
+                       if ( renderTargetProperties.__webglColorRenderbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglColorRenderbuffer );
+                       if ( renderTargetProperties.__webglDepthRenderbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthRenderbuffer );
+
+               }
+
+               properties.remove( renderTarget.texture );
+               properties.remove( renderTarget );
+
+       }
+
+       //
+
+       let textureUnits = 0;
+
+       function resetTextureUnits() {
+
+               textureUnits = 0;
+
+       }
+
+       function allocateTextureUnit() {
+
+               const textureUnit = textureUnits;
+
+               if ( textureUnit >= maxTextures ) {
+
+                       console.warn( 'THREE.WebGLTextures: Trying to use ' + textureUnit + ' texture units while this GPU supports only ' + maxTextures );
+
+               }
+
+               textureUnits += 1;
+
+               return textureUnit;
+
+       }
+
+       //
+
+       function setTexture2D( texture, slot ) {
+
+               const textureProperties = properties.get( texture );
+
+               if ( texture.isVideoTexture ) updateVideoTexture( texture );
+
+               if ( texture.version > 0 && textureProperties.__version !== texture.version ) {
+
+                       const image = texture.image;
+
+                       if ( image === undefined ) {
+
+                               console.warn( 'THREE.WebGLRenderer: Texture marked for update but image is undefined' );
+
+                       } else if ( image.complete === false ) {
+
+                               console.warn( 'THREE.WebGLRenderer: Texture marked for update but image is incomplete' );
+
+                       } else {
+
+                               uploadTexture( textureProperties, texture, slot );
+                               return;
+
+                       }
+
+               }
+
+               state.activeTexture( 33984 + slot );
+               state.bindTexture( 3553, textureProperties.__webglTexture );
+
+       }
+
+       function setTexture2DArray( texture, slot ) {
+
+               const textureProperties = properties.get( texture );
+
+               if ( texture.version > 0 && textureProperties.__version !== texture.version ) {
+
+                       uploadTexture( textureProperties, texture, slot );
+                       return;
+
+               }
+
+               state.activeTexture( 33984 + slot );
+               state.bindTexture( 35866, textureProperties.__webglTexture );
+
+       }
+
+       function setTexture3D( texture, slot ) {
+
+               const textureProperties = properties.get( texture );
+
+               if ( texture.version > 0 && textureProperties.__version !== texture.version ) {
+
+                       uploadTexture( textureProperties, texture, slot );
+                       return;
+
+               }
+
+               state.activeTexture( 33984 + slot );
+               state.bindTexture( 32879, textureProperties.__webglTexture );
+
+       }
+
+       function setTextureCube( texture, slot ) {
+
+               const textureProperties = properties.get( texture );
+
+               if ( texture.version > 0 && textureProperties.__version !== texture.version ) {
+
+                       uploadCubeTexture( textureProperties, texture, slot );
+                       return;
+
+               }
+
+               state.activeTexture( 33984 + slot );
+               state.bindTexture( 34067, textureProperties.__webglTexture );
+
+       }
+
+       const wrappingToGL = {
+               [ RepeatWrapping ]: 10497,
+               [ ClampToEdgeWrapping ]: 33071,
+               [ MirroredRepeatWrapping ]: 33648
+       };
+
+       const filterToGL = {
+               [ NearestFilter ]: 9728,
+               [ NearestMipmapNearestFilter ]: 9984,
+               [ NearestMipmapLinearFilter ]: 9986,
+
+               [ LinearFilter ]: 9729,
+               [ LinearMipmapNearestFilter ]: 9985,
+               [ LinearMipmapLinearFilter ]: 9987
+       };
+
+       function setTextureParameters( textureType, texture, supportsMips ) {
+
+               if ( supportsMips ) {
+
+                       _gl.texParameteri( textureType, 10242, wrappingToGL[ texture.wrapS ] );
+                       _gl.texParameteri( textureType, 10243, wrappingToGL[ texture.wrapT ] );
+
+                       if ( textureType === 32879 || textureType === 35866 ) {
+
+                               _gl.texParameteri( textureType, 32882, wrappingToGL[ texture.wrapR ] );
+
+                       }
+
+                       _gl.texParameteri( textureType, 10240, filterToGL[ texture.magFilter ] );
+                       _gl.texParameteri( textureType, 10241, filterToGL[ texture.minFilter ] );
+
+               } else {
+
+                       _gl.texParameteri( textureType, 10242, 33071 );
+                       _gl.texParameteri( textureType, 10243, 33071 );
+
+                       if ( textureType === 32879 || textureType === 35866 ) {
+
+                               _gl.texParameteri( textureType, 32882, 33071 );
+
+                       }
+
+                       if ( texture.wrapS !== ClampToEdgeWrapping || texture.wrapT !== ClampToEdgeWrapping ) {
+
+                               console.warn( 'THREE.WebGLRenderer: Texture is not power of two. Texture.wrapS and Texture.wrapT should be set to THREE.ClampToEdgeWrapping.' );
+
+                       }
+
+                       _gl.texParameteri( textureType, 10240, filterFallback( texture.magFilter ) );
+                       _gl.texParameteri( textureType, 10241, filterFallback( texture.minFilter ) );
+
+                       if ( texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter ) {
+
+                               console.warn( 'THREE.WebGLRenderer: Texture is not power of two. Texture.minFilter should be set to THREE.NearestFilter or THREE.LinearFilter.' );
+
+                       }
+
+               }
+
+               const extension = extensions.get( 'EXT_texture_filter_anisotropic' );
+
+               if ( extension ) {
+
+                       if ( texture.type === FloatType && extensions.get( 'OES_texture_float_linear' ) === null ) return;
+                       if ( texture.type === HalfFloatType && ( isWebGL2 || extensions.get( 'OES_texture_half_float_linear' ) ) === null ) return;
+
+                       if ( texture.anisotropy > 1 || properties.get( texture ).__currentAnisotropy ) {
+
+                               _gl.texParameterf( textureType, extension.TEXTURE_MAX_ANISOTROPY_EXT, Math.min( texture.anisotropy, capabilities.getMaxAnisotropy() ) );
+                               properties.get( texture ).__currentAnisotropy = texture.anisotropy;
+
+                       }
+
+               }
+
+       }
+
+       function initTexture( textureProperties, texture ) {
+
+               if ( textureProperties.__webglInit === undefined ) {
+
+                       textureProperties.__webglInit = true;
+
+                       texture.addEventListener( 'dispose', onTextureDispose );
+
+                       textureProperties.__webglTexture = _gl.createTexture();
+
+                       info.memory.textures ++;
+
+               }
+
+       }
+
+       function uploadTexture( textureProperties, texture, slot ) {
+
+               let textureType = 3553;
+
+               if ( texture.isDataTexture2DArray ) textureType = 35866;
+               if ( texture.isDataTexture3D ) textureType = 32879;
+
+               initTexture( textureProperties, texture );
+
+               state.activeTexture( 33984 + slot );
+               state.bindTexture( textureType, textureProperties.__webglTexture );
+
+               _gl.pixelStorei( 37440, texture.flipY );
+               _gl.pixelStorei( 37441, texture.premultiplyAlpha );
+               _gl.pixelStorei( 3317, texture.unpackAlignment );
+
+               const needsPowerOfTwo = textureNeedsPowerOfTwo( texture ) && isPowerOfTwo( texture.image ) === false;
+               const image = resizeImage( texture.image, needsPowerOfTwo, false, maxTextureSize );
+
+               const supportsMips = isPowerOfTwo( image ) || isWebGL2,
+                       glFormat = utils.convert( texture.format );
+
+               let glType = utils.convert( texture.type ),
+                       glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType );
+
+               setTextureParameters( textureType, texture, supportsMips );
+
+               let mipmap;
+               const mipmaps = texture.mipmaps;
+
+               if ( texture.isDepthTexture ) {
+
+                       // populate depth texture with dummy data
+
+                       glInternalFormat = 6402;
+
+                       if ( isWebGL2 ) {
+
+                               if ( texture.type === FloatType ) {
+
+                                       glInternalFormat = 36012;
+
+                               } else if ( texture.type === UnsignedIntType ) {
+
+                                       glInternalFormat = 33190;
+
+                               } else if ( texture.type === UnsignedInt248Type ) {
+
+                                       glInternalFormat = 35056;
+
+                               } else {
+
+                                       glInternalFormat = 33189; // WebGL2 requires sized internalformat for glTexImage2D
+
+                               }
+
+                       } else {
+
+                               if ( texture.type === FloatType ) {
+
+                                       console.error( 'WebGLRenderer: Floating point depth texture requires WebGL2.' );
+
+                               }
+
+                       }
+
+                       // validation checks for WebGL 1
+
+                       if ( texture.format === DepthFormat && glInternalFormat === 6402 ) {
+
+                               // The error INVALID_OPERATION is generated by texImage2D if format and internalformat are
+                               // DEPTH_COMPONENT and type is not UNSIGNED_SHORT or UNSIGNED_INT
+                               // (https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/)
+                               if ( texture.type !== UnsignedShortType && texture.type !== UnsignedIntType ) {
+
+                                       console.warn( 'THREE.WebGLRenderer: Use UnsignedShortType or UnsignedIntType for DepthFormat DepthTexture.' );
+
+                                       texture.type = UnsignedShortType;
+                                       glType = utils.convert( texture.type );
+
+                               }
+
+                       }
+
+                       if ( texture.format === DepthStencilFormat && glInternalFormat === 6402 ) {
+
+                               // Depth stencil textures need the DEPTH_STENCIL internal format
+                               // (https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/)
+                               glInternalFormat = 34041;
+
+                               // The error INVALID_OPERATION is generated by texImage2D if format and internalformat are
+                               // DEPTH_STENCIL and type is not UNSIGNED_INT_24_8_WEBGL.
+                               // (https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/)
+                               if ( texture.type !== UnsignedInt248Type ) {
+
+                                       console.warn( 'THREE.WebGLRenderer: Use UnsignedInt248Type for DepthStencilFormat DepthTexture.' );
+
+                                       texture.type = UnsignedInt248Type;
+                                       glType = utils.convert( texture.type );
+
+                               }
+
+                       }
+
+                       //
+
+                       state.texImage2D( 3553, 0, glInternalFormat, image.width, image.height, 0, glFormat, glType, null );
+
+               } else if ( texture.isDataTexture ) {
+
+                       // use manually created mipmaps if available
+                       // if there are no manual mipmaps
+                       // set 0 level mipmap and then use GL to generate other mipmap levels
+
+                       if ( mipmaps.length > 0 && supportsMips ) {
+
+                               for ( let i = 0, il = mipmaps.length; i < il; i ++ ) {
+
+                                       mipmap = mipmaps[ i ];
+                                       state.texImage2D( 3553, i, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data );
+
+                               }
+
+                               texture.generateMipmaps = false;
+                               textureProperties.__maxMipLevel = mipmaps.length - 1;
+
+                       } else {
+
+                               state.texImage2D( 3553, 0, glInternalFormat, image.width, image.height, 0, glFormat, glType, image.data );
+                               textureProperties.__maxMipLevel = 0;
+
+                       }
+
+               } else if ( texture.isCompressedTexture ) {
+
+                       for ( let i = 0, il = mipmaps.length; i < il; i ++ ) {
+
+                               mipmap = mipmaps[ i ];
+
+                               if ( texture.format !== RGBAFormat && texture.format !== RGBFormat ) {
+
+                                       if ( glFormat !== null ) {
+
+                                               state.compressedTexImage2D( 3553, i, glInternalFormat, mipmap.width, mipmap.height, 0, mipmap.data );
+
+                                       } else {
+
+                                               console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .uploadTexture()' );
+
+                                       }
+
+                               } else {
+
+                                       state.texImage2D( 3553, i, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data );
+
+                               }
+
+                       }
+
+                       textureProperties.__maxMipLevel = mipmaps.length - 1;
+
+               } else if ( texture.isDataTexture2DArray ) {
+
+                       state.texImage3D( 35866, 0, glInternalFormat, image.width, image.height, image.depth, 0, glFormat, glType, image.data );
+                       textureProperties.__maxMipLevel = 0;
+
+               } else if ( texture.isDataTexture3D ) {
+
+                       state.texImage3D( 32879, 0, glInternalFormat, image.width, image.height, image.depth, 0, glFormat, glType, image.data );
+                       textureProperties.__maxMipLevel = 0;
+
+               } else {
+
+                       // regular Texture (image, video, canvas)
+
+                       // use manually created mipmaps if available
+                       // if there are no manual mipmaps
+                       // set 0 level mipmap and then use GL to generate other mipmap levels
+
+                       if ( mipmaps.length > 0 && supportsMips ) {
+
+                               for ( let i = 0, il = mipmaps.length; i < il; i ++ ) {
+
+                                       mipmap = mipmaps[ i ];
+                                       state.texImage2D( 3553, i, glInternalFormat, glFormat, glType, mipmap );
+
+                               }
+
+                               texture.generateMipmaps = false;
+                               textureProperties.__maxMipLevel = mipmaps.length - 1;
+
+                       } else {
+
+                               state.texImage2D( 3553, 0, glInternalFormat, glFormat, glType, image );
+                               textureProperties.__maxMipLevel = 0;
+
+                       }
+
+               }
+
+               if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) {
+
+                       generateMipmap( textureType, texture, image.width, image.height );
+
+               }
+
+               textureProperties.__version = texture.version;
+
+               if ( texture.onUpdate ) texture.onUpdate( texture );
+
+       }
+
+       function uploadCubeTexture( textureProperties, texture, slot ) {
+
+               if ( texture.image.length !== 6 ) return;
+
+               initTexture( textureProperties, texture );
+
+               state.activeTexture( 33984 + slot );
+               state.bindTexture( 34067, textureProperties.__webglTexture );
+
+               _gl.pixelStorei( 37440, texture.flipY );
+               _gl.pixelStorei( 37441, texture.premultiplyAlpha );
+               _gl.pixelStorei( 3317, texture.unpackAlignment );
+
+               const isCompressed = ( texture && ( texture.isCompressedTexture || texture.image[ 0 ].isCompressedTexture ) );
+               const isDataTexture = ( texture.image[ 0 ] && texture.image[ 0 ].isDataTexture );
+
+               const cubeImage = [];
+
+               for ( let i = 0; i < 6; i ++ ) {
+
+                       if ( ! isCompressed && ! isDataTexture ) {
+
+                               cubeImage[ i ] = resizeImage( texture.image[ i ], false, true, maxCubemapSize );
+
+                       } else {
+
+                               cubeImage[ i ] = isDataTexture ? texture.image[ i ].image : texture.image[ i ];
+
+                       }
+
+               }
+
+               const image = cubeImage[ 0 ],
+                       supportsMips = isPowerOfTwo( image ) || isWebGL2,
+                       glFormat = utils.convert( texture.format ),
+                       glType = utils.convert( texture.type ),
+                       glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType );
+
+               setTextureParameters( 34067, texture, supportsMips );
+
+               let mipmaps;
+
+               if ( isCompressed ) {
+
+                       for ( let i = 0; i < 6; i ++ ) {
+
+                               mipmaps = cubeImage[ i ].mipmaps;
+
+                               for ( let j = 0; j < mipmaps.length; j ++ ) {
+
+                                       const mipmap = mipmaps[ j ];
+
+                                       if ( texture.format !== RGBAFormat && texture.format !== RGBFormat ) {
+
+                                               if ( glFormat !== null ) {
+
+                                                       state.compressedTexImage2D( 34069 + i, j, glInternalFormat, mipmap.width, mipmap.height, 0, mipmap.data );
+
+                                               } else {
+
+                                                       console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .setTextureCube()' );
+
+                                               }
+
+                                       } else {
+
+                                               state.texImage2D( 34069 + i, j, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data );
+
+                                       }
+
+                               }
+
+                       }
+
+                       textureProperties.__maxMipLevel = mipmaps.length - 1;
+
+               } else {
+
+                       mipmaps = texture.mipmaps;
+
+                       for ( let i = 0; i < 6; i ++ ) {
+
+                               if ( isDataTexture ) {
+
+                                       state.texImage2D( 34069 + i, 0, glInternalFormat, cubeImage[ i ].width, cubeImage[ i ].height, 0, glFormat, glType, cubeImage[ i ].data );
+
+                                       for ( let j = 0; j < mipmaps.length; j ++ ) {
+
+                                               const mipmap = mipmaps[ j ];
+                                               const mipmapImage = mipmap.image[ i ].image;
+
+                                               state.texImage2D( 34069 + i, j + 1, glInternalFormat, mipmapImage.width, mipmapImage.height, 0, glFormat, glType, mipmapImage.data );
+
+                                       }
+
+                               } else {
+
+                                       state.texImage2D( 34069 + i, 0, glInternalFormat, glFormat, glType, cubeImage[ i ] );
+
+                                       for ( let j = 0; j < mipmaps.length; j ++ ) {
+
+                                               const mipmap = mipmaps[ j ];
+
+                                               state.texImage2D( 34069 + i, j + 1, glInternalFormat, glFormat, glType, mipmap.image[ i ] );
+
+                                       }
+
+                               }
+
+                       }
+
+                       textureProperties.__maxMipLevel = mipmaps.length;
+
+               }
+
+               if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) {
+
+                       // We assume images for cube map have the same size.
+                       generateMipmap( 34067, texture, image.width, image.height );
+
+               }
+
+               textureProperties.__version = texture.version;
+
+               if ( texture.onUpdate ) texture.onUpdate( texture );
+
+       }
+
+       // Render targets
+
+       // Setup storage for target texture and bind it to correct framebuffer
+       function setupFrameBufferTexture( framebuffer, renderTarget, attachment, textureTarget ) {
+
+               const glFormat = utils.convert( renderTarget.texture.format );
+               const glType = utils.convert( renderTarget.texture.type );
+               const glInternalFormat = getInternalFormat( renderTarget.texture.internalFormat, glFormat, glType );
+               state.texImage2D( textureTarget, 0, glInternalFormat, renderTarget.width, renderTarget.height, 0, glFormat, glType, null );
+               _gl.bindFramebuffer( 36160, framebuffer );
+               _gl.framebufferTexture2D( 36160, attachment, textureTarget, properties.get( renderTarget.texture ).__webglTexture, 0 );
+               _gl.bindFramebuffer( 36160, null );
+
+       }
+
+       // Setup storage for internal depth/stencil buffers and bind to correct framebuffer
+       function setupRenderBufferStorage( renderbuffer, renderTarget, isMultisample ) {
+
+               _gl.bindRenderbuffer( 36161, renderbuffer );
+
+               if ( renderTarget.depthBuffer && ! renderTarget.stencilBuffer ) {
+
+                       let glInternalFormat = 33189;
+
+                       if ( isMultisample ) {
+
+                               const depthTexture = renderTarget.depthTexture;
+
+                               if ( depthTexture && depthTexture.isDepthTexture ) {
+
+                                       if ( depthTexture.type === FloatType ) {
+
+                                               glInternalFormat = 36012;
+
+                                       } else if ( depthTexture.type === UnsignedIntType ) {
+
+                                               glInternalFormat = 33190;
+
+                                       }
+
+                               }
+
+                               const samples = getRenderTargetSamples( renderTarget );
+
+                               _gl.renderbufferStorageMultisample( 36161, samples, glInternalFormat, renderTarget.width, renderTarget.height );
+
+                       } else {
+
+                               _gl.renderbufferStorage( 36161, glInternalFormat, renderTarget.width, renderTarget.height );
+
+                       }
+
+                       _gl.framebufferRenderbuffer( 36160, 36096, 36161, renderbuffer );
+
+               } else if ( renderTarget.depthBuffer && renderTarget.stencilBuffer ) {
+
+                       if ( isMultisample ) {
+
+                               const samples = getRenderTargetSamples( renderTarget );
+
+                               _gl.renderbufferStorageMultisample( 36161, samples, 35056, renderTarget.width, renderTarget.height );
+
+                       } else {
+
+                               _gl.renderbufferStorage( 36161, 34041, renderTarget.width, renderTarget.height );
+
+                       }
+
+
+                       _gl.framebufferRenderbuffer( 36160, 33306, 36161, renderbuffer );
+
+               } else {
+
+                       const glFormat = utils.convert( renderTarget.texture.format );
+                       const glType = utils.convert( renderTarget.texture.type );
+                       const glInternalFormat = getInternalFormat( renderTarget.texture.internalFormat, glFormat, glType );
+
+                       if ( isMultisample ) {
+
+                               const samples = getRenderTargetSamples( renderTarget );
+
+                               _gl.renderbufferStorageMultisample( 36161, samples, glInternalFormat, renderTarget.width, renderTarget.height );
+
+                       } else {
+
+                               _gl.renderbufferStorage( 36161, glInternalFormat, renderTarget.width, renderTarget.height );
+
+                       }
+
+               }
+
+               _gl.bindRenderbuffer( 36161, null );
+
+       }
+
+       // Setup resources for a Depth Texture for a FBO (needs an extension)
+       function setupDepthTexture( framebuffer, renderTarget ) {
+
+               const isCube = ( renderTarget && renderTarget.isWebGLCubeRenderTarget );
+               if ( isCube ) throw new Error( 'Depth Texture with cube render targets is not supported' );
+
+               _gl.bindFramebuffer( 36160, framebuffer );
+
+               if ( ! ( renderTarget.depthTexture && renderTarget.depthTexture.isDepthTexture ) ) {
+
+                       throw new Error( 'renderTarget.depthTexture must be an instance of THREE.DepthTexture' );
+
+               }
+
+               // upload an empty depth texture with framebuffer size
+               if ( ! properties.get( renderTarget.depthTexture ).__webglTexture ||
+                               renderTarget.depthTexture.image.width !== renderTarget.width ||
+                               renderTarget.depthTexture.image.height !== renderTarget.height ) {
+
+                       renderTarget.depthTexture.image.width = renderTarget.width;
+                       renderTarget.depthTexture.image.height = renderTarget.height;
+                       renderTarget.depthTexture.needsUpdate = true;
+
+               }
+
+               setTexture2D( renderTarget.depthTexture, 0 );
+
+               const webglDepthTexture = properties.get( renderTarget.depthTexture ).__webglTexture;
+
+               if ( renderTarget.depthTexture.format === DepthFormat ) {
+
+                       _gl.framebufferTexture2D( 36160, 36096, 3553, webglDepthTexture, 0 );
+
+               } else if ( renderTarget.depthTexture.format === DepthStencilFormat ) {
+
+                       _gl.framebufferTexture2D( 36160, 33306, 3553, webglDepthTexture, 0 );
+
+               } else {
+
+                       throw new Error( 'Unknown depthTexture format' );
+
+               }
+
+       }
+
+       // Setup GL resources for a non-texture depth buffer
+       function setupDepthRenderbuffer( renderTarget ) {
+
+               const renderTargetProperties = properties.get( renderTarget );
+
+               const isCube = ( renderTarget.isWebGLCubeRenderTarget === true );
+
+               if ( renderTarget.depthTexture ) {
+
+                       if ( isCube ) throw new Error( 'target.depthTexture not supported in Cube render targets' );
+
+                       setupDepthTexture( renderTargetProperties.__webglFramebuffer, renderTarget );
+
+               } else {
+
+                       if ( isCube ) {
+
+                               renderTargetProperties.__webglDepthbuffer = [];
+
+                               for ( let i = 0; i < 6; i ++ ) {
+
+                                       _gl.bindFramebuffer( 36160, renderTargetProperties.__webglFramebuffer[ i ] );
+                                       renderTargetProperties.__webglDepthbuffer[ i ] = _gl.createRenderbuffer();
+                                       setupRenderBufferStorage( renderTargetProperties.__webglDepthbuffer[ i ], renderTarget, false );
+
+                               }
+
+                       } else {
+
+                               _gl.bindFramebuffer( 36160, renderTargetProperties.__webglFramebuffer );
+                               renderTargetProperties.__webglDepthbuffer = _gl.createRenderbuffer();
+                               setupRenderBufferStorage( renderTargetProperties.__webglDepthbuffer, renderTarget, false );
+
+                       }
+
+               }
+
+               _gl.bindFramebuffer( 36160, null );
+
+       }
+
+       // Set up GL resources for the render target
+       function setupRenderTarget( renderTarget ) {
+
+               const renderTargetProperties = properties.get( renderTarget );
+               const textureProperties = properties.get( renderTarget.texture );
+
+               renderTarget.addEventListener( 'dispose', onRenderTargetDispose );
+
+               textureProperties.__webglTexture = _gl.createTexture();
+
+               info.memory.textures ++;
+
+               const isCube = ( renderTarget.isWebGLCubeRenderTarget === true );
+               const isMultisample = ( renderTarget.isWebGLMultisampleRenderTarget === true );
+               const supportsMips = isPowerOfTwo( renderTarget ) || isWebGL2;
+
+               // Handles WebGL2 RGBFormat fallback - #18858
+
+               if ( isWebGL2 && renderTarget.texture.format === RGBFormat && ( renderTarget.texture.type === FloatType || renderTarget.texture.type === HalfFloatType ) ) {
+
+                       renderTarget.texture.format = RGBAFormat;
+
+                       console.warn( 'THREE.WebGLRenderer: Rendering to textures with RGB format is not supported. Using RGBA format instead.' );
+
+               }
+
+               // Setup framebuffer
+
+               if ( isCube ) {
+
+                       renderTargetProperties.__webglFramebuffer = [];
+
+                       for ( let i = 0; i < 6; i ++ ) {
+
+                               renderTargetProperties.__webglFramebuffer[ i ] = _gl.createFramebuffer();
+
+                       }
+
+               } else {
+
+                       renderTargetProperties.__webglFramebuffer = _gl.createFramebuffer();
+
+                       if ( isMultisample ) {
+
+                               if ( isWebGL2 ) {
+
+                                       renderTargetProperties.__webglMultisampledFramebuffer = _gl.createFramebuffer();
+                                       renderTargetProperties.__webglColorRenderbuffer = _gl.createRenderbuffer();
+
+                                       _gl.bindRenderbuffer( 36161, renderTargetProperties.__webglColorRenderbuffer );
+
+                                       const glFormat = utils.convert( renderTarget.texture.format );
+                                       const glType = utils.convert( renderTarget.texture.type );
+                                       const glInternalFormat = getInternalFormat( renderTarget.texture.internalFormat, glFormat, glType );
+                                       const samples = getRenderTargetSamples( renderTarget );
+                                       _gl.renderbufferStorageMultisample( 36161, samples, glInternalFormat, renderTarget.width, renderTarget.height );
+
+                                       _gl.bindFramebuffer( 36160, renderTargetProperties.__webglMultisampledFramebuffer );
+                                       _gl.framebufferRenderbuffer( 36160, 36064, 36161, renderTargetProperties.__webglColorRenderbuffer );
+                                       _gl.bindRenderbuffer( 36161, null );
+
+                                       if ( renderTarget.depthBuffer ) {
+
+                                               renderTargetProperties.__webglDepthRenderbuffer = _gl.createRenderbuffer();
+                                               setupRenderBufferStorage( renderTargetProperties.__webglDepthRenderbuffer, renderTarget, true );
+
+                                       }
+
+                                       _gl.bindFramebuffer( 36160, null );
+
+
+                               } else {
+
+                                       console.warn( 'THREE.WebGLRenderer: WebGLMultisampleRenderTarget can only be used with WebGL2.' );
+
+                               }
+
+                       }
+
+               }
+
+               // Setup color buffer
+
+               if ( isCube ) {
+
+                       state.bindTexture( 34067, textureProperties.__webglTexture );
+                       setTextureParameters( 34067, renderTarget.texture, supportsMips );
+
+                       for ( let i = 0; i < 6; i ++ ) {
+
+                               setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer[ i ], renderTarget, 36064, 34069 + i );
+
+                       }
+
+                       if ( textureNeedsGenerateMipmaps( renderTarget.texture, supportsMips ) ) {
+
+                               generateMipmap( 34067, renderTarget.texture, renderTarget.width, renderTarget.height );
+
+                       }
+
+                       state.bindTexture( 34067, null );
+
+               } else {
+
+                       state.bindTexture( 3553, textureProperties.__webglTexture );
+                       setTextureParameters( 3553, renderTarget.texture, supportsMips );
+                       setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, 36064, 3553 );
+
+                       if ( textureNeedsGenerateMipmaps( renderTarget.texture, supportsMips ) ) {
+
+                               generateMipmap( 3553, renderTarget.texture, renderTarget.width, renderTarget.height );
+
+                       }
+
+                       state.bindTexture( 3553, null );
+
+               }
+
+               // Setup depth and stencil buffers
+
+               if ( renderTarget.depthBuffer ) {
+
+                       setupDepthRenderbuffer( renderTarget );
+
+               }
+
+       }
+
+       function updateRenderTargetMipmap( renderTarget ) {
+
+               const texture = renderTarget.texture;
+               const supportsMips = isPowerOfTwo( renderTarget ) || isWebGL2;
+
+               if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) {
+
+                       const target = renderTarget.isWebGLCubeRenderTarget ? 34067 : 3553;
+                       const webglTexture = properties.get( texture ).__webglTexture;
+
+                       state.bindTexture( target, webglTexture );
+                       generateMipmap( target, texture, renderTarget.width, renderTarget.height );
+                       state.bindTexture( target, null );
+
+               }
+
+       }
+
+       function updateMultisampleRenderTarget( renderTarget ) {
+
+               if ( renderTarget.isWebGLMultisampleRenderTarget ) {
+
+                       if ( isWebGL2 ) {
+
+                               const renderTargetProperties = properties.get( renderTarget );
+
+                               _gl.bindFramebuffer( 36008, renderTargetProperties.__webglMultisampledFramebuffer );
+                               _gl.bindFramebuffer( 36009, renderTargetProperties.__webglFramebuffer );
+
+                               const width = renderTarget.width;
+                               const height = renderTarget.height;
+                               let mask = 16384;
+
+                               if ( renderTarget.depthBuffer ) mask |= 256;
+                               if ( renderTarget.stencilBuffer ) mask |= 1024;
+
+                               _gl.blitFramebuffer( 0, 0, width, height, 0, 0, width, height, mask, 9728 );
+
+                               _gl.bindFramebuffer( 36160, renderTargetProperties.__webglMultisampledFramebuffer ); // see #18905
+
+                       } else {
+
+                               console.warn( 'THREE.WebGLRenderer: WebGLMultisampleRenderTarget can only be used with WebGL2.' );
+
+                       }
+
+               }
+
+       }
+
+       function getRenderTargetSamples( renderTarget ) {
+
+               return ( isWebGL2 && renderTarget.isWebGLMultisampleRenderTarget ) ?
+                       Math.min( maxSamples, renderTarget.samples ) : 0;
+
+       }
+
+       function updateVideoTexture( texture ) {
+
+               const frame = info.render.frame;
+
+               // Check the last frame we updated the VideoTexture
+
+               if ( _videoTextures.get( texture ) !== frame ) {
+
+                       _videoTextures.set( texture, frame );
+                       texture.update();
+
+               }
+
+       }
+
+       // backwards compatibility
+
+       let warnedTexture2D = false;
+       let warnedTextureCube = false;
+
+       function safeSetTexture2D( texture, slot ) {
+
+               if ( texture && texture.isWebGLRenderTarget ) {
+
+                       if ( warnedTexture2D === false ) {
+
+                               console.warn( 'THREE.WebGLTextures.safeSetTexture2D: don\'t use render targets as textures. Use their .texture property instead.' );
+                               warnedTexture2D = true;
+
+                       }
+
+                       texture = texture.texture;
+
+               }
+
+               setTexture2D( texture, slot );
+
+       }
+
+       function safeSetTextureCube( texture, slot ) {
+
+               if ( texture && texture.isWebGLCubeRenderTarget ) {
+
+                       if ( warnedTextureCube === false ) {
+
+                               console.warn( 'THREE.WebGLTextures.safeSetTextureCube: don\'t use cube render targets as textures. Use their .texture property instead.' );
+                               warnedTextureCube = true;
+
+                       }
+
+                       texture = texture.texture;
+
+               }
+
+
+               setTextureCube( texture, slot );
+
+       }
+
+       //
+
+       this.allocateTextureUnit = allocateTextureUnit;
+       this.resetTextureUnits = resetTextureUnits;
+
+       this.setTexture2D = setTexture2D;
+       this.setTexture2DArray = setTexture2DArray;
+       this.setTexture3D = setTexture3D;
+       this.setTextureCube = setTextureCube;
+       this.setupRenderTarget = setupRenderTarget;
+       this.updateRenderTargetMipmap = updateRenderTargetMipmap;
+       this.updateMultisampleRenderTarget = updateMultisampleRenderTarget;
+
+       this.safeSetTexture2D = safeSetTexture2D;
+       this.safeSetTextureCube = safeSetTextureCube;
+
+}
+
+function WebGLUtils( gl, extensions, capabilities ) {
+
+       const isWebGL2 = capabilities.isWebGL2;
+
+       function convert( p ) {
+
+               let extension;
+
+               if ( p === UnsignedByteType ) return 5121;
+               if ( p === UnsignedShort4444Type ) return 32819;
+               if ( p === UnsignedShort5551Type ) return 32820;
+               if ( p === UnsignedShort565Type ) return 33635;
+
+               if ( p === ByteType ) return 5120;
+               if ( p === ShortType ) return 5122;
+               if ( p === UnsignedShortType ) return 5123;
+               if ( p === IntType ) return 5124;
+               if ( p === UnsignedIntType ) return 5125;
+               if ( p === FloatType ) return 5126;
+
+               if ( p === HalfFloatType ) {
+
+                       if ( isWebGL2 ) return 5131;
+
+                       extension = extensions.get( 'OES_texture_half_float' );
+
+                       if ( extension !== null ) {
+
+                               return extension.HALF_FLOAT_OES;
+
+                       } else {
+
+                               return null;
+
+                       }
+
+               }
+
+               if ( p === AlphaFormat ) return 6406;
+               if ( p === RGBFormat ) return 6407;
+               if ( p === RGBAFormat ) return 6408;
+               if ( p === LuminanceFormat ) return 6409;
+               if ( p === LuminanceAlphaFormat ) return 6410;
+               if ( p === DepthFormat ) return 6402;
+               if ( p === DepthStencilFormat ) return 34041;
+               if ( p === RedFormat ) return 6403;
+
+               // WebGL2 formats.
+
+               if ( p === RedIntegerFormat ) return 36244;
+               if ( p === RGFormat ) return 33319;
+               if ( p === RGIntegerFormat ) return 33320;
+               if ( p === RGBIntegerFormat ) return 36248;
+               if ( p === RGBAIntegerFormat ) return 36249;
+
+               if ( p === RGB_S3TC_DXT1_Format || p === RGBA_S3TC_DXT1_Format ||
+                       p === RGBA_S3TC_DXT3_Format || p === RGBA_S3TC_DXT5_Format ) {
+
+                       extension = extensions.get( 'WEBGL_compressed_texture_s3tc' );
+
+                       if ( extension !== null ) {
+
+                               if ( p === RGB_S3TC_DXT1_Format ) return extension.COMPRESSED_RGB_S3TC_DXT1_EXT;
+                               if ( p === RGBA_S3TC_DXT1_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT1_EXT;
+                               if ( p === RGBA_S3TC_DXT3_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT3_EXT;
+                               if ( p === RGBA_S3TC_DXT5_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT5_EXT;
+
+                       } else {
+
+                               return null;
+
+                       }
+
+               }
+
+               if ( p === RGB_PVRTC_4BPPV1_Format || p === RGB_PVRTC_2BPPV1_Format ||
+                       p === RGBA_PVRTC_4BPPV1_Format || p === RGBA_PVRTC_2BPPV1_Format ) {
+
+                       extension = extensions.get( 'WEBGL_compressed_texture_pvrtc' );
+
+                       if ( extension !== null ) {
+
+                               if ( p === RGB_PVRTC_4BPPV1_Format ) return extension.COMPRESSED_RGB_PVRTC_4BPPV1_IMG;
+                               if ( p === RGB_PVRTC_2BPPV1_Format ) return extension.COMPRESSED_RGB_PVRTC_2BPPV1_IMG;
+                               if ( p === RGBA_PVRTC_4BPPV1_Format ) return extension.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;
+                               if ( p === RGBA_PVRTC_2BPPV1_Format ) return extension.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG;
+
+                       } else {
+
+                               return null;
+
+                       }
+
+               }
+
+               if ( p === RGB_ETC1_Format ) {
+
+                       extension = extensions.get( 'WEBGL_compressed_texture_etc1' );
+
+                       if ( extension !== null ) {
+
+                               return extension.COMPRESSED_RGB_ETC1_WEBGL;
+
+                       } else {
+
+                               return null;
+
+                       }
+
+               }
+
+               if ( p === RGB_ETC2_Format || p === RGBA_ETC2_EAC_Format ) {
+
+                       extension = extensions.get( 'WEBGL_compressed_texture_etc' );
+
+                       if ( extension !== null ) {
+
+                               if ( p === RGB_ETC2_Format ) return extension.COMPRESSED_RGB8_ETC2;
+                               if ( p === RGBA_ETC2_EAC_Format ) return extension.COMPRESSED_RGBA8_ETC2_EAC;
+
+                       }
+
+               }
+
+               if ( p === RGBA_ASTC_4x4_Format || p === RGBA_ASTC_5x4_Format || p === RGBA_ASTC_5x5_Format ||
+                       p === RGBA_ASTC_6x5_Format || p === RGBA_ASTC_6x6_Format || p === RGBA_ASTC_8x5_Format ||
+                       p === RGBA_ASTC_8x6_Format || p === RGBA_ASTC_8x8_Format || p === RGBA_ASTC_10x5_Format ||
+                       p === RGBA_ASTC_10x6_Format || p === RGBA_ASTC_10x8_Format || p === RGBA_ASTC_10x10_Format ||
+                       p === RGBA_ASTC_12x10_Format || p === RGBA_ASTC_12x12_Format ||
+                       p === SRGB8_ALPHA8_ASTC_4x4_Format || p === SRGB8_ALPHA8_ASTC_5x4_Format || p === SRGB8_ALPHA8_ASTC_5x5_Format ||
+                       p === SRGB8_ALPHA8_ASTC_6x5_Format || p === SRGB8_ALPHA8_ASTC_6x6_Format || p === SRGB8_ALPHA8_ASTC_8x5_Format ||
+                       p === SRGB8_ALPHA8_ASTC_8x6_Format || p === SRGB8_ALPHA8_ASTC_8x8_Format || p === SRGB8_ALPHA8_ASTC_10x5_Format ||
+                       p === SRGB8_ALPHA8_ASTC_10x6_Format || p === SRGB8_ALPHA8_ASTC_10x8_Format || p === SRGB8_ALPHA8_ASTC_10x10_Format ||
+                       p === SRGB8_ALPHA8_ASTC_12x10_Format || p === SRGB8_ALPHA8_ASTC_12x12_Format ) {
+
+                       extension = extensions.get( 'WEBGL_compressed_texture_astc' );
+
+                       if ( extension !== null ) {
+
+                               // TODO Complete?
+
+                               return p;
+
+                       } else {
+
+                               return null;
+
+                       }
+
+               }
+
+               if ( p === RGBA_BPTC_Format ) {
+
+                       extension = extensions.get( 'EXT_texture_compression_bptc' );
+
+                       if ( extension !== null ) {
+
+                               // TODO Complete?
+
+                               return p;
+
+                       } else {
+
+                               return null;
+
+                       }
+
+               }
+
+               if ( p === UnsignedInt248Type ) {
+
+                       if ( isWebGL2 ) return 34042;
+
+                       extension = extensions.get( 'WEBGL_depth_texture' );
+
+                       if ( extension !== null ) {
+
+                               return extension.UNSIGNED_INT_24_8_WEBGL;
+
+                       } else {
+
+                               return null;
+
+                       }
+
+               }
+
+       }
+
+       return { convert: convert };
+
+}
+
+function ArrayCamera( array = [] ) {
+
+       PerspectiveCamera.call( this );
+
+       this.cameras = array;
+
+}
+
+ArrayCamera.prototype = Object.assign( Object.create( PerspectiveCamera.prototype ), {
+
+       constructor: ArrayCamera,
+
+       isArrayCamera: true
+
+} );
+
+function Group() {
+
+       Object3D.call( this );
+
+       this.type = 'Group';
+
+}
+
+Group.prototype = Object.assign( Object.create( Object3D.prototype ), {
+
+       constructor: Group,
+
+       isGroup: true
+
+} );
+
+function WebXRController() {
+
+       this._targetRay = null;
+       this._grip = null;
+       this._hand = null;
+
+}
+
+Object.assign( WebXRController.prototype, {
+
+       constructor: WebXRController,
+
+       getHandSpace: function () {
+
+               if ( this._hand === null ) {
+
+                       this._hand = new Group();
+                       this._hand.matrixAutoUpdate = false;
+                       this._hand.visible = false;
+
+                       this._hand.joints = {};
+                       this._hand.inputState = { pinching: false };
+
+               }
+
+               return this._hand;
+
+       },
+
+       getTargetRaySpace: function () {
+
+               if ( this._targetRay === null ) {
+
+                       this._targetRay = new Group();
+                       this._targetRay.matrixAutoUpdate = false;
+                       this._targetRay.visible = false;
+
+               }
+
+               return this._targetRay;
+
+       },
+
+       getGripSpace: function () {
+
+               if ( this._grip === null ) {
+
+                       this._grip = new Group();
+                       this._grip.matrixAutoUpdate = false;
+                       this._grip.visible = false;
+
+               }
+
+               return this._grip;
+
+       },
+
+       dispatchEvent: function ( event ) {
+
+               if ( this._targetRay !== null ) {
+
+                       this._targetRay.dispatchEvent( event );
+
+               }
+
+               if ( this._grip !== null ) {
+
+                       this._grip.dispatchEvent( event );
+
+               }
+
+               if ( this._hand !== null ) {
+
+                       this._hand.dispatchEvent( event );
+
+               }
+
+               return this;
+
+       },
+
+       disconnect: function ( inputSource ) {
+
+               this.dispatchEvent( { type: 'disconnected', data: inputSource } );
+
+               if ( this._targetRay !== null ) {
+
+                       this._targetRay.visible = false;
+
+               }
+
+               if ( this._grip !== null ) {
+
+                       this._grip.visible = false;
+
+               }
+
+               if ( this._hand !== null ) {
+
+                       this._hand.visible = false;
+
+               }
+
+               return this;
+
+       },
+
+       update: function ( inputSource, frame, referenceSpace ) {
+
+               let inputPose = null;
+               let gripPose = null;
+               let handPose = null;
+
+               const targetRay = this._targetRay;
+               const grip = this._grip;
+               const hand = this._hand;
+
+               if ( inputSource && frame.session.visibilityState !== 'visible-blurred' ) {
+
+                       if ( hand && inputSource.hand ) {
+
+                               handPose = true;
+
+                               for ( const inputjoint of inputSource.hand.values() ) {
+
+                                       // Update the joints groups with the XRJoint poses
+                                       const jointPose = frame.getJointPose( inputjoint, referenceSpace );
+
+                                       if ( hand.joints[ inputjoint.jointName ] === undefined ) {
+
+                                               // The transform of this joint will be updated with the joint pose on each frame
+                                               const joint = new Group();
+                                               joint.matrixAutoUpdate = false;
+                                               joint.visible = false;
+                                               hand.joints[ inputjoint.jointName ] = joint;
+                                               // ??
+                                               hand.add( joint );
+
+                                       }
+
+                                       const joint = hand.joints[ inputjoint.jointName ];
+
+                                       if ( jointPose !== null ) {
+
+                                               joint.matrix.fromArray( jointPose.transform.matrix );
+                                               joint.matrix.decompose( joint.position, joint.rotation, joint.scale );
+                                               joint.jointRadius = jointPose.radius;
+
+                                       }
+
+                                       joint.visible = jointPose !== null;
+
+                               }
+
+                               // Custom events
+
+                               // Check pinchz
+                               const indexTip = hand.joints[ 'index-finger-tip' ];
+                               const thumbTip = hand.joints[ 'thumb-tip' ];
+                               const distance = indexTip.position.distanceTo( thumbTip.position );
+
+                               const distanceToPinch = 0.02;
+                               const threshold = 0.005;
+
+                               if ( hand.inputState.pinching && distance > distanceToPinch + threshold ) {
+
+                                       hand.inputState.pinching = false;
+                                       this.dispatchEvent( {
+                                               type: 'pinchend',
+                                               handedness: inputSource.handedness,
+                                               target: this
+                                       } );
+
+                               } else if ( ! hand.inputState.pinching && distance <= distanceToPinch - threshold ) {
+
+                                       hand.inputState.pinching = true;
+                                       this.dispatchEvent( {
+                                               type: 'pinchstart',
+                                               handedness: inputSource.handedness,
+                                               target: this
+                                       } );
+
+                               }
+
+                       } else {
+
+                               if ( targetRay !== null ) {
+
+                                       inputPose = frame.getPose( inputSource.targetRaySpace, referenceSpace );
+
+                                       if ( inputPose !== null ) {
+
+                                               targetRay.matrix.fromArray( inputPose.transform.matrix );
+                                               targetRay.matrix.decompose( targetRay.position, targetRay.rotation, targetRay.scale );
+
+                                       }
+
+                               }
+
+                               if ( grip !== null && inputSource.gripSpace ) {
+
+                                       gripPose = frame.getPose( inputSource.gripSpace, referenceSpace );
+
+                                       if ( gripPose !== null ) {
+
+                                               grip.matrix.fromArray( gripPose.transform.matrix );
+                                               grip.matrix.decompose( grip.position, grip.rotation, grip.scale );
+
+                                       }
+
+                               }
+
+                       }
+
+               }
+
+               if ( targetRay !== null ) {
+
+                       targetRay.visible = ( inputPose !== null );
+
+               }
+
+               if ( grip !== null ) {
+
+                       grip.visible = ( gripPose !== null );
+
+               }
+
+               if ( hand !== null ) {
+
+                       hand.visible = ( handPose !== null );
+
+               }
+
+               return this;
+
+       }
+
+} );
+
+function WebXRManager( renderer, gl ) {
+
+       const scope = this;
+
+       let session = null;
+
+       let framebufferScaleFactor = 1.0;
+
+       let referenceSpace = null;
+       let referenceSpaceType = 'local-floor';
+
+       let pose = null;
+
+       const controllers = [];
+       const inputSourcesMap = new Map();
+
+       //
+
+       const cameraL = new PerspectiveCamera();
+       cameraL.layers.enable( 1 );
+       cameraL.viewport = new Vector4();
+
+       const cameraR = new PerspectiveCamera();
+       cameraR.layers.enable( 2 );
+       cameraR.viewport = new Vector4();
+
+       const cameras = [ cameraL, cameraR ];
+
+       const cameraVR = new ArrayCamera();
+       cameraVR.layers.enable( 1 );
+       cameraVR.layers.enable( 2 );
+
+       let _currentDepthNear = null;
+       let _currentDepthFar = null;
+
+       //
+
+       this.enabled = false;
+
+       this.isPresenting = false;
+
+       this.getController = function ( index ) {
+
+               let controller = controllers[ index ];
+
+               if ( controller === undefined ) {
+
+                       controller = new WebXRController();
+                       controllers[ index ] = controller;
+
+               }
+
+               return controller.getTargetRaySpace();
+
+       };
+
+       this.getControllerGrip = function ( index ) {
+
+               let controller = controllers[ index ];
+
+               if ( controller === undefined ) {
+
+                       controller = new WebXRController();
+                       controllers[ index ] = controller;
+
+               }
+
+               return controller.getGripSpace();
+
+       };
+
+       this.getHand = function ( index ) {
+
+               let controller = controllers[ index ];
+
+               if ( controller === undefined ) {
+
+                       controller = new WebXRController();
+                       controllers[ index ] = controller;
+
+               }
+
+               return controller.getHandSpace();
+
+       };
+
+       //
+
+       function onSessionEvent( event ) {
+
+               const controller = inputSourcesMap.get( event.inputSource );
+
+               if ( controller ) {
+
+                       controller.dispatchEvent( { type: event.type, data: event.inputSource } );
+
+               }
+
+       }
+
+       function onSessionEnd() {
+
+               inputSourcesMap.forEach( function ( controller, inputSource ) {
+
+                       controller.disconnect( inputSource );
+
+               } );
+
+               inputSourcesMap.clear();
+
+               _currentDepthNear = null;
+               _currentDepthFar = null;
+
+               //
+
+               renderer.setFramebuffer( null );
+               renderer.setRenderTarget( renderer.getRenderTarget() ); // Hack #15830
+               animation.stop();
+
+               scope.isPresenting = false;
+
+               scope.dispatchEvent( { type: 'sessionend' } );
+
+       }
+
+       this.setFramebufferScaleFactor = function ( value ) {
+
+               framebufferScaleFactor = value;
+
+               if ( scope.isPresenting === true ) {
+
+                       console.warn( 'THREE.WebXRManager: Cannot change framebuffer scale while presenting.' );
+
+               }
+
+       };
+
+       this.setReferenceSpaceType = function ( value ) {
+
+               referenceSpaceType = value;
+
+               if ( scope.isPresenting === true ) {
+
+                       console.warn( 'THREE.WebXRManager: Cannot change reference space type while presenting.' );
+
+               }
+
+       };
+
+       this.getReferenceSpace = function () {
+
+               return referenceSpace;
+
+       };
+
+       this.getSession = function () {
+
+               return session;
+
+       };
+
+       this.setSession = async function ( value ) {
+
+               session = value;
+
+               if ( session !== null ) {
+
+                       session.addEventListener( 'select', onSessionEvent );
+                       session.addEventListener( 'selectstart', onSessionEvent );
+                       session.addEventListener( 'selectend', onSessionEvent );
+                       session.addEventListener( 'squeeze', onSessionEvent );
+                       session.addEventListener( 'squeezestart', onSessionEvent );
+                       session.addEventListener( 'squeezeend', onSessionEvent );
+                       session.addEventListener( 'end', onSessionEnd );
+                       session.addEventListener( 'inputsourceschange', onInputSourcesChange );
+
+                       const attributes = gl.getContextAttributes();
+
+                       if ( attributes.xrCompatible !== true ) {
+
+                               await gl.makeXRCompatible();
+
+                       }
+
+                       const layerInit = {
+                               antialias: attributes.antialias,
+                               alpha: attributes.alpha,
+                               depth: attributes.depth,
+                               stencil: attributes.stencil,
+                               framebufferScaleFactor: framebufferScaleFactor
+                       };
+
+                       // eslint-disable-next-line no-undef
+                       const baseLayer = new XRWebGLLayer( session, gl, layerInit );
+
+                       session.updateRenderState( { baseLayer: baseLayer } );
+
+                       referenceSpace = await session.requestReferenceSpace( referenceSpaceType );
+
+                       animation.setContext( session );
+                       animation.start();
+
+                       scope.isPresenting = true;
+
+                       scope.dispatchEvent( { type: 'sessionstart' } );
+
+               }
+
+       };
+
+       function onInputSourcesChange( event ) {
+
+               const inputSources = session.inputSources;
+
+               // Assign inputSources to available controllers
+
+               for ( let i = 0; i < controllers.length; i ++ ) {
+
+                       inputSourcesMap.set( inputSources[ i ], controllers[ i ] );
+
+               }
+
+               // Notify disconnected
+
+               for ( let i = 0; i < event.removed.length; i ++ ) {
+
+                       const inputSource = event.removed[ i ];
+                       const controller = inputSourcesMap.get( inputSource );
+
+                       if ( controller ) {
+
+                               controller.dispatchEvent( { type: 'disconnected', data: inputSource } );
+                               inputSourcesMap.delete( inputSource );
+
+                       }
+
+               }
+
+               // Notify connected
+
+               for ( let i = 0; i < event.added.length; i ++ ) {
+
+                       const inputSource = event.added[ i ];
+                       const controller = inputSourcesMap.get( inputSource );
+
+                       if ( controller ) {
+
+                               controller.dispatchEvent( { type: 'connected', data: inputSource } );
+
+                       }
+
+               }
+
+       }
+
+       //
+
+       const cameraLPos = new Vector3();
+       const cameraRPos = new Vector3();
+
+       /**
+        * Assumes 2 cameras that are parallel and share an X-axis, and that
+        * the cameras' projection and world matrices have already been set.
+        * And that near and far planes are identical for both cameras.
+        * Visualization of this technique: https://computergraphics.stackexchange.com/a/4765
+        */
+       function setProjectionFromUnion( camera, cameraL, cameraR ) {
+
+               cameraLPos.setFromMatrixPosition( cameraL.matrixWorld );
+               cameraRPos.setFromMatrixPosition( cameraR.matrixWorld );
+
+               const ipd = cameraLPos.distanceTo( cameraRPos );
+
+               const projL = cameraL.projectionMatrix.elements;
+               const projR = cameraR.projectionMatrix.elements;
+
+               // VR systems will have identical far and near planes, and
+               // most likely identical top and bottom frustum extents.
+               // Use the left camera for these values.
+               const near = projL[ 14 ] / ( projL[ 10 ] - 1 );
+               const far = projL[ 14 ] / ( projL[ 10 ] + 1 );
+               const topFov = ( projL[ 9 ] + 1 ) / projL[ 5 ];
+               const bottomFov = ( projL[ 9 ] - 1 ) / projL[ 5 ];
+
+               const leftFov = ( projL[ 8 ] - 1 ) / projL[ 0 ];
+               const rightFov = ( projR[ 8 ] + 1 ) / projR[ 0 ];
+               const left = near * leftFov;
+               const right = near * rightFov;
+
+               // Calculate the new camera's position offset from the
+               // left camera. xOffset should be roughly half `ipd`.
+               const zOffset = ipd / ( - leftFov + rightFov );
+               const xOffset = zOffset * - leftFov;
+
+               // TODO: Better way to apply this offset?
+               cameraL.matrixWorld.decompose( camera.position, camera.quaternion, camera.scale );
+               camera.translateX( xOffset );
+               camera.translateZ( zOffset );
+               camera.matrixWorld.compose( camera.position, camera.quaternion, camera.scale );
+               camera.matrixWorldInverse.copy( camera.matrixWorld ).invert();
+
+               // Find the union of the frustum values of the cameras and scale
+               // the values so that the near plane's position does not change in world space,
+               // although must now be relative to the new union camera.
+               const near2 = near + zOffset;
+               const far2 = far + zOffset;
+               const left2 = left - xOffset;
+               const right2 = right + ( ipd - xOffset );
+               const top2 = topFov * far / far2 * near2;
+               const bottom2 = bottomFov * far / far2 * near2;
+
+               camera.projectionMatrix.makePerspective( left2, right2, top2, bottom2, near2, far2 );
+
+       }
+
+       function updateCamera( camera, parent ) {
+
+               if ( parent === null ) {
+
+                       camera.matrixWorld.copy( camera.matrix );
+
+               } else {
+
+                       camera.matrixWorld.multiplyMatrices( parent.matrixWorld, camera.matrix );
+
+               }
+
+               camera.matrixWorldInverse.copy( camera.matrixWorld ).invert();
+
+       }
+
+       this.getCamera = function ( camera ) {
+
+               cameraVR.near = cameraR.near = cameraL.near = camera.near;
+               cameraVR.far = cameraR.far = cameraL.far = camera.far;
+
+               if ( _currentDepthNear !== cameraVR.near || _currentDepthFar !== cameraVR.far ) {
+
+                       // Note that the new renderState won't apply until the next frame. See #18320
+
+                       session.updateRenderState( {
+                               depthNear: cameraVR.near,
+                               depthFar: cameraVR.far
+                       } );
+
+                       _currentDepthNear = cameraVR.near;
+                       _currentDepthFar = cameraVR.far;
+
+               }
+
+               const parent = camera.parent;
+               const cameras = cameraVR.cameras;
+
+               updateCamera( cameraVR, parent );
+
+               for ( let i = 0; i < cameras.length; i ++ ) {
+
+                       updateCamera( cameras[ i ], parent );
+
+               }
+
+               // update camera and its children
+
+               camera.matrixWorld.copy( cameraVR.matrixWorld );
+               camera.matrix.copy( cameraVR.matrix );
+               camera.matrix.decompose( camera.position, camera.quaternion, camera.scale );
+
+               const children = camera.children;
+
+               for ( let i = 0, l = children.length; i < l; i ++ ) {
+
+                       children[ i ].updateMatrixWorld( true );
+
+               }
+
+               // update projection matrix for proper view frustum culling
+
+               if ( cameras.length === 2 ) {
+
+                       setProjectionFromUnion( cameraVR, cameraL, cameraR );
+
+               } else {
+
+                       // assume single camera setup (AR)
+
+                       cameraVR.projectionMatrix.copy( cameraL.projectionMatrix );
+
+               }
+
+               return cameraVR;
+
+       };
+
+       // Animation Loop
+
+       let onAnimationFrameCallback = null;
+
+       function onAnimationFrame( time, frame ) {
+
+               pose = frame.getViewerPose( referenceSpace );
+
+               if ( pose !== null ) {
+
+                       const views = pose.views;
+                       const baseLayer = session.renderState.baseLayer;
+
+                       renderer.setFramebuffer( baseLayer.framebuffer );
+
+                       let cameraVRNeedsUpdate = false;
+
+                       // check if it's necessary to rebuild cameraVR's camera list
+
+                       if ( views.length !== cameraVR.cameras.length ) {
+
+                               cameraVR.cameras.length = 0;
+                               cameraVRNeedsUpdate = true;
+
+                       }
+
+                       for ( let i = 0; i < views.length; i ++ ) {
+
+                               const view = views[ i ];
+                               const viewport = baseLayer.getViewport( view );
+
+                               const camera = cameras[ i ];
+                               camera.matrix.fromArray( view.transform.matrix );
+                               camera.projectionMatrix.fromArray( view.projectionMatrix );
+                               camera.viewport.set( viewport.x, viewport.y, viewport.width, viewport.height );
+
+                               if ( i === 0 ) {
+
+                                       cameraVR.matrix.copy( camera.matrix );
+
+                               }
+
+                               if ( cameraVRNeedsUpdate === true ) {
+
+                                       cameraVR.cameras.push( camera );
+
+                               }
+
+                       }
+
+               }
+
+               //
+
+               const inputSources = session.inputSources;
+
+               for ( let i = 0; i < controllers.length; i ++ ) {
+
+                       const controller = controllers[ i ];
+                       const inputSource = inputSources[ i ];
+
+                       controller.update( inputSource, frame, referenceSpace );
+
+               }
+
+               if ( onAnimationFrameCallback ) onAnimationFrameCallback( time, frame );
+
+       }
+
+       const animation = new WebGLAnimation();
+       animation.setAnimationLoop( onAnimationFrame );
+
+       this.setAnimationLoop = function ( callback ) {
+
+               onAnimationFrameCallback = callback;
+
+       };
+
+       this.dispose = function () {};
+
+}
+
+Object.assign( WebXRManager.prototype, EventDispatcher.prototype );
+
+function WebGLMaterials( properties ) {
+
+       function refreshFogUniforms( uniforms, fog ) {
+
+               uniforms.fogColor.value.copy( fog.color );
+
+               if ( fog.isFog ) {
+
+                       uniforms.fogNear.value = fog.near;
+                       uniforms.fogFar.value = fog.far;
+
+               } else if ( fog.isFogExp2 ) {
+
+                       uniforms.fogDensity.value = fog.density;
+
+               }
+
+       }
+
+       function refreshMaterialUniforms( uniforms, material, pixelRatio, height ) {
+
+               if ( material.isMeshBasicMaterial ) {
+
+                       refreshUniformsCommon( uniforms, material );
+
+               } else if ( material.isMeshLambertMaterial ) {
+
+                       refreshUniformsCommon( uniforms, material );
+                       refreshUniformsLambert( uniforms, material );
+
+               } else if ( material.isMeshToonMaterial ) {
+
+                       refreshUniformsCommon( uniforms, material );
+                       refreshUniformsToon( uniforms, material );
+
+               } else if ( material.isMeshPhongMaterial ) {
+
+                       refreshUniformsCommon( uniforms, material );
+                       refreshUniformsPhong( uniforms, material );
+
+               } else if ( material.isMeshStandardMaterial ) {
+
+                       refreshUniformsCommon( uniforms, material );
+
+                       if ( material.isMeshPhysicalMaterial ) {
+
+                               refreshUniformsPhysical( uniforms, material );
+
+                       } else {
+
+                               refreshUniformsStandard( uniforms, material );
+
+                       }
+
+               } else if ( material.isMeshMatcapMaterial ) {
+
+                       refreshUniformsCommon( uniforms, material );
+                       refreshUniformsMatcap( uniforms, material );
+
+               } else if ( material.isMeshDepthMaterial ) {
+
+                       refreshUniformsCommon( uniforms, material );
+                       refreshUniformsDepth( uniforms, material );
+
+               } else if ( material.isMeshDistanceMaterial ) {
+
+                       refreshUniformsCommon( uniforms, material );
+                       refreshUniformsDistance( uniforms, material );
+
+               } else if ( material.isMeshNormalMaterial ) {
+
+                       refreshUniformsCommon( uniforms, material );
+                       refreshUniformsNormal( uniforms, material );
+
+               } else if ( material.isLineBasicMaterial ) {
+
+                       refreshUniformsLine( uniforms, material );
+
+                       if ( material.isLineDashedMaterial ) {
+
+                               refreshUniformsDash( uniforms, material );
+
+                       }
+
+               } else if ( material.isPointsMaterial ) {
+
+                       refreshUniformsPoints( uniforms, material, pixelRatio, height );
+
+               } else if ( material.isSpriteMaterial ) {
+
+                       refreshUniformsSprites( uniforms, material );
+
+               } else if ( material.isShadowMaterial ) {
+
+                       uniforms.color.value.copy( material.color );
+                       uniforms.opacity.value = material.opacity;
+
+               } else if ( material.isShaderMaterial ) {
+
+                       material.uniformsNeedUpdate = false; // #15581
+
+               }
+
+       }
+
+       function refreshUniformsCommon( uniforms, material ) {
+
+               uniforms.opacity.value = material.opacity;
+
+               if ( material.color ) {
+
+                       uniforms.diffuse.value.copy( material.color );
+
+               }
+
+               if ( material.emissive ) {
+
+                       uniforms.emissive.value.copy( material.emissive ).multiplyScalar( material.emissiveIntensity );
+
+               }
+
+               if ( material.map ) {
+
+                       uniforms.map.value = material.map;
+
+               }
+
+               if ( material.alphaMap ) {
+
+                       uniforms.alphaMap.value = material.alphaMap;
+
+               }
+
+               if ( material.specularMap ) {
+
+                       uniforms.specularMap.value = material.specularMap;
+
+               }
+
+               const envMap = properties.get( material ).envMap;
+
+               if ( envMap ) {
+
+                       uniforms.envMap.value = envMap;
+
+                       uniforms.flipEnvMap.value = ( envMap.isCubeTexture && envMap._needsFlipEnvMap ) ? - 1 : 1;
+
+                       uniforms.reflectivity.value = material.reflectivity;
+                       uniforms.refractionRatio.value = material.refractionRatio;
+
+                       const maxMipLevel = properties.get( envMap ).__maxMipLevel;
+
+                       if ( maxMipLevel !== undefined ) {
+
+                               uniforms.maxMipLevel.value = maxMipLevel;
+
+                       }
+
+               }
+
+               if ( material.lightMap ) {
+
+                       uniforms.lightMap.value = material.lightMap;
+                       uniforms.lightMapIntensity.value = material.lightMapIntensity;
+
+               }
+
+               if ( material.aoMap ) {
+
+                       uniforms.aoMap.value = material.aoMap;
+                       uniforms.aoMapIntensity.value = material.aoMapIntensity;
+
+               }
+
+               // uv repeat and offset setting priorities
+               // 1. color map
+               // 2. specular map
+               // 3. displacementMap map
+               // 4. normal map
+               // 5. bump map
+               // 6. roughnessMap map
+               // 7. metalnessMap map
+               // 8. alphaMap map
+               // 9. emissiveMap map
+               // 10. clearcoat map
+               // 11. clearcoat normal map
+               // 12. clearcoat roughnessMap map
+
+               let uvScaleMap;
+
+               if ( material.map ) {
+
+                       uvScaleMap = material.map;
+
+               } else if ( material.specularMap ) {
+
+                       uvScaleMap = material.specularMap;
+
+               } else if ( material.displacementMap ) {
+
+                       uvScaleMap = material.displacementMap;
+
+               } else if ( material.normalMap ) {
+
+                       uvScaleMap = material.normalMap;
+
+               } else if ( material.bumpMap ) {
+
+                       uvScaleMap = material.bumpMap;
+
+               } else if ( material.roughnessMap ) {
+
+                       uvScaleMap = material.roughnessMap;
+
+               } else if ( material.metalnessMap ) {
+
+                       uvScaleMap = material.metalnessMap;
+
+               } else if ( material.alphaMap ) {
+
+                       uvScaleMap = material.alphaMap;
+
+               } else if ( material.emissiveMap ) {
+
+                       uvScaleMap = material.emissiveMap;
+
+               } else if ( material.clearcoatMap ) {
+
+                       uvScaleMap = material.clearcoatMap;
+
+               } else if ( material.clearcoatNormalMap ) {
+
+                       uvScaleMap = material.clearcoatNormalMap;
+
+               } else if ( material.clearcoatRoughnessMap ) {
+
+                       uvScaleMap = material.clearcoatRoughnessMap;
+
+               }
+
+               if ( uvScaleMap !== undefined ) {
+
+                       // backwards compatibility
+                       if ( uvScaleMap.isWebGLRenderTarget ) {
+
+                               uvScaleMap = uvScaleMap.texture;
+
+                       }
+
+                       if ( uvScaleMap.matrixAutoUpdate === true ) {
+
+                               uvScaleMap.updateMatrix();
+
+                       }
+
+                       uniforms.uvTransform.value.copy( uvScaleMap.matrix );
+
+               }
+
+               // uv repeat and offset setting priorities for uv2
+               // 1. ao map
+               // 2. light map
+
+               let uv2ScaleMap;
+
+               if ( material.aoMap ) {
+
+                       uv2ScaleMap = material.aoMap;
+
+               } else if ( material.lightMap ) {
+
+                       uv2ScaleMap = material.lightMap;
+
+               }
+
+               if ( uv2ScaleMap !== undefined ) {
+
+                       // backwards compatibility
+                       if ( uv2ScaleMap.isWebGLRenderTarget ) {
+
+                               uv2ScaleMap = uv2ScaleMap.texture;
+
+                       }
+
+                       if ( uv2ScaleMap.matrixAutoUpdate === true ) {
+
+                               uv2ScaleMap.updateMatrix();
+
+                       }
+
+                       uniforms.uv2Transform.value.copy( uv2ScaleMap.matrix );
+
+               }
+
+       }
+
+       function refreshUniformsLine( uniforms, material ) {
+
+               uniforms.diffuse.value.copy( material.color );
+               uniforms.opacity.value = material.opacity;
+
+       }
+
+       function refreshUniformsDash( uniforms, material ) {
+
+               uniforms.dashSize.value = material.dashSize;
+               uniforms.totalSize.value = material.dashSize + material.gapSize;
+               uniforms.scale.value = material.scale;
+
+       }
+
+       function refreshUniformsPoints( uniforms, material, pixelRatio, height ) {
+
+               uniforms.diffuse.value.copy( material.color );
+               uniforms.opacity.value = material.opacity;
+               uniforms.size.value = material.size * pixelRatio;
+               uniforms.scale.value = height * 0.5;
+
+               if ( material.map ) {
+
+                       uniforms.map.value = material.map;
+
+               }
+
+               if ( material.alphaMap ) {
+
+                       uniforms.alphaMap.value = material.alphaMap;
+
+               }
+
+               // uv repeat and offset setting priorities
+               // 1. color map
+               // 2. alpha map
+
+               let uvScaleMap;
+
+               if ( material.map ) {
+
+                       uvScaleMap = material.map;
+
+               } else if ( material.alphaMap ) {
+
+                       uvScaleMap = material.alphaMap;
+
+               }
+
+               if ( uvScaleMap !== undefined ) {
+
+                       if ( uvScaleMap.matrixAutoUpdate === true ) {
+
+                               uvScaleMap.updateMatrix();
+
+                       }
+
+                       uniforms.uvTransform.value.copy( uvScaleMap.matrix );
+
+               }
+
+       }
+
+       function refreshUniformsSprites( uniforms, material ) {
+
+               uniforms.diffuse.value.copy( material.color );
+               uniforms.opacity.value = material.opacity;
+               uniforms.rotation.value = material.rotation;
+
+               if ( material.map ) {
+
+                       uniforms.map.value = material.map;
+
+               }
+
+               if ( material.alphaMap ) {
+
+                       uniforms.alphaMap.value = material.alphaMap;
+
+               }
+
+               // uv repeat and offset setting priorities
+               // 1. color map
+               // 2. alpha map
+
+               let uvScaleMap;
+
+               if ( material.map ) {
+
+                       uvScaleMap = material.map;
+
+               } else if ( material.alphaMap ) {
+
+                       uvScaleMap = material.alphaMap;
+
+               }
+
+               if ( uvScaleMap !== undefined ) {
+
+                       if ( uvScaleMap.matrixAutoUpdate === true ) {
+
+                               uvScaleMap.updateMatrix();
+
+                       }
+
+                       uniforms.uvTransform.value.copy( uvScaleMap.matrix );
+
+               }
+
+       }
+
+       function refreshUniformsLambert( uniforms, material ) {
+
+               if ( material.emissiveMap ) {
+
+                       uniforms.emissiveMap.value = material.emissiveMap;
+
+               }
+
+       }
+
+       function refreshUniformsPhong( uniforms, material ) {
+
+               uniforms.specular.value.copy( material.specular );
+               uniforms.shininess.value = Math.max( material.shininess, 1e-4 ); // to prevent pow( 0.0, 0.0 )
+
+               if ( material.emissiveMap ) {
+
+                       uniforms.emissiveMap.value = material.emissiveMap;
+
+               }
+
+               if ( material.bumpMap ) {
+
+                       uniforms.bumpMap.value = material.bumpMap;
+                       uniforms.bumpScale.value = material.bumpScale;
+                       if ( material.side === BackSide ) uniforms.bumpScale.value *= - 1;
+
+               }
+
+               if ( material.normalMap ) {
+
+                       uniforms.normalMap.value = material.normalMap;
+                       uniforms.normalScale.value.copy( material.normalScale );
+                       if ( material.side === BackSide ) uniforms.normalScale.value.negate();
+
+               }
+
+               if ( material.displacementMap ) {
+
+                       uniforms.displacementMap.value = material.displacementMap;
+                       uniforms.displacementScale.value = material.displacementScale;
+                       uniforms.displacementBias.value = material.displacementBias;
+
+               }
+
+       }
+
+       function refreshUniformsToon( uniforms, material ) {
+
+               if ( material.gradientMap ) {
+
+                       uniforms.gradientMap.value = material.gradientMap;
+
+               }
+
+               if ( material.emissiveMap ) {
+
+                       uniforms.emissiveMap.value = material.emissiveMap;
+
+               }
+
+               if ( material.bumpMap ) {
+
+                       uniforms.bumpMap.value = material.bumpMap;
+                       uniforms.bumpScale.value = material.bumpScale;
+                       if ( material.side === BackSide ) uniforms.bumpScale.value *= - 1;
+
+               }
+
+               if ( material.normalMap ) {
+
+                       uniforms.normalMap.value = material.normalMap;
+                       uniforms.normalScale.value.copy( material.normalScale );
+                       if ( material.side === BackSide ) uniforms.normalScale.value.negate();
+
+               }
+
+               if ( material.displacementMap ) {
+
+                       uniforms.displacementMap.value = material.displacementMap;
+                       uniforms.displacementScale.value = material.displacementScale;
+                       uniforms.displacementBias.value = material.displacementBias;
+
+               }
+
+       }
+
+       function refreshUniformsStandard( uniforms, material ) {
+
+               uniforms.roughness.value = material.roughness;
+               uniforms.metalness.value = material.metalness;
+
+               if ( material.roughnessMap ) {
+
+                       uniforms.roughnessMap.value = material.roughnessMap;
+
+               }
+
+               if ( material.metalnessMap ) {
+
+                       uniforms.metalnessMap.value = material.metalnessMap;
+
+               }
+
+               if ( material.emissiveMap ) {
+
+                       uniforms.emissiveMap.value = material.emissiveMap;
+
+               }
+
+               if ( material.bumpMap ) {
+
+                       uniforms.bumpMap.value = material.bumpMap;
+                       uniforms.bumpScale.value = material.bumpScale;
+                       if ( material.side === BackSide ) uniforms.bumpScale.value *= - 1;
+
+               }
+
+               if ( material.normalMap ) {
+
+                       uniforms.normalMap.value = material.normalMap;
+                       uniforms.normalScale.value.copy( material.normalScale );
+                       if ( material.side === BackSide ) uniforms.normalScale.value.negate();
+
+               }
+
+               if ( material.displacementMap ) {
+
+                       uniforms.displacementMap.value = material.displacementMap;
+                       uniforms.displacementScale.value = material.displacementScale;
+                       uniforms.displacementBias.value = material.displacementBias;
+
+               }
+
+               const envMap = properties.get( material ).envMap;
+
+               if ( envMap ) {
+
+                       //uniforms.envMap.value = material.envMap; // part of uniforms common
+                       uniforms.envMapIntensity.value = material.envMapIntensity;
+
+               }
+
+       }
+
+       function refreshUniformsPhysical( uniforms, material ) {
+
+               refreshUniformsStandard( uniforms, material );
+
+               uniforms.reflectivity.value = material.reflectivity; // also part of uniforms common
+
+               uniforms.clearcoat.value = material.clearcoat;
+               uniforms.clearcoatRoughness.value = material.clearcoatRoughness;
+               if ( material.sheen ) uniforms.sheen.value.copy( material.sheen );
+
+               if ( material.clearcoatMap ) {
+
+                       uniforms.clearcoatMap.value = material.clearcoatMap;
+
+               }
+
+               if ( material.clearcoatRoughnessMap ) {
+
+                       uniforms.clearcoatRoughnessMap.value = material.clearcoatRoughnessMap;
+
+               }
+
+               if ( material.clearcoatNormalMap ) {
+
+                       uniforms.clearcoatNormalScale.value.copy( material.clearcoatNormalScale );
+                       uniforms.clearcoatNormalMap.value = material.clearcoatNormalMap;
+
+                       if ( material.side === BackSide ) {
+
+                               uniforms.clearcoatNormalScale.value.negate();
+
+                       }
+
+               }
+
+               uniforms.transmission.value = material.transmission;
+
+               if ( material.transmissionMap ) {
+
+                       uniforms.transmissionMap.value = material.transmissionMap;
+
+               }
+
+       }
+
+       function refreshUniformsMatcap( uniforms, material ) {
+
+               if ( material.matcap ) {
+
+                       uniforms.matcap.value = material.matcap;
+
+               }
+
+               if ( material.bumpMap ) {
+
+                       uniforms.bumpMap.value = material.bumpMap;
+                       uniforms.bumpScale.value = material.bumpScale;
+                       if ( material.side === BackSide ) uniforms.bumpScale.value *= - 1;
+
+               }
+
+               if ( material.normalMap ) {
+
+                       uniforms.normalMap.value = material.normalMap;
+                       uniforms.normalScale.value.copy( material.normalScale );
+                       if ( material.side === BackSide ) uniforms.normalScale.value.negate();
+
+               }
+
+               if ( material.displacementMap ) {
+
+                       uniforms.displacementMap.value = material.displacementMap;
+                       uniforms.displacementScale.value = material.displacementScale;
+                       uniforms.displacementBias.value = material.displacementBias;
+
+               }
+
+       }
+
+       function refreshUniformsDepth( uniforms, material ) {
+
+               if ( material.displacementMap ) {
+
+                       uniforms.displacementMap.value = material.displacementMap;
+                       uniforms.displacementScale.value = material.displacementScale;
+                       uniforms.displacementBias.value = material.displacementBias;
+
+               }
+
+       }
+
+       function refreshUniformsDistance( uniforms, material ) {
+
+               if ( material.displacementMap ) {
+
+                       uniforms.displacementMap.value = material.displacementMap;
+                       uniforms.displacementScale.value = material.displacementScale;
+                       uniforms.displacementBias.value = material.displacementBias;
+
+               }
+
+               uniforms.referencePosition.value.copy( material.referencePosition );
+               uniforms.nearDistance.value = material.nearDistance;
+               uniforms.farDistance.value = material.farDistance;
+
+       }
+
+       function refreshUniformsNormal( uniforms, material ) {
+
+               if ( material.bumpMap ) {
+
+                       uniforms.bumpMap.value = material.bumpMap;
+                       uniforms.bumpScale.value = material.bumpScale;
+                       if ( material.side === BackSide ) uniforms.bumpScale.value *= - 1;
+
+               }
+
+               if ( material.normalMap ) {
+
+                       uniforms.normalMap.value = material.normalMap;
+                       uniforms.normalScale.value.copy( material.normalScale );
+                       if ( material.side === BackSide ) uniforms.normalScale.value.negate();
+
+               }
+
+               if ( material.displacementMap ) {
+
+                       uniforms.displacementMap.value = material.displacementMap;
+                       uniforms.displacementScale.value = material.displacementScale;
+                       uniforms.displacementBias.value = material.displacementBias;
+
+               }
+
+       }
+
+       return {
+               refreshFogUniforms: refreshFogUniforms,
+               refreshMaterialUniforms: refreshMaterialUniforms
+       };
+
+}
+
+function createCanvasElement() {
+
+       const canvas = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' );
+       canvas.style.display = 'block';
+       return canvas;
+
+}
+
+function WebGLRenderer( parameters ) {
+
+       parameters = parameters || {};
+
+       const _canvas = parameters.canvas !== undefined ? parameters.canvas : createCanvasElement(),
+               _context = parameters.context !== undefined ? parameters.context : null,
+
+               _alpha = parameters.alpha !== undefined ? parameters.alpha : false,
+               _depth = parameters.depth !== undefined ? parameters.depth : true,
+               _stencil = parameters.stencil !== undefined ? parameters.stencil : true,
+               _antialias = parameters.antialias !== undefined ? parameters.antialias : false,
+               _premultipliedAlpha = parameters.premultipliedAlpha !== undefined ? parameters.premultipliedAlpha : true,
+               _preserveDrawingBuffer = parameters.preserveDrawingBuffer !== undefined ? parameters.preserveDrawingBuffer : false,
+               _powerPreference = parameters.powerPreference !== undefined ? parameters.powerPreference : 'default',
+               _failIfMajorPerformanceCaveat = parameters.failIfMajorPerformanceCaveat !== undefined ? parameters.failIfMajorPerformanceCaveat : false;
+
+       let currentRenderList = null;
+       let currentRenderState = null;
+
+       // render() can be called from within a callback triggered by another render.
+       // We track this so that the nested render call gets its state isolated from the parent render call.
+
+       const renderStateStack = [];
+
+       // public properties
+
+       this.domElement = _canvas;
+
+       // Debug configuration container
+       this.debug = {
+
+               /**
+                * Enables error checking and reporting when shader programs are being compiled
+                * @type {boolean}
+                */
+               checkShaderErrors: true
+       };
+
+       // clearing
+
+       this.autoClear = true;
+       this.autoClearColor = true;
+       this.autoClearDepth = true;
+       this.autoClearStencil = true;
+
+       // scene graph
+
+       this.sortObjects = true;
+
+       // user-defined clipping
+
+       this.clippingPlanes = [];
+       this.localClippingEnabled = false;
+
+       // physically based shading
+
+       this.gammaFactor = 2.0; // for backwards compatibility
+       this.outputEncoding = LinearEncoding;
+
+       // physical lights
+
+       this.physicallyCorrectLights = false;
+
+       // tone mapping
+
+       this.toneMapping = NoToneMapping;
+       this.toneMappingExposure = 1.0;
+
+       // morphs
+
+       this.maxMorphTargets = 8;
+       this.maxMorphNormals = 4;
+
+       // internal properties
+
+       const _this = this;
+
+       let _isContextLost = false;
+
+       // internal state cache
+
+       let _framebuffer = null;
+
+       let _currentActiveCubeFace = 0;
+       let _currentActiveMipmapLevel = 0;
+       let _currentRenderTarget = null;
+       let _currentFramebuffer = null;
+       let _currentMaterialId = - 1;
+
+       let _currentCamera = null;
+
+       const _currentViewport = new Vector4();
+       const _currentScissor = new Vector4();
+       let _currentScissorTest = null;
+
+       //
+
+       let _width = _canvas.width;
+       let _height = _canvas.height;
+
+       let _pixelRatio = 1;
+       let _opaqueSort = null;
+       let _transparentSort = null;
+
+       const _viewport = new Vector4( 0, 0, _width, _height );
+       const _scissor = new Vector4( 0, 0, _width, _height );
+       let _scissorTest = false;
+
+       // frustum
+
+       const _frustum = new Frustum();
+
+       // clipping
+
+       let _clippingEnabled = false;
+       let _localClippingEnabled = false;
+
+       // camera matrices cache
+
+       const _projScreenMatrix = new Matrix4();
+
+       const _vector3 = new Vector3();
+
+       const _emptyScene = { background: null, fog: null, environment: null, overrideMaterial: null, isScene: true };
+
+       function getTargetPixelRatio() {
+
+               return _currentRenderTarget === null ? _pixelRatio : 1;
+
+       }
+
+       // initialize
+
+       let _gl = _context;
+
+       function getContext( contextNames, contextAttributes ) {
+
+               for ( let i = 0; i < contextNames.length; i ++ ) {
+
+                       const contextName = contextNames[ i ];
+                       const context = _canvas.getContext( contextName, contextAttributes );
+                       if ( context !== null ) return context;
+
+               }
+
+               return null;
+
+       }
+
+       try {
+
+               const contextAttributes = {
+                       alpha: _alpha,
+                       depth: _depth,
+                       stencil: _stencil,
+                       antialias: _antialias,
+                       premultipliedAlpha: _premultipliedAlpha,
+                       preserveDrawingBuffer: _preserveDrawingBuffer,
+                       powerPreference: _powerPreference,
+                       failIfMajorPerformanceCaveat: _failIfMajorPerformanceCaveat
+               };
+
+               // event listeners must be registered before WebGL context is created, see #12753
+
+               _canvas.addEventListener( 'webglcontextlost', onContextLost, false );
+               _canvas.addEventListener( 'webglcontextrestored', onContextRestore, false );
+
+               if ( _gl === null ) {
+
+                       const contextNames = [ 'webgl2', 'webgl', 'experimental-webgl' ];
+
+                       if ( _this.isWebGL1Renderer === true ) {
+
+                               contextNames.shift();
+
+                       }
+
+                       _gl = getContext( contextNames, contextAttributes );
+
+                       if ( _gl === null ) {
+
+                               if ( getContext( contextNames ) ) {
+
+                                       throw new Error( 'Error creating WebGL context with your selected attributes.' );
+
+                               } else {
+
+                                       throw new Error( 'Error creating WebGL context.' );
+
+                               }
+
+                       }
+
+               }
+
+               // Some experimental-webgl implementations do not have getShaderPrecisionFormat
+
+               if ( _gl.getShaderPrecisionFormat === undefined ) {
+
+                       _gl.getShaderPrecisionFormat = function () {
+
+                               return { 'rangeMin': 1, 'rangeMax': 1, 'precision': 1 };
+
+                       };
+
+               }
+
+       } catch ( error ) {
+
+               console.error( 'THREE.WebGLRenderer: ' + error.message );
+               throw error;
+
+       }
+
+       let extensions, capabilities, state, info;
+       let properties, textures, cubemaps, attributes, geometries, objects;
+       let programCache, materials, renderLists, renderStates, clipping;
+
+       let background, morphtargets, bufferRenderer, indexedBufferRenderer;
+
+       let utils, bindingStates;
+
+       function initGLContext() {
+
+               extensions = new WebGLExtensions( _gl );
+
+               capabilities = new WebGLCapabilities( _gl, extensions, parameters );
+
+               extensions.init( capabilities );
+
+               utils = new WebGLUtils( _gl, extensions, capabilities );
+
+               state = new WebGLState( _gl, extensions, capabilities );
+               state.scissor( _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ).floor() );
+               state.viewport( _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ).floor() );
+
+               info = new WebGLInfo( _gl );
+               properties = new WebGLProperties();
+               textures = new WebGLTextures( _gl, extensions, state, properties, capabilities, utils, info );
+               cubemaps = new WebGLCubeMaps( _this );
+               attributes = new WebGLAttributes( _gl, capabilities );
+               bindingStates = new WebGLBindingStates( _gl, extensions, attributes, capabilities );
+               geometries = new WebGLGeometries( _gl, attributes, info, bindingStates );
+               objects = new WebGLObjects( _gl, geometries, attributes, info );
+               morphtargets = new WebGLMorphtargets( _gl );
+               clipping = new WebGLClipping( properties );
+               programCache = new WebGLPrograms( _this, cubemaps, extensions, capabilities, bindingStates, clipping );
+               materials = new WebGLMaterials( properties );
+               renderLists = new WebGLRenderLists( properties );
+               renderStates = new WebGLRenderStates( extensions, capabilities );
+               background = new WebGLBackground( _this, cubemaps, state, objects, _premultipliedAlpha );
+
+               bufferRenderer = new WebGLBufferRenderer( _gl, extensions, info, capabilities );
+               indexedBufferRenderer = new WebGLIndexedBufferRenderer( _gl, extensions, info, capabilities );
+
+               info.programs = programCache.programs;
+
+               _this.capabilities = capabilities;
+               _this.extensions = extensions;
+               _this.properties = properties;
+               _this.renderLists = renderLists;
+               _this.state = state;
+               _this.info = info;
+
+       }
+
+       initGLContext();
+
+       // xr
+
+       const xr = new WebXRManager( _this, _gl );
+
+       this.xr = xr;
+
+       // shadow map
+
+       const shadowMap = new WebGLShadowMap( _this, objects, capabilities.maxTextureSize );
+
+       this.shadowMap = shadowMap;
+
+       // API
+
+       this.getContext = function () {
+
+               return _gl;
+
+       };
+
+       this.getContextAttributes = function () {
+
+               return _gl.getContextAttributes();
+
+       };
+
+       this.forceContextLoss = function () {
+
+               const extension = extensions.get( 'WEBGL_lose_context' );
+               if ( extension ) extension.loseContext();
+
+       };
+
+       this.forceContextRestore = function () {
+
+               const extension = extensions.get( 'WEBGL_lose_context' );
+               if ( extension ) extension.restoreContext();
+
+       };
+
+       this.getPixelRatio = function () {
+
+               return _pixelRatio;
+
+       };
+
+       this.setPixelRatio = function ( value ) {
+
+               if ( value === undefined ) return;
+
+               _pixelRatio = value;
+
+               this.setSize( _width, _height, false );
+
+       };
+
+       this.getSize = function ( target ) {
+
+               if ( target === undefined ) {
+
+                       console.warn( 'WebGLRenderer: .getsize() now requires a Vector2 as an argument' );
+
+                       target = new Vector2();
+
+               }
+
+               return target.set( _width, _height );
+
+       };
+
+       this.setSize = function ( width, height, updateStyle ) {
+
+               if ( xr.isPresenting ) {
+
+                       console.warn( 'THREE.WebGLRenderer: Can\'t change size while VR device is presenting.' );
+                       return;
+
+               }
+
+               _width = width;
+               _height = height;
+
+               _canvas.width = Math.floor( width * _pixelRatio );
+               _canvas.height = Math.floor( height * _pixelRatio );
+
+               if ( updateStyle !== false ) {
+
+                       _canvas.style.width = width + 'px';
+                       _canvas.style.height = height + 'px';
+
+               }
+
+               this.setViewport( 0, 0, width, height );
+
+       };
+
+       this.getDrawingBufferSize = function ( target ) {
+
+               if ( target === undefined ) {
+
+                       console.warn( 'WebGLRenderer: .getdrawingBufferSize() now requires a Vector2 as an argument' );
+
+                       target = new Vector2();
+
+               }
+
+               return target.set( _width * _pixelRatio, _height * _pixelRatio ).floor();
+
+       };
+
+       this.setDrawingBufferSize = function ( width, height, pixelRatio ) {
+
+               _width = width;
+               _height = height;
+
+               _pixelRatio = pixelRatio;
+
+               _canvas.width = Math.floor( width * pixelRatio );
+               _canvas.height = Math.floor( height * pixelRatio );
+
+               this.setViewport( 0, 0, width, height );
+
+       };
+
+       this.getCurrentViewport = function ( target ) {
+
+               if ( target === undefined ) {
+
+                       console.warn( 'WebGLRenderer: .getCurrentViewport() now requires a Vector4 as an argument' );
+
+                       target = new Vector4();
+
+               }
+
+               return target.copy( _currentViewport );
+
+       };
+
+       this.getViewport = function ( target ) {
+
+               return target.copy( _viewport );
+
+       };
+
+       this.setViewport = function ( x, y, width, height ) {
+
+               if ( x.isVector4 ) {
+
+                       _viewport.set( x.x, x.y, x.z, x.w );
+
+               } else {
+
+                       _viewport.set( x, y, width, height );
+
+               }
+
+               state.viewport( _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ).floor() );
+
+       };
+
+       this.getScissor = function ( target ) {
+
+               return target.copy( _scissor );
+
+       };
+
+       this.setScissor = function ( x, y, width, height ) {
+
+               if ( x.isVector4 ) {
+
+                       _scissor.set( x.x, x.y, x.z, x.w );
+
+               } else {
+
+                       _scissor.set( x, y, width, height );
+
+               }
+
+               state.scissor( _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ).floor() );
+
+       };
+
+       this.getScissorTest = function () {
+
+               return _scissorTest;
+
+       };
+
+       this.setScissorTest = function ( boolean ) {
+
+               state.setScissorTest( _scissorTest = boolean );
+
+       };
+
+       this.setOpaqueSort = function ( method ) {
+
+               _opaqueSort = method;
+
+       };
+
+       this.setTransparentSort = function ( method ) {
+
+               _transparentSort = method;
+
+       };
+
+       // Clearing
+
+       this.getClearColor = function ( target ) {
+
+               if ( target === undefined ) {
+
+                       console.warn( 'WebGLRenderer: .getClearColor() now requires a Color as an argument' );
+
+                       target = new Color();
+
+               }
+
+               return target.copy( background.getClearColor() );
+
+       };
+
+       this.setClearColor = function () {
+
+               background.setClearColor.apply( background, arguments );
+
+       };
+
+       this.getClearAlpha = function () {
+
+               return background.getClearAlpha();
+
+       };
+
+       this.setClearAlpha = function () {
+
+               background.setClearAlpha.apply( background, arguments );
+
+       };
+
+       this.clear = function ( color, depth, stencil ) {
+
+               let bits = 0;
+
+               if ( color === undefined || color ) bits |= 16384;
+               if ( depth === undefined || depth ) bits |= 256;
+               if ( stencil === undefined || stencil ) bits |= 1024;
+
+               _gl.clear( bits );
+
+       };
+
+       this.clearColor = function () {
+
+               this.clear( true, false, false );
+
+       };
+
+       this.clearDepth = function () {
+
+               this.clear( false, true, false );
+
+       };
+
+       this.clearStencil = function () {
+
+               this.clear( false, false, true );
+
+       };
+
+       //
+
+       this.dispose = function () {
+
+               _canvas.removeEventListener( 'webglcontextlost', onContextLost, false );
+               _canvas.removeEventListener( 'webglcontextrestored', onContextRestore, false );
+
+               renderLists.dispose();
+               renderStates.dispose();
+               properties.dispose();
+               cubemaps.dispose();
+               objects.dispose();
+               bindingStates.dispose();
+
+               xr.dispose();
+
+               animation.stop();
+
+       };
+
+       // Events
+
+       function onContextLost( event ) {
+
+               event.preventDefault();
+
+               console.log( 'THREE.WebGLRenderer: Context Lost.' );
+
+               _isContextLost = true;
+
+       }
+
+       function onContextRestore( /* event */ ) {
+
+               console.log( 'THREE.WebGLRenderer: Context Restored.' );
+
+               _isContextLost = false;
+
+               initGLContext();
+
+       }
+
+       function onMaterialDispose( event ) {
+
+               const material = event.target;
+
+               material.removeEventListener( 'dispose', onMaterialDispose );
+
+               deallocateMaterial( material );
+
+       }
+
+       // Buffer deallocation
+
+       function deallocateMaterial( material ) {
+
+               releaseMaterialProgramReference( material );
+
+               properties.remove( material );
+
+       }
+
+
+       function releaseMaterialProgramReference( material ) {
+
+               const programInfo = properties.get( material ).program;
+
+               if ( programInfo !== undefined ) {
+
+                       programCache.releaseProgram( programInfo );
+
+               }
+
+       }
+
+       // Buffer rendering
+
+       function renderObjectImmediate( object, program ) {
+
+               object.render( function ( object ) {
+
+                       _this.renderBufferImmediate( object, program );
+
+               } );
+
+       }
+
+       this.renderBufferImmediate = function ( object, program ) {
+
+               bindingStates.initAttributes();
+
+               const buffers = properties.get( object );
+
+               if ( object.hasPositions && ! buffers.position ) buffers.position = _gl.createBuffer();
+               if ( object.hasNormals && ! buffers.normal ) buffers.normal = _gl.createBuffer();
+               if ( object.hasUvs && ! buffers.uv ) buffers.uv = _gl.createBuffer();
+               if ( object.hasColors && ! buffers.color ) buffers.color = _gl.createBuffer();
+
+               const programAttributes = program.getAttributes();
+
+               if ( object.hasPositions ) {
+
+                       _gl.bindBuffer( 34962, buffers.position );
+                       _gl.bufferData( 34962, object.positionArray, 35048 );
+
+                       bindingStates.enableAttribute( programAttributes.position );
+                       _gl.vertexAttribPointer( programAttributes.position, 3, 5126, false, 0, 0 );
+
+               }
+
+               if ( object.hasNormals ) {
+
+                       _gl.bindBuffer( 34962, buffers.normal );
+                       _gl.bufferData( 34962, object.normalArray, 35048 );
+
+                       bindingStates.enableAttribute( programAttributes.normal );
+                       _gl.vertexAttribPointer( programAttributes.normal, 3, 5126, false, 0, 0 );
+
+               }
+
+               if ( object.hasUvs ) {
+
+                       _gl.bindBuffer( 34962, buffers.uv );
+                       _gl.bufferData( 34962, object.uvArray, 35048 );
+
+                       bindingStates.enableAttribute( programAttributes.uv );
+                       _gl.vertexAttribPointer( programAttributes.uv, 2, 5126, false, 0, 0 );
+
+               }
+
+               if ( object.hasColors ) {
+
+                       _gl.bindBuffer( 34962, buffers.color );
+                       _gl.bufferData( 34962, object.colorArray, 35048 );
+
+                       bindingStates.enableAttribute( programAttributes.color );
+                       _gl.vertexAttribPointer( programAttributes.color, 3, 5126, false, 0, 0 );
+
+               }
+
+               bindingStates.disableUnusedAttributes();
+
+               _gl.drawArrays( 4, 0, object.count );
+
+               object.count = 0;
+
+       };
+
+       this.renderBufferDirect = function ( camera, scene, geometry, material, object, group ) {
+
+               if ( scene === null ) scene = _emptyScene; // renderBufferDirect second parameter used to be fog (could be null)
+
+               const frontFaceCW = ( object.isMesh && object.matrixWorld.determinant() < 0 );
+
+               const program = setProgram( camera, scene, material, object );
+
+               state.setMaterial( material, frontFaceCW );
+
+               //
+
+               let index = geometry.index;
+               const position = geometry.attributes.position;
+
+               //
+
+               if ( index === null ) {
+
+                       if ( position === undefined || position.count === 0 ) return;
+
+               } else if ( index.count === 0 ) {
+
+                       return;
+
+               }
+
+               //
+
+               let rangeFactor = 1;
+
+               if ( material.wireframe === true ) {
+
+                       index = geometries.getWireframeAttribute( geometry );
+                       rangeFactor = 2;
+
+               }
+
+               if ( material.morphTargets || material.morphNormals ) {
+
+                       morphtargets.update( object, geometry, material, program );
+
+               }
+
+               bindingStates.setup( object, material, program, geometry, index );
+
+               let attribute;
+               let renderer = bufferRenderer;
+
+               if ( index !== null ) {
+
+                       attribute = attributes.get( index );
+
+                       renderer = indexedBufferRenderer;
+                       renderer.setIndex( attribute );
+
+               }
+
+               //
+
+               const dataCount = ( index !== null ) ? index.count : position.count;
+
+               const rangeStart = geometry.drawRange.start * rangeFactor;
+               const rangeCount = geometry.drawRange.count * rangeFactor;
+
+               const groupStart = group !== null ? group.start * rangeFactor : 0;
+               const groupCount = group !== null ? group.count * rangeFactor : Infinity;
+
+               const drawStart = Math.max( rangeStart, groupStart );
+               const drawEnd = Math.min( dataCount, rangeStart + rangeCount, groupStart + groupCount ) - 1;
+
+               const drawCount = Math.max( 0, drawEnd - drawStart + 1 );
+
+               if ( drawCount === 0 ) return;
+
+               //
+
+               if ( object.isMesh ) {
+
+                       if ( material.wireframe === true ) {
+
+                               state.setLineWidth( material.wireframeLinewidth * getTargetPixelRatio() );
+                               renderer.setMode( 1 );
+
+                       } else {
+
+                               renderer.setMode( 4 );
+
+                       }
+
+               } else if ( object.isLine ) {
+
+                       let lineWidth = material.linewidth;
+
+                       if ( lineWidth === undefined ) lineWidth = 1; // Not using Line*Material
+
+                       state.setLineWidth( lineWidth * getTargetPixelRatio() );
+
+                       if ( object.isLineSegments ) {
+
+                               renderer.setMode( 1 );
+
+                       } else if ( object.isLineLoop ) {
+
+                               renderer.setMode( 2 );
+
+                       } else {
+
+                               renderer.setMode( 3 );
+
+                       }
+
+               } else if ( object.isPoints ) {
+
+                       renderer.setMode( 0 );
+
+               } else if ( object.isSprite ) {
+
+                       renderer.setMode( 4 );
+
+               }
+
+               if ( object.isInstancedMesh ) {
+
+                       renderer.renderInstances( drawStart, drawCount, object.count );
+
+               } else if ( geometry.isInstancedBufferGeometry ) {
+
+                       const instanceCount = Math.min( geometry.instanceCount, geometry._maxInstanceCount );
+
+                       renderer.renderInstances( drawStart, drawCount, instanceCount );
+
+               } else {
+
+                       renderer.render( drawStart, drawCount );
+
+               }
+
+       };
+
+       // Compile
+
+       this.compile = function ( scene, camera ) {
+
+               currentRenderState = renderStates.get( scene );
+               currentRenderState.init();
+
+               scene.traverseVisible( function ( object ) {
+
+                       if ( object.isLight && object.layers.test( camera.layers ) ) {
+
+                               currentRenderState.pushLight( object );
+
+                               if ( object.castShadow ) {
+
+                                       currentRenderState.pushShadow( object );
+
+                               }
+
+                       }
+
+               } );
+
+               currentRenderState.setupLights();
+
+               const compiled = new WeakMap();
+
+               scene.traverse( function ( object ) {
+
+                       const material = object.material;
+
+                       if ( material ) {
+
+                               if ( Array.isArray( material ) ) {
+
+                                       for ( let i = 0; i < material.length; i ++ ) {
+
+                                               const material2 = material[ i ];
+
+                                               if ( compiled.has( material2 ) === false ) {
+
+                                                       initMaterial( material2, scene, object );
+                                                       compiled.set( material2 );
+
+                                               }
+
+                                       }
+
+                               } else if ( compiled.has( material ) === false ) {
+
+                                       initMaterial( material, scene, object );
+                                       compiled.set( material );
+
+                               }
+
+                       }
+
+               } );
+
+       };
+
+       // Animation Loop
+
+       let onAnimationFrameCallback = null;
+
+       function onAnimationFrame( time ) {
+
+               if ( xr.isPresenting ) return;
+               if ( onAnimationFrameCallback ) onAnimationFrameCallback( time );
+
+       }
+
+       const animation = new WebGLAnimation();
+       animation.setAnimationLoop( onAnimationFrame );
+
+       if ( typeof window !== 'undefined' ) animation.setContext( window );
+
+       this.setAnimationLoop = function ( callback ) {
+
+               onAnimationFrameCallback = callback;
+               xr.setAnimationLoop( callback );
+
+               ( callback === null ) ? animation.stop() : animation.start();
+
+       };
+
+       // Rendering
+
+       this.render = function ( scene, camera ) {
+
+               let renderTarget, forceClear;
+
+               if ( arguments[ 2 ] !== undefined ) {
+
+                       console.warn( 'THREE.WebGLRenderer.render(): the renderTarget argument has been removed. Use .setRenderTarget() instead.' );
+                       renderTarget = arguments[ 2 ];
+
+               }
+
+               if ( arguments[ 3 ] !== undefined ) {
+
+                       console.warn( 'THREE.WebGLRenderer.render(): the forceClear argument has been removed. Use .clear() instead.' );
+                       forceClear = arguments[ 3 ];
+
+               }
+
+               if ( camera !== undefined && camera.isCamera !== true ) {
+
+                       console.error( 'THREE.WebGLRenderer.render: camera is not an instance of THREE.Camera.' );
+                       return;
+
+               }
+
+               if ( _isContextLost === true ) return;
+
+               // reset caching for this frame
+
+               bindingStates.resetDefaultState();
+               _currentMaterialId = - 1;
+               _currentCamera = null;
+
+               // update scene graph
+
+               if ( scene.autoUpdate === true ) scene.updateMatrixWorld();
+
+               // update camera matrices and frustum
+
+               if ( camera.parent === null ) camera.updateMatrixWorld();
+
+               if ( xr.enabled === true && xr.isPresenting === true ) {
+
+                       camera = xr.getCamera( camera );
+
+               }
+
+               //
+               if ( scene.isScene === true ) scene.onBeforeRender( _this, scene, camera, renderTarget || _currentRenderTarget );
+
+               currentRenderState = renderStates.get( scene, renderStateStack.length );
+               currentRenderState.init();
+
+               renderStateStack.push( currentRenderState );
+
+               _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse );
+               _frustum.setFromProjectionMatrix( _projScreenMatrix );
+
+               _localClippingEnabled = this.localClippingEnabled;
+               _clippingEnabled = clipping.init( this.clippingPlanes, _localClippingEnabled, camera );
+
+               currentRenderList = renderLists.get( scene, camera );
+               currentRenderList.init();
+
+               projectObject( scene, camera, 0, _this.sortObjects );
+
+               currentRenderList.finish();
+
+               if ( _this.sortObjects === true ) {
+
+                       currentRenderList.sort( _opaqueSort, _transparentSort );
+
+               }
+
+               //
+
+               if ( _clippingEnabled === true ) clipping.beginShadows();
+
+               const shadowsArray = currentRenderState.state.shadowsArray;
+
+               shadowMap.render( shadowsArray, scene, camera );
+
+               currentRenderState.setupLights();
+               currentRenderState.setupLightsView( camera );
+
+               if ( _clippingEnabled === true ) clipping.endShadows();
+
+               //
+
+               if ( this.info.autoReset === true ) this.info.reset();
+
+               if ( renderTarget !== undefined ) {
+
+                       this.setRenderTarget( renderTarget );
+
+               }
+
+               //
+
+               background.render( currentRenderList, scene, camera, forceClear );
+
+               // render scene
+
+               const opaqueObjects = currentRenderList.opaque;
+               const transparentObjects = currentRenderList.transparent;
+
+               if ( opaqueObjects.length > 0 ) renderObjects( opaqueObjects, scene, camera );
+               if ( transparentObjects.length > 0 ) renderObjects( transparentObjects, scene, camera );
+
+               //
+
+               if ( scene.isScene === true ) scene.onAfterRender( _this, scene, camera );
+
+               //
+
+               if ( _currentRenderTarget !== null ) {
+
+                       // Generate mipmap if we're using any kind of mipmap filtering
+
+                       textures.updateRenderTargetMipmap( _currentRenderTarget );
+
+                       // resolve multisample renderbuffers to a single-sample texture if necessary
+
+                       textures.updateMultisampleRenderTarget( _currentRenderTarget );
+
+               }
+
+               // Ensure depth buffer writing is enabled so it can be cleared on next render
+
+               state.buffers.depth.setTest( true );
+               state.buffers.depth.setMask( true );
+               state.buffers.color.setMask( true );
+
+               state.setPolygonOffset( false );
+
+               // _gl.finish();
+
+               renderStateStack.pop();
+               if ( renderStateStack.length > 0 ) {
+
+                       currentRenderState = renderStateStack[ renderStateStack.length - 1 ];
+
+               } else {
+
+                       currentRenderState = null;
+
+               }
+
+               currentRenderList = null;
+
+       };
+
+       function projectObject( object, camera, groupOrder, sortObjects ) {
+
+               if ( object.visible === false ) return;
+
+               const visible = object.layers.test( camera.layers );
+
+               if ( visible ) {
+
+                       if ( object.isGroup ) {
+
+                               groupOrder = object.renderOrder;
+
+                       } else if ( object.isLOD ) {
+
+                               if ( object.autoUpdate === true ) object.update( camera );
+
+                       } else if ( object.isLight ) {
+
+                               currentRenderState.pushLight( object );
+
+                               if ( object.castShadow ) {
+
+                                       currentRenderState.pushShadow( object );
+
+                               }
+
+                       } else if ( object.isSprite ) {
+
+                               if ( ! object.frustumCulled || _frustum.intersectsSprite( object ) ) {
+
+                                       if ( sortObjects ) {
+
+                                               _vector3.setFromMatrixPosition( object.matrixWorld )
+                                                       .applyMatrix4( _projScreenMatrix );
+
+                                       }
+
+                                       const geometry = objects.update( object );
+                                       const material = object.material;
+
+                                       if ( material.visible ) {
+
+                                               currentRenderList.push( object, geometry, material, groupOrder, _vector3.z, null );
+
+                                       }
+
+                               }
+
+                       } else if ( object.isImmediateRenderObject ) {
+
+                               if ( sortObjects ) {
+
+                                       _vector3.setFromMatrixPosition( object.matrixWorld )
+                                               .applyMatrix4( _projScreenMatrix );
+
+                               }
+
+                               currentRenderList.push( object, null, object.material, groupOrder, _vector3.z, null );
+
+                       } else if ( object.isMesh || object.isLine || object.isPoints ) {
+
+                               if ( object.isSkinnedMesh ) {
+
+                                       // update skeleton only once in a frame
+
+                                       if ( object.skeleton.frame !== info.render.frame ) {
+
+                                               object.skeleton.update();
+                                               object.skeleton.frame = info.render.frame;
+
+                                       }
+
+                               }
+
+                               if ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) {
+
+                                       if ( sortObjects ) {
+
+                                               _vector3.setFromMatrixPosition( object.matrixWorld )
+                                                       .applyMatrix4( _projScreenMatrix );
+
+                                       }
+
+                                       const geometry = objects.update( object );
+                                       const material = object.material;
+
+                                       if ( Array.isArray( material ) ) {
+
+                                               const groups = geometry.groups;
+
+                                               for ( let i = 0, l = groups.length; i < l; i ++ ) {
+
+                                                       const group = groups[ i ];
+                                                       const groupMaterial = material[ group.materialIndex ];
+
+                                                       if ( groupMaterial && groupMaterial.visible ) {
+
+                                                               currentRenderList.push( object, geometry, groupMaterial, groupOrder, _vector3.z, group );
+
+                                                       }
+
+                                               }
+
+                                       } else if ( material.visible ) {
+
+                                               currentRenderList.push( object, geometry, material, groupOrder, _vector3.z, null );
+
+                                       }
+
+                               }
+
+                       }
+
+               }
+
+               const children = object.children;
+
+               for ( let i = 0, l = children.length; i < l; i ++ ) {
+
+                       projectObject( children[ i ], camera, groupOrder, sortObjects );
+
+               }
+
+       }
+
+       function renderObjects( renderList, scene, camera ) {
+
+               const overrideMaterial = scene.isScene === true ? scene.overrideMaterial : null;
+
+               for ( let i = 0, l = renderList.length; i < l; i ++ ) {
+
+                       const renderItem = renderList[ i ];
+
+                       const object = renderItem.object;
+                       const geometry = renderItem.geometry;
+                       const material = overrideMaterial === null ? renderItem.material : overrideMaterial;
+                       const group = renderItem.group;
+
+                       if ( camera.isArrayCamera ) {
+
+                               const cameras = camera.cameras;
+
+                               for ( let j = 0, jl = cameras.length; j < jl; j ++ ) {
+
+                                       const camera2 = cameras[ j ];
+
+                                       if ( object.layers.test( camera2.layers ) ) {
+
+                                               state.viewport( _currentViewport.copy( camera2.viewport ) );
+
+                                               currentRenderState.setupLightsView( camera2 );
+
+                                               renderObject( object, scene, camera2, geometry, material, group );
+
+                                       }
+
+                               }
+
+                       } else {
+
+                               renderObject( object, scene, camera, geometry, material, group );
+
+                       }
+
+               }
+
+       }
+
+       function renderObject( object, scene, camera, geometry, material, group ) {
+
+               object.onBeforeRender( _this, scene, camera, geometry, material, group );
+
+               object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld );
+               object.normalMatrix.getNormalMatrix( object.modelViewMatrix );
+
+               if ( object.isImmediateRenderObject ) {
+
+                       const program = setProgram( camera, scene, material, object );
+
+                       state.setMaterial( material );
+
+                       bindingStates.reset();
+
+                       renderObjectImmediate( object, program );
+
+               } else {
+
+                       _this.renderBufferDirect( camera, scene, geometry, material, object, group );
+
+               }
+
+               object.onAfterRender( _this, scene, camera, geometry, material, group );
+
+       }
+
+       function initMaterial( material, scene, object ) {
+
+               if ( scene.isScene !== true ) scene = _emptyScene; // scene could be a Mesh, Line, Points, ...
+
+               const materialProperties = properties.get( material );
+
+               const lights = currentRenderState.state.lights;
+               const shadowsArray = currentRenderState.state.shadowsArray;
+
+               const lightsStateVersion = lights.state.version;
+
+               const parameters = programCache.getParameters( material, lights.state, shadowsArray, scene, object );
+               const programCacheKey = programCache.getProgramCacheKey( parameters );
+
+               let program = materialProperties.program;
+               let programChange = true;
+
+               // always update environment and fog - changing these trigger an initMaterial call, but it's possible that the program doesn't change
+
+               materialProperties.environment = material.isMeshStandardMaterial ? scene.environment : null;
+               materialProperties.fog = scene.fog;
+               materialProperties.envMap = cubemaps.get( material.envMap || materialProperties.environment );
+
+               if ( program === undefined ) {
+
+                       // new material
+                       material.addEventListener( 'dispose', onMaterialDispose );
+
+               } else if ( program.cacheKey !== programCacheKey ) {
+
+                       // changed glsl or parameters
+                       releaseMaterialProgramReference( material );
+
+               } else if ( materialProperties.lightsStateVersion !== lightsStateVersion ) {
+
+                       programChange = false;
+
+               } else if ( parameters.shaderID !== undefined ) {
+
+                       // same glsl and uniform list
+                       return;
+
+               } else {
+
+                       // only rebuild uniform list
+                       programChange = false;
+
+               }
+
+               if ( programChange ) {
+
+                       parameters.uniforms = programCache.getUniforms( material );
+
+                       material.onBeforeCompile( parameters, _this );
+
+                       program = programCache.acquireProgram( parameters, programCacheKey );
+
+                       materialProperties.program = program;
+                       materialProperties.uniforms = parameters.uniforms;
+                       materialProperties.outputEncoding = parameters.outputEncoding;
+
+               }
+
+               const uniforms = materialProperties.uniforms;
+
+               if ( ! material.isShaderMaterial &&
+                       ! material.isRawShaderMaterial ||
+                       material.clipping === true ) {
+
+                       materialProperties.numClippingPlanes = clipping.numPlanes;
+                       materialProperties.numIntersection = clipping.numIntersection;
+                       uniforms.clippingPlanes = clipping.uniform;
+
+               }
+
+               // store the light setup it was created for
+
+               materialProperties.needsLights = materialNeedsLights( material );
+               materialProperties.lightsStateVersion = lightsStateVersion;
+
+               if ( materialProperties.needsLights ) {
+
+                       // wire up the material to this renderer's lighting state
+
+                       uniforms.ambientLightColor.value = lights.state.ambient;
+                       uniforms.lightProbe.value = lights.state.probe;
+                       uniforms.directionalLights.value = lights.state.directional;
+                       uniforms.directionalLightShadows.value = lights.state.directionalShadow;
+                       uniforms.spotLights.value = lights.state.spot;
+                       uniforms.spotLightShadows.value = lights.state.spotShadow;
+                       uniforms.rectAreaLights.value = lights.state.rectArea;
+                       uniforms.ltc_1.value = lights.state.rectAreaLTC1;
+                       uniforms.ltc_2.value = lights.state.rectAreaLTC2;
+                       uniforms.pointLights.value = lights.state.point;
+                       uniforms.pointLightShadows.value = lights.state.pointShadow;
+                       uniforms.hemisphereLights.value = lights.state.hemi;
+
+                       uniforms.directionalShadowMap.value = lights.state.directionalShadowMap;
+                       uniforms.directionalShadowMatrix.value = lights.state.directionalShadowMatrix;
+                       uniforms.spotShadowMap.value = lights.state.spotShadowMap;
+                       uniforms.spotShadowMatrix.value = lights.state.spotShadowMatrix;
+                       uniforms.pointShadowMap.value = lights.state.pointShadowMap;
+                       uniforms.pointShadowMatrix.value = lights.state.pointShadowMatrix;
+                       // TODO (abelnation): add area lights shadow info to uniforms
+
+               }
+
+               const progUniforms = materialProperties.program.getUniforms();
+               const uniformsList = WebGLUniforms.seqWithValue( progUniforms.seq, uniforms );
+
+               materialProperties.uniformsList = uniformsList;
+
+       }
+
+       function setProgram( camera, scene, material, object ) {
+
+               if ( scene.isScene !== true ) scene = _emptyScene; // scene could be a Mesh, Line, Points, ...
+
+               textures.resetTextureUnits();
+
+               const fog = scene.fog;
+               const environment = material.isMeshStandardMaterial ? scene.environment : null;
+               const encoding = ( _currentRenderTarget === null ) ? _this.outputEncoding : _currentRenderTarget.texture.encoding;
+               const envMap = cubemaps.get( material.envMap || environment );
+
+               const materialProperties = properties.get( material );
+               const lights = currentRenderState.state.lights;
+
+               if ( _clippingEnabled === true ) {
+
+                       if ( _localClippingEnabled === true || camera !== _currentCamera ) {
+
+                               const useCache =
+                                       camera === _currentCamera &&
+                                       material.id === _currentMaterialId;
+
+                               // we might want to call this function with some ClippingGroup
+                               // object instead of the material, once it becomes feasible
+                               // (#8465, #8379)
+                               clipping.setState( material, camera, useCache );
+
+                       }
+
+               }
+
+               if ( material.version === materialProperties.__version ) {
+
+                       if ( material.fog && materialProperties.fog !== fog ) {
+
+                               initMaterial( material, scene, object );
+
+                       } else if ( materialProperties.environment !== environment ) {
+
+                               initMaterial( material, scene, object );
+
+                       } else if ( materialProperties.needsLights && ( materialProperties.lightsStateVersion !== lights.state.version ) ) {
+
+                               initMaterial( material, scene, object );
+
+                       } else if ( materialProperties.numClippingPlanes !== undefined &&
+                               ( materialProperties.numClippingPlanes !== clipping.numPlanes ||
+                               materialProperties.numIntersection !== clipping.numIntersection ) ) {
+
+                               initMaterial( material, scene, object );
+
+                       } else if ( materialProperties.outputEncoding !== encoding ) {
+
+                               initMaterial( material, scene, object );
+
+                       } else if ( materialProperties.envMap !== envMap ) {
+
+                               initMaterial( material, scene, object );
+
+                       }
+
+               } else {
+
+                       initMaterial( material, scene, object );
+                       materialProperties.__version = material.version;
+
+               }
+
+               let refreshProgram = false;
+               let refreshMaterial = false;
+               let refreshLights = false;
+
+               const program = materialProperties.program,
+                       p_uniforms = program.getUniforms(),
+                       m_uniforms = materialProperties.uniforms;
+
+               if ( state.useProgram( program.program ) ) {
+
+                       refreshProgram = true;
+                       refreshMaterial = true;
+                       refreshLights = true;
+
+               }
+
+               if ( material.id !== _currentMaterialId ) {
+
+                       _currentMaterialId = material.id;
+
+                       refreshMaterial = true;
+
+               }
+
+               if ( refreshProgram || _currentCamera !== camera ) {
+
+                       p_uniforms.setValue( _gl, 'projectionMatrix', camera.projectionMatrix );
+
+                       if ( capabilities.logarithmicDepthBuffer ) {
+
+                               p_uniforms.setValue( _gl, 'logDepthBufFC',
+                                       2.0 / ( Math.log( camera.far + 1.0 ) / Math.LN2 ) );
+
+                       }
+
+                       if ( _currentCamera !== camera ) {
+
+                               _currentCamera = camera;
+
+                               // lighting uniforms depend on the camera so enforce an update
+                               // now, in case this material supports lights - or later, when
+                               // the next material that does gets activated:
+
+                               refreshMaterial = true;         // set to true on material change
+                               refreshLights = true;           // remains set until update done
+
+                       }
+
+                       // load material specific uniforms
+                       // (shader material also gets them for the sake of genericity)
+
+                       if ( material.isShaderMaterial ||
+                               material.isMeshPhongMaterial ||
+                               material.isMeshToonMaterial ||
+                               material.isMeshStandardMaterial ||
+                               material.envMap ) {
+
+                               const uCamPos = p_uniforms.map.cameraPosition;
+
+                               if ( uCamPos !== undefined ) {
+
+                                       uCamPos.setValue( _gl,
+                                               _vector3.setFromMatrixPosition( camera.matrixWorld ) );
+
+                               }
+
+                       }
+
+                       if ( material.isMeshPhongMaterial ||
+                               material.isMeshToonMaterial ||
+                               material.isMeshLambertMaterial ||
+                               material.isMeshBasicMaterial ||
+                               material.isMeshStandardMaterial ||
+                               material.isShaderMaterial ) {
+
+                               p_uniforms.setValue( _gl, 'isOrthographic', camera.isOrthographicCamera === true );
+
+                       }
+
+                       if ( material.isMeshPhongMaterial ||
+                               material.isMeshToonMaterial ||
+                               material.isMeshLambertMaterial ||
+                               material.isMeshBasicMaterial ||
+                               material.isMeshStandardMaterial ||
+                               material.isShaderMaterial ||
+                               material.isShadowMaterial ||
+                               material.skinning ) {
+
+                               p_uniforms.setValue( _gl, 'viewMatrix', camera.matrixWorldInverse );
+
+                       }
+
+               }
+
+               // skinning uniforms must be set even if material didn't change
+               // auto-setting of texture unit for bone texture must go before other textures
+               // otherwise textures used for skinning can take over texture units reserved for other material textures
+
+               if ( material.skinning ) {
+
+                       p_uniforms.setOptional( _gl, object, 'bindMatrix' );
+                       p_uniforms.setOptional( _gl, object, 'bindMatrixInverse' );
+
+                       const skeleton = object.skeleton;
+
+                       if ( skeleton ) {
+
+                               const bones = skeleton.bones;
+
+                               if ( capabilities.floatVertexTextures ) {
+
+                                       if ( skeleton.boneTexture === null ) {
+
+                                               // layout (1 matrix = 4 pixels)
+                                               //      RGBA RGBA RGBA RGBA (=> column1, column2, column3, column4)
+                                               //  with  8x8  pixel texture max   16 bones * 4 pixels =  (8 * 8)
+                                               //       16x16 pixel texture max   64 bones * 4 pixels = (16 * 16)
+                                               //       32x32 pixel texture max  256 bones * 4 pixels = (32 * 32)
+                                               //       64x64 pixel texture max 1024 bones * 4 pixels = (64 * 64)
+
+
+                                               let size = Math.sqrt( bones.length * 4 ); // 4 pixels needed for 1 matrix
+                                               size = MathUtils.ceilPowerOfTwo( size );
+                                               size = Math.max( size, 4 );
+
+                                               const boneMatrices = new Float32Array( size * size * 4 ); // 4 floats per RGBA pixel
+                                               boneMatrices.set( skeleton.boneMatrices ); // copy current values
+
+                                               const boneTexture = new DataTexture( boneMatrices, size, size, RGBAFormat, FloatType );
+
+                                               skeleton.boneMatrices = boneMatrices;
+                                               skeleton.boneTexture = boneTexture;
+                                               skeleton.boneTextureSize = size;
+
+                                       }
+
+                                       p_uniforms.setValue( _gl, 'boneTexture', skeleton.boneTexture, textures );
+                                       p_uniforms.setValue( _gl, 'boneTextureSize', skeleton.boneTextureSize );
+
+                               } else {
+
+                                       p_uniforms.setOptional( _gl, skeleton, 'boneMatrices' );
+
+                               }
+
+                       }
+
+               }
+
+               if ( refreshMaterial || materialProperties.receiveShadow !== object.receiveShadow ) {
+
+                       materialProperties.receiveShadow = object.receiveShadow;
+                       p_uniforms.setValue( _gl, 'receiveShadow', object.receiveShadow );
+
+               }
+
+               if ( refreshMaterial ) {
+
+                       p_uniforms.setValue( _gl, 'toneMappingExposure', _this.toneMappingExposure );
+
+                       if ( materialProperties.needsLights ) {
+
+                               // the current material requires lighting info
+
+                               // note: all lighting uniforms are always set correctly
+                               // they simply reference the renderer's state for their
+                               // values
+                               //
+                               // use the current material's .needsUpdate flags to set
+                               // the GL state when required
+
+                               markUniformsLightsNeedsUpdate( m_uniforms, refreshLights );
+
+                       }
+
+                       // refresh uniforms common to several materials
+
+                       if ( fog && material.fog ) {
+
+                               materials.refreshFogUniforms( m_uniforms, fog );
+
+                       }
+
+                       materials.refreshMaterialUniforms( m_uniforms, material, _pixelRatio, _height );
+
+                       WebGLUniforms.upload( _gl, materialProperties.uniformsList, m_uniforms, textures );
+
+               }
+
+               if ( material.isShaderMaterial && material.uniformsNeedUpdate === true ) {
+
+                       WebGLUniforms.upload( _gl, materialProperties.uniformsList, m_uniforms, textures );
+                       material.uniformsNeedUpdate = false;
+
+               }
+
+               if ( material.isSpriteMaterial ) {
+
+                       p_uniforms.setValue( _gl, 'center', object.center );
+
+               }
+
+               // common matrices
+
+               p_uniforms.setValue( _gl, 'modelViewMatrix', object.modelViewMatrix );
+               p_uniforms.setValue( _gl, 'normalMatrix', object.normalMatrix );
+               p_uniforms.setValue( _gl, 'modelMatrix', object.matrixWorld );
+
+               return program;
+
+       }
+
+       // If uniforms are marked as clean, they don't need to be loaded to the GPU.
+
+       function markUniformsLightsNeedsUpdate( uniforms, value ) {
+
+               uniforms.ambientLightColor.needsUpdate = value;
+               uniforms.lightProbe.needsUpdate = value;
+
+               uniforms.directionalLights.needsUpdate = value;
+               uniforms.directionalLightShadows.needsUpdate = value;
+               uniforms.pointLights.needsUpdate = value;
+               uniforms.pointLightShadows.needsUpdate = value;
+               uniforms.spotLights.needsUpdate = value;
+               uniforms.spotLightShadows.needsUpdate = value;
+               uniforms.rectAreaLights.needsUpdate = value;
+               uniforms.hemisphereLights.needsUpdate = value;
+
+       }
+
+       function materialNeedsLights( material ) {
+
+               return material.isMeshLambertMaterial || material.isMeshToonMaterial || material.isMeshPhongMaterial ||
+                       material.isMeshStandardMaterial || material.isShadowMaterial ||
+                       ( material.isShaderMaterial && material.lights === true );
+
+       }
+
+       //
+       this.setFramebuffer = function ( value ) {
+
+               if ( _framebuffer !== value && _currentRenderTarget === null ) _gl.bindFramebuffer( 36160, value );
+
+               _framebuffer = value;
+
+       };
+
+       this.getActiveCubeFace = function () {
+
+               return _currentActiveCubeFace;
+
+       };
+
+       this.getActiveMipmapLevel = function () {
+
+               return _currentActiveMipmapLevel;
+
+       };
+
+       this.getRenderList = function () {
+
+               return currentRenderList;
+
+       };
+
+       this.setRenderList = function ( renderList ) {
+
+               currentRenderList = renderList;
+
+       };
+
+       this.getRenderTarget = function () {
+
+               return _currentRenderTarget;
+
+       };
+
+       this.setRenderTarget = function ( renderTarget, activeCubeFace = 0, activeMipmapLevel = 0 ) {
+
+               _currentRenderTarget = renderTarget;
+               _currentActiveCubeFace = activeCubeFace;
+               _currentActiveMipmapLevel = activeMipmapLevel;
+
+               if ( renderTarget && properties.get( renderTarget ).__webglFramebuffer === undefined ) {
+
+                       textures.setupRenderTarget( renderTarget );
+
+               }
+
+               let framebuffer = _framebuffer;
+               let isCube = false;
+
+               if ( renderTarget ) {
+
+                       const __webglFramebuffer = properties.get( renderTarget ).__webglFramebuffer;
+
+                       if ( renderTarget.isWebGLCubeRenderTarget ) {
+
+                               framebuffer = __webglFramebuffer[ activeCubeFace ];
+                               isCube = true;
+
+                       } else if ( renderTarget.isWebGLMultisampleRenderTarget ) {
+
+                               framebuffer = properties.get( renderTarget ).__webglMultisampledFramebuffer;
+
+                       } else {
+
+                               framebuffer = __webglFramebuffer;
+
+                       }
+
+                       _currentViewport.copy( renderTarget.viewport );
+                       _currentScissor.copy( renderTarget.scissor );
+                       _currentScissorTest = renderTarget.scissorTest;
+
+               } else {
+
+                       _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ).floor();
+                       _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ).floor();
+                       _currentScissorTest = _scissorTest;
+
+               }
+
+               if ( _currentFramebuffer !== framebuffer ) {
+
+                       _gl.bindFramebuffer( 36160, framebuffer );
+                       _currentFramebuffer = framebuffer;
+
+               }
+
+               state.viewport( _currentViewport );
+               state.scissor( _currentScissor );
+               state.setScissorTest( _currentScissorTest );
+
+               if ( isCube ) {
+
+                       const textureProperties = properties.get( renderTarget.texture );
+                       _gl.framebufferTexture2D( 36160, 36064, 34069 + activeCubeFace, textureProperties.__webglTexture, activeMipmapLevel );
+
+               }
+
+       };
+
+       this.readRenderTargetPixels = function ( renderTarget, x, y, width, height, buffer, activeCubeFaceIndex ) {
+
+               if ( ! ( renderTarget && renderTarget.isWebGLRenderTarget ) ) {
+
+                       console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.' );
+                       return;
+
+               }
+
+               let framebuffer = properties.get( renderTarget ).__webglFramebuffer;
+
+               if ( renderTarget.isWebGLCubeRenderTarget && activeCubeFaceIndex !== undefined ) {
+
+                       framebuffer = framebuffer[ activeCubeFaceIndex ];
+
+               }
+
+               if ( framebuffer ) {
+
+                       let restore = false;
+
+                       if ( framebuffer !== _currentFramebuffer ) {
+
+                               _gl.bindFramebuffer( 36160, framebuffer );
+
+                               restore = true;
+
+                       }
+
+                       try {
+
+                               const texture = renderTarget.texture;
+                               const textureFormat = texture.format;
+                               const textureType = texture.type;
+
+                               if ( textureFormat !== RGBAFormat && utils.convert( textureFormat ) !== _gl.getParameter( 35739 ) ) {
+
+                                       console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in RGBA or implementation defined format.' );
+                                       return;
+
+                               }
+
+                               const halfFloatSupportedByExt = ( textureType === HalfFloatType ) && ( extensions.has( 'EXT_color_buffer_half_float' ) || ( capabilities.isWebGL2 && extensions.has( 'EXT_color_buffer_float' ) ) );
+
+                               if ( textureType !== UnsignedByteType && utils.convert( textureType ) !== _gl.getParameter( 35738 ) && // IE11, Edge and Chrome Mac < 52 (#9513)
+                                       ! ( textureType === FloatType && ( capabilities.isWebGL2 || extensions.has( 'OES_texture_float' ) || extensions.has( 'WEBGL_color_buffer_float' ) ) ) && // Chrome Mac >= 52 and Firefox
+                                       ! halfFloatSupportedByExt ) {
+
+                                       console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in UnsignedByteType or implementation defined type.' );
+                                       return;
+
+                               }
+
+                               if ( _gl.checkFramebufferStatus( 36160 ) === 36053 ) {
+
+                                       // the following if statement ensures valid read requests (no out-of-bounds pixels, see #8604)
+
+                                       if ( ( x >= 0 && x <= ( renderTarget.width - width ) ) && ( y >= 0 && y <= ( renderTarget.height - height ) ) ) {
+
+                                               _gl.readPixels( x, y, width, height, utils.convert( textureFormat ), utils.convert( textureType ), buffer );
+
+                                       }
+
+                               } else {
+
+                                       console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: readPixels from renderTarget failed. Framebuffer not complete.' );
+
+                               }
+
+                       } finally {
+
+                               if ( restore ) {
+
+                                       _gl.bindFramebuffer( 36160, _currentFramebuffer );
+
+                               }
+
+                       }
+
+               }
+
+       };
+
+       this.copyFramebufferToTexture = function ( position, texture, level = 0 ) {
+
+               const levelScale = Math.pow( 2, - level );
+               const width = Math.floor( texture.image.width * levelScale );
+               const height = Math.floor( texture.image.height * levelScale );
+               const glFormat = utils.convert( texture.format );
+
+               textures.setTexture2D( texture, 0 );
+
+               _gl.copyTexImage2D( 3553, level, glFormat, position.x, position.y, width, height, 0 );
+
+               state.unbindTexture();
+
+       };
+
+       this.copyTextureToTexture = function ( position, srcTexture, dstTexture, level = 0 ) {
+
+               const width = srcTexture.image.width;
+               const height = srcTexture.image.height;
+               const glFormat = utils.convert( dstTexture.format );
+               const glType = utils.convert( dstTexture.type );
+
+               textures.setTexture2D( dstTexture, 0 );
+
+               // As another texture upload may have changed pixelStorei
+               // parameters, make sure they are correct for the dstTexture
+               _gl.pixelStorei( 37440, dstTexture.flipY );
+               _gl.pixelStorei( 37441, dstTexture.premultiplyAlpha );
+               _gl.pixelStorei( 3317, dstTexture.unpackAlignment );
+
+               if ( srcTexture.isDataTexture ) {
+
+                       _gl.texSubImage2D( 3553, level, position.x, position.y, width, height, glFormat, glType, srcTexture.image.data );
+
+               } else {
+
+                       if ( srcTexture.isCompressedTexture ) {
+
+                               _gl.compressedTexSubImage2D( 3553, level, position.x, position.y, srcTexture.mipmaps[ 0 ].width, srcTexture.mipmaps[ 0 ].height, glFormat, srcTexture.mipmaps[ 0 ].data );
+
+                       } else {
+
+                               _gl.texSubImage2D( 3553, level, position.x, position.y, glFormat, glType, srcTexture.image );
+
+                       }
+
+               }
+
+               // Generate mipmaps only when copying level 0
+               if ( level === 0 && dstTexture.generateMipmaps ) _gl.generateMipmap( 3553 );
+
+               state.unbindTexture();
+
+       };
+
+       this.initTexture = function ( texture ) {
+
+               textures.setTexture2D( texture, 0 );
+
+               state.unbindTexture();
+
+       };
+
+       this.resetState = function () {
+
+               state.reset();
+               bindingStates.reset();
+
+       };
+
+       if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) {
+
+               __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'observe', { detail: this } ) ); // eslint-disable-line no-undef
+
+       }
+
+}
+
+function WebGL1Renderer( parameters ) {
+
+       WebGLRenderer.call( this, parameters );
+
+}
+
+WebGL1Renderer.prototype = Object.assign( Object.create( WebGLRenderer.prototype ), {
+
+       constructor: WebGL1Renderer,
+
+       isWebGL1Renderer: true
+
+} );
+
+class Scene extends Object3D {
+
+       constructor() {
+
+               super();
+
+               Object.defineProperty( this, 'isScene', { value: true } );
+
+               this.type = 'Scene';
+
+               this.background = null;
+               this.environment = null;
+               this.fog = null;
+
+               this.overrideMaterial = null;
+
+               this.autoUpdate = true; // checked by the renderer
+
+               if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) {
+
+                       __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'observe', { detail: this } ) ); // eslint-disable-line no-undef
+
+               }
+
+       }
+
+       copy( source, recursive ) {
+
+               super.copy( source, recursive );
+
+               if ( source.background !== null ) this.background = source.background.clone();
+               if ( source.environment !== null ) this.environment = source.environment.clone();
+               if ( source.fog !== null ) this.fog = source.fog.clone();
+
+               if ( source.overrideMaterial !== null ) this.overrideMaterial = source.overrideMaterial.clone();
+
+               this.autoUpdate = source.autoUpdate;
+               this.matrixAutoUpdate = source.matrixAutoUpdate;
+
+               return this;
+
+       }
+
+       toJSON( meta ) {
+
+               const data = super.toJSON( meta );
+
+               if ( this.background !== null ) data.object.background = this.background.toJSON( meta );
+               if ( this.environment !== null ) data.object.environment = this.environment.toJSON( meta );
+               if ( this.fog !== null ) data.object.fog = this.fog.toJSON();
+
+               return data;
+
+       }
+
+}
+
+function InterleavedBuffer( array, stride ) {
+
+       this.array = array;
+       this.stride = stride;
+       this.count = array !== undefined ? array.length / stride : 0;
+
+       this.usage = StaticDrawUsage;
+       this.updateRange = { offset: 0, count: - 1 };
+
+       this.version = 0;
+
+       this.uuid = MathUtils.generateUUID();
+
+}
+
+Object.defineProperty( InterleavedBuffer.prototype, 'needsUpdate', {
+
+       set: function ( value ) {
+
+               if ( value === true ) this.version ++;
+
+       }
+
+} );
+
+Object.assign( InterleavedBuffer.prototype, {
+
+       isInterleavedBuffer: true,
+
+       onUploadCallback: function () {},
+
+       setUsage: function ( value ) {
+
+               this.usage = value;
+
+               return this;
+
+       },
+
+       copy: function ( source ) {
+
+               this.array = new source.array.constructor( source.array );
+               this.count = source.count;
+               this.stride = source.stride;
+               this.usage = source.usage;
+
+               return this;
+
+       },
+
+       copyAt: function ( index1, attribute, index2 ) {
+
+               index1 *= this.stride;
+               index2 *= attribute.stride;
+
+               for ( let i = 0, l = this.stride; i < l; i ++ ) {
+
+                       this.array[ index1 + i ] = attribute.array[ index2 + i ];
+
+               }
+
+               return this;
+
+       },
+
+       set: function ( value, offset = 0 ) {
+
+               this.array.set( value, offset );
+
+               return this;
+
+       },
+
+       clone: function ( data ) {
+
+               if ( data.arrayBuffers === undefined ) {
+
+                       data.arrayBuffers = {};
+
+               }
+
+               if ( this.array.buffer._uuid === undefined ) {
+
+                       this.array.buffer._uuid = MathUtils.generateUUID();
+
+               }
+
+               if ( data.arrayBuffers[ this.array.buffer._uuid ] === undefined ) {
+
+                       data.arrayBuffers[ this.array.buffer._uuid ] = this.array.slice( 0 ).buffer;
+
+               }
+
+               const array = new this.array.constructor( data.arrayBuffers[ this.array.buffer._uuid ] );
+
+               const ib = new InterleavedBuffer( array, this.stride );
+               ib.setUsage( this.usage );
+
+               return ib;
+
+       },
+
+       onUpload: function ( callback ) {
+
+               this.onUploadCallback = callback;
+
+               return this;
+
+       },
+
+       toJSON: function ( data ) {
+
+               if ( data.arrayBuffers === undefined ) {
+
+                       data.arrayBuffers = {};
+
+               }
+
+               // generate UUID for array buffer if necessary
+
+               if ( this.array.buffer._uuid === undefined ) {
+
+                       this.array.buffer._uuid = MathUtils.generateUUID();
+
+               }
+
+               if ( data.arrayBuffers[ this.array.buffer._uuid ] === undefined ) {
+
+                       data.arrayBuffers[ this.array.buffer._uuid ] = Array.prototype.slice.call( new Uint32Array( this.array.buffer ) );
+
+               }
+
+               //
+
+               return {
+                       uuid: this.uuid,
+                       buffer: this.array.buffer._uuid,
+                       type: this.array.constructor.name,
+                       stride: this.stride
+               };
+
+       }
+
+} );
+
+const _vector$6 = new Vector3();
+
+function InterleavedBufferAttribute( interleavedBuffer, itemSize, offset, normalized ) {
+
+       this.name = '';
+
+       this.data = interleavedBuffer;
+       this.itemSize = itemSize;
+       this.offset = offset;
+
+       this.normalized = normalized === true;
+
+}
+
+Object.defineProperties( InterleavedBufferAttribute.prototype, {
+
+       count: {
+
+               get: function () {
+
+                       return this.data.count;
+
+               }
+
+       },
+
+       array: {
+
+               get: function () {
+
+                       return this.data.array;
+
+               }
+
+       },
+
+       needsUpdate: {
+
+               set: function ( value ) {
+
+                       this.data.needsUpdate = value;
+
+               }
+
+       }
+
+} );
+
+Object.assign( InterleavedBufferAttribute.prototype, {
+
+       isInterleavedBufferAttribute: true,
+
+       applyMatrix4: function ( m ) {
+
+               for ( let i = 0, l = this.data.count; i < l; i ++ ) {
+
+                       _vector$6.x = this.getX( i );
+                       _vector$6.y = this.getY( i );
+                       _vector$6.z = this.getZ( i );
+
+                       _vector$6.applyMatrix4( m );
+
+                       this.setXYZ( i, _vector$6.x, _vector$6.y, _vector$6.z );
+
+               }
+
+               return this;
+
+       },
+
+       setX: function ( index, x ) {
+
+               this.data.array[ index * this.data.stride + this.offset ] = x;
+
+               return this;
+
+       },
+
+       setY: function ( index, y ) {
+
+               this.data.array[ index * this.data.stride + this.offset + 1 ] = y;
+
+               return this;
+
+       },
+
+       setZ: function ( index, z ) {
+
+               this.data.array[ index * this.data.stride + this.offset + 2 ] = z;
+
+               return this;
+
+       },
+
+       setW: function ( index, w ) {
+
+               this.data.array[ index * this.data.stride + this.offset + 3 ] = w;
+
+               return this;
+
+       },
+
+       getX: function ( index ) {
+
+               return this.data.array[ index * this.data.stride + this.offset ];
+
+       },
+
+       getY: function ( index ) {
+
+               return this.data.array[ index * this.data.stride + this.offset + 1 ];
+
+       },
+
+       getZ: function ( index ) {
+
+               return this.data.array[ index * this.data.stride + this.offset + 2 ];
+
+       },
+
+       getW: function ( index ) {
+
+               return this.data.array[ index * this.data.stride + this.offset + 3 ];
+
+       },
+
+       setXY: function ( index, x, y ) {
+
+               index = index * this.data.stride + this.offset;
+
+               this.data.array[ index + 0 ] = x;
+               this.data.array[ index + 1 ] = y;
+
+               return this;
+
+       },
+
+       setXYZ: function ( index, x, y, z ) {
+
+               index = index * this.data.stride + this.offset;
+
+               this.data.array[ index + 0 ] = x;
+               this.data.array[ index + 1 ] = y;
+               this.data.array[ index + 2 ] = z;
+
+               return this;
+
+       },
+
+       setXYZW: function ( index, x, y, z, w ) {
+
+               index = index * this.data.stride + this.offset;
+
+               this.data.array[ index + 0 ] = x;
+               this.data.array[ index + 1 ] = y;
+               this.data.array[ index + 2 ] = z;
+               this.data.array[ index + 3 ] = w;
+
+               return this;
+
+       },
+
+       clone: function ( data ) {
+
+               if ( data === undefined ) {
+
+                       console.log( 'THREE.InterleavedBufferAttribute.clone(): Cloning an interlaved buffer attribute will deinterleave buffer data.' );
+
+                       const array = [];
+
+                       for ( let i = 0; i < this.count; i ++ ) {
+
+                               const index = i * this.data.stride + this.offset;
+
+                               for ( let j = 0; j < this.itemSize; j ++ ) {
+
+                                       array.push( this.data.array[ index + j ] );
+
+                               }
+
+                       }
+
+                       return new BufferAttribute( new this.array.constructor( array ), this.itemSize, this.normalized );
+
+               } else {
+
+                       if ( data.interleavedBuffers === undefined ) {
+
+                               data.interleavedBuffers = {};
+
+                       }
+
+                       if ( data.interleavedBuffers[ this.data.uuid ] === undefined ) {
+
+                               data.interleavedBuffers[ this.data.uuid ] = this.data.clone( data );
+
+                       }
+
+                       return new InterleavedBufferAttribute( data.interleavedBuffers[ this.data.uuid ], this.itemSize, this.offset, this.normalized );
+
+               }
+
+       },
+
+       toJSON: function ( data ) {
+
+               if ( data === undefined ) {
+
+                       console.log( 'THREE.InterleavedBufferAttribute.toJSON(): Serializing an interlaved buffer attribute will deinterleave buffer data.' );
+
+                       const array = [];
+
+                       for ( let i = 0; i < this.count; i ++ ) {
+
+                               const index = i * this.data.stride + this.offset;
+
+                               for ( let j = 0; j < this.itemSize; j ++ ) {
+
+                                       array.push( this.data.array[ index + j ] );
+
+                               }
+
+                       }
+
+                       // deinterleave data and save it as an ordinary buffer attribute for now
+
+                       return {
+                               itemSize: this.itemSize,
+                               type: this.array.constructor.name,
+                               array: array,
+                               normalized: this.normalized
+                       };
+
+               } else {
+
+                       // save as true interlaved attribtue
+
+                       if ( data.interleavedBuffers === undefined ) {
+
+                               data.interleavedBuffers = {};
+
+                       }
+
+                       if ( data.interleavedBuffers[ this.data.uuid ] === undefined ) {
+
+                               data.interleavedBuffers[ this.data.uuid ] = this.data.toJSON( data );
+
+                       }
+
+                       return {
+                               isInterleavedBufferAttribute: true,
+                               itemSize: this.itemSize,
+                               data: this.data.uuid,
+                               offset: this.offset,
+                               normalized: this.normalized
+                       };
+
+               }
+
+       }
+
+} );
+
+/**
+ * parameters = {
+ *  color: <hex>,
+ *  map: new THREE.Texture( <Image> ),
+ *  alphaMap: new THREE.Texture( <Image> ),
+ *  rotation: <float>,
+ *  sizeAttenuation: <bool>
+ * }
+ */
+
+function SpriteMaterial( parameters ) {
+
+       Material.call( this );
+
+       this.type = 'SpriteMaterial';
+
+       this.color = new Color( 0xffffff );
+
+       this.map = null;
+
+       this.alphaMap = null;
+
+       this.rotation = 0;
+
+       this.sizeAttenuation = true;
+
+       this.transparent = true;
+
+       this.setValues( parameters );
+
+}
+
+SpriteMaterial.prototype = Object.create( Material.prototype );
+SpriteMaterial.prototype.constructor = SpriteMaterial;
+SpriteMaterial.prototype.isSpriteMaterial = true;
+
+SpriteMaterial.prototype.copy = function ( source ) {
+
+       Material.prototype.copy.call( this, source );
+
+       this.color.copy( source.color );
+
+       this.map = source.map;
+
+       this.alphaMap = source.alphaMap;
+
+       this.rotation = source.rotation;
+
+       this.sizeAttenuation = source.sizeAttenuation;
+
+       return this;
+
+};
+
+let _geometry;
+
+const _intersectPoint = new Vector3();
+const _worldScale = new Vector3();
+const _mvPosition = new Vector3();
+
+const _alignedPosition = new Vector2();
+const _rotatedPosition = new Vector2();
+const _viewWorldMatrix = new Matrix4();
+
+const _vA$1 = new Vector3();
+const _vB$1 = new Vector3();
+const _vC$1 = new Vector3();
+
+const _uvA$1 = new Vector2();
+const _uvB$1 = new Vector2();
+const _uvC$1 = new Vector2();
+
+function Sprite( material ) {
+
+       Object3D.call( this );
+
+       this.type = 'Sprite';
+
+       if ( _geometry === undefined ) {
+
+               _geometry = new BufferGeometry();
+
+               const float32Array = new Float32Array( [
+                       - 0.5, - 0.5, 0, 0, 0,
+                       0.5, - 0.5, 0, 1, 0,
+                       0.5, 0.5, 0, 1, 1,
+                       - 0.5, 0.5, 0, 0, 1
+               ] );
+
+               const interleavedBuffer = new InterleavedBuffer( float32Array, 5 );
+
+               _geometry.setIndex( [ 0, 1, 2,  0, 2, 3 ] );
+               _geometry.setAttribute( 'position', new InterleavedBufferAttribute( interleavedBuffer, 3, 0, false ) );
+               _geometry.setAttribute( 'uv', new InterleavedBufferAttribute( interleavedBuffer, 2, 3, false ) );
+
+       }
+
+       this.geometry = _geometry;
+       this.material = ( material !== undefined ) ? material : new SpriteMaterial();
+
+       this.center = new Vector2( 0.5, 0.5 );
+
+}
+
+Sprite.prototype = Object.assign( Object.create( Object3D.prototype ), {
+
+       constructor: Sprite,
+
+       isSprite: true,
+
+       raycast: function ( raycaster, intersects ) {
+
+               if ( raycaster.camera === null ) {
+
+                       console.error( 'THREE.Sprite: "Raycaster.camera" needs to be set in order to raycast against sprites.' );
+
+               }
+
+               _worldScale.setFromMatrixScale( this.matrixWorld );
+
+               _viewWorldMatrix.copy( raycaster.camera.matrixWorld );
+               this.modelViewMatrix.multiplyMatrices( raycaster.camera.matrixWorldInverse, this.matrixWorld );
+
+               _mvPosition.setFromMatrixPosition( this.modelViewMatrix );
+
+               if ( raycaster.camera.isPerspectiveCamera && this.material.sizeAttenuation === false ) {
+
+                       _worldScale.multiplyScalar( - _mvPosition.z );
+
+               }
+
+               const rotation = this.material.rotation;
+               let sin, cos;
+
+               if ( rotation !== 0 ) {
+
+                       cos = Math.cos( rotation );
+                       sin = Math.sin( rotation );
+
+               }
+
+               const center = this.center;
+
+               transformVertex( _vA$1.set( - 0.5, - 0.5, 0 ), _mvPosition, center, _worldScale, sin, cos );
+               transformVertex( _vB$1.set( 0.5, - 0.5, 0 ), _mvPosition, center, _worldScale, sin, cos );
+               transformVertex( _vC$1.set( 0.5, 0.5, 0 ), _mvPosition, center, _worldScale, sin, cos );
+
+               _uvA$1.set( 0, 0 );
+               _uvB$1.set( 1, 0 );
+               _uvC$1.set( 1, 1 );
+
+               // check first triangle
+               let intersect = raycaster.ray.intersectTriangle( _vA$1, _vB$1, _vC$1, false, _intersectPoint );
+
+               if ( intersect === null ) {
+
+                       // check second triangle
+                       transformVertex( _vB$1.set( - 0.5, 0.5, 0 ), _mvPosition, center, _worldScale, sin, cos );
+                       _uvB$1.set( 0, 1 );
+
+                       intersect = raycaster.ray.intersectTriangle( _vA$1, _vC$1, _vB$1, false, _intersectPoint );
+                       if ( intersect === null ) {
+
+                               return;
+
+                       }
+
+               }
+
+               const distance = raycaster.ray.origin.distanceTo( _intersectPoint );
+
+               if ( distance < raycaster.near || distance > raycaster.far ) return;
+
+               intersects.push( {
+
+                       distance: distance,
+                       point: _intersectPoint.clone(),
+                       uv: Triangle.getUV( _intersectPoint, _vA$1, _vB$1, _vC$1, _uvA$1, _uvB$1, _uvC$1, new Vector2() ),
+                       face: null,
+                       object: this
+
+               } );
+
+       },
+
+       copy: function ( source ) {
+
+               Object3D.prototype.copy.call( this, source );
+
+               if ( source.center !== undefined ) this.center.copy( source.center );
+
+               this.material = source.material;
+
+               return this;
+
+       }
+
+} );
+
+function transformVertex( vertexPosition, mvPosition, center, scale, sin, cos ) {
+
+       // compute position in camera space
+       _alignedPosition.subVectors( vertexPosition, center ).addScalar( 0.5 ).multiply( scale );
+
+       // to check if rotation is not zero
+       if ( sin !== undefined ) {
+
+               _rotatedPosition.x = ( cos * _alignedPosition.x ) - ( sin * _alignedPosition.y );
+               _rotatedPosition.y = ( sin * _alignedPosition.x ) + ( cos * _alignedPosition.y );
+
+       } else {
+
+               _rotatedPosition.copy( _alignedPosition );
+
+       }
+
+
+       vertexPosition.copy( mvPosition );
+       vertexPosition.x += _rotatedPosition.x;
+       vertexPosition.y += _rotatedPosition.y;
+
+       // transform to world space
+       vertexPosition.applyMatrix4( _viewWorldMatrix );
+
+}
+
+const _v1$4 = new Vector3();
+const _v2$2 = new Vector3();
+
+function LOD() {
+
+       Object3D.call( this );
+
+       this._currentLevel = 0;
+
+       this.type = 'LOD';
+
+       Object.defineProperties( this, {
+               levels: {
+                       enumerable: true,
+                       value: []
+               }
+       } );
+
+       this.autoUpdate = true;
+
+}
+
+LOD.prototype = Object.assign( Object.create( Object3D.prototype ), {
+
+       constructor: LOD,
+
+       isLOD: true,
+
+       copy: function ( source ) {
+
+               Object3D.prototype.copy.call( this, source, false );
+
+               const levels = source.levels;
+
+               for ( let i = 0, l = levels.length; i < l; i ++ ) {
+
+                       const level = levels[ i ];
+
+                       this.addLevel( level.object.clone(), level.distance );
+
+               }
+
+               this.autoUpdate = source.autoUpdate;
+
+               return this;
+
+       },
+
+       addLevel: function ( object, distance = 0 ) {
+
+               distance = Math.abs( distance );
+
+               const levels = this.levels;
+
+               let l;
+
+               for ( l = 0; l < levels.length; l ++ ) {
+
+                       if ( distance < levels[ l ].distance ) {
+
+                               break;
+
+                       }
+
+               }
+
+               levels.splice( l, 0, { distance: distance, object: object } );
+
+               this.add( object );
+
+               return this;
+
+       },
+
+       getCurrentLevel: function () {
+
+               return this._currentLevel;
+
+       },
+
+       getObjectForDistance: function ( distance ) {
+
+               const levels = this.levels;
+
+               if ( levels.length > 0 ) {
+
+                       let i, l;
+
+                       for ( i = 1, l = levels.length; i < l; i ++ ) {
+
+                               if ( distance < levels[ i ].distance ) {
+
+                                       break;
+
+                               }
+
+                       }
+
+                       return levels[ i - 1 ].object;
+
+               }
+
+               return null;
+
+       },
+
+       raycast: function ( raycaster, intersects ) {
+
+               const levels = this.levels;
+
+               if ( levels.length > 0 ) {
+
+                       _v1$4.setFromMatrixPosition( this.matrixWorld );
+
+                       const distance = raycaster.ray.origin.distanceTo( _v1$4 );
+
+                       this.getObjectForDistance( distance ).raycast( raycaster, intersects );
+
+               }
+
+       },
+
+       update: function ( camera ) {
+
+               const levels = this.levels;
+
+               if ( levels.length > 1 ) {
+
+                       _v1$4.setFromMatrixPosition( camera.matrixWorld );
+                       _v2$2.setFromMatrixPosition( this.matrixWorld );
+
+                       const distance = _v1$4.distanceTo( _v2$2 ) / camera.zoom;
+
+                       levels[ 0 ].object.visible = true;
+
+                       let i, l;
+
+                       for ( i = 1, l = levels.length; i < l; i ++ ) {
+
+                               if ( distance >= levels[ i ].distance ) {
+
+                                       levels[ i - 1 ].object.visible = false;
+                                       levels[ i ].object.visible = true;
+
+                               } else {
+
+                                       break;
+
+                               }
+
+                       }
+
+                       this._currentLevel = i - 1;
+
+                       for ( ; i < l; i ++ ) {
+
+                               levels[ i ].object.visible = false;
+
+                       }
+
+               }
+
+       },
+
+       toJSON: function ( meta ) {
+
+               const data = Object3D.prototype.toJSON.call( this, meta );
+
+               if ( this.autoUpdate === false ) data.object.autoUpdate = false;
+
+               data.object.levels = [];
+
+               const levels = this.levels;
+
+               for ( let i = 0, l = levels.length; i < l; i ++ ) {
+
+                       const level = levels[ i ];
+
+                       data.object.levels.push( {
+                               object: level.object.uuid,
+                               distance: level.distance
+                       } );
+
+               }
+
+               return data;
+
+       }
+
+} );
+
+const _basePosition = new Vector3();
+
+const _skinIndex = new Vector4();
+const _skinWeight = new Vector4();
+
+const _vector$7 = new Vector3();
+const _matrix$1 = new Matrix4();
+
+function SkinnedMesh( geometry, material ) {
+
+       if ( geometry && geometry.isGeometry ) {
+
+               console.error( 'THREE.SkinnedMesh no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.' );
+
+       }
+
+       Mesh.call( this, geometry, material );
+
+       this.type = 'SkinnedMesh';
+
+       this.bindMode = 'attached';
+       this.bindMatrix = new Matrix4();
+       this.bindMatrixInverse = new Matrix4();
+
+}
+
+SkinnedMesh.prototype = Object.assign( Object.create( Mesh.prototype ), {
+
+       constructor: SkinnedMesh,
+
+       isSkinnedMesh: true,
+
+       copy: function ( source ) {
+
+               Mesh.prototype.copy.call( this, source );
+
+               this.bindMode = source.bindMode;
+               this.bindMatrix.copy( source.bindMatrix );
+               this.bindMatrixInverse.copy( source.bindMatrixInverse );
+
+               this.skeleton = source.skeleton;
+
+               return this;
+
+       },
+
+       bind: function ( skeleton, bindMatrix ) {
+
+               this.skeleton = skeleton;
+
+               if ( bindMatrix === undefined ) {
+
+                       this.updateMatrixWorld( true );
+
+                       this.skeleton.calculateInverses();
+
+                       bindMatrix = this.matrixWorld;
+
+               }
+
+               this.bindMatrix.copy( bindMatrix );
+               this.bindMatrixInverse.copy( bindMatrix ).invert();
+
+       },
+
+       pose: function () {
+
+               this.skeleton.pose();
+
+       },
+
+       normalizeSkinWeights: function () {
+
+               const vector = new Vector4();
+
+               const skinWeight = this.geometry.attributes.skinWeight;
+
+               for ( let i = 0, l = skinWeight.count; i < l; i ++ ) {
+
+                       vector.x = skinWeight.getX( i );
+                       vector.y = skinWeight.getY( i );
+                       vector.z = skinWeight.getZ( i );
+                       vector.w = skinWeight.getW( i );
+
+                       const scale = 1.0 / vector.manhattanLength();
+
+                       if ( scale !== Infinity ) {
+
+                               vector.multiplyScalar( scale );
+
+                       } else {
+
+                               vector.set( 1, 0, 0, 0 ); // do something reasonable
+
+                       }
+
+                       skinWeight.setXYZW( i, vector.x, vector.y, vector.z, vector.w );
+
+               }
+
+       },
+
+       updateMatrixWorld: function ( force ) {
+
+               Mesh.prototype.updateMatrixWorld.call( this, force );
+
+               if ( this.bindMode === 'attached' ) {
+
+                       this.bindMatrixInverse.copy( this.matrixWorld ).invert();
+
+               } else if ( this.bindMode === 'detached' ) {
+
+                       this.bindMatrixInverse.copy( this.bindMatrix ).invert();
+
+               } else {
+
+                       console.warn( 'THREE.SkinnedMesh: Unrecognized bindMode: ' + this.bindMode );
+
+               }
+
+       },
+
+       boneTransform: function ( index, target ) {
+
+               const skeleton = this.skeleton;
+               const geometry = this.geometry;
+
+               _skinIndex.fromBufferAttribute( geometry.attributes.skinIndex, index );
+               _skinWeight.fromBufferAttribute( geometry.attributes.skinWeight, index );
+
+               _basePosition.fromBufferAttribute( geometry.attributes.position, index ).applyMatrix4( this.bindMatrix );
+
+               target.set( 0, 0, 0 );
+
+               for ( let i = 0; i < 4; i ++ ) {
+
+                       const weight = _skinWeight.getComponent( i );
+
+                       if ( weight !== 0 ) {
+
+                               const boneIndex = _skinIndex.getComponent( i );
+
+                               _matrix$1.multiplyMatrices( skeleton.bones[ boneIndex ].matrixWorld, skeleton.boneInverses[ boneIndex ] );
+
+                               target.addScaledVector( _vector$7.copy( _basePosition ).applyMatrix4( _matrix$1 ), weight );
+
+                       }
+
+               }
+
+               return target.applyMatrix4( this.bindMatrixInverse );
+
+       }
+
+} );
+
+function Bone() {
+
+       Object3D.call( this );
+
+       this.type = 'Bone';
+
+}
+
+Bone.prototype = Object.assign( Object.create( Object3D.prototype ), {
+
+       constructor: Bone,
+
+       isBone: true
+
+} );
+
+const _offsetMatrix = new Matrix4();
+const _identityMatrix = new Matrix4();
+
+function Skeleton( bones = [], boneInverses = [] ) {
+
+       this.uuid = MathUtils.generateUUID();
+
+       this.bones = bones.slice( 0 );
+       this.boneInverses = boneInverses;
+       this.boneMatrices = null;
+
+       this.boneTexture = null;
+       this.boneTextureSize = 0;
+
+       this.frame = - 1;
+
+       this.init();
+
+}
+
+Object.assign( Skeleton.prototype, {
+
+       init: function () {
+
+               const bones = this.bones;
+               const boneInverses = this.boneInverses;
+
+               this.boneMatrices = new Float32Array( bones.length * 16 );
+
+               // calculate inverse bone matrices if necessary
+
+               if ( boneInverses.length === 0 ) {
+
+                       this.calculateInverses();
+
+               } else {
+
+                       // handle special case
+
+                       if ( bones.length !== boneInverses.length ) {
+
+                               console.warn( 'THREE.Skeleton: Number of inverse bone matrices does not match amount of bones.' );
+
+                               this.boneInverses = [];
+
+                               for ( let i = 0, il = this.bones.length; i < il; i ++ ) {
+
+                                       this.boneInverses.push( new Matrix4() );
+
+                               }
+
+                       }
+
+               }
+
+       },
+
+       calculateInverses: function () {
+
+               this.boneInverses.length = 0;
+
+               for ( let i = 0, il = this.bones.length; i < il; i ++ ) {
+
+                       const inverse = new Matrix4();
+
+                       if ( this.bones[ i ] ) {
+
+                               inverse.copy( this.bones[ i ].matrixWorld ).invert();
+
+                       }
+
+                       this.boneInverses.push( inverse );
+
+               }
+
+       },
+
+       pose: function () {
+
+               // recover the bind-time world matrices
+
+               for ( let i = 0, il = this.bones.length; i < il; i ++ ) {
+
+                       const bone = this.bones[ i ];
+
+                       if ( bone ) {
+
+                               bone.matrixWorld.copy( this.boneInverses[ i ] ).invert();
+
+                       }
+
+               }
+
+               // compute the local matrices, positions, rotations and scales
+
+               for ( let i = 0, il = this.bones.length; i < il; i ++ ) {
+
+                       const bone = this.bones[ i ];
+
+                       if ( bone ) {
+
+                               if ( bone.parent && bone.parent.isBone ) {
+
+                                       bone.matrix.copy( bone.parent.matrixWorld ).invert();
+                                       bone.matrix.multiply( bone.matrixWorld );
+
+                               } else {
+
+                                       bone.matrix.copy( bone.matrixWorld );
+
+                               }
+
+                               bone.matrix.decompose( bone.position, bone.quaternion, bone.scale );
+
+                       }
+
+               }
+
+       },
+
+       update: function () {
+
+               const bones = this.bones;
+               const boneInverses = this.boneInverses;
+               const boneMatrices = this.boneMatrices;
+               const boneTexture = this.boneTexture;
+
+               // flatten bone matrices to array
+
+               for ( let i = 0, il = bones.length; i < il; i ++ ) {
+
+                       // compute the offset between the current and the original transform
+
+                       const matrix = bones[ i ] ? bones[ i ].matrixWorld : _identityMatrix;
+
+                       _offsetMatrix.multiplyMatrices( matrix, boneInverses[ i ] );
+                       _offsetMatrix.toArray( boneMatrices, i * 16 );
+
+               }
+
+               if ( boneTexture !== null ) {
+
+                       boneTexture.needsUpdate = true;
+
+               }
+
+       },
+
+       clone: function () {
+
+               return new Skeleton( this.bones, this.boneInverses );
+
+       },
+
+       getBoneByName: function ( name ) {
+
+               for ( let i = 0, il = this.bones.length; i < il; i ++ ) {
+
+                       const bone = this.bones[ i ];
+
+                       if ( bone.name === name ) {
+
+                               return bone;
+
+                       }
+
+               }
+
+               return undefined;
+
+       },
+
+       dispose: function ( ) {
+
+               if ( this.boneTexture !== null ) {
+
+                       this.boneTexture.dispose();
+
+                       this.boneTexture = null;
+
+               }
+
+       },
+
+       fromJSON: function ( json, bones ) {
+
+               this.uuid = json.uuid;
+
+               for ( let i = 0, l = json.bones.length; i < l; i ++ ) {
+
+                       const uuid = json.bones[ i ];
+                       let bone = bones[ uuid ];
+
+                       if ( bone === undefined ) {
+
+                               console.warn( 'THREE.Skeleton: No bone found with UUID:', uuid );
+                               bone = new Bone();
+
+                       }
+
+                       this.bones.push( bone );
+                       this.boneInverses.push( new Matrix4().fromArray( json.boneInverses[ i ] ) );
+
+               }
+
+               this.init();
+
+               return this;
+
+       },
+
+       toJSON: function () {
+
+               const data = {
+                       metadata: {
+                               version: 4.5,
+                               type: 'Skeleton',
+                               generator: 'Skeleton.toJSON'
+                       },
+                       bones: [],
+                       boneInverses: []
+               };
+
+               data.uuid = this.uuid;
+
+               const bones = this.bones;
+               const boneInverses = this.boneInverses;
+
+               for ( let i = 0, l = bones.length; i < l; i ++ ) {
+
+                       const bone = bones[ i ];
+                       data.bones.push( bone.uuid );
+
+                       const boneInverse = boneInverses[ i ];
+                       data.boneInverses.push( boneInverse.toArray() );
+
+               }
+
+               return data;
+
+       }
+
+} );
+
+const _instanceLocalMatrix = new Matrix4();
+const _instanceWorldMatrix = new Matrix4();
+
+const _instanceIntersects = [];
+
+const _mesh = new Mesh();
+
+function InstancedMesh( geometry, material, count ) {
+
+       Mesh.call( this, geometry, material );
+
+       this.instanceMatrix = new BufferAttribute( new Float32Array( count * 16 ), 16 );
+       this.instanceColor = null;
+
+       this.count = count;
+
+       this.frustumCulled = false;
+
+}
+
+InstancedMesh.prototype = Object.assign( Object.create( Mesh.prototype ), {
+
+       constructor: InstancedMesh,
+
+       isInstancedMesh: true,
+
+       copy: function ( source ) {
+
+               Mesh.prototype.copy.call( this, source );
+
+               this.instanceMatrix.copy( source.instanceMatrix );
+
+               if ( source.instanceColor !== null ) this.instanceColor = source.instanceColor.clone();
+
+               this.count = source.count;
+
+               return this;
+
+       },
+
+       getColorAt: function ( index, color ) {
+
+               color.fromArray( this.instanceColor.array, index * 3 );
+
+       },
+
+       getMatrixAt: function ( index, matrix ) {
+
+               matrix.fromArray( this.instanceMatrix.array, index * 16 );
+
+       },
+
+       raycast: function ( raycaster, intersects ) {
+
+               const matrixWorld = this.matrixWorld;
+               const raycastTimes = this.count;
+
+               _mesh.geometry = this.geometry;
+               _mesh.material = this.material;
+
+               if ( _mesh.material === undefined ) return;
+
+               for ( let instanceId = 0; instanceId < raycastTimes; instanceId ++ ) {
+
+                       // calculate the world matrix for each instance
+
+                       this.getMatrixAt( instanceId, _instanceLocalMatrix );
+
+                       _instanceWorldMatrix.multiplyMatrices( matrixWorld, _instanceLocalMatrix );
+
+                       // the mesh represents this single instance
+
+                       _mesh.matrixWorld = _instanceWorldMatrix;
+
+                       _mesh.raycast( raycaster, _instanceIntersects );
+
+                       // process the result of raycast
+
+                       for ( let i = 0, l = _instanceIntersects.length; i < l; i ++ ) {
+
+                               const intersect = _instanceIntersects[ i ];
+                               intersect.instanceId = instanceId;
+                               intersect.object = this;
+                               intersects.push( intersect );
+
+                       }
+
+                       _instanceIntersects.length = 0;
+
+               }
+
+       },
+
+       setColorAt: function ( index, color ) {
+
+               if ( this.instanceColor === null ) {
+
+                       this.instanceColor = new BufferAttribute( new Float32Array( this.count * 3 ), 3 );
+
+               }
+
+               color.toArray( this.instanceColor.array, index * 3 );
+
+       },
+
+       setMatrixAt: function ( index, matrix ) {
+
+               matrix.toArray( this.instanceMatrix.array, index * 16 );
+
+       },
+
+       updateMorphTargets: function () {
+
+       },
+
+       dispose: function () {
+
+               this.dispatchEvent( { type: 'dispose' } );
+
+       }
+
+} );
+
+/**
+ * parameters = {
+ *  color: <hex>,
+ *  opacity: <float>,
+ *
+ *  linewidth: <float>,
+ *  linecap: "round",
+ *  linejoin: "round"
+ * }
+ */
+
+function LineBasicMaterial( parameters ) {
+
+       Material.call( this );
+
+       this.type = 'LineBasicMaterial';
+
+       this.color = new Color( 0xffffff );
+
+       this.linewidth = 1;
+       this.linecap = 'round';
+       this.linejoin = 'round';
+
+       this.morphTargets = false;
+
+       this.setValues( parameters );
+
+}
+
+LineBasicMaterial.prototype = Object.create( Material.prototype );
+LineBasicMaterial.prototype.constructor = LineBasicMaterial;
+
+LineBasicMaterial.prototype.isLineBasicMaterial = true;
+
+LineBasicMaterial.prototype.copy = function ( source ) {
+
+       Material.prototype.copy.call( this, source );
+
+       this.color.copy( source.color );
+
+       this.linewidth = source.linewidth;
+       this.linecap = source.linecap;
+       this.linejoin = source.linejoin;
+
+       this.morphTargets = source.morphTargets;
+
+       return this;
+
+};
+
+const _start = new Vector3();
+const _end = new Vector3();
+const _inverseMatrix$1 = new Matrix4();
+const _ray$1 = new Ray();
+const _sphere$2 = new Sphere();
+
+function Line( geometry = new BufferGeometry(), material = new LineBasicMaterial() ) {
+
+       Object3D.call( this );
+
+       this.type = 'Line';
+
+       this.geometry = geometry;
+       this.material = material;
+
+       this.updateMorphTargets();
+
+}
+
+Line.prototype = Object.assign( Object.create( Object3D.prototype ), {
+
+       constructor: Line,
+
+       isLine: true,
+
+       copy: function ( source ) {
+
+               Object3D.prototype.copy.call( this, source );
+
+               this.material = source.material;
+               this.geometry = source.geometry;
+
+               return this;
+
+       },
+
+       computeLineDistances: function () {
+
+               const geometry = this.geometry;
+
+               if ( geometry.isBufferGeometry ) {
+
+                       // we assume non-indexed geometry
+
+                       if ( geometry.index === null ) {
+
+                               const positionAttribute = geometry.attributes.position;
+                               const lineDistances = [ 0 ];
+
+                               for ( let i = 1, l = positionAttribute.count; i < l; i ++ ) {
+
+                                       _start.fromBufferAttribute( positionAttribute, i - 1 );
+                                       _end.fromBufferAttribute( positionAttribute, i );
+
+                                       lineDistances[ i ] = lineDistances[ i - 1 ];
+                                       lineDistances[ i ] += _start.distanceTo( _end );
+
+                               }
+
+                               geometry.setAttribute( 'lineDistance', new Float32BufferAttribute( lineDistances, 1 ) );
+
+                       } else {
+
+                               console.warn( 'THREE.Line.computeLineDistances(): Computation only possible with non-indexed BufferGeometry.' );
+
+                       }
+
+               } else if ( geometry.isGeometry ) {
+
+                       console.error( 'THREE.Line.computeLineDistances() no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.' );
+
+               }
+
+               return this;
+
+       },
+
+       raycast: function ( raycaster, intersects ) {
+
+               const geometry = this.geometry;
+               const matrixWorld = this.matrixWorld;
+               const threshold = raycaster.params.Line.threshold;
+
+               // Checking boundingSphere distance to ray
+
+               if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();
+
+               _sphere$2.copy( geometry.boundingSphere );
+               _sphere$2.applyMatrix4( matrixWorld );
+               _sphere$2.radius += threshold;
+
+               if ( raycaster.ray.intersectsSphere( _sphere$2 ) === false ) return;
+
+               //
+
+               _inverseMatrix$1.copy( matrixWorld ).invert();
+               _ray$1.copy( raycaster.ray ).applyMatrix4( _inverseMatrix$1 );
+
+               const localThreshold = threshold / ( ( this.scale.x + this.scale.y + this.scale.z ) / 3 );
+               const localThresholdSq = localThreshold * localThreshold;
+
+               const vStart = new Vector3();
+               const vEnd = new Vector3();
+               const interSegment = new Vector3();
+               const interRay = new Vector3();
+               const step = this.isLineSegments ? 2 : 1;
+
+               if ( geometry.isBufferGeometry ) {
+
+                       const index = geometry.index;
+                       const attributes = geometry.attributes;
+                       const positionAttribute = attributes.position;
+
+                       if ( index !== null ) {
+
+                               const indices = index.array;
+
+                               for ( let i = 0, l = indices.length - 1; i < l; i += step ) {
+
+                                       const a = indices[ i ];
+                                       const b = indices[ i + 1 ];
+
+                                       vStart.fromBufferAttribute( positionAttribute, a );
+                                       vEnd.fromBufferAttribute( positionAttribute, b );
+
+                                       const distSq = _ray$1.distanceSqToSegment( vStart, vEnd, interRay, interSegment );
+
+                                       if ( distSq > localThresholdSq ) continue;
+
+                                       interRay.applyMatrix4( this.matrixWorld ); //Move back to world space for distance calculation
+
+                                       const distance = raycaster.ray.origin.distanceTo( interRay );
+
+                                       if ( distance < raycaster.near || distance > raycaster.far ) continue;
+
+                                       intersects.push( {
+
+                                               distance: distance,
+                                               // What do we want? intersection point on the ray or on the segment??
+                                               // point: raycaster.ray.at( distance ),
+                                               point: interSegment.clone().applyMatrix4( this.matrixWorld ),
+                                               index: i,
+                                               face: null,
+                                               faceIndex: null,
+                                               object: this
+
+                                       } );
+
+                               }
+
+                       } else {
+
+                               for ( let i = 0, l = positionAttribute.count - 1; i < l; i += step ) {
+
+                                       vStart.fromBufferAttribute( positionAttribute, i );
+                                       vEnd.fromBufferAttribute( positionAttribute, i + 1 );
+
+                                       const distSq = _ray$1.distanceSqToSegment( vStart, vEnd, interRay, interSegment );
+
+                                       if ( distSq > localThresholdSq ) continue;
+
+                                       interRay.applyMatrix4( this.matrixWorld ); //Move back to world space for distance calculation
+
+                                       const distance = raycaster.ray.origin.distanceTo( interRay );
+
+                                       if ( distance < raycaster.near || distance > raycaster.far ) continue;
+
+                                       intersects.push( {
+
+                                               distance: distance,
+                                               // What do we want? intersection point on the ray or on the segment??
+                                               // point: raycaster.ray.at( distance ),
+                                               point: interSegment.clone().applyMatrix4( this.matrixWorld ),
+                                               index: i,
+                                               face: null,
+                                               faceIndex: null,
+                                               object: this
+
+                                       } );
+
+                               }
+
+                       }
+
+               } else if ( geometry.isGeometry ) {
+
+                       console.error( 'THREE.Line.raycast() no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.' );
+
+               }
+
+       },
+
+       updateMorphTargets: function () {
+
+               const geometry = this.geometry;
+
+               if ( geometry.isBufferGeometry ) {
+
+                       const morphAttributes = geometry.morphAttributes;
+                       const keys = Object.keys( morphAttributes );
+
+                       if ( keys.length > 0 ) {
+
+                               const morphAttribute = morphAttributes[ keys[ 0 ] ];
+
+                               if ( morphAttribute !== undefined ) {
+
+                                       this.morphTargetInfluences = [];
+                                       this.morphTargetDictionary = {};
+
+                                       for ( let m = 0, ml = morphAttribute.length; m < ml; m ++ ) {
+
+                                               const name = morphAttribute[ m ].name || String( m );
+
+                                               this.morphTargetInfluences.push( 0 );
+                                               this.morphTargetDictionary[ name ] = m;
+
+                                       }
+
+                               }
+
+                       }
+
+               } else {
+
+                       const morphTargets = geometry.morphTargets;
+
+                       if ( morphTargets !== undefined && morphTargets.length > 0 ) {
+
+                               console.error( 'THREE.Line.updateMorphTargets() does not support THREE.Geometry. Use THREE.BufferGeometry instead.' );
+
+                       }
+
+               }
+
+       }
+
+} );
+
+const _start$1 = new Vector3();
+const _end$1 = new Vector3();
+
+function LineSegments( geometry, material ) {
+
+       Line.call( this, geometry, material );
+
+       this.type = 'LineSegments';
+
+}
+
+LineSegments.prototype = Object.assign( Object.create( Line.prototype ), {
+
+       constructor: LineSegments,
+
+       isLineSegments: true,
+
+       computeLineDistances: function () {
+
+               const geometry = this.geometry;
+
+               if ( geometry.isBufferGeometry ) {
+
+                       // we assume non-indexed geometry
+
+                       if ( geometry.index === null ) {
+
+                               const positionAttribute = geometry.attributes.position;
+                               const lineDistances = [];
+
+                               for ( let i = 0, l = positionAttribute.count; i < l; i += 2 ) {
+
+                                       _start$1.fromBufferAttribute( positionAttribute, i );
+                                       _end$1.fromBufferAttribute( positionAttribute, i + 1 );
+
+                                       lineDistances[ i ] = ( i === 0 ) ? 0 : lineDistances[ i - 1 ];
+                                       lineDistances[ i + 1 ] = lineDistances[ i ] + _start$1.distanceTo( _end$1 );
+
+                               }
+
+                               geometry.setAttribute( 'lineDistance', new Float32BufferAttribute( lineDistances, 1 ) );
+
+                       } else {
+
+                               console.warn( 'THREE.LineSegments.computeLineDistances(): Computation only possible with non-indexed BufferGeometry.' );
+
+                       }
+
+               } else if ( geometry.isGeometry ) {
+
+                       console.error( 'THREE.LineSegments.computeLineDistances() no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.' );
+
+               }
+
+               return this;
+
+       }
+
+} );
+
+function LineLoop( geometry, material ) {
+
+       Line.call( this, geometry, material );
+
+       this.type = 'LineLoop';
+
+}
+
+LineLoop.prototype = Object.assign( Object.create( Line.prototype ), {
+
+       constructor: LineLoop,
+
+       isLineLoop: true,
+
+} );
+
+/**
+ * parameters = {
+ *  color: <hex>,
+ *  opacity: <float>,
+ *  map: new THREE.Texture( <Image> ),
+ *  alphaMap: new THREE.Texture( <Image> ),
+ *
+ *  size: <float>,
+ *  sizeAttenuation: <bool>
+ *
+ *  morphTargets: <bool>
+ * }
+ */
+
+function PointsMaterial( parameters ) {
+
+       Material.call( this );
+
+       this.type = 'PointsMaterial';
+
+       this.color = new Color( 0xffffff );
+
+       this.map = null;
+
+       this.alphaMap = null;
+
+       this.size = 1;
+       this.sizeAttenuation = true;
+
+       this.morphTargets = false;
+
+       this.setValues( parameters );
+
+}
+
+PointsMaterial.prototype = Object.create( Material.prototype );
+PointsMaterial.prototype.constructor = PointsMaterial;
+
+PointsMaterial.prototype.isPointsMaterial = true;
+
+PointsMaterial.prototype.copy = function ( source ) {
+
+       Material.prototype.copy.call( this, source );
+
+       this.color.copy( source.color );
+
+       this.map = source.map;
+
+       this.alphaMap = source.alphaMap;
+
+       this.size = source.size;
+       this.sizeAttenuation = source.sizeAttenuation;
+
+       this.morphTargets = source.morphTargets;
+
+       return this;
+
+};
+
+const _inverseMatrix$2 = new Matrix4();
+const _ray$2 = new Ray();
+const _sphere$3 = new Sphere();
+const _position$1 = new Vector3();
+
+function Points( geometry = new BufferGeometry(), material = new PointsMaterial() ) {
+
+       Object3D.call( this );
+
+       this.type = 'Points';
+
+       this.geometry = geometry;
+       this.material = material;
+
+       this.updateMorphTargets();
+
+}
+
+Points.prototype = Object.assign( Object.create( Object3D.prototype ), {
+
+       constructor: Points,
+
+       isPoints: true,
+
+       copy: function ( source ) {
+
+               Object3D.prototype.copy.call( this, source );
+
+               this.material = source.material;
+               this.geometry = source.geometry;
+
+               return this;
+
+       },
+
+       raycast: function ( raycaster, intersects ) {
+
+               const geometry = this.geometry;
+               const matrixWorld = this.matrixWorld;
+               const threshold = raycaster.params.Points.threshold;
+
+               // Checking boundingSphere distance to ray
+
+               if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();
+
+               _sphere$3.copy( geometry.boundingSphere );
+               _sphere$3.applyMatrix4( matrixWorld );
+               _sphere$3.radius += threshold;
+
+               if ( raycaster.ray.intersectsSphere( _sphere$3 ) === false ) return;
+
+               //
+
+               _inverseMatrix$2.copy( matrixWorld ).invert();
+               _ray$2.copy( raycaster.ray ).applyMatrix4( _inverseMatrix$2 );
+
+               const localThreshold = threshold / ( ( this.scale.x + this.scale.y + this.scale.z ) / 3 );
+               const localThresholdSq = localThreshold * localThreshold;
+
+               if ( geometry.isBufferGeometry ) {
+
+                       const index = geometry.index;
+                       const attributes = geometry.attributes;
+                       const positionAttribute = attributes.position;
+
+                       if ( index !== null ) {
+
+                               const indices = index.array;
+
+                               for ( let i = 0, il = indices.length; i < il; i ++ ) {
+
+                                       const a = indices[ i ];
+
+                                       _position$1.fromBufferAttribute( positionAttribute, a );
+
+                                       testPoint( _position$1, a, localThresholdSq, matrixWorld, raycaster, intersects, this );
+
+                               }
+
+                       } else {
+
+                               for ( let i = 0, l = positionAttribute.count; i < l; i ++ ) {
+
+                                       _position$1.fromBufferAttribute( positionAttribute, i );
+
+                                       testPoint( _position$1, i, localThresholdSq, matrixWorld, raycaster, intersects, this );
+
+                               }
+
+                       }
+
+               } else {
+
+                       console.error( 'THREE.Points.raycast() no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.' );
+
+               }
+
+       },
+
+       updateMorphTargets: function () {
+
+               const geometry = this.geometry;
+
+               if ( geometry.isBufferGeometry ) {
+
+                       const morphAttributes = geometry.morphAttributes;
+                       const keys = Object.keys( morphAttributes );
+
+                       if ( keys.length > 0 ) {
+
+                               const morphAttribute = morphAttributes[ keys[ 0 ] ];
+
+                               if ( morphAttribute !== undefined ) {
+
+                                       this.morphTargetInfluences = [];
+                                       this.morphTargetDictionary = {};
+
+                                       for ( let m = 0, ml = morphAttribute.length; m < ml; m ++ ) {
+
+                                               const name = morphAttribute[ m ].name || String( m );
+
+                                               this.morphTargetInfluences.push( 0 );
+                                               this.morphTargetDictionary[ name ] = m;
+
+                                       }
+
+                               }
+
+                       }
+
+               } else {
+
+                       const morphTargets = geometry.morphTargets;
+
+                       if ( morphTargets !== undefined && morphTargets.length > 0 ) {
+
+                               console.error( 'THREE.Points.updateMorphTargets() does not support THREE.Geometry. Use THREE.BufferGeometry instead.' );
+
+                       }
+
+               }
+
+       }
+
+} );
+
+function testPoint( point, index, localThresholdSq, matrixWorld, raycaster, intersects, object ) {
+
+       const rayPointDistanceSq = _ray$2.distanceSqToPoint( point );
+
+       if ( rayPointDistanceSq < localThresholdSq ) {
+
+               const intersectPoint = new Vector3();
+
+               _ray$2.closestPointToPoint( point, intersectPoint );
+               intersectPoint.applyMatrix4( matrixWorld );
+
+               const distance = raycaster.ray.origin.distanceTo( intersectPoint );
+
+               if ( distance < raycaster.near || distance > raycaster.far ) return;
+
+               intersects.push( {
+
+                       distance: distance,
+                       distanceToRay: Math.sqrt( rayPointDistanceSq ),
+                       point: intersectPoint,
+                       index: index,
+                       face: null,
+                       object: object
+
+               } );
+
+       }
+
+}
+
+function VideoTexture( video, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) {
+
+       Texture.call( this, video, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy );
+
+       this.format = format !== undefined ? format : RGBFormat;
+
+       this.minFilter = minFilter !== undefined ? minFilter : LinearFilter;
+       this.magFilter = magFilter !== undefined ? magFilter : LinearFilter;
+
+       this.generateMipmaps = false;
+
+       const scope = this;
+
+       function updateVideo() {
+
+               scope.needsUpdate = true;
+               video.requestVideoFrameCallback( updateVideo );
+
+       }
+
+       if ( 'requestVideoFrameCallback' in video ) {
+
+               video.requestVideoFrameCallback( updateVideo );
+
+       }
+
+}
+
+VideoTexture.prototype = Object.assign( Object.create( Texture.prototype ), {
+
+       constructor: VideoTexture,
+
+       clone: function () {
+
+               return new this.constructor( this.image ).copy( this );
+
+       },
+
+       isVideoTexture: true,
+
+       update: function () {
+
+               const video = this.image;
+               const hasVideoFrameCallback = 'requestVideoFrameCallback' in video;
+
+               if ( hasVideoFrameCallback === false && video.readyState >= video.HAVE_CURRENT_DATA ) {
+
+                       this.needsUpdate = true;
+
+               }
+
+       }
+
+} );
+
+function CompressedTexture( mipmaps, width, height, format, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, encoding ) {
+
+       Texture.call( this, null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, encoding );
+
+       this.image = { width: width, height: height };
+       this.mipmaps = mipmaps;
+
+       // no flipping for cube textures
+       // (also flipping doesn't work for compressed textures )
+
+       this.flipY = false;
+
+       // can't generate mipmaps for compressed textures
+       // mips must be embedded in DDS files
+
+       this.generateMipmaps = false;
+
+}
+
+CompressedTexture.prototype = Object.create( Texture.prototype );
+CompressedTexture.prototype.constructor = CompressedTexture;
+
+CompressedTexture.prototype.isCompressedTexture = true;
+
+function CanvasTexture( canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) {
+
+       Texture.call( this, canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy );
+
+       this.needsUpdate = true;
+
+}
+
+CanvasTexture.prototype = Object.create( Texture.prototype );
+CanvasTexture.prototype.constructor = CanvasTexture;
+CanvasTexture.prototype.isCanvasTexture = true;
+
+function DepthTexture( width, height, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, format ) {
+
+       format = format !== undefined ? format : DepthFormat;
+
+       if ( format !== DepthFormat && format !== DepthStencilFormat ) {
+
+               throw new Error( 'DepthTexture format must be either THREE.DepthFormat or THREE.DepthStencilFormat' );
+
+       }
+
+       if ( type === undefined && format === DepthFormat ) type = UnsignedShortType;
+       if ( type === undefined && format === DepthStencilFormat ) type = UnsignedInt248Type;
+
+       Texture.call( this, null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy );
+
+       this.image = { width: width, height: height };
+
+       this.magFilter = magFilter !== undefined ? magFilter : NearestFilter;
+       this.minFilter = minFilter !== undefined ? minFilter : NearestFilter;
+
+       this.flipY = false;
+       this.generateMipmaps = false;
+
+}
+
+DepthTexture.prototype = Object.create( Texture.prototype );
+DepthTexture.prototype.constructor = DepthTexture;
+DepthTexture.prototype.isDepthTexture = true;
+
+class CircleGeometry extends BufferGeometry {
+
+       constructor( radius = 1, segments = 8, thetaStart = 0, thetaLength = Math.PI * 2 ) {
+
+               super();
+
+               this.type = 'CircleGeometry';
+
+               this.parameters = {
+                       radius: radius,
+                       segments: segments,
+                       thetaStart: thetaStart,
+                       thetaLength: thetaLength
+               };
+
+               segments = Math.max( 3, segments );
+
+               // buffers
+
+               const indices = [];
+               const vertices = [];
+               const normals = [];
+               const uvs = [];
+
+               // helper variables
+
+               const vertex = new Vector3();
+               const uv = new Vector2();
+
+               // center point
+
+               vertices.push( 0, 0, 0 );
+               normals.push( 0, 0, 1 );
+               uvs.push( 0.5, 0.5 );
+
+               for ( let s = 0, i = 3; s <= segments; s ++, i += 3 ) {
+
+                       const segment = thetaStart + s / segments * thetaLength;
+
+                       // vertex
+
+                       vertex.x = radius * Math.cos( segment );
+                       vertex.y = radius * Math.sin( segment );
+
+                       vertices.push( vertex.x, vertex.y, vertex.z );
+
+                       // normal
+
+                       normals.push( 0, 0, 1 );
+
+                       // uvs
+
+                       uv.x = ( vertices[ i ] / radius + 1 ) / 2;
+                       uv.y = ( vertices[ i + 1 ] / radius + 1 ) / 2;
+
+                       uvs.push( uv.x, uv.y );
+
+               }
+
+               // indices
+
+               for ( let i = 1; i <= segments; i ++ ) {
+
+                       indices.push( i, i + 1, 0 );
+
+               }
+
+               // build geometry
+
+               this.setIndex( indices );
+               this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
+               this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );
+               this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );
+
+       }
+
+}
+
+new Vector3();
+new Vector3();
+new Vector3();
+new Triangle();
+
+/**
+ * Port from https://github.com/mapbox/earcut (v2.2.2)
+ */
+
+const Earcut = {
+
+       triangulate: function ( data, holeIndices, dim ) {
+
+               dim = dim || 2;
+
+               const hasHoles = holeIndices && holeIndices.length;
+               const outerLen = hasHoles ? holeIndices[ 0 ] * dim : data.length;
+               let outerNode = linkedList$1( data, 0, outerLen, dim, true );
+               const triangles = [];
+
+               if ( ! outerNode || outerNode.next === outerNode.prev ) return triangles;
+
+               let minX, minY, maxX, maxY, x, y, invSize;
+
+               if ( hasHoles ) outerNode = eliminateHoles$1( data, holeIndices, outerNode, dim );
+
+               // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox
+               if ( data.length > 80 * dim ) {
+
+                       minX = maxX = data[ 0 ];
+                       minY = maxY = data[ 1 ];
+
+                       for ( let i = dim; i < outerLen; i += dim ) {
+
+                               x = data[ i ];
+                               y = data[ i + 1 ];
+                               if ( x < minX ) minX = x;
+                               if ( y < minY ) minY = y;
+                               if ( x > maxX ) maxX = x;
+                               if ( y > maxY ) maxY = y;
+
+                       }
+
+                       // minX, minY and invSize are later used to transform coords into integers for z-order calculation
+                       invSize = Math.max( maxX - minX, maxY - minY );
+                       invSize = invSize !== 0 ? 1 / invSize : 0;
+
+               }
+
+               earcutLinked$1( outerNode, triangles, dim, minX, minY, invSize );
+
+               return triangles;
+
+       }
+
+};
+
+// create a circular doubly linked list from polygon points in the specified winding order
+function linkedList$1( data, start, end, dim, clockwise ) {
+
+       let i, last;
+
+       if ( clockwise === ( signedArea$2( data, start, end, dim ) > 0 ) ) {
+
+               for ( i = start; i < end; i += dim ) last = insertNode$2( i, data[ i ], data[ i + 1 ], last );
+
+       } else {
+
+               for ( i = end - dim; i >= start; i -= dim ) last = insertNode$2( i, data[ i ], data[ i + 1 ], last );
+
+       }
+
+       if ( last && equals$2( last, last.next ) ) {
+
+               removeNode$2( last );
+               last = last.next;
+
+       }
+
+       return last;
+
+}
+
+// eliminate colinear or duplicate points
+function filterPoints$1( start, end ) {
+
+       if ( ! start ) return start;
+       if ( ! end ) end = start;
+
+       let p = start,
+               again;
+       do {
+
+               again = false;
+
+               if ( ! p.steiner && ( equals$2( p, p.next ) || area$1( p.prev, p, p.next ) === 0 ) ) {
+
+                       removeNode$2( p );
+                       p = end = p.prev;
+                       if ( p === p.next ) break;
+                       again = true;
+
+               } else {
+
+                       p = p.next;
+
+               }
+
+       } while ( again || p !== end );
+
+       return end;
+
+}
+
+// main ear slicing loop which triangulates a polygon (given as a linked list)
+function earcutLinked$1( ear, triangles, dim, minX, minY, invSize, pass ) {
+
+       if ( ! ear ) return;
+
+       // interlink polygon nodes in z-order
+       if ( ! pass && invSize ) indexCurve$1( ear, minX, minY, invSize );
+
+       let stop = ear,
+               prev, next;
+
+       // iterate through ears, slicing them one by one
+       while ( ear.prev !== ear.next ) {
+
+               prev = ear.prev;
+               next = ear.next;
+
+               if ( invSize ? isEarHashed$1( ear, minX, minY, invSize ) : isEar$1( ear ) ) {
+
+                       // cut off the triangle
+                       triangles.push( prev.i / dim );
+                       triangles.push( ear.i / dim );
+                       triangles.push( next.i / dim );
+
+                       removeNode$2( ear );
+
+                       // skipping the next vertex leads to less sliver triangles
+                       ear = next.next;
+                       stop = next.next;
+
+                       continue;
+
+               }
+
+               ear = next;
+
+               // if we looped through the whole remaining polygon and can't find any more ears
+               if ( ear === stop ) {
+
+                       // try filtering points and slicing again
+                       if ( ! pass ) {
+
+                               earcutLinked$1( filterPoints$1( ear ), triangles, dim, minX, minY, invSize, 1 );
+
+                               // if this didn't work, try curing all small self-intersections locally
+
+                       } else if ( pass === 1 ) {
+
+                               ear = cureLocalIntersections$1( filterPoints$1( ear ), triangles, dim );
+                               earcutLinked$1( ear, triangles, dim, minX, minY, invSize, 2 );
+
+                               // as a last resort, try splitting the remaining polygon into two
+
+                       } else if ( pass === 2 ) {
+
+                               splitEarcut$1( ear, triangles, dim, minX, minY, invSize );
+
+                       }
+
+                       break;
+
+               }
+
+       }
+
+}
+
+// check whether a polygon node forms a valid ear with adjacent nodes
+function isEar$1( ear ) {
+
+       const a = ear.prev,
+               b = ear,
+               c = ear.next;
+
+       if ( area$1( a, b, c ) >= 0 ) return false; // reflex, can't be an ear
+
+       // now make sure we don't have other points inside the potential ear
+       let p = ear.next.next;
+
+       while ( p !== ear.prev ) {
+
+               if ( pointInTriangle$1( a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y ) &&
+                       area$1( p.prev, p, p.next ) >= 0 ) return false;
+               p = p.next;
+
+       }
+
+       return true;
+
+}
+
+function isEarHashed$1( ear, minX, minY, invSize ) {
+
+       const a = ear.prev,
+               b = ear,
+               c = ear.next;
+
+       if ( area$1( a, b, c ) >= 0 ) return false; // reflex, can't be an ear
+
+       // triangle bbox; min & max are calculated like this for speed
+       const minTX = a.x < b.x ? ( a.x < c.x ? a.x : c.x ) : ( b.x < c.x ? b.x : c.x ),
+               minTY = a.y < b.y ? ( a.y < c.y ? a.y : c.y ) : ( b.y < c.y ? b.y : c.y ),
+               maxTX = a.x > b.x ? ( a.x > c.x ? a.x : c.x ) : ( b.x > c.x ? b.x : c.x ),
+               maxTY = a.y > b.y ? ( a.y > c.y ? a.y : c.y ) : ( b.y > c.y ? b.y : c.y );
+
+       // z-order range for the current triangle bbox;
+       const minZ = zOrder$1( minTX, minTY, minX, minY, invSize ),
+               maxZ = zOrder$1( maxTX, maxTY, minX, minY, invSize );
+
+       let p = ear.prevZ,
+               n = ear.nextZ;
+
+       // look for points inside the triangle in both directions
+       while ( p && p.z >= minZ && n && n.z <= maxZ ) {
+
+               if ( p !== ear.prev && p !== ear.next &&
+                       pointInTriangle$1( a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y ) &&
+                       area$1( p.prev, p, p.next ) >= 0 ) return false;
+               p = p.prevZ;
+
+               if ( n !== ear.prev && n !== ear.next &&
+                       pointInTriangle$1( a.x, a.y, b.x, b.y, c.x, c.y, n.x, n.y ) &&
+                       area$1( n.prev, n, n.next ) >= 0 ) return false;
+               n = n.nextZ;
+
+       }
+
+       // look for remaining points in decreasing z-order
+       while ( p && p.z >= minZ ) {
+
+               if ( p !== ear.prev && p !== ear.next &&
+                       pointInTriangle$1( a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y ) &&
+                       area$1( p.prev, p, p.next ) >= 0 ) return false;
+               p = p.prevZ;
+
+       }
+
+       // look for remaining points in increasing z-order
+       while ( n && n.z <= maxZ ) {
+
+               if ( n !== ear.prev && n !== ear.next &&
+                       pointInTriangle$1( a.x, a.y, b.x, b.y, c.x, c.y, n.x, n.y ) &&
+                       area$1( n.prev, n, n.next ) >= 0 ) return false;
+               n = n.nextZ;
+
+       }
+
+       return true;
+
+}
+
+// go through all polygon nodes and cure small local self-intersections
+function cureLocalIntersections$1( start, triangles, dim ) {
+
+       let p = start;
+       do {
+
+               const a = p.prev,
+                       b = p.next.next;
+
+               if ( ! equals$2( a, b ) && intersects$2( a, p, p.next, b ) && locallyInside$1( a, b ) && locallyInside$1( b, a ) ) {
+
+                       triangles.push( a.i / dim );
+                       triangles.push( p.i / dim );
+                       triangles.push( b.i / dim );
+
+                       // remove two nodes involved
+                       removeNode$2( p );
+                       removeNode$2( p.next );
+
+                       p = start = b;
+
+               }
+
+               p = p.next;
+
+       } while ( p !== start );
+
+       return filterPoints$1( p );
+
+}
+
+// try splitting polygon into two and triangulate them independently
+function splitEarcut$1( start, triangles, dim, minX, minY, invSize ) {
+
+       // look for a valid diagonal that divides the polygon into two
+       let a = start;
+       do {
+
+               let b = a.next.next;
+               while ( b !== a.prev ) {
+
+                       if ( a.i !== b.i && isValidDiagonal$1( a, b ) ) {
+
+                               // split the polygon in two by the diagonal
+                               let c = splitPolygon$1( a, b );
+
+                               // filter colinear points around the cuts
+                               a = filterPoints$1( a, a.next );
+                               c = filterPoints$1( c, c.next );
+
+                               // run earcut on each half
+                               earcutLinked$1( a, triangles, dim, minX, minY, invSize );
+                               earcutLinked$1( c, triangles, dim, minX, minY, invSize );
+                               return;
+
+                       }
+
+                       b = b.next;
+
+               }
+
+               a = a.next;
+
+       } while ( a !== start );
+
+}
+
+// link every hole into the outer loop, producing a single-ring polygon without holes
+function eliminateHoles$1( data, holeIndices, outerNode, dim ) {
+
+       const queue = [];
+       let i, len, start, end, list;
+
+       for ( i = 0, len = holeIndices.length; i < len; i ++ ) {
+
+               start = holeIndices[ i ] * dim;
+               end = i < len - 1 ? holeIndices[ i + 1 ] * dim : data.length;
+               list = linkedList$1( data, start, end, dim, false );
+               if ( list === list.next ) list.steiner = true;
+               queue.push( getLeftmost$1( list ) );
+
+       }
+
+       queue.sort( compareX$1 );
+
+       // process holes from left to right
+       for ( i = 0; i < queue.length; i ++ ) {
+
+               eliminateHole$1( queue[ i ], outerNode );
+               outerNode = filterPoints$1( outerNode, outerNode.next );
+
+       }
+
+       return outerNode;
+
+}
+
+function compareX$1( a, b ) {
+
+       return a.x - b.x;
+
+}
+
+// find a bridge between vertices that connects hole with an outer ring and and link it
+function eliminateHole$1( hole, outerNode ) {
+
+       outerNode = findHoleBridge$1( hole, outerNode );
+       if ( outerNode ) {
+
+               const b = splitPolygon$1( outerNode, hole );
+
+               // filter collinear points around the cuts
+               filterPoints$1( outerNode, outerNode.next );
+               filterPoints$1( b, b.next );
+
+       }
+
+}
+
+// David Eberly's algorithm for finding a bridge between hole and outer polygon
+function findHoleBridge$1( hole, outerNode ) {
+
+       let p = outerNode;
+       const hx = hole.x;
+       const hy = hole.y;
+       let qx = - Infinity, m;
+
+       // find a segment intersected by a ray from the hole's leftmost point to the left;
+       // segment's endpoint with lesser x will be potential connection point
+       do {
+
+               if ( hy <= p.y && hy >= p.next.y && p.next.y !== p.y ) {
+
+                       const x = p.x + ( hy - p.y ) * ( p.next.x - p.x ) / ( p.next.y - p.y );
+                       if ( x <= hx && x > qx ) {
+
+                               qx = x;
+                               if ( x === hx ) {
+
+                                       if ( hy === p.y ) return p;
+                                       if ( hy === p.next.y ) return p.next;
+
+                               }
+
+                               m = p.x < p.next.x ? p : p.next;
+
+                       }
+
+               }
+
+               p = p.next;
+
+       } while ( p !== outerNode );
+
+       if ( ! m ) return null;
+
+       if ( hx === qx ) return m; // hole touches outer segment; pick leftmost endpoint
+
+       // look for points inside the triangle of hole point, segment intersection and endpoint;
+       // if there are no points found, we have a valid connection;
+       // otherwise choose the point of the minimum angle with the ray as connection point
+
+       const stop = m,
+               mx = m.x,
+               my = m.y;
+       let tanMin = Infinity, tan;
+
+       p = m;
+
+       do {
+
+               if ( hx >= p.x && p.x >= mx && hx !== p.x &&
+                               pointInTriangle$1( hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y ) ) {
+
+                       tan = Math.abs( hy - p.y ) / ( hx - p.x ); // tangential
+
+                       if ( locallyInside$1( p, hole ) && ( tan < tanMin || ( tan === tanMin && ( p.x > m.x || ( p.x === m.x && sectorContainsSector$1( m, p ) ) ) ) ) ) {
+
+                               m = p;
+                               tanMin = tan;
+
+                       }
+
+               }
+
+               p = p.next;
+
+       } while ( p !== stop );
+
+       return m;
+
+}
+
+// whether sector in vertex m contains sector in vertex p in the same coordinates
+function sectorContainsSector$1( m, p ) {
+
+       return area$1( m.prev, m, p.prev ) < 0 && area$1( p.next, m, m.next ) < 0;
+
+}
+
+// interlink polygon nodes in z-order
+function indexCurve$1( start, minX, minY, invSize ) {
+
+       let p = start;
+       do {
+
+               if ( p.z === null ) p.z = zOrder$1( p.x, p.y, minX, minY, invSize );
+               p.prevZ = p.prev;
+               p.nextZ = p.next;
+               p = p.next;
+
+       } while ( p !== start );
+
+       p.prevZ.nextZ = null;
+       p.prevZ = null;
+
+       sortLinked$1( p );
+
+}
+
+// Simon Tatham's linked list merge sort algorithm
+// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html
+function sortLinked$1( list ) {
+
+       let i, p, q, e, tail, numMerges, pSize, qSize,
+               inSize = 1;
+
+       do {
+
+               p = list;
+               list = null;
+               tail = null;
+               numMerges = 0;
+
+               while ( p ) {
+
+                       numMerges ++;
+                       q = p;
+                       pSize = 0;
+                       for ( i = 0; i < inSize; i ++ ) {
+
+                               pSize ++;
+                               q = q.nextZ;
+                               if ( ! q ) break;
+
+                       }
+
+                       qSize = inSize;
+
+                       while ( pSize > 0 || ( qSize > 0 && q ) ) {
+
+                               if ( pSize !== 0 && ( qSize === 0 || ! q || p.z <= q.z ) ) {
+
+                                       e = p;
+                                       p = p.nextZ;
+                                       pSize --;
+
+                               } else {
+
+                                       e = q;
+                                       q = q.nextZ;
+                                       qSize --;
+
+                               }
+
+                               if ( tail ) tail.nextZ = e;
+                               else list = e;
+
+                               e.prevZ = tail;
+                               tail = e;
+
+                       }
+
+                       p = q;
+
+               }
+
+               tail.nextZ = null;
+               inSize *= 2;
+
+       } while ( numMerges > 1 );
+
+       return list;
+
+}
+
+// z-order of a point given coords and inverse of the longer side of data bbox
+function zOrder$1( x, y, minX, minY, invSize ) {
+
+       // coords are transformed into non-negative 15-bit integer range
+       x = 32767 * ( x - minX ) * invSize;
+       y = 32767 * ( y - minY ) * invSize;
+
+       x = ( x | ( x << 8 ) ) & 0x00FF00FF;
+       x = ( x | ( x << 4 ) ) & 0x0F0F0F0F;
+       x = ( x | ( x << 2 ) ) & 0x33333333;
+       x = ( x | ( x << 1 ) ) & 0x55555555;
+
+       y = ( y | ( y << 8 ) ) & 0x00FF00FF;
+       y = ( y | ( y << 4 ) ) & 0x0F0F0F0F;
+       y = ( y | ( y << 2 ) ) & 0x33333333;
+       y = ( y | ( y << 1 ) ) & 0x55555555;
+
+       return x | ( y << 1 );
+
+}
+
+// find the leftmost node of a polygon ring
+function getLeftmost$1( start ) {
+
+       let p = start,
+               leftmost = start;
+       do {
+
+               if ( p.x < leftmost.x || ( p.x === leftmost.x && p.y < leftmost.y ) ) leftmost = p;
+               p = p.next;
+
+       } while ( p !== start );
+
+       return leftmost;
+
+}
+
+// check if a point lies within a convex triangle
+function pointInTriangle$1( ax, ay, bx, by, cx, cy, px, py ) {
+
+       return ( cx - px ) * ( ay - py ) - ( ax - px ) * ( cy - py ) >= 0 &&
+                       ( ax - px ) * ( by - py ) - ( bx - px ) * ( ay - py ) >= 0 &&
+                       ( bx - px ) * ( cy - py ) - ( cx - px ) * ( by - py ) >= 0;
+
+}
+
+// check if a diagonal between two polygon nodes is valid (lies in polygon interior)
+function isValidDiagonal$1( a, b ) {
+
+       return a.next.i !== b.i && a.prev.i !== b.i && ! intersectsPolygon$1( a, b ) && // dones't intersect other edges
+               ( locallyInside$1( a, b ) && locallyInside$1( b, a ) && middleInside$1( a, b ) && // locally visible
+               ( area$1( a.prev, a, b.prev ) || area$1( a, b.prev, b ) ) || // does not create opposite-facing sectors
+               equals$2( a, b ) && area$1( a.prev, a, a.next ) > 0 && area$1( b.prev, b, b.next ) > 0 ); // special zero-length case
+
+}
+
+// signed area of a triangle
+function area$1( p, q, r ) {
+
+       return ( q.y - p.y ) * ( r.x - q.x ) - ( q.x - p.x ) * ( r.y - q.y );
+
+}
+
+// check if two points are equal
+function equals$2( p1, p2 ) {
+
+       return p1.x === p2.x && p1.y === p2.y;
+
+}
+
+// check if two segments intersect
+function intersects$2( p1, q1, p2, q2 ) {
+
+       const o1 = sign$2( area$1( p1, q1, p2 ) );
+       const o2 = sign$2( area$1( p1, q1, q2 ) );
+       const o3 = sign$2( area$1( p2, q2, p1 ) );
+       const o4 = sign$2( area$1( p2, q2, q1 ) );
+
+       if ( o1 !== o2 && o3 !== o4 ) return true; // general case
+
+       if ( o1 === 0 && onSegment$1( p1, p2, q1 ) ) return true; // p1, q1 and p2 are collinear and p2 lies on p1q1
+       if ( o2 === 0 && onSegment$1( p1, q2, q1 ) ) return true; // p1, q1 and q2 are collinear and q2 lies on p1q1
+       if ( o3 === 0 && onSegment$1( p2, p1, q2 ) ) return true; // p2, q2 and p1 are collinear and p1 lies on p2q2
+       if ( o4 === 0 && onSegment$1( p2, q1, q2 ) ) return true; // p2, q2 and q1 are collinear and q1 lies on p2q2
+
+       return false;
+
+}
+
+// for collinear points p, q, r, check if point q lies on segment pr
+function onSegment$1( p, q, r ) {
+
+       return q.x <= Math.max( p.x, r.x ) && q.x >= Math.min( p.x, r.x ) && q.y <= Math.max( p.y, r.y ) && q.y >= Math.min( p.y, r.y );
+
+}
+
+function sign$2( num ) {
+
+       return num > 0 ? 1 : num < 0 ? - 1 : 0;
+
+}
+
+// check if a polygon diagonal intersects any polygon segments
+function intersectsPolygon$1( a, b ) {
+
+       let p = a;
+       do {
+
+               if ( p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i &&
+                               intersects$2( p, p.next, a, b ) ) return true;
+               p = p.next;
+
+       } while ( p !== a );
+
+       return false;
+
+}
+
+// check if a polygon diagonal is locally inside the polygon
+function locallyInside$1( a, b ) {
+
+       return area$1( a.prev, a, a.next ) < 0 ?
+               area$1( a, b, a.next ) >= 0 && area$1( a, a.prev, b ) >= 0 :
+               area$1( a, b, a.prev ) < 0 || area$1( a, a.next, b ) < 0;
+
+}
+
+// check if the middle point of a polygon diagonal is inside the polygon
+function middleInside$1( a, b ) {
+
+       let p = a,
+               inside = false;
+       const px = ( a.x + b.x ) / 2,
+               py = ( a.y + b.y ) / 2;
+       do {
+
+               if ( ( ( p.y > py ) !== ( p.next.y > py ) ) && p.next.y !== p.y &&
+                               ( px < ( p.next.x - p.x ) * ( py - p.y ) / ( p.next.y - p.y ) + p.x ) )
+                       inside = ! inside;
+               p = p.next;
+
+       } while ( p !== a );
+
+       return inside;
+
+}
+
+// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two;
+// if one belongs to the outer ring and another to a hole, it merges it into a single ring
+function splitPolygon$1( a, b ) {
+
+       const a2 = new Node$1( a.i, a.x, a.y ),
+               b2 = new Node$1( b.i, b.x, b.y ),
+               an = a.next,
+               bp = b.prev;
+
+       a.next = b;
+       b.prev = a;
+
+       a2.next = an;
+       an.prev = a2;
+
+       b2.next = a2;
+       a2.prev = b2;
+
+       bp.next = b2;
+       b2.prev = bp;
+
+       return b2;
+
+}
+
+// create a node and optionally link it with previous one (in a circular doubly linked list)
+function insertNode$2( i, x, y, last ) {
+
+       const p = new Node$1( i, x, y );
+
+       if ( ! last ) {
+
+               p.prev = p;
+               p.next = p;
+
+       } else {
+
+               p.next = last.next;
+               p.prev = last;
+               last.next.prev = p;
+               last.next = p;
+
+       }
+
+       return p;
+
+}
+
+function removeNode$2( p ) {
+
+       p.next.prev = p.prev;
+       p.prev.next = p.next;
+
+       if ( p.prevZ ) p.prevZ.nextZ = p.nextZ;
+       if ( p.nextZ ) p.nextZ.prevZ = p.prevZ;
+
+}
+
+function Node$1( i, x, y ) {
+
+       // vertex index in coordinates array
+       this.i = i;
+
+       // vertex coordinates
+       this.x = x;
+       this.y = y;
+
+       // previous and next vertex nodes in a polygon ring
+       this.prev = null;
+       this.next = null;
+
+       // z-order curve value
+       this.z = null;
+
+       // previous and next nodes in z-order
+       this.prevZ = null;
+       this.nextZ = null;
+
+       // indicates whether this is a steiner point
+       this.steiner = false;
+
+}
+
+function signedArea$2( data, start, end, dim ) {
+
+       let sum = 0;
+       for ( let i = start, j = end - dim; i < end; i += dim ) {
+
+               sum += ( data[ j ] - data[ i ] ) * ( data[ i + 1 ] + data[ j + 1 ] );
+               j = i;
+
+       }
+
+       return sum;
+
+}
+
+const ShapeUtils = {
+
+       // calculate area of the contour polygon
+
+       area: function ( contour ) {
+
+               const n = contour.length;
+               let a = 0.0;
+
+               for ( let p = n - 1, q = 0; q < n; p = q ++ ) {
+
+                       a += contour[ p ].x * contour[ q ].y - contour[ q ].x * contour[ p ].y;
+
+               }
+
+               return a * 0.5;
+
+       },
+
+       isClockWise: function ( pts ) {
+
+               return ShapeUtils.area( pts ) < 0;
+
+       },
+
+       triangulateShape: function ( contour, holes ) {
+
+               const vertices = []; // flat array of vertices like [ x0,y0, x1,y1, x2,y2, ... ]
+               const holeIndices = []; // array of hole indices
+               const faces = []; // final array of vertex indices like [ [ a,b,d ], [ b,c,d ] ]
+
+               removeDupEndPts( contour );
+               addContour( vertices, contour );
+
+               //
+
+               let holeIndex = contour.length;
+
+               holes.forEach( removeDupEndPts );
+
+               for ( let i = 0; i < holes.length; i ++ ) {
+
+                       holeIndices.push( holeIndex );
+                       holeIndex += holes[ i ].length;
+                       addContour( vertices, holes[ i ] );
+
+               }
+
+               //
+
+               const triangles = Earcut.triangulate( vertices, holeIndices );
+
+               //
+
+               for ( let i = 0; i < triangles.length; i += 3 ) {
+
+                       faces.push( triangles.slice( i, i + 3 ) );
+
+               }
+
+               return faces;
+
+       }
+
+};
+
+function removeDupEndPts( points ) {
+
+       const l = points.length;
+
+       if ( l > 2 && points[ l - 1 ].equals( points[ 0 ] ) ) {
+
+               points.pop();
+
+       }
+
+}
+
+function addContour( vertices, contour ) {
+
+       for ( let i = 0; i < contour.length; i ++ ) {
+
+               vertices.push( contour[ i ].x );
+               vertices.push( contour[ i ].y );
+
+       }
+
+}
+
+/**
+ * Creates extruded geometry from a path shape.
+ *
+ * parameters = {
+ *
+ *  curveSegments: <int>, // number of points on the curves
+ *  steps: <int>, // number of points for z-side extrusions / used for subdividing segments of extrude spline too
+ *  depth: <float>, // Depth to extrude the shape
+ *
+ *  bevelEnabled: <bool>, // turn on bevel
+ *  bevelThickness: <float>, // how deep into the original shape bevel goes
+ *  bevelSize: <float>, // how far from shape outline (including bevelOffset) is bevel
+ *  bevelOffset: <float>, // how far from shape outline does bevel start
+ *  bevelSegments: <int>, // number of bevel layers
+ *
+ *  extrudePath: <THREE.Curve> // curve to extrude shape along
+ *
+ *  UVGenerator: <Object> // object that provides UV generator functions
+ *
+ * }
+ */
+
+class ExtrudeGeometry extends BufferGeometry {
+
+       constructor( shapes, options ) {
+
+               super();
+
+               this.type = 'ExtrudeGeometry';
+
+               this.parameters = {
+                       shapes: shapes,
+                       options: options
+               };
+
+               shapes = Array.isArray( shapes ) ? shapes : [ shapes ];
+
+               const scope = this;
+
+               const verticesArray = [];
+               const uvArray = [];
+
+               for ( let i = 0, l = shapes.length; i < l; i ++ ) {
+
+                       const shape = shapes[ i ];
+                       addShape( shape );
+
+               }
+
+               // build geometry
+
+               this.setAttribute( 'position', new Float32BufferAttribute( verticesArray, 3 ) );
+               this.setAttribute( 'uv', new Float32BufferAttribute( uvArray, 2 ) );
+
+               this.computeVertexNormals();
+
+               // functions
+
+               function addShape( shape ) {
+
+                       const placeholder = [];
+
+                       // options
+
+                       const curveSegments = options.curveSegments !== undefined ? options.curveSegments : 12;
+                       const steps = options.steps !== undefined ? options.steps : 1;
+                       let depth = options.depth !== undefined ? options.depth : 100;
+
+                       let bevelEnabled = options.bevelEnabled !== undefined ? options.bevelEnabled : true;
+                       let bevelThickness = options.bevelThickness !== undefined ? options.bevelThickness : 6;
+                       let bevelSize = options.bevelSize !== undefined ? options.bevelSize : bevelThickness - 2;
+                       let bevelOffset = options.bevelOffset !== undefined ? options.bevelOffset : 0;
+                       let bevelSegments = options.bevelSegments !== undefined ? options.bevelSegments : 3;
+
+                       const extrudePath = options.extrudePath;
+
+                       const uvgen = options.UVGenerator !== undefined ? options.UVGenerator : WorldUVGenerator;
+
+                       // deprecated options
+
+                       if ( options.amount !== undefined ) {
+
+                               console.warn( 'THREE.ExtrudeBufferGeometry: amount has been renamed to depth.' );
+                               depth = options.amount;
+
+                       }
+
+                       //
+
+                       let extrudePts, extrudeByPath = false;
+                       let splineTube, binormal, normal, position2;
+
+                       if ( extrudePath ) {
+
+                               extrudePts = extrudePath.getSpacedPoints( steps );
+
+                               extrudeByPath = true;
+                               bevelEnabled = false; // bevels not supported for path extrusion
+
+                               // SETUP TNB variables
+
+                               // TODO1 - have a .isClosed in spline?
+
+                               splineTube = extrudePath.computeFrenetFrames( steps, false );
+
+                               // console.log(splineTube, 'splineTube', splineTube.normals.length, 'steps', steps, 'extrudePts', extrudePts.length);
+
+                               binormal = new Vector3();
+                               normal = new Vector3();
+                               position2 = new Vector3();
+
+                       }
+
+                       // Safeguards if bevels are not enabled
+
+                       if ( ! bevelEnabled ) {
+
+                               bevelSegments = 0;
+                               bevelThickness = 0;
+                               bevelSize = 0;
+                               bevelOffset = 0;
+
+                       }
+
+                       // Variables initialization
+
+                       const shapePoints = shape.extractPoints( curveSegments );
+
+                       let vertices = shapePoints.shape;
+                       const holes = shapePoints.holes;
+
+                       const reverse = ! ShapeUtils.isClockWise( vertices );
+
+                       if ( reverse ) {
+
+                               vertices = vertices.reverse();
+
+                               // Maybe we should also check if holes are in the opposite direction, just to be safe ...
+
+                               for ( let h = 0, hl = holes.length; h < hl; h ++ ) {
+
+                                       const ahole = holes[ h ];
+
+                                       if ( ShapeUtils.isClockWise( ahole ) ) {
+
+                                               holes[ h ] = ahole.reverse();
+
+                                       }
+
+                               }
+
+                       }
+
+
+                       const faces = ShapeUtils.triangulateShape( vertices, holes );
+
+                       /* Vertices */
+
+                       const contour = vertices; // vertices has all points but contour has only points of circumference
+
+                       for ( let h = 0, hl = holes.length; h < hl; h ++ ) {
+
+                               const ahole = holes[ h ];
+
+                               vertices = vertices.concat( ahole );
+
+                       }
+
+
+                       function scalePt2( pt, vec, size ) {
+
+                               if ( ! vec ) console.error( 'THREE.ExtrudeGeometry: vec does not exist' );
+
+                               return vec.clone().multiplyScalar( size ).add( pt );
+
+                       }
+
+                       const vlen = vertices.length, flen = faces.length;
+
+
+                       // Find directions for point movement
+
+
+                       function getBevelVec( inPt, inPrev, inNext ) {
+
+                               // computes for inPt the corresponding point inPt' on a new contour
+                               //   shifted by 1 unit (length of normalized vector) to the left
+                               // if we walk along contour clockwise, this new contour is outside the old one
+                               //
+                               // inPt' is the intersection of the two lines parallel to the two
+                               //  adjacent edges of inPt at a distance of 1 unit on the left side.
+
+                               let v_trans_x, v_trans_y, shrink_by; // resulting translation vector for inPt
+
+                               // good reading for geometry algorithms (here: line-line intersection)
+                               // http://geomalgorithms.com/a05-_intersect-1.html
+
+                               const v_prev_x = inPt.x - inPrev.x,
+                                       v_prev_y = inPt.y - inPrev.y;
+                               const v_next_x = inNext.x - inPt.x,
+                                       v_next_y = inNext.y - inPt.y;
+
+                               const v_prev_lensq = ( v_prev_x * v_prev_x + v_prev_y * v_prev_y );
+
+                               // check for collinear edges
+                               const collinear0 = ( v_prev_x * v_next_y - v_prev_y * v_next_x );
+
+                               if ( Math.abs( collinear0 ) > Number.EPSILON ) {
+
+                                       // not collinear
+
+                                       // length of vectors for normalizing
+
+                                       const v_prev_len = Math.sqrt( v_prev_lensq );
+                                       const v_next_len = Math.sqrt( v_next_x * v_next_x + v_next_y * v_next_y );
+
+                                       // shift adjacent points by unit vectors to the left
+
+                                       const ptPrevShift_x = ( inPrev.x - v_prev_y / v_prev_len );
+                                       const ptPrevShift_y = ( inPrev.y + v_prev_x / v_prev_len );
+
+                                       const ptNextShift_x = ( inNext.x - v_next_y / v_next_len );
+                                       const ptNextShift_y = ( inNext.y + v_next_x / v_next_len );
+
+                                       // scaling factor for v_prev to intersection point
+
+                                       const sf = ( ( ptNextShift_x - ptPrevShift_x ) * v_next_y -
+                                                       ( ptNextShift_y - ptPrevShift_y ) * v_next_x ) /
+                                               ( v_prev_x * v_next_y - v_prev_y * v_next_x );
+
+                                       // vector from inPt to intersection point
+
+                                       v_trans_x = ( ptPrevShift_x + v_prev_x * sf - inPt.x );
+                                       v_trans_y = ( ptPrevShift_y + v_prev_y * sf - inPt.y );
+
+                                       // Don't normalize!, otherwise sharp corners become ugly
+                                       //  but prevent crazy spikes
+                                       const v_trans_lensq = ( v_trans_x * v_trans_x + v_trans_y * v_trans_y );
+                                       if ( v_trans_lensq <= 2 ) {
+
+                                               return new Vector2( v_trans_x, v_trans_y );
+
+                                       } else {
+
+                                               shrink_by = Math.sqrt( v_trans_lensq / 2 );
+
+                                       }
+
+                               } else {
+
+                                       // handle special case of collinear edges
+
+                                       let direction_eq = false; // assumes: opposite
+
+                                       if ( v_prev_x > Number.EPSILON ) {
+
+                                               if ( v_next_x > Number.EPSILON ) {
+
+                                                       direction_eq = true;
+
+                                               }
+
+                                       } else {
+
+                                               if ( v_prev_x < - Number.EPSILON ) {
+
+                                                       if ( v_next_x < - Number.EPSILON ) {
+
+                                                               direction_eq = true;
+
+                                                       }
+
+                                               } else {
+
+                                                       if ( Math.sign( v_prev_y ) === Math.sign( v_next_y ) ) {
+
+                                                               direction_eq = true;
+
+                                                       }
+
+                                               }
+
+                                       }
+
+                                       if ( direction_eq ) {
+
+                                               // console.log("Warning: lines are a straight sequence");
+                                               v_trans_x = - v_prev_y;
+                                               v_trans_y = v_prev_x;
+                                               shrink_by = Math.sqrt( v_prev_lensq );
+
+                                       } else {
+
+                                               // console.log("Warning: lines are a straight spike");
+                                               v_trans_x = v_prev_x;
+                                               v_trans_y = v_prev_y;
+                                               shrink_by = Math.sqrt( v_prev_lensq / 2 );
+
+                                       }
+
+                               }
+
+                               return new Vector2( v_trans_x / shrink_by, v_trans_y / shrink_by );
+
+                       }
+
+
+                       const contourMovements = [];
+
+                       for ( let i = 0, il = contour.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) {
+
+                               if ( j === il ) j = 0;
+                               if ( k === il ) k = 0;
+
+                               //  (j)---(i)---(k)
+                               // console.log('i,j,k', i, j , k)
+
+                               contourMovements[ i ] = getBevelVec( contour[ i ], contour[ j ], contour[ k ] );
+
+                       }
+
+                       const holesMovements = [];
+                       let oneHoleMovements, verticesMovements = contourMovements.concat();
+
+                       for ( let h = 0, hl = holes.length; h < hl; h ++ ) {
+
+                               const ahole = holes[ h ];
+
+                               oneHoleMovements = [];
+
+                               for ( let i = 0, il = ahole.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) {
+
+                                       if ( j === il ) j = 0;
+                                       if ( k === il ) k = 0;
+
+                                       //  (j)---(i)---(k)
+                                       oneHoleMovements[ i ] = getBevelVec( ahole[ i ], ahole[ j ], ahole[ k ] );
+
+                               }
+
+                               holesMovements.push( oneHoleMovements );
+                               verticesMovements = verticesMovements.concat( oneHoleMovements );
+
+                       }
+
+
+                       // Loop bevelSegments, 1 for the front, 1 for the back
+
+                       for ( let b = 0; b < bevelSegments; b ++ ) {
+
+                               //for ( b = bevelSegments; b > 0; b -- ) {
+
+                               const t = b / bevelSegments;
+                               const z = bevelThickness * Math.cos( t * Math.PI / 2 );
+                               const bs = bevelSize * Math.sin( t * Math.PI / 2 ) + bevelOffset;
+
+                               // contract shape
+
+                               for ( let i = 0, il = contour.length; i < il; i ++ ) {
+
+                                       const vert = scalePt2( contour[ i ], contourMovements[ i ], bs );
+
+                                       v( vert.x, vert.y, - z );
+
+                               }
+
+                               // expand holes
+
+                               for ( let h = 0, hl = holes.length; h < hl; h ++ ) {
+
+                                       const ahole = holes[ h ];
+                                       oneHoleMovements = holesMovements[ h ];
+
+                                       for ( let i = 0, il = ahole.length; i < il; i ++ ) {
+
+                                               const vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs );
+
+                                               v( vert.x, vert.y, - z );
+
+                                       }
+
+                               }
+
+                       }
+
+                       const bs = bevelSize + bevelOffset;
+
+                       // Back facing vertices
+
+                       for ( let i = 0; i < vlen; i ++ ) {
+
+                               const vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ];
+
+                               if ( ! extrudeByPath ) {
+
+                                       v( vert.x, vert.y, 0 );
+
+                               } else {
+
+                                       // v( vert.x, vert.y + extrudePts[ 0 ].y, extrudePts[ 0 ].x );
+
+                                       normal.copy( splineTube.normals[ 0 ] ).multiplyScalar( vert.x );
+                                       binormal.copy( splineTube.binormals[ 0 ] ).multiplyScalar( vert.y );
+
+                                       position2.copy( extrudePts[ 0 ] ).add( normal ).add( binormal );
+
+                                       v( position2.x, position2.y, position2.z );
+
+                               }
+
+                       }
+
+                       // Add stepped vertices...
+                       // Including front facing vertices
+
+                       for ( let s = 1; s <= steps; s ++ ) {
+
+                               for ( let i = 0; i < vlen; i ++ ) {
+
+                                       const vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ];
+
+                                       if ( ! extrudeByPath ) {
+
+                                               v( vert.x, vert.y, depth / steps * s );
+
+                                       } else {
+
+                                               // v( vert.x, vert.y + extrudePts[ s - 1 ].y, extrudePts[ s - 1 ].x );
+
+                                               normal.copy( splineTube.normals[ s ] ).multiplyScalar( vert.x );
+                                               binormal.copy( splineTube.binormals[ s ] ).multiplyScalar( vert.y );
+
+                                               position2.copy( extrudePts[ s ] ).add( normal ).add( binormal );
+
+                                               v( position2.x, position2.y, position2.z );
+
+                                       }
+
+                               }
+
+                       }
+
+
+                       // Add bevel segments planes
+
+                       //for ( b = 1; b <= bevelSegments; b ++ ) {
+                       for ( let b = bevelSegments - 1; b >= 0; b -- ) {
+
+                               const t = b / bevelSegments;
+                               const z = bevelThickness * Math.cos( t * Math.PI / 2 );
+                               const bs = bevelSize * Math.sin( t * Math.PI / 2 ) + bevelOffset;
+
+                               // contract shape
+
+                               for ( let i = 0, il = contour.length; i < il; i ++ ) {
+
+                                       const vert = scalePt2( contour[ i ], contourMovements[ i ], bs );
+                                       v( vert.x, vert.y, depth + z );
+
+                               }
+
+                               // expand holes
+
+                               for ( let h = 0, hl = holes.length; h < hl; h ++ ) {
+
+                                       const ahole = holes[ h ];
+                                       oneHoleMovements = holesMovements[ h ];
+
+                                       for ( let i = 0, il = ahole.length; i < il; i ++ ) {
+
+                                               const vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs );
+
+                                               if ( ! extrudeByPath ) {
+
+                                                       v( vert.x, vert.y, depth + z );
+
+                                               } else {
+
+                                                       v( vert.x, vert.y + extrudePts[ steps - 1 ].y, extrudePts[ steps - 1 ].x + z );
+
+                                               }
+
+                                       }
+
+                               }
+
+                       }
+
+                       /* Faces */
+
+                       // Top and bottom faces
+
+                       buildLidFaces();
+
+                       // Sides faces
+
+                       buildSideFaces();
+
+
+                       /////  Internal functions
+
+                       function buildLidFaces() {
+
+                               const start = verticesArray.length / 3;
+
+                               if ( bevelEnabled ) {
+
+                                       let layer = 0; // steps + 1
+                                       let offset = vlen * layer;
+
+                                       // Bottom faces
+
+                                       for ( let i = 0; i < flen; i ++ ) {
+
+                                               const face = faces[ i ];
+                                               f3( face[ 2 ] + offset, face[ 1 ] + offset, face[ 0 ] + offset );
+
+                                       }
+
+                                       layer = steps + bevelSegments * 2;
+                                       offset = vlen * layer;
+
+                                       // Top faces
+
+                                       for ( let i = 0; i < flen; i ++ ) {
+
+                                               const face = faces[ i ];
+                                               f3( face[ 0 ] + offset, face[ 1 ] + offset, face[ 2 ] + offset );
+
+                                       }
+
+                               } else {
+
+                                       // Bottom faces
+
+                                       for ( let i = 0; i < flen; i ++ ) {
+
+                                               const face = faces[ i ];
+                                               f3( face[ 2 ], face[ 1 ], face[ 0 ] );
+
+                                       }
+
+                                       // Top faces
+
+                                       for ( let i = 0; i < flen; i ++ ) {
+
+                                               const face = faces[ i ];
+                                               f3( face[ 0 ] + vlen * steps, face[ 1 ] + vlen * steps, face[ 2 ] + vlen * steps );
+
+                                       }
+
+                               }
+
+                               scope.addGroup( start, verticesArray.length / 3 - start, 0 );
+
+                       }
+
+                       // Create faces for the z-sides of the shape
+
+                       function buildSideFaces() {
+
+                               const start = verticesArray.length / 3;
+                               let layeroffset = 0;
+                               sidewalls( contour, layeroffset );
+                               layeroffset += contour.length;
+
+                               for ( let h = 0, hl = holes.length; h < hl; h ++ ) {
+
+                                       const ahole = holes[ h ];
+                                       sidewalls( ahole, layeroffset );
+
+                                       //, true
+                                       layeroffset += ahole.length;
+
+                               }
+
+
+                               scope.addGroup( start, verticesArray.length / 3 - start, 1 );
+
+
+                       }
+
+                       function sidewalls( contour, layeroffset ) {
+
+                               let i = contour.length;
+
+                               while ( -- i >= 0 ) {
+
+                                       const j = i;
+                                       let k = i - 1;
+                                       if ( k < 0 ) k = contour.length - 1;
+
+                                       //console.log('b', i,j, i-1, k,vertices.length);
+
+                                       for ( let s = 0, sl = ( steps + bevelSegments * 2 ); s < sl; s ++ ) {
+
+                                               const slen1 = vlen * s;
+                                               const slen2 = vlen * ( s + 1 );
+
+                                               const a = layeroffset + j + slen1,
+                                                       b = layeroffset + k + slen1,
+                                                       c = layeroffset + k + slen2,
+                                                       d = layeroffset + j + slen2;
+
+                                               f4( a, b, c, d );
+
+                                       }
+
+                               }
+
+                       }
+
+                       function v( x, y, z ) {
+
+                               placeholder.push( x );
+                               placeholder.push( y );
+                               placeholder.push( z );
+
+                       }
+
+
+                       function f3( a, b, c ) {
+
+                               addVertex( a );
+                               addVertex( b );
+                               addVertex( c );
+
+                               const nextIndex = verticesArray.length / 3;
+                               const uvs = uvgen.generateTopUV( scope, verticesArray, nextIndex - 3, nextIndex - 2, nextIndex - 1 );
+
+                               addUV( uvs[ 0 ] );
+                               addUV( uvs[ 1 ] );
+                               addUV( uvs[ 2 ] );
+
+                       }
+
+                       function f4( a, b, c, d ) {
+
+                               addVertex( a );
+                               addVertex( b );
+                               addVertex( d );
+
+                               addVertex( b );
+                               addVertex( c );
+                               addVertex( d );
+
+
+                               const nextIndex = verticesArray.length / 3;
+                               const uvs = uvgen.generateSideWallUV( scope, verticesArray, nextIndex - 6, nextIndex - 3, nextIndex - 2, nextIndex - 1 );
+
+                               addUV( uvs[ 0 ] );
+                               addUV( uvs[ 1 ] );
+                               addUV( uvs[ 3 ] );
+
+                               addUV( uvs[ 1 ] );
+                               addUV( uvs[ 2 ] );
+                               addUV( uvs[ 3 ] );
+
+                       }
+
+                       function addVertex( index ) {
+
+                               verticesArray.push( placeholder[ index * 3 + 0 ] );
+                               verticesArray.push( placeholder[ index * 3 + 1 ] );
+                               verticesArray.push( placeholder[ index * 3 + 2 ] );
+
+                       }
+
+
+                       function addUV( vector2 ) {
+
+                               uvArray.push( vector2.x );
+                               uvArray.push( vector2.y );
+
+                       }
+
+               }
+
+       }
+
+       toJSON() {
+
+               const data = BufferGeometry.prototype.toJSON.call( this );
+
+               const shapes = this.parameters.shapes;
+               const options = this.parameters.options;
+
+               return toJSON( shapes, options, data );
+
+       }
+
+}
+
+const WorldUVGenerator = {
+
+       generateTopUV: function ( geometry, vertices, indexA, indexB, indexC ) {
+
+               const a_x = vertices[ indexA * 3 ];
+               const a_y = vertices[ indexA * 3 + 1 ];
+               const b_x = vertices[ indexB * 3 ];
+               const b_y = vertices[ indexB * 3 + 1 ];
+               const c_x = vertices[ indexC * 3 ];
+               const c_y = vertices[ indexC * 3 + 1 ];
+
+               return [
+                       new Vector2( a_x, a_y ),
+                       new Vector2( b_x, b_y ),
+                       new Vector2( c_x, c_y )
+               ];
+
+       },
+
+       generateSideWallUV: function ( geometry, vertices, indexA, indexB, indexC, indexD ) {
+
+               const a_x = vertices[ indexA * 3 ];
+               const a_y = vertices[ indexA * 3 + 1 ];
+               const a_z = vertices[ indexA * 3 + 2 ];
+               const b_x = vertices[ indexB * 3 ];
+               const b_y = vertices[ indexB * 3 + 1 ];
+               const b_z = vertices[ indexB * 3 + 2 ];
+               const c_x = vertices[ indexC * 3 ];
+               const c_y = vertices[ indexC * 3 + 1 ];
+               const c_z = vertices[ indexC * 3 + 2 ];
+               const d_x = vertices[ indexD * 3 ];
+               const d_y = vertices[ indexD * 3 + 1 ];
+               const d_z = vertices[ indexD * 3 + 2 ];
+
+               if ( Math.abs( a_y - b_y ) < 0.01 ) {
+
+                       return [
+                               new Vector2( a_x, 1 - a_z ),
+                               new Vector2( b_x, 1 - b_z ),
+                               new Vector2( c_x, 1 - c_z ),
+                               new Vector2( d_x, 1 - d_z )
+                       ];
+
+               } else {
+
+                       return [
+                               new Vector2( a_y, 1 - a_z ),
+                               new Vector2( b_y, 1 - b_z ),
+                               new Vector2( c_y, 1 - c_z ),
+                               new Vector2( d_y, 1 - d_z )
+                       ];
+
+               }
+
+       }
+
+};
+
+function toJSON( shapes, options, data ) {
+
+       data.shapes = [];
+
+       if ( Array.isArray( shapes ) ) {
+
+               for ( let i = 0, l = shapes.length; i < l; i ++ ) {
+
+                       const shape = shapes[ i ];
+
+                       data.shapes.push( shape.uuid );
+
+               }
+
+       } else {
+
+               data.shapes.push( shapes.uuid );
+
+       }
+
+       if ( options.extrudePath !== undefined ) data.options.extrudePath = options.extrudePath.toJSON();
+
+       return data;
+
+}
+
+/**
+ * Parametric Surfaces Geometry
+ * based on the brilliant article by @prideout https://prideout.net/blog/old/blog/index.html@p=44.html
+ */
+
+function ParametricGeometry( func, slices, stacks ) {
+
+       BufferGeometry.call( this );
+
+       this.type = 'ParametricGeometry';
+
+       this.parameters = {
+               func: func,
+               slices: slices,
+               stacks: stacks
+       };
+
+       // buffers
+
+       const indices = [];
+       const vertices = [];
+       const normals = [];
+       const uvs = [];
+
+       const EPS = 0.00001;
+
+       const normal = new Vector3();
+
+       const p0 = new Vector3(), p1 = new Vector3();
+       const pu = new Vector3(), pv = new Vector3();
+
+       if ( func.length < 3 ) {
+
+               console.error( 'THREE.ParametricGeometry: Function must now modify a Vector3 as third parameter.' );
+
+       }
+
+       // generate vertices, normals and uvs
+
+       const sliceCount = slices + 1;
+
+       for ( let i = 0; i <= stacks; i ++ ) {
+
+               const v = i / stacks;
+
+               for ( let j = 0; j <= slices; j ++ ) {
+
+                       const u = j / slices;
+
+                       // vertex
+
+                       func( u, v, p0 );
+                       vertices.push( p0.x, p0.y, p0.z );
+
+                       // normal
+
+                       // approximate tangent vectors via finite differences
+
+                       if ( u - EPS >= 0 ) {
+
+                               func( u - EPS, v, p1 );
+                               pu.subVectors( p0, p1 );
+
+                       } else {
+
+                               func( u + EPS, v, p1 );
+                               pu.subVectors( p1, p0 );
+
+                       }
+
+                       if ( v - EPS >= 0 ) {
+
+                               func( u, v - EPS, p1 );
+                               pv.subVectors( p0, p1 );
+
+                       } else {
+
+                               func( u, v + EPS, p1 );
+                               pv.subVectors( p1, p0 );
+
+                       }
+
+                       // cross product of tangent vectors returns surface normal
+
+                       normal.crossVectors( pu, pv ).normalize();
+                       normals.push( normal.x, normal.y, normal.z );
+
+                       // uv
+
+                       uvs.push( u, v );
+
+               }
+
+       }
+
+       // generate indices
+
+       for ( let i = 0; i < stacks; i ++ ) {
+
+               for ( let j = 0; j < slices; j ++ ) {
+
+                       const a = i * sliceCount + j;
+                       const b = i * sliceCount + j + 1;
+                       const c = ( i + 1 ) * sliceCount + j + 1;
+                       const d = ( i + 1 ) * sliceCount + j;
+
+                       // faces one and two
+
+                       indices.push( a, b, d );
+                       indices.push( b, c, d );
+
+               }
+
+       }
+
+       // build geometry
+
+       this.setIndex( indices );
+       this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
+       this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );
+       this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );
+
+}
+
+ParametricGeometry.prototype = Object.create( BufferGeometry.prototype );
+ParametricGeometry.prototype.constructor = ParametricGeometry;
+
+class ShapeGeometry extends BufferGeometry {
+
+       constructor( shapes, curveSegments = 12 ) {
+
+               super();
+               this.type = 'ShapeGeometry';
+
+               this.parameters = {
+                       shapes: shapes,
+                       curveSegments: curveSegments
+               };
+
+               // buffers
+
+               const indices = [];
+               const vertices = [];
+               const normals = [];
+               const uvs = [];
+
+               // helper variables
+
+               let groupStart = 0;
+               let groupCount = 0;
+
+               // allow single and array values for "shapes" parameter
+
+               if ( Array.isArray( shapes ) === false ) {
+
+                       addShape( shapes );
+
+               } else {
+
+                       for ( let i = 0; i < shapes.length; i ++ ) {
+
+                               addShape( shapes[ i ] );
+
+                               this.addGroup( groupStart, groupCount, i ); // enables MultiMaterial support
+
+                               groupStart += groupCount;
+                               groupCount = 0;
+
+                       }
+
+               }
+
+               // build geometry
+
+               this.setIndex( indices );
+               this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
+               this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );
+               this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );
+
+
+               // helper functions
+
+               function addShape( shape ) {
+
+                       const indexOffset = vertices.length / 3;
+                       const points = shape.extractPoints( curveSegments );
+
+                       let shapeVertices = points.shape;
+                       const shapeHoles = points.holes;
+
+                       // check direction of vertices
+
+                       if ( ShapeUtils.isClockWise( shapeVertices ) === false ) {
+
+                               shapeVertices = shapeVertices.reverse();
+
+                       }
+
+                       for ( let i = 0, l = shapeHoles.length; i < l; i ++ ) {
+
+                               const shapeHole = shapeHoles[ i ];
+
+                               if ( ShapeUtils.isClockWise( shapeHole ) === true ) {
+
+                                       shapeHoles[ i ] = shapeHole.reverse();
+
+                               }
+
+                       }
+
+                       const faces = ShapeUtils.triangulateShape( shapeVertices, shapeHoles );
+
+                       // join vertices of inner and outer paths to a single array
+
+                       for ( let i = 0, l = shapeHoles.length; i < l; i ++ ) {
+
+                               const shapeHole = shapeHoles[ i ];
+                               shapeVertices = shapeVertices.concat( shapeHole );
+
+                       }
+
+                       // vertices, normals, uvs
+
+                       for ( let i = 0, l = shapeVertices.length; i < l; i ++ ) {
+
+                               const vertex = shapeVertices[ i ];
+
+                               vertices.push( vertex.x, vertex.y, 0 );
+                               normals.push( 0, 0, 1 );
+                               uvs.push( vertex.x, vertex.y ); // world uvs
+
+                       }
+
+                       // incides
+
+                       for ( let i = 0, l = faces.length; i < l; i ++ ) {
+
+                               const face = faces[ i ];
+
+                               const a = face[ 0 ] + indexOffset;
+                               const b = face[ 1 ] + indexOffset;
+                               const c = face[ 2 ] + indexOffset;
+
+                               indices.push( a, b, c );
+                               groupCount += 3;
+
+                       }
+
+               }
+
+       }
+
+       toJSON() {
+
+               const data = BufferGeometry.prototype.toJSON.call( this );
+
+               const shapes = this.parameters.shapes;
+
+               return toJSON$1( shapes, data );
+
+       }
+
+}
+
+function toJSON$1( shapes, data ) {
+
+       data.shapes = [];
+
+       if ( Array.isArray( shapes ) ) {
+
+               for ( let i = 0, l = shapes.length; i < l; i ++ ) {
+
+                       const shape = shapes[ i ];
+
+                       data.shapes.push( shape.uuid );
+
+               }
+
+       } else {
+
+               data.shapes.push( shapes.uuid );
+
+       }
+
+       return data;
+
+}
+
+class SphereGeometry extends BufferGeometry {
+
+       constructor( radius = 1, widthSegments = 8, heightSegments = 6, phiStart = 0, phiLength = Math.PI * 2, thetaStart = 0, thetaLength = Math.PI ) {
+
+               super();
+               this.type = 'SphereGeometry';
+
+               this.parameters = {
+                       radius: radius,
+                       widthSegments: widthSegments,
+                       heightSegments: heightSegments,
+                       phiStart: phiStart,
+                       phiLength: phiLength,
+                       thetaStart: thetaStart,
+                       thetaLength: thetaLength
+               };
+
+               widthSegments = Math.max( 3, Math.floor( widthSegments ) );
+               heightSegments = Math.max( 2, Math.floor( heightSegments ) );
+
+               const thetaEnd = Math.min( thetaStart + thetaLength, Math.PI );
+
+               let index = 0;
+               const grid = [];
+
+               const vertex = new Vector3();
+               const normal = new Vector3();
+
+               // buffers
+
+               const indices = [];
+               const vertices = [];
+               const normals = [];
+               const uvs = [];
+
+               // generate vertices, normals and uvs
+
+               for ( let iy = 0; iy <= heightSegments; iy ++ ) {
+
+                       const verticesRow = [];
+
+                       const v = iy / heightSegments;
+
+                       // special case for the poles
+
+                       let uOffset = 0;
+
+                       if ( iy == 0 && thetaStart == 0 ) {
+
+                               uOffset = 0.5 / widthSegments;
+
+                       } else if ( iy == heightSegments && thetaEnd == Math.PI ) {
+
+                               uOffset = - 0.5 / widthSegments;
+
+                       }
+
+                       for ( let ix = 0; ix <= widthSegments; ix ++ ) {
+
+                               const u = ix / widthSegments;
+
+                               // vertex
+
+                               vertex.x = - radius * Math.cos( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength );
+                               vertex.y = radius * Math.cos( thetaStart + v * thetaLength );
+                               vertex.z = radius * Math.sin( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength );
+
+                               vertices.push( vertex.x, vertex.y, vertex.z );
+
+                               // normal
+
+                               normal.copy( vertex ).normalize();
+                               normals.push( normal.x, normal.y, normal.z );
+
+                               // uv
+
+                               uvs.push( u + uOffset, 1 - v );
+
+                               verticesRow.push( index ++ );
+
+                       }
+
+                       grid.push( verticesRow );
+
+               }
+
+               // indices
+
+               for ( let iy = 0; iy < heightSegments; iy ++ ) {
+
+                       for ( let ix = 0; ix < widthSegments; ix ++ ) {
+
+                               const a = grid[ iy ][ ix + 1 ];
+                               const b = grid[ iy ][ ix ];
+                               const c = grid[ iy + 1 ][ ix ];
+                               const d = grid[ iy + 1 ][ ix + 1 ];
+
+                               if ( iy !== 0 || thetaStart > 0 ) indices.push( a, b, d );
+                               if ( iy !== heightSegments - 1 || thetaEnd < Math.PI ) indices.push( b, c, d );
+
+                       }
+
+               }
+
+               // build geometry
+
+               this.setIndex( indices );
+               this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
+               this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );
+               this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );
+
+       }
+
+}
+
+/**
+ * parameters = {
+ *  color: <THREE.Color>
+ * }
+ */
+
+function ShadowMaterial( parameters ) {
+
+       Material.call( this );
+
+       this.type = 'ShadowMaterial';
+
+       this.color = new Color( 0x000000 );
+       this.transparent = true;
+
+       this.setValues( parameters );
+
+}
+
+ShadowMaterial.prototype = Object.create( Material.prototype );
+ShadowMaterial.prototype.constructor = ShadowMaterial;
+
+ShadowMaterial.prototype.isShadowMaterial = true;
+
+ShadowMaterial.prototype.copy = function ( source ) {
+
+       Material.prototype.copy.call( this, source );
+
+       this.color.copy( source.color );
+
+       return this;
+
+};
+
+function RawShaderMaterial( parameters ) {
+
+       ShaderMaterial.call( this, parameters );
+
+       this.type = 'RawShaderMaterial';
+
+}
+
+RawShaderMaterial.prototype = Object.create( ShaderMaterial.prototype );
+RawShaderMaterial.prototype.constructor = RawShaderMaterial;
+
+RawShaderMaterial.prototype.isRawShaderMaterial = true;
+
+/**
+ * parameters = {
+ *  color: <hex>,
+ *  roughness: <float>,
+ *  metalness: <float>,
+ *  opacity: <float>,
+ *
+ *  map: new THREE.Texture( <Image> ),
+ *
+ *  lightMap: new THREE.Texture( <Image> ),
+ *  lightMapIntensity: <float>
+ *
+ *  aoMap: new THREE.Texture( <Image> ),
+ *  aoMapIntensity: <float>
+ *
+ *  emissive: <hex>,
+ *  emissiveIntensity: <float>
+ *  emissiveMap: new THREE.Texture( <Image> ),
+ *
+ *  bumpMap: new THREE.Texture( <Image> ),
+ *  bumpScale: <float>,
+ *
+ *  normalMap: new THREE.Texture( <Image> ),
+ *  normalMapType: THREE.TangentSpaceNormalMap,
+ *  normalScale: <Vector2>,
+ *
+ *  displacementMap: new THREE.Texture( <Image> ),
+ *  displacementScale: <float>,
+ *  displacementBias: <float>,
+ *
+ *  roughnessMap: new THREE.Texture( <Image> ),
+ *
+ *  metalnessMap: new THREE.Texture( <Image> ),
+ *
+ *  alphaMap: new THREE.Texture( <Image> ),
+ *
+ *  envMap: new THREE.CubeTexture( [posx, negx, posy, negy, posz, negz] ),
+ *  envMapIntensity: <float>
+ *
+ *  refractionRatio: <float>,
+ *
+ *  wireframe: <boolean>,
+ *  wireframeLinewidth: <float>,
+ *
+ *  skinning: <bool>,
+ *  morphTargets: <bool>,
+ *  morphNormals: <bool>
+ * }
+ */
+
+function MeshStandardMaterial( parameters ) {
+
+       Material.call( this );
+
+       this.defines = { 'STANDARD': '' };
+
+       this.type = 'MeshStandardMaterial';
+
+       this.color = new Color( 0xffffff ); // diffuse
+       this.roughness = 1.0;
+       this.metalness = 0.0;
+
+       this.map = null;
+
+       this.lightMap = null;
+       this.lightMapIntensity = 1.0;
+
+       this.aoMap = null;
+       this.aoMapIntensity = 1.0;
+
+       this.emissive = new Color( 0x000000 );
+       this.emissiveIntensity = 1.0;
+       this.emissiveMap = null;
+
+       this.bumpMap = null;
+       this.bumpScale = 1;
+
+       this.normalMap = null;
+       this.normalMapType = TangentSpaceNormalMap;
+       this.normalScale = new Vector2( 1, 1 );
+
+       this.displacementMap = null;
+       this.displacementScale = 1;
+       this.displacementBias = 0;
+
+       this.roughnessMap = null;
+
+       this.metalnessMap = null;
+
+       this.alphaMap = null;
+
+       this.envMap = null;
+       this.envMapIntensity = 1.0;
+
+       this.refractionRatio = 0.98;
+
+       this.wireframe = false;
+       this.wireframeLinewidth = 1;
+       this.wireframeLinecap = 'round';
+       this.wireframeLinejoin = 'round';
+
+       this.skinning = false;
+       this.morphTargets = false;
+       this.morphNormals = false;
+
+       this.vertexTangents = false;
+
+       this.setValues( parameters );
+
+}
+
+MeshStandardMaterial.prototype = Object.create( Material.prototype );
+MeshStandardMaterial.prototype.constructor = MeshStandardMaterial;
+
+MeshStandardMaterial.prototype.isMeshStandardMaterial = true;
+
+MeshStandardMaterial.prototype.copy = function ( source ) {
+
+       Material.prototype.copy.call( this, source );
+
+       this.defines = { 'STANDARD': '' };
+
+       this.color.copy( source.color );
+       this.roughness = source.roughness;
+       this.metalness = source.metalness;
+
+       this.map = source.map;
+
+       this.lightMap = source.lightMap;
+       this.lightMapIntensity = source.lightMapIntensity;
+
+       this.aoMap = source.aoMap;
+       this.aoMapIntensity = source.aoMapIntensity;
+
+       this.emissive.copy( source.emissive );
+       this.emissiveMap = source.emissiveMap;
+       this.emissiveIntensity = source.emissiveIntensity;
+
+       this.bumpMap = source.bumpMap;
+       this.bumpScale = source.bumpScale;
+
+       this.normalMap = source.normalMap;
+       this.normalMapType = source.normalMapType;
+       this.normalScale.copy( source.normalScale );
+
+       this.displacementMap = source.displacementMap;
+       this.displacementScale = source.displacementScale;
+       this.displacementBias = source.displacementBias;
+
+       this.roughnessMap = source.roughnessMap;
+
+       this.metalnessMap = source.metalnessMap;
+
+       this.alphaMap = source.alphaMap;
+
+       this.envMap = source.envMap;
+       this.envMapIntensity = source.envMapIntensity;
+
+       this.refractionRatio = source.refractionRatio;
+
+       this.wireframe = source.wireframe;
+       this.wireframeLinewidth = source.wireframeLinewidth;
+       this.wireframeLinecap = source.wireframeLinecap;
+       this.wireframeLinejoin = source.wireframeLinejoin;
+
+       this.skinning = source.skinning;
+       this.morphTargets = source.morphTargets;
+       this.morphNormals = source.morphNormals;
+
+       this.vertexTangents = source.vertexTangents;
+
+       return this;
+
+};
+
+/**
+ * parameters = {
+ *  clearcoat: <float>,
+ *  clearcoatMap: new THREE.Texture( <Image> ),
+ *  clearcoatRoughness: <float>,
+ *  clearcoatRoughnessMap: new THREE.Texture( <Image> ),
+ *  clearcoatNormalScale: <Vector2>,
+ *  clearcoatNormalMap: new THREE.Texture( <Image> ),
+ *
+ *  reflectivity: <float>,
+ *  ior: <float>,
+ *
+ *  sheen: <Color>,
+ *
+ *  transmission: <float>,
+ *  transmissionMap: new THREE.Texture( <Image> )
+ * }
+ */
+
+function MeshPhysicalMaterial( parameters ) {
+
+       MeshStandardMaterial.call( this );
+
+       this.defines = {
+
+               'STANDARD': '',
+               'PHYSICAL': ''
+
+       };
+
+       this.type = 'MeshPhysicalMaterial';
+
+       this.clearcoat = 0.0;
+       this.clearcoatMap = null;
+       this.clearcoatRoughness = 0.0;
+       this.clearcoatRoughnessMap = null;
+       this.clearcoatNormalScale = new Vector2( 1, 1 );
+       this.clearcoatNormalMap = null;
+
+       this.reflectivity = 0.5; // maps to F0 = 0.04
+
+       Object.defineProperty( this, 'ior', {
+               get: function () {
+
+                       return ( 1 + 0.4 * this.reflectivity ) / ( 1 - 0.4 * this.reflectivity );
+
+               },
+               set: function ( ior ) {
+
+                       this.reflectivity = MathUtils.clamp( 2.5 * ( ior - 1 ) / ( ior + 1 ), 0, 1 );
+
+               }
+       } );
+
+       this.sheen = null; // null will disable sheen bsdf
+
+       this.transmission = 0.0;
+       this.transmissionMap = null;
+
+       this.setValues( parameters );
+
+}
+
+MeshPhysicalMaterial.prototype = Object.create( MeshStandardMaterial.prototype );
+MeshPhysicalMaterial.prototype.constructor = MeshPhysicalMaterial;
+
+MeshPhysicalMaterial.prototype.isMeshPhysicalMaterial = true;
+
+MeshPhysicalMaterial.prototype.copy = function ( source ) {
+
+       MeshStandardMaterial.prototype.copy.call( this, source );
+
+       this.defines = {
+
+               'STANDARD': '',
+               'PHYSICAL': ''
+
+       };
+
+       this.clearcoat = source.clearcoat;
+       this.clearcoatMap = source.clearcoatMap;
+       this.clearcoatRoughness = source.clearcoatRoughness;
+       this.clearcoatRoughnessMap = source.clearcoatRoughnessMap;
+       this.clearcoatNormalMap = source.clearcoatNormalMap;
+       this.clearcoatNormalScale.copy( source.clearcoatNormalScale );
+
+       this.reflectivity = source.reflectivity;
+
+       if ( source.sheen ) {
+
+               this.sheen = ( this.sheen || new Color() ).copy( source.sheen );
+
+       } else {
+
+               this.sheen = null;
+
+       }
+
+       this.transmission = source.transmission;
+       this.transmissionMap = source.transmissionMap;
+
+       return this;
+
+};
+
+/**
+ * parameters = {
+ *  color: <hex>,
+ *  specular: <hex>,
+ *  shininess: <float>,
+ *  opacity: <float>,
+ *
+ *  map: new THREE.Texture( <Image> ),
+ *
+ *  lightMap: new THREE.Texture( <Image> ),
+ *  lightMapIntensity: <float>
+ *
+ *  aoMap: new THREE.Texture( <Image> ),
+ *  aoMapIntensity: <float>
+ *
+ *  emissive: <hex>,
+ *  emissiveIntensity: <float>
+ *  emissiveMap: new THREE.Texture( <Image> ),
+ *
+ *  bumpMap: new THREE.Texture( <Image> ),
+ *  bumpScale: <float>,
+ *
+ *  normalMap: new THREE.Texture( <Image> ),
+ *  normalMapType: THREE.TangentSpaceNormalMap,
+ *  normalScale: <Vector2>,
+ *
+ *  displacementMap: new THREE.Texture( <Image> ),
+ *  displacementScale: <float>,
+ *  displacementBias: <float>,
+ *
+ *  specularMap: new THREE.Texture( <Image> ),
+ *
+ *  alphaMap: new THREE.Texture( <Image> ),
+ *
+ *  envMap: new THREE.CubeTexture( [posx, negx, posy, negy, posz, negz] ),
+ *  combine: THREE.MultiplyOperation,
+ *  reflectivity: <float>,
+ *  refractionRatio: <float>,
+ *
+ *  wireframe: <boolean>,
+ *  wireframeLinewidth: <float>,
+ *
+ *  skinning: <bool>,
+ *  morphTargets: <bool>,
+ *  morphNormals: <bool>
+ * }
+ */
+
+function MeshPhongMaterial( parameters ) {
+
+       Material.call( this );
+
+       this.type = 'MeshPhongMaterial';
+
+       this.color = new Color( 0xffffff ); // diffuse
+       this.specular = new Color( 0x111111 );
+       this.shininess = 30;
+
+       this.map = null;
+
+       this.lightMap = null;
+       this.lightMapIntensity = 1.0;
+
+       this.aoMap = null;
+       this.aoMapIntensity = 1.0;
+
+       this.emissive = new Color( 0x000000 );
+       this.emissiveIntensity = 1.0;
+       this.emissiveMap = null;
+
+       this.bumpMap = null;
+       this.bumpScale = 1;
+
+       this.normalMap = null;
+       this.normalMapType = TangentSpaceNormalMap;
+       this.normalScale = new Vector2( 1, 1 );
+
+       this.displacementMap = null;
+       this.displacementScale = 1;
+       this.displacementBias = 0;
+
+       this.specularMap = null;
+
+       this.alphaMap = null;
+
+       this.envMap = null;
+       this.combine = MultiplyOperation;
+       this.reflectivity = 1;
+       this.refractionRatio = 0.98;
+
+       this.wireframe = false;
+       this.wireframeLinewidth = 1;
+       this.wireframeLinecap = 'round';
+       this.wireframeLinejoin = 'round';
+
+       this.skinning = false;
+       this.morphTargets = false;
+       this.morphNormals = false;
+
+       this.setValues( parameters );
+
+}
+
+MeshPhongMaterial.prototype = Object.create( Material.prototype );
+MeshPhongMaterial.prototype.constructor = MeshPhongMaterial;
+
+MeshPhongMaterial.prototype.isMeshPhongMaterial = true;
+
+MeshPhongMaterial.prototype.copy = function ( source ) {
+
+       Material.prototype.copy.call( this, source );
+
+       this.color.copy( source.color );
+       this.specular.copy( source.specular );
+       this.shininess = source.shininess;
+
+       this.map = source.map;
+
+       this.lightMap = source.lightMap;
+       this.lightMapIntensity = source.lightMapIntensity;
+
+       this.aoMap = source.aoMap;
+       this.aoMapIntensity = source.aoMapIntensity;
+
+       this.emissive.copy( source.emissive );
+       this.emissiveMap = source.emissiveMap;
+       this.emissiveIntensity = source.emissiveIntensity;
+
+       this.bumpMap = source.bumpMap;
+       this.bumpScale = source.bumpScale;
+
+       this.normalMap = source.normalMap;
+       this.normalMapType = source.normalMapType;
+       this.normalScale.copy( source.normalScale );
+
+       this.displacementMap = source.displacementMap;
+       this.displacementScale = source.displacementScale;
+       this.displacementBias = source.displacementBias;
+
+       this.specularMap = source.specularMap;
+
+       this.alphaMap = source.alphaMap;
+
+       this.envMap = source.envMap;
+       this.combine = source.combine;
+       this.reflectivity = source.reflectivity;
+       this.refractionRatio = source.refractionRatio;
+
+       this.wireframe = source.wireframe;
+       this.wireframeLinewidth = source.wireframeLinewidth;
+       this.wireframeLinecap = source.wireframeLinecap;
+       this.wireframeLinejoin = source.wireframeLinejoin;
+
+       this.skinning = source.skinning;
+       this.morphTargets = source.morphTargets;
+       this.morphNormals = source.morphNormals;
+
+       return this;
+
+};
+
+/**
+ * parameters = {
+ *  color: <hex>,
+ *
+ *  map: new THREE.Texture( <Image> ),
+ *  gradientMap: new THREE.Texture( <Image> ),
+ *
+ *  lightMap: new THREE.Texture( <Image> ),
+ *  lightMapIntensity: <float>
+ *
+ *  aoMap: new THREE.Texture( <Image> ),
+ *  aoMapIntensity: <float>
+ *
+ *  emissive: <hex>,
+ *  emissiveIntensity: <float>
+ *  emissiveMap: new THREE.Texture( <Image> ),
+ *
+ *  bumpMap: new THREE.Texture( <Image> ),
+ *  bumpScale: <float>,
+ *
+ *  normalMap: new THREE.Texture( <Image> ),
+ *  normalMapType: THREE.TangentSpaceNormalMap,
+ *  normalScale: <Vector2>,
+ *
+ *  displacementMap: new THREE.Texture( <Image> ),
+ *  displacementScale: <float>,
+ *  displacementBias: <float>,
+ *
+ *  alphaMap: new THREE.Texture( <Image> ),
+ *
+ *  wireframe: <boolean>,
+ *  wireframeLinewidth: <float>,
+ *
+ *  skinning: <bool>,
+ *  morphTargets: <bool>,
+ *  morphNormals: <bool>
+ * }
+ */
+
+function MeshToonMaterial( parameters ) {
+
+       Material.call( this );
+
+       this.defines = { 'TOON': '' };
+
+       this.type = 'MeshToonMaterial';
+
+       this.color = new Color( 0xffffff );
+
+       this.map = null;
+       this.gradientMap = null;
+
+       this.lightMap = null;
+       this.lightMapIntensity = 1.0;
+
+       this.aoMap = null;
+       this.aoMapIntensity = 1.0;
+
+       this.emissive = new Color( 0x000000 );
+       this.emissiveIntensity = 1.0;
+       this.emissiveMap = null;
+
+       this.bumpMap = null;
+       this.bumpScale = 1;
+
+       this.normalMap = null;
+       this.normalMapType = TangentSpaceNormalMap;
+       this.normalScale = new Vector2( 1, 1 );
+
+       this.displacementMap = null;
+       this.displacementScale = 1;
+       this.displacementBias = 0;
+
+       this.alphaMap = null;
+
+       this.wireframe = false;
+       this.wireframeLinewidth = 1;
+       this.wireframeLinecap = 'round';
+       this.wireframeLinejoin = 'round';
+
+       this.skinning = false;
+       this.morphTargets = false;
+       this.morphNormals = false;
+
+       this.setValues( parameters );
+
+}
+
+MeshToonMaterial.prototype = Object.create( Material.prototype );
+MeshToonMaterial.prototype.constructor = MeshToonMaterial;
+
+MeshToonMaterial.prototype.isMeshToonMaterial = true;
+
+MeshToonMaterial.prototype.copy = function ( source ) {
+
+       Material.prototype.copy.call( this, source );
+
+       this.color.copy( source.color );
+
+       this.map = source.map;
+       this.gradientMap = source.gradientMap;
+
+       this.lightMap = source.lightMap;
+       this.lightMapIntensity = source.lightMapIntensity;
+
+       this.aoMap = source.aoMap;
+       this.aoMapIntensity = source.aoMapIntensity;
+
+       this.emissive.copy( source.emissive );
+       this.emissiveMap = source.emissiveMap;
+       this.emissiveIntensity = source.emissiveIntensity;
+
+       this.bumpMap = source.bumpMap;
+       this.bumpScale = source.bumpScale;
+
+       this.normalMap = source.normalMap;
+       this.normalMapType = source.normalMapType;
+       this.normalScale.copy( source.normalScale );
+
+       this.displacementMap = source.displacementMap;
+       this.displacementScale = source.displacementScale;
+       this.displacementBias = source.displacementBias;
+
+       this.alphaMap = source.alphaMap;
+
+       this.wireframe = source.wireframe;
+       this.wireframeLinewidth = source.wireframeLinewidth;
+       this.wireframeLinecap = source.wireframeLinecap;
+       this.wireframeLinejoin = source.wireframeLinejoin;
+
+       this.skinning = source.skinning;
+       this.morphTargets = source.morphTargets;
+       this.morphNormals = source.morphNormals;
+
+       return this;
+
+};
+
+/**
+ * parameters = {
+ *  opacity: <float>,
+ *
+ *  bumpMap: new THREE.Texture( <Image> ),
+ *  bumpScale: <float>,
+ *
+ *  normalMap: new THREE.Texture( <Image> ),
+ *  normalMapType: THREE.TangentSpaceNormalMap,
+ *  normalScale: <Vector2>,
+ *
+ *  displacementMap: new THREE.Texture( <Image> ),
+ *  displacementScale: <float>,
+ *  displacementBias: <float>,
+ *
+ *  wireframe: <boolean>,
+ *  wireframeLinewidth: <float>
+ *
+ *  skinning: <bool>,
+ *  morphTargets: <bool>,
+ *  morphNormals: <bool>
+ * }
+ */
+
+function MeshNormalMaterial( parameters ) {
+
+       Material.call( this );
+
+       this.type = 'MeshNormalMaterial';
+
+       this.bumpMap = null;
+       this.bumpScale = 1;
+
+       this.normalMap = null;
+       this.normalMapType = TangentSpaceNormalMap;
+       this.normalScale = new Vector2( 1, 1 );
+
+       this.displacementMap = null;
+       this.displacementScale = 1;
+       this.displacementBias = 0;
+
+       this.wireframe = false;
+       this.wireframeLinewidth = 1;
+
+       this.fog = false;
+
+       this.skinning = false;
+       this.morphTargets = false;
+       this.morphNormals = false;
+
+       this.setValues( parameters );
+
+}
+
+MeshNormalMaterial.prototype = Object.create( Material.prototype );
+MeshNormalMaterial.prototype.constructor = MeshNormalMaterial;
+
+MeshNormalMaterial.prototype.isMeshNormalMaterial = true;
+
+MeshNormalMaterial.prototype.copy = function ( source ) {
+
+       Material.prototype.copy.call( this, source );
+
+       this.bumpMap = source.bumpMap;
+       this.bumpScale = source.bumpScale;
+
+       this.normalMap = source.normalMap;
+       this.normalMapType = source.normalMapType;
+       this.normalScale.copy( source.normalScale );
+
+       this.displacementMap = source.displacementMap;
+       this.displacementScale = source.displacementScale;
+       this.displacementBias = source.displacementBias;
+
+       this.wireframe = source.wireframe;
+       this.wireframeLinewidth = source.wireframeLinewidth;
+
+       this.skinning = source.skinning;
+       this.morphTargets = source.morphTargets;
+       this.morphNormals = source.morphNormals;
+
+       return this;
+
+};
+
+/**
+ * parameters = {
+ *  color: <hex>,
+ *  opacity: <float>,
+ *
+ *  map: new THREE.Texture( <Image> ),
+ *
+ *  lightMap: new THREE.Texture( <Image> ),
+ *  lightMapIntensity: <float>
+ *
+ *  aoMap: new THREE.Texture( <Image> ),
+ *  aoMapIntensity: <float>
+ *
+ *  emissive: <hex>,
+ *  emissiveIntensity: <float>
+ *  emissiveMap: new THREE.Texture( <Image> ),
+ *
+ *  specularMap: new THREE.Texture( <Image> ),
+ *
+ *  alphaMap: new THREE.Texture( <Image> ),
+ *
+ *  envMap: new THREE.CubeTexture( [posx, negx, posy, negy, posz, negz] ),
+ *  combine: THREE.Multiply,
+ *  reflectivity: <float>,
+ *  refractionRatio: <float>,
+ *
+ *  wireframe: <boolean>,
+ *  wireframeLinewidth: <float>,
+ *
+ *  skinning: <bool>,
+ *  morphTargets: <bool>,
+ *  morphNormals: <bool>
+ * }
+ */
+
+function MeshLambertMaterial( parameters ) {
+
+       Material.call( this );
+
+       this.type = 'MeshLambertMaterial';
+
+       this.color = new Color( 0xffffff ); // diffuse
+
+       this.map = null;
+
+       this.lightMap = null;
+       this.lightMapIntensity = 1.0;
+
+       this.aoMap = null;
+       this.aoMapIntensity = 1.0;
+
+       this.emissive = new Color( 0x000000 );
+       this.emissiveIntensity = 1.0;
+       this.emissiveMap = null;
+
+       this.specularMap = null;
+
+       this.alphaMap = null;
+
+       this.envMap = null;
+       this.combine = MultiplyOperation;
+       this.reflectivity = 1;
+       this.refractionRatio = 0.98;
+
+       this.wireframe = false;
+       this.wireframeLinewidth = 1;
+       this.wireframeLinecap = 'round';
+       this.wireframeLinejoin = 'round';
+
+       this.skinning = false;
+       this.morphTargets = false;
+       this.morphNormals = false;
+
+       this.setValues( parameters );
+
+}
+
+MeshLambertMaterial.prototype = Object.create( Material.prototype );
+MeshLambertMaterial.prototype.constructor = MeshLambertMaterial;
+
+MeshLambertMaterial.prototype.isMeshLambertMaterial = true;
+
+MeshLambertMaterial.prototype.copy = function ( source ) {
+
+       Material.prototype.copy.call( this, source );
+
+       this.color.copy( source.color );
+
+       this.map = source.map;
+
+       this.lightMap = source.lightMap;
+       this.lightMapIntensity = source.lightMapIntensity;
+
+       this.aoMap = source.aoMap;
+       this.aoMapIntensity = source.aoMapIntensity;
+
+       this.emissive.copy( source.emissive );
+       this.emissiveMap = source.emissiveMap;
+       this.emissiveIntensity = source.emissiveIntensity;
+
+       this.specularMap = source.specularMap;
+
+       this.alphaMap = source.alphaMap;
+
+       this.envMap = source.envMap;
+       this.combine = source.combine;
+       this.reflectivity = source.reflectivity;
+       this.refractionRatio = source.refractionRatio;
+
+       this.wireframe = source.wireframe;
+       this.wireframeLinewidth = source.wireframeLinewidth;
+       this.wireframeLinecap = source.wireframeLinecap;
+       this.wireframeLinejoin = source.wireframeLinejoin;
+
+       this.skinning = source.skinning;
+       this.morphTargets = source.morphTargets;
+       this.morphNormals = source.morphNormals;
+
+       return this;
+
+};
+
+/**
+ * parameters = {
+ *  color: <hex>,
+ *  opacity: <float>,
+ *
+ *  matcap: new THREE.Texture( <Image> ),
+ *
+ *  map: new THREE.Texture( <Image> ),
+ *
+ *  bumpMap: new THREE.Texture( <Image> ),
+ *  bumpScale: <float>,
+ *
+ *  normalMap: new THREE.Texture( <Image> ),
+ *  normalMapType: THREE.TangentSpaceNormalMap,
+ *  normalScale: <Vector2>,
+ *
+ *  displacementMap: new THREE.Texture( <Image> ),
+ *  displacementScale: <float>,
+ *  displacementBias: <float>,
+ *
+ *  alphaMap: new THREE.Texture( <Image> ),
+ *
+ *  skinning: <bool>,
+ *  morphTargets: <bool>,
+ *  morphNormals: <bool>
+ * }
+ */
+
+function MeshMatcapMaterial( parameters ) {
+
+       Material.call( this );
+
+       this.defines = { 'MATCAP': '' };
+
+       this.type = 'MeshMatcapMaterial';
+
+       this.color = new Color( 0xffffff ); // diffuse
+
+       this.matcap = null;
+
+       this.map = null;
+
+       this.bumpMap = null;
+       this.bumpScale = 1;
+
+       this.normalMap = null;
+       this.normalMapType = TangentSpaceNormalMap;
+       this.normalScale = new Vector2( 1, 1 );
+
+       this.displacementMap = null;
+       this.displacementScale = 1;
+       this.displacementBias = 0;
+
+       this.alphaMap = null;
+
+       this.skinning = false;
+       this.morphTargets = false;
+       this.morphNormals = false;
+
+       this.setValues( parameters );
+
+}
+
+MeshMatcapMaterial.prototype = Object.create( Material.prototype );
+MeshMatcapMaterial.prototype.constructor = MeshMatcapMaterial;
+
+MeshMatcapMaterial.prototype.isMeshMatcapMaterial = true;
+
+MeshMatcapMaterial.prototype.copy = function ( source ) {
+
+       Material.prototype.copy.call( this, source );
+
+       this.defines = { 'MATCAP': '' };
+
+       this.color.copy( source.color );
+
+       this.matcap = source.matcap;
+
+       this.map = source.map;
+
+       this.bumpMap = source.bumpMap;
+       this.bumpScale = source.bumpScale;
+
+       this.normalMap = source.normalMap;
+       this.normalMapType = source.normalMapType;
+       this.normalScale.copy( source.normalScale );
+
+       this.displacementMap = source.displacementMap;
+       this.displacementScale = source.displacementScale;
+       this.displacementBias = source.displacementBias;
+
+       this.alphaMap = source.alphaMap;
+
+       this.skinning = source.skinning;
+       this.morphTargets = source.morphTargets;
+       this.morphNormals = source.morphNormals;
+
+       return this;
+
+};
+
+/**
+ * parameters = {
+ *  color: <hex>,
+ *  opacity: <float>,
+ *
+ *  linewidth: <float>,
+ *
+ *  scale: <float>,
+ *  dashSize: <float>,
+ *  gapSize: <float>
+ * }
+ */
+
+function LineDashedMaterial( parameters ) {
+
+       LineBasicMaterial.call( this );
+
+       this.type = 'LineDashedMaterial';
+
+       this.scale = 1;
+       this.dashSize = 3;
+       this.gapSize = 1;
+
+       this.setValues( parameters );
+
+}
+
+LineDashedMaterial.prototype = Object.create( LineBasicMaterial.prototype );
+LineDashedMaterial.prototype.constructor = LineDashedMaterial;
+
+LineDashedMaterial.prototype.isLineDashedMaterial = true;
+
+LineDashedMaterial.prototype.copy = function ( source ) {
+
+       LineBasicMaterial.prototype.copy.call( this, source );
+
+       this.scale = source.scale;
+       this.dashSize = source.dashSize;
+       this.gapSize = source.gapSize;
+
+       return this;
+
+};
+
+var Materials = /*#__PURE__*/Object.freeze({
+       __proto__: null,
+       ShadowMaterial: ShadowMaterial,
+       SpriteMaterial: SpriteMaterial,
+       RawShaderMaterial: RawShaderMaterial,
+       ShaderMaterial: ShaderMaterial,
+       PointsMaterial: PointsMaterial,
+       MeshPhysicalMaterial: MeshPhysicalMaterial,
+       MeshStandardMaterial: MeshStandardMaterial,
+       MeshPhongMaterial: MeshPhongMaterial,
+       MeshToonMaterial: MeshToonMaterial,
+       MeshNormalMaterial: MeshNormalMaterial,
+       MeshLambertMaterial: MeshLambertMaterial,
+       MeshDepthMaterial: MeshDepthMaterial,
+       MeshDistanceMaterial: MeshDistanceMaterial,
+       MeshBasicMaterial: MeshBasicMaterial,
+       MeshMatcapMaterial: MeshMatcapMaterial,
+       LineDashedMaterial: LineDashedMaterial,
+       LineBasicMaterial: LineBasicMaterial,
+       Material: Material
+});
+
+const AnimationUtils = {
+
+       // same as Array.prototype.slice, but also works on typed arrays
+       arraySlice: function ( array, from, to ) {
+
+               if ( AnimationUtils.isTypedArray( array ) ) {
+
+                       // in ios9 array.subarray(from, undefined) will return empty array
+                       // but array.subarray(from) or array.subarray(from, len) is correct
+                       return new array.constructor( array.subarray( from, to !== undefined ? to : array.length ) );
+
+               }
+
+               return array.slice( from, to );
+
+       },
+
+       // converts an array to a specific type
+       convertArray: function ( array, type, forceClone ) {
+
+               if ( ! array || // let 'undefined' and 'null' pass
+                       ! forceClone && array.constructor === type ) return array;
+
+               if ( typeof type.BYTES_PER_ELEMENT === 'number' ) {
+
+                       return new type( array ); // create typed array
+
+               }
+
+               return Array.prototype.slice.call( array ); // create Array
+
+       },
+
+       isTypedArray: function ( object ) {
+
+               return ArrayBuffer.isView( object ) &&
+                       ! ( object instanceof DataView );
+
+       },
+
+       // returns an array by which times and values can be sorted
+       getKeyframeOrder: function ( times ) {
+
+               function compareTime( i, j ) {
+
+                       return times[ i ] - times[ j ];
+
+               }
+
+               const n = times.length;
+               const result = new Array( n );
+               for ( let i = 0; i !== n; ++ i ) result[ i ] = i;
+
+               result.sort( compareTime );
+
+               return result;
+
+       },
+
+       // uses the array previously returned by 'getKeyframeOrder' to sort data
+       sortedArray: function ( values, stride, order ) {
+
+               const nValues = values.length;
+               const result = new values.constructor( nValues );
+
+               for ( let i = 0, dstOffset = 0; dstOffset !== nValues; ++ i ) {
+
+                       const srcOffset = order[ i ] * stride;
+
+                       for ( let j = 0; j !== stride; ++ j ) {
+
+                               result[ dstOffset ++ ] = values[ srcOffset + j ];
+
+                       }
+
+               }
+
+               return result;
+
+       },
+
+       // function for parsing AOS keyframe formats
+       flattenJSON: function ( jsonKeys, times, values, valuePropertyName ) {
+
+               let i = 1, key = jsonKeys[ 0 ];
+
+               while ( key !== undefined && key[ valuePropertyName ] === undefined ) {
+
+                       key = jsonKeys[ i ++ ];
+
+               }
+
+               if ( key === undefined ) return; // no data
+
+               let value = key[ valuePropertyName ];
+               if ( value === undefined ) return; // no data
+
+               if ( Array.isArray( value ) ) {
+
+                       do {
+
+                               value = key[ valuePropertyName ];
+
+                               if ( value !== undefined ) {
+
+                                       times.push( key.time );
+                                       values.push.apply( values, value ); // push all elements
+
+                               }
+
+                               key = jsonKeys[ i ++ ];
+
+                       } while ( key !== undefined );
+
+               } else if ( value.toArray !== undefined ) {
+
+                       // ...assume THREE.Math-ish
+
+                       do {
+
+                               value = key[ valuePropertyName ];
+
+                               if ( value !== undefined ) {
+
+                                       times.push( key.time );
+                                       value.toArray( values, values.length );
+
+                               }
+
+                               key = jsonKeys[ i ++ ];
+
+                       } while ( key !== undefined );
+
+               } else {
+
+                       // otherwise push as-is
+
+                       do {
+
+                               value = key[ valuePropertyName ];
+
+                               if ( value !== undefined ) {
+
+                                       times.push( key.time );
+                                       values.push( value );
+
+                               }
+
+                               key = jsonKeys[ i ++ ];
+
+                       } while ( key !== undefined );
+
+               }
+
+       },
+
+       subclip: function ( sourceClip, name, startFrame, endFrame, fps = 30 ) {
+
+               const clip = sourceClip.clone();
+
+               clip.name = name;
+
+               const tracks = [];
+
+               for ( let i = 0; i < clip.tracks.length; ++ i ) {
+
+                       const track = clip.tracks[ i ];
+                       const valueSize = track.getValueSize();
+
+                       const times = [];
+                       const values = [];
+
+                       for ( let j = 0; j < track.times.length; ++ j ) {
+
+                               const frame = track.times[ j ] * fps;
+
+                               if ( frame < startFrame || frame >= endFrame ) continue;
+
+                               times.push( track.times[ j ] );
+
+                               for ( let k = 0; k < valueSize; ++ k ) {
+
+                                       values.push( track.values[ j * valueSize + k ] );
+
+                               }
+
+                       }
+
+                       if ( times.length === 0 ) continue;
+
+                       track.times = AnimationUtils.convertArray( times, track.times.constructor );
+                       track.values = AnimationUtils.convertArray( values, track.values.constructor );
+
+                       tracks.push( track );
+
+               }
+
+               clip.tracks = tracks;
+
+               // find minimum .times value across all tracks in the trimmed clip
+
+               let minStartTime = Infinity;
+
+               for ( let i = 0; i < clip.tracks.length; ++ i ) {
+
+                       if ( minStartTime > clip.tracks[ i ].times[ 0 ] ) {
+
+                               minStartTime = clip.tracks[ i ].times[ 0 ];
+
+                       }
+
+               }
+
+               // shift all tracks such that clip begins at t=0
+
+               for ( let i = 0; i < clip.tracks.length; ++ i ) {
+
+                       clip.tracks[ i ].shift( - 1 * minStartTime );
+
+               }
+
+               clip.resetDuration();
+
+               return clip;
+
+       },
+
+       makeClipAdditive: function ( targetClip, referenceFrame = 0, referenceClip = targetClip, fps = 30 ) {
+
+               if ( fps <= 0 ) fps = 30;
+
+               const numTracks = referenceClip.tracks.length;
+               const referenceTime = referenceFrame / fps;
+
+               // Make each track's values relative to the values at the reference frame
+               for ( let i = 0; i < numTracks; ++ i ) {
+
+                       const referenceTrack = referenceClip.tracks[ i ];
+                       const referenceTrackType = referenceTrack.ValueTypeName;
+
+                       // Skip this track if it's non-numeric
+                       if ( referenceTrackType === 'bool' || referenceTrackType === 'string' ) continue;
+
+                       // Find the track in the target clip whose name and type matches the reference track
+                       const targetTrack = targetClip.tracks.find( function ( track ) {
+
+                               return track.name === referenceTrack.name
+                                       && track.ValueTypeName === referenceTrackType;
+
+                       } );
+
+                       if ( targetTrack === undefined ) continue;
+
+                       let referenceOffset = 0;
+                       const referenceValueSize = referenceTrack.getValueSize();
+
+                       if ( referenceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) {
+
+                               referenceOffset = referenceValueSize / 3;
+
+                       }
+
+                       let targetOffset = 0;
+                       const targetValueSize = targetTrack.getValueSize();
+
+                       if ( targetTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) {
+
+                               targetOffset = targetValueSize / 3;
+
+                       }
+
+                       const lastIndex = referenceTrack.times.length - 1;
+                       let referenceValue;
+
+                       // Find the value to subtract out of the track
+                       if ( referenceTime <= referenceTrack.times[ 0 ] ) {
+
+                               // Reference frame is earlier than the first keyframe, so just use the first keyframe
+                               const startIndex = referenceOffset;
+                               const endIndex = referenceValueSize - referenceOffset;
+                               referenceValue = AnimationUtils.arraySlice( referenceTrack.values, startIndex, endIndex );
+
+                       } else if ( referenceTime >= referenceTrack.times[ lastIndex ] ) {
+
+                               // Reference frame is after the last keyframe, so just use the last keyframe
+                               const startIndex = lastIndex * referenceValueSize + referenceOffset;
+                               const endIndex = startIndex + referenceValueSize - referenceOffset;
+                               referenceValue = AnimationUtils.arraySlice( referenceTrack.values, startIndex, endIndex );
+
+                       } else {
+
+                               // Interpolate to the reference value
+                               const interpolant = referenceTrack.createInterpolant();
+                               const startIndex = referenceOffset;
+                               const endIndex = referenceValueSize - referenceOffset;
+                               interpolant.evaluate( referenceTime );
+                               referenceValue = AnimationUtils.arraySlice( interpolant.resultBuffer, startIndex, endIndex );
+
+                       }
+
+                       // Conjugate the quaternion
+                       if ( referenceTrackType === 'quaternion' ) {
+
+                               const referenceQuat = new Quaternion().fromArray( referenceValue ).normalize().conjugate();
+                               referenceQuat.toArray( referenceValue );
+
+                       }
+
+                       // Subtract the reference value from all of the track values
+
+                       const numTimes = targetTrack.times.length;
+                       for ( let j = 0; j < numTimes; ++ j ) {
+
+                               const valueStart = j * targetValueSize + targetOffset;
+
+                               if ( referenceTrackType === 'quaternion' ) {
+
+                                       // Multiply the conjugate for quaternion track types
+                                       Quaternion.multiplyQuaternionsFlat(
+                                               targetTrack.values,
+                                               valueStart,
+                                               referenceValue,
+                                               0,
+                                               targetTrack.values,
+                                               valueStart
+                                       );
+
+                               } else {
+
+                                       const valueEnd = targetValueSize - targetOffset * 2;
+
+                                       // Subtract each value for all other numeric track types
+                                       for ( let k = 0; k < valueEnd; ++ k ) {
+
+                                               targetTrack.values[ valueStart + k ] -= referenceValue[ k ];
+
+                                       }
+
+                               }
+
+                       }
+
+               }
+
+               targetClip.blendMode = AdditiveAnimationBlendMode;
+
+               return targetClip;
+
+       }
+
+};
+
+/**
+ * Abstract base class of interpolants over parametric samples.
+ *
+ * The parameter domain is one dimensional, typically the time or a path
+ * along a curve defined by the data.
+ *
+ * The sample values can have any dimensionality and derived classes may
+ * apply special interpretations to the data.
+ *
+ * This class provides the interval seek in a Template Method, deferring
+ * the actual interpolation to derived classes.
+ *
+ * Time complexity is O(1) for linear access crossing at most two points
+ * and O(log N) for random access, where N is the number of positions.
+ *
+ * References:
+ *
+ *             http://www.oodesign.com/template-method-pattern.html
+ *
+ */
+
+function Interpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) {
+
+       this.parameterPositions = parameterPositions;
+       this._cachedIndex = 0;
+
+       this.resultBuffer = resultBuffer !== undefined ?
+               resultBuffer : new sampleValues.constructor( sampleSize );
+       this.sampleValues = sampleValues;
+       this.valueSize = sampleSize;
+
+}
+
+Object.assign( Interpolant.prototype, {
+
+       evaluate: function ( t ) {
+
+               const pp = this.parameterPositions;
+               let i1 = this._cachedIndex,
+                       t1 = pp[ i1 ],
+                       t0 = pp[ i1 - 1 ];
+
+               validate_interval: {
+
+                       seek: {
+
+                               let right;
+
+                               linear_scan: {
+
+                                       //- See http://jsperf.com/comparison-to-undefined/3
+                                       //- slower code:
+                                       //-
+                                       //-                             if ( t >= t1 || t1 === undefined ) {
+                                       forward_scan: if ( ! ( t < t1 ) ) {
+
+                                               for ( let giveUpAt = i1 + 2; ; ) {
+
+                                                       if ( t1 === undefined ) {
+
+                                                               if ( t < t0 ) break forward_scan;
+
+                                                               // after end
+
+                                                               i1 = pp.length;
+                                                               this._cachedIndex = i1;
+                                                               return this.afterEnd_( i1 - 1, t, t0 );
+
+                                                       }
+
+                                                       if ( i1 === giveUpAt ) break; // this loop
+
+                                                       t0 = t1;
+                                                       t1 = pp[ ++ i1 ];
+
+                                                       if ( t < t1 ) {
+
+                                                               // we have arrived at the sought interval
+                                                               break seek;
+
+                                                       }
+
+                                               }
+
+                                               // prepare binary search on the right side of the index
+                                               right = pp.length;
+                                               break linear_scan;
+
+                                       }
+
+                                       //- slower code:
+                                       //-                                     if ( t < t0 || t0 === undefined ) {
+                                       if ( ! ( t >= t0 ) ) {
+
+                                               // looping?
+
+                                               const t1global = pp[ 1 ];
+
+                                               if ( t < t1global ) {
+
+                                                       i1 = 2; // + 1, using the scan for the details
+                                                       t0 = t1global;
+
+                                               }
+
+                                               // linear reverse scan
+
+                                               for ( let giveUpAt = i1 - 2; ; ) {
+
+                                                       if ( t0 === undefined ) {
+
+                                                               // before start
+
+                                                               this._cachedIndex = 0;
+                                                               return this.beforeStart_( 0, t, t1 );
+
+                                                       }
+
+                                                       if ( i1 === giveUpAt ) break; // this loop
+
+                                                       t1 = t0;
+                                                       t0 = pp[ -- i1 - 1 ];
+
+                                                       if ( t >= t0 ) {
+
+                                                               // we have arrived at the sought interval
+                                                               break seek;
+
+                                                       }
+
+                                               }
+
+                                               // prepare binary search on the left side of the index
+                                               right = i1;
+                                               i1 = 0;
+                                               break linear_scan;
+
+                                       }
+
+                                       // the interval is valid
+
+                                       break validate_interval;
+
+                               } // linear scan
+
+                               // binary search
+
+                               while ( i1 < right ) {
+
+                                       const mid = ( i1 + right ) >>> 1;
+
+                                       if ( t < pp[ mid ] ) {
+
+                                               right = mid;
+
+                                       } else {
+
+                                               i1 = mid + 1;
+
+                                       }
+
+                               }
+
+                               t1 = pp[ i1 ];
+                               t0 = pp[ i1 - 1 ];
+
+                               // check boundary cases, again
+
+                               if ( t0 === undefined ) {
+
+                                       this._cachedIndex = 0;
+                                       return this.beforeStart_( 0, t, t1 );
+
+                               }
+
+                               if ( t1 === undefined ) {
+
+                                       i1 = pp.length;
+                                       this._cachedIndex = i1;
+                                       return this.afterEnd_( i1 - 1, t0, t );
+
+                               }
+
+                       } // seek
+
+                       this._cachedIndex = i1;
+
+                       this.intervalChanged_( i1, t0, t1 );
+
+               } // validate_interval
+
+               return this.interpolate_( i1, t0, t, t1 );
+
+       },
+
+       settings: null, // optional, subclass-specific settings structure
+       // Note: The indirection allows central control of many interpolants.
+
+       // --- Protected interface
+
+       DefaultSettings_: {},
+
+       getSettings_: function () {
+
+               return this.settings || this.DefaultSettings_;
+
+       },
+
+       copySampleValue_: function ( index ) {
+
+               // copies a sample value to the result buffer
+
+               const result = this.resultBuffer,
+                       values = this.sampleValues,
+                       stride = this.valueSize,
+                       offset = index * stride;
+
+               for ( let i = 0; i !== stride; ++ i ) {
+
+                       result[ i ] = values[ offset + i ];
+
+               }
+
+               return result;
+
+       },
+
+       // Template methods for derived classes:
+
+       interpolate_: function ( /* i1, t0, t, t1 */ ) {
+
+               throw new Error( 'call to abstract method' );
+               // implementations shall return this.resultBuffer
+
+       },
+
+       intervalChanged_: function ( /* i1, t0, t1 */ ) {
+
+               // empty
+
+       }
+
+} );
+
+// DECLARE ALIAS AFTER assign prototype
+Object.assign( Interpolant.prototype, {
+
+       //( 0, t, t0 ), returns this.resultBuffer
+       beforeStart_: Interpolant.prototype.copySampleValue_,
+
+       //( N-1, tN-1, t ), returns this.resultBuffer
+       afterEnd_: Interpolant.prototype.copySampleValue_,
+
+} );
+
+/**
+ * Fast and simple cubic spline interpolant.
+ *
+ * It was derived from a Hermitian construction setting the first derivative
+ * at each sample position to the linear slope between neighboring positions
+ * over their parameter interval.
+ */
+
+function CubicInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) {
+
+       Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer );
+
+       this._weightPrev = - 0;
+       this._offsetPrev = - 0;
+       this._weightNext = - 0;
+       this._offsetNext = - 0;
+
+}
+
+CubicInterpolant.prototype = Object.assign( Object.create( Interpolant.prototype ), {
+
+       constructor: CubicInterpolant,
+
+       DefaultSettings_: {
+
+               endingStart: ZeroCurvatureEnding,
+               endingEnd: ZeroCurvatureEnding
+
+       },
+
+       intervalChanged_: function ( i1, t0, t1 ) {
+
+               const pp = this.parameterPositions;
+               let iPrev = i1 - 2,
+                       iNext = i1 + 1,
+
+                       tPrev = pp[ iPrev ],
+                       tNext = pp[ iNext ];
+
+               if ( tPrev === undefined ) {
+
+                       switch ( this.getSettings_().endingStart ) {
+
+                               case ZeroSlopeEnding:
+
+                                       // f'(t0) = 0
+                                       iPrev = i1;
+                                       tPrev = 2 * t0 - t1;
+
+                                       break;
+
+                               case WrapAroundEnding:
+
+                                       // use the other end of the curve
+                                       iPrev = pp.length - 2;
+                                       tPrev = t0 + pp[ iPrev ] - pp[ iPrev + 1 ];
+
+                                       break;
+
+                               default: // ZeroCurvatureEnding
+
+                                       // f''(t0) = 0 a.k.a. Natural Spline
+                                       iPrev = i1;
+                                       tPrev = t1;
+
+                       }
+
+               }
+
+               if ( tNext === undefined ) {
+
+                       switch ( this.getSettings_().endingEnd ) {
+
+                               case ZeroSlopeEnding:
+
+                                       // f'(tN) = 0
+                                       iNext = i1;
+                                       tNext = 2 * t1 - t0;
+
+                                       break;
+
+                               case WrapAroundEnding:
+
+                                       // use the other end of the curve
+                                       iNext = 1;
+                                       tNext = t1 + pp[ 1 ] - pp[ 0 ];
+
+                                       break;
+
+                               default: // ZeroCurvatureEnding
+
+                                       // f''(tN) = 0, a.k.a. Natural Spline
+                                       iNext = i1 - 1;
+                                       tNext = t0;
+
+                       }
+
+               }
+
+               const halfDt = ( t1 - t0 ) * 0.5,
+                       stride = this.valueSize;
+
+               this._weightPrev = halfDt / ( t0 - tPrev );
+               this._weightNext = halfDt / ( tNext - t1 );
+               this._offsetPrev = iPrev * stride;
+               this._offsetNext = iNext * stride;
+
+       },
+
+       interpolate_: function ( i1, t0, t, t1 ) {
+
+               const result = this.resultBuffer,
+                       values = this.sampleValues,
+                       stride = this.valueSize,
+
+                       o1 = i1 * stride,               o0 = o1 - stride,
+                       oP = this._offsetPrev,  oN = this._offsetNext,
+                       wP = this._weightPrev,  wN = this._weightNext,
+
+                       p = ( t - t0 ) / ( t1 - t0 ),
+                       pp = p * p,
+                       ppp = pp * p;
+
+               // evaluate polynomials
+
+               const sP = - wP * ppp + 2 * wP * pp - wP * p;
+               const s0 = ( 1 + wP ) * ppp + ( - 1.5 - 2 * wP ) * pp + ( - 0.5 + wP ) * p + 1;
+               const s1 = ( - 1 - wN ) * ppp + ( 1.5 + wN ) * pp + 0.5 * p;
+               const sN = wN * ppp - wN * pp;
+
+               // combine data linearly
+
+               for ( let i = 0; i !== stride; ++ i ) {
+
+                       result[ i ] =
+                                       sP * values[ oP + i ] +
+                                       s0 * values[ o0 + i ] +
+                                       s1 * values[ o1 + i ] +
+                                       sN * values[ oN + i ];
+
+               }
+
+               return result;
+
+       }
+
+} );
+
+function LinearInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) {
+
+       Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer );
+
+}
+
+LinearInterpolant.prototype = Object.assign( Object.create( Interpolant.prototype ), {
+
+       constructor: LinearInterpolant,
+
+       interpolate_: function ( i1, t0, t, t1 ) {
+
+               const result = this.resultBuffer,
+                       values = this.sampleValues,
+                       stride = this.valueSize,
+
+                       offset1 = i1 * stride,
+                       offset0 = offset1 - stride,
+
+                       weight1 = ( t - t0 ) / ( t1 - t0 ),
+                       weight0 = 1 - weight1;
+
+               for ( let i = 0; i !== stride; ++ i ) {
+
+                       result[ i ] =
+                                       values[ offset0 + i ] * weight0 +
+                                       values[ offset1 + i ] * weight1;
+
+               }
+
+               return result;
+
+       }
+
+} );
+
+/**
+ *
+ * Interpolant that evaluates to the sample value at the position preceeding
+ * the parameter.
+ */
+
+function DiscreteInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) {
+
+       Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer );
+
+}
+
+DiscreteInterpolant.prototype = Object.assign( Object.create( Interpolant.prototype ), {
+
+       constructor: DiscreteInterpolant,
+
+       interpolate_: function ( i1 /*, t0, t, t1 */ ) {
+
+               return this.copySampleValue_( i1 - 1 );
+
+       }
+
+} );
+
+function KeyframeTrack( name, times, values, interpolation ) {
+
+       if ( name === undefined ) throw new Error( 'THREE.KeyframeTrack: track name is undefined' );
+       if ( times === undefined || times.length === 0 ) throw new Error( 'THREE.KeyframeTrack: no keyframes in track named ' + name );
+
+       this.name = name;
+
+       this.times = AnimationUtils.convertArray( times, this.TimeBufferType );
+       this.values = AnimationUtils.convertArray( values, this.ValueBufferType );
+
+       this.setInterpolation( interpolation || this.DefaultInterpolation );
+
+}
+
+// Static methods
+
+Object.assign( KeyframeTrack, {
+
+       // Serialization (in static context, because of constructor invocation
+       // and automatic invocation of .toJSON):
+
+       toJSON: function ( track ) {
+
+               const trackType = track.constructor;
+
+               let json;
+
+               // derived classes can define a static toJSON method
+               if ( trackType.toJSON !== undefined ) {
+
+                       json = trackType.toJSON( track );
+
+               } else {
+
+                       // by default, we assume the data can be serialized as-is
+                       json = {
+
+                               'name': track.name,
+                               'times': AnimationUtils.convertArray( track.times, Array ),
+                               'values': AnimationUtils.convertArray( track.values, Array )
+
+                       };
+
+                       const interpolation = track.getInterpolation();
+
+                       if ( interpolation !== track.DefaultInterpolation ) {
+
+                               json.interpolation = interpolation;
+
+                       }
+
+               }
+
+               json.type = track.ValueTypeName; // mandatory
+
+               return json;
+
+       }
+
+} );
+
+Object.assign( KeyframeTrack.prototype, {
+
+       constructor: KeyframeTrack,
+
+       TimeBufferType: Float32Array,
+
+       ValueBufferType: Float32Array,
+
+       DefaultInterpolation: InterpolateLinear,
+
+       InterpolantFactoryMethodDiscrete: function ( result ) {
+
+               return new DiscreteInterpolant( this.times, this.values, this.getValueSize(), result );
+
+       },
+
+       InterpolantFactoryMethodLinear: function ( result ) {
+
+               return new LinearInterpolant( this.times, this.values, this.getValueSize(), result );
+
+       },
+
+       InterpolantFactoryMethodSmooth: function ( result ) {
+
+               return new CubicInterpolant( this.times, this.values, this.getValueSize(), result );
+
+       },
+
+       setInterpolation: function ( interpolation ) {
+
+               let factoryMethod;
+
+               switch ( interpolation ) {
+
+                       case InterpolateDiscrete:
+
+                               factoryMethod = this.InterpolantFactoryMethodDiscrete;
+
+                               break;
+
+                       case InterpolateLinear:
+
+                               factoryMethod = this.InterpolantFactoryMethodLinear;
+
+                               break;
+
+                       case InterpolateSmooth:
+
+                               factoryMethod = this.InterpolantFactoryMethodSmooth;
+
+                               break;
+
+               }
+
+               if ( factoryMethod === undefined ) {
+
+                       const message = 'unsupported interpolation for ' +
+                               this.ValueTypeName + ' keyframe track named ' + this.name;
+
+                       if ( this.createInterpolant === undefined ) {
+
+                               // fall back to default, unless the default itself is messed up
+                               if ( interpolation !== this.DefaultInterpolation ) {
+
+                                       this.setInterpolation( this.DefaultInterpolation );
+
+                               } else {
+
+                                       throw new Error( message ); // fatal, in this case
+
+                               }
+
+                       }
+
+                       console.warn( 'THREE.KeyframeTrack:', message );
+                       return this;
+
+               }
+
+               this.createInterpolant = factoryMethod;
+
+               return this;
+
+       },
+
+       getInterpolation: function () {
+
+               switch ( this.createInterpolant ) {
+
+                       case this.InterpolantFactoryMethodDiscrete:
+
+                               return InterpolateDiscrete;
+
+                       case this.InterpolantFactoryMethodLinear:
+
+                               return InterpolateLinear;
+
+                       case this.InterpolantFactoryMethodSmooth:
+
+                               return InterpolateSmooth;
+
+               }
+
+       },
+
+       getValueSize: function () {
+
+               return this.values.length / this.times.length;
+
+       },
+
+       // move all keyframes either forwards or backwards in time
+       shift: function ( timeOffset ) {
+
+               if ( timeOffset !== 0.0 ) {
+
+                       const times = this.times;
+
+                       for ( let i = 0, n = times.length; i !== n; ++ i ) {
+
+                               times[ i ] += timeOffset;
+
+                       }
+
+               }
+
+               return this;
+
+       },
+
+       // scale all keyframe times by a factor (useful for frame <-> seconds conversions)
+       scale: function ( timeScale ) {
+
+               if ( timeScale !== 1.0 ) {
+
+                       const times = this.times;
+
+                       for ( let i = 0, n = times.length; i !== n; ++ i ) {
+
+                               times[ i ] *= timeScale;
+
+                       }
+
+               }
+
+               return this;
+
+       },
+
+       // removes keyframes before and after animation without changing any values within the range [startTime, endTime].
+       // IMPORTANT: We do not shift around keys to the start of the track time, because for interpolated keys this will change their values
+       trim: function ( startTime, endTime ) {
+
+               const times = this.times,
+                       nKeys = times.length;
+
+               let from = 0,
+                       to = nKeys - 1;
+
+               while ( from !== nKeys && times[ from ] < startTime ) {
+
+                       ++ from;
+
+               }
+
+               while ( to !== - 1 && times[ to ] > endTime ) {
+
+                       -- to;
+
+               }
+
+               ++ to; // inclusive -> exclusive bound
+
+               if ( from !== 0 || to !== nKeys ) {
+
+                       // empty tracks are forbidden, so keep at least one keyframe
+                       if ( from >= to ) {
+
+                               to = Math.max( to, 1 );
+                               from = to - 1;
+
+                       }
+
+                       const stride = this.getValueSize();
+                       this.times = AnimationUtils.arraySlice( times, from, to );
+                       this.values = AnimationUtils.arraySlice( this.values, from * stride, to * stride );
+
+               }
+
+               return this;
+
+       },
+
+       // ensure we do not get a GarbageInGarbageOut situation, make sure tracks are at least minimally viable
+       validate: function () {
+
+               let valid = true;
+
+               const valueSize = this.getValueSize();
+               if ( valueSize - Math.floor( valueSize ) !== 0 ) {
+
+                       console.error( 'THREE.KeyframeTrack: Invalid value size in track.', this );
+                       valid = false;
+
+               }
+
+               const times = this.times,
+                       values = this.values,
+
+                       nKeys = times.length;
+
+               if ( nKeys === 0 ) {
+
+                       console.error( 'THREE.KeyframeTrack: Track is empty.', this );
+                       valid = false;
+
+               }
+
+               let prevTime = null;
+
+               for ( let i = 0; i !== nKeys; i ++ ) {
+
+                       const currTime = times[ i ];
+
+                       if ( typeof currTime === 'number' && isNaN( currTime ) ) {
+
+                               console.error( 'THREE.KeyframeTrack: Time is not a valid number.', this, i, currTime );
+                               valid = false;
+                               break;
+
+                       }
+
+                       if ( prevTime !== null && prevTime > currTime ) {
+
+                               console.error( 'THREE.KeyframeTrack: Out of order keys.', this, i, currTime, prevTime );
+                               valid = false;
+                               break;
+
+                       }
+
+                       prevTime = currTime;
+
+               }
+
+               if ( values !== undefined ) {
+
+                       if ( AnimationUtils.isTypedArray( values ) ) {
+
+                               for ( let i = 0, n = values.length; i !== n; ++ i ) {
+
+                                       const value = values[ i ];
+
+                                       if ( isNaN( value ) ) {
+
+                                               console.error( 'THREE.KeyframeTrack: Value is not a valid number.', this, i, value );
+                                               valid = false;
+                                               break;
+
+                                       }
+
+                               }
+
+                       }
+
+               }
+
+               return valid;
+
+       },
+
+       // removes equivalent sequential keys as common in morph target sequences
+       // (0,0,0,0,1,1,1,0,0,0,0,0,0,0) --> (0,0,1,1,0,0)
+       optimize: function () {
+
+               // times or values may be shared with other tracks, so overwriting is unsafe
+               const times = AnimationUtils.arraySlice( this.times ),
+                       values = AnimationUtils.arraySlice( this.values ),
+                       stride = this.getValueSize(),
+
+                       smoothInterpolation = this.getInterpolation() === InterpolateSmooth,
+
+                       lastIndex = times.length - 1;
+
+               let writeIndex = 1;
+
+               for ( let i = 1; i < lastIndex; ++ i ) {
+
+                       let keep = false;
+
+                       const time = times[ i ];
+                       const timeNext = times[ i + 1 ];
+
+                       // remove adjacent keyframes scheduled at the same time
+
+                       if ( time !== timeNext && ( i !== 1 || time !== times[ 0 ] ) ) {
+
+                               if ( ! smoothInterpolation ) {
+
+                                       // remove unnecessary keyframes same as their neighbors
+
+                                       const offset = i * stride,
+                                               offsetP = offset - stride,
+                                               offsetN = offset + stride;
+
+                                       for ( let j = 0; j !== stride; ++ j ) {
+
+                                               const value = values[ offset + j ];
+
+                                               if ( value !== values[ offsetP + j ] ||
+                                                       value !== values[ offsetN + j ] ) {
+
+                                                       keep = true;
+                                                       break;
+
+                                               }
+
+                                       }
+
+                               } else {
+
+                                       keep = true;
+
+                               }
+
+                       }
+
+                       // in-place compaction
+
+                       if ( keep ) {
+
+                               if ( i !== writeIndex ) {
+
+                                       times[ writeIndex ] = times[ i ];
+
+                                       const readOffset = i * stride,
+                                               writeOffset = writeIndex * stride;
+
+                                       for ( let j = 0; j !== stride; ++ j ) {
+
+                                               values[ writeOffset + j ] = values[ readOffset + j ];
+
+                                       }
+
+                               }
+
+                               ++ writeIndex;
+
+                       }
+
+               }
+
+               // flush last keyframe (compaction looks ahead)
+
+               if ( lastIndex > 0 ) {
+
+                       times[ writeIndex ] = times[ lastIndex ];
+
+                       for ( let readOffset = lastIndex * stride, writeOffset = writeIndex * stride, j = 0; j !== stride; ++ j ) {
+
+                               values[ writeOffset + j ] = values[ readOffset + j ];
+
+                       }
+
+                       ++ writeIndex;
+
+               }
+
+               if ( writeIndex !== times.length ) {
+
+                       this.times = AnimationUtils.arraySlice( times, 0, writeIndex );
+                       this.values = AnimationUtils.arraySlice( values, 0, writeIndex * stride );
+
+               } else {
+
+                       this.times = times;
+                       this.values = values;
+
+               }
+
+               return this;
+
+       },
+
+       clone: function () {
+
+               const times = AnimationUtils.arraySlice( this.times, 0 );
+               const values = AnimationUtils.arraySlice( this.values, 0 );
+
+               const TypedKeyframeTrack = this.constructor;
+               const track = new TypedKeyframeTrack( this.name, times, values );
+
+               // Interpolant argument to constructor is not saved, so copy the factory method directly.
+               track.createInterpolant = this.createInterpolant;
+
+               return track;
+
+       }
+
+} );
+
+/**
+ * A Track of Boolean keyframe values.
+ */
+
+function BooleanKeyframeTrack( name, times, values ) {
+
+       KeyframeTrack.call( this, name, times, values );
+
+}
+
+BooleanKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {
+
+       constructor: BooleanKeyframeTrack,
+
+       ValueTypeName: 'bool',
+       ValueBufferType: Array,
+
+       DefaultInterpolation: InterpolateDiscrete,
+
+       InterpolantFactoryMethodLinear: undefined,
+       InterpolantFactoryMethodSmooth: undefined
+
+       // Note: Actually this track could have a optimized / compressed
+       // representation of a single value and a custom interpolant that
+       // computes "firstValue ^ isOdd( index )".
+
+} );
+
+/**
+ * A Track of keyframe values that represent color.
+ */
+
+function ColorKeyframeTrack( name, times, values, interpolation ) {
+
+       KeyframeTrack.call( this, name, times, values, interpolation );
+
+}
+
+ColorKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {
+
+       constructor: ColorKeyframeTrack,
+
+       ValueTypeName: 'color'
+
+       // ValueBufferType is inherited
+
+       // DefaultInterpolation is inherited
+
+       // Note: Very basic implementation and nothing special yet.
+       // However, this is the place for color space parameterization.
+
+} );
+
+/**
+ * A Track of numeric keyframe values.
+ */
+
+function NumberKeyframeTrack( name, times, values, interpolation ) {
+
+       KeyframeTrack.call( this, name, times, values, interpolation );
+
+}
+
+NumberKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {
+
+       constructor: NumberKeyframeTrack,
+
+       ValueTypeName: 'number'
+
+       // ValueBufferType is inherited
+
+       // DefaultInterpolation is inherited
+
+} );
+
+/**
+ * Spherical linear unit quaternion interpolant.
+ */
+
+function QuaternionLinearInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) {
+
+       Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer );
+
+}
+
+QuaternionLinearInterpolant.prototype = Object.assign( Object.create( Interpolant.prototype ), {
+
+       constructor: QuaternionLinearInterpolant,
+
+       interpolate_: function ( i1, t0, t, t1 ) {
+
+               const result = this.resultBuffer,
+                       values = this.sampleValues,
+                       stride = this.valueSize,
+
+                       alpha = ( t - t0 ) / ( t1 - t0 );
+
+               let offset = i1 * stride;
+
+               for ( let end = offset + stride; offset !== end; offset += 4 ) {
+
+                       Quaternion.slerpFlat( result, 0, values, offset - stride, values, offset, alpha );
+
+               }
+
+               return result;
+
+       }
+
+} );
+
+/**
+ * A Track of quaternion keyframe values.
+ */
+
+function QuaternionKeyframeTrack( name, times, values, interpolation ) {
+
+       KeyframeTrack.call( this, name, times, values, interpolation );
+
+}
+
+QuaternionKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {
+
+       constructor: QuaternionKeyframeTrack,
+
+       ValueTypeName: 'quaternion',
+
+       // ValueBufferType is inherited
+
+       DefaultInterpolation: InterpolateLinear,
+
+       InterpolantFactoryMethodLinear: function ( result ) {
+
+               return new QuaternionLinearInterpolant( this.times, this.values, this.getValueSize(), result );
+
+       },
+
+       InterpolantFactoryMethodSmooth: undefined // not yet implemented
+
+} );
+
+/**
+ * A Track that interpolates Strings
+ */
+
+function StringKeyframeTrack( name, times, values, interpolation ) {
+
+       KeyframeTrack.call( this, name, times, values, interpolation );
+
+}
+
+StringKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {
+
+       constructor: StringKeyframeTrack,
+
+       ValueTypeName: 'string',
+       ValueBufferType: Array,
+
+       DefaultInterpolation: InterpolateDiscrete,
+
+       InterpolantFactoryMethodLinear: undefined,
+
+       InterpolantFactoryMethodSmooth: undefined
+
+} );
+
+/**
+ * A Track of vectored keyframe values.
+ */
+
+function VectorKeyframeTrack( name, times, values, interpolation ) {
+
+       KeyframeTrack.call( this, name, times, values, interpolation );
+
+}
+
+VectorKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {
+
+       constructor: VectorKeyframeTrack,
+
+       ValueTypeName: 'vector'
+
+       // ValueBufferType is inherited
+
+       // DefaultInterpolation is inherited
+
+} );
+
+function AnimationClip( name, duration = - 1, tracks, blendMode = NormalAnimationBlendMode ) {
+
+       this.name = name;
+       this.tracks = tracks;
+       this.duration = duration;
+       this.blendMode = blendMode;
+
+       this.uuid = MathUtils.generateUUID();
+
+       // this means it should figure out its duration by scanning the tracks
+       if ( this.duration < 0 ) {
+
+               this.resetDuration();
+
+       }
+
+}
+
+function getTrackTypeForValueTypeName( typeName ) {
+
+       switch ( typeName.toLowerCase() ) {
+
+               case 'scalar':
+               case 'double':
+               case 'float':
+               case 'number':
+               case 'integer':
+
+                       return NumberKeyframeTrack;
+
+               case 'vector':
+               case 'vector2':
+               case 'vector3':
+               case 'vector4':
+
+                       return VectorKeyframeTrack;
+
+               case 'color':
+
+                       return ColorKeyframeTrack;
+
+               case 'quaternion':
+
+                       return QuaternionKeyframeTrack;
+
+               case 'bool':
+               case 'boolean':
+
+                       return BooleanKeyframeTrack;
+
+               case 'string':
+
+                       return StringKeyframeTrack;
+
+       }
+
+       throw new Error( 'THREE.KeyframeTrack: Unsupported typeName: ' + typeName );
+
+}
+
+function parseKeyframeTrack( json ) {
+
+       if ( json.type === undefined ) {
+
+               throw new Error( 'THREE.KeyframeTrack: track type undefined, can not parse' );
+
+       }
+
+       const trackType = getTrackTypeForValueTypeName( json.type );
+
+       if ( json.times === undefined ) {
+
+               const times = [], values = [];
+
+               AnimationUtils.flattenJSON( json.keys, times, values, 'value' );
+
+               json.times = times;
+               json.values = values;
+
+       }
+
+       // derived classes can define a static parse method
+       if ( trackType.parse !== undefined ) {
+
+               return trackType.parse( json );
+
+       } else {
+
+               // by default, we assume a constructor compatible with the base
+               return new trackType( json.name, json.times, json.values, json.interpolation );
+
+       }
+
+}
+
+Object.assign( AnimationClip, {
+
+       parse: function ( json ) {
+
+               const tracks = [],
+                       jsonTracks = json.tracks,
+                       frameTime = 1.0 / ( json.fps || 1.0 );
+
+               for ( let i = 0, n = jsonTracks.length; i !== n; ++ i ) {
+
+                       tracks.push( parseKeyframeTrack( jsonTracks[ i ] ).scale( frameTime ) );
+
+               }
+
+               const clip = new AnimationClip( json.name, json.duration, tracks, json.blendMode );
+               clip.uuid = json.uuid;
+
+               return clip;
+
+       },
+
+       toJSON: function ( clip ) {
+
+               const tracks = [],
+                       clipTracks = clip.tracks;
+
+               const json = {
+
+                       'name': clip.name,
+                       'duration': clip.duration,
+                       'tracks': tracks,
+                       'uuid': clip.uuid,
+                       'blendMode': clip.blendMode
+
+               };
+
+               for ( let i = 0, n = clipTracks.length; i !== n; ++ i ) {
+
+                       tracks.push( KeyframeTrack.toJSON( clipTracks[ i ] ) );
+
+               }
+
+               return json;
+
+       },
+
+       CreateFromMorphTargetSequence: function ( name, morphTargetSequence, fps, noLoop ) {
+
+               const numMorphTargets = morphTargetSequence.length;
+               const tracks = [];
+
+               for ( let i = 0; i < numMorphTargets; i ++ ) {
+
+                       let times = [];
+                       let values = [];
+
+                       times.push(
+                               ( i + numMorphTargets - 1 ) % numMorphTargets,
+                               i,
+                               ( i + 1 ) % numMorphTargets );
+
+                       values.push( 0, 1, 0 );
+
+                       const order = AnimationUtils.getKeyframeOrder( times );
+                       times = AnimationUtils.sortedArray( times, 1, order );
+                       values = AnimationUtils.sortedArray( values, 1, order );
+
+                       // if there is a key at the first frame, duplicate it as the
+                       // last frame as well for perfect loop.
+                       if ( ! noLoop && times[ 0 ] === 0 ) {
+
+                               times.push( numMorphTargets );
+                               values.push( values[ 0 ] );
+
+                       }
+
+                       tracks.push(
+                               new NumberKeyframeTrack(
+                                       '.morphTargetInfluences[' + morphTargetSequence[ i ].name + ']',
+                                       times, values
+                               ).scale( 1.0 / fps ) );
+
+               }
+
+               return new AnimationClip( name, - 1, tracks );
+
+       },
+
+       findByName: function ( objectOrClipArray, name ) {
+
+               let clipArray = objectOrClipArray;
+
+               if ( ! Array.isArray( objectOrClipArray ) ) {
+
+                       const o = objectOrClipArray;
+                       clipArray = o.geometry && o.geometry.animations || o.animations;
+
+               }
+
+               for ( let i = 0; i < clipArray.length; i ++ ) {
+
+                       if ( clipArray[ i ].name === name ) {
+
+                               return clipArray[ i ];
+
+                       }
+
+               }
+
+               return null;
+
+       },
+
+       CreateClipsFromMorphTargetSequences: function ( morphTargets, fps, noLoop ) {
+
+               const animationToMorphTargets = {};
+
+               // tested with https://regex101.com/ on trick sequences
+               // such flamingo_flyA_003, flamingo_run1_003, crdeath0059
+               const pattern = /^([\w-]*?)([\d]+)$/;
+
+               // sort morph target names into animation groups based
+               // patterns like Walk_001, Walk_002, Run_001, Run_002
+               for ( let i = 0, il = morphTargets.length; i < il; i ++ ) {
+
+                       const morphTarget = morphTargets[ i ];
+                       const parts = morphTarget.name.match( pattern );
+
+                       if ( parts && parts.length > 1 ) {
+
+                               const name = parts[ 1 ];
+
+                               let animationMorphTargets = animationToMorphTargets[ name ];
+
+                               if ( ! animationMorphTargets ) {
+
+                                       animationToMorphTargets[ name ] = animationMorphTargets = [];
+
+                               }
+
+                               animationMorphTargets.push( morphTarget );
+
+                       }
+
+               }
+
+               const clips = [];
+
+               for ( const name in animationToMorphTargets ) {
+
+                       clips.push( AnimationClip.CreateFromMorphTargetSequence( name, animationToMorphTargets[ name ], fps, noLoop ) );
+
+               }
+
+               return clips;
+
+       },
+
+       // parse the animation.hierarchy format
+       parseAnimation: function ( animation, bones ) {
+
+               if ( ! animation ) {
+
+                       console.error( 'THREE.AnimationClip: No animation in JSONLoader data.' );
+                       return null;
+
+               }
+
+               const addNonemptyTrack = function ( trackType, trackName, animationKeys, propertyName, destTracks ) {
+
+                       // only return track if there are actually keys.
+                       if ( animationKeys.length !== 0 ) {
+
+                               const times = [];
+                               const values = [];
+
+                               AnimationUtils.flattenJSON( animationKeys, times, values, propertyName );
+
+                               // empty keys are filtered out, so check again
+                               if ( times.length !== 0 ) {
+
+                                       destTracks.push( new trackType( trackName, times, values ) );
+
+                               }
+
+                       }
+
+               };
+
+               const tracks = [];
+
+               const clipName = animation.name || 'default';
+               const fps = animation.fps || 30;
+               const blendMode = animation.blendMode;
+
+               // automatic length determination in AnimationClip.
+               let duration = animation.length || - 1;
+
+               const hierarchyTracks = animation.hierarchy || [];
+
+               for ( let h = 0; h < hierarchyTracks.length; h ++ ) {
+
+                       const animationKeys = hierarchyTracks[ h ].keys;
+
+                       // skip empty tracks
+                       if ( ! animationKeys || animationKeys.length === 0 ) continue;
+
+                       // process morph targets
+                       if ( animationKeys[ 0 ].morphTargets ) {
+
+                               // figure out all morph targets used in this track
+                               const morphTargetNames = {};
+
+                               let k;
+
+                               for ( k = 0; k < animationKeys.length; k ++ ) {
+
+                                       if ( animationKeys[ k ].morphTargets ) {
+
+                                               for ( let m = 0; m < animationKeys[ k ].morphTargets.length; m ++ ) {
+
+                                                       morphTargetNames[ animationKeys[ k ].morphTargets[ m ] ] = - 1;
+
+                                               }
+
+                                       }
+
+                               }
+
+                               // create a track for each morph target with all zero
+                               // morphTargetInfluences except for the keys in which
+                               // the morphTarget is named.
+                               for ( const morphTargetName in morphTargetNames ) {
+
+                                       const times = [];
+                                       const values = [];
+
+                                       for ( let m = 0; m !== animationKeys[ k ].morphTargets.length; ++ m ) {
+
+                                               const animationKey = animationKeys[ k ];
+
+                                               times.push( animationKey.time );
+                                               values.push( ( animationKey.morphTarget === morphTargetName ) ? 1 : 0 );
+
+                                       }
+
+                                       tracks.push( new NumberKeyframeTrack( '.morphTargetInfluence[' + morphTargetName + ']', times, values ) );
+
+                               }
+
+                               duration = morphTargetNames.length * ( fps || 1.0 );
+
+                       } else {
+
+                               // ...assume skeletal animation
+
+                               const boneName = '.bones[' + bones[ h ].name + ']';
+
+                               addNonemptyTrack(
+                                       VectorKeyframeTrack, boneName + '.position',
+                                       animationKeys, 'pos', tracks );
+
+                               addNonemptyTrack(
+                                       QuaternionKeyframeTrack, boneName + '.quaternion',
+                                       animationKeys, 'rot', tracks );
+
+                               addNonemptyTrack(
+                                       VectorKeyframeTrack, boneName + '.scale',
+                                       animationKeys, 'scl', tracks );
+
+                       }
+
+               }
+
+               if ( tracks.length === 0 ) {
+
+                       return null;
+
+               }
+
+               const clip = new AnimationClip( clipName, duration, tracks, blendMode );
+
+               return clip;
+
+       }
+
+} );
+
+Object.assign( AnimationClip.prototype, {
+
+       resetDuration: function () {
+
+               const tracks = this.tracks;
+               let duration = 0;
+
+               for ( let i = 0, n = tracks.length; i !== n; ++ i ) {
+
+                       const track = this.tracks[ i ];
+
+                       duration = Math.max( duration, track.times[ track.times.length - 1 ] );
+
+               }
+
+               this.duration = duration;
+
+               return this;
+
+       },
+
+       trim: function () {
+
+               for ( let i = 0; i < this.tracks.length; i ++ ) {
+
+                       this.tracks[ i ].trim( 0, this.duration );
+
+               }
+
+               return this;
+
+       },
+
+       validate: function () {
+
+               let valid = true;
+
+               for ( let i = 0; i < this.tracks.length; i ++ ) {
+
+                       valid = valid && this.tracks[ i ].validate();
+
+               }
+
+               return valid;
+
+       },
+
+       optimize: function () {
+
+               for ( let i = 0; i < this.tracks.length; i ++ ) {
+
+                       this.tracks[ i ].optimize();
+
+               }
+
+               return this;
+
+       },
+
+       clone: function () {
+
+               const tracks = [];
+
+               for ( let i = 0; i < this.tracks.length; i ++ ) {
+
+                       tracks.push( this.tracks[ i ].clone() );
+
+               }
+
+               return new AnimationClip( this.name, this.duration, tracks, this.blendMode );
+
+       },
+
+       toJSON: function () {
+
+               return AnimationClip.toJSON( this );
+
+       }
+
+} );
+
+const Cache = {
+
+       enabled: false,
+
+       files: {},
+
+       add: function ( key, file ) {
+
+               if ( this.enabled === false ) return;
+
+               // console.log( 'THREE.Cache', 'Adding key:', key );
+
+               this.files[ key ] = file;
+
+       },
+
+       get: function ( key ) {
+
+               if ( this.enabled === false ) return;
+
+               // console.log( 'THREE.Cache', 'Checking key:', key );
+
+               return this.files[ key ];
+
+       },
+
+       remove: function ( key ) {
+
+               delete this.files[ key ];
+
+       },
+
+       clear: function () {
+
+               this.files = {};
+
+       }
+
+};
+
+function LoadingManager( onLoad, onProgress, onError ) {
+
+       const scope = this;
+
+       let isLoading = false;
+       let itemsLoaded = 0;
+       let itemsTotal = 0;
+       let urlModifier = undefined;
+       const handlers = [];
+
+       // Refer to #5689 for the reason why we don't set .onStart
+       // in the constructor
+
+       this.onStart = undefined;
+       this.onLoad = onLoad;
+       this.onProgress = onProgress;
+       this.onError = onError;
+
+       this.itemStart = function ( url ) {
+
+               itemsTotal ++;
+
+               if ( isLoading === false ) {
+
+                       if ( scope.onStart !== undefined ) {
+
+                               scope.onStart( url, itemsLoaded, itemsTotal );
+
+                       }
+
+               }
+
+               isLoading = true;
+
+       };
+
+       this.itemEnd = function ( url ) {
+
+               itemsLoaded ++;
+
+               if ( scope.onProgress !== undefined ) {
+
+                       scope.onProgress( url, itemsLoaded, itemsTotal );
+
+               }
+
+               if ( itemsLoaded === itemsTotal ) {
+
+                       isLoading = false;
+
+                       if ( scope.onLoad !== undefined ) {
+
+                               scope.onLoad();
+
+                       }
+
+               }
+
+       };
+
+       this.itemError = function ( url ) {
+
+               if ( scope.onError !== undefined ) {
+
+                       scope.onError( url );
+
+               }
+
+       };
+
+       this.resolveURL = function ( url ) {
+
+               if ( urlModifier ) {
+
+                       return urlModifier( url );
+
+               }
+
+               return url;
+
+       };
+
+       this.setURLModifier = function ( transform ) {
+
+               urlModifier = transform;
+
+               return this;
+
+       };
+
+       this.addHandler = function ( regex, loader ) {
+
+               handlers.push( regex, loader );
+
+               return this;
+
+       };
+
+       this.removeHandler = function ( regex ) {
+
+               const index = handlers.indexOf( regex );
+
+               if ( index !== - 1 ) {
+
+                       handlers.splice( index, 2 );
+
+               }
+
+               return this;
+
+       };
+
+       this.getHandler = function ( file ) {
+
+               for ( let i = 0, l = handlers.length; i < l; i += 2 ) {
+
+                       const regex = handlers[ i ];
+                       const loader = handlers[ i + 1 ];
+
+                       if ( regex.global ) regex.lastIndex = 0; // see #17920
+
+                       if ( regex.test( file ) ) {
+
+                               return loader;
+
+                       }
+
+               }
+
+               return null;
+
+       };
+
+}
+
+const DefaultLoadingManager = new LoadingManager();
+
+function Loader( manager ) {
+
+       this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;
+
+       this.crossOrigin = 'anonymous';
+       this.withCredentials = false;
+       this.path = '';
+       this.resourcePath = '';
+       this.requestHeader = {};
+
+}
+
+Object.assign( Loader.prototype, {
+
+       load: function ( /* url, onLoad, onProgress, onError */ ) {},
+
+       loadAsync: function ( url, onProgress ) {
+
+               const scope = this;
+
+               return new Promise( function ( resolve, reject ) {
+
+                       scope.load( url, resolve, onProgress, reject );
+
+               } );
+
+       },
+
+       parse: function ( /* data */ ) {},
+
+       setCrossOrigin: function ( crossOrigin ) {
+
+               this.crossOrigin = crossOrigin;
+               return this;
+
+       },
+
+       setWithCredentials: function ( value ) {
+
+               this.withCredentials = value;
+               return this;
+
+       },
+
+       setPath: function ( path ) {
+
+               this.path = path;
+               return this;
+
+       },
+
+       setResourcePath: function ( resourcePath ) {
+
+               this.resourcePath = resourcePath;
+               return this;
+
+       },
+
+       setRequestHeader: function ( requestHeader ) {
+
+               this.requestHeader = requestHeader;
+               return this;
+
+       }
+
+} );
+
+const loading = {};
+
+function FileLoader( manager ) {
+
+       Loader.call( this, manager );
+
+}
+
+FileLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
+
+       constructor: FileLoader,
+
+       load: function ( url, onLoad, onProgress, onError ) {
+
+               if ( url === undefined ) url = '';
+
+               if ( this.path !== undefined ) url = this.path + url;
+
+               url = this.manager.resolveURL( url );
+
+               const scope = this;
+
+               const cached = Cache.get( url );
+
+               if ( cached !== undefined ) {
+
+                       scope.manager.itemStart( url );
+
+                       setTimeout( function () {
+
+                               if ( onLoad ) onLoad( cached );
+
+                               scope.manager.itemEnd( url );
+
+                       }, 0 );
+
+                       return cached;
+
+               }
+
+               // Check if request is duplicate
+
+               if ( loading[ url ] !== undefined ) {
+
+                       loading[ url ].push( {
+
+                               onLoad: onLoad,
+                               onProgress: onProgress,
+                               onError: onError
+
+                       } );
+
+                       return;
+
+               }
+
+               // Check for data: URI
+               const dataUriRegex = /^data:(.*?)(;base64)?,(.*)$/;
+               const dataUriRegexResult = url.match( dataUriRegex );
+               let request;
+
+               // Safari can not handle Data URIs through XMLHttpRequest so process manually
+               if ( dataUriRegexResult ) {
+
+                       const mimeType = dataUriRegexResult[ 1 ];
+                       const isBase64 = !! dataUriRegexResult[ 2 ];
+
+                       let data = dataUriRegexResult[ 3 ];
+                       data = decodeURIComponent( data );
+
+                       if ( isBase64 ) data = atob( data );
+
+                       try {
+
+                               let response;
+                               const responseType = ( this.responseType || '' ).toLowerCase();
+
+                               switch ( responseType ) {
+
+                                       case 'arraybuffer':
+                                       case 'blob':
+
+                                               const view = new Uint8Array( data.length );
+
+                                               for ( let i = 0; i < data.length; i ++ ) {
+
+                                                       view[ i ] = data.charCodeAt( i );
+
+                                               }
+
+                                               if ( responseType === 'blob' ) {
+
+                                                       response = new Blob( [ view.buffer ], { type: mimeType } );
+
+                                               } else {
+
+                                                       response = view.buffer;
+
+                                               }
+
+                                               break;
+
+                                       case 'document':
+
+                                               const parser = new DOMParser();
+                                               response = parser.parseFromString( data, mimeType );
+
+                                               break;
+
+                                       case 'json':
+
+                                               response = JSON.parse( data );
+
+                                               break;
+
+                                       default: // 'text' or other
+
+                                               response = data;
+
+                                               break;
+
+                               }
+
+                               // Wait for next browser tick like standard XMLHttpRequest event dispatching does
+                               setTimeout( function () {
+
+                                       if ( onLoad ) onLoad( response );
+
+                                       scope.manager.itemEnd( url );
+
+                               }, 0 );
+
+                       } catch ( error ) {
+
+                               // Wait for next browser tick like standard XMLHttpRequest event dispatching does
+                               setTimeout( function () {
+
+                                       if ( onError ) onError( error );
+
+                                       scope.manager.itemError( url );
+                                       scope.manager.itemEnd( url );
+
+                               }, 0 );
+
+                       }
+
+               } else {
+
+                       // Initialise array for duplicate requests
+
+                       loading[ url ] = [];
+
+                       loading[ url ].push( {
+
+                               onLoad: onLoad,
+                               onProgress: onProgress,
+                               onError: onError
+
+                       } );
+
+                       request = new XMLHttpRequest();
+
+                       request.open( 'GET', url, true );
+
+                       request.addEventListener( 'load', function ( event ) {
+
+                               const response = this.response;
+
+                               const callbacks = loading[ url ];
+
+                               delete loading[ url ];
+
+                               if ( this.status === 200 || this.status === 0 ) {
+
+                                       // Some browsers return HTTP Status 0 when using non-http protocol
+                                       // e.g. 'file://' or 'data://'. Handle as success.
+
+                                       if ( this.status === 0 ) console.warn( 'THREE.FileLoader: HTTP Status 0 received.' );
+
+                                       // Add to cache only on HTTP success, so that we do not cache
+                                       // error response bodies as proper responses to requests.
+                                       Cache.add( url, response );
+
+                                       for ( let i = 0, il = callbacks.length; i < il; i ++ ) {
+
+                                               const callback = callbacks[ i ];
+                                               if ( callback.onLoad ) callback.onLoad( response );
+
+                                       }
+
+                                       scope.manager.itemEnd( url );
+
+                               } else {
+
+                                       for ( let i = 0, il = callbacks.length; i < il; i ++ ) {
+
+                                               const callback = callbacks[ i ];
+                                               if ( callback.onError ) callback.onError( event );
+
+                                       }
+
+                                       scope.manager.itemError( url );
+                                       scope.manager.itemEnd( url );
+
+                               }
+
+                       }, false );
+
+                       request.addEventListener( 'progress', function ( event ) {
+
+                               const callbacks = loading[ url ];
+
+                               for ( let i = 0, il = callbacks.length; i < il; i ++ ) {
+
+                                       const callback = callbacks[ i ];
+                                       if ( callback.onProgress ) callback.onProgress( event );
+
+                               }
+
+                       }, false );
+
+                       request.addEventListener( 'error', function ( event ) {
+
+                               const callbacks = loading[ url ];
+
+                               delete loading[ url ];
+
+                               for ( let i = 0, il = callbacks.length; i < il; i ++ ) {
+
+                                       const callback = callbacks[ i ];
+                                       if ( callback.onError ) callback.onError( event );
+
+                               }
+
+                               scope.manager.itemError( url );
+                               scope.manager.itemEnd( url );
+
+                       }, false );
+
+                       request.addEventListener( 'abort', function ( event ) {
+
+                               const callbacks = loading[ url ];
+
+                               delete loading[ url ];
+
+                               for ( let i = 0, il = callbacks.length; i < il; i ++ ) {
+
+                                       const callback = callbacks[ i ];
+                                       if ( callback.onError ) callback.onError( event );
+
+                               }
+
+                               scope.manager.itemError( url );
+                               scope.manager.itemEnd( url );
+
+                       }, false );
+
+                       if ( this.responseType !== undefined ) request.responseType = this.responseType;
+                       if ( this.withCredentials !== undefined ) request.withCredentials = this.withCredentials;
+
+                       if ( request.overrideMimeType ) request.overrideMimeType( this.mimeType !== undefined ? this.mimeType : 'text/plain' );
+
+                       for ( const header in this.requestHeader ) {
+
+                               request.setRequestHeader( header, this.requestHeader[ header ] );
+
+                       }
+
+                       request.send( null );
+
+               }
+
+               scope.manager.itemStart( url );
+
+               return request;
+
+       },
+
+       setResponseType: function ( value ) {
+
+               this.responseType = value;
+               return this;
+
+       },
+
+       setMimeType: function ( value ) {
+
+               this.mimeType = value;
+               return this;
+
+       }
+
+} );
+
+function AnimationLoader( manager ) {
+
+       Loader.call( this, manager );
+
+}
+
+AnimationLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
+
+       constructor: AnimationLoader,
+
+       load: function ( url, onLoad, onProgress, onError ) {
+
+               const scope = this;
+
+               const loader = new FileLoader( scope.manager );
+               loader.setPath( scope.path );
+               loader.setRequestHeader( scope.requestHeader );
+               loader.setWithCredentials( scope.withCredentials );
+               loader.load( url, function ( text ) {
+
+                       try {
+
+                               onLoad( scope.parse( JSON.parse( text ) ) );
+
+                       } catch ( e ) {
+
+                               if ( onError ) {
+
+                                       onError( e );
+
+                               } else {
+
+                                       console.error( e );
+
+                               }
+
+                               scope.manager.itemError( url );
+
+                       }
+
+               }, onProgress, onError );
+
+       },
+
+       parse: function ( json ) {
+
+               const animations = [];
+
+               for ( let i = 0; i < json.length; i ++ ) {
+
+                       const clip = AnimationClip.parse( json[ i ] );
+
+                       animations.push( clip );
+
+               }
+
+               return animations;
+
+       }
+
+} );
+
+/**
+ * Abstract Base class to block based textures loader (dds, pvr, ...)
+ *
+ * Sub classes have to implement the parse() method which will be used in load().
+ */
+
+function CompressedTextureLoader( manager ) {
+
+       Loader.call( this, manager );
+
+}
+
+CompressedTextureLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
+
+       constructor: CompressedTextureLoader,
+
+       load: function ( url, onLoad, onProgress, onError ) {
+
+               const scope = this;
+
+               const images = [];
+
+               const texture = new CompressedTexture();
+
+               const loader = new FileLoader( this.manager );
+               loader.setPath( this.path );
+               loader.setResponseType( 'arraybuffer' );
+               loader.setRequestHeader( this.requestHeader );
+               loader.setWithCredentials( scope.withCredentials );
+
+               let loaded = 0;
+
+               function loadTexture( i ) {
+
+                       loader.load( url[ i ], function ( buffer ) {
+
+                               const texDatas = scope.parse( buffer, true );
+
+                               images[ i ] = {
+                                       width: texDatas.width,
+                                       height: texDatas.height,
+                                       format: texDatas.format,
+                                       mipmaps: texDatas.mipmaps
+                               };
+
+                               loaded += 1;
+
+                               if ( loaded === 6 ) {
+
+                                       if ( texDatas.mipmapCount === 1 ) texture.minFilter = LinearFilter;
+
+                                       texture.image = images;
+                                       texture.format = texDatas.format;
+                                       texture.needsUpdate = true;
+
+                                       if ( onLoad ) onLoad( texture );
+
+                               }
+
+                       }, onProgress, onError );
+
+               }
+
+               if ( Array.isArray( url ) ) {
+
+                       for ( let i = 0, il = url.length; i < il; ++ i ) {
+
+                               loadTexture( i );
+
+                       }
+
+               } else {
+
+                       // compressed cubemap texture stored in a single DDS file
+
+                       loader.load( url, function ( buffer ) {
+
+                               const texDatas = scope.parse( buffer, true );
+
+                               if ( texDatas.isCubemap ) {
+
+                                       const faces = texDatas.mipmaps.length / texDatas.mipmapCount;
+
+                                       for ( let f = 0; f < faces; f ++ ) {
+
+                                               images[ f ] = { mipmaps: [] };
+
+                                               for ( let i = 0; i < texDatas.mipmapCount; i ++ ) {
+
+                                                       images[ f ].mipmaps.push( texDatas.mipmaps[ f * texDatas.mipmapCount + i ] );
+                                                       images[ f ].format = texDatas.format;
+                                                       images[ f ].width = texDatas.width;
+                                                       images[ f ].height = texDatas.height;
+
+                                               }
+
+                                       }
+
+                                       texture.image = images;
+
+                               } else {
+
+                                       texture.image.width = texDatas.width;
+                                       texture.image.height = texDatas.height;
+                                       texture.mipmaps = texDatas.mipmaps;
+
+                               }
+
+                               if ( texDatas.mipmapCount === 1 ) {
+
+                                       texture.minFilter = LinearFilter;
+
+                               }
+
+                               texture.format = texDatas.format;
+                               texture.needsUpdate = true;
+
+                               if ( onLoad ) onLoad( texture );
+
+                       }, onProgress, onError );
+
+               }
+
+               return texture;
+
+       }
+
+} );
+
+function ImageLoader( manager ) {
+
+       Loader.call( this, manager );
+
+}
+
+ImageLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
+
+       constructor: ImageLoader,
+
+       load: function ( url, onLoad, onProgress, onError ) {
+
+               if ( this.path !== undefined ) url = this.path + url;
+
+               url = this.manager.resolveURL( url );
+
+               const scope = this;
+
+               const cached = Cache.get( url );
+
+               if ( cached !== undefined ) {
+
+                       scope.manager.itemStart( url );
+
+                       setTimeout( function () {
+
+                               if ( onLoad ) onLoad( cached );
+
+                               scope.manager.itemEnd( url );
+
+                       }, 0 );
+
+                       return cached;
+
+               }
+
+               const image = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'img' );
+
+               function onImageLoad() {
+
+                       image.removeEventListener( 'load', onImageLoad, false );
+                       image.removeEventListener( 'error', onImageError, false );
+
+                       Cache.add( url, this );
+
+                       if ( onLoad ) onLoad( this );
+
+                       scope.manager.itemEnd( url );
+
+               }
+
+               function onImageError( event ) {
+
+                       image.removeEventListener( 'load', onImageLoad, false );
+                       image.removeEventListener( 'error', onImageError, false );
+
+                       if ( onError ) onError( event );
+
+                       scope.manager.itemError( url );
+                       scope.manager.itemEnd( url );
+
+               }
+
+               image.addEventListener( 'load', onImageLoad, false );
+               image.addEventListener( 'error', onImageError, false );
+
+               if ( url.substr( 0, 5 ) !== 'data:' ) {
+
+                       if ( this.crossOrigin !== undefined ) image.crossOrigin = this.crossOrigin;
+
+               }
+
+               scope.manager.itemStart( url );
+
+               image.src = url;
+
+               return image;
+
+       }
+
+} );
+
+function CubeTextureLoader( manager ) {
+
+       Loader.call( this, manager );
+
+}
+
+CubeTextureLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
+
+       constructor: CubeTextureLoader,
+
+       load: function ( urls, onLoad, onProgress, onError ) {
+
+               const texture = new CubeTexture();
+
+               const loader = new ImageLoader( this.manager );
+               loader.setCrossOrigin( this.crossOrigin );
+               loader.setPath( this.path );
+
+               let loaded = 0;
+
+               function loadTexture( i ) {
+
+                       loader.load( urls[ i ], function ( image ) {
+
+                               texture.images[ i ] = image;
+
+                               loaded ++;
+
+                               if ( loaded === 6 ) {
+
+                                       texture.needsUpdate = true;
+
+                                       if ( onLoad ) onLoad( texture );
+
+                               }
+
+                       }, undefined, onError );
+
+               }
+
+               for ( let i = 0; i < urls.length; ++ i ) {
+
+                       loadTexture( i );
+
+               }
+
+               return texture;
+
+       }
+
+} );
+
+/**
+ * Abstract Base class to load generic binary textures formats (rgbe, hdr, ...)
+ *
+ * Sub classes have to implement the parse() method which will be used in load().
+ */
+
+function DataTextureLoader( manager ) {
+
+       Loader.call( this, manager );
+
+}
+
+DataTextureLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
+
+       constructor: DataTextureLoader,
+
+       load: function ( url, onLoad, onProgress, onError ) {
+
+               const scope = this;
+
+               const texture = new DataTexture();
+
+               const loader = new FileLoader( this.manager );
+               loader.setResponseType( 'arraybuffer' );
+               loader.setRequestHeader( this.requestHeader );
+               loader.setPath( this.path );
+               loader.setWithCredentials( scope.withCredentials );
+               loader.load( url, function ( buffer ) {
+
+                       const texData = scope.parse( buffer );
+
+                       if ( ! texData ) return;
+
+                       if ( texData.image !== undefined ) {
+
+                               texture.image = texData.image;
+
+                       } else if ( texData.data !== undefined ) {
+
+                               texture.image.width = texData.width;
+                               texture.image.height = texData.height;
+                               texture.image.data = texData.data;
+
+                       }
+
+                       texture.wrapS = texData.wrapS !== undefined ? texData.wrapS : ClampToEdgeWrapping;
+                       texture.wrapT = texData.wrapT !== undefined ? texData.wrapT : ClampToEdgeWrapping;
+
+                       texture.magFilter = texData.magFilter !== undefined ? texData.magFilter : LinearFilter;
+                       texture.minFilter = texData.minFilter !== undefined ? texData.minFilter : LinearFilter;
+
+                       texture.anisotropy = texData.anisotropy !== undefined ? texData.anisotropy : 1;
+
+                       if ( texData.encoding !== undefined ) {
+
+                               texture.encoding = texData.encoding;
+
+                       }
+
+                       if ( texData.flipY !== undefined ) {
+
+                               texture.flipY = texData.flipY;
+
+                       }
+
+                       if ( texData.format !== undefined ) {
+
+                               texture.format = texData.format;
+
+                       }
+
+                       if ( texData.type !== undefined ) {
+
+                               texture.type = texData.type;
+
+                       }
+
+                       if ( texData.mipmaps !== undefined ) {
+
+                               texture.mipmaps = texData.mipmaps;
+                               texture.minFilter = LinearMipmapLinearFilter; // presumably...
+
+                       }
+
+                       if ( texData.mipmapCount === 1 ) {
+
+                               texture.minFilter = LinearFilter;
+
+                       }
+
+                       texture.needsUpdate = true;
+
+                       if ( onLoad ) onLoad( texture, texData );
+
+               }, onProgress, onError );
+
+
+               return texture;
+
+       }
+
+} );
+
+function TextureLoader( manager ) {
+
+       Loader.call( this, manager );
+
+}
+
+TextureLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
+
+       constructor: TextureLoader,
+
+       load: function ( url, onLoad, onProgress, onError ) {
+
+               const texture = new Texture();
+
+               const loader = new ImageLoader( this.manager );
+               loader.setCrossOrigin( this.crossOrigin );
+               loader.setPath( this.path );
+
+               loader.load( url, function ( image ) {
+
+                       texture.image = image;
+
+                       // JPEGs can't have an alpha channel, so memory can be saved by storing them as RGB.
+                       const isJPEG = url.search( /\.jpe?g($|\?)/i ) > 0 || url.search( /^data\:image\/jpeg/ ) === 0;
+
+                       texture.format = isJPEG ? RGBFormat : RGBAFormat;
+                       texture.needsUpdate = true;
+
+                       if ( onLoad !== undefined ) {
+
+                               onLoad( texture );
+
+                       }
+
+               }, onProgress, onError );
+
+               return texture;
+
+       }
+
+} );
+
+/**
+ * Extensible curve object.
+ *
+ * Some common of curve methods:
+ * .getPoint( t, optionalTarget ), .getTangent( t, optionalTarget )
+ * .getPointAt( u, optionalTarget ), .getTangentAt( u, optionalTarget )
+ * .getPoints(), .getSpacedPoints()
+ * .getLength()
+ * .updateArcLengths()
+ *
+ * This following curves inherit from THREE.Curve:
+ *
+ * -- 2D curves --
+ * THREE.ArcCurve
+ * THREE.CubicBezierCurve
+ * THREE.EllipseCurve
+ * THREE.LineCurve
+ * THREE.QuadraticBezierCurve
+ * THREE.SplineCurve
+ *
+ * -- 3D curves --
+ * THREE.CatmullRomCurve3
+ * THREE.CubicBezierCurve3
+ * THREE.LineCurve3
+ * THREE.QuadraticBezierCurve3
+ *
+ * A series of curves can be represented as a THREE.CurvePath.
+ *
+ **/
+
+function Curve() {
+
+       this.type = 'Curve';
+
+       this.arcLengthDivisions = 200;
+
+}
+
+Object.assign( Curve.prototype, {
+
+       // Virtual base class method to overwrite and implement in subclasses
+       //      - t [0 .. 1]
+
+       getPoint: function ( /* t, optionalTarget */ ) {
+
+               console.warn( 'THREE.Curve: .getPoint() not implemented.' );
+               return null;
+
+       },
+
+       // Get point at relative position in curve according to arc length
+       // - u [0 .. 1]
+
+       getPointAt: function ( u, optionalTarget ) {
+
+               const t = this.getUtoTmapping( u );
+               return this.getPoint( t, optionalTarget );
+
+       },
+
+       // Get sequence of points using getPoint( t )
+
+       getPoints: function ( divisions = 5 ) {
+
+               const points = [];
+
+               for ( let d = 0; d <= divisions; d ++ ) {
+
+                       points.push( this.getPoint( d / divisions ) );
+
+               }
+
+               return points;
+
+       },
+
+       // Get sequence of points using getPointAt( u )
+
+       getSpacedPoints: function ( divisions = 5 ) {
+
+               const points = [];
+
+               for ( let d = 0; d <= divisions; d ++ ) {
+
+                       points.push( this.getPointAt( d / divisions ) );
+
+               }
+
+               return points;
+
+       },
+
+       // Get total curve arc length
+
+       getLength: function () {
+
+               const lengths = this.getLengths();
+               return lengths[ lengths.length - 1 ];
+
+       },
+
+       // Get list of cumulative segment lengths
+
+       getLengths: function ( divisions ) {
+
+               if ( divisions === undefined ) divisions = this.arcLengthDivisions;
+
+               if ( this.cacheArcLengths &&
+                       ( this.cacheArcLengths.length === divisions + 1 ) &&
+                       ! this.needsUpdate ) {
+
+                       return this.cacheArcLengths;
+
+               }
+
+               this.needsUpdate = false;
+
+               const cache = [];
+               let current, last = this.getPoint( 0 );
+               let sum = 0;
+
+               cache.push( 0 );
+
+               for ( let p = 1; p <= divisions; p ++ ) {
+
+                       current = this.getPoint( p / divisions );
+                       sum += current.distanceTo( last );
+                       cache.push( sum );
+                       last = current;
+
+               }
+
+               this.cacheArcLengths = cache;
+
+               return cache; // { sums: cache, sum: sum }; Sum is in the last element.
+
+       },
+
+       updateArcLengths: function () {
+
+               this.needsUpdate = true;
+               this.getLengths();
+
+       },
+
+       // Given u ( 0 .. 1 ), get a t to find p. This gives you points which are equidistant
+
+       getUtoTmapping: function ( u, distance ) {
+
+               const arcLengths = this.getLengths();
+
+               let i = 0;
+               const il = arcLengths.length;
+
+               let targetArcLength; // The targeted u distance value to get
+
+               if ( distance ) {
+
+                       targetArcLength = distance;
+
+               } else {
+
+                       targetArcLength = u * arcLengths[ il - 1 ];
+
+               }
+
+               // binary search for the index with largest value smaller than target u distance
+
+               let low = 0, high = il - 1, comparison;
+
+               while ( low <= high ) {
+
+                       i = Math.floor( low + ( high - low ) / 2 ); // less likely to overflow, though probably not issue here, JS doesn't really have integers, all numbers are floats
+
+                       comparison = arcLengths[ i ] - targetArcLength;
+
+                       if ( comparison < 0 ) {
+
+                               low = i + 1;
+
+                       } else if ( comparison > 0 ) {
+
+                               high = i - 1;
+
+                       } else {
+
+                               high = i;
+                               break;
+
+                               // DONE
+
+                       }
+
+               }
+
+               i = high;
+
+               if ( arcLengths[ i ] === targetArcLength ) {
+
+                       return i / ( il - 1 );
+
+               }
+
+               // we could get finer grain at lengths, or use simple interpolation between two points
+
+               const lengthBefore = arcLengths[ i ];
+               const lengthAfter = arcLengths[ i + 1 ];
+
+               const segmentLength = lengthAfter - lengthBefore;
+
+               // determine where we are between the 'before' and 'after' points
+
+               const segmentFraction = ( targetArcLength - lengthBefore ) / segmentLength;
+
+               // add that fractional amount to t
+
+               const t = ( i + segmentFraction ) / ( il - 1 );
+
+               return t;
+
+       },
+
+       // Returns a unit vector tangent at t
+       // In case any sub curve does not implement its tangent derivation,
+       // 2 points a small delta apart will be used to find its gradient
+       // which seems to give a reasonable approximation
+
+       getTangent: function ( t, optionalTarget ) {
+
+               const delta = 0.0001;
+               let t1 = t - delta;
+               let t2 = t + delta;
+
+               // Capping in case of danger
+
+               if ( t1 < 0 ) t1 = 0;
+               if ( t2 > 1 ) t2 = 1;
+
+               const pt1 = this.getPoint( t1 );
+               const pt2 = this.getPoint( t2 );
+
+               const tangent = optionalTarget || ( ( pt1.isVector2 ) ? new Vector2() : new Vector3() );
+
+               tangent.copy( pt2 ).sub( pt1 ).normalize();
+
+               return tangent;
+
+       },
+
+       getTangentAt: function ( u, optionalTarget ) {
+
+               const t = this.getUtoTmapping( u );
+               return this.getTangent( t, optionalTarget );
+
+       },
+
+       computeFrenetFrames: function ( segments, closed ) {
+
+               // see http://www.cs.indiana.edu/pub/techreports/TR425.pdf
+
+               const normal = new Vector3();
+
+               const tangents = [];
+               const normals = [];
+               const binormals = [];
+
+               const vec = new Vector3();
+               const mat = new Matrix4();
+
+               // compute the tangent vectors for each segment on the curve
+
+               for ( let i = 0; i <= segments; i ++ ) {
+
+                       const u = i / segments;
+
+                       tangents[ i ] = this.getTangentAt( u, new Vector3() );
+                       tangents[ i ].normalize();
+
+               }
+
+               // select an initial normal vector perpendicular to the first tangent vector,
+               // and in the direction of the minimum tangent xyz component
+
+               normals[ 0 ] = new Vector3();
+               binormals[ 0 ] = new Vector3();
+               let min = Number.MAX_VALUE;
+               const tx = Math.abs( tangents[ 0 ].x );
+               const ty = Math.abs( tangents[ 0 ].y );
+               const tz = Math.abs( tangents[ 0 ].z );
+
+               if ( tx <= min ) {
+
+                       min = tx;
+                       normal.set( 1, 0, 0 );
+
+               }
+
+               if ( ty <= min ) {
+
+                       min = ty;
+                       normal.set( 0, 1, 0 );
+
+               }
+
+               if ( tz <= min ) {
+
+                       normal.set( 0, 0, 1 );
+
+               }
+
+               vec.crossVectors( tangents[ 0 ], normal ).normalize();
+
+               normals[ 0 ].crossVectors( tangents[ 0 ], vec );
+               binormals[ 0 ].crossVectors( tangents[ 0 ], normals[ 0 ] );
+
+
+               // compute the slowly-varying normal and binormal vectors for each segment on the curve
+
+               for ( let i = 1; i <= segments; i ++ ) {
+
+                       normals[ i ] = normals[ i - 1 ].clone();
+
+                       binormals[ i ] = binormals[ i - 1 ].clone();
+
+                       vec.crossVectors( tangents[ i - 1 ], tangents[ i ] );
+
+                       if ( vec.length() > Number.EPSILON ) {
+
+                               vec.normalize();
+
+                               const theta = Math.acos( MathUtils.clamp( tangents[ i - 1 ].dot( tangents[ i ] ), - 1, 1 ) ); // clamp for floating pt errors
+
+                               normals[ i ].applyMatrix4( mat.makeRotationAxis( vec, theta ) );
+
+                       }
+
+                       binormals[ i ].crossVectors( tangents[ i ], normals[ i ] );
+
+               }
+
+               // if the curve is closed, postprocess the vectors so the first and last normal vectors are the same
+
+               if ( closed === true ) {
+
+                       let theta = Math.acos( MathUtils.clamp( normals[ 0 ].dot( normals[ segments ] ), - 1, 1 ) );
+                       theta /= segments;
+
+                       if ( tangents[ 0 ].dot( vec.crossVectors( normals[ 0 ], normals[ segments ] ) ) > 0 ) {
+
+                               theta = - theta;
+
+                       }
+
+                       for ( let i = 1; i <= segments; i ++ ) {
+
+                               // twist a little...
+                               normals[ i ].applyMatrix4( mat.makeRotationAxis( tangents[ i ], theta * i ) );
+                               binormals[ i ].crossVectors( tangents[ i ], normals[ i ] );
+
+                       }
+
+               }
+
+               return {
+                       tangents: tangents,
+                       normals: normals,
+                       binormals: binormals
+               };
+
+       },
+
+       clone: function () {
+
+               return new this.constructor().copy( this );
+
+       },
+
+       copy: function ( source ) {
+
+               this.arcLengthDivisions = source.arcLengthDivisions;
+
+               return this;
+
+       },
+
+       toJSON: function () {
+
+               const data = {
+                       metadata: {
+                               version: 4.5,
+                               type: 'Curve',
+                               generator: 'Curve.toJSON'
+                       }
+               };
+
+               data.arcLengthDivisions = this.arcLengthDivisions;
+               data.type = this.type;
+
+               return data;
+
+       },
+
+       fromJSON: function ( json ) {
+
+               this.arcLengthDivisions = json.arcLengthDivisions;
+
+               return this;
+
+       }
+
+} );
+
+function EllipseCurve( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) {
+
+       Curve.call( this );
+
+       this.type = 'EllipseCurve';
+
+       this.aX = aX || 0;
+       this.aY = aY || 0;
+
+       this.xRadius = xRadius || 1;
+       this.yRadius = yRadius || 1;
+
+       this.aStartAngle = aStartAngle || 0;
+       this.aEndAngle = aEndAngle || 2 * Math.PI;
+
+       this.aClockwise = aClockwise || false;
+
+       this.aRotation = aRotation || 0;
+
+}
+
+EllipseCurve.prototype = Object.create( Curve.prototype );
+EllipseCurve.prototype.constructor = EllipseCurve;
+
+EllipseCurve.prototype.isEllipseCurve = true;
+
+EllipseCurve.prototype.getPoint = function ( t, optionalTarget ) {
+
+       const point = optionalTarget || new Vector2();
+
+       const twoPi = Math.PI * 2;
+       let deltaAngle = this.aEndAngle - this.aStartAngle;
+       const samePoints = Math.abs( deltaAngle ) < Number.EPSILON;
+
+       // ensures that deltaAngle is 0 .. 2 PI
+       while ( deltaAngle < 0 ) deltaAngle += twoPi;
+       while ( deltaAngle > twoPi ) deltaAngle -= twoPi;
+
+       if ( deltaAngle < Number.EPSILON ) {
+
+               if ( samePoints ) {
+
+                       deltaAngle = 0;
+
+               } else {
+
+                       deltaAngle = twoPi;
+
+               }
+
+       }
+
+       if ( this.aClockwise === true && ! samePoints ) {
+
+               if ( deltaAngle === twoPi ) {
+
+                       deltaAngle = - twoPi;
+
+               } else {
+
+                       deltaAngle = deltaAngle - twoPi;
+
+               }
+
+       }
+
+       const angle = this.aStartAngle + t * deltaAngle;
+       let x = this.aX + this.xRadius * Math.cos( angle );
+       let y = this.aY + this.yRadius * Math.sin( angle );
+
+       if ( this.aRotation !== 0 ) {
+
+               const cos = Math.cos( this.aRotation );
+               const sin = Math.sin( this.aRotation );
+
+               const tx = x - this.aX;
+               const ty = y - this.aY;
+
+               // Rotate the point about the center of the ellipse.
+               x = tx * cos - ty * sin + this.aX;
+               y = tx * sin + ty * cos + this.aY;
+
+       }
+
+       return point.set( x, y );
+
+};
+
+EllipseCurve.prototype.copy = function ( source ) {
+
+       Curve.prototype.copy.call( this, source );
+
+       this.aX = source.aX;
+       this.aY = source.aY;
+
+       this.xRadius = source.xRadius;
+       this.yRadius = source.yRadius;
+
+       this.aStartAngle = source.aStartAngle;
+       this.aEndAngle = source.aEndAngle;
+
+       this.aClockwise = source.aClockwise;
+
+       this.aRotation = source.aRotation;
+
+       return this;
+
+};
+
+
+EllipseCurve.prototype.toJSON = function () {
+
+       const data = Curve.prototype.toJSON.call( this );
+
+       data.aX = this.aX;
+       data.aY = this.aY;
+
+       data.xRadius = this.xRadius;
+       data.yRadius = this.yRadius;
+
+       data.aStartAngle = this.aStartAngle;
+       data.aEndAngle = this.aEndAngle;
+
+       data.aClockwise = this.aClockwise;
+
+       data.aRotation = this.aRotation;
+
+       return data;
+
+};
+
+EllipseCurve.prototype.fromJSON = function ( json ) {
+
+       Curve.prototype.fromJSON.call( this, json );
+
+       this.aX = json.aX;
+       this.aY = json.aY;
+
+       this.xRadius = json.xRadius;
+       this.yRadius = json.yRadius;
+
+       this.aStartAngle = json.aStartAngle;
+       this.aEndAngle = json.aEndAngle;
+
+       this.aClockwise = json.aClockwise;
+
+       this.aRotation = json.aRotation;
+
+       return this;
+
+};
+
+function ArcCurve( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) {
+
+       EllipseCurve.call( this, aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise );
+
+       this.type = 'ArcCurve';
+
+}
+
+ArcCurve.prototype = Object.create( EllipseCurve.prototype );
+ArcCurve.prototype.constructor = ArcCurve;
+
+ArcCurve.prototype.isArcCurve = true;
+
+/**
+ * Centripetal CatmullRom Curve - which is useful for avoiding
+ * cusps and self-intersections in non-uniform catmull rom curves.
+ * http://www.cemyuksel.com/research/catmullrom_param/catmullrom.pdf
+ *
+ * curve.type accepts centripetal(default), chordal and catmullrom
+ * curve.tension is used for catmullrom which defaults to 0.5
+ */
+
+
+/*
+Based on an optimized c++ solution in
+ - http://stackoverflow.com/questions/9489736/catmull-rom-curve-with-no-cusps-and-no-self-intersections/
+ - http://ideone.com/NoEbVM
+
+This CubicPoly class could be used for reusing some variables and calculations,
+but for three.js curve use, it could be possible inlined and flatten into a single function call
+which can be placed in CurveUtils.
+*/
+
+function CubicPoly() {
+
+       let c0 = 0, c1 = 0, c2 = 0, c3 = 0;
+
+       /*
+        * Compute coefficients for a cubic polynomial
+        *   p(s) = c0 + c1*s + c2*s^2 + c3*s^3
+        * such that
+        *   p(0) = x0, p(1) = x1
+        *  and
+        *   p'(0) = t0, p'(1) = t1.
+        */
+       function init( x0, x1, t0, t1 ) {
+
+               c0 = x0;
+               c1 = t0;
+               c2 = - 3 * x0 + 3 * x1 - 2 * t0 - t1;
+               c3 = 2 * x0 - 2 * x1 + t0 + t1;
+
+       }
+
+       return {
+
+               initCatmullRom: function ( x0, x1, x2, x3, tension ) {
+
+                       init( x1, x2, tension * ( x2 - x0 ), tension * ( x3 - x1 ) );
+
+               },
+
+               initNonuniformCatmullRom: function ( x0, x1, x2, x3, dt0, dt1, dt2 ) {
+
+                       // compute tangents when parameterized in [t1,t2]
+                       let t1 = ( x1 - x0 ) / dt0 - ( x2 - x0 ) / ( dt0 + dt1 ) + ( x2 - x1 ) / dt1;
+                       let t2 = ( x2 - x1 ) / dt1 - ( x3 - x1 ) / ( dt1 + dt2 ) + ( x3 - x2 ) / dt2;
+
+                       // rescale tangents for parametrization in [0,1]
+                       t1 *= dt1;
+                       t2 *= dt1;
+
+                       init( x1, x2, t1, t2 );
+
+               },
+
+               calc: function ( t ) {
+
+                       const t2 = t * t;
+                       const t3 = t2 * t;
+                       return c0 + c1 * t + c2 * t2 + c3 * t3;
+
+               }
+
+       };
+
+}
+
+//
+
+const tmp = new Vector3();
+const px = new CubicPoly(), py = new CubicPoly(), pz = new CubicPoly();
+
+function CatmullRomCurve3( points = [], closed = false, curveType = 'centripetal', tension = 0.5 ) {
+
+       Curve.call( this );
+
+       this.type = 'CatmullRomCurve3';
+
+       this.points = points;
+       this.closed = closed;
+       this.curveType = curveType;
+       this.tension = tension;
+
+}
+
+CatmullRomCurve3.prototype = Object.create( Curve.prototype );
+CatmullRomCurve3.prototype.constructor = CatmullRomCurve3;
+
+CatmullRomCurve3.prototype.isCatmullRomCurve3 = true;
+
+CatmullRomCurve3.prototype.getPoint = function ( t, optionalTarget = new Vector3() ) {
+
+       const point = optionalTarget;
+
+       const points = this.points;
+       const l = points.length;
+
+       const p = ( l - ( this.closed ? 0 : 1 ) ) * t;
+       let intPoint = Math.floor( p );
+       let weight = p - intPoint;
+
+       if ( this.closed ) {
+
+               intPoint += intPoint > 0 ? 0 : ( Math.floor( Math.abs( intPoint ) / l ) + 1 ) * l;
+
+       } else if ( weight === 0 && intPoint === l - 1 ) {
+
+               intPoint = l - 2;
+               weight = 1;
+
+       }
+
+       let p0, p3; // 4 points (p1 & p2 defined below)
+
+       if ( this.closed || intPoint > 0 ) {
+
+               p0 = points[ ( intPoint - 1 ) % l ];
+
+       } else {
+
+               // extrapolate first point
+               tmp.subVectors( points[ 0 ], points[ 1 ] ).add( points[ 0 ] );
+               p0 = tmp;
+
+       }
+
+       const p1 = points[ intPoint % l ];
+       const p2 = points[ ( intPoint + 1 ) % l ];
+
+       if ( this.closed || intPoint + 2 < l ) {
+
+               p3 = points[ ( intPoint + 2 ) % l ];
+
+       } else {
+
+               // extrapolate last point
+               tmp.subVectors( points[ l - 1 ], points[ l - 2 ] ).add( points[ l - 1 ] );
+               p3 = tmp;
+
+       }
+
+       if ( this.curveType === 'centripetal' || this.curveType === 'chordal' ) {
+
+               // init Centripetal / Chordal Catmull-Rom
+               const pow = this.curveType === 'chordal' ? 0.5 : 0.25;
+               let dt0 = Math.pow( p0.distanceToSquared( p1 ), pow );
+               let dt1 = Math.pow( p1.distanceToSquared( p2 ), pow );
+               let dt2 = Math.pow( p2.distanceToSquared( p3 ), pow );
+
+               // safety check for repeated points
+               if ( dt1 < 1e-4 ) dt1 = 1.0;
+               if ( dt0 < 1e-4 ) dt0 = dt1;
+               if ( dt2 < 1e-4 ) dt2 = dt1;
+
+               px.initNonuniformCatmullRom( p0.x, p1.x, p2.x, p3.x, dt0, dt1, dt2 );
+               py.initNonuniformCatmullRom( p0.y, p1.y, p2.y, p3.y, dt0, dt1, dt2 );
+               pz.initNonuniformCatmullRom( p0.z, p1.z, p2.z, p3.z, dt0, dt1, dt2 );
+
+       } else if ( this.curveType === 'catmullrom' ) {
+
+               px.initCatmullRom( p0.x, p1.x, p2.x, p3.x, this.tension );
+               py.initCatmullRom( p0.y, p1.y, p2.y, p3.y, this.tension );
+               pz.initCatmullRom( p0.z, p1.z, p2.z, p3.z, this.tension );
+
+       }
+
+       point.set(
+               px.calc( weight ),
+               py.calc( weight ),
+               pz.calc( weight )
+       );
+
+       return point;
+
+};
+
+CatmullRomCurve3.prototype.copy = function ( source ) {
+
+       Curve.prototype.copy.call( this, source );
+
+       this.points = [];
+
+       for ( let i = 0, l = source.points.length; i < l; i ++ ) {
+
+               const point = source.points[ i ];
+
+               this.points.push( point.clone() );
+
+       }
+
+       this.closed = source.closed;
+       this.curveType = source.curveType;
+       this.tension = source.tension;
+
+       return this;
+
+};
+
+CatmullRomCurve3.prototype.toJSON = function () {
+
+       const data = Curve.prototype.toJSON.call( this );
+
+       data.points = [];
+
+       for ( let i = 0, l = this.points.length; i < l; i ++ ) {
+
+               const point = this.points[ i ];
+               data.points.push( point.toArray() );
+
+       }
+
+       data.closed = this.closed;
+       data.curveType = this.curveType;
+       data.tension = this.tension;
+
+       return data;
+
+};
+
+CatmullRomCurve3.prototype.fromJSON = function ( json ) {
+
+       Curve.prototype.fromJSON.call( this, json );
+
+       this.points = [];
+
+       for ( let i = 0, l = json.points.length; i < l; i ++ ) {
+
+               const point = json.points[ i ];
+               this.points.push( new Vector3().fromArray( point ) );
+
+       }
+
+       this.closed = json.closed;
+       this.curveType = json.curveType;
+       this.tension = json.tension;
+
+       return this;
+
+};
+
+/**
+ * Bezier Curves formulas obtained from
+ * http://en.wikipedia.org/wiki/Bézier_curve
+ */
+
+function CatmullRom( t, p0, p1, p2, p3 ) {
+
+       const v0 = ( p2 - p0 ) * 0.5;
+       const v1 = ( p3 - p1 ) * 0.5;
+       const t2 = t * t;
+       const t3 = t * t2;
+       return ( 2 * p1 - 2 * p2 + v0 + v1 ) * t3 + ( - 3 * p1 + 3 * p2 - 2 * v0 - v1 ) * t2 + v0 * t + p1;
+
+}
+
+//
+
+function QuadraticBezierP0( t, p ) {
+
+       const k = 1 - t;
+       return k * k * p;
+
+}
+
+function QuadraticBezierP1( t, p ) {
+
+       return 2 * ( 1 - t ) * t * p;
+
+}
+
+function QuadraticBezierP2( t, p ) {
+
+       return t * t * p;
+
+}
+
+function QuadraticBezier( t, p0, p1, p2 ) {
+
+       return QuadraticBezierP0( t, p0 ) + QuadraticBezierP1( t, p1 ) +
+               QuadraticBezierP2( t, p2 );
+
+}
+
+//
+
+function CubicBezierP0( t, p ) {
+
+       const k = 1 - t;
+       return k * k * k * p;
+
+}
+
+function CubicBezierP1( t, p ) {
+
+       const k = 1 - t;
+       return 3 * k * k * t * p;
+
+}
+
+function CubicBezierP2( t, p ) {
+
+       return 3 * ( 1 - t ) * t * t * p;
+
+}
+
+function CubicBezierP3( t, p ) {
+
+       return t * t * t * p;
+
+}
+
+function CubicBezier( t, p0, p1, p2, p3 ) {
+
+       return CubicBezierP0( t, p0 ) + CubicBezierP1( t, p1 ) + CubicBezierP2( t, p2 ) +
+               CubicBezierP3( t, p3 );
+
+}
+
+function CubicBezierCurve( v0 = new Vector2(), v1 = new Vector2(), v2 = new Vector2(), v3 = new Vector2() ) {
+
+       Curve.call( this );
+
+       this.type = 'CubicBezierCurve';
+
+       this.v0 = v0;
+       this.v1 = v1;
+       this.v2 = v2;
+       this.v3 = v3;
+
+}
+
+CubicBezierCurve.prototype = Object.create( Curve.prototype );
+CubicBezierCurve.prototype.constructor = CubicBezierCurve;
+
+CubicBezierCurve.prototype.isCubicBezierCurve = true;
+
+CubicBezierCurve.prototype.getPoint = function ( t, optionalTarget = new Vector2() ) {
+
+       const point = optionalTarget;
+
+       const v0 = this.v0, v1 = this.v1, v2 = this.v2, v3 = this.v3;
+
+       point.set(
+               CubicBezier( t, v0.x, v1.x, v2.x, v3.x ),
+               CubicBezier( t, v0.y, v1.y, v2.y, v3.y )
+       );
+
+       return point;
+
+};
+
+CubicBezierCurve.prototype.copy = function ( source ) {
+
+       Curve.prototype.copy.call( this, source );
+
+       this.v0.copy( source.v0 );
+       this.v1.copy( source.v1 );
+       this.v2.copy( source.v2 );
+       this.v3.copy( source.v3 );
+
+       return this;
+
+};
+
+CubicBezierCurve.prototype.toJSON = function () {
+
+       const data = Curve.prototype.toJSON.call( this );
+
+       data.v0 = this.v0.toArray();
+       data.v1 = this.v1.toArray();
+       data.v2 = this.v2.toArray();
+       data.v3 = this.v3.toArray();
+
+       return data;
+
+};
+
+CubicBezierCurve.prototype.fromJSON = function ( json ) {
+
+       Curve.prototype.fromJSON.call( this, json );
+
+       this.v0.fromArray( json.v0 );
+       this.v1.fromArray( json.v1 );
+       this.v2.fromArray( json.v2 );
+       this.v3.fromArray( json.v3 );
+
+       return this;
+
+};
+
+function CubicBezierCurve3( v0 = new Vector3(), v1 = new Vector3(), v2 = new Vector3(), v3 = new Vector3() ) {
+
+       Curve.call( this );
+
+       this.type = 'CubicBezierCurve3';
+
+       this.v0 = v0;
+       this.v1 = v1;
+       this.v2 = v2;
+       this.v3 = v3;
+
+}
+
+CubicBezierCurve3.prototype = Object.create( Curve.prototype );
+CubicBezierCurve3.prototype.constructor = CubicBezierCurve3;
+
+CubicBezierCurve3.prototype.isCubicBezierCurve3 = true;
+
+CubicBezierCurve3.prototype.getPoint = function ( t, optionalTarget = new Vector3() ) {
+
+       const point = optionalTarget;
+
+       const v0 = this.v0, v1 = this.v1, v2 = this.v2, v3 = this.v3;
+
+       point.set(
+               CubicBezier( t, v0.x, v1.x, v2.x, v3.x ),
+               CubicBezier( t, v0.y, v1.y, v2.y, v3.y ),
+               CubicBezier( t, v0.z, v1.z, v2.z, v3.z )
+       );
+
+       return point;
+
+};
+
+CubicBezierCurve3.prototype.copy = function ( source ) {
+
+       Curve.prototype.copy.call( this, source );
+
+       this.v0.copy( source.v0 );
+       this.v1.copy( source.v1 );
+       this.v2.copy( source.v2 );
+       this.v3.copy( source.v3 );
+
+       return this;
+
+};
+
+CubicBezierCurve3.prototype.toJSON = function () {
+
+       const data = Curve.prototype.toJSON.call( this );
+
+       data.v0 = this.v0.toArray();
+       data.v1 = this.v1.toArray();
+       data.v2 = this.v2.toArray();
+       data.v3 = this.v3.toArray();
+
+       return data;
+
+};
+
+CubicBezierCurve3.prototype.fromJSON = function ( json ) {
+
+       Curve.prototype.fromJSON.call( this, json );
+
+       this.v0.fromArray( json.v0 );
+       this.v1.fromArray( json.v1 );
+       this.v2.fromArray( json.v2 );
+       this.v3.fromArray( json.v3 );
+
+       return this;
+
+};
+
+function LineCurve( v1 = new Vector2(), v2 = new Vector2() ) {
+
+       Curve.call( this );
+
+       this.type = 'LineCurve';
+
+       this.v1 = v1;
+       this.v2 = v2;
+
+}
+
+LineCurve.prototype = Object.create( Curve.prototype );
+LineCurve.prototype.constructor = LineCurve;
+
+LineCurve.prototype.isLineCurve = true;
+
+LineCurve.prototype.getPoint = function ( t, optionalTarget = new Vector2() ) {
+
+       const point = optionalTarget;
+
+       if ( t === 1 ) {
+
+               point.copy( this.v2 );
+
+       } else {
+
+               point.copy( this.v2 ).sub( this.v1 );
+               point.multiplyScalar( t ).add( this.v1 );
+
+       }
+
+       return point;
+
+};
+
+// Line curve is linear, so we can overwrite default getPointAt
+
+LineCurve.prototype.getPointAt = function ( u, optionalTarget ) {
+
+       return this.getPoint( u, optionalTarget );
+
+};
+
+LineCurve.prototype.getTangent = function ( t, optionalTarget ) {
+
+       const tangent = optionalTarget || new Vector2();
+
+       tangent.copy( this.v2 ).sub( this.v1 ).normalize();
+
+       return tangent;
+
+};
+
+LineCurve.prototype.copy = function ( source ) {
+
+       Curve.prototype.copy.call( this, source );
+
+       this.v1.copy( source.v1 );
+       this.v2.copy( source.v2 );
+
+       return this;
+
+};
+
+LineCurve.prototype.toJSON = function () {
+
+       const data = Curve.prototype.toJSON.call( this );
+
+       data.v1 = this.v1.toArray();
+       data.v2 = this.v2.toArray();
+
+       return data;
+
+};
+
+LineCurve.prototype.fromJSON = function ( json ) {
+
+       Curve.prototype.fromJSON.call( this, json );
+
+       this.v1.fromArray( json.v1 );
+       this.v2.fromArray( json.v2 );
+
+       return this;
+
+};
+
+function LineCurve3( v1 = new Vector3(), v2 = new Vector3() ) {
+
+       Curve.call( this );
+
+       this.type = 'LineCurve3';
+
+       this.v1 = v1;
+       this.v2 = v2;
+
+}
+
+LineCurve3.prototype = Object.create( Curve.prototype );
+LineCurve3.prototype.constructor = LineCurve3;
+
+LineCurve3.prototype.isLineCurve3 = true;
+
+LineCurve3.prototype.getPoint = function ( t, optionalTarget = new Vector3() ) {
+
+       const point = optionalTarget;
+
+       if ( t === 1 ) {
+
+               point.copy( this.v2 );
+
+       } else {
+
+               point.copy( this.v2 ).sub( this.v1 );
+               point.multiplyScalar( t ).add( this.v1 );
+
+       }
+
+       return point;
+
+};
+
+// Line curve is linear, so we can overwrite default getPointAt
+
+LineCurve3.prototype.getPointAt = function ( u, optionalTarget ) {
+
+       return this.getPoint( u, optionalTarget );
+
+};
+
+LineCurve3.prototype.copy = function ( source ) {
+
+       Curve.prototype.copy.call( this, source );
+
+       this.v1.copy( source.v1 );
+       this.v2.copy( source.v2 );
+
+       return this;
+
+};
+
+LineCurve3.prototype.toJSON = function () {
+
+       const data = Curve.prototype.toJSON.call( this );
+
+       data.v1 = this.v1.toArray();
+       data.v2 = this.v2.toArray();
+
+       return data;
+
+};
+
+LineCurve3.prototype.fromJSON = function ( json ) {
+
+       Curve.prototype.fromJSON.call( this, json );
+
+       this.v1.fromArray( json.v1 );
+       this.v2.fromArray( json.v2 );
+
+       return this;
+
+};
+
+function QuadraticBezierCurve( v0 = new Vector2(), v1 = new Vector2(), v2 = new Vector2() ) {
+
+       Curve.call( this );
+
+       this.type = 'QuadraticBezierCurve';
+
+       this.v0 = v0;
+       this.v1 = v1;
+       this.v2 = v2;
+
+}
+
+QuadraticBezierCurve.prototype = Object.create( Curve.prototype );
+QuadraticBezierCurve.prototype.constructor = QuadraticBezierCurve;
+
+QuadraticBezierCurve.prototype.isQuadraticBezierCurve = true;
+
+QuadraticBezierCurve.prototype.getPoint = function ( t, optionalTarget = new Vector2() ) {
+
+       const point = optionalTarget;
+
+       const v0 = this.v0, v1 = this.v1, v2 = this.v2;
+
+       point.set(
+               QuadraticBezier( t, v0.x, v1.x, v2.x ),
+               QuadraticBezier( t, v0.y, v1.y, v2.y )
+       );
+
+       return point;
+
+};
+
+QuadraticBezierCurve.prototype.copy = function ( source ) {
+
+       Curve.prototype.copy.call( this, source );
+
+       this.v0.copy( source.v0 );
+       this.v1.copy( source.v1 );
+       this.v2.copy( source.v2 );
+
+       return this;
+
+};
+
+QuadraticBezierCurve.prototype.toJSON = function () {
+
+       const data = Curve.prototype.toJSON.call( this );
+
+       data.v0 = this.v0.toArray();
+       data.v1 = this.v1.toArray();
+       data.v2 = this.v2.toArray();
+
+       return data;
+
+};
+
+QuadraticBezierCurve.prototype.fromJSON = function ( json ) {
+
+       Curve.prototype.fromJSON.call( this, json );
+
+       this.v0.fromArray( json.v0 );
+       this.v1.fromArray( json.v1 );
+       this.v2.fromArray( json.v2 );
+
+       return this;
+
+};
+
+function QuadraticBezierCurve3( v0 = new Vector3(), v1 = new Vector3(), v2 = new Vector3() ) {
+
+       Curve.call( this );
+
+       this.type = 'QuadraticBezierCurve3';
+
+       this.v0 = v0;
+       this.v1 = v1;
+       this.v2 = v2;
+
+}
+
+QuadraticBezierCurve3.prototype = Object.create( Curve.prototype );
+QuadraticBezierCurve3.prototype.constructor = QuadraticBezierCurve3;
+
+QuadraticBezierCurve3.prototype.isQuadraticBezierCurve3 = true;
+
+QuadraticBezierCurve3.prototype.getPoint = function ( t, optionalTarget = new Vector3() ) {
+
+       const point = optionalTarget;
+
+       const v0 = this.v0, v1 = this.v1, v2 = this.v2;
+
+       point.set(
+               QuadraticBezier( t, v0.x, v1.x, v2.x ),
+               QuadraticBezier( t, v0.y, v1.y, v2.y ),
+               QuadraticBezier( t, v0.z, v1.z, v2.z )
+       );
+
+       return point;
+
+};
+
+QuadraticBezierCurve3.prototype.copy = function ( source ) {
+
+       Curve.prototype.copy.call( this, source );
+
+       this.v0.copy( source.v0 );
+       this.v1.copy( source.v1 );
+       this.v2.copy( source.v2 );
+
+       return this;
+
+};
+
+QuadraticBezierCurve3.prototype.toJSON = function () {
+
+       const data = Curve.prototype.toJSON.call( this );
+
+       data.v0 = this.v0.toArray();
+       data.v1 = this.v1.toArray();
+       data.v2 = this.v2.toArray();
+
+       return data;
+
+};
+
+QuadraticBezierCurve3.prototype.fromJSON = function ( json ) {
+
+       Curve.prototype.fromJSON.call( this, json );
+
+       this.v0.fromArray( json.v0 );
+       this.v1.fromArray( json.v1 );
+       this.v2.fromArray( json.v2 );
+
+       return this;
+
+};
+
+function SplineCurve( points = [] ) {
+
+       Curve.call( this );
+
+       this.type = 'SplineCurve';
+
+       this.points = points;
+
+}
+
+SplineCurve.prototype = Object.create( Curve.prototype );
+SplineCurve.prototype.constructor = SplineCurve;
+
+SplineCurve.prototype.isSplineCurve = true;
+
+SplineCurve.prototype.getPoint = function ( t, optionalTarget = new Vector2() ) {
+
+       const point = optionalTarget;
+
+       const points = this.points;
+       const p = ( points.length - 1 ) * t;
+
+       const intPoint = Math.floor( p );
+       const weight = p - intPoint;
+
+       const p0 = points[ intPoint === 0 ? intPoint : intPoint - 1 ];
+       const p1 = points[ intPoint ];
+       const p2 = points[ intPoint > points.length - 2 ? points.length - 1 : intPoint + 1 ];
+       const p3 = points[ intPoint > points.length - 3 ? points.length - 1 : intPoint + 2 ];
+
+       point.set(
+               CatmullRom( weight, p0.x, p1.x, p2.x, p3.x ),
+               CatmullRom( weight, p0.y, p1.y, p2.y, p3.y )
+       );
+
+       return point;
+
+};
+
+SplineCurve.prototype.copy = function ( source ) {
+
+       Curve.prototype.copy.call( this, source );
+
+       this.points = [];
+
+       for ( let i = 0, l = source.points.length; i < l; i ++ ) {
+
+               const point = source.points[ i ];
+
+               this.points.push( point.clone() );
+
+       }
+
+       return this;
+
+};
+
+SplineCurve.prototype.toJSON = function () {
+
+       const data = Curve.prototype.toJSON.call( this );
+
+       data.points = [];
+
+       for ( let i = 0, l = this.points.length; i < l; i ++ ) {
+
+               const point = this.points[ i ];
+               data.points.push( point.toArray() );
+
+       }
+
+       return data;
+
+};
+
+SplineCurve.prototype.fromJSON = function ( json ) {
+
+       Curve.prototype.fromJSON.call( this, json );
+
+       this.points = [];
+
+       for ( let i = 0, l = json.points.length; i < l; i ++ ) {
+
+               const point = json.points[ i ];
+               this.points.push( new Vector2().fromArray( point ) );
+
+       }
+
+       return this;
+
+};
+
+var Curves = /*#__PURE__*/Object.freeze({
+       __proto__: null,
+       ArcCurve: ArcCurve,
+       CatmullRomCurve3: CatmullRomCurve3,
+       CubicBezierCurve: CubicBezierCurve,
+       CubicBezierCurve3: CubicBezierCurve3,
+       EllipseCurve: EllipseCurve,
+       LineCurve: LineCurve,
+       LineCurve3: LineCurve3,
+       QuadraticBezierCurve: QuadraticBezierCurve,
+       QuadraticBezierCurve3: QuadraticBezierCurve3,
+       SplineCurve: SplineCurve
+});
+
+/**************************************************************
+ *     Curved Path - a curve path is simply a array of connected
+ *  curves, but retains the api of a curve
+ **************************************************************/
+
+function CurvePath() {
+
+       Curve.call( this );
+
+       this.type = 'CurvePath';
+
+       this.curves = [];
+       this.autoClose = false; // Automatically closes the path
+
+}
+
+CurvePath.prototype = Object.assign( Object.create( Curve.prototype ), {
+
+       constructor: CurvePath,
+
+       add: function ( curve ) {
+
+               this.curves.push( curve );
+
+       },
+
+       closePath: function () {
+
+               // Add a line curve if start and end of lines are not connected
+               const startPoint = this.curves[ 0 ].getPoint( 0 );
+               const endPoint = this.curves[ this.curves.length - 1 ].getPoint( 1 );
+
+               if ( ! startPoint.equals( endPoint ) ) {
+
+                       this.curves.push( new LineCurve( endPoint, startPoint ) );
+
+               }
+
+       },
+
+       // To get accurate point with reference to
+       // entire path distance at time t,
+       // following has to be done:
+
+       // 1. Length of each sub path have to be known
+       // 2. Locate and identify type of curve
+       // 3. Get t for the curve
+       // 4. Return curve.getPointAt(t')
+
+       getPoint: function ( t ) {
+
+               const d = t * this.getLength();
+               const curveLengths = this.getCurveLengths();
+               let i = 0;
+
+               // To think about boundaries points.
+
+               while ( i < curveLengths.length ) {
+
+                       if ( curveLengths[ i ] >= d ) {
+
+                               const diff = curveLengths[ i ] - d;
+                               const curve = this.curves[ i ];
+
+                               const segmentLength = curve.getLength();
+                               const u = segmentLength === 0 ? 0 : 1 - diff / segmentLength;
+
+                               return curve.getPointAt( u );
+
+                       }
+
+                       i ++;
+
+               }
+
+               return null;
+
+               // loop where sum != 0, sum > d , sum+1 <d
+
+       },
+
+       // We cannot use the default THREE.Curve getPoint() with getLength() because in
+       // THREE.Curve, getLength() depends on getPoint() but in THREE.CurvePath
+       // getPoint() depends on getLength
+
+       getLength: function () {
+
+               const lens = this.getCurveLengths();
+               return lens[ lens.length - 1 ];
+
+       },
+
+       // cacheLengths must be recalculated.
+       updateArcLengths: function () {
+
+               this.needsUpdate = true;
+               this.cacheLengths = null;
+               this.getCurveLengths();
+
+       },
+
+       // Compute lengths and cache them
+       // We cannot overwrite getLengths() because UtoT mapping uses it.
+
+       getCurveLengths: function () {
+
+               // We use cache values if curves and cache array are same length
+
+               if ( this.cacheLengths && this.cacheLengths.length === this.curves.length ) {
+
+                       return this.cacheLengths;
+
+               }
+
+               // Get length of sub-curve
+               // Push sums into cached array
+
+               const lengths = [];
+               let sums = 0;
+
+               for ( let i = 0, l = this.curves.length; i < l; i ++ ) {
+
+                       sums += this.curves[ i ].getLength();
+                       lengths.push( sums );
+
+               }
+
+               this.cacheLengths = lengths;
+
+               return lengths;
+
+       },
+
+       getSpacedPoints: function ( divisions = 40 ) {
+
+               const points = [];
+
+               for ( let i = 0; i <= divisions; i ++ ) {
+
+                       points.push( this.getPoint( i / divisions ) );
+
+               }
+
+               if ( this.autoClose ) {
+
+                       points.push( points[ 0 ] );
+
+               }
+
+               return points;
+
+       },
+
+       getPoints: function ( divisions = 12 ) {
+
+               const points = [];
+               let last;
+
+               for ( let i = 0, curves = this.curves; i < curves.length; i ++ ) {
+
+                       const curve = curves[ i ];
+                       const resolution = ( curve && curve.isEllipseCurve ) ? divisions * 2
+                               : ( curve && ( curve.isLineCurve || curve.isLineCurve3 ) ) ? 1
+                                       : ( curve && curve.isSplineCurve ) ? divisions * curve.points.length
+                                               : divisions;
+
+                       const pts = curve.getPoints( resolution );
+
+                       for ( let j = 0; j < pts.length; j ++ ) {
+
+                               const point = pts[ j ];
+
+                               if ( last && last.equals( point ) ) continue; // ensures no consecutive points are duplicates
+
+                               points.push( point );
+                               last = point;
+
+                       }
+
+               }
+
+               if ( this.autoClose && points.length > 1 && ! points[ points.length - 1 ].equals( points[ 0 ] ) ) {
+
+                       points.push( points[ 0 ] );
+
+               }
+
+               return points;
+
+       },
+
+       copy: function ( source ) {
+
+               Curve.prototype.copy.call( this, source );
+
+               this.curves = [];
+
+               for ( let i = 0, l = source.curves.length; i < l; i ++ ) {
+
+                       const curve = source.curves[ i ];
+
+                       this.curves.push( curve.clone() );
+
+               }
+
+               this.autoClose = source.autoClose;
+
+               return this;
+
+       },
+
+       toJSON: function () {
+
+               const data = Curve.prototype.toJSON.call( this );
+
+               data.autoClose = this.autoClose;
+               data.curves = [];
+
+               for ( let i = 0, l = this.curves.length; i < l; i ++ ) {
+
+                       const curve = this.curves[ i ];
+                       data.curves.push( curve.toJSON() );
+
+               }
+
+               return data;
+
+       },
+
+       fromJSON: function ( json ) {
+
+               Curve.prototype.fromJSON.call( this, json );
+
+               this.autoClose = json.autoClose;
+               this.curves = [];
+
+               for ( let i = 0, l = json.curves.length; i < l; i ++ ) {
+
+                       const curve = json.curves[ i ];
+                       this.curves.push( new Curves[ curve.type ]().fromJSON( curve ) );
+
+               }
+
+               return this;
+
+       }
+
+} );
+
+function Path( points ) {
+
+       CurvePath.call( this );
+
+       this.type = 'Path';
+
+       this.currentPoint = new Vector2();
+
+       if ( points ) {
+
+               this.setFromPoints( points );
+
+       }
+
+}
+
+Path.prototype = Object.assign( Object.create( CurvePath.prototype ), {
+
+       constructor: Path,
+
+       setFromPoints: function ( points ) {
+
+               this.moveTo( points[ 0 ].x, points[ 0 ].y );
+
+               for ( let i = 1, l = points.length; i < l; i ++ ) {
+
+                       this.lineTo( points[ i ].x, points[ i ].y );
+
+               }
+
+               return this;
+
+       },
+
+       moveTo: function ( x, y ) {
+
+               this.currentPoint.set( x, y ); // TODO consider referencing vectors instead of copying?
+
+               return this;
+
+       },
+
+       lineTo: function ( x, y ) {
+
+               const curve = new LineCurve( this.currentPoint.clone(), new Vector2( x, y ) );
+               this.curves.push( curve );
+
+               this.currentPoint.set( x, y );
+
+               return this;
+
+       },
+
+       quadraticCurveTo: function ( aCPx, aCPy, aX, aY ) {
+
+               const curve = new QuadraticBezierCurve(
+                       this.currentPoint.clone(),
+                       new Vector2( aCPx, aCPy ),
+                       new Vector2( aX, aY )
+               );
+
+               this.curves.push( curve );
+
+               this.currentPoint.set( aX, aY );
+
+               return this;
+
+       },
+
+       bezierCurveTo: function ( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) {
+
+               const curve = new CubicBezierCurve(
+                       this.currentPoint.clone(),
+                       new Vector2( aCP1x, aCP1y ),
+                       new Vector2( aCP2x, aCP2y ),
+                       new Vector2( aX, aY )
+               );
+
+               this.curves.push( curve );
+
+               this.currentPoint.set( aX, aY );
+
+               return this;
+
+       },
+
+       splineThru: function ( pts /*Array of Vector*/ ) {
+
+               const npts = [ this.currentPoint.clone() ].concat( pts );
+
+               const curve = new SplineCurve( npts );
+               this.curves.push( curve );
+
+               this.currentPoint.copy( pts[ pts.length - 1 ] );
+
+               return this;
+
+       },
+
+       arc: function ( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) {
+
+               const x0 = this.currentPoint.x;
+               const y0 = this.currentPoint.y;
+
+               this.absarc( aX + x0, aY + y0, aRadius,
+                       aStartAngle, aEndAngle, aClockwise );
+
+               return this;
+
+       },
+
+       absarc: function ( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) {
+
+               this.absellipse( aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise );
+
+               return this;
+
+       },
+
+       ellipse: function ( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) {
+
+               const x0 = this.currentPoint.x;
+               const y0 = this.currentPoint.y;
+
+               this.absellipse( aX + x0, aY + y0, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation );
+
+               return this;
+
+       },
+
+       absellipse: function ( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) {
+
+               const curve = new EllipseCurve( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation );
+
+               if ( this.curves.length > 0 ) {
+
+                       // if a previous curve is present, attempt to join
+                       const firstPoint = curve.getPoint( 0 );
+
+                       if ( ! firstPoint.equals( this.currentPoint ) ) {
+
+                               this.lineTo( firstPoint.x, firstPoint.y );
+
+                       }
+
+               }
+
+               this.curves.push( curve );
+
+               const lastPoint = curve.getPoint( 1 );
+               this.currentPoint.copy( lastPoint );
+
+               return this;
+
+       },
+
+       copy: function ( source ) {
+
+               CurvePath.prototype.copy.call( this, source );
+
+               this.currentPoint.copy( source.currentPoint );
+
+               return this;
+
+       },
+
+       toJSON: function () {
+
+               const data = CurvePath.prototype.toJSON.call( this );
+
+               data.currentPoint = this.currentPoint.toArray();
+
+               return data;
+
+       },
+
+       fromJSON: function ( json ) {
+
+               CurvePath.prototype.fromJSON.call( this, json );
+
+               this.currentPoint.fromArray( json.currentPoint );
+
+               return this;
+
+       }
+
+} );
+
+function Shape( points ) {
+
+       Path.call( this, points );
+
+       this.uuid = MathUtils.generateUUID();
+
+       this.type = 'Shape';
+
+       this.holes = [];
+
+}
+
+Shape.prototype = Object.assign( Object.create( Path.prototype ), {
+
+       constructor: Shape,
+
+       getPointsHoles: function ( divisions ) {
+
+               const holesPts = [];
+
+               for ( let i = 0, l = this.holes.length; i < l; i ++ ) {
+
+                       holesPts[ i ] = this.holes[ i ].getPoints( divisions );
+
+               }
+
+               return holesPts;
+
+       },
+
+       // get points of shape and holes (keypoints based on segments parameter)
+
+       extractPoints: function ( divisions ) {
+
+               return {
+
+                       shape: this.getPoints( divisions ),
+                       holes: this.getPointsHoles( divisions )
+
+               };
+
+       },
+
+       copy: function ( source ) {
+
+               Path.prototype.copy.call( this, source );
+
+               this.holes = [];
+
+               for ( let i = 0, l = source.holes.length; i < l; i ++ ) {
+
+                       const hole = source.holes[ i ];
+
+                       this.holes.push( hole.clone() );
+
+               }
+
+               return this;
+
+       },
+
+       toJSON: function () {
+
+               const data = Path.prototype.toJSON.call( this );
+
+               data.uuid = this.uuid;
+               data.holes = [];
+
+               for ( let i = 0, l = this.holes.length; i < l; i ++ ) {
+
+                       const hole = this.holes[ i ];
+                       data.holes.push( hole.toJSON() );
+
+               }
+
+               return data;
+
+       },
+
+       fromJSON: function ( json ) {
+
+               Path.prototype.fromJSON.call( this, json );
+
+               this.uuid = json.uuid;
+               this.holes = [];
+
+               for ( let i = 0, l = json.holes.length; i < l; i ++ ) {
+
+                       const hole = json.holes[ i ];
+                       this.holes.push( new Path().fromJSON( hole ) );
+
+               }
+
+               return this;
+
+       }
+
+} );
+
+function Light( color, intensity = 1 ) {
+
+       Object3D.call( this );
+
+       this.type = 'Light';
+
+       this.color = new Color( color );
+       this.intensity = intensity;
+
+}
+
+Light.prototype = Object.assign( Object.create( Object3D.prototype ), {
+
+       constructor: Light,
+
+       isLight: true,
+
+       copy: function ( source ) {
+
+               Object3D.prototype.copy.call( this, source );
+
+               this.color.copy( source.color );
+               this.intensity = source.intensity;
+
+               return this;
+
+       },
+
+       toJSON: function ( meta ) {
+
+               const data = Object3D.prototype.toJSON.call( this, meta );
+
+               data.object.color = this.color.getHex();
+               data.object.intensity = this.intensity;
+
+               if ( this.groundColor !== undefined ) data.object.groundColor = this.groundColor.getHex();
+
+               if ( this.distance !== undefined ) data.object.distance = this.distance;
+               if ( this.angle !== undefined ) data.object.angle = this.angle;
+               if ( this.decay !== undefined ) data.object.decay = this.decay;
+               if ( this.penumbra !== undefined ) data.object.penumbra = this.penumbra;
+
+               if ( this.shadow !== undefined ) data.object.shadow = this.shadow.toJSON();
+
+               return data;
+
+       }
+
+} );
+
+function HemisphereLight( skyColor, groundColor, intensity ) {
+
+       Light.call( this, skyColor, intensity );
+
+       this.type = 'HemisphereLight';
+
+       this.position.copy( Object3D.DefaultUp );
+       this.updateMatrix();
+
+       this.groundColor = new Color( groundColor );
+
+}
+
+HemisphereLight.prototype = Object.assign( Object.create( Light.prototype ), {
+
+       constructor: HemisphereLight,
+
+       isHemisphereLight: true,
+
+       copy: function ( source ) {
+
+               Light.prototype.copy.call( this, source );
+
+               this.groundColor.copy( source.groundColor );
+
+               return this;
+
+       }
+
+} );
+
+function LightShadow( camera ) {
+
+       this.camera = camera;
+
+       this.bias = 0;
+       this.normalBias = 0;
+       this.radius = 1;
+
+       this.mapSize = new Vector2( 512, 512 );
+
+       this.map = null;
+       this.mapPass = null;
+       this.matrix = new Matrix4();
+
+       this.autoUpdate = true;
+       this.needsUpdate = false;
+
+       this._frustum = new Frustum();
+       this._frameExtents = new Vector2( 1, 1 );
+
+       this._viewportCount = 1;
+
+       this._viewports = [
+
+               new Vector4( 0, 0, 1, 1 )
+
+       ];
+
+}
+
+Object.assign( LightShadow.prototype, {
+
+       _projScreenMatrix: new Matrix4(),
+
+       _lightPositionWorld: new Vector3(),
+
+       _lookTarget: new Vector3(),
+
+       getViewportCount: function () {
+
+               return this._viewportCount;
+
+       },
+
+       getFrustum: function () {
+
+               return this._frustum;
+
+       },
+
+       updateMatrices: function ( light ) {
+
+               const shadowCamera = this.camera,
+                       shadowMatrix = this.matrix,
+                       projScreenMatrix = this._projScreenMatrix,
+                       lookTarget = this._lookTarget,
+                       lightPositionWorld = this._lightPositionWorld;
+
+               lightPositionWorld.setFromMatrixPosition( light.matrixWorld );
+               shadowCamera.position.copy( lightPositionWorld );
+
+               lookTarget.setFromMatrixPosition( light.target.matrixWorld );
+               shadowCamera.lookAt( lookTarget );
+               shadowCamera.updateMatrixWorld();
+
+               projScreenMatrix.multiplyMatrices( shadowCamera.projectionMatrix, shadowCamera.matrixWorldInverse );
+               this._frustum.setFromProjectionMatrix( projScreenMatrix );
+
+               shadowMatrix.set(
+                       0.5, 0.0, 0.0, 0.5,
+                       0.0, 0.5, 0.0, 0.5,
+                       0.0, 0.0, 0.5, 0.5,
+                       0.0, 0.0, 0.0, 1.0
+               );
+
+               shadowMatrix.multiply( shadowCamera.projectionMatrix );
+               shadowMatrix.multiply( shadowCamera.matrixWorldInverse );
+
+       },
+
+       getViewport: function ( viewportIndex ) {
+
+               return this._viewports[ viewportIndex ];
+
+       },
+
+       getFrameExtents: function () {
+
+               return this._frameExtents;
+
+       },
+
+       copy: function ( source ) {
+
+               this.camera = source.camera.clone();
+
+               this.bias = source.bias;
+               this.radius = source.radius;
+
+               this.mapSize.copy( source.mapSize );
+
+               return this;
+
+       },
+
+       clone: function () {
+
+               return new this.constructor().copy( this );
+
+       },
+
+       toJSON: function () {
+
+               const object = {};
+
+               if ( this.bias !== 0 ) object.bias = this.bias;
+               if ( this.normalBias !== 0 ) object.normalBias = this.normalBias;
+               if ( this.radius !== 1 ) object.radius = this.radius;
+               if ( this.mapSize.x !== 512 || this.mapSize.y !== 512 ) object.mapSize = this.mapSize.toArray();
+
+               object.camera = this.camera.toJSON( false ).object;
+               delete object.camera.matrix;
+
+               return object;
+
+       }
+
+} );
+
+function SpotLightShadow() {
+
+       LightShadow.call( this, new PerspectiveCamera( 50, 1, 0.5, 500 ) );
+
+       this.focus = 1;
+
+}
+
+SpotLightShadow.prototype = Object.assign( Object.create( LightShadow.prototype ), {
+
+       constructor: SpotLightShadow,
+
+       isSpotLightShadow: true,
+
+       updateMatrices: function ( light ) {
+
+               const camera = this.camera;
+
+               const fov = MathUtils.RAD2DEG * 2 * light.angle * this.focus;
+               const aspect = this.mapSize.width / this.mapSize.height;
+               const far = light.distance || camera.far;
+
+               if ( fov !== camera.fov || aspect !== camera.aspect || far !== camera.far ) {
+
+                       camera.fov = fov;
+                       camera.aspect = aspect;
+                       camera.far = far;
+                       camera.updateProjectionMatrix();
+
+               }
+
+               LightShadow.prototype.updateMatrices.call( this, light );
+
+       }
+
+} );
+
+function SpotLight( color, intensity, distance, angle, penumbra, decay ) {
+
+       Light.call( this, color, intensity );
+
+       this.type = 'SpotLight';
+
+       this.position.copy( Object3D.DefaultUp );
+       this.updateMatrix();
+
+       this.target = new Object3D();
+
+       Object.defineProperty( this, 'power', {
+               get: function () {
+
+                       // intensity = power per solid angle.
+                       // ref: equation (17) from https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf
+                       return this.intensity * Math.PI;
+
+               },
+               set: function ( power ) {
+
+                       // intensity = power per solid angle.
+                       // ref: equation (17) from https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf
+                       this.intensity = power / Math.PI;
+
+               }
+       } );
+
+       this.distance = ( distance !== undefined ) ? distance : 0;
+       this.angle = ( angle !== undefined ) ? angle : Math.PI / 3;
+       this.penumbra = ( penumbra !== undefined ) ? penumbra : 0;
+       this.decay = ( decay !== undefined ) ? decay : 1;       // for physically correct lights, should be 2.
+
+       this.shadow = new SpotLightShadow();
+
+}
+
+SpotLight.prototype = Object.assign( Object.create( Light.prototype ), {
+
+       constructor: SpotLight,
+
+       isSpotLight: true,
+
+       copy: function ( source ) {
+
+               Light.prototype.copy.call( this, source );
+
+               this.distance = source.distance;
+               this.angle = source.angle;
+               this.penumbra = source.penumbra;
+               this.decay = source.decay;
+
+               this.target = source.target.clone();
+
+               this.shadow = source.shadow.clone();
+
+               return this;
+
+       }
+
+} );
+
+function PointLightShadow() {
+
+       LightShadow.call( this, new PerspectiveCamera( 90, 1, 0.5, 500 ) );
+
+       this._frameExtents = new Vector2( 4, 2 );
+
+       this._viewportCount = 6;
+
+       this._viewports = [
+               // These viewports map a cube-map onto a 2D texture with the
+               // following orientation:
+               //
+               //  xzXZ
+               //   y Y
+               //
+               // X - Positive x direction
+               // x - Negative x direction
+               // Y - Positive y direction
+               // y - Negative y direction
+               // Z - Positive z direction
+               // z - Negative z direction
+
+               // positive X
+               new Vector4( 2, 1, 1, 1 ),
+               // negative X
+               new Vector4( 0, 1, 1, 1 ),
+               // positive Z
+               new Vector4( 3, 1, 1, 1 ),
+               // negative Z
+               new Vector4( 1, 1, 1, 1 ),
+               // positive Y
+               new Vector4( 3, 0, 1, 1 ),
+               // negative Y
+               new Vector4( 1, 0, 1, 1 )
+       ];
+
+       this._cubeDirections = [
+               new Vector3( 1, 0, 0 ), new Vector3( - 1, 0, 0 ), new Vector3( 0, 0, 1 ),
+               new Vector3( 0, 0, - 1 ), new Vector3( 0, 1, 0 ), new Vector3( 0, - 1, 0 )
+       ];
+
+       this._cubeUps = [
+               new Vector3( 0, 1, 0 ), new Vector3( 0, 1, 0 ), new Vector3( 0, 1, 0 ),
+               new Vector3( 0, 1, 0 ), new Vector3( 0, 0, 1 ), new Vector3( 0, 0, - 1 )
+       ];
+
+}
+
+PointLightShadow.prototype = Object.assign( Object.create( LightShadow.prototype ), {
+
+       constructor: PointLightShadow,
+
+       isPointLightShadow: true,
+
+       updateMatrices: function ( light, viewportIndex = 0 ) {
+
+               const camera = this.camera,
+                       shadowMatrix = this.matrix,
+                       lightPositionWorld = this._lightPositionWorld,
+                       lookTarget = this._lookTarget,
+                       projScreenMatrix = this._projScreenMatrix;
+
+               lightPositionWorld.setFromMatrixPosition( light.matrixWorld );
+               camera.position.copy( lightPositionWorld );
+
+               lookTarget.copy( camera.position );
+               lookTarget.add( this._cubeDirections[ viewportIndex ] );
+               camera.up.copy( this._cubeUps[ viewportIndex ] );
+               camera.lookAt( lookTarget );
+               camera.updateMatrixWorld();
+
+               shadowMatrix.makeTranslation( - lightPositionWorld.x, - lightPositionWorld.y, - lightPositionWorld.z );
+
+               projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse );
+               this._frustum.setFromProjectionMatrix( projScreenMatrix );
+
+       }
+
+} );
+
+function PointLight( color, intensity, distance, decay ) {
+
+       Light.call( this, color, intensity );
+
+       this.type = 'PointLight';
+
+       Object.defineProperty( this, 'power', {
+               get: function () {
+
+                       // intensity = power per solid angle.
+                       // ref: equation (15) from https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf
+                       return this.intensity * 4 * Math.PI;
+
+               },
+               set: function ( power ) {
+
+                       // intensity = power per solid angle.
+                       // ref: equation (15) from https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf
+                       this.intensity = power / ( 4 * Math.PI );
+
+               }
+       } );
+
+       this.distance = ( distance !== undefined ) ? distance : 0;
+       this.decay = ( decay !== undefined ) ? decay : 1;       // for physically correct lights, should be 2.
+
+       this.shadow = new PointLightShadow();
+
+}
+
+PointLight.prototype = Object.assign( Object.create( Light.prototype ), {
+
+       constructor: PointLight,
+
+       isPointLight: true,
+
+       copy: function ( source ) {
+
+               Light.prototype.copy.call( this, source );
+
+               this.distance = source.distance;
+               this.decay = source.decay;
+
+               this.shadow = source.shadow.clone();
+
+               return this;
+
+       }
+
+} );
+
+function OrthographicCamera( left = - 1, right = 1, top = 1, bottom = - 1, near = 0.1, far = 2000 ) {
+
+       Camera$1.call( this );
+
+       this.type = 'OrthographicCamera';
+
+       this.zoom = 1;
+       this.view = null;
+
+       this.left = left;
+       this.right = right;
+       this.top = top;
+       this.bottom = bottom;
+
+       this.near = near;
+       this.far = far;
+
+       this.updateProjectionMatrix();
+
+}
+
+OrthographicCamera.prototype = Object.assign( Object.create( Camera$1.prototype ), {
+
+       constructor: OrthographicCamera,
+
+       isOrthographicCamera: true,
+
+       copy: function ( source, recursive ) {
+
+               Camera$1.prototype.copy.call( this, source, recursive );
+
+               this.left = source.left;
+               this.right = source.right;
+               this.top = source.top;
+               this.bottom = source.bottom;
+               this.near = source.near;
+               this.far = source.far;
+
+               this.zoom = source.zoom;
+               this.view = source.view === null ? null : Object.assign( {}, source.view );
+
+               return this;
+
+       },
+
+       setViewOffset: function ( fullWidth, fullHeight, x, y, width, height ) {
+
+               if ( this.view === null ) {
+
+                       this.view = {
+                               enabled: true,
+                               fullWidth: 1,
+                               fullHeight: 1,
+                               offsetX: 0,
+                               offsetY: 0,
+                               width: 1,
+                               height: 1
+                       };
+
+               }
+
+               this.view.enabled = true;
+               this.view.fullWidth = fullWidth;
+               this.view.fullHeight = fullHeight;
+               this.view.offsetX = x;
+               this.view.offsetY = y;
+               this.view.width = width;
+               this.view.height = height;
+
+               this.updateProjectionMatrix();
+
+       },
+
+       clearViewOffset: function () {
+
+               if ( this.view !== null ) {
+
+                       this.view.enabled = false;
+
+               }
+
+               this.updateProjectionMatrix();
+
+       },
+
+       updateProjectionMatrix: function () {
+
+               const dx = ( this.right - this.left ) / ( 2 * this.zoom );
+               const dy = ( this.top - this.bottom ) / ( 2 * this.zoom );
+               const cx = ( this.right + this.left ) / 2;
+               const cy = ( this.top + this.bottom ) / 2;
+
+               let left = cx - dx;
+               let right = cx + dx;
+               let top = cy + dy;
+               let bottom = cy - dy;
+
+               if ( this.view !== null && this.view.enabled ) {
+
+                       const scaleW = ( this.right - this.left ) / this.view.fullWidth / this.zoom;
+                       const scaleH = ( this.top - this.bottom ) / this.view.fullHeight / this.zoom;
+
+                       left += scaleW * this.view.offsetX;
+                       right = left + scaleW * this.view.width;
+                       top -= scaleH * this.view.offsetY;
+                       bottom = top - scaleH * this.view.height;
+
+               }
+
+               this.projectionMatrix.makeOrthographic( left, right, top, bottom, this.near, this.far );
+
+               this.projectionMatrixInverse.copy( this.projectionMatrix ).invert();
+
+       },
+
+       toJSON: function ( meta ) {
+
+               const data = Object3D.prototype.toJSON.call( this, meta );
+
+               data.object.zoom = this.zoom;
+               data.object.left = this.left;
+               data.object.right = this.right;
+               data.object.top = this.top;
+               data.object.bottom = this.bottom;
+               data.object.near = this.near;
+               data.object.far = this.far;
+
+               if ( this.view !== null ) data.object.view = Object.assign( {}, this.view );
+
+               return data;
+
+       }
+
+} );
+
+function DirectionalLightShadow() {
+
+       LightShadow.call( this, new OrthographicCamera( - 5, 5, 5, - 5, 0.5, 500 ) );
+
+}
+
+DirectionalLightShadow.prototype = Object.assign( Object.create( LightShadow.prototype ), {
+
+       constructor: DirectionalLightShadow,
+
+       isDirectionalLightShadow: true,
+
+       updateMatrices: function ( light ) {
+
+               LightShadow.prototype.updateMatrices.call( this, light );
+
+       }
+
+} );
+
+function DirectionalLight( color, intensity ) {
+
+       Light.call( this, color, intensity );
+
+       this.type = 'DirectionalLight';
+
+       this.position.copy( Object3D.DefaultUp );
+       this.updateMatrix();
+
+       this.target = new Object3D();
+
+       this.shadow = new DirectionalLightShadow();
+
+}
+
+DirectionalLight.prototype = Object.assign( Object.create( Light.prototype ), {
+
+       constructor: DirectionalLight,
+
+       isDirectionalLight: true,
+
+       copy: function ( source ) {
+
+               Light.prototype.copy.call( this, source );
+
+               this.target = source.target.clone();
+
+               this.shadow = source.shadow.clone();
+
+               return this;
+
+       }
+
+} );
+
+function AmbientLight( color, intensity ) {
+
+       Light.call( this, color, intensity );
+
+       this.type = 'AmbientLight';
+
+}
+
+AmbientLight.prototype = Object.assign( Object.create( Light.prototype ), {
+
+       constructor: AmbientLight,
+
+       isAmbientLight: true
+
+} );
+
+function RectAreaLight( color, intensity, width, height ) {
+
+       Light.call( this, color, intensity );
+
+       this.type = 'RectAreaLight';
+
+       this.width = ( width !== undefined ) ? width : 10;
+       this.height = ( height !== undefined ) ? height : 10;
+
+}
+
+RectAreaLight.prototype = Object.assign( Object.create( Light.prototype ), {
+
+       constructor: RectAreaLight,
+
+       isRectAreaLight: true,
+
+       copy: function ( source ) {
+
+               Light.prototype.copy.call( this, source );
+
+               this.width = source.width;
+               this.height = source.height;
+
+               return this;
+
+       },
+
+       toJSON: function ( meta ) {
+
+               const data = Light.prototype.toJSON.call( this, meta );
+
+               data.object.width = this.width;
+               data.object.height = this.height;
+
+               return data;
+
+       }
+
+} );
+
+/**
+ * Primary reference:
+ *   https://graphics.stanford.edu/papers/envmap/envmap.pdf
+ *
+ * Secondary reference:
+ *   https://www.ppsloan.org/publications/StupidSH36.pdf
+ */
+
+// 3-band SH defined by 9 coefficients
+
+class SphericalHarmonics3 {
+
+       constructor() {
+
+               Object.defineProperty( this, 'isSphericalHarmonics3', { value: true } );
+
+               this.coefficients = [];
+
+               for ( let i = 0; i < 9; i ++ ) {
+
+                       this.coefficients.push( new Vector3() );
+
+               }
+
+       }
+
+       set( coefficients ) {
+
+               for ( let i = 0; i < 9; i ++ ) {
+
+                       this.coefficients[ i ].copy( coefficients[ i ] );
+
+               }
+
+               return this;
+
+       }
+
+       zero() {
+
+               for ( let i = 0; i < 9; i ++ ) {
+
+                       this.coefficients[ i ].set( 0, 0, 0 );
+
+               }
+
+               return this;
+
+       }
+
+       // get the radiance in the direction of the normal
+       // target is a Vector3
+       getAt( normal, target ) {
+
+               // normal is assumed to be unit length
+
+               const x = normal.x, y = normal.y, z = normal.z;
+
+               const coeff = this.coefficients;
+
+               // band 0
+               target.copy( coeff[ 0 ] ).multiplyScalar( 0.282095 );
+
+               // band 1
+               target.addScaledVector( coeff[ 1 ], 0.488603 * y );
+               target.addScaledVector( coeff[ 2 ], 0.488603 * z );
+               target.addScaledVector( coeff[ 3 ], 0.488603 * x );
+
+               // band 2
+               target.addScaledVector( coeff[ 4 ], 1.092548 * ( x * y ) );
+               target.addScaledVector( coeff[ 5 ], 1.092548 * ( y * z ) );
+               target.addScaledVector( coeff[ 6 ], 0.315392 * ( 3.0 * z * z - 1.0 ) );
+               target.addScaledVector( coeff[ 7 ], 1.092548 * ( x * z ) );
+               target.addScaledVector( coeff[ 8 ], 0.546274 * ( x * x - y * y ) );
+
+               return target;
+
+       }
+
+       // get the irradiance (radiance convolved with cosine lobe) in the direction of the normal
+       // target is a Vector3
+       // https://graphics.stanford.edu/papers/envmap/envmap.pdf
+       getIrradianceAt( normal, target ) {
+
+               // normal is assumed to be unit length
+
+               const x = normal.x, y = normal.y, z = normal.z;
+
+               const coeff = this.coefficients;
+
+               // band 0
+               target.copy( coeff[ 0 ] ).multiplyScalar( 0.886227 ); // π * 0.282095
+
+               // band 1
+               target.addScaledVector( coeff[ 1 ], 2.0 * 0.511664 * y ); // ( 2 * π / 3 ) * 0.488603
+               target.addScaledVector( coeff[ 2 ], 2.0 * 0.511664 * z );
+               target.addScaledVector( coeff[ 3 ], 2.0 * 0.511664 * x );
+
+               // band 2
+               target.addScaledVector( coeff[ 4 ], 2.0 * 0.429043 * x * y ); // ( π / 4 ) * 1.092548
+               target.addScaledVector( coeff[ 5 ], 2.0 * 0.429043 * y * z );
+               target.addScaledVector( coeff[ 6 ], 0.743125 * z * z - 0.247708 ); // ( π / 4 ) * 0.315392 * 3
+               target.addScaledVector( coeff[ 7 ], 2.0 * 0.429043 * x * z );
+               target.addScaledVector( coeff[ 8 ], 0.429043 * ( x * x - y * y ) ); // ( π / 4 ) * 0.546274
+
+               return target;
+
+       }
+
+       add( sh ) {
+
+               for ( let i = 0; i < 9; i ++ ) {
+
+                       this.coefficients[ i ].add( sh.coefficients[ i ] );
+
+               }
+
+               return this;
+
+       }
+
+       addScaledSH( sh, s ) {
+
+               for ( let i = 0; i < 9; i ++ ) {
+
+                       this.coefficients[ i ].addScaledVector( sh.coefficients[ i ], s );
+
+               }
+
+               return this;
+
+       }
+
+       scale( s ) {
+
+               for ( let i = 0; i < 9; i ++ ) {
+
+                       this.coefficients[ i ].multiplyScalar( s );
+
+               }
+
+               return this;
+
+       }
+
+       lerp( sh, alpha ) {
+
+               for ( let i = 0; i < 9; i ++ ) {
+
+                       this.coefficients[ i ].lerp( sh.coefficients[ i ], alpha );
+
+               }
+
+               return this;
+
+       }
+
+       equals( sh ) {
+
+               for ( let i = 0; i < 9; i ++ ) {
+
+                       if ( ! this.coefficients[ i ].equals( sh.coefficients[ i ] ) ) {
+
+                               return false;
+
+                       }
+
+               }
+
+               return true;
+
+       }
+
+       copy( sh ) {
+
+               return this.set( sh.coefficients );
+
+       }
+
+       clone() {
+
+               return new this.constructor().copy( this );
+
+       }
+
+       fromArray( array, offset = 0 ) {
+
+               const coefficients = this.coefficients;
+
+               for ( let i = 0; i < 9; i ++ ) {
+
+                       coefficients[ i ].fromArray( array, offset + ( i * 3 ) );
+
+               }
+
+               return this;
+
+       }
+
+       toArray( array = [], offset = 0 ) {
+
+               const coefficients = this.coefficients;
+
+               for ( let i = 0; i < 9; i ++ ) {
+
+                       coefficients[ i ].toArray( array, offset + ( i * 3 ) );
+
+               }
+
+               return array;
+
+       }
+
+       // evaluate the basis functions
+       // shBasis is an Array[ 9 ]
+       static getBasisAt( normal, shBasis ) {
+
+               // normal is assumed to be unit length
+
+               const x = normal.x, y = normal.y, z = normal.z;
+
+               // band 0
+               shBasis[ 0 ] = 0.282095;
+
+               // band 1
+               shBasis[ 1 ] = 0.488603 * y;
+               shBasis[ 2 ] = 0.488603 * z;
+               shBasis[ 3 ] = 0.488603 * x;
+
+               // band 2
+               shBasis[ 4 ] = 1.092548 * x * y;
+               shBasis[ 5 ] = 1.092548 * y * z;
+               shBasis[ 6 ] = 0.315392 * ( 3 * z * z - 1 );
+               shBasis[ 7 ] = 1.092548 * x * z;
+               shBasis[ 8 ] = 0.546274 * ( x * x - y * y );
+
+       }
+
+}
+
+function LightProbe( sh, intensity ) {
+
+       Light.call( this, undefined, intensity );
+
+       this.type = 'LightProbe';
+
+       this.sh = ( sh !== undefined ) ? sh : new SphericalHarmonics3();
+
+}
+
+LightProbe.prototype = Object.assign( Object.create( Light.prototype ), {
+
+       constructor: LightProbe,
+
+       isLightProbe: true,
+
+       copy: function ( source ) {
+
+               Light.prototype.copy.call( this, source );
+
+               this.sh.copy( source.sh );
+
+               return this;
+
+       },
+
+       fromJSON: function ( json ) {
+
+               this.intensity = json.intensity; // TODO: Move this bit to Light.fromJSON();
+               this.sh.fromArray( json.sh );
+
+               return this;
+
+       },
+
+       toJSON: function ( meta ) {
+
+               const data = Light.prototype.toJSON.call( this, meta );
+
+               data.object.sh = this.sh.toArray();
+
+               return data;
+
+       }
+
+} );
+
+function MaterialLoader( manager ) {
+
+       Loader.call( this, manager );
+
+       this.textures = {};
+
+}
+
+MaterialLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
+
+       constructor: MaterialLoader,
+
+       load: function ( url, onLoad, onProgress, onError ) {
+
+               const scope = this;
+
+               const loader = new FileLoader( scope.manager );
+               loader.setPath( scope.path );
+               loader.setRequestHeader( scope.requestHeader );
+               loader.setWithCredentials( scope.withCredentials );
+               loader.load( url, function ( text ) {
+
+                       try {
+
+                               onLoad( scope.parse( JSON.parse( text ) ) );
+
+                       } catch ( e ) {
+
+                               if ( onError ) {
+
+                                       onError( e );
+
+                               } else {
+
+                                       console.error( e );
+
+                               }
+
+                               scope.manager.itemError( url );
+
+                       }
+
+               }, onProgress, onError );
+
+       },
+
+       parse: function ( json ) {
+
+               const textures = this.textures;
+
+               function getTexture( name ) {
+
+                       if ( textures[ name ] === undefined ) {
+
+                               console.warn( 'THREE.MaterialLoader: Undefined texture', name );
+
+                       }
+
+                       return textures[ name ];
+
+               }
+
+               const material = new Materials[ json.type ]();
+
+               if ( json.uuid !== undefined ) material.uuid = json.uuid;
+               if ( json.name !== undefined ) material.name = json.name;
+               if ( json.color !== undefined && material.color !== undefined ) material.color.setHex( json.color );
+               if ( json.roughness !== undefined ) material.roughness = json.roughness;
+               if ( json.metalness !== undefined ) material.metalness = json.metalness;
+               if ( json.sheen !== undefined ) material.sheen = new Color().setHex( json.sheen );
+               if ( json.emissive !== undefined && material.emissive !== undefined ) material.emissive.setHex( json.emissive );
+               if ( json.specular !== undefined && material.specular !== undefined ) material.specular.setHex( json.specular );
+               if ( json.shininess !== undefined ) material.shininess = json.shininess;
+               if ( json.clearcoat !== undefined ) material.clearcoat = json.clearcoat;
+               if ( json.clearcoatRoughness !== undefined ) material.clearcoatRoughness = json.clearcoatRoughness;
+               if ( json.fog !== undefined ) material.fog = json.fog;
+               if ( json.flatShading !== undefined ) material.flatShading = json.flatShading;
+               if ( json.blending !== undefined ) material.blending = json.blending;
+               if ( json.combine !== undefined ) material.combine = json.combine;
+               if ( json.side !== undefined ) material.side = json.side;
+               if ( json.opacity !== undefined ) material.opacity = json.opacity;
+               if ( json.transparent !== undefined ) material.transparent = json.transparent;
+               if ( json.alphaTest !== undefined ) material.alphaTest = json.alphaTest;
+               if ( json.depthTest !== undefined ) material.depthTest = json.depthTest;
+               if ( json.depthWrite !== undefined ) material.depthWrite = json.depthWrite;
+               if ( json.colorWrite !== undefined ) material.colorWrite = json.colorWrite;
+
+               if ( json.stencilWrite !== undefined ) material.stencilWrite = json.stencilWrite;
+               if ( json.stencilWriteMask !== undefined ) material.stencilWriteMask = json.stencilWriteMask;
+               if ( json.stencilFunc !== undefined ) material.stencilFunc = json.stencilFunc;
+               if ( json.stencilRef !== undefined ) material.stencilRef = json.stencilRef;
+               if ( json.stencilFuncMask !== undefined ) material.stencilFuncMask = json.stencilFuncMask;
+               if ( json.stencilFail !== undefined ) material.stencilFail = json.stencilFail;
+               if ( json.stencilZFail !== undefined ) material.stencilZFail = json.stencilZFail;
+               if ( json.stencilZPass !== undefined ) material.stencilZPass = json.stencilZPass;
+
+               if ( json.wireframe !== undefined ) material.wireframe = json.wireframe;
+               if ( json.wireframeLinewidth !== undefined ) material.wireframeLinewidth = json.wireframeLinewidth;
+               if ( json.wireframeLinecap !== undefined ) material.wireframeLinecap = json.wireframeLinecap;
+               if ( json.wireframeLinejoin !== undefined ) material.wireframeLinejoin = json.wireframeLinejoin;
+
+               if ( json.rotation !== undefined ) material.rotation = json.rotation;
+
+               if ( json.linewidth !== 1 ) material.linewidth = json.linewidth;
+               if ( json.dashSize !== undefined ) material.dashSize = json.dashSize;
+               if ( json.gapSize !== undefined ) material.gapSize = json.gapSize;
+               if ( json.scale !== undefined ) material.scale = json.scale;
+
+               if ( json.polygonOffset !== undefined ) material.polygonOffset = json.polygonOffset;
+               if ( json.polygonOffsetFactor !== undefined ) material.polygonOffsetFactor = json.polygonOffsetFactor;
+               if ( json.polygonOffsetUnits !== undefined ) material.polygonOffsetUnits = json.polygonOffsetUnits;
+
+               if ( json.skinning !== undefined ) material.skinning = json.skinning;
+               if ( json.morphTargets !== undefined ) material.morphTargets = json.morphTargets;
+               if ( json.morphNormals !== undefined ) material.morphNormals = json.morphNormals;
+               if ( json.dithering !== undefined ) material.dithering = json.dithering;
+
+               if ( json.vertexTangents !== undefined ) material.vertexTangents = json.vertexTangents;
+
+               if ( json.visible !== undefined ) material.visible = json.visible;
+
+               if ( json.toneMapped !== undefined ) material.toneMapped = json.toneMapped;
+
+               if ( json.userData !== undefined ) material.userData = json.userData;
+
+               if ( json.vertexColors !== undefined ) {
+
+                       if ( typeof json.vertexColors === 'number' ) {
+
+                               material.vertexColors = ( json.vertexColors > 0 ) ? true : false;
+
+                       } else {
+
+                               material.vertexColors = json.vertexColors;
+
+                       }
+
+               }
+
+               // Shader Material
+
+               if ( json.uniforms !== undefined ) {
+
+                       for ( const name in json.uniforms ) {
+
+                               const uniform = json.uniforms[ name ];
+
+                               material.uniforms[ name ] = {};
+
+                               switch ( uniform.type ) {
+
+                                       case 't':
+                                               material.uniforms[ name ].value = getTexture( uniform.value );
+                                               break;
+
+                                       case 'c':
+                                               material.uniforms[ name ].value = new Color().setHex( uniform.value );
+                                               break;
+
+                                       case 'v2':
+                                               material.uniforms[ name ].value = new Vector2().fromArray( uniform.value );
+                                               break;
+
+                                       case 'v3':
+                                               material.uniforms[ name ].value = new Vector3().fromArray( uniform.value );
+                                               break;
+
+                                       case 'v4':
+                                               material.uniforms[ name ].value = new Vector4().fromArray( uniform.value );
+                                               break;
+
+                                       case 'm3':
+                                               material.uniforms[ name ].value = new Matrix3().fromArray( uniform.value );
+                                               break;
+
+                                       case 'm4':
+                                               material.uniforms[ name ].value = new Matrix4().fromArray( uniform.value );
+                                               break;
+
+                                       default:
+                                               material.uniforms[ name ].value = uniform.value;
+
+                               }
+
+                       }
+
+               }
+
+               if ( json.defines !== undefined ) material.defines = json.defines;
+               if ( json.vertexShader !== undefined ) material.vertexShader = json.vertexShader;
+               if ( json.fragmentShader !== undefined ) material.fragmentShader = json.fragmentShader;
+
+               if ( json.extensions !== undefined ) {
+
+                       for ( const key in json.extensions ) {
+
+                               material.extensions[ key ] = json.extensions[ key ];
+
+                       }
+
+               }
+
+               // Deprecated
+
+               if ( json.shading !== undefined ) material.flatShading = json.shading === 1; // THREE.FlatShading
+
+               // for PointsMaterial
+
+               if ( json.size !== undefined ) material.size = json.size;
+               if ( json.sizeAttenuation !== undefined ) material.sizeAttenuation = json.sizeAttenuation;
+
+               // maps
+
+               if ( json.map !== undefined ) material.map = getTexture( json.map );
+               if ( json.matcap !== undefined ) material.matcap = getTexture( json.matcap );
+
+               if ( json.alphaMap !== undefined ) material.alphaMap = getTexture( json.alphaMap );
+
+               if ( json.bumpMap !== undefined ) material.bumpMap = getTexture( json.bumpMap );
+               if ( json.bumpScale !== undefined ) material.bumpScale = json.bumpScale;
+
+               if ( json.normalMap !== undefined ) material.normalMap = getTexture( json.normalMap );
+               if ( json.normalMapType !== undefined ) material.normalMapType = json.normalMapType;
+               if ( json.normalScale !== undefined ) {
+
+                       let normalScale = json.normalScale;
+
+                       if ( Array.isArray( normalScale ) === false ) {
+
+                               // Blender exporter used to export a scalar. See #7459
+
+                               normalScale = [ normalScale, normalScale ];
+
+                       }
+
+                       material.normalScale = new Vector2().fromArray( normalScale );
+
+               }
+
+               if ( json.displacementMap !== undefined ) material.displacementMap = getTexture( json.displacementMap );
+               if ( json.displacementScale !== undefined ) material.displacementScale = json.displacementScale;
+               if ( json.displacementBias !== undefined ) material.displacementBias = json.displacementBias;
+
+               if ( json.roughnessMap !== undefined ) material.roughnessMap = getTexture( json.roughnessMap );
+               if ( json.metalnessMap !== undefined ) material.metalnessMap = getTexture( json.metalnessMap );
+
+               if ( json.emissiveMap !== undefined ) material.emissiveMap = getTexture( json.emissiveMap );
+               if ( json.emissiveIntensity !== undefined ) material.emissiveIntensity = json.emissiveIntensity;
+
+               if ( json.specularMap !== undefined ) material.specularMap = getTexture( json.specularMap );
+
+               if ( json.envMap !== undefined ) material.envMap = getTexture( json.envMap );
+               if ( json.envMapIntensity !== undefined ) material.envMapIntensity = json.envMapIntensity;
+
+               if ( json.reflectivity !== undefined ) material.reflectivity = json.reflectivity;
+               if ( json.refractionRatio !== undefined ) material.refractionRatio = json.refractionRatio;
+
+               if ( json.lightMap !== undefined ) material.lightMap = getTexture( json.lightMap );
+               if ( json.lightMapIntensity !== undefined ) material.lightMapIntensity = json.lightMapIntensity;
+
+               if ( json.aoMap !== undefined ) material.aoMap = getTexture( json.aoMap );
+               if ( json.aoMapIntensity !== undefined ) material.aoMapIntensity = json.aoMapIntensity;
+
+               if ( json.gradientMap !== undefined ) material.gradientMap = getTexture( json.gradientMap );
+
+               if ( json.clearcoatMap !== undefined ) material.clearcoatMap = getTexture( json.clearcoatMap );
+               if ( json.clearcoatRoughnessMap !== undefined ) material.clearcoatRoughnessMap = getTexture( json.clearcoatRoughnessMap );
+               if ( json.clearcoatNormalMap !== undefined ) material.clearcoatNormalMap = getTexture( json.clearcoatNormalMap );
+               if ( json.clearcoatNormalScale !== undefined ) material.clearcoatNormalScale = new Vector2().fromArray( json.clearcoatNormalScale );
+
+               if ( json.transmission !== undefined ) material.transmission = json.transmission;
+               if ( json.transmissionMap !== undefined ) material.transmissionMap = getTexture( json.transmissionMap );
+
+               return material;
+
+       },
+
+       setTextures: function ( value ) {
+
+               this.textures = value;
+               return this;
+
+       }
+
+} );
+
+const LoaderUtils = {
+
+       decodeText: function ( array ) {
+
+               if ( typeof TextDecoder !== 'undefined' ) {
+
+                       return new TextDecoder().decode( array );
+
+               }
+
+               // Avoid the String.fromCharCode.apply(null, array) shortcut, which
+               // throws a "maximum call stack size exceeded" error for large arrays.
+
+               let s = '';
+
+               for ( let i = 0, il = array.length; i < il; i ++ ) {
+
+                       // Implicitly assumes little-endian.
+                       s += String.fromCharCode( array[ i ] );
+
+               }
+
+               try {
+
+                       // merges multi-byte utf-8 characters.
+
+                       return decodeURIComponent( escape( s ) );
+
+               } catch ( e ) { // see #16358
+
+                       return s;
+
+               }
+
+       },
+
+       extractUrlBase: function ( url ) {
+
+               const index = url.lastIndexOf( '/' );
+
+               if ( index === - 1 ) return './';
+
+               return url.substr( 0, index + 1 );
+
+       }
+
+};
+
+function InstancedBufferGeometry() {
+
+       BufferGeometry.call( this );
+
+       this.type = 'InstancedBufferGeometry';
+       this.instanceCount = Infinity;
+
+}
+
+InstancedBufferGeometry.prototype = Object.assign( Object.create( BufferGeometry.prototype ), {
+
+       constructor: InstancedBufferGeometry,
+
+       isInstancedBufferGeometry: true,
+
+       copy: function ( source ) {
+
+               BufferGeometry.prototype.copy.call( this, source );
+
+               this.instanceCount = source.instanceCount;
+
+               return this;
+
+       },
+
+       clone: function () {
+
+               return new this.constructor().copy( this );
+
+       },
+
+       toJSON: function () {
+
+               const data = BufferGeometry.prototype.toJSON.call( this );
+
+               data.instanceCount = this.instanceCount;
+
+               data.isInstancedBufferGeometry = true;
+
+               return data;
+
+       }
+
+} );
+
+function InstancedBufferAttribute( array, itemSize, normalized, meshPerAttribute ) {
+
+       if ( typeof ( normalized ) === 'number' ) {
+
+               meshPerAttribute = normalized;
+
+               normalized = false;
+
+               console.error( 'THREE.InstancedBufferAttribute: The constructor now expects normalized as the third argument.' );
+
+       }
+
+       BufferAttribute.call( this, array, itemSize, normalized );
+
+       this.meshPerAttribute = meshPerAttribute || 1;
+
+}
+
+InstancedBufferAttribute.prototype = Object.assign( Object.create( BufferAttribute.prototype ), {
+
+       constructor: InstancedBufferAttribute,
+
+       isInstancedBufferAttribute: true,
+
+       copy: function ( source ) {
+
+               BufferAttribute.prototype.copy.call( this, source );
+
+               this.meshPerAttribute = source.meshPerAttribute;
+
+               return this;
+
+       },
+
+       toJSON: function ()     {
+
+               const data = BufferAttribute.prototype.toJSON.call( this );
+
+               data.meshPerAttribute = this.meshPerAttribute;
+
+               data.isInstancedBufferAttribute = true;
+
+               return data;
+
+       }
+
+} );
+
+function BufferGeometryLoader( manager ) {
+
+       Loader.call( this, manager );
+
+}
+
+BufferGeometryLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
+
+       constructor: BufferGeometryLoader,
+
+       load: function ( url, onLoad, onProgress, onError ) {
+
+               const scope = this;
+
+               const loader = new FileLoader( scope.manager );
+               loader.setPath( scope.path );
+               loader.setRequestHeader( scope.requestHeader );
+               loader.setWithCredentials( scope.withCredentials );
+               loader.load( url, function ( text ) {
+
+                       try {
+
+                               onLoad( scope.parse( JSON.parse( text ) ) );
+
+                       } catch ( e ) {
+
+                               if ( onError ) {
+
+                                       onError( e );
+
+                               } else {
+
+                                       console.error( e );
+
+                               }
+
+                               scope.manager.itemError( url );
+
+                       }
+
+               }, onProgress, onError );
+
+       },
+
+       parse: function ( json ) {
+
+               const interleavedBufferMap = {};
+               const arrayBufferMap = {};
+
+               function getInterleavedBuffer( json, uuid ) {
+
+                       if ( interleavedBufferMap[ uuid ] !== undefined ) return interleavedBufferMap[ uuid ];
+
+                       const interleavedBuffers = json.interleavedBuffers;
+                       const interleavedBuffer = interleavedBuffers[ uuid ];
+
+                       const buffer = getArrayBuffer( json, interleavedBuffer.buffer );
+
+                       const array = getTypedArray( interleavedBuffer.type, buffer );
+                       const ib = new InterleavedBuffer( array, interleavedBuffer.stride );
+                       ib.uuid = interleavedBuffer.uuid;
+
+                       interleavedBufferMap[ uuid ] = ib;
+
+                       return ib;
+
+               }
+
+               function getArrayBuffer( json, uuid ) {
+
+                       if ( arrayBufferMap[ uuid ] !== undefined ) return arrayBufferMap[ uuid ];
+
+                       const arrayBuffers = json.arrayBuffers;
+                       const arrayBuffer = arrayBuffers[ uuid ];
+
+                       const ab = new Uint32Array( arrayBuffer ).buffer;
+
+                       arrayBufferMap[ uuid ] = ab;
+
+                       return ab;
+
+               }
+
+               const geometry = json.isInstancedBufferGeometry ? new InstancedBufferGeometry() : new BufferGeometry();
+
+               const index = json.data.index;
+
+               if ( index !== undefined ) {
+
+                       const typedArray = getTypedArray( index.type, index.array );
+                       geometry.setIndex( new BufferAttribute( typedArray, 1 ) );
+
+               }
+
+               const attributes = json.data.attributes;
+
+               for ( const key in attributes ) {
+
+                       const attribute = attributes[ key ];
+                       let bufferAttribute;
+
+                       if ( attribute.isInterleavedBufferAttribute ) {
+
+                               const interleavedBuffer = getInterleavedBuffer( json.data, attribute.data );
+                               bufferAttribute = new InterleavedBufferAttribute( interleavedBuffer, attribute.itemSize, attribute.offset, attribute.normalized );
+
+                       } else {
+
+                               const typedArray = getTypedArray( attribute.type, attribute.array );
+                               const bufferAttributeConstr = attribute.isInstancedBufferAttribute ? InstancedBufferAttribute : BufferAttribute;
+                               bufferAttribute = new bufferAttributeConstr( typedArray, attribute.itemSize, attribute.normalized );
+
+                       }
+
+                       if ( attribute.name !== undefined ) bufferAttribute.name = attribute.name;
+                       geometry.setAttribute( key, bufferAttribute );
+
+               }
+
+               const morphAttributes = json.data.morphAttributes;
+
+               if ( morphAttributes ) {
+
+                       for ( const key in morphAttributes ) {
+
+                               const attributeArray = morphAttributes[ key ];
+
+                               const array = [];
+
+                               for ( let i = 0, il = attributeArray.length; i < il; i ++ ) {
+
+                                       const attribute = attributeArray[ i ];
+                                       let bufferAttribute;
+
+                                       if ( attribute.isInterleavedBufferAttribute ) {
+
+                                               const interleavedBuffer = getInterleavedBuffer( json.data, attribute.data );
+                                               bufferAttribute = new InterleavedBufferAttribute( interleavedBuffer, attribute.itemSize, attribute.offset, attribute.normalized );
+
+                                       } else {
+
+                                               const typedArray = getTypedArray( attribute.type, attribute.array );
+                                               bufferAttribute = new BufferAttribute( typedArray, attribute.itemSize, attribute.normalized );
+
+                                       }
+
+                                       if ( attribute.name !== undefined ) bufferAttribute.name = attribute.name;
+                                       array.push( bufferAttribute );
+
+                               }
+
+                               geometry.morphAttributes[ key ] = array;
+
+                       }
+
+               }
+
+               const morphTargetsRelative = json.data.morphTargetsRelative;
+
+               if ( morphTargetsRelative ) {
+
+                       geometry.morphTargetsRelative = true;
+
+               }
+
+               const groups = json.data.groups || json.data.drawcalls || json.data.offsets;
+
+               if ( groups !== undefined ) {
+
+                       for ( let i = 0, n = groups.length; i !== n; ++ i ) {
+
+                               const group = groups[ i ];
+
+                               geometry.addGroup( group.start, group.count, group.materialIndex );
+
+                       }
+
+               }
+
+               const boundingSphere = json.data.boundingSphere;
+
+               if ( boundingSphere !== undefined ) {
+
+                       const center = new Vector3();
+
+                       if ( boundingSphere.center !== undefined ) {
+
+                               center.fromArray( boundingSphere.center );
+
+                       }
+
+                       geometry.boundingSphere = new Sphere( center, boundingSphere.radius );
+
+               }
+
+               if ( json.name ) geometry.name = json.name;
+               if ( json.userData ) geometry.userData = json.userData;
+
+               return geometry;
+
+       }
+
+} );
+
+function ImageBitmapLoader( manager ) {
+
+       if ( typeof createImageBitmap === 'undefined' ) {
+
+               console.warn( 'THREE.ImageBitmapLoader: createImageBitmap() not supported.' );
+
+       }
+
+       if ( typeof fetch === 'undefined' ) {
+
+               console.warn( 'THREE.ImageBitmapLoader: fetch() not supported.' );
+
+       }
+
+       Loader.call( this, manager );
+
+       this.options = { premultiplyAlpha: 'none' };
+
+}
+
+ImageBitmapLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
+
+       constructor: ImageBitmapLoader,
+
+       isImageBitmapLoader: true,
+
+       setOptions: function setOptions( options ) {
+
+               this.options = options;
+
+               return this;
+
+       },
+
+       load: function ( url, onLoad, onProgress, onError ) {
+
+               if ( url === undefined ) url = '';
+
+               if ( this.path !== undefined ) url = this.path + url;
+
+               url = this.manager.resolveURL( url );
+
+               const scope = this;
+
+               const cached = Cache.get( url );
+
+               if ( cached !== undefined ) {
+
+                       scope.manager.itemStart( url );
+
+                       setTimeout( function () {
+
+                               if ( onLoad ) onLoad( cached );
+
+                               scope.manager.itemEnd( url );
+
+                       }, 0 );
+
+                       return cached;
+
+               }
+
+               const fetchOptions = {};
+               fetchOptions.credentials = ( this.crossOrigin === 'anonymous' ) ? 'same-origin' : 'include';
+
+               fetch( url, fetchOptions ).then( function ( res ) {
+
+                       return res.blob();
+
+               } ).then( function ( blob ) {
+
+                       return createImageBitmap( blob, scope.options );
+
+               } ).then( function ( imageBitmap ) {
+
+                       Cache.add( url, imageBitmap );
+
+                       if ( onLoad ) onLoad( imageBitmap );
+
+                       scope.manager.itemEnd( url );
+
+               } ).catch( function ( e ) {
+
+                       if ( onError ) onError( e );
+
+                       scope.manager.itemError( url );
+                       scope.manager.itemEnd( url );
+
+               } );
+
+               scope.manager.itemStart( url );
+
+       }
+
+} );
+
+function ShapePath() {
+
+       this.type = 'ShapePath';
+
+       this.color = new Color();
+
+       this.subPaths = [];
+       this.currentPath = null;
+
+}
+
+Object.assign( ShapePath.prototype, {
+
+       moveTo: function ( x, y ) {
+
+               this.currentPath = new Path();
+               this.subPaths.push( this.currentPath );
+               this.currentPath.moveTo( x, y );
+
+               return this;
+
+       },
+
+       lineTo: function ( x, y ) {
+
+               this.currentPath.lineTo( x, y );
+
+               return this;
+
+       },
+
+       quadraticCurveTo: function ( aCPx, aCPy, aX, aY ) {
+
+               this.currentPath.quadraticCurveTo( aCPx, aCPy, aX, aY );
+
+               return this;
+
+       },
+
+       bezierCurveTo: function ( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) {
+
+               this.currentPath.bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY );
+
+               return this;
+
+       },
+
+       splineThru: function ( pts ) {
+
+               this.currentPath.splineThru( pts );
+
+               return this;
+
+       },
+
+       toShapes: function ( isCCW, noHoles ) {
+
+               function toShapesNoHoles( inSubpaths ) {
+
+                       const shapes = [];
+
+                       for ( let i = 0, l = inSubpaths.length; i < l; i ++ ) {
+
+                               const tmpPath = inSubpaths[ i ];
+
+                               const tmpShape = new Shape();
+                               tmpShape.curves = tmpPath.curves;
+
+                               shapes.push( tmpShape );
+
+                       }
+
+                       return shapes;
+
+               }
+
+               function isPointInsidePolygon( inPt, inPolygon ) {
+
+                       const polyLen = inPolygon.length;
+
+                       // inPt on polygon contour => immediate success    or
+                       // toggling of inside/outside at every single! intersection point of an edge
+                       //  with the horizontal line through inPt, left of inPt
+                       //  not counting lowerY endpoints of edges and whole edges on that line
+                       let inside = false;
+                       for ( let p = polyLen - 1, q = 0; q < polyLen; p = q ++ ) {
+
+                               let edgeLowPt = inPolygon[ p ];
+                               let edgeHighPt = inPolygon[ q ];
+
+                               let edgeDx = edgeHighPt.x - edgeLowPt.x;
+                               let edgeDy = edgeHighPt.y - edgeLowPt.y;
+
+                               if ( Math.abs( edgeDy ) > Number.EPSILON ) {
+
+                                       // not parallel
+                                       if ( edgeDy < 0 ) {
+
+                                               edgeLowPt = inPolygon[ q ]; edgeDx = - edgeDx;
+                                               edgeHighPt = inPolygon[ p ]; edgeDy = - edgeDy;
+
+                                       }
+
+                                       if ( ( inPt.y < edgeLowPt.y ) || ( inPt.y > edgeHighPt.y ) )            continue;
+
+                                       if ( inPt.y === edgeLowPt.y ) {
+
+                                               if ( inPt.x === edgeLowPt.x )           return  true;           // inPt is on contour ?
+                                               // continue;                            // no intersection or edgeLowPt => doesn't count !!!
+
+                                       } else {
+
+                                               const perpEdge = edgeDy * ( inPt.x - edgeLowPt.x ) - edgeDx * ( inPt.y - edgeLowPt.y );
+                                               if ( perpEdge === 0 )                           return  true;           // inPt is on contour ?
+                                               if ( perpEdge < 0 )                             continue;
+                                               inside = ! inside;              // true intersection left of inPt
+
+                                       }
+
+                               } else {
+
+                                       // parallel or collinear
+                                       if ( inPt.y !== edgeLowPt.y )           continue;                       // parallel
+                                       // edge lies on the same horizontal line as inPt
+                                       if ( ( ( edgeHighPt.x <= inPt.x ) && ( inPt.x <= edgeLowPt.x ) ) ||
+                                                ( ( edgeLowPt.x <= inPt.x ) && ( inPt.x <= edgeHighPt.x ) ) )          return  true;   // inPt: Point on contour !
+                                       // continue;
+
+                               }
+
+                       }
+
+                       return  inside;
+
+               }
+
+               const isClockWise = ShapeUtils.isClockWise;
+
+               const subPaths = this.subPaths;
+               if ( subPaths.length === 0 ) return [];
+
+               if ( noHoles === true ) return  toShapesNoHoles( subPaths );
+
+
+               let solid, tmpPath, tmpShape;
+               const shapes = [];
+
+               if ( subPaths.length === 1 ) {
+
+                       tmpPath = subPaths[ 0 ];
+                       tmpShape = new Shape();
+                       tmpShape.curves = tmpPath.curves;
+                       shapes.push( tmpShape );
+                       return shapes;
+
+               }
+
+               let holesFirst = ! isClockWise( subPaths[ 0 ].getPoints() );
+               holesFirst = isCCW ? ! holesFirst : holesFirst;
+
+               // console.log("Holes first", holesFirst);
+
+               const betterShapeHoles = [];
+               const newShapes = [];
+               let newShapeHoles = [];
+               let mainIdx = 0;
+               let tmpPoints;
+
+               newShapes[ mainIdx ] = undefined;
+               newShapeHoles[ mainIdx ] = [];
+
+               for ( let i = 0, l = subPaths.length; i < l; i ++ ) {
+
+                       tmpPath = subPaths[ i ];
+                       tmpPoints = tmpPath.getPoints();
+                       solid = isClockWise( tmpPoints );
+                       solid = isCCW ? ! solid : solid;
+
+                       if ( solid ) {
+
+                               if ( ( ! holesFirst ) && ( newShapes[ mainIdx ] ) )     mainIdx ++;
+
+                               newShapes[ mainIdx ] = { s: new Shape(), p: tmpPoints };
+                               newShapes[ mainIdx ].s.curves = tmpPath.curves;
+
+                               if ( holesFirst )       mainIdx ++;
+                               newShapeHoles[ mainIdx ] = [];
+
+                               //console.log('cw', i);
+
+                       } else {
+
+                               newShapeHoles[ mainIdx ].push( { h: tmpPath, p: tmpPoints[ 0 ] } );
+
+                               //console.log('ccw', i);
+
+                       }
+
+               }
+
+               // only Holes? -> probably all Shapes with wrong orientation
+               if ( ! newShapes[ 0 ] ) return  toShapesNoHoles( subPaths );
+
+
+               if ( newShapes.length > 1 ) {
+
+                       let ambiguous = false;
+                       const toChange = [];
+
+                       for ( let sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx ++ ) {
+
+                               betterShapeHoles[ sIdx ] = [];
+
+                       }
+
+                       for ( let sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx ++ ) {
+
+                               const sho = newShapeHoles[ sIdx ];
+
+                               for ( let hIdx = 0; hIdx < sho.length; hIdx ++ ) {
+
+                                       const ho = sho[ hIdx ];
+                                       let hole_unassigned = true;
+
+                                       for ( let s2Idx = 0; s2Idx < newShapes.length; s2Idx ++ ) {
+
+                                               if ( isPointInsidePolygon( ho.p, newShapes[ s2Idx ].p ) ) {
+
+                                                       if ( sIdx !== s2Idx )   toChange.push( { froms: sIdx, tos: s2Idx, hole: hIdx } );
+                                                       if ( hole_unassigned ) {
+
+                                                               hole_unassigned = false;
+                                                               betterShapeHoles[ s2Idx ].push( ho );
+
+                                                       } else {
+
+                                                               ambiguous = true;
+
+                                                       }
+
+                                               }
+
+                                       }
+
+                                       if ( hole_unassigned ) {
+
+                                               betterShapeHoles[ sIdx ].push( ho );
+
+                                       }
+
+                               }
+
+                       }
+                       // console.log("ambiguous: ", ambiguous);
+
+                       if ( toChange.length > 0 ) {
+
+                               // console.log("to change: ", toChange);
+                               if ( ! ambiguous )      newShapeHoles = betterShapeHoles;
+
+                       }
+
+               }
+
+               let tmpHoles;
+
+               for ( let i = 0, il = newShapes.length; i < il; i ++ ) {
+
+                       tmpShape = newShapes[ i ].s;
+                       shapes.push( tmpShape );
+                       tmpHoles = newShapeHoles[ i ];
+
+                       for ( let j = 0, jl = tmpHoles.length; j < jl; j ++ ) {
+
+                               tmpShape.holes.push( tmpHoles[ j ].h );
+
+                       }
+
+               }
+
+               //console.log("shape", shapes);
+
+               return shapes;
+
+       }
+
+} );
+
+class Font {
+
+       constructor( data ) {
+
+               Object.defineProperty( this, 'isFont', { value: true } );
+
+               this.type = 'Font';
+
+               this.data = data;
+
+       }
+
+       generateShapes( text, size = 100 ) {
+
+               const shapes = [];
+               const paths = createPaths( text, size, this.data );
+
+               for ( let p = 0, pl = paths.length; p < pl; p ++ ) {
+
+                       Array.prototype.push.apply( shapes, paths[ p ].toShapes() );
+
+               }
+
+               return shapes;
+
+       }
+
+}
+
+function createPaths( text, size, data ) {
+
+       const chars = Array.from ? Array.from( text ) : String( text ).split( '' ); // workaround for IE11, see #13988
+       const scale = size / data.resolution;
+       const line_height = ( data.boundingBox.yMax - data.boundingBox.yMin + data.underlineThickness ) * scale;
+
+       const paths = [];
+
+       let offsetX = 0, offsetY = 0;
+
+       for ( let i = 0; i < chars.length; i ++ ) {
+
+               const char = chars[ i ];
+
+               if ( char === '\n' ) {
+
+                       offsetX = 0;
+                       offsetY -= line_height;
+
+               } else {
+
+                       const ret = createPath( char, scale, offsetX, offsetY, data );
+                       offsetX += ret.offsetX;
+                       paths.push( ret.path );
+
+               }
+
+       }
+
+       return paths;
+
+}
+
+function createPath( char, scale, offsetX, offsetY, data ) {
+
+       const glyph = data.glyphs[ char ] || data.glyphs[ '?' ];
+
+       if ( ! glyph ) {
+
+               console.error( 'THREE.Font: character "' + char + '" does not exists in font family ' + data.familyName + '.' );
+
+               return;
+
+       }
+
+       const path = new ShapePath();
+
+       let x, y, cpx, cpy, cpx1, cpy1, cpx2, cpy2;
+
+       if ( glyph.o ) {
+
+               const outline = glyph._cachedOutline || ( glyph._cachedOutline = glyph.o.split( ' ' ) );
+
+               for ( let i = 0, l = outline.length; i < l; ) {
+
+                       const action = outline[ i ++ ];
+
+                       switch ( action ) {
+
+                               case 'm': // moveTo
+
+                                       x = outline[ i ++ ] * scale + offsetX;
+                                       y = outline[ i ++ ] * scale + offsetY;
+
+                                       path.moveTo( x, y );
+
+                                       break;
+
+                               case 'l': // lineTo
+
+                                       x = outline[ i ++ ] * scale + offsetX;
+                                       y = outline[ i ++ ] * scale + offsetY;
+
+                                       path.lineTo( x, y );
+
+                                       break;
+
+                               case 'q': // quadraticCurveTo
+
+                                       cpx = outline[ i ++ ] * scale + offsetX;
+                                       cpy = outline[ i ++ ] * scale + offsetY;
+                                       cpx1 = outline[ i ++ ] * scale + offsetX;
+                                       cpy1 = outline[ i ++ ] * scale + offsetY;
+
+                                       path.quadraticCurveTo( cpx1, cpy1, cpx, cpy );
+
+                                       break;
+
+                               case 'b': // bezierCurveTo
+
+                                       cpx = outline[ i ++ ] * scale + offsetX;
+                                       cpy = outline[ i ++ ] * scale + offsetY;
+                                       cpx1 = outline[ i ++ ] * scale + offsetX;
+                                       cpy1 = outline[ i ++ ] * scale + offsetY;
+                                       cpx2 = outline[ i ++ ] * scale + offsetX;
+                                       cpy2 = outline[ i ++ ] * scale + offsetY;
+
+                                       path.bezierCurveTo( cpx1, cpy1, cpx2, cpy2, cpx, cpy );
+
+                                       break;
+
+                       }
+
+               }
+
+       }
+
+       return { offsetX: glyph.ha * scale, path: path };
+
+}
+
+function FontLoader( manager ) {
+
+       Loader.call( this, manager );
+
+}
+
+FontLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
+
+       constructor: FontLoader,
+
+       load: function ( url, onLoad, onProgress, onError ) {
+
+               const scope = this;
+
+               const loader = new FileLoader( this.manager );
+               loader.setPath( this.path );
+               loader.setRequestHeader( this.requestHeader );
+               loader.setWithCredentials( scope.withCredentials );
+               loader.load( url, function ( text ) {
+
+                       let json;
+
+                       try {
+
+                               json = JSON.parse( text );
+
+                       } catch ( e ) {
+
+                               console.warn( 'THREE.FontLoader: typeface.js support is being deprecated. Use typeface.json instead.' );
+                               json = JSON.parse( text.substring( 65, text.length - 2 ) );
+
+                       }
+
+                       const font = scope.parse( json );
+
+                       if ( onLoad ) onLoad( font );
+
+               }, onProgress, onError );
+
+       },
+
+       parse: function ( json ) {
+
+               return new Font( json );
+
+       }
+
+} );
+
+let _context;
+
+const AudioContext = {
+
+       getContext: function () {
+
+               if ( _context === undefined ) {
+
+                       _context = new ( window.AudioContext || window.webkitAudioContext )();
+
+               }
+
+               return _context;
+
+       },
+
+       setContext: function ( value ) {
+
+               _context = value;
+
+       }
+
+};
+
+function AudioLoader( manager ) {
+
+       Loader.call( this, manager );
+
+}
+
+AudioLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
+
+       constructor: AudioLoader,
+
+       load: function ( url, onLoad, onProgress, onError ) {
+
+               const scope = this;
+
+               const loader = new FileLoader( scope.manager );
+               loader.setResponseType( 'arraybuffer' );
+               loader.setPath( scope.path );
+               loader.setRequestHeader( scope.requestHeader );
+               loader.setWithCredentials( scope.withCredentials );
+               loader.load( url, function ( buffer ) {
+
+                       try {
+
+                               // Create a copy of the buffer. The `decodeAudioData` method
+                               // detaches the buffer when complete, preventing reuse.
+                               const bufferCopy = buffer.slice( 0 );
+
+                               const context = AudioContext.getContext();
+                               context.decodeAudioData( bufferCopy, function ( audioBuffer ) {
+
+                                       onLoad( audioBuffer );
+
+                               } );
+
+                       } catch ( e ) {
+
+                               if ( onError ) {
+
+                                       onError( e );
+
+                               } else {
+
+                                       console.error( e );
+
+                               }
+
+                               scope.manager.itemError( url );
+
+                       }
+
+               }, onProgress, onError );
+
+       }
+
+} );
+
+function HemisphereLightProbe( skyColor, groundColor, intensity ) {
+
+       LightProbe.call( this, undefined, intensity );
+
+       const color1 = new Color().set( skyColor );
+       const color2 = new Color().set( groundColor );
+
+       const sky = new Vector3( color1.r, color1.g, color1.b );
+       const ground = new Vector3( color2.r, color2.g, color2.b );
+
+       // without extra factor of PI in the shader, should = 1 / Math.sqrt( Math.PI );
+       const c0 = Math.sqrt( Math.PI );
+       const c1 = c0 * Math.sqrt( 0.75 );
+
+       this.sh.coefficients[ 0 ].copy( sky ).add( ground ).multiplyScalar( c0 );
+       this.sh.coefficients[ 1 ].copy( sky ).sub( ground ).multiplyScalar( c1 );
+
+}
+
+HemisphereLightProbe.prototype = Object.assign( Object.create( LightProbe.prototype ), {
+
+       constructor: HemisphereLightProbe,
+
+       isHemisphereLightProbe: true,
+
+       copy: function ( source ) { // modifying colors not currently supported
+
+               LightProbe.prototype.copy.call( this, source );
+
+               return this;
+
+       },
+
+       toJSON: function ( meta ) {
+
+               const data = LightProbe.prototype.toJSON.call( this, meta );
+
+               // data.sh = this.sh.toArray(); // todo
+
+               return data;
+
+       }
+
+} );
+
+function AmbientLightProbe( color, intensity ) {
+
+       LightProbe.call( this, undefined, intensity );
+
+       const color1 = new Color().set( color );
+
+       // without extra factor of PI in the shader, would be 2 / Math.sqrt( Math.PI );
+       this.sh.coefficients[ 0 ].set( color1.r, color1.g, color1.b ).multiplyScalar( 2 * Math.sqrt( Math.PI ) );
+
+}
+
+AmbientLightProbe.prototype = Object.assign( Object.create( LightProbe.prototype ), {
+
+       constructor: AmbientLightProbe,
+
+       isAmbientLightProbe: true,
+
+       copy: function ( source ) { // modifying color not currently supported
+
+               LightProbe.prototype.copy.call( this, source );
+
+               return this;
+
+       },
+
+       toJSON: function ( meta ) {
+
+               const data = LightProbe.prototype.toJSON.call( this, meta );
+
+               // data.sh = this.sh.toArray(); // todo
+
+               return data;
+
+       }
+
+} );
+
+const _eyeRight = new Matrix4();
+const _eyeLeft = new Matrix4();
+
+function StereoCamera() {
+
+       this.type = 'StereoCamera';
+
+       this.aspect = 1;
+
+       this.eyeSep = 0.064;
+
+       this.cameraL = new PerspectiveCamera();
+       this.cameraL.layers.enable( 1 );
+       this.cameraL.matrixAutoUpdate = false;
+
+       this.cameraR = new PerspectiveCamera();
+       this.cameraR.layers.enable( 2 );
+       this.cameraR.matrixAutoUpdate = false;
+
+       this._cache = {
+               focus: null,
+               fov: null,
+               aspect: null,
+               near: null,
+               far: null,
+               zoom: null,
+               eyeSep: null
+       };
+
+}
+
+Object.assign( StereoCamera.prototype, {
+
+       update: function ( camera ) {
+
+               const cache = this._cache;
+
+               const needsUpdate = cache.focus !== camera.focus || cache.fov !== camera.fov ||
+                       cache.aspect !== camera.aspect * this.aspect || cache.near !== camera.near ||
+                       cache.far !== camera.far || cache.zoom !== camera.zoom || cache.eyeSep !== this.eyeSep;
+
+               if ( needsUpdate ) {
+
+                       cache.focus = camera.focus;
+                       cache.fov = camera.fov;
+                       cache.aspect = camera.aspect * this.aspect;
+                       cache.near = camera.near;
+                       cache.far = camera.far;
+                       cache.zoom = camera.zoom;
+                       cache.eyeSep = this.eyeSep;
+
+                       // Off-axis stereoscopic effect based on
+                       // http://paulbourke.net/stereographics/stereorender/
+
+                       const projectionMatrix = camera.projectionMatrix.clone();
+                       const eyeSepHalf = cache.eyeSep / 2;
+                       const eyeSepOnProjection = eyeSepHalf * cache.near / cache.focus;
+                       const ymax = ( cache.near * Math.tan( MathUtils.DEG2RAD * cache.fov * 0.5 ) ) / cache.zoom;
+                       let xmin, xmax;
+
+                       // translate xOffset
+
+                       _eyeLeft.elements[ 12 ] = - eyeSepHalf;
+                       _eyeRight.elements[ 12 ] = eyeSepHalf;
+
+                       // for left eye
+
+                       xmin = - ymax * cache.aspect + eyeSepOnProjection;
+                       xmax = ymax * cache.aspect + eyeSepOnProjection;
+
+                       projectionMatrix.elements[ 0 ] = 2 * cache.near / ( xmax - xmin );
+                       projectionMatrix.elements[ 8 ] = ( xmax + xmin ) / ( xmax - xmin );
+
+                       this.cameraL.projectionMatrix.copy( projectionMatrix );
+
+                       // for right eye
+
+                       xmin = - ymax * cache.aspect - eyeSepOnProjection;
+                       xmax = ymax * cache.aspect - eyeSepOnProjection;
+
+                       projectionMatrix.elements[ 0 ] = 2 * cache.near / ( xmax - xmin );
+                       projectionMatrix.elements[ 8 ] = ( xmax + xmin ) / ( xmax - xmin );
+
+                       this.cameraR.projectionMatrix.copy( projectionMatrix );
+
+               }
+
+               this.cameraL.matrixWorld.copy( camera.matrixWorld ).multiply( _eyeLeft );
+               this.cameraR.matrixWorld.copy( camera.matrixWorld ).multiply( _eyeRight );
+
+       }
+
+} );
+
+class Audio extends Object3D {
+
+       constructor( listener ) {
+
+               super();
+
+               this.type = 'Audio';
+
+               this.listener = listener;
+               this.context = listener.context;
+
+               this.gain = this.context.createGain();
+               this.gain.connect( listener.getInput() );
+
+               this.autoplay = false;
+
+               this.buffer = null;
+               this.detune = 0;
+               this.loop = false;
+               this.loopStart = 0;
+               this.loopEnd = 0;
+               this.offset = 0;
+               this.duration = undefined;
+               this.playbackRate = 1;
+               this.isPlaying = false;
+               this.hasPlaybackControl = true;
+               this.source = null;
+               this.sourceType = 'empty';
+
+               this._startedAt = 0;
+               this._progress = 0;
+               this._connected = false;
+
+               this.filters = [];
+
+       }
+
+       getOutput() {
+
+               return this.gain;
+
+       }
+
+       setNodeSource( audioNode ) {
+
+               this.hasPlaybackControl = false;
+               this.sourceType = 'audioNode';
+               this.source = audioNode;
+               this.connect();
+
+               return this;
+
+       }
+
+       setMediaElementSource( mediaElement ) {
+
+               this.hasPlaybackControl = false;
+               this.sourceType = 'mediaNode';
+               this.source = this.context.createMediaElementSource( mediaElement );
+               this.connect();
+
+               return this;
+
+       }
+
+       setMediaStreamSource( mediaStream ) {
+
+               this.hasPlaybackControl = false;
+               this.sourceType = 'mediaStreamNode';
+               this.source = this.context.createMediaStreamSource( mediaStream );
+               this.connect();
+
+               return this;
+
+       }
+
+       setBuffer( audioBuffer ) {
+
+               this.buffer = audioBuffer;
+               this.sourceType = 'buffer';
+
+               if ( this.autoplay ) this.play();
+
+               return this;
+
+       }
+
+       play( delay = 0 ) {
+
+               if ( this.isPlaying === true ) {
+
+                       console.warn( 'THREE.Audio: Audio is already playing.' );
+                       return;
+
+               }
+
+               if ( this.hasPlaybackControl === false ) {
+
+                       console.warn( 'THREE.Audio: this Audio has no playback control.' );
+                       return;
+
+               }
+
+               this._startedAt = this.context.currentTime + delay;
+
+               const source = this.context.createBufferSource();
+               source.buffer = this.buffer;
+               source.loop = this.loop;
+               source.loopStart = this.loopStart;
+               source.loopEnd = this.loopEnd;
+               source.onended = this.onEnded.bind( this );
+               source.start( this._startedAt, this._progress + this.offset, this.duration );
+
+               this.isPlaying = true;
+
+               this.source = source;
+
+               this.setDetune( this.detune );
+               this.setPlaybackRate( this.playbackRate );
+
+               return this.connect();
+
+       }
+
+       pause() {
+
+               if ( this.hasPlaybackControl === false ) {
+
+                       console.warn( 'THREE.Audio: this Audio has no playback control.' );
+                       return;
+
+               }
+
+               if ( this.isPlaying === true ) {
+
+                       // update current progress
+
+                       this._progress += Math.max( this.context.currentTime - this._startedAt, 0 ) * this.playbackRate;
+
+                       if ( this.loop === true ) {
+
+                               // ensure _progress does not exceed duration with looped audios
+
+                               this._progress = this._progress % ( this.duration || this.buffer.duration );
+
+                       }
+
+                       this.source.stop();
+                       this.source.onended = null;
+
+                       this.isPlaying = false;
+
+               }
+
+               return this;
+
+       }
+
+       stop() {
+
+               if ( this.hasPlaybackControl === false ) {
+
+                       console.warn( 'THREE.Audio: this Audio has no playback control.' );
+                       return;
+
+               }
+
+               this._progress = 0;
+
+               this.source.stop();
+               this.source.onended = null;
+               this.isPlaying = false;
+
+               return this;
+
+       }
+
+       connect() {
+
+               if ( this.filters.length > 0 ) {
+
+                       this.source.connect( this.filters[ 0 ] );
+
+                       for ( let i = 1, l = this.filters.length; i < l; i ++ ) {
+
+                               this.filters[ i - 1 ].connect( this.filters[ i ] );
+
+                       }
+
+                       this.filters[ this.filters.length - 1 ].connect( this.getOutput() );
+
+               } else {
+
+                       this.source.connect( this.getOutput() );
+
+               }
+
+               this._connected = true;
+
+               return this;
+
+       }
+
+       disconnect() {
+
+               if ( this.filters.length > 0 ) {
+
+                       this.source.disconnect( this.filters[ 0 ] );
+
+                       for ( let i = 1, l = this.filters.length; i < l; i ++ ) {
+
+                               this.filters[ i - 1 ].disconnect( this.filters[ i ] );
+
+                       }
+
+                       this.filters[ this.filters.length - 1 ].disconnect( this.getOutput() );
+
+               } else {
+
+                       this.source.disconnect( this.getOutput() );
+
+               }
+
+               this._connected = false;
+
+               return this;
+
+       }
+
+       getFilters() {
+
+               return this.filters;
+
+       }
+
+       setFilters( value ) {
+
+               if ( ! value ) value = [];
+
+               if ( this._connected === true ) {
+
+                       this.disconnect();
+                       this.filters = value.slice();
+                       this.connect();
+
+               } else {
+
+                       this.filters = value.slice();
+
+               }
+
+               return this;
+
+       }
+
+       setDetune( value ) {
+
+               this.detune = value;
+
+               if ( this.source.detune === undefined ) return; // only set detune when available
+
+               if ( this.isPlaying === true ) {
+
+                       this.source.detune.setTargetAtTime( this.detune, this.context.currentTime, 0.01 );
+
+               }
+
+               return this;
+
+       }
+
+       getDetune() {
+
+               return this.detune;
+
+       }
+
+       getFilter() {
+
+               return this.getFilters()[ 0 ];
+
+       }
+
+       setFilter( filter ) {
+
+               return this.setFilters( filter ? [ filter ] : [] );
+
+       }
+
+       setPlaybackRate( value ) {
+
+               if ( this.hasPlaybackControl === false ) {
+
+                       console.warn( 'THREE.Audio: this Audio has no playback control.' );
+                       return;
+
+               }
+
+               this.playbackRate = value;
+
+               if ( this.isPlaying === true ) {
+
+                       this.source.playbackRate.setTargetAtTime( this.playbackRate, this.context.currentTime, 0.01 );
+
+               }
+
+               return this;
+
+       }
+
+       getPlaybackRate() {
+
+               return this.playbackRate;
+
+       }
+
+       onEnded() {
+
+               this.isPlaying = false;
+
+       }
+
+       getLoop() {
+
+               if ( this.hasPlaybackControl === false ) {
+
+                       console.warn( 'THREE.Audio: this Audio has no playback control.' );
+                       return false;
+
+               }
+
+               return this.loop;
+
+       }
+
+       setLoop( value ) {
+
+               if ( this.hasPlaybackControl === false ) {
+
+                       console.warn( 'THREE.Audio: this Audio has no playback control.' );
+                       return;
+
+               }
+
+               this.loop = value;
+
+               if ( this.isPlaying === true ) {
+
+                       this.source.loop = this.loop;
+
+               }
+
+               return this;
+
+       }
+
+       setLoopStart( value ) {
+
+               this.loopStart = value;
+
+               return this;
+
+       }
+
+       setLoopEnd( value ) {
+
+               this.loopEnd = value;
+
+               return this;
+
+       }
+
+       getVolume() {
+
+               return this.gain.gain.value;
+
+       }
+
+       setVolume( value ) {
+
+               this.gain.gain.setTargetAtTime( value, this.context.currentTime, 0.01 );
+
+               return this;
+
+       }
+
+}
+
+function PropertyMixer( binding, typeName, valueSize ) {
+
+       this.binding = binding;
+       this.valueSize = valueSize;
+
+       let mixFunction,
+               mixFunctionAdditive,
+               setIdentity;
+
+       // buffer layout: [ incoming | accu0 | accu1 | orig | addAccu | (optional work) ]
+       //
+       // interpolators can use .buffer as their .result
+       // the data then goes to 'incoming'
+       //
+       // 'accu0' and 'accu1' are used frame-interleaved for
+       // the cumulative result and are compared to detect
+       // changes
+       //
+       // 'orig' stores the original state of the property
+       //
+       // 'add' is used for additive cumulative results
+       //
+       // 'work' is optional and is only present for quaternion types. It is used
+       // to store intermediate quaternion multiplication results
+
+       switch ( typeName ) {
+
+               case 'quaternion':
+                       mixFunction = this._slerp;
+                       mixFunctionAdditive = this._slerpAdditive;
+                       setIdentity = this._setAdditiveIdentityQuaternion;
+
+                       this.buffer = new Float64Array( valueSize * 6 );
+                       this._workIndex = 5;
+                       break;
+
+               case 'string':
+               case 'bool':
+                       mixFunction = this._select;
+
+                       // Use the regular mix function and for additive on these types,
+                       // additive is not relevant for non-numeric types
+                       mixFunctionAdditive = this._select;
+
+                       setIdentity = this._setAdditiveIdentityOther;
+
+                       this.buffer = new Array( valueSize * 5 );
+                       break;
+
+               default:
+                       mixFunction = this._lerp;
+                       mixFunctionAdditive = this._lerpAdditive;
+                       setIdentity = this._setAdditiveIdentityNumeric;
+
+                       this.buffer = new Float64Array( valueSize * 5 );
+
+       }
+
+       this._mixBufferRegion = mixFunction;
+       this._mixBufferRegionAdditive = mixFunctionAdditive;
+       this._setIdentity = setIdentity;
+       this._origIndex = 3;
+       this._addIndex = 4;
+
+       this.cumulativeWeight = 0;
+       this.cumulativeWeightAdditive = 0;
+
+       this.useCount = 0;
+       this.referenceCount = 0;
+
+}
+
+Object.assign( PropertyMixer.prototype, {
+
+       // accumulate data in the 'incoming' region into 'accu<i>'
+       accumulate: function ( accuIndex, weight ) {
+
+               // note: happily accumulating nothing when weight = 0, the caller knows
+               // the weight and shouldn't have made the call in the first place
+
+               const buffer = this.buffer,
+                       stride = this.valueSize,
+                       offset = accuIndex * stride + stride;
+
+               let currentWeight = this.cumulativeWeight;
+
+               if ( currentWeight === 0 ) {
+
+                       // accuN := incoming * weight
+
+                       for ( let i = 0; i !== stride; ++ i ) {
+
+                               buffer[ offset + i ] = buffer[ i ];
+
+                       }
+
+                       currentWeight = weight;
+
+               } else {
+
+                       // accuN := accuN + incoming * weight
+
+                       currentWeight += weight;
+                       const mix = weight / currentWeight;
+                       this._mixBufferRegion( buffer, offset, 0, mix, stride );
+
+               }
+
+               this.cumulativeWeight = currentWeight;
+
+       },
+
+       // accumulate data in the 'incoming' region into 'add'
+       accumulateAdditive: function ( weight ) {
+
+               const buffer = this.buffer,
+                       stride = this.valueSize,
+                       offset = stride * this._addIndex;
+
+               if ( this.cumulativeWeightAdditive === 0 ) {
+
+                       // add = identity
+
+                       this._setIdentity();
+
+               }
+
+               // add := add + incoming * weight
+
+               this._mixBufferRegionAdditive( buffer, offset, 0, weight, stride );
+               this.cumulativeWeightAdditive += weight;
+
+       },
+
+       // apply the state of 'accu<i>' to the binding when accus differ
+       apply: function ( accuIndex ) {
+
+               const stride = this.valueSize,
+                       buffer = this.buffer,
+                       offset = accuIndex * stride + stride,
+
+                       weight = this.cumulativeWeight,
+                       weightAdditive = this.cumulativeWeightAdditive,
+
+                       binding = this.binding;
+
+               this.cumulativeWeight = 0;
+               this.cumulativeWeightAdditive = 0;
+
+               if ( weight < 1 ) {
+
+                       // accuN := accuN + original * ( 1 - cumulativeWeight )
+
+                       const originalValueOffset = stride * this._origIndex;
+
+                       this._mixBufferRegion(
+                               buffer, offset, originalValueOffset, 1 - weight, stride );
+
+               }
+
+               if ( weightAdditive > 0 ) {
+
+                       // accuN := accuN + additive accuN
+
+                       this._mixBufferRegionAdditive( buffer, offset, this._addIndex * stride, 1, stride );
+
+               }
+
+               for ( let i = stride, e = stride + stride; i !== e; ++ i ) {
+
+                       if ( buffer[ i ] !== buffer[ i + stride ] ) {
+
+                               // value has changed -> update scene graph
+
+                               binding.setValue( buffer, offset );
+                               break;
+
+                       }
+
+               }
+
+       },
+
+       // remember the state of the bound property and copy it to both accus
+       saveOriginalState: function () {
+
+               const binding = this.binding;
+
+               const buffer = this.buffer,
+                       stride = this.valueSize,
+
+                       originalValueOffset = stride * this._origIndex;
+
+               binding.getValue( buffer, originalValueOffset );
+
+               // accu[0..1] := orig -- initially detect changes against the original
+               for ( let i = stride, e = originalValueOffset; i !== e; ++ i ) {
+
+                       buffer[ i ] = buffer[ originalValueOffset + ( i % stride ) ];
+
+               }
+
+               // Add to identity for additive
+               this._setIdentity();
+
+               this.cumulativeWeight = 0;
+               this.cumulativeWeightAdditive = 0;
+
+       },
+
+       // apply the state previously taken via 'saveOriginalState' to the binding
+       restoreOriginalState: function () {
+
+               const originalValueOffset = this.valueSize * 3;
+               this.binding.setValue( this.buffer, originalValueOffset );
+
+       },
+
+       _setAdditiveIdentityNumeric: function () {
+
+               const startIndex = this._addIndex * this.valueSize;
+               const endIndex = startIndex + this.valueSize;
+
+               for ( let i = startIndex; i < endIndex; i ++ ) {
+
+                       this.buffer[ i ] = 0;
+
+               }
+
+       },
+
+       _setAdditiveIdentityQuaternion: function () {
+
+               this._setAdditiveIdentityNumeric();
+               this.buffer[ this._addIndex * this.valueSize + 3 ] = 1;
+
+       },
+
+       _setAdditiveIdentityOther: function () {
+
+               const startIndex = this._origIndex * this.valueSize;
+               const targetIndex = this._addIndex * this.valueSize;
+
+               for ( let i = 0; i < this.valueSize; i ++ ) {
+
+                       this.buffer[ targetIndex + i ] = this.buffer[ startIndex + i ];
+
+               }
+
+       },
+
+
+       // mix functions
+
+       _select: function ( buffer, dstOffset, srcOffset, t, stride ) {
+
+               if ( t >= 0.5 ) {
+
+                       for ( let i = 0; i !== stride; ++ i ) {
+
+                               buffer[ dstOffset + i ] = buffer[ srcOffset + i ];
+
+                       }
+
+               }
+
+       },
+
+       _slerp: function ( buffer, dstOffset, srcOffset, t ) {
+
+               Quaternion.slerpFlat( buffer, dstOffset, buffer, dstOffset, buffer, srcOffset, t );
+
+       },
+
+       _slerpAdditive: function ( buffer, dstOffset, srcOffset, t, stride ) {
+
+               const workOffset = this._workIndex * stride;
+
+               // Store result in intermediate buffer offset
+               Quaternion.multiplyQuaternionsFlat( buffer, workOffset, buffer, dstOffset, buffer, srcOffset );
+
+               // Slerp to the intermediate result
+               Quaternion.slerpFlat( buffer, dstOffset, buffer, dstOffset, buffer, workOffset, t );
+
+       },
+
+       _lerp: function ( buffer, dstOffset, srcOffset, t, stride ) {
+
+               const s = 1 - t;
+
+               for ( let i = 0; i !== stride; ++ i ) {
+
+                       const j = dstOffset + i;
+
+                       buffer[ j ] = buffer[ j ] * s + buffer[ srcOffset + i ] * t;
+
+               }
+
+       },
+
+       _lerpAdditive: function ( buffer, dstOffset, srcOffset, t, stride ) {
+
+               for ( let i = 0; i !== stride; ++ i ) {
+
+                       const j = dstOffset + i;
+
+                       buffer[ j ] = buffer[ j ] + buffer[ srcOffset + i ] * t;
+
+               }
+
+       }
+
+} );
+
+// Characters [].:/ are reserved for track binding syntax.
+const _RESERVED_CHARS_RE = '\\[\\]\\.:\\/';
+const _reservedRe = new RegExp( '[' + _RESERVED_CHARS_RE + ']', 'g' );
+
+// Attempts to allow node names from any language. ES5's `\w` regexp matches
+// only latin characters, and the unicode \p{L} is not yet supported. So
+// instead, we exclude reserved characters and match everything else.
+const _wordChar = '[^' + _RESERVED_CHARS_RE + ']';
+const _wordCharOrDot = '[^' + _RESERVED_CHARS_RE.replace( '\\.', '' ) + ']';
+
+// Parent directories, delimited by '/' or ':'. Currently unused, but must
+// be matched to parse the rest of the track name.
+const _directoryRe = /((?:WC+[\/:])*)/.source.replace( 'WC', _wordChar );
+
+// Target node. May contain word characters (a-zA-Z0-9_) and '.' or '-'.
+const _nodeRe = /(WCOD+)?/.source.replace( 'WCOD', _wordCharOrDot );
+
+// Object on target node, and accessor. May not contain reserved
+// characters. Accessor may contain any character except closing bracket.
+const _objectRe = /(?:\.(WC+)(?:\[(.+)\])?)?/.source.replace( 'WC', _wordChar );
+
+// Property and accessor. May not contain reserved characters. Accessor may
+// contain any non-bracket characters.
+const _propertyRe = /\.(WC+)(?:\[(.+)\])?/.source.replace( 'WC', _wordChar );
+
+const _trackRe = new RegExp( ''
+       + '^'
+       + _directoryRe
+       + _nodeRe
+       + _objectRe
+       + _propertyRe
+       + '$'
+);
+
+const _supportedObjectNames = [ 'material', 'materials', 'bones' ];
+
+function Composite( targetGroup, path, optionalParsedPath ) {
+
+       const parsedPath = optionalParsedPath || PropertyBinding.parseTrackName( path );
+
+       this._targetGroup = targetGroup;
+       this._bindings = targetGroup.subscribe_( path, parsedPath );
+
+}
+
+Object.assign( Composite.prototype, {
+
+       getValue: function ( array, offset ) {
+
+               this.bind(); // bind all binding
+
+               const firstValidIndex = this._targetGroup.nCachedObjects_,
+                       binding = this._bindings[ firstValidIndex ];
+
+               // and only call .getValue on the first
+               if ( binding !== undefined ) binding.getValue( array, offset );
+
+       },
+
+       setValue: function ( array, offset ) {
+
+               const bindings = this._bindings;
+
+               for ( let i = this._targetGroup.nCachedObjects_, n = bindings.length; i !== n; ++ i ) {
+
+                       bindings[ i ].setValue( array, offset );
+
+               }
+
+       },
+
+       bind: function () {
+
+               const bindings = this._bindings;
+
+               for ( let i = this._targetGroup.nCachedObjects_, n = bindings.length; i !== n; ++ i ) {
+
+                       bindings[ i ].bind();
+
+               }
+
+       },
+
+       unbind: function () {
+
+               const bindings = this._bindings;
+
+               for ( let i = this._targetGroup.nCachedObjects_, n = bindings.length; i !== n; ++ i ) {
+
+                       bindings[ i ].unbind();
+
+               }
+
+       }
+
+} );
+
+
+function PropertyBinding( rootNode, path, parsedPath ) {
+
+       this.path = path;
+       this.parsedPath = parsedPath || PropertyBinding.parseTrackName( path );
+
+       this.node = PropertyBinding.findNode( rootNode, this.parsedPath.nodeName ) || rootNode;
+
+       this.rootNode = rootNode;
+
+}
+
+Object.assign( PropertyBinding, {
+
+       Composite: Composite,
+
+       create: function ( root, path, parsedPath ) {
+
+               if ( ! ( root && root.isAnimationObjectGroup ) ) {
+
+                       return new PropertyBinding( root, path, parsedPath );
+
+               } else {
+
+                       return new PropertyBinding.Composite( root, path, parsedPath );
+
+               }
+
+       },
+
+       /**
+        * Replaces spaces with underscores and removes unsupported characters from
+        * node names, to ensure compatibility with parseTrackName().
+        *
+        * @param {string} name Node name to be sanitized.
+        * @return {string}
+        */
+       sanitizeNodeName: function ( name ) {
+
+               return name.replace( /\s/g, '_' ).replace( _reservedRe, '' );
+
+       },
+
+       parseTrackName: function ( trackName ) {
+
+               const matches = _trackRe.exec( trackName );
+
+               if ( ! matches ) {
+
+                       throw new Error( 'PropertyBinding: Cannot parse trackName: ' + trackName );
+
+               }
+
+               const results = {
+                       // directoryName: matches[ 1 ], // (tschw) currently unused
+                       nodeName: matches[ 2 ],
+                       objectName: matches[ 3 ],
+                       objectIndex: matches[ 4 ],
+                       propertyName: matches[ 5 ], // required
+                       propertyIndex: matches[ 6 ]
+               };
+
+               const lastDot = results.nodeName && results.nodeName.lastIndexOf( '.' );
+
+               if ( lastDot !== undefined && lastDot !== - 1 ) {
+
+                       const objectName = results.nodeName.substring( lastDot + 1 );
+
+                       // Object names must be checked against an allowlist. Otherwise, there
+                       // is no way to parse 'foo.bar.baz': 'baz' must be a property, but
+                       // 'bar' could be the objectName, or part of a nodeName (which can
+                       // include '.' characters).
+                       if ( _supportedObjectNames.indexOf( objectName ) !== - 1 ) {
+
+                               results.nodeName = results.nodeName.substring( 0, lastDot );
+                               results.objectName = objectName;
+
+                       }
+
+               }
+
+               if ( results.propertyName === null || results.propertyName.length === 0 ) {
+
+                       throw new Error( 'PropertyBinding: can not parse propertyName from trackName: ' + trackName );
+
+               }
+
+               return results;
+
+       },
+
+       findNode: function ( root, nodeName ) {
+
+               if ( ! nodeName || nodeName === '' || nodeName === '.' || nodeName === - 1 || nodeName === root.name || nodeName === root.uuid ) {
+
+                       return root;
+
+               }
+
+               // search into skeleton bones.
+               if ( root.skeleton ) {
+
+                       const bone = root.skeleton.getBoneByName( nodeName );
+
+                       if ( bone !== undefined ) {
+
+                               return bone;
+
+                       }
+
+               }
+
+               // search into node subtree.
+               if ( root.children ) {
+
+                       const searchNodeSubtree = function ( children ) {
+
+                               for ( let i = 0; i < children.length; i ++ ) {
+
+                                       const childNode = children[ i ];
+
+                                       if ( childNode.name === nodeName || childNode.uuid === nodeName ) {
+
+                                               return childNode;
+
+                                       }
+
+                                       const result = searchNodeSubtree( childNode.children );
+
+                                       if ( result ) return result;
+
+                               }
+
+                               return null;
+
+                       };
+
+                       const subTreeNode = searchNodeSubtree( root.children );
+
+                       if ( subTreeNode ) {
+
+                               return subTreeNode;
+
+                       }
+
+               }
+
+               return null;
+
+       }
+
+} );
+
+Object.assign( PropertyBinding.prototype, { // prototype, continued
+
+       // these are used to "bind" a nonexistent property
+       _getValue_unavailable: function () {},
+       _setValue_unavailable: function () {},
+
+       BindingType: {
+               Direct: 0,
+               EntireArray: 1,
+               ArrayElement: 2,
+               HasFromToArray: 3
+       },
+
+       Versioning: {
+               None: 0,
+               NeedsUpdate: 1,
+               MatrixWorldNeedsUpdate: 2
+       },
+
+       GetterByBindingType: [
+
+               function getValue_direct( buffer, offset ) {
+
+                       buffer[ offset ] = this.node[ this.propertyName ];
+
+               },
+
+               function getValue_array( buffer, offset ) {
+
+                       const source = this.resolvedProperty;
+
+                       for ( let i = 0, n = source.length; i !== n; ++ i ) {
+
+                               buffer[ offset ++ ] = source[ i ];
+
+                       }
+
+               },
+
+               function getValue_arrayElement( buffer, offset ) {
+
+                       buffer[ offset ] = this.resolvedProperty[ this.propertyIndex ];
+
+               },
+
+               function getValue_toArray( buffer, offset ) {
+
+                       this.resolvedProperty.toArray( buffer, offset );
+
+               }
+
+       ],
+
+       SetterByBindingTypeAndVersioning: [
+
+               [
+                       // Direct
+
+                       function setValue_direct( buffer, offset ) {
+
+                               this.targetObject[ this.propertyName ] = buffer[ offset ];
+
+                       },
+
+                       function setValue_direct_setNeedsUpdate( buffer, offset ) {
+
+                               this.targetObject[ this.propertyName ] = buffer[ offset ];
+                               this.targetObject.needsUpdate = true;
+
+                       },
+
+                       function setValue_direct_setMatrixWorldNeedsUpdate( buffer, offset ) {
+
+                               this.targetObject[ this.propertyName ] = buffer[ offset ];
+                               this.targetObject.matrixWorldNeedsUpdate = true;
+
+                       }
+
+               ], [
+
+                       // EntireArray
+
+                       function setValue_array( buffer, offset ) {
+
+                               const dest = this.resolvedProperty;
+
+                               for ( let i = 0, n = dest.length; i !== n; ++ i ) {
+
+                                       dest[ i ] = buffer[ offset ++ ];
+
+                               }
+
+                       },
+
+                       function setValue_array_setNeedsUpdate( buffer, offset ) {
+
+                               const dest = this.resolvedProperty;
+
+                               for ( let i = 0, n = dest.length; i !== n; ++ i ) {
+
+                                       dest[ i ] = buffer[ offset ++ ];
+
+                               }
+
+                               this.targetObject.needsUpdate = true;
+
+                       },
+
+                       function setValue_array_setMatrixWorldNeedsUpdate( buffer, offset ) {
+
+                               const dest = this.resolvedProperty;
+
+                               for ( let i = 0, n = dest.length; i !== n; ++ i ) {
+
+                                       dest[ i ] = buffer[ offset ++ ];
+
+                               }
+
+                               this.targetObject.matrixWorldNeedsUpdate = true;
+
+                       }
+
+               ], [
+
+                       // ArrayElement
+
+                       function setValue_arrayElement( buffer, offset ) {
+
+                               this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ];
+
+                       },
+
+                       function setValue_arrayElement_setNeedsUpdate( buffer, offset ) {
+
+                               this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ];
+                               this.targetObject.needsUpdate = true;
+
+                       },
+
+                       function setValue_arrayElement_setMatrixWorldNeedsUpdate( buffer, offset ) {
+
+                               this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ];
+                               this.targetObject.matrixWorldNeedsUpdate = true;
+
+                       }
+
+               ], [
+
+                       // HasToFromArray
+
+                       function setValue_fromArray( buffer, offset ) {
+
+                               this.resolvedProperty.fromArray( buffer, offset );
+
+                       },
+
+                       function setValue_fromArray_setNeedsUpdate( buffer, offset ) {
+
+                               this.resolvedProperty.fromArray( buffer, offset );
+                               this.targetObject.needsUpdate = true;
+
+                       },
+
+                       function setValue_fromArray_setMatrixWorldNeedsUpdate( buffer, offset ) {
+
+                               this.resolvedProperty.fromArray( buffer, offset );
+                               this.targetObject.matrixWorldNeedsUpdate = true;
+
+                       }
+
+               ]
+
+       ],
+
+       getValue: function getValue_unbound( targetArray, offset ) {
+
+               this.bind();
+               this.getValue( targetArray, offset );
+
+               // Note: This class uses a State pattern on a per-method basis:
+               // 'bind' sets 'this.getValue' / 'setValue' and shadows the
+               // prototype version of these methods with one that represents
+               // the bound state. When the property is not found, the methods
+               // become no-ops.
+
+       },
+
+       setValue: function getValue_unbound( sourceArray, offset ) {
+
+               this.bind();
+               this.setValue( sourceArray, offset );
+
+       },
+
+       // create getter / setter pair for a property in the scene graph
+       bind: function () {
+
+               let targetObject = this.node;
+               const parsedPath = this.parsedPath;
+
+               const objectName = parsedPath.objectName;
+               const propertyName = parsedPath.propertyName;
+               let propertyIndex = parsedPath.propertyIndex;
+
+               if ( ! targetObject ) {
+
+                       targetObject = PropertyBinding.findNode( this.rootNode, parsedPath.nodeName ) || this.rootNode;
+
+                       this.node = targetObject;
+
+               }
+
+               // set fail state so we can just 'return' on error
+               this.getValue = this._getValue_unavailable;
+               this.setValue = this._setValue_unavailable;
+
+               // ensure there is a value node
+               if ( ! targetObject ) {
+
+                       console.error( 'THREE.PropertyBinding: Trying to update node for track: ' + this.path + ' but it wasn\'t found.' );
+                       return;
+
+               }
+
+               if ( objectName ) {
+
+                       let objectIndex = parsedPath.objectIndex;
+
+                       // special cases were we need to reach deeper into the hierarchy to get the face materials....
+                       switch ( objectName ) {
+
+                               case 'materials':
+
+                                       if ( ! targetObject.material ) {
+
+                                               console.error( 'THREE.PropertyBinding: Can not bind to material as node does not have a material.', this );
+                                               return;
+
+                                       }
+
+                                       if ( ! targetObject.material.materials ) {
+
+                                               console.error( 'THREE.PropertyBinding: Can not bind to material.materials as node.material does not have a materials array.', this );
+                                               return;
+
+                                       }
+
+                                       targetObject = targetObject.material.materials;
+
+                                       break;
+
+                               case 'bones':
+
+                                       if ( ! targetObject.skeleton ) {
+
+                                               console.error( 'THREE.PropertyBinding: Can not bind to bones as node does not have a skeleton.', this );
+                                               return;
+
+                                       }
+
+                                       // potential future optimization: skip this if propertyIndex is already an integer
+                                       // and convert the integer string to a true integer.
+
+                                       targetObject = targetObject.skeleton.bones;
+
+                                       // support resolving morphTarget names into indices.
+                                       for ( let i = 0; i < targetObject.length; i ++ ) {
+
+                                               if ( targetObject[ i ].name === objectIndex ) {
+
+                                                       objectIndex = i;
+                                                       break;
+
+                                               }
+
+                                       }
+
+                                       break;
+
+                               default:
+
+                                       if ( targetObject[ objectName ] === undefined ) {
+
+                                               console.error( 'THREE.PropertyBinding: Can not bind to objectName of node undefined.', this );
+                                               return;
+
+                                       }
+
+                                       targetObject = targetObject[ objectName ];
+
+                       }
+
+
+                       if ( objectIndex !== undefined ) {
+
+                               if ( targetObject[ objectIndex ] === undefined ) {
+
+                                       console.error( 'THREE.PropertyBinding: Trying to bind to objectIndex of objectName, but is undefined.', this, targetObject );
+                                       return;
+
+                               }
+
+                               targetObject = targetObject[ objectIndex ];
+
+                       }
+
+               }
+
+               // resolve property
+               const nodeProperty = targetObject[ propertyName ];
+
+               if ( nodeProperty === undefined ) {
+
+                       const nodeName = parsedPath.nodeName;
+
+                       console.error( 'THREE.PropertyBinding: Trying to update property for track: ' + nodeName +
+                               '.' + propertyName + ' but it wasn\'t found.', targetObject );
+                       return;
+
+               }
+
+               // determine versioning scheme
+               let versioning = this.Versioning.None;
+
+               this.targetObject = targetObject;
+
+               if ( targetObject.needsUpdate !== undefined ) { // material
+
+                       versioning = this.Versioning.NeedsUpdate;
+
+               } else if ( targetObject.matrixWorldNeedsUpdate !== undefined ) { // node transform
+
+                       versioning = this.Versioning.MatrixWorldNeedsUpdate;
+
+               }
+
+               // determine how the property gets bound
+               let bindingType = this.BindingType.Direct;
+
+               if ( propertyIndex !== undefined ) {
+
+                       // access a sub element of the property array (only primitives are supported right now)
+
+                       if ( propertyName === 'morphTargetInfluences' ) {
+
+                               // potential optimization, skip this if propertyIndex is already an integer, and convert the integer string to a true integer.
+
+                               // support resolving morphTarget names into indices.
+                               if ( ! targetObject.geometry ) {
+
+                                       console.error( 'THREE.PropertyBinding: Can not bind to morphTargetInfluences because node does not have a geometry.', this );
+                                       return;
+
+                               }
+
+                               if ( targetObject.geometry.isBufferGeometry ) {
+
+                                       if ( ! targetObject.geometry.morphAttributes ) {
+
+                                               console.error( 'THREE.PropertyBinding: Can not bind to morphTargetInfluences because node does not have a geometry.morphAttributes.', this );
+                                               return;
+
+                                       }
+
+                                       if ( targetObject.morphTargetDictionary[ propertyIndex ] !== undefined ) {
+
+                                               propertyIndex = targetObject.morphTargetDictionary[ propertyIndex ];
+
+                                       }
+
+
+                               } else {
+
+                                       console.error( 'THREE.PropertyBinding: Can not bind to morphTargetInfluences on THREE.Geometry. Use THREE.BufferGeometry instead.', this );
+                                       return;
+
+                               }
+
+                       }
+
+                       bindingType = this.BindingType.ArrayElement;
+
+                       this.resolvedProperty = nodeProperty;
+                       this.propertyIndex = propertyIndex;
+
+               } else if ( nodeProperty.fromArray !== undefined && nodeProperty.toArray !== undefined ) {
+
+                       // must use copy for Object3D.Euler/Quaternion
+
+                       bindingType = this.BindingType.HasFromToArray;
+
+                       this.resolvedProperty = nodeProperty;
+
+               } else if ( Array.isArray( nodeProperty ) ) {
+
+                       bindingType = this.BindingType.EntireArray;
+
+                       this.resolvedProperty = nodeProperty;
+
+               } else {
+
+                       this.propertyName = propertyName;
+
+               }
+
+               // select getter / setter
+               this.getValue = this.GetterByBindingType[ bindingType ];
+               this.setValue = this.SetterByBindingTypeAndVersioning[ bindingType ][ versioning ];
+
+       },
+
+       unbind: function () {
+
+               this.node = null;
+
+               // back to the prototype version of getValue / setValue
+               // note: avoiding to mutate the shape of 'this' via 'delete'
+               this.getValue = this._getValue_unbound;
+               this.setValue = this._setValue_unbound;
+
+       }
+
+} );
+
+// DECLARE ALIAS AFTER assign prototype
+Object.assign( PropertyBinding.prototype, {
+
+       // initial state of these methods that calls 'bind'
+       _getValue_unbound: PropertyBinding.prototype.getValue,
+       _setValue_unbound: PropertyBinding.prototype.setValue,
+
+} );
+
+/**
+ *
+ * A group of objects that receives a shared animation state.
+ *
+ * Usage:
+ *
+ *  - Add objects you would otherwise pass as 'root' to the
+ *    constructor or the .clipAction method of AnimationMixer.
+ *
+ *  - Instead pass this object as 'root'.
+ *
+ *  - You can also add and remove objects later when the mixer
+ *    is running.
+ *
+ * Note:
+ *
+ *    Objects of this class appear as one object to the mixer,
+ *    so cache control of the individual objects must be done
+ *    on the group.
+ *
+ * Limitation:
+ *
+ *  - The animated properties must be compatible among the
+ *    all objects in the group.
+ *
+ *  - A single property can either be controlled through a
+ *    target group or directly, but not both.
+ */
+
+function AnimationObjectGroup() {
+
+       this.uuid = MathUtils.generateUUID();
+
+       // cached objects followed by the active ones
+       this._objects = Array.prototype.slice.call( arguments );
+
+       this.nCachedObjects_ = 0; // threshold
+       // note: read by PropertyBinding.Composite
+
+       const indices = {};
+       this._indicesByUUID = indices; // for bookkeeping
+
+       for ( let i = 0, n = arguments.length; i !== n; ++ i ) {
+
+               indices[ arguments[ i ].uuid ] = i;
+
+       }
+
+       this._paths = []; // inside: string
+       this._parsedPaths = []; // inside: { we don't care, here }
+       this._bindings = []; // inside: Array< PropertyBinding >
+       this._bindingsIndicesByPath = {}; // inside: indices in these arrays
+
+       const scope = this;
+
+       this.stats = {
+
+               objects: {
+                       get total() {
+
+                               return scope._objects.length;
+
+                       },
+                       get inUse() {
+
+                               return this.total - scope.nCachedObjects_;
+
+                       }
+               },
+               get bindingsPerObject() {
+
+                       return scope._bindings.length;
+
+               }
+
+       };
+
+}
+
+Object.assign( AnimationObjectGroup.prototype, {
+
+       isAnimationObjectGroup: true,
+
+       add: function () {
+
+               const objects = this._objects,
+                       indicesByUUID = this._indicesByUUID,
+                       paths = this._paths,
+                       parsedPaths = this._parsedPaths,
+                       bindings = this._bindings,
+                       nBindings = bindings.length;
+
+               let knownObject = undefined,
+                       nObjects = objects.length,
+                       nCachedObjects = this.nCachedObjects_;
+
+               for ( let i = 0, n = arguments.length; i !== n; ++ i ) {
+
+                       const object = arguments[ i ],
+                               uuid = object.uuid;
+                       let index = indicesByUUID[ uuid ];
+
+                       if ( index === undefined ) {
+
+                               // unknown object -> add it to the ACTIVE region
+
+                               index = nObjects ++;
+                               indicesByUUID[ uuid ] = index;
+                               objects.push( object );
+
+                               // accounting is done, now do the same for all bindings
+
+                               for ( let j = 0, m = nBindings; j !== m; ++ j ) {
+
+                                       bindings[ j ].push( new PropertyBinding( object, paths[ j ], parsedPaths[ j ] ) );
+
+                               }
+
+                       } else if ( index < nCachedObjects ) {
+
+                               knownObject = objects[ index ];
+
+                               // move existing object to the ACTIVE region
+
+                               const firstActiveIndex = -- nCachedObjects,
+                                       lastCachedObject = objects[ firstActiveIndex ];
+
+                               indicesByUUID[ lastCachedObject.uuid ] = index;
+                               objects[ index ] = lastCachedObject;
+
+                               indicesByUUID[ uuid ] = firstActiveIndex;
+                               objects[ firstActiveIndex ] = object;
+
+                               // accounting is done, now do the same for all bindings
+
+                               for ( let j = 0, m = nBindings; j !== m; ++ j ) {
+
+                                       const bindingsForPath = bindings[ j ],
+                                               lastCached = bindingsForPath[ firstActiveIndex ];
+
+                                       let binding = bindingsForPath[ index ];
+
+                                       bindingsForPath[ index ] = lastCached;
+
+                                       if ( binding === undefined ) {
+
+                                               // since we do not bother to create new bindings
+                                               // for objects that are cached, the binding may
+                                               // or may not exist
+
+                                               binding = new PropertyBinding( object, paths[ j ], parsedPaths[ j ] );
+
+                                       }
+
+                                       bindingsForPath[ firstActiveIndex ] = binding;
+
+                               }
+
+                       } else if ( objects[ index ] !== knownObject ) {
+
+                               console.error( 'THREE.AnimationObjectGroup: Different objects with the same UUID ' +
+                                       'detected. Clean the caches or recreate your infrastructure when reloading scenes.' );
+
+                       } // else the object is already where we want it to be
+
+               } // for arguments
+
+               this.nCachedObjects_ = nCachedObjects;
+
+       },
+
+       remove: function () {
+
+               const objects = this._objects,
+                       indicesByUUID = this._indicesByUUID,
+                       bindings = this._bindings,
+                       nBindings = bindings.length;
+
+               let nCachedObjects = this.nCachedObjects_;
+
+               for ( let i = 0, n = arguments.length; i !== n; ++ i ) {
+
+                       const object = arguments[ i ],
+                               uuid = object.uuid,
+                               index = indicesByUUID[ uuid ];
+
+                       if ( index !== undefined && index >= nCachedObjects ) {
+
+                               // move existing object into the CACHED region
+
+                               const lastCachedIndex = nCachedObjects ++,
+                                       firstActiveObject = objects[ lastCachedIndex ];
+
+                               indicesByUUID[ firstActiveObject.uuid ] = index;
+                               objects[ index ] = firstActiveObject;
+
+                               indicesByUUID[ uuid ] = lastCachedIndex;
+                               objects[ lastCachedIndex ] = object;
+
+                               // accounting is done, now do the same for all bindings
+
+                               for ( let j = 0, m = nBindings; j !== m; ++ j ) {
+
+                                       const bindingsForPath = bindings[ j ],
+                                               firstActive = bindingsForPath[ lastCachedIndex ],
+                                               binding = bindingsForPath[ index ];
+
+                                       bindingsForPath[ index ] = firstActive;
+                                       bindingsForPath[ lastCachedIndex ] = binding;
+
+                               }
+
+                       }
+
+               } // for arguments
+
+               this.nCachedObjects_ = nCachedObjects;
+
+       },
+
+       // remove & forget
+       uncache: function () {
+
+               const objects = this._objects,
+                       indicesByUUID = this._indicesByUUID,
+                       bindings = this._bindings,
+                       nBindings = bindings.length;
+
+               let nCachedObjects = this.nCachedObjects_,
+                       nObjects = objects.length;
+
+               for ( let i = 0, n = arguments.length; i !== n; ++ i ) {
+
+                       const object = arguments[ i ],
+                               uuid = object.uuid,
+                               index = indicesByUUID[ uuid ];
+
+                       if ( index !== undefined ) {
+
+                               delete indicesByUUID[ uuid ];
+
+                               if ( index < nCachedObjects ) {
+
+                                       // object is cached, shrink the CACHED region
+
+                                       const firstActiveIndex = -- nCachedObjects,
+                                               lastCachedObject = objects[ firstActiveIndex ],
+                                               lastIndex = -- nObjects,
+                                               lastObject = objects[ lastIndex ];
+
+                                       // last cached object takes this object's place
+                                       indicesByUUID[ lastCachedObject.uuid ] = index;
+                                       objects[ index ] = lastCachedObject;
+
+                                       // last object goes to the activated slot and pop
+                                       indicesByUUID[ lastObject.uuid ] = firstActiveIndex;
+                                       objects[ firstActiveIndex ] = lastObject;
+                                       objects.pop();
+
+                                       // accounting is done, now do the same for all bindings
+
+                                       for ( let j = 0, m = nBindings; j !== m; ++ j ) {
+
+                                               const bindingsForPath = bindings[ j ],
+                                                       lastCached = bindingsForPath[ firstActiveIndex ],
+                                                       last = bindingsForPath[ lastIndex ];
+
+                                               bindingsForPath[ index ] = lastCached;
+                                               bindingsForPath[ firstActiveIndex ] = last;
+                                               bindingsForPath.pop();
+
+                                       }
+
+                               } else {
+
+                                       // object is active, just swap with the last and pop
+
+                                       const lastIndex = -- nObjects,
+                                               lastObject = objects[ lastIndex ];
+
+                                       if ( lastIndex > 0 ) {
+
+                                               indicesByUUID[ lastObject.uuid ] = index;
+
+                                       }
+
+                                       objects[ index ] = lastObject;
+                                       objects.pop();
+
+                                       // accounting is done, now do the same for all bindings
+
+                                       for ( let j = 0, m = nBindings; j !== m; ++ j ) {
+
+                                               const bindingsForPath = bindings[ j ];
+
+                                               bindingsForPath[ index ] = bindingsForPath[ lastIndex ];
+                                               bindingsForPath.pop();
+
+                                       }
+
+                               } // cached or active
+
+                       } // if object is known
+
+               } // for arguments
+
+               this.nCachedObjects_ = nCachedObjects;
+
+       },
+
+       // Internal interface used by befriended PropertyBinding.Composite:
+
+       subscribe_: function ( path, parsedPath ) {
+
+               // returns an array of bindings for the given path that is changed
+               // according to the contained objects in the group
+
+               const indicesByPath = this._bindingsIndicesByPath;
+               let index = indicesByPath[ path ];
+               const bindings = this._bindings;
+
+               if ( index !== undefined ) return bindings[ index ];
+
+               const paths = this._paths,
+                       parsedPaths = this._parsedPaths,
+                       objects = this._objects,
+                       nObjects = objects.length,
+                       nCachedObjects = this.nCachedObjects_,
+                       bindingsForPath = new Array( nObjects );
+
+               index = bindings.length;
+
+               indicesByPath[ path ] = index;
+
+               paths.push( path );
+               parsedPaths.push( parsedPath );
+               bindings.push( bindingsForPath );
+
+               for ( let i = nCachedObjects, n = objects.length; i !== n; ++ i ) {
+
+                       const object = objects[ i ];
+                       bindingsForPath[ i ] = new PropertyBinding( object, path, parsedPath );
+
+               }
+
+               return bindingsForPath;
+
+       },
+
+       unsubscribe_: function ( path ) {
+
+               // tells the group to forget about a property path and no longer
+               // update the array previously obtained with 'subscribe_'
+
+               const indicesByPath = this._bindingsIndicesByPath,
+                       index = indicesByPath[ path ];
+
+               if ( index !== undefined ) {
+
+                       const paths = this._paths,
+                               parsedPaths = this._parsedPaths,
+                               bindings = this._bindings,
+                               lastBindingsIndex = bindings.length - 1,
+                               lastBindings = bindings[ lastBindingsIndex ],
+                               lastBindingsPath = path[ lastBindingsIndex ];
+
+                       indicesByPath[ lastBindingsPath ] = index;
+
+                       bindings[ index ] = lastBindings;
+                       bindings.pop();
+
+                       parsedPaths[ index ] = parsedPaths[ lastBindingsIndex ];
+                       parsedPaths.pop();
+
+                       paths[ index ] = paths[ lastBindingsIndex ];
+                       paths.pop();
+
+               }
+
+       }
+
+} );
+
+class AnimationAction {
+
+       constructor( mixer, clip, localRoot = null, blendMode = clip.blendMode ) {
+
+               this._mixer = mixer;
+               this._clip = clip;
+               this._localRoot = localRoot;
+               this.blendMode = blendMode;
+
+               const tracks = clip.tracks,
+                       nTracks = tracks.length,
+                       interpolants = new Array( nTracks );
+
+               const interpolantSettings = {
+                       endingStart: ZeroCurvatureEnding,
+                       endingEnd: ZeroCurvatureEnding
+               };
+
+               for ( let i = 0; i !== nTracks; ++ i ) {
+
+                       const interpolant = tracks[ i ].createInterpolant( null );
+                       interpolants[ i ] = interpolant;
+                       interpolant.settings = interpolantSettings;
+
+               }
+
+               this._interpolantSettings = interpolantSettings;
+
+               this._interpolants = interpolants; // bound by the mixer
+
+               // inside: PropertyMixer (managed by the mixer)
+               this._propertyBindings = new Array( nTracks );
+
+               this._cacheIndex = null; // for the memory manager
+               this._byClipCacheIndex = null; // for the memory manager
+
+               this._timeScaleInterpolant = null;
+               this._weightInterpolant = null;
+
+               this.loop = LoopRepeat;
+               this._loopCount = - 1;
+
+               // global mixer time when the action is to be started
+               // it's set back to 'null' upon start of the action
+               this._startTime = null;
+
+               // scaled local time of the action
+               // gets clamped or wrapped to 0..clip.duration according to loop
+               this.time = 0;
+
+               this.timeScale = 1;
+               this._effectiveTimeScale = 1;
+
+               this.weight = 1;
+               this._effectiveWeight = 1;
+
+               this.repetitions = Infinity; // no. of repetitions when looping
+
+               this.paused = false; // true -> zero effective time scale
+               this.enabled = true; // false -> zero effective weight
+
+               this.clampWhenFinished = false;// keep feeding the last frame?
+
+               this.zeroSlopeAtStart = true;// for smooth interpolation w/o separate
+               this.zeroSlopeAtEnd = true;// clips for start, loop and end
+
+       }
+
+       // State & Scheduling
+
+       play() {
+
+               this._mixer._activateAction( this );
+
+               return this;
+
+       }
+
+       stop() {
+
+               this._mixer._deactivateAction( this );
+
+               return this.reset();
+
+       }
+
+       reset() {
+
+               this.paused = false;
+               this.enabled = true;
+
+               this.time = 0; // restart clip
+               this._loopCount = - 1;// forget previous loops
+               this._startTime = null;// forget scheduling
+
+               return this.stopFading().stopWarping();
+
+       }
+
+       isRunning() {
+
+               return this.enabled && ! this.paused && this.timeScale !== 0 &&
+                       this._startTime === null && this._mixer._isActiveAction( this );
+
+       }
+
+       // return true when play has been called
+       isScheduled() {
+
+               return this._mixer._isActiveAction( this );
+
+       }
+
+       startAt( time ) {
+
+               this._startTime = time;
+
+               return this;
+
+       }
+
+       setLoop( mode, repetitions ) {
+
+               this.loop = mode;
+               this.repetitions = repetitions;
+
+               return this;
+
+       }
+
+       // Weight
+
+       // set the weight stopping any scheduled fading
+       // although .enabled = false yields an effective weight of zero, this
+       // method does *not* change .enabled, because it would be confusing
+       setEffectiveWeight( weight ) {
+
+               this.weight = weight;
+
+               // note: same logic as when updated at runtime
+               this._effectiveWeight = this.enabled ? weight : 0;
+
+               return this.stopFading();
+
+       }
+
+       // return the weight considering fading and .enabled
+       getEffectiveWeight() {
+
+               return this._effectiveWeight;
+
+       }
+
+       fadeIn( duration ) {
+
+               return this._scheduleFading( duration, 0, 1 );
+
+       }
+
+       fadeOut( duration ) {
+
+               return this._scheduleFading( duration, 1, 0 );
+
+       }
+
+       crossFadeFrom( fadeOutAction, duration, warp ) {
+
+               fadeOutAction.fadeOut( duration );
+               this.fadeIn( duration );
+
+               if ( warp ) {
+
+                       const fadeInDuration = this._clip.duration,
+                               fadeOutDuration = fadeOutAction._clip.duration,
+
+                               startEndRatio = fadeOutDuration / fadeInDuration,
+                               endStartRatio = fadeInDuration / fadeOutDuration;
+
+                       fadeOutAction.warp( 1.0, startEndRatio, duration );
+                       this.warp( endStartRatio, 1.0, duration );
+
+               }
+
+               return this;
+
+       }
+
+       crossFadeTo( fadeInAction, duration, warp ) {
+
+               return fadeInAction.crossFadeFrom( this, duration, warp );
+
+       }
+
+       stopFading() {
+
+               const weightInterpolant = this._weightInterpolant;
+
+               if ( weightInterpolant !== null ) {
+
+                       this._weightInterpolant = null;
+                       this._mixer._takeBackControlInterpolant( weightInterpolant );
+
+               }
+
+               return this;
+
+       }
+
+       // Time Scale Control
+
+       // set the time scale stopping any scheduled warping
+       // although .paused = true yields an effective time scale of zero, this
+       // method does *not* change .paused, because it would be confusing
+       setEffectiveTimeScale( timeScale ) {
+
+               this.timeScale = timeScale;
+               this._effectiveTimeScale = this.paused ? 0 : timeScale;
+
+               return this.stopWarping();
+
+       }
+
+       // return the time scale considering warping and .paused
+       getEffectiveTimeScale() {
+
+               return this._effectiveTimeScale;
+
+       }
+
+       setDuration( duration ) {
+
+               this.timeScale = this._clip.duration / duration;
+
+               return this.stopWarping();
+
+       }
+
+       syncWith( action ) {
+
+               this.time = action.time;
+               this.timeScale = action.timeScale;
+
+               return this.stopWarping();
+
+       }
+
+       halt( duration ) {
+
+               return this.warp( this._effectiveTimeScale, 0, duration );
+
+       }
+
+       warp( startTimeScale, endTimeScale, duration ) {
+
+               const mixer = this._mixer,
+                       now = mixer.time,
+                       timeScale = this.timeScale;
+
+               let interpolant = this._timeScaleInterpolant;
+
+               if ( interpolant === null ) {
+
+                       interpolant = mixer._lendControlInterpolant();
+                       this._timeScaleInterpolant = interpolant;
+
+               }
+
+               const times = interpolant.parameterPositions,
+                       values = interpolant.sampleValues;
+
+               times[ 0 ] = now;
+               times[ 1 ] = now + duration;
+
+               values[ 0 ] = startTimeScale / timeScale;
+               values[ 1 ] = endTimeScale / timeScale;
+
+               return this;
+
+       }
+
+       stopWarping() {
+
+               const timeScaleInterpolant = this._timeScaleInterpolant;
+
+               if ( timeScaleInterpolant !== null ) {
+
+                       this._timeScaleInterpolant = null;
+                       this._mixer._takeBackControlInterpolant( timeScaleInterpolant );
+
+               }
+
+               return this;
+
+       }
+
+       // Object Accessors
+
+       getMixer() {
+
+               return this._mixer;
+
+       }
+
+       getClip() {
+
+               return this._clip;
+
+       }
+
+       getRoot() {
+
+               return this._localRoot || this._mixer._root;
+
+       }
+
+       // Interna
+
+       _update( time, deltaTime, timeDirection, accuIndex ) {
+
+               // called by the mixer
+
+               if ( ! this.enabled ) {
+
+                       // call ._updateWeight() to update ._effectiveWeight
+
+                       this._updateWeight( time );
+                       return;
+
+               }
+
+               const startTime = this._startTime;
+
+               if ( startTime !== null ) {
+
+                       // check for scheduled start of action
+
+                       const timeRunning = ( time - startTime ) * timeDirection;
+                       if ( timeRunning < 0 || timeDirection === 0 ) {
+
+                               return; // yet to come / don't decide when delta = 0
+
+                       }
+
+                       // start
+
+                       this._startTime = null; // unschedule
+                       deltaTime = timeDirection * timeRunning;
+
+               }
+
+               // apply time scale and advance time
+
+               deltaTime *= this._updateTimeScale( time );
+               const clipTime = this._updateTime( deltaTime );
+
+               // note: _updateTime may disable the action resulting in
+               // an effective weight of 0
+
+               const weight = this._updateWeight( time );
+
+               if ( weight > 0 ) {
+
+                       const interpolants = this._interpolants;
+                       const propertyMixers = this._propertyBindings;
+
+                       switch ( this.blendMode ) {
+
+                               case AdditiveAnimationBlendMode:
+
+                                       for ( let j = 0, m = interpolants.length; j !== m; ++ j ) {
+
+                                               interpolants[ j ].evaluate( clipTime );
+                                               propertyMixers[ j ].accumulateAdditive( weight );
+
+                                       }
+
+                                       break;
+
+                               case NormalAnimationBlendMode:
+                               default:
+
+                                       for ( let j = 0, m = interpolants.length; j !== m; ++ j ) {
+
+                                               interpolants[ j ].evaluate( clipTime );
+                                               propertyMixers[ j ].accumulate( accuIndex, weight );
+
+                                       }
+
+                       }
+
+               }
+
+       }
+
+       _updateWeight( time ) {
+
+               let weight = 0;
+
+               if ( this.enabled ) {
+
+                       weight = this.weight;
+                       const interpolant = this._weightInterpolant;
+
+                       if ( interpolant !== null ) {
+
+                               const interpolantValue = interpolant.evaluate( time )[ 0 ];
+
+                               weight *= interpolantValue;
+
+                               if ( time > interpolant.parameterPositions[ 1 ] ) {
+
+                                       this.stopFading();
+
+                                       if ( interpolantValue === 0 ) {
+
+                                               // faded out, disable
+                                               this.enabled = false;
+
+                                       }
+
+                               }
+
+                       }
+
+               }
+
+               this._effectiveWeight = weight;
+               return weight;
+
+       }
+
+       _updateTimeScale( time ) {
+
+               let timeScale = 0;
+
+               if ( ! this.paused ) {
+
+                       timeScale = this.timeScale;
+
+                       const interpolant = this._timeScaleInterpolant;
+
+                       if ( interpolant !== null ) {
+
+                               const interpolantValue = interpolant.evaluate( time )[ 0 ];
+
+                               timeScale *= interpolantValue;
+
+                               if ( time > interpolant.parameterPositions[ 1 ] ) {
+
+                                       this.stopWarping();
+
+                                       if ( timeScale === 0 ) {
+
+                                               // motion has halted, pause
+                                               this.paused = true;
+
+                                       } else {
+
+                                               // warp done - apply final time scale
+                                               this.timeScale = timeScale;
+
+                                       }
+
+                               }
+
+                       }
+
+               }
+
+               this._effectiveTimeScale = timeScale;
+               return timeScale;
+
+       }
+
+       _updateTime( deltaTime ) {
+
+               const duration = this._clip.duration;
+               const loop = this.loop;
+
+               let time = this.time + deltaTime;
+               let loopCount = this._loopCount;
+
+               const pingPong = ( loop === LoopPingPong );
+
+               if ( deltaTime === 0 ) {
+
+                       if ( loopCount === - 1 ) return time;
+
+                       return ( pingPong && ( loopCount & 1 ) === 1 ) ? duration - time : time;
+
+               }
+
+               if ( loop === LoopOnce ) {
+
+                       if ( loopCount === - 1 ) {
+
+                               // just started
+
+                               this._loopCount = 0;
+                               this._setEndings( true, true, false );
+
+                       }
+
+                       handle_stop: {
+
+                               if ( time >= duration ) {
+
+                                       time = duration;
+
+                               } else if ( time < 0 ) {
+
+                                       time = 0;
+
+                               } else {
+
+                                       this.time = time;
+
+                                       break handle_stop;
+
+                               }
+
+                               if ( this.clampWhenFinished ) this.paused = true;
+                               else this.enabled = false;
+
+                               this.time = time;
+
+                               this._mixer.dispatchEvent( {
+                                       type: 'finished', action: this,
+                                       direction: deltaTime < 0 ? - 1 : 1
+                               } );
+
+                       }
+
+               } else { // repetitive Repeat or PingPong
+
+                       if ( loopCount === - 1 ) {
+
+                               // just started
+
+                               if ( deltaTime >= 0 ) {
+
+                                       loopCount = 0;
+
+                                       this._setEndings( true, this.repetitions === 0, pingPong );
+
+                               } else {
+
+                                       // when looping in reverse direction, the initial
+                                       // transition through zero counts as a repetition,
+                                       // so leave loopCount at -1
+
+                                       this._setEndings( this.repetitions === 0, true, pingPong );
+
+                               }
+
+                       }
+
+                       if ( time >= duration || time < 0 ) {
+
+                               // wrap around
+
+                               const loopDelta = Math.floor( time / duration ); // signed
+                               time -= duration * loopDelta;
+
+                               loopCount += Math.abs( loopDelta );
+
+                               const pending = this.repetitions - loopCount;
+
+                               if ( pending <= 0 ) {
+
+                                       // have to stop (switch state, clamp time, fire event)
+
+                                       if ( this.clampWhenFinished ) this.paused = true;
+                                       else this.enabled = false;
+
+                                       time = deltaTime > 0 ? duration : 0;
+
+                                       this.time = time;
+
+                                       this._mixer.dispatchEvent( {
+                                               type: 'finished', action: this,
+                                               direction: deltaTime > 0 ? 1 : - 1
+                                       } );
+
+                               } else {
+
+                                       // keep running
+
+                                       if ( pending === 1 ) {
+
+                                               // entering the last round
+
+                                               const atStart = deltaTime < 0;
+                                               this._setEndings( atStart, ! atStart, pingPong );
+
+                                       } else {
+
+                                               this._setEndings( false, false, pingPong );
+
+                                       }
+
+                                       this._loopCount = loopCount;
+
+                                       this.time = time;
+
+                                       this._mixer.dispatchEvent( {
+                                               type: 'loop', action: this, loopDelta: loopDelta
+                                       } );
+
+                               }
+
+                       } else {
+
+                               this.time = time;
+
+                       }
+
+                       if ( pingPong && ( loopCount & 1 ) === 1 ) {
+
+                               // invert time for the "pong round"
+
+                               return duration - time;
+
+                       }
+
+               }
+
+               return time;
+
+       }
+
+       _setEndings( atStart, atEnd, pingPong ) {
+
+               const settings = this._interpolantSettings;
+
+               if ( pingPong ) {
+
+                       settings.endingStart = ZeroSlopeEnding;
+                       settings.endingEnd = ZeroSlopeEnding;
+
+               } else {
+
+                       // assuming for LoopOnce atStart == atEnd == true
+
+                       if ( atStart ) {
+
+                               settings.endingStart = this.zeroSlopeAtStart ? ZeroSlopeEnding : ZeroCurvatureEnding;
+
+                       } else {
+
+                               settings.endingStart = WrapAroundEnding;
+
+                       }
+
+                       if ( atEnd ) {
+
+                               settings.endingEnd = this.zeroSlopeAtEnd ? ZeroSlopeEnding : ZeroCurvatureEnding;
+
+                       } else {
+
+                               settings.endingEnd       = WrapAroundEnding;
+
+                       }
+
+               }
+
+       }
+
+       _scheduleFading( duration, weightNow, weightThen ) {
+
+               const mixer = this._mixer, now = mixer.time;
+               let interpolant = this._weightInterpolant;
+
+               if ( interpolant === null ) {
+
+                       interpolant = mixer._lendControlInterpolant();
+                       this._weightInterpolant = interpolant;
+
+               }
+
+               const times = interpolant.parameterPositions,
+                       values = interpolant.sampleValues;
+
+               times[ 0 ] = now;
+               values[ 0 ] = weightNow;
+               times[ 1 ] = now + duration;
+               values[ 1 ] = weightThen;
+
+               return this;
+
+       }
+
+}
+
+function AnimationMixer( root ) {
+
+       this._root = root;
+       this._initMemoryManager();
+       this._accuIndex = 0;
+
+       this.time = 0;
+
+       this.timeScale = 1.0;
+
+}
+
+AnimationMixer.prototype = Object.assign( Object.create( EventDispatcher.prototype ), {
+
+       constructor: AnimationMixer,
+
+       _bindAction: function ( action, prototypeAction ) {
+
+               const root = action._localRoot || this._root,
+                       tracks = action._clip.tracks,
+                       nTracks = tracks.length,
+                       bindings = action._propertyBindings,
+                       interpolants = action._interpolants,
+                       rootUuid = root.uuid,
+                       bindingsByRoot = this._bindingsByRootAndName;
+
+               let bindingsByName = bindingsByRoot[ rootUuid ];
+
+               if ( bindingsByName === undefined ) {
+
+                       bindingsByName = {};
+                       bindingsByRoot[ rootUuid ] = bindingsByName;
+
+               }
+
+               for ( let i = 0; i !== nTracks; ++ i ) {
+
+                       const track = tracks[ i ],
+                               trackName = track.name;
+
+                       let binding = bindingsByName[ trackName ];
+
+                       if ( binding !== undefined ) {
+
+                               bindings[ i ] = binding;
+
+                       } else {
+
+                               binding = bindings[ i ];
+
+                               if ( binding !== undefined ) {
+
+                                       // existing binding, make sure the cache knows
+
+                                       if ( binding._cacheIndex === null ) {
+
+                                               ++ binding.referenceCount;
+                                               this._addInactiveBinding( binding, rootUuid, trackName );
+
+                                       }
+
+                                       continue;
+
+                               }
+
+                               const path = prototypeAction && prototypeAction.
+                                       _propertyBindings[ i ].binding.parsedPath;
+
+                               binding = new PropertyMixer(
+                                       PropertyBinding.create( root, trackName, path ),
+                                       track.ValueTypeName, track.getValueSize() );
+
+                               ++ binding.referenceCount;
+                               this._addInactiveBinding( binding, rootUuid, trackName );
+
+                               bindings[ i ] = binding;
+
+                       }
+
+                       interpolants[ i ].resultBuffer = binding.buffer;
+
+               }
+
+       },
+
+       _activateAction: function ( action ) {
+
+               if ( ! this._isActiveAction( action ) ) {
+
+                       if ( action._cacheIndex === null ) {
+
+                               // this action has been forgotten by the cache, but the user
+                               // appears to be still using it -> rebind
+
+                               const rootUuid = ( action._localRoot || this._root ).uuid,
+                                       clipUuid = action._clip.uuid,
+                                       actionsForClip = this._actionsByClip[ clipUuid ];
+
+                               this._bindAction( action,
+                                       actionsForClip && actionsForClip.knownActions[ 0 ] );
+
+                               this._addInactiveAction( action, clipUuid, rootUuid );
+
+                       }
+
+                       const bindings = action._propertyBindings;
+
+                       // increment reference counts / sort out state
+                       for ( let i = 0, n = bindings.length; i !== n; ++ i ) {
+
+                               const binding = bindings[ i ];
+
+                               if ( binding.useCount ++ === 0 ) {
+
+                                       this._lendBinding( binding );
+                                       binding.saveOriginalState();
+
+                               }
+
+                       }
+
+                       this._lendAction( action );
+
+               }
+
+       },
+
+       _deactivateAction: function ( action ) {
+
+               if ( this._isActiveAction( action ) ) {
+
+                       const bindings = action._propertyBindings;
+
+                       // decrement reference counts / sort out state
+                       for ( let i = 0, n = bindings.length; i !== n; ++ i ) {
+
+                               const binding = bindings[ i ];
+
+                               if ( -- binding.useCount === 0 ) {
+
+                                       binding.restoreOriginalState();
+                                       this._takeBackBinding( binding );
+
+                               }
+
+                       }
+
+                       this._takeBackAction( action );
+
+               }
+
+       },
+
+       // Memory manager
+
+       _initMemoryManager: function () {
+
+               this._actions = []; // 'nActiveActions' followed by inactive ones
+               this._nActiveActions = 0;
+
+               this._actionsByClip = {};
+               // inside:
+               // {
+               //      knownActions: Array< AnimationAction > - used as prototypes
+               //      actionByRoot: AnimationAction - lookup
+               // }
+
+
+               this._bindings = []; // 'nActiveBindings' followed by inactive ones
+               this._nActiveBindings = 0;
+
+               this._bindingsByRootAndName = {}; // inside: Map< name, PropertyMixer >
+
+
+               this._controlInterpolants = []; // same game as above
+               this._nActiveControlInterpolants = 0;
+
+               const scope = this;
+
+               this.stats = {
+
+                       actions: {
+                               get total() {
+
+                                       return scope._actions.length;
+
+                               },
+                               get inUse() {
+
+                                       return scope._nActiveActions;
+
+                               }
+                       },
+                       bindings: {
+                               get total() {
+
+                                       return scope._bindings.length;
+
+                               },
+                               get inUse() {
+
+                                       return scope._nActiveBindings;
+
+                               }
+                       },
+                       controlInterpolants: {
+                               get total() {
+
+                                       return scope._controlInterpolants.length;
+
+                               },
+                               get inUse() {
+
+                                       return scope._nActiveControlInterpolants;
+
+                               }
+                       }
+
+               };
+
+       },
+
+       // Memory management for AnimationAction objects
+
+       _isActiveAction: function ( action ) {
+
+               const index = action._cacheIndex;
+               return index !== null && index < this._nActiveActions;
+
+       },
+
+       _addInactiveAction: function ( action, clipUuid, rootUuid ) {
+
+               const actions = this._actions,
+                       actionsByClip = this._actionsByClip;
+
+               let actionsForClip = actionsByClip[ clipUuid ];
+
+               if ( actionsForClip === undefined ) {
+
+                       actionsForClip = {
+
+                               knownActions: [ action ],
+                               actionByRoot: {}
+
+                       };
+
+                       action._byClipCacheIndex = 0;
+
+                       actionsByClip[ clipUuid ] = actionsForClip;
+
+               } else {
+
+                       const knownActions = actionsForClip.knownActions;
+
+                       action._byClipCacheIndex = knownActions.length;
+                       knownActions.push( action );
+
+               }
+
+               action._cacheIndex = actions.length;
+               actions.push( action );
+
+               actionsForClip.actionByRoot[ rootUuid ] = action;
+
+       },
+
+       _removeInactiveAction: function ( action ) {
+
+               const actions = this._actions,
+                       lastInactiveAction = actions[ actions.length - 1 ],
+                       cacheIndex = action._cacheIndex;
+
+               lastInactiveAction._cacheIndex = cacheIndex;
+               actions[ cacheIndex ] = lastInactiveAction;
+               actions.pop();
+
+               action._cacheIndex = null;
+
+
+               const clipUuid = action._clip.uuid,
+                       actionsByClip = this._actionsByClip,
+                       actionsForClip = actionsByClip[ clipUuid ],
+                       knownActionsForClip = actionsForClip.knownActions,
+
+                       lastKnownAction =
+                               knownActionsForClip[ knownActionsForClip.length - 1 ],
+
+                       byClipCacheIndex = action._byClipCacheIndex;
+
+               lastKnownAction._byClipCacheIndex = byClipCacheIndex;
+               knownActionsForClip[ byClipCacheIndex ] = lastKnownAction;
+               knownActionsForClip.pop();
+
+               action._byClipCacheIndex = null;
+
+
+               const actionByRoot = actionsForClip.actionByRoot,
+                       rootUuid = ( action._localRoot || this._root ).uuid;
+
+               delete actionByRoot[ rootUuid ];
+
+               if ( knownActionsForClip.length === 0 ) {
+
+                       delete actionsByClip[ clipUuid ];
+
+               }
+
+               this._removeInactiveBindingsForAction( action );
+
+       },
+
+       _removeInactiveBindingsForAction: function ( action ) {
+
+               const bindings = action._propertyBindings;
+
+               for ( let i = 0, n = bindings.length; i !== n; ++ i ) {
+
+                       const binding = bindings[ i ];
+
+                       if ( -- binding.referenceCount === 0 ) {
+
+                               this._removeInactiveBinding( binding );
+
+                       }
+
+               }
+
+       },
+
+       _lendAction: function ( action ) {
+
+               // [ active actions |  inactive actions  ]
+               // [  active actions >| inactive actions ]
+               //                 s        a
+               //                  <-swap->
+               //                 a        s
+
+               const actions = this._actions,
+                       prevIndex = action._cacheIndex,
+
+                       lastActiveIndex = this._nActiveActions ++,
+
+                       firstInactiveAction = actions[ lastActiveIndex ];
+
+               action._cacheIndex = lastActiveIndex;
+               actions[ lastActiveIndex ] = action;
+
+               firstInactiveAction._cacheIndex = prevIndex;
+               actions[ prevIndex ] = firstInactiveAction;
+
+       },
+
+       _takeBackAction: function ( action ) {
+
+               // [  active actions  | inactive actions ]
+               // [ active actions |< inactive actions  ]
+               //        a        s
+               //         <-swap->
+               //        s        a
+
+               const actions = this._actions,
+                       prevIndex = action._cacheIndex,
+
+                       firstInactiveIndex = -- this._nActiveActions,
+
+                       lastActiveAction = actions[ firstInactiveIndex ];
+
+               action._cacheIndex = firstInactiveIndex;
+               actions[ firstInactiveIndex ] = action;
+
+               lastActiveAction._cacheIndex = prevIndex;
+               actions[ prevIndex ] = lastActiveAction;
+
+       },
+
+       // Memory management for PropertyMixer objects
+
+       _addInactiveBinding: function ( binding, rootUuid, trackName ) {
+
+               const bindingsByRoot = this._bindingsByRootAndName,
+                       bindings = this._bindings;
+
+               let bindingByName = bindingsByRoot[ rootUuid ];
+
+               if ( bindingByName === undefined ) {
+
+                       bindingByName = {};
+                       bindingsByRoot[ rootUuid ] = bindingByName;
+
+               }
+
+               bindingByName[ trackName ] = binding;
+
+               binding._cacheIndex = bindings.length;
+               bindings.push( binding );
+
+       },
+
+       _removeInactiveBinding: function ( binding ) {
+
+               const bindings = this._bindings,
+                       propBinding = binding.binding,
+                       rootUuid = propBinding.rootNode.uuid,
+                       trackName = propBinding.path,
+                       bindingsByRoot = this._bindingsByRootAndName,
+                       bindingByName = bindingsByRoot[ rootUuid ],
+
+                       lastInactiveBinding = bindings[ bindings.length - 1 ],
+                       cacheIndex = binding._cacheIndex;
+
+               lastInactiveBinding._cacheIndex = cacheIndex;
+               bindings[ cacheIndex ] = lastInactiveBinding;
+               bindings.pop();
+
+               delete bindingByName[ trackName ];
+
+               if ( Object.keys( bindingByName ).length === 0 ) {
+
+                       delete bindingsByRoot[ rootUuid ];
+
+               }
+
+       },
+
+       _lendBinding: function ( binding ) {
+
+               const bindings = this._bindings,
+                       prevIndex = binding._cacheIndex,
+
+                       lastActiveIndex = this._nActiveBindings ++,
+
+                       firstInactiveBinding = bindings[ lastActiveIndex ];
+
+               binding._cacheIndex = lastActiveIndex;
+               bindings[ lastActiveIndex ] = binding;
+
+               firstInactiveBinding._cacheIndex = prevIndex;
+               bindings[ prevIndex ] = firstInactiveBinding;
+
+       },
+
+       _takeBackBinding: function ( binding ) {
+
+               const bindings = this._bindings,
+                       prevIndex = binding._cacheIndex,
+
+                       firstInactiveIndex = -- this._nActiveBindings,
+
+                       lastActiveBinding = bindings[ firstInactiveIndex ];
+
+               binding._cacheIndex = firstInactiveIndex;
+               bindings[ firstInactiveIndex ] = binding;
+
+               lastActiveBinding._cacheIndex = prevIndex;
+               bindings[ prevIndex ] = lastActiveBinding;
+
+       },
+
+
+       // Memory management of Interpolants for weight and time scale
+
+       _lendControlInterpolant: function () {
+
+               const interpolants = this._controlInterpolants,
+                       lastActiveIndex = this._nActiveControlInterpolants ++;
+
+               let interpolant = interpolants[ lastActiveIndex ];
+
+               if ( interpolant === undefined ) {
+
+                       interpolant = new LinearInterpolant(
+                               new Float32Array( 2 ), new Float32Array( 2 ),
+                               1, this._controlInterpolantsResultBuffer );
+
+                       interpolant.__cacheIndex = lastActiveIndex;
+                       interpolants[ lastActiveIndex ] = interpolant;
+
+               }
+
+               return interpolant;
+
+       },
+
+       _takeBackControlInterpolant: function ( interpolant ) {
+
+               const interpolants = this._controlInterpolants,
+                       prevIndex = interpolant.__cacheIndex,
+
+                       firstInactiveIndex = -- this._nActiveControlInterpolants,
+
+                       lastActiveInterpolant = interpolants[ firstInactiveIndex ];
+
+               interpolant.__cacheIndex = firstInactiveIndex;
+               interpolants[ firstInactiveIndex ] = interpolant;
+
+               lastActiveInterpolant.__cacheIndex = prevIndex;
+               interpolants[ prevIndex ] = lastActiveInterpolant;
+
+       },
+
+       _controlInterpolantsResultBuffer: new Float32Array( 1 ),
+
+       // return an action for a clip optionally using a custom root target
+       // object (this method allocates a lot of dynamic memory in case a
+       // previously unknown clip/root combination is specified)
+       clipAction: function ( clip, optionalRoot, blendMode ) {
+
+               const root = optionalRoot || this._root,
+                       rootUuid = root.uuid;
+
+               let clipObject = typeof clip === 'string' ? AnimationClip.findByName( root, clip ) : clip;
+
+               const clipUuid = clipObject !== null ? clipObject.uuid : clip;
+
+               const actionsForClip = this._actionsByClip[ clipUuid ];
+               let prototypeAction = null;
+
+               if ( blendMode === undefined ) {
+
+                       if ( clipObject !== null ) {
+
+                               blendMode = clipObject.blendMode;
+
+                       } else {
+
+                               blendMode = NormalAnimationBlendMode;
+
+                       }
+
+               }
+
+               if ( actionsForClip !== undefined ) {
+
+                       const existingAction = actionsForClip.actionByRoot[ rootUuid ];
+
+                       if ( existingAction !== undefined && existingAction.blendMode === blendMode ) {
+
+                               return existingAction;
+
+                       }
+
+                       // we know the clip, so we don't have to parse all
+                       // the bindings again but can just copy
+                       prototypeAction = actionsForClip.knownActions[ 0 ];
+
+                       // also, take the clip from the prototype action
+                       if ( clipObject === null )
+                               clipObject = prototypeAction._clip;
+
+               }
+
+               // clip must be known when specified via string
+               if ( clipObject === null ) return null;
+
+               // allocate all resources required to run it
+               const newAction = new AnimationAction( this, clipObject, optionalRoot, blendMode );
+
+               this._bindAction( newAction, prototypeAction );
+
+               // and make the action known to the memory manager
+               this._addInactiveAction( newAction, clipUuid, rootUuid );
+
+               return newAction;
+
+       },
+
+       // get an existing action
+       existingAction: function ( clip, optionalRoot ) {
+
+               const root = optionalRoot || this._root,
+                       rootUuid = root.uuid,
+
+                       clipObject = typeof clip === 'string' ?
+                               AnimationClip.findByName( root, clip ) : clip,
+
+                       clipUuid = clipObject ? clipObject.uuid : clip,
+
+                       actionsForClip = this._actionsByClip[ clipUuid ];
+
+               if ( actionsForClip !== undefined ) {
+
+                       return actionsForClip.actionByRoot[ rootUuid ] || null;
+
+               }
+
+               return null;
+
+       },
+
+       // deactivates all previously scheduled actions
+       stopAllAction: function () {
+
+               const actions = this._actions,
+                       nActions = this._nActiveActions;
+
+               for ( let i = nActions - 1; i >= 0; -- i ) {
+
+                       actions[ i ].stop();
+
+               }
+
+               return this;
+
+       },
+
+       // advance the time and update apply the animation
+       update: function ( deltaTime ) {
+
+               deltaTime *= this.timeScale;
+
+               const actions = this._actions,
+                       nActions = this._nActiveActions,
+
+                       time = this.time += deltaTime,
+                       timeDirection = Math.sign( deltaTime ),
+
+                       accuIndex = this._accuIndex ^= 1;
+
+               // run active actions
+
+               for ( let i = 0; i !== nActions; ++ i ) {
+
+                       const action = actions[ i ];
+
+                       action._update( time, deltaTime, timeDirection, accuIndex );
+
+               }
+
+               // update scene graph
+
+               const bindings = this._bindings,
+                       nBindings = this._nActiveBindings;
+
+               for ( let i = 0; i !== nBindings; ++ i ) {
+
+                       bindings[ i ].apply( accuIndex );
+
+               }
+
+               return this;
+
+       },
+
+       // Allows you to seek to a specific time in an animation.
+       setTime: function ( timeInSeconds ) {
+
+               this.time = 0; // Zero out time attribute for AnimationMixer object;
+               for ( let i = 0; i < this._actions.length; i ++ ) {
+
+                       this._actions[ i ].time = 0; // Zero out time attribute for all associated AnimationAction objects.
+
+               }
+
+               return this.update( timeInSeconds ); // Update used to set exact time. Returns "this" AnimationMixer object.
+
+       },
+
+       // return this mixer's root target object
+       getRoot: function () {
+
+               return this._root;
+
+       },
+
+       // free all resources specific to a particular clip
+       uncacheClip: function ( clip ) {
+
+               const actions = this._actions,
+                       clipUuid = clip.uuid,
+                       actionsByClip = this._actionsByClip,
+                       actionsForClip = actionsByClip[ clipUuid ];
+
+               if ( actionsForClip !== undefined ) {
+
+                       // note: just calling _removeInactiveAction would mess up the
+                       // iteration state and also require updating the state we can
+                       // just throw away
+
+                       const actionsToRemove = actionsForClip.knownActions;
+
+                       for ( let i = 0, n = actionsToRemove.length; i !== n; ++ i ) {
+
+                               const action = actionsToRemove[ i ];
+
+                               this._deactivateAction( action );
+
+                               const cacheIndex = action._cacheIndex,
+                                       lastInactiveAction = actions[ actions.length - 1 ];
+
+                               action._cacheIndex = null;
+                               action._byClipCacheIndex = null;
+
+                               lastInactiveAction._cacheIndex = cacheIndex;
+                               actions[ cacheIndex ] = lastInactiveAction;
+                               actions.pop();
+
+                               this._removeInactiveBindingsForAction( action );
+
+                       }
+
+                       delete actionsByClip[ clipUuid ];
+
+               }
+
+       },
+
+       // free all resources specific to a particular root target object
+       uncacheRoot: function ( root ) {
+
+               const rootUuid = root.uuid,
+                       actionsByClip = this._actionsByClip;
+
+               for ( const clipUuid in actionsByClip ) {
+
+                       const actionByRoot = actionsByClip[ clipUuid ].actionByRoot,
+                               action = actionByRoot[ rootUuid ];
+
+                       if ( action !== undefined ) {
+
+                               this._deactivateAction( action );
+                               this._removeInactiveAction( action );
+
+                       }
+
+               }
+
+               const bindingsByRoot = this._bindingsByRootAndName,
+                       bindingByName = bindingsByRoot[ rootUuid ];
+
+               if ( bindingByName !== undefined ) {
+
+                       for ( const trackName in bindingByName ) {
+
+                               const binding = bindingByName[ trackName ];
+                               binding.restoreOriginalState();
+                               this._removeInactiveBinding( binding );
+
+                       }
+
+               }
+
+       },
+
+       // remove a targeted clip from the cache
+       uncacheAction: function ( clip, optionalRoot ) {
+
+               const action = this.existingAction( clip, optionalRoot );
+
+               if ( action !== null ) {
+
+                       this._deactivateAction( action );
+                       this._removeInactiveAction( action );
+
+               }
+
+       }
+
+} );
+
+class Uniform {
+
+       constructor( value ) {
+
+               if ( typeof value === 'string' ) {
+
+                       console.warn( 'THREE.Uniform: Type parameter is no longer needed.' );
+                       value = arguments[ 1 ];
+
+               }
+
+               this.value = value;
+
+       }
+
+       clone() {
+
+               return new Uniform( this.value.clone === undefined ? this.value : this.value.clone() );
+
+       }
+
+}
+
+function InstancedInterleavedBuffer( array, stride, meshPerAttribute ) {
+
+       InterleavedBuffer.call( this, array, stride );
+
+       this.meshPerAttribute = meshPerAttribute || 1;
+
+}
+
+InstancedInterleavedBuffer.prototype = Object.assign( Object.create( InterleavedBuffer.prototype ), {
+
+       constructor: InstancedInterleavedBuffer,
+
+       isInstancedInterleavedBuffer: true,
+
+       copy: function ( source ) {
+
+               InterleavedBuffer.prototype.copy.call( this, source );
+
+               this.meshPerAttribute = source.meshPerAttribute;
+
+               return this;
+
+       },
+
+       clone: function ( data ) {
+
+               const ib = InterleavedBuffer.prototype.clone.call( this, data );
+
+               ib.meshPerAttribute = this.meshPerAttribute;
+
+               return ib;
+
+       },
+
+       toJSON: function ( data ) {
+
+               const json = InterleavedBuffer.prototype.toJSON.call( this, data );
+
+               json.isInstancedInterleavedBuffer = true;
+               json.meshPerAttribute = this.meshPerAttribute;
+
+               return json;
+
+       }
+
+} );
+
+function GLBufferAttribute( buffer, type, itemSize, elementSize, count ) {
+
+       this.buffer = buffer;
+       this.type = type;
+       this.itemSize = itemSize;
+       this.elementSize = elementSize;
+       this.count = count;
+
+       this.version = 0;
+
+}
+
+Object.defineProperty( GLBufferAttribute.prototype, 'needsUpdate', {
+
+       set: function ( value ) {
+
+               if ( value === true ) this.version ++;
+
+       }
+
+} );
+
+Object.assign( GLBufferAttribute.prototype, {
+
+       isGLBufferAttribute: true,
+
+       setBuffer: function ( buffer ) {
+
+               this.buffer = buffer;
+
+               return this;
+
+       },
+
+       setType: function ( type, elementSize ) {
+
+               this.type = type;
+               this.elementSize = elementSize;
+
+               return this;
+
+       },
+
+       setItemSize: function ( itemSize ) {
+
+               this.itemSize = itemSize;
+
+               return this;
+
+       },
+
+       setCount: function ( count ) {
+
+               this.count = count;
+
+               return this;
+
+       },
+
+} );
+
+function Raycaster( origin, direction, near, far ) {
+
+       this.ray = new Ray( origin, direction );
+       // direction is assumed to be normalized (for accurate distance calculations)
+
+       this.near = near || 0;
+       this.far = far || Infinity;
+       this.camera = null;
+       this.layers = new Layers();
+
+       this.params = {
+               Mesh: {},
+               Line: { threshold: 1 },
+               LOD: {},
+               Points: { threshold: 1 },
+               Sprite: {}
+       };
+
+       Object.defineProperties( this.params, {
+               PointCloud: {
+                       get: function () {
+
+                               console.warn( 'THREE.Raycaster: params.PointCloud has been renamed to params.Points.' );
+                               return this.Points;
+
+                       }
+               }
+       } );
+
+}
+
+function ascSort( a, b ) {
+
+       return a.distance - b.distance;
+
+}
+
+function intersectObject( object, raycaster, intersects, recursive ) {
+
+       if ( object.layers.test( raycaster.layers ) ) {
+
+               object.raycast( raycaster, intersects );
+
+       }
+
+       if ( recursive === true ) {
+
+               const children = object.children;
+
+               for ( let i = 0, l = children.length; i < l; i ++ ) {
+
+                       intersectObject( children[ i ], raycaster, intersects, true );
+
+               }
+
+       }
+
+}
+
+Object.assign( Raycaster.prototype, {
+
+       set: function ( origin, direction ) {
+
+               // direction is assumed to be normalized (for accurate distance calculations)
+
+               this.ray.set( origin, direction );
+
+       },
+
+       setFromCamera: function ( coords, camera ) {
+
+               if ( camera && camera.isPerspectiveCamera ) {
+
+                       this.ray.origin.setFromMatrixPosition( camera.matrixWorld );
+                       this.ray.direction.set( coords.x, coords.y, 0.5 ).unproject( camera ).sub( this.ray.origin ).normalize();
+                       this.camera = camera;
+
+               } else if ( camera && camera.isOrthographicCamera ) {
+
+                       this.ray.origin.set( coords.x, coords.y, ( camera.near + camera.far ) / ( camera.near - camera.far ) ).unproject( camera ); // set origin in plane of camera
+                       this.ray.direction.set( 0, 0, - 1 ).transformDirection( camera.matrixWorld );
+                       this.camera = camera;
+
+               } else {
+
+                       console.error( 'THREE.Raycaster: Unsupported camera type: ' + camera.type );
+
+               }
+
+       },
+
+       intersectObject: function ( object, recursive, optionalTarget ) {
+
+               const intersects = optionalTarget || [];
+
+               intersectObject( object, this, intersects, recursive );
+
+               intersects.sort( ascSort );
+
+               return intersects;
+
+       },
+
+       intersectObjects: function ( objects, recursive, optionalTarget ) {
+
+               const intersects = optionalTarget || [];
+
+               if ( Array.isArray( objects ) === false ) {
+
+                       console.warn( 'THREE.Raycaster.intersectObjects: objects is not an Array.' );
+                       return intersects;
+
+               }
+
+               for ( let i = 0, l = objects.length; i < l; i ++ ) {
+
+                       intersectObject( objects[ i ], this, intersects, recursive );
+
+               }
+
+               intersects.sort( ascSort );
+
+               return intersects;
+
+       }
+
+} );
+
+const _vector$8 = /*@__PURE__*/ new Vector2();
+
+class Box2 {
+
+       constructor( min, max ) {
+
+               Object.defineProperty( this, 'isBox2', { value: true } );
+
+               this.min = ( min !== undefined ) ? min : new Vector2( + Infinity, + Infinity );
+               this.max = ( max !== undefined ) ? max : new Vector2( - Infinity, - Infinity );
+
+       }
+
+       set( min, max ) {
+
+               this.min.copy( min );
+               this.max.copy( max );
+
+               return this;
+
+       }
+
+       setFromPoints( points ) {
+
+               this.makeEmpty();
+
+               for ( let i = 0, il = points.length; i < il; i ++ ) {
+
+                       this.expandByPoint( points[ i ] );
+
+               }
+
+               return this;
+
+       }
+
+       setFromCenterAndSize( center, size ) {
+
+               const halfSize = _vector$8.copy( size ).multiplyScalar( 0.5 );
+               this.min.copy( center ).sub( halfSize );
+               this.max.copy( center ).add( halfSize );
+
+               return this;
+
+       }
+
+       clone() {
+
+               return new this.constructor().copy( this );
+
+       }
+
+       copy( box ) {
+
+               this.min.copy( box.min );
+               this.max.copy( box.max );
+
+               return this;
+
+       }
+
+       makeEmpty() {
+
+               this.min.x = this.min.y = + Infinity;
+               this.max.x = this.max.y = - Infinity;
+
+               return this;
+
+       }
+
+       isEmpty() {
+
+               // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes
+
+               return ( this.max.x < this.min.x ) || ( this.max.y < this.min.y );
+
+       }
+
+       getCenter( target ) {
+
+               if ( target === undefined ) {
+
+                       console.warn( 'THREE.Box2: .getCenter() target is now required' );
+                       target = new Vector2();
+
+               }
+
+               return this.isEmpty() ? target.set( 0, 0 ) : target.addVectors( this.min, this.max ).multiplyScalar( 0.5 );
+
+       }
+
+       getSize( target ) {
+
+               if ( target === undefined ) {
+
+                       console.warn( 'THREE.Box2: .getSize() target is now required' );
+                       target = new Vector2();
+
+               }
+
+               return this.isEmpty() ? target.set( 0, 0 ) : target.subVectors( this.max, this.min );
+
+       }
+
+       expandByPoint( point ) {
+
+               this.min.min( point );
+               this.max.max( point );
+
+               return this;
+
+       }
+
+       expandByVector( vector ) {
+
+               this.min.sub( vector );
+               this.max.add( vector );
+
+               return this;
+
+       }
+
+       expandByScalar( scalar ) {
+
+               this.min.addScalar( - scalar );
+               this.max.addScalar( scalar );
+
+               return this;
+
+       }
+
+       containsPoint( point ) {
+
+               return point.x < this.min.x || point.x > this.max.x ||
+                       point.y < this.min.y || point.y > this.max.y ? false : true;
+
+       }
+
+       containsBox( box ) {
+
+               return this.min.x <= box.min.x && box.max.x <= this.max.x &&
+                       this.min.y <= box.min.y && box.max.y <= this.max.y;
+
+       }
+
+       getParameter( point, target ) {
+
+               // This can potentially have a divide by zero if the box
+               // has a size dimension of 0.
+
+               if ( target === undefined ) {
+
+                       console.warn( 'THREE.Box2: .getParameter() target is now required' );
+                       target = new Vector2();
+
+               }
+
+               return target.set(
+                       ( point.x - this.min.x ) / ( this.max.x - this.min.x ),
+                       ( point.y - this.min.y ) / ( this.max.y - this.min.y )
+               );
+
+       }
+
+       intersectsBox( box ) {
+
+               // using 4 splitting planes to rule out intersections
+
+               return box.max.x < this.min.x || box.min.x > this.max.x ||
+                       box.max.y < this.min.y || box.min.y > this.max.y ? false : true;
+
+       }
+
+       clampPoint( point, target ) {
+
+               if ( target === undefined ) {
+
+                       console.warn( 'THREE.Box2: .clampPoint() target is now required' );
+                       target = new Vector2();
+
+               }
+
+               return target.copy( point ).clamp( this.min, this.max );
+
+       }
+
+       distanceToPoint( point ) {
+
+               const clampedPoint = _vector$8.copy( point ).clamp( this.min, this.max );
+               return clampedPoint.sub( point ).length();
+
+       }
+
+       intersect( box ) {
+
+               this.min.max( box.min );
+               this.max.min( box.max );
+
+               return this;
+
+       }
+
+       union( box ) {
+
+               this.min.min( box.min );
+               this.max.max( box.max );
+
+               return this;
+
+       }
+
+       translate( offset ) {
+
+               this.min.add( offset );
+               this.max.add( offset );
+
+               return this;
+
+       }
+
+       equals( box ) {
+
+               return box.min.equals( this.min ) && box.max.equals( this.max );
+
+       }
+
+}
+
+function ImmediateRenderObject( material ) {
+
+       Object3D.call( this );
+
+       this.material = material;
+       this.render = function ( /* renderCallback */ ) {};
+
+       this.hasPositions = false;
+       this.hasNormals = false;
+       this.hasColors = false;
+       this.hasUvs = false;
+
+       this.positionArray = null;
+       this.normalArray = null;
+       this.colorArray = null;
+       this.uvArray = null;
+
+       this.count = 0;
+
+}
+
+ImmediateRenderObject.prototype = Object.create( Object3D.prototype );
+ImmediateRenderObject.prototype.constructor = ImmediateRenderObject;
+
+ImmediateRenderObject.prototype.isImmediateRenderObject = true;
+
+const backgroundMaterial = new MeshBasicMaterial( {
+       side: BackSide,
+       depthWrite: false,
+       depthTest: false,
+} );
+new Mesh( new BoxGeometry(), backgroundMaterial );
+
+//
+
+Curve.create = function ( construct, getPoint ) {
+
+       console.log( 'THREE.Curve.create() has been deprecated' );
+
+       construct.prototype = Object.create( Curve.prototype );
+       construct.prototype.constructor = construct;
+       construct.prototype.getPoint = getPoint;
+
+       return construct;
+
+};
+
+//
+
+Object.assign( Path.prototype, {
+
+       fromPoints: function ( points ) {
+
+               console.warn( 'THREE.Path: .fromPoints() has been renamed to .setFromPoints().' );
+               return this.setFromPoints( points );
+
+       }
+
+} );
+
+//
+
+function Spline( points ) {
+
+       console.warn( 'THREE.Spline has been removed. Use THREE.CatmullRomCurve3 instead.' );
+
+       CatmullRomCurve3.call( this, points );
+       this.type = 'catmullrom';
+
+}
+
+Spline.prototype = Object.create( CatmullRomCurve3.prototype );
+
+Object.assign( Spline.prototype, {
+
+       initFromArray: function ( /* a */ ) {
+
+               console.error( 'THREE.Spline: .initFromArray() has been removed.' );
+
+       },
+       getControlPointsArray: function ( /* optionalTarget */ ) {
+
+               console.error( 'THREE.Spline: .getControlPointsArray() has been removed.' );
+
+       },
+       reparametrizeByArcLength: function ( /* samplingCoef */ ) {
+
+               console.error( 'THREE.Spline: .reparametrizeByArcLength() has been removed.' );
+
+       }
+
+} );
+
+//
+
+Object.assign( Loader.prototype, {
+
+       extractUrlBase: function ( url ) {
+
+               console.warn( 'THREE.Loader: .extractUrlBase() has been deprecated. Use THREE.LoaderUtils.extractUrlBase() instead.' );
+               return LoaderUtils.extractUrlBase( url );
+
+       }
+
+} );
+
+Loader.Handlers = {
+
+       add: function ( /* regex, loader */ ) {
+
+               console.error( 'THREE.Loader: Handlers.add() has been removed. Use LoadingManager.addHandler() instead.' );
+
+       },
+
+       get: function ( /* file */ ) {
+
+               console.error( 'THREE.Loader: Handlers.get() has been removed. Use LoadingManager.getHandler() instead.' );
+
+       }
+
+};
+
+//
+
+Object.assign( Box2.prototype, {
+
+       center: function ( optionalTarget ) {
+
+               console.warn( 'THREE.Box2: .center() has been renamed to .getCenter().' );
+               return this.getCenter( optionalTarget );
+
+       },
+       empty: function () {
+
+               console.warn( 'THREE.Box2: .empty() has been renamed to .isEmpty().' );
+               return this.isEmpty();
+
+       },
+       isIntersectionBox: function ( box ) {
+
+               console.warn( 'THREE.Box2: .isIntersectionBox() has been renamed to .intersectsBox().' );
+               return this.intersectsBox( box );
+
+       },
+       size: function ( optionalTarget ) {
+
+               console.warn( 'THREE.Box2: .size() has been renamed to .getSize().' );
+               return this.getSize( optionalTarget );
+
+       }
+} );
+
+Object.assign( Box3.prototype, {
+
+       center: function ( optionalTarget ) {
+
+               console.warn( 'THREE.Box3: .center() has been renamed to .getCenter().' );
+               return this.getCenter( optionalTarget );
+
+       },
+       empty: function () {
+
+               console.warn( 'THREE.Box3: .empty() has been renamed to .isEmpty().' );
+               return this.isEmpty();
+
+       },
+       isIntersectionBox: function ( box ) {
+
+               console.warn( 'THREE.Box3: .isIntersectionBox() has been renamed to .intersectsBox().' );
+               return this.intersectsBox( box );
+
+       },
+       isIntersectionSphere: function ( sphere ) {
+
+               console.warn( 'THREE.Box3: .isIntersectionSphere() has been renamed to .intersectsSphere().' );
+               return this.intersectsSphere( sphere );
+
+       },
+       size: function ( optionalTarget ) {
+
+               console.warn( 'THREE.Box3: .size() has been renamed to .getSize().' );
+               return this.getSize( optionalTarget );
+
+       }
+} );
+
+Object.assign( Sphere.prototype, {
+
+       empty: function () {
+
+               console.warn( 'THREE.Sphere: .empty() has been renamed to .isEmpty().' );
+               return this.isEmpty();
+
+       },
+
+} );
+
+Frustum.prototype.setFromMatrix = function ( m ) {
+
+       console.warn( 'THREE.Frustum: .setFromMatrix() has been renamed to .setFromProjectionMatrix().' );
+       return this.setFromProjectionMatrix( m );
+
+};
+
+Object.assign( MathUtils, {
+
+       random16: function () {
+
+               console.warn( 'THREE.Math: .random16() has been deprecated. Use Math.random() instead.' );
+               return Math.random();
+
+       },
+
+       nearestPowerOfTwo: function ( value ) {
+
+               console.warn( 'THREE.Math: .nearestPowerOfTwo() has been renamed to .floorPowerOfTwo().' );
+               return MathUtils.floorPowerOfTwo( value );
+
+       },
+
+       nextPowerOfTwo: function ( value ) {
+
+               console.warn( 'THREE.Math: .nextPowerOfTwo() has been renamed to .ceilPowerOfTwo().' );
+               return MathUtils.ceilPowerOfTwo( value );
+
+       }
+
+} );
+
+Object.assign( Matrix3.prototype, {
+
+       flattenToArrayOffset: function ( array, offset ) {
+
+               console.warn( 'THREE.Matrix3: .flattenToArrayOffset() has been deprecated. Use .toArray() instead.' );
+               return this.toArray( array, offset );
+
+       },
+       multiplyVector3: function ( vector ) {
+
+               console.warn( 'THREE.Matrix3: .multiplyVector3() has been removed. Use vector.applyMatrix3( matrix ) instead.' );
+               return vector.applyMatrix3( this );
+
+       },
+       multiplyVector3Array: function ( /* a */ ) {
+
+               console.error( 'THREE.Matrix3: .multiplyVector3Array() has been removed.' );
+
+       },
+       applyToBufferAttribute: function ( attribute ) {
+
+               console.warn( 'THREE.Matrix3: .applyToBufferAttribute() has been removed. Use attribute.applyMatrix3( matrix ) instead.' );
+               return attribute.applyMatrix3( this );
+
+       },
+       applyToVector3Array: function ( /* array, offset, length */ ) {
+
+               console.error( 'THREE.Matrix3: .applyToVector3Array() has been removed.' );
+
+       },
+       getInverse: function ( matrix ) {
+
+               console.warn( 'THREE.Matrix3: .getInverse() has been removed. Use matrixInv.copy( matrix ).invert(); instead.' );
+               return this.copy( matrix ).invert();
+
+       }
+
+} );
+
+Object.assign( Matrix4.prototype, {
+
+       extractPosition: function ( m ) {
+
+               console.warn( 'THREE.Matrix4: .extractPosition() has been renamed to .copyPosition().' );
+               return this.copyPosition( m );
+
+       },
+       flattenToArrayOffset: function ( array, offset ) {
+
+               console.warn( 'THREE.Matrix4: .flattenToArrayOffset() has been deprecated. Use .toArray() instead.' );
+               return this.toArray( array, offset );
+
+       },
+       getPosition: function () {
+
+               console.warn( 'THREE.Matrix4: .getPosition() has been removed. Use Vector3.setFromMatrixPosition( matrix ) instead.' );
+               return new Vector3().setFromMatrixColumn( this, 3 );
+
+       },
+       setRotationFromQuaternion: function ( q ) {
+
+               console.warn( 'THREE.Matrix4: .setRotationFromQuaternion() has been renamed to .makeRotationFromQuaternion().' );
+               return this.makeRotationFromQuaternion( q );
+
+       },
+       multiplyToArray: function () {
+
+               console.warn( 'THREE.Matrix4: .multiplyToArray() has been removed.' );
+
+       },
+       multiplyVector3: function ( vector ) {
+
+               console.warn( 'THREE.Matrix4: .multiplyVector3() has been removed. Use vector.applyMatrix4( matrix ) instead.' );
+               return vector.applyMatrix4( this );
+
+       },
+       multiplyVector4: function ( vector ) {
+
+               console.warn( 'THREE.Matrix4: .multiplyVector4() has been removed. Use vector.applyMatrix4( matrix ) instead.' );
+               return vector.applyMatrix4( this );
+
+       },
+       multiplyVector3Array: function ( /* a */ ) {
+
+               console.error( 'THREE.Matrix4: .multiplyVector3Array() has been removed.' );
+
+       },
+       rotateAxis: function ( v ) {
+
+               console.warn( 'THREE.Matrix4: .rotateAxis() has been removed. Use Vector3.transformDirection( matrix ) instead.' );
+               v.transformDirection( this );
+
+       },
+       crossVector: function ( vector ) {
+
+               console.warn( 'THREE.Matrix4: .crossVector() has been removed. Use vector.applyMatrix4( matrix ) instead.' );
+               return vector.applyMatrix4( this );
+
+       },
+       translate: function () {
+
+               console.error( 'THREE.Matrix4: .translate() has been removed.' );
+
+       },
+       rotateX: function () {
+
+               console.error( 'THREE.Matrix4: .rotateX() has been removed.' );
+
+       },
+       rotateY: function () {
+
+               console.error( 'THREE.Matrix4: .rotateY() has been removed.' );
+
+       },
+       rotateZ: function () {
+
+               console.error( 'THREE.Matrix4: .rotateZ() has been removed.' );
+
+       },
+       rotateByAxis: function () {
+
+               console.error( 'THREE.Matrix4: .rotateByAxis() has been removed.' );
+
+       },
+       applyToBufferAttribute: function ( attribute ) {
+
+               console.warn( 'THREE.Matrix4: .applyToBufferAttribute() has been removed. Use attribute.applyMatrix4( matrix ) instead.' );
+               return attribute.applyMatrix4( this );
+
+       },
+       applyToVector3Array: function ( /* array, offset, length */ ) {
+
+               console.error( 'THREE.Matrix4: .applyToVector3Array() has been removed.' );
+
+       },
+       makeFrustum: function ( left, right, bottom, top, near, far ) {
+
+               console.warn( 'THREE.Matrix4: .makeFrustum() has been removed. Use .makePerspective( left, right, top, bottom, near, far ) instead.' );
+               return this.makePerspective( left, right, top, bottom, near, far );
+
+       },
+       getInverse: function ( matrix ) {
+
+               console.warn( 'THREE.Matrix4: .getInverse() has been removed. Use matrixInv.copy( matrix ).invert(); instead.' );
+               return this.copy( matrix ).invert();
+
+       }
+
+} );
+
+Plane.prototype.isIntersectionLine = function ( line ) {
+
+       console.warn( 'THREE.Plane: .isIntersectionLine() has been renamed to .intersectsLine().' );
+       return this.intersectsLine( line );
+
+};
+
+Object.assign( Quaternion.prototype, {
+
+       multiplyVector3: function ( vector ) {
+
+               console.warn( 'THREE.Quaternion: .multiplyVector3() has been removed. Use is now vector.applyQuaternion( quaternion ) instead.' );
+               return vector.applyQuaternion( this );
+
+       },
+       inverse: function ( ) {
+
+               console.warn( 'THREE.Quaternion: .inverse() has been renamed to invert().' );
+               return this.invert();
+
+       }
+
+} );
+
+Object.assign( Ray.prototype, {
+
+       isIntersectionBox: function ( box ) {
+
+               console.warn( 'THREE.Ray: .isIntersectionBox() has been renamed to .intersectsBox().' );
+               return this.intersectsBox( box );
+
+       },
+       isIntersectionPlane: function ( plane ) {
+
+               console.warn( 'THREE.Ray: .isIntersectionPlane() has been renamed to .intersectsPlane().' );
+               return this.intersectsPlane( plane );
+
+       },
+       isIntersectionSphere: function ( sphere ) {
+
+               console.warn( 'THREE.Ray: .isIntersectionSphere() has been renamed to .intersectsSphere().' );
+               return this.intersectsSphere( sphere );
+
+       }
+
+} );
+
+Object.assign( Triangle.prototype, {
+
+       area: function () {
+
+               console.warn( 'THREE.Triangle: .area() has been renamed to .getArea().' );
+               return this.getArea();
+
+       },
+       barycoordFromPoint: function ( point, target ) {
+
+               console.warn( 'THREE.Triangle: .barycoordFromPoint() has been renamed to .getBarycoord().' );
+               return this.getBarycoord( point, target );
+
+       },
+       midpoint: function ( target ) {
+
+               console.warn( 'THREE.Triangle: .midpoint() has been renamed to .getMidpoint().' );
+               return this.getMidpoint( target );
+
+       },
+       normal: function ( target ) {
+
+               console.warn( 'THREE.Triangle: .normal() has been renamed to .getNormal().' );
+               return this.getNormal( target );
+
+       },
+       plane: function ( target ) {
+
+               console.warn( 'THREE.Triangle: .plane() has been renamed to .getPlane().' );
+               return this.getPlane( target );
+
+       }
+
+} );
+
+Object.assign( Triangle, {
+
+       barycoordFromPoint: function ( point, a, b, c, target ) {
+
+               console.warn( 'THREE.Triangle: .barycoordFromPoint() has been renamed to .getBarycoord().' );
+               return Triangle.getBarycoord( point, a, b, c, target );
+
+       },
+       normal: function ( a, b, c, target ) {
+
+               console.warn( 'THREE.Triangle: .normal() has been renamed to .getNormal().' );
+               return Triangle.getNormal( a, b, c, target );
+
+       }
+
+} );
+
+Object.assign( Shape.prototype, {
+
+       extractAllPoints: function ( divisions ) {
+
+               console.warn( 'THREE.Shape: .extractAllPoints() has been removed. Use .extractPoints() instead.' );
+               return this.extractPoints( divisions );
+
+       },
+       extrude: function ( options ) {
+
+               console.warn( 'THREE.Shape: .extrude() has been removed. Use ExtrudeGeometry() instead.' );
+               return new ExtrudeGeometry( this, options );
+
+       },
+       makeGeometry: function ( options ) {
+
+               console.warn( 'THREE.Shape: .makeGeometry() has been removed. Use ShapeGeometry() instead.' );
+               return new ShapeGeometry( this, options );
+
+       }
+
+} );
+
+Object.assign( Vector2.prototype, {
+
+       fromAttribute: function ( attribute, index, offset ) {
+
+               console.warn( 'THREE.Vector2: .fromAttribute() has been renamed to .fromBufferAttribute().' );
+               return this.fromBufferAttribute( attribute, index, offset );
+
+       },
+       distanceToManhattan: function ( v ) {
+
+               console.warn( 'THREE.Vector2: .distanceToManhattan() has been renamed to .manhattanDistanceTo().' );
+               return this.manhattanDistanceTo( v );
+
+       },
+       lengthManhattan: function () {
+
+               console.warn( 'THREE.Vector2: .lengthManhattan() has been renamed to .manhattanLength().' );
+               return this.manhattanLength();
+
+       }
+
+} );
+
+Object.assign( Vector3.prototype, {
+
+       setEulerFromRotationMatrix: function () {
+
+               console.error( 'THREE.Vector3: .setEulerFromRotationMatrix() has been removed. Use Euler.setFromRotationMatrix() instead.' );
+
+       },
+       setEulerFromQuaternion: function () {
+
+               console.error( 'THREE.Vector3: .setEulerFromQuaternion() has been removed. Use Euler.setFromQuaternion() instead.' );
+
+       },
+       getPositionFromMatrix: function ( m ) {
+
+               console.warn( 'THREE.Vector3: .getPositionFromMatrix() has been renamed to .setFromMatrixPosition().' );
+               return this.setFromMatrixPosition( m );
+
+       },
+       getScaleFromMatrix: function ( m ) {
+
+               console.warn( 'THREE.Vector3: .getScaleFromMatrix() has been renamed to .setFromMatrixScale().' );
+               return this.setFromMatrixScale( m );
+
+       },
+       getColumnFromMatrix: function ( index, matrix ) {
+
+               console.warn( 'THREE.Vector3: .getColumnFromMatrix() has been renamed to .setFromMatrixColumn().' );
+               return this.setFromMatrixColumn( matrix, index );
+
+       },
+       applyProjection: function ( m ) {
+
+               console.warn( 'THREE.Vector3: .applyProjection() has been removed. Use .applyMatrix4( m ) instead.' );
+               return this.applyMatrix4( m );
+
+       },
+       fromAttribute: function ( attribute, index, offset ) {
+
+               console.warn( 'THREE.Vector3: .fromAttribute() has been renamed to .fromBufferAttribute().' );
+               return this.fromBufferAttribute( attribute, index, offset );
+
+       },
+       distanceToManhattan: function ( v ) {
+
+               console.warn( 'THREE.Vector3: .distanceToManhattan() has been renamed to .manhattanDistanceTo().' );
+               return this.manhattanDistanceTo( v );
+
+       },
+       lengthManhattan: function () {
+
+               console.warn( 'THREE.Vector3: .lengthManhattan() has been renamed to .manhattanLength().' );
+               return this.manhattanLength();
+
+       }
+
+} );
+
+Object.assign( Vector4.prototype, {
+
+       fromAttribute: function ( attribute, index, offset ) {
+
+               console.warn( 'THREE.Vector4: .fromAttribute() has been renamed to .fromBufferAttribute().' );
+               return this.fromBufferAttribute( attribute, index, offset );
+
+       },
+       lengthManhattan: function () {
+
+               console.warn( 'THREE.Vector4: .lengthManhattan() has been renamed to .manhattanLength().' );
+               return this.manhattanLength();
+
+       }
+
+} );
+
+//
+
+Object.assign( Object3D.prototype, {
+
+       getChildByName: function ( name ) {
+
+               console.warn( 'THREE.Object3D: .getChildByName() has been renamed to .getObjectByName().' );
+               return this.getObjectByName( name );
+
+       },
+       renderDepth: function () {
+
+               console.warn( 'THREE.Object3D: .renderDepth has been removed. Use .renderOrder, instead.' );
+
+       },
+       translate: function ( distance, axis ) {
+
+               console.warn( 'THREE.Object3D: .translate() has been removed. Use .translateOnAxis( axis, distance ) instead.' );
+               return this.translateOnAxis( axis, distance );
+
+       },
+       getWorldRotation: function () {
+
+               console.error( 'THREE.Object3D: .getWorldRotation() has been removed. Use THREE.Object3D.getWorldQuaternion( target ) instead.' );
+
+       },
+       applyMatrix: function ( matrix ) {
+
+               console.warn( 'THREE.Object3D: .applyMatrix() has been renamed to .applyMatrix4().' );
+               return this.applyMatrix4( matrix );
+
+       }
+
+} );
+
+Object.defineProperties( Object3D.prototype, {
+
+       eulerOrder: {
+               get: function () {
+
+                       console.warn( 'THREE.Object3D: .eulerOrder is now .rotation.order.' );
+                       return this.rotation.order;
+
+               },
+               set: function ( value ) {
+
+                       console.warn( 'THREE.Object3D: .eulerOrder is now .rotation.order.' );
+                       this.rotation.order = value;
+
+               }
+       },
+       useQuaternion: {
+               get: function () {
+
+                       console.warn( 'THREE.Object3D: .useQuaternion has been removed. The library now uses quaternions by default.' );
+
+               },
+               set: function () {
+
+                       console.warn( 'THREE.Object3D: .useQuaternion has been removed. The library now uses quaternions by default.' );
+
+               }
+       }
+
+} );
+
+Object.assign( Mesh.prototype, {
+
+       setDrawMode: function () {
+
+               console.error( 'THREE.Mesh: .setDrawMode() has been removed. The renderer now always assumes THREE.TrianglesDrawMode. Transform your geometry via BufferGeometryUtils.toTrianglesDrawMode() if necessary.' );
+
+       },
+
+} );
+
+Object.defineProperties( Mesh.prototype, {
+
+       drawMode: {
+               get: function () {
+
+                       console.error( 'THREE.Mesh: .drawMode has been removed. The renderer now always assumes THREE.TrianglesDrawMode.' );
+                       return TrianglesDrawMode;
+
+               },
+               set: function () {
+
+                       console.error( 'THREE.Mesh: .drawMode has been removed. The renderer now always assumes THREE.TrianglesDrawMode. Transform your geometry via BufferGeometryUtils.toTrianglesDrawMode() if necessary.' );
+
+               }
+       }
+
+} );
+
+Object.defineProperties( LOD.prototype, {
+
+       objects: {
+               get: function () {
+
+                       console.warn( 'THREE.LOD: .objects has been renamed to .levels.' );
+                       return this.levels;
+
+               }
+       }
+
+} );
+
+Object.defineProperty( Skeleton.prototype, 'useVertexTexture', {
+
+       get: function () {
+
+               console.warn( 'THREE.Skeleton: useVertexTexture has been removed.' );
+
+       },
+       set: function () {
+
+               console.warn( 'THREE.Skeleton: useVertexTexture has been removed.' );
+
+       }
+
+} );
+
+SkinnedMesh.prototype.initBones = function () {
+
+       console.error( 'THREE.SkinnedMesh: initBones() has been removed.' );
+
+};
+
+Object.defineProperty( Curve.prototype, '__arcLengthDivisions', {
+
+       get: function () {
+
+               console.warn( 'THREE.Curve: .__arcLengthDivisions is now .arcLengthDivisions.' );
+               return this.arcLengthDivisions;
+
+       },
+       set: function ( value ) {
+
+               console.warn( 'THREE.Curve: .__arcLengthDivisions is now .arcLengthDivisions.' );
+               this.arcLengthDivisions = value;
+
+       }
+
+} );
+
+//
+
+PerspectiveCamera.prototype.setLens = function ( focalLength, filmGauge ) {
+
+       console.warn( 'THREE.PerspectiveCamera.setLens is deprecated. ' +
+                       'Use .setFocalLength and .filmGauge for a photographic setup.' );
+
+       if ( filmGauge !== undefined ) this.filmGauge = filmGauge;
+       this.setFocalLength( focalLength );
+
+};
+
+//
+
+Object.defineProperties( Light.prototype, {
+       onlyShadow: {
+               set: function () {
+
+                       console.warn( 'THREE.Light: .onlyShadow has been removed.' );
+
+               }
+       },
+       shadowCameraFov: {
+               set: function ( value ) {
+
+                       console.warn( 'THREE.Light: .shadowCameraFov is now .shadow.camera.fov.' );
+                       this.shadow.camera.fov = value;
+
+               }
+       },
+       shadowCameraLeft: {
+               set: function ( value ) {
+
+                       console.warn( 'THREE.Light: .shadowCameraLeft is now .shadow.camera.left.' );
+                       this.shadow.camera.left = value;
+
+               }
+       },
+       shadowCameraRight: {
+               set: function ( value ) {
+
+                       console.warn( 'THREE.Light: .shadowCameraRight is now .shadow.camera.right.' );
+                       this.shadow.camera.right = value;
+
+               }
+       },
+       shadowCameraTop: {
+               set: function ( value ) {
+
+                       console.warn( 'THREE.Light: .shadowCameraTop is now .shadow.camera.top.' );
+                       this.shadow.camera.top = value;
+
+               }
+       },
+       shadowCameraBottom: {
+               set: function ( value ) {
+
+                       console.warn( 'THREE.Light: .shadowCameraBottom is now .shadow.camera.bottom.' );
+                       this.shadow.camera.bottom = value;
+
+               }
+       },
+       shadowCameraNear: {
+               set: function ( value ) {
+
+                       console.warn( 'THREE.Light: .shadowCameraNear is now .shadow.camera.near.' );
+                       this.shadow.camera.near = value;
+
+               }
+       },
+       shadowCameraFar: {
+               set: function ( value ) {
+
+                       console.warn( 'THREE.Light: .shadowCameraFar is now .shadow.camera.far.' );
+                       this.shadow.camera.far = value;
+
+               }
+       },
+       shadowCameraVisible: {
+               set: function () {
+
+                       console.warn( 'THREE.Light: .shadowCameraVisible has been removed. Use new THREE.CameraHelper( light.shadow.camera ) instead.' );
+
+               }
+       },
+       shadowBias: {
+               set: function ( value ) {
+
+                       console.warn( 'THREE.Light: .shadowBias is now .shadow.bias.' );
+                       this.shadow.bias = value;
+
+               }
+       },
+       shadowDarkness: {
+               set: function () {
+
+                       console.warn( 'THREE.Light: .shadowDarkness has been removed.' );
+
+               }
+       },
+       shadowMapWidth: {
+               set: function ( value ) {
+
+                       console.warn( 'THREE.Light: .shadowMapWidth is now .shadow.mapSize.width.' );
+                       this.shadow.mapSize.width = value;
+
+               }
+       },
+       shadowMapHeight: {
+               set: function ( value ) {
+
+                       console.warn( 'THREE.Light: .shadowMapHeight is now .shadow.mapSize.height.' );
+                       this.shadow.mapSize.height = value;
+
+               }
+       }
+} );
+
+//
+
+Object.defineProperties( BufferAttribute.prototype, {
+
+       length: {
+               get: function () {
+
+                       console.warn( 'THREE.BufferAttribute: .length has been deprecated. Use .count instead.' );
+                       return this.array.length;
+
+               }
+       },
+       dynamic: {
+               get: function () {
+
+                       console.warn( 'THREE.BufferAttribute: .dynamic has been deprecated. Use .usage instead.' );
+                       return this.usage === DynamicDrawUsage;
+
+               },
+               set: function ( /* value */ ) {
+
+                       console.warn( 'THREE.BufferAttribute: .dynamic has been deprecated. Use .usage instead.' );
+                       this.setUsage( DynamicDrawUsage );
+
+               }
+       }
+
+} );
+
+Object.assign( BufferAttribute.prototype, {
+       setDynamic: function ( value ) {
+
+               console.warn( 'THREE.BufferAttribute: .setDynamic() has been deprecated. Use .setUsage() instead.' );
+               this.setUsage( value === true ? DynamicDrawUsage : StaticDrawUsage );
+               return this;
+
+       },
+       copyIndicesArray: function ( /* indices */ ) {
+
+               console.error( 'THREE.BufferAttribute: .copyIndicesArray() has been removed.' );
+
+       },
+       setArray: function ( /* array */ ) {
+
+               console.error( 'THREE.BufferAttribute: .setArray has been removed. Use BufferGeometry .setAttribute to replace/resize attribute buffers' );
+
+       }
+} );
+
+Object.assign( BufferGeometry.prototype, {
+
+       addIndex: function ( index ) {
+
+               console.warn( 'THREE.BufferGeometry: .addIndex() has been renamed to .setIndex().' );
+               this.setIndex( index );
+
+       },
+       addAttribute: function ( name, attribute ) {
+
+               console.warn( 'THREE.BufferGeometry: .addAttribute() has been renamed to .setAttribute().' );
+
+               if ( ! ( attribute && attribute.isBufferAttribute ) && ! ( attribute && attribute.isInterleavedBufferAttribute ) ) {
+
+                       console.warn( 'THREE.BufferGeometry: .addAttribute() now expects ( name, attribute ).' );
+
+                       return this.setAttribute( name, new BufferAttribute( arguments[ 1 ], arguments[ 2 ] ) );
+
+               }
+
+               if ( name === 'index' ) {
+
+                       console.warn( 'THREE.BufferGeometry.addAttribute: Use .setIndex() for index attribute.' );
+                       this.setIndex( attribute );
+
+                       return this;
+
+               }
+
+               return this.setAttribute( name, attribute );
+
+       },
+       addDrawCall: function ( start, count, indexOffset ) {
+
+               if ( indexOffset !== undefined ) {
+
+                       console.warn( 'THREE.BufferGeometry: .addDrawCall() no longer supports indexOffset.' );
+
+               }
+
+               console.warn( 'THREE.BufferGeometry: .addDrawCall() is now .addGroup().' );
+               this.addGroup( start, count );
+
+       },
+       clearDrawCalls: function () {
+
+               console.warn( 'THREE.BufferGeometry: .clearDrawCalls() is now .clearGroups().' );
+               this.clearGroups();
+
+       },
+       computeOffsets: function () {
+
+               console.warn( 'THREE.BufferGeometry: .computeOffsets() has been removed.' );
+
+       },
+       removeAttribute: function ( name ) {
+
+               console.warn( 'THREE.BufferGeometry: .removeAttribute() has been renamed to .deleteAttribute().' );
+
+               return this.deleteAttribute( name );
+
+       },
+       applyMatrix: function ( matrix ) {
+
+               console.warn( 'THREE.BufferGeometry: .applyMatrix() has been renamed to .applyMatrix4().' );
+               return this.applyMatrix4( matrix );
+
+       }
+
+} );
+
+Object.defineProperties( BufferGeometry.prototype, {
+
+       drawcalls: {
+               get: function () {
+
+                       console.error( 'THREE.BufferGeometry: .drawcalls has been renamed to .groups.' );
+                       return this.groups;
+
+               }
+       },
+       offsets: {
+               get: function () {
+
+                       console.warn( 'THREE.BufferGeometry: .offsets has been renamed to .groups.' );
+                       return this.groups;
+
+               }
+       }
+
+} );
+
+Object.defineProperties( InstancedBufferGeometry.prototype, {
+
+       maxInstancedCount: {
+               get: function () {
+
+                       console.warn( 'THREE.InstancedBufferGeometry: .maxInstancedCount has been renamed to .instanceCount.' );
+                       return this.instanceCount;
+
+               },
+               set: function ( value ) {
+
+                       console.warn( 'THREE.InstancedBufferGeometry: .maxInstancedCount has been renamed to .instanceCount.' );
+                       this.instanceCount = value;
+
+               }
+       }
+
+} );
+
+Object.defineProperties( Raycaster.prototype, {
+
+       linePrecision: {
+               get: function () {
+
+                       console.warn( 'THREE.Raycaster: .linePrecision has been deprecated. Use .params.Line.threshold instead.' );
+                       return this.params.Line.threshold;
+
+               },
+               set: function ( value ) {
+
+                       console.warn( 'THREE.Raycaster: .linePrecision has been deprecated. Use .params.Line.threshold instead.' );
+                       this.params.Line.threshold = value;
+
+               }
+       }
+
+} );
+
+Object.defineProperties( InterleavedBuffer.prototype, {
+
+       dynamic: {
+               get: function () {
+
+                       console.warn( 'THREE.InterleavedBuffer: .length has been deprecated. Use .usage instead.' );
+                       return this.usage === DynamicDrawUsage;
+
+               },
+               set: function ( value ) {
+
+                       console.warn( 'THREE.InterleavedBuffer: .length has been deprecated. Use .usage instead.' );
+                       this.setUsage( value );
+
+               }
+       }
+
+} );
+
+Object.assign( InterleavedBuffer.prototype, {
+       setDynamic: function ( value ) {
+
+               console.warn( 'THREE.InterleavedBuffer: .setDynamic() has been deprecated. Use .setUsage() instead.' );
+               this.setUsage( value === true ? DynamicDrawUsage : StaticDrawUsage );
+               return this;
+
+       },
+       setArray: function ( /* array */ ) {
+
+               console.error( 'THREE.InterleavedBuffer: .setArray has been removed. Use BufferGeometry .setAttribute to replace/resize attribute buffers' );
+
+       }
+} );
+
+//
+
+Object.assign( ExtrudeGeometry.prototype, {
+
+       getArrays: function () {
+
+               console.error( 'THREE.ExtrudeGeometry: .getArrays() has been removed.' );
+
+       },
+
+       addShapeList: function () {
+
+               console.error( 'THREE.ExtrudeGeometry: .addShapeList() has been removed.' );
+
+       },
+
+       addShape: function () {
+
+               console.error( 'THREE.ExtrudeGeometry: .addShape() has been removed.' );
+
+       }
+
+} );
+
+//
+
+Object.assign( Scene.prototype, {
+
+       dispose: function () {
+
+               console.error( 'THREE.Scene: .dispose() has been removed.' );
+
+       }
+
+} );
+
+//
+
+Object.defineProperties( Uniform.prototype, {
+
+       dynamic: {
+               set: function () {
+
+                       console.warn( 'THREE.Uniform: .dynamic has been removed. Use object.onBeforeRender() instead.' );
+
+               }
+       },
+       onUpdate: {
+               value: function () {
+
+                       console.warn( 'THREE.Uniform: .onUpdate() has been removed. Use object.onBeforeRender() instead.' );
+                       return this;
+
+               }
+       }
+
+} );
+
+//
+
+Object.defineProperties( Material.prototype, {
+
+       wrapAround: {
+               get: function () {
+
+                       console.warn( 'THREE.Material: .wrapAround has been removed.' );
+
+               },
+               set: function () {
+
+                       console.warn( 'THREE.Material: .wrapAround has been removed.' );
+
+               }
+       },
+
+       overdraw: {
+               get: function () {
+
+                       console.warn( 'THREE.Material: .overdraw has been removed.' );
+
+               },
+               set: function () {
+
+                       console.warn( 'THREE.Material: .overdraw has been removed.' );
+
+               }
+       },
+
+       wrapRGB: {
+               get: function () {
+
+                       console.warn( 'THREE.Material: .wrapRGB has been removed.' );
+                       return new Color();
+
+               }
+       },
+
+       shading: {
+               get: function () {
+
+                       console.error( 'THREE.' + this.type + ': .shading has been removed. Use the boolean .flatShading instead.' );
+
+               },
+               set: function ( value ) {
+
+                       console.warn( 'THREE.' + this.type + ': .shading has been removed. Use the boolean .flatShading instead.' );
+                       this.flatShading = ( value === FlatShading );
+
+               }
+       },
+
+       stencilMask: {
+               get: function () {
+
+                       console.warn( 'THREE.' + this.type + ': .stencilMask has been removed. Use .stencilFuncMask instead.' );
+                       return this.stencilFuncMask;
+
+               },
+               set: function ( value ) {
+
+                       console.warn( 'THREE.' + this.type + ': .stencilMask has been removed. Use .stencilFuncMask instead.' );
+                       this.stencilFuncMask = value;
+
+               }
+       }
+
+} );
+
+Object.defineProperties( MeshPhongMaterial.prototype, {
+
+       metal: {
+               get: function () {
+
+                       console.warn( 'THREE.MeshPhongMaterial: .metal has been removed. Use THREE.MeshStandardMaterial instead.' );
+                       return false;
+
+               },
+               set: function () {
+
+                       console.warn( 'THREE.MeshPhongMaterial: .metal has been removed. Use THREE.MeshStandardMaterial instead' );
+
+               }
+       }
+
+} );
+
+Object.defineProperties( MeshPhysicalMaterial.prototype, {
+
+       transparency: {
+               get: function () {
+
+                       console.warn( 'THREE.MeshPhysicalMaterial: .transparency has been renamed to .transmission.' );
+                       return this.transmission;
+
+               },
+               set: function ( value ) {
+
+                       console.warn( 'THREE.MeshPhysicalMaterial: .transparency has been renamed to .transmission.' );
+                       this.transmission = value;
+
+               }
+       }
+
+} );
+
+Object.defineProperties( ShaderMaterial.prototype, {
+
+       derivatives: {
+               get: function () {
+
+                       console.warn( 'THREE.ShaderMaterial: .derivatives has been moved to .extensions.derivatives.' );
+                       return this.extensions.derivatives;
+
+               },
+               set: function ( value ) {
+
+                       console.warn( 'THREE. ShaderMaterial: .derivatives has been moved to .extensions.derivatives.' );
+                       this.extensions.derivatives = value;
+
+               }
+       }
+
+} );
+
+//
+
+Object.assign( WebGLRenderer.prototype, {
+
+       clearTarget: function ( renderTarget, color, depth, stencil ) {
+
+               console.warn( 'THREE.WebGLRenderer: .clearTarget() has been deprecated. Use .setRenderTarget() and .clear() instead.' );
+               this.setRenderTarget( renderTarget );
+               this.clear( color, depth, stencil );
+
+       },
+       animate: function ( callback ) {
+
+               console.warn( 'THREE.WebGLRenderer: .animate() is now .setAnimationLoop().' );
+               this.setAnimationLoop( callback );
+
+       },
+       getCurrentRenderTarget: function () {
+
+               console.warn( 'THREE.WebGLRenderer: .getCurrentRenderTarget() is now .getRenderTarget().' );
+               return this.getRenderTarget();
+
+       },
+       getMaxAnisotropy: function () {
+
+               console.warn( 'THREE.WebGLRenderer: .getMaxAnisotropy() is now .capabilities.getMaxAnisotropy().' );
+               return this.capabilities.getMaxAnisotropy();
+
+       },
+       getPrecision: function () {
+
+               console.warn( 'THREE.WebGLRenderer: .getPrecision() is now .capabilities.precision.' );
+               return this.capabilities.precision;
+
+       },
+       resetGLState: function () {
+
+               console.warn( 'THREE.WebGLRenderer: .resetGLState() is now .state.reset().' );
+               return this.state.reset();
+
+       },
+       supportsFloatTextures: function () {
+
+               console.warn( 'THREE.WebGLRenderer: .supportsFloatTextures() is now .extensions.get( \'OES_texture_float\' ).' );
+               return this.extensions.get( 'OES_texture_float' );
+
+       },
+       supportsHalfFloatTextures: function () {
+
+               console.warn( 'THREE.WebGLRenderer: .supportsHalfFloatTextures() is now .extensions.get( \'OES_texture_half_float\' ).' );
+               return this.extensions.get( 'OES_texture_half_float' );
+
+       },
+       supportsStandardDerivatives: function () {
+
+               console.warn( 'THREE.WebGLRenderer: .supportsStandardDerivatives() is now .extensions.get( \'OES_standard_derivatives\' ).' );
+               return this.extensions.get( 'OES_standard_derivatives' );
+
+       },
+       supportsCompressedTextureS3TC: function () {
+
+               console.warn( 'THREE.WebGLRenderer: .supportsCompressedTextureS3TC() is now .extensions.get( \'WEBGL_compressed_texture_s3tc\' ).' );
+               return this.extensions.get( 'WEBGL_compressed_texture_s3tc' );
+
+       },
+       supportsCompressedTexturePVRTC: function () {
+
+               console.warn( 'THREE.WebGLRenderer: .supportsCompressedTexturePVRTC() is now .extensions.get( \'WEBGL_compressed_texture_pvrtc\' ).' );
+               return this.extensions.get( 'WEBGL_compressed_texture_pvrtc' );
+
+       },
+       supportsBlendMinMax: function () {
+
+               console.warn( 'THREE.WebGLRenderer: .supportsBlendMinMax() is now .extensions.get( \'EXT_blend_minmax\' ).' );
+               return this.extensions.get( 'EXT_blend_minmax' );
+
+       },
+       supportsVertexTextures: function () {
+
+               console.warn( 'THREE.WebGLRenderer: .supportsVertexTextures() is now .capabilities.vertexTextures.' );
+               return this.capabilities.vertexTextures;
+
+       },
+       supportsInstancedArrays: function () {
+
+               console.warn( 'THREE.WebGLRenderer: .supportsInstancedArrays() is now .extensions.get( \'ANGLE_instanced_arrays\' ).' );
+               return this.extensions.get( 'ANGLE_instanced_arrays' );
+
+       },
+       enableScissorTest: function ( boolean ) {
+
+               console.warn( 'THREE.WebGLRenderer: .enableScissorTest() is now .setScissorTest().' );
+               this.setScissorTest( boolean );
+
+       },
+       initMaterial: function () {
+
+               console.warn( 'THREE.WebGLRenderer: .initMaterial() has been removed.' );
+
+       },
+       addPrePlugin: function () {
+
+               console.warn( 'THREE.WebGLRenderer: .addPrePlugin() has been removed.' );
+
+       },
+       addPostPlugin: function () {
+
+               console.warn( 'THREE.WebGLRenderer: .addPostPlugin() has been removed.' );
+
+       },
+       updateShadowMap: function () {
+
+               console.warn( 'THREE.WebGLRenderer: .updateShadowMap() has been removed.' );
+
+       },
+       setFaceCulling: function () {
+
+               console.warn( 'THREE.WebGLRenderer: .setFaceCulling() has been removed.' );
+
+       },
+       allocTextureUnit: function () {
+
+               console.warn( 'THREE.WebGLRenderer: .allocTextureUnit() has been removed.' );
+
+       },
+       setTexture: function () {
+
+               console.warn( 'THREE.WebGLRenderer: .setTexture() has been removed.' );
+
+       },
+       setTexture2D: function () {
+
+               console.warn( 'THREE.WebGLRenderer: .setTexture2D() has been removed.' );
+
+       },
+       setTextureCube: function () {
+
+               console.warn( 'THREE.WebGLRenderer: .setTextureCube() has been removed.' );
+
+       },
+       getActiveMipMapLevel: function () {
+
+               console.warn( 'THREE.WebGLRenderer: .getActiveMipMapLevel() is now .getActiveMipmapLevel().' );
+               return this.getActiveMipmapLevel();
+
+       }
+
+} );
+
+Object.defineProperties( WebGLRenderer.prototype, {
+
+       shadowMapEnabled: {
+               get: function () {
+
+                       return this.shadowMap.enabled;
+
+               },
+               set: function ( value ) {
+
+                       console.warn( 'THREE.WebGLRenderer: .shadowMapEnabled is now .shadowMap.enabled.' );
+                       this.shadowMap.enabled = value;
+
+               }
+       },
+       shadowMapType: {
+               get: function () {
+
+                       return this.shadowMap.type;
+
+               },
+               set: function ( value ) {
+
+                       console.warn( 'THREE.WebGLRenderer: .shadowMapType is now .shadowMap.type.' );
+                       this.shadowMap.type = value;
+
+               }
+       },
+       shadowMapCullFace: {
+               get: function () {
+
+                       console.warn( 'THREE.WebGLRenderer: .shadowMapCullFace has been removed. Set Material.shadowSide instead.' );
+                       return undefined;
+
+               },
+               set: function ( /* value */ ) {
+
+                       console.warn( 'THREE.WebGLRenderer: .shadowMapCullFace has been removed. Set Material.shadowSide instead.' );
+
+               }
+       },
+       context: {
+               get: function () {
+
+                       console.warn( 'THREE.WebGLRenderer: .context has been removed. Use .getContext() instead.' );
+                       return this.getContext();
+
+               }
+       },
+       vr: {
+               get: function () {
+
+                       console.warn( 'THREE.WebGLRenderer: .vr has been renamed to .xr' );
+                       return this.xr;
+
+               }
+       },
+       gammaInput: {
+               get: function () {
+
+                       console.warn( 'THREE.WebGLRenderer: .gammaInput has been removed. Set the encoding for textures via Texture.encoding instead.' );
+                       return false;
+
+               },
+               set: function () {
+
+                       console.warn( 'THREE.WebGLRenderer: .gammaInput has been removed. Set the encoding for textures via Texture.encoding instead.' );
+
+               }
+       },
+       gammaOutput: {
+               get: function () {
+
+                       console.warn( 'THREE.WebGLRenderer: .gammaOutput has been removed. Set WebGLRenderer.outputEncoding instead.' );
+                       return false;
+
+               },
+               set: function ( value ) {
+
+                       console.warn( 'THREE.WebGLRenderer: .gammaOutput has been removed. Set WebGLRenderer.outputEncoding instead.' );
+                       this.outputEncoding = ( value === true ) ? sRGBEncoding : LinearEncoding;
+
+               }
+       },
+       toneMappingWhitePoint: {
+               get: function () {
+
+                       console.warn( 'THREE.WebGLRenderer: .toneMappingWhitePoint has been removed.' );
+                       return 1.0;
+
+               },
+               set: function () {
+
+                       console.warn( 'THREE.WebGLRenderer: .toneMappingWhitePoint has been removed.' );
+
+               }
+       },
+
+} );
+
+Object.defineProperties( WebGLShadowMap.prototype, {
+
+       cullFace: {
+               get: function () {
+
+                       console.warn( 'THREE.WebGLRenderer: .shadowMap.cullFace has been removed. Set Material.shadowSide instead.' );
+                       return undefined;
+
+               },
+               set: function ( /* cullFace */ ) {
+
+                       console.warn( 'THREE.WebGLRenderer: .shadowMap.cullFace has been removed. Set Material.shadowSide instead.' );
+
+               }
+       },
+       renderReverseSided: {
+               get: function () {
+
+                       console.warn( 'THREE.WebGLRenderer: .shadowMap.renderReverseSided has been removed. Set Material.shadowSide instead.' );
+                       return undefined;
+
+               },
+               set: function () {
+
+                       console.warn( 'THREE.WebGLRenderer: .shadowMap.renderReverseSided has been removed. Set Material.shadowSide instead.' );
+
+               }
+       },
+       renderSingleSided: {
+               get: function () {
+
+                       console.warn( 'THREE.WebGLRenderer: .shadowMap.renderSingleSided has been removed. Set Material.shadowSide instead.' );
+                       return undefined;
+
+               },
+               set: function () {
+
+                       console.warn( 'THREE.WebGLRenderer: .shadowMap.renderSingleSided has been removed. Set Material.shadowSide instead.' );
+
+               }
+       }
+
+} );
+
+//
+
+Object.defineProperties( WebGLRenderTarget.prototype, {
+
+       wrapS: {
+               get: function () {
+
+                       console.warn( 'THREE.WebGLRenderTarget: .wrapS is now .texture.wrapS.' );
+                       return this.texture.wrapS;
+
+               },
+               set: function ( value ) {
+
+                       console.warn( 'THREE.WebGLRenderTarget: .wrapS is now .texture.wrapS.' );
+                       this.texture.wrapS = value;
+
+               }
+       },
+       wrapT: {
+               get: function () {
+
+                       console.warn( 'THREE.WebGLRenderTarget: .wrapT is now .texture.wrapT.' );
+                       return this.texture.wrapT;
+
+               },
+               set: function ( value ) {
+
+                       console.warn( 'THREE.WebGLRenderTarget: .wrapT is now .texture.wrapT.' );
+                       this.texture.wrapT = value;
+
+               }
+       },
+       magFilter: {
+               get: function () {
+
+                       console.warn( 'THREE.WebGLRenderTarget: .magFilter is now .texture.magFilter.' );
+                       return this.texture.magFilter;
+
+               },
+               set: function ( value ) {
+
+                       console.warn( 'THREE.WebGLRenderTarget: .magFilter is now .texture.magFilter.' );
+                       this.texture.magFilter = value;
+
+               }
+       },
+       minFilter: {
+               get: function () {
+
+                       console.warn( 'THREE.WebGLRenderTarget: .minFilter is now .texture.minFilter.' );
+                       return this.texture.minFilter;
+
+               },
+               set: function ( value ) {
+
+                       console.warn( 'THREE.WebGLRenderTarget: .minFilter is now .texture.minFilter.' );
+                       this.texture.minFilter = value;
+
+               }
+       },
+       anisotropy: {
+               get: function () {
+
+                       console.warn( 'THREE.WebGLRenderTarget: .anisotropy is now .texture.anisotropy.' );
+                       return this.texture.anisotropy;
+
+               },
+               set: function ( value ) {
+
+                       console.warn( 'THREE.WebGLRenderTarget: .anisotropy is now .texture.anisotropy.' );
+                       this.texture.anisotropy = value;
+
+               }
+       },
+       offset: {
+               get: function () {
+
+                       console.warn( 'THREE.WebGLRenderTarget: .offset is now .texture.offset.' );
+                       return this.texture.offset;
+
+               },
+               set: function ( value ) {
+
+                       console.warn( 'THREE.WebGLRenderTarget: .offset is now .texture.offset.' );
+                       this.texture.offset = value;
+
+               }
+       },
+       repeat: {
+               get: function () {
+
+                       console.warn( 'THREE.WebGLRenderTarget: .repeat is now .texture.repeat.' );
+                       return this.texture.repeat;
+
+               },
+               set: function ( value ) {
+
+                       console.warn( 'THREE.WebGLRenderTarget: .repeat is now .texture.repeat.' );
+                       this.texture.repeat = value;
+
+               }
+       },
+       format: {
+               get: function () {
+
+                       console.warn( 'THREE.WebGLRenderTarget: .format is now .texture.format.' );
+                       return this.texture.format;
+
+               },
+               set: function ( value ) {
+
+                       console.warn( 'THREE.WebGLRenderTarget: .format is now .texture.format.' );
+                       this.texture.format = value;
+
+               }
+       },
+       type: {
+               get: function () {
+
+                       console.warn( 'THREE.WebGLRenderTarget: .type is now .texture.type.' );
+                       return this.texture.type;
+
+               },
+               set: function ( value ) {
+
+                       console.warn( 'THREE.WebGLRenderTarget: .type is now .texture.type.' );
+                       this.texture.type = value;
+
+               }
+       },
+       generateMipmaps: {
+               get: function () {
+
+                       console.warn( 'THREE.WebGLRenderTarget: .generateMipmaps is now .texture.generateMipmaps.' );
+                       return this.texture.generateMipmaps;
+
+               },
+               set: function ( value ) {
+
+                       console.warn( 'THREE.WebGLRenderTarget: .generateMipmaps is now .texture.generateMipmaps.' );
+                       this.texture.generateMipmaps = value;
+
+               }
+       }
+
+} );
+
+//
+
+Object.defineProperties( Audio.prototype, {
+
+       load: {
+               value: function ( file ) {
+
+                       console.warn( 'THREE.Audio: .load has been deprecated. Use THREE.AudioLoader instead.' );
+                       const scope = this;
+                       const audioLoader = new AudioLoader();
+                       audioLoader.load( file, function ( buffer ) {
+
+                               scope.setBuffer( buffer );
+
+                       } );
+                       return this;
+
+               }
+       },
+       startTime: {
+               set: function () {
+
+                       console.warn( 'THREE.Audio: .startTime is now .play( delay ).' );
+
+               }
+       }
+
+} );
+
+//
+
+CubeCamera.prototype.updateCubeMap = function ( renderer, scene ) {
+
+       console.warn( 'THREE.CubeCamera: .updateCubeMap() is now .update().' );
+       return this.update( renderer, scene );
+
+};
+
+CubeCamera.prototype.clear = function ( renderer, color, depth, stencil ) {
+
+       console.warn( 'THREE.CubeCamera: .clear() is now .renderTarget.clear().' );
+       return this.renderTarget.clear( renderer, color, depth, stencil );
+
+};
+
+ImageUtils.crossOrigin = undefined;
+
+ImageUtils.loadTexture = function ( url, mapping, onLoad, onError ) {
+
+       console.warn( 'THREE.ImageUtils.loadTexture has been deprecated. Use THREE.TextureLoader() instead.' );
+
+       const loader = new TextureLoader();
+       loader.setCrossOrigin( this.crossOrigin );
+
+       const texture = loader.load( url, onLoad, undefined, onError );
+
+       if ( mapping ) texture.mapping = mapping;
+
+       return texture;
+
+};
+
+ImageUtils.loadTextureCube = function ( urls, mapping, onLoad, onError ) {
+
+       console.warn( 'THREE.ImageUtils.loadTextureCube has been deprecated. Use THREE.CubeTextureLoader() instead.' );
+
+       const loader = new CubeTextureLoader();
+       loader.setCrossOrigin( this.crossOrigin );
+
+       const texture = loader.load( urls, onLoad, undefined, onError );
+
+       if ( mapping ) texture.mapping = mapping;
+
+       return texture;
+
+};
+
+ImageUtils.loadCompressedTexture = function () {
+
+       console.error( 'THREE.ImageUtils.loadCompressedTexture has been removed. Use THREE.DDSLoader instead.' );
+
+};
+
+ImageUtils.loadCompressedTextureCube = function () {
+
+       console.error( 'THREE.ImageUtils.loadCompressedTextureCube has been removed. Use THREE.DDSLoader instead.' );
+
+};
+
+if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) {
+
+       /* eslint-disable no-undef */
+       __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'register', { detail: {
+               revision: REVISION,
+       } } ) );
+       /* eslint-enable no-undef */
+
+}
+
+if ( typeof window !== 'undefined' ) {
+
+       if ( window.__THREE__ ) {
+
+               console.warn( 'WARNING: Multiple instances of Three.js being imported.' );
+
+       } else {
+
+               window.__THREE__ = REVISION;
+
+       }
+
+}
+
+const DEG2RAD = Math.PI / 180;
+const RAD2DEG = 180 / Math.PI;
+const WGS84A = 6378137.0;
+const WGS84B = 6356752.31424518;
+/**
+ * Convert coordinates from geodetic (WGS84) reference to local topocentric
+ * (ENU) reference.
+ *
+ * @param {number} lng Longitude in degrees.
+ * @param {number} lat Latitude in degrees.
+ * @param {number} alt Altitude in meters.
+ * @param {number} refLng Reference longitude in degrees.
+ * @param {number} refLat Reference latitude in degrees.
+ * @param {number} refAlt Reference altitude in meters.
+ * @returns {Array<number>} The x, y, z local topocentric ENU coordinates.
+ */
+function geodeticToEnu(lng, lat, alt, refLng, refLat, refAlt) {
+    const ecef = geodeticToEcef(lng, lat, alt);
+    return ecefToEnu(ecef[0], ecef[1], ecef[2], refLng, refLat, refAlt);
+}
+/**
+ * Convert coordinates from local topocentric (ENU) reference to
+ * geodetic (WGS84) reference.
+ *
+ * @param {number} x Topocentric ENU coordinate in East direction.
+ * @param {number} y Topocentric ENU coordinate in North direction.
+ * @param {number} z Topocentric ENU coordinate in Up direction.
+ * @param {number} refLng Reference longitude in degrees.
+ * @param {number} refLat Reference latitude in degrees.
+ * @param {number} refAlt Reference altitude in meters.
+ * @returns {Array<number>} The longitude, latitude in degrees
+ * and altitude in meters.
+ */
+function enuToGeodetic(x, y, z, refLng, refLat, refAlt) {
+    const ecef = enuToEcef(x, y, z, refLng, refLat, refAlt);
+    return ecefToGeodetic(ecef[0], ecef[1], ecef[2]);
+}
+/**
+ * Convert coordinates from Earth-Centered, Earth-Fixed (ECEF) reference
+ * to local topocentric (ENU) reference.
+ *
+ * @param {number} X ECEF X-value.
+ * @param {number} Y ECEF Y-value.
+ * @param {number} Z ECEF Z-value.
+ * @param {number} refLng Reference longitude in degrees.
+ * @param {number} refLat Reference latitude in degrees.
+ * @param {number} refAlt Reference altitude in meters.
+ * @returns {Array<number>} The x, y, z topocentric ENU coordinates in East, North
+ * and Up directions respectively.
+ */
+function ecefToEnu(X, Y, Z, refLng, refLat, refAlt) {
+    const refEcef = geodeticToEcef(refLng, refLat, refAlt);
+    const V = [
+        X - refEcef[0],
+        Y - refEcef[1],
+        Z - refEcef[2],
+    ];
+    refLng = refLng * DEG2RAD;
+    refLat = refLat * DEG2RAD;
+    const cosLng = Math.cos(refLng);
+    const sinLng = Math.sin(refLng);
+    const cosLat = Math.cos(refLat);
+    const sinLat = Math.sin(refLat);
+    const x = -sinLng * V[0] + cosLng * V[1];
+    const y = -sinLat * cosLng * V[0] - sinLat * sinLng * V[1] + cosLat * V[2];
+    const z = cosLat * cosLng * V[0] + cosLat * sinLng * V[1] + sinLat * V[2];
+    return [x, y, z];
+}
+/**
+ * Convert coordinates from local topocentric (ENU) reference
+ * to Earth-Centered, Earth-Fixed (ECEF) reference.
+ *
+ * @param {number} x Topocentric ENU coordinate in East direction.
+ * @param {number} y Topocentric ENU coordinate in North direction.
+ * @param {number} z Topocentric ENU coordinate in Up direction.
+ * @param {number} refLng Reference longitude in degrees.
+ * @param {number} refLat Reference latitude in degrees.
+ * @param {number} refAlt Reference altitude in meters.
+ * @returns {Array<number>} The X, Y, Z ECEF coordinates.
+ */
+function enuToEcef(x, y, z, refLng, refLat, refAlt) {
+    const refEcef = geodeticToEcef(refLng, refLat, refAlt);
+    refLng = refLng * DEG2RAD;
+    refLat = refLat * DEG2RAD;
+    const cosLng = Math.cos(refLng);
+    const sinLng = Math.sin(refLng);
+    const cosLat = Math.cos(refLat);
+    const sinLat = Math.sin(refLat);
+    const X = -sinLng * x
+        - sinLat * cosLng * y
+        + cosLat * cosLng * z
+        + refEcef[0];
+    const Y = cosLng * x
+        - sinLat * sinLng * y
+        + cosLat * sinLng * z
+        + refEcef[1];
+    const Z = cosLat * y +
+        sinLat * z +
+        refEcef[2];
+    return [X, Y, Z];
+}
+/**
+ * Convert coordinates from geodetic reference (WGS84) to Earth-Centered,
+ * Earth-Fixed (ECEF) reference.
+ *
+ * @param {number} lng Longitude in degrees.
+ * @param {number} lat Latitude in degrees.
+ * @param {number} alt Altitude in meters.
+ * @returns {Array<number>} The X, Y, Z ECEF coordinates.
+ */
+function geodeticToEcef(lng, lat, alt) {
+    const a = WGS84A;
+    const b = WGS84B;
+    lng = lng * DEG2RAD;
+    lat = lat * DEG2RAD;
+    const cosLng = Math.cos(lng);
+    const sinLng = Math.sin(lng);
+    const cosLat = Math.cos(lat);
+    const sinLat = Math.sin(lat);
+    const a2 = a * a;
+    const b2 = b * b;
+    const L = 1.0 / Math.sqrt(a2 * cosLat * cosLat + b2 * sinLat * sinLat);
+    const nhcl = (a2 * L + alt) * cosLat;
+    const X = nhcl * cosLng;
+    const Y = nhcl * sinLng;
+    const Z = (b2 * L + alt) * sinLat;
+    return [X, Y, Z];
+}
+/**
+ * Convert coordinates from Earth-Centered, Earth-Fixed (ECEF) reference
+ * to geodetic reference (WGS84).
+ *
+ * @param {number} X ECEF X-value.
+ * @param {number} Y ECEF Y-value.
+ * @param {number} Z ECEF Z-value.
+ * @returns {Array<number>} The longitude, latitude in degrees
+ * and altitude in meters.
+ */
+function ecefToGeodetic(X, Y, Z) {
+    const a = WGS84A;
+    const b = WGS84B;
+    const a2 = a * a;
+    const b2 = b * b;
+    const a2mb2 = a2 - b2;
+    const ea = Math.sqrt(a2mb2 / a2);
+    const eb = Math.sqrt(a2mb2 / b2);
+    const p = Math.sqrt(X * X + Y * Y);
+    const theta = Math.atan2(Z * a, p * b);
+    const sinTheta = Math.sin(theta);
+    const cosTheta = Math.cos(theta);
+    const lng = Math.atan2(Y, X);
+    const lat = Math.atan2(Z + eb * eb * b * sinTheta * sinTheta * sinTheta, p - ea * ea * a * cosTheta * cosTheta * cosTheta);
+    const sinLat = Math.sin(lat);
+    const cosLat = Math.cos(lat);
+    const N = a / Math.sqrt(1 - ea * ea * sinLat * sinLat);
+    const alt = p / cosLat - N;
+    return [
+        lng * RAD2DEG,
+        lat * RAD2DEG,
+        alt
+    ];
+}
+
+/**
+ * @class GraphCalculator
+ *
+ * @classdesc Represents a calculator for graph entities.
+ */
+class GraphCalculator {
+    /**
+     * Get the bounding box corners for a circle with radius of a threshold
+     * with center in a geodetic position.
+     *
+     * @param {LngLat} lngLat - Longitude, latitude to encode.
+     * @param {number} threshold - Threshold distance from the position in meters.
+     *
+     * @returns {Array<LngLat>} The south west and north east corners of the
+     * bounding box.
+     */
+    boundingBoxCorners(lngLat, threshold) {
+        const sw = enuToGeodetic(-threshold, -threshold, 0, lngLat.lng, lngLat.lat, 0);
+        const ne = enuToGeodetic(threshold, threshold, 0, lngLat.lng, lngLat.lat, 0);
+        return [
+            { lat: sw[1], lng: sw[0] },
+            { lat: ne[1], lng: ne[0] },
+        ];
+    }
+    /**
+     * Convert a compass angle to an angle axis rotation vector.
+     *
+     * @param {number} compassAngle - The compass angle in degrees.
+     * @param {number} orientation - The orientation of the original image.
+     *
+     * @returns {Array<number>} Angle axis rotation vector.
+     */
+    rotationFromCompass(compassAngle, orientation) {
+        let x = 0;
+        let y = 0;
+        let z = 0;
+        switch (orientation) {
+            case 1:
+                x = Math.PI / 2;
+                break;
+            case 3:
+                x = -Math.PI / 2;
+                z = Math.PI;
+                break;
+            case 6:
+                y = -Math.PI / 2;
+                z = -Math.PI / 2;
+                break;
+            case 8:
+                y = Math.PI / 2;
+                z = Math.PI / 2;
+                break;
+        }
+        const rz = new Matrix4()
+            .makeRotationZ(z);
+        const euler = new Euler(x, y, compassAngle * Math.PI / 180, "XYZ");
+        const re = new Matrix4()
+            .makeRotationFromEuler(euler);
+        const rotation = new Vector4()
+            .setAxisAngleFromRotationMatrix(re.multiply(rz));
+        return rotation
+            .multiplyScalar(rotation.w)
+            .toArray()
+            .slice(0, 3);
+    }
+}
+
+/**
+ * @class Image
+ *
+ * @classdesc Represents a image in the navigation graph.
+ *
+ * Explanation of position and bearing properties:
+ *
+ * When images are uploaded they will have GPS information in the EXIF, this is what
+ * is called `originalLngLat` {@link Image.originalLngLat}.
+ *
+ * When Structure from Motions has been run for a image a `computedLngLat` that
+ * differs from the `originalLngLat` will be created. It is different because
+ * GPS positions are not very exact and SfM aligns the camera positions according
+ * to the 3D reconstruction {@link Image.computedLngLat}.
+ *
+ * At last there exist a `lngLat` property which evaluates to
+ * the `computedLngLat` from SfM if it exists but falls back
+ * to the `originalLngLat` from the EXIF GPS otherwise {@link Image.lngLat}.
+ *
+ * Everything that is done in in the Viewer is based on the SfM positions,
+ * i.e. `computedLngLat`. That is why the smooth transitions go in the right
+ * direction (nd not in strange directions because of bad GPS).
+ *
+ * E.g. when placing a marker in the Viewer it is relative to the SfM
+ * position i.e. the `computedLngLat`.
+ *
+ * The same concept as above also applies to the compass angle (or bearing) properties
+ * `originalCa`, `computedCa` and `ca`.
+ */
+class Image$1 {
+    /**
+     * Create a new image instance.
+     *
+     * @description Images are always created internally by the library.
+     * Images can not be added to the library through any API method.
+     *
+     * @param {CoreImageEnt} core- Raw core image data.
+     * @ignore
+     */
+    constructor(core) {
+        if (!core) {
+            throw new Error(`Incorrect core image data ${core}`);
+        }
+        this._cache = null;
+        this._core = core;
+        this._spatial = null;
+    }
+    /**
+     * Get assets cached.
+     *
+     * @description The assets that need to be cached for this property
+     * to report true are the following: fill properties, image and mesh.
+     * The library ensures that the current image will always have the
+     * assets cached.
+     *
+     * @returns {boolean} Value indicating whether all assets have been
+     * cached.
+     *
+     * @ignore
+     */
+    get assetsCached() {
+        return this._core != null &&
+            this._spatial != null &&
+            this._cache != null &&
+            this._cache.image != null &&
+            this._cache.mesh != null;
+    }
+    /**
+     * Get cameraParameters.
+     *
+     * @description Will be undefined if SfM has
+     * not been run.
+     *
+     * Camera type dependent parameters.
+     *
+     * For perspective and fisheye camera types,
+     * the camera parameters array should be
+     * constructed according to
+     *
+     * `[focal, k1, k2]`
+     *
+     * where focal is the camera focal length,
+     * and k1, k2 are radial distortion parameters.
+     *
+     * For spherical camera type the camera
+     * parameters are unset or emtpy array.
+     *
+     * @returns {Array<number>} The parameters
+     * related to the camera type.
+     */
+    get cameraParameters() {
+        return this._spatial.camera_parameters;
+    }
+    /**
+     * Get cameraType.
+     *
+     * @description Will be undefined if SfM has not been run.
+     *
+     * @returns {string} The camera type that captured the image.
+     */
+    get cameraType() {
+        return this._spatial.camera_type;
+    }
+    /**
+     * Get capturedAt.
+     *
+     * @description Timestamp of the image capture date
+     * and time represented as a Unix epoch timestamp in milliseconds.
+     *
+     * @returns {number} Timestamp when the image was captured.
+     */
+    get capturedAt() {
+        return this._spatial.captured_at;
+    }
+    /**
+     * Get clusterId.
+     *
+     * @returns {string} Globally unique id of the SfM cluster to which
+     * the image belongs.
+     */
+    get clusterId() {
+        return !!this._spatial.cluster ?
+            this._spatial.cluster.id :
+            null;
+    }
+    /**
+     * Get clusterUrl.
+     *
+     * @returns {string} Url to the cluster reconstruction file.
+     *
+     * @ignore
+     */
+    get clusterUrl() {
+        return !!this._spatial.cluster ?
+            this._spatial.cluster.url :
+            null;
+    }
+    /**
+     * Get compassAngle.
+     *
+     * @description If the SfM computed compass angle exists it will
+     * be returned, otherwise the original EXIF compass angle.
+     *
+     * @returns {number} Compass angle, measured in degrees
+     * clockwise with respect to north.
+     */
+    get compassAngle() {
+        return this._spatial.computed_compass_angle != null ?
+            this._spatial.computed_compass_angle :
+            this._spatial.compass_angle;
+    }
+    /**
+     * Get complete.
+     *
+     * @description The library ensures that the current image will
+     * always be full.
+     *
+     * @returns {boolean} Value indicating whether the image has all
+     * properties filled.
+     *
+     * @ignore
+     */
+    get complete() {
+        return this._spatial != null;
+    }
+    /**
+     * Get computedAltitude.
+     *
+     * @description If SfM has not been run the computed altitude is
+     * set to a default value of two meters.
+     *
+     * @returns {number} Altitude, in meters.
+     */
+    get computedAltitude() {
+        return this._spatial.computed_altitude;
+    }
+    /**
+     * Get computedCompassAngle.
+     *
+     * @description Will not be set if SfM has not been run.
+     *
+     * @returns {number} SfM computed compass angle, measured
+     * in degrees clockwise with respect to north.
+     */
+    get computedCompassAngle() {
+        return this._spatial.computed_compass_angle;
+    }
+    /**
+     * Get computedLngLat.
+     *
+     * @description Will not be set if SfM has not been run.
+     *
+     * @returns {LngLat} SfM computed longitude, latitude in WGS84 datum,
+     * measured in degrees.
+     */
+    get computedLngLat() {
+        return this._core.computed_geometry;
+    }
+    /**
+     * Get creatorId.
+     *
+     * @description Note that the creator ID will not be set when using
+     * the Mapillary API.
+     *
+     * @returns {string} Globally unique id of the user who uploaded
+     * the image.
+     */
+    get creatorId() {
+        return this._spatial.creator.id;
+    }
+    /**
+     * Get creatorUsername.
+     *
+     * @description Note that the creator username will not be set when
+     * using the Mapillary API.
+     *
+     * @returns {string} Username of the creator who uploaded
+     * the image.
+     */
+    get creatorUsername() {
+        return this._spatial.creator.username;
+    }
+    /**
+     * Get exifOrientation.
+     *
+     * @returns {number} EXIF orientation of original image.
+     */
+    get exifOrientation() {
+        return this._spatial.exif_orientation;
+    }
+    /**
+     * Get height.
+     *
+     * @returns {number} Height of original image, not adjusted
+     * for orientation.
+     */
+    get height() {
+        return this._spatial.height;
+    }
+    /**
+     * Get image.
+     *
+     * @description The image will always be set on the current image.
+     *
+     * @returns {HTMLImageElement} Cached image element of the image.
+     */
+    get image() {
+        return this._cache.image;
+    }
+    /**
+     * Get image$.
+     *
+     * @returns {Observable<HTMLImageElement>} Observable emitting
+     * the cached image when it is updated.
+     *
+     * @ignore
+     */
+    get image$() {
+        return this._cache.image$;
+    }
+    /**
+     * Get id.
+     *
+     * @returns {string} Globally unique id of the image.
+     */
+    get id() {
+        return this._core.id;
+    }
+    /**
+     * Get lngLat.
+     *
+     * @description If the SfM computed longitude, latitude exist
+     * it will be returned, otherwise the original EXIF latitude
+     * longitude.
+     *
+     * @returns {LngLat} Longitude, latitude in WGS84 datum,
+     * measured in degrees.
+     */
+    get lngLat() {
+        return this._core.computed_geometry != null ?
+            this._core.computed_geometry :
+            this._core.geometry;
+    }
+    /**
+     * Get merged.
+     *
+     * @returns {boolean} Value indicating whether SfM has been
+     * run on the image and the image has been merged into a
+     * connected component.
+     */
+    get merged() {
+        return this._spatial != null &&
+            this._spatial.merge_id != null;
+    }
+    /**
+     * Get mergeId.
+     *
+     * @description Will not be set if SfM has not yet been run on
+     * image.
+     *
+     * @returns {stirng} Id of connected component to which image
+     * belongs after the aligning merge.
+     */
+    get mergeId() {
+        return this._spatial.merge_id;
+    }
+    /**
+     * Get mesh.
+     *
+     * @description The mesh will always be set on the current image.
+     *
+     * @returns {MeshContract} SfM triangulated mesh of reconstructed
+     * atomic 3D points.
+     */
+    get mesh() {
+        return this._cache.mesh;
+    }
+    /**
+     * Get originalAltitude.
+     *
+     * @returns {number} EXIF altitude, in meters, if available.
+     */
+    get originalAltitude() {
+        return this._spatial.altitude;
+    }
+    /**
+     * Get originalCompassAngle.
+     *
+     * @returns {number} Original EXIF compass angle, measured in
+     * degrees.
+     */
+    get originalCompassAngle() {
+        return this._spatial.compass_angle;
+    }
+    /**
+     * Get originalLngLat.
+     *
+     * @returns {LngLat} Original EXIF longitude, latitude in
+     * WGS84 datum, measured in degrees.
+     */
+    get originalLngLat() {
+        return this._core.geometry;
+    }
+    /**
+     * Get ownerId.
+     *
+     * @returns {string} Globally unique id of the owner to which
+     * the image belongs. If the image does not belong to an
+     * owner the owner id will be undefined.
+     */
+    get ownerId() {
+        return !!this._spatial.owner ?
+            this._spatial.owner.id :
+            null;
+    }
+    /**
+     * Get private.
+     *
+     * @returns {boolean} Value specifying if image is accessible to
+     * organization members only or to everyone.
+     */
+    get private() {
+        return this._spatial.private;
+    }
+    /**
+     * Get qualityScore.
+     *
+     * @returns {number} A number between zero and one
+     * determining the quality of the image. Blurriness
+     * (motion blur / out-of-focus), occlusion (camera
+     * mount, ego vehicle, water-drops), windshield
+     * reflections, bad illumination (exposure, glare),
+     * and bad weather condition (fog, rain, snow)
+     * affect the quality score.
+     *
+     * @description Value should be on the interval [0, 1].
+     */
+    get qualityScore() {
+        return this._spatial.quality_score;
+    }
+    /**
+     * Get rotation.
+     *
+     * @description Will not be set if SfM has not been run.
+     *
+     * @returns {Array<number>} Rotation vector in angle axis representation.
+     */
+    get rotation() {
+        return this._spatial.computed_rotation;
+    }
+    /**
+     * Get scale.
+     *
+     * @description Will not be set if SfM has not been run.
+     *
+     * @returns {number} Scale of reconstruction the image
+     * belongs to.
+     */
+    get scale() {
+        return this._spatial.atomic_scale;
+    }
+    /**
+     * Get sequenceId.
+     *
+     * @returns {string} Globally unique id of the sequence
+     * to which the image belongs.
+     */
+    get sequenceId() {
+        return !!this._core.sequence ?
+            this._core.sequence.id :
+            null;
+    }
+    /**
+     * Get sequenceEdges.
+     *
+     * @returns {NavigationEdgeStatus} Value describing the status of the
+     * sequence edges.
+     *
+     * @ignore
+     */
+    get sequenceEdges() {
+        return this._cache.sequenceEdges;
+    }
+    /**
+     * Get sequenceEdges$.
+     *
+     * @description Internal observable, should not be used as an API.
+     *
+     * @returns {Observable<NavigationEdgeStatus>} Observable emitting
+     * values describing the status of the sequence edges.
+     *
+     * @ignore
+     */
+    get sequenceEdges$() {
+        return this._cache.sequenceEdges$;
+    }
+    /**
+     * Get spatialEdges.
+     *
+     * @returns {NavigationEdgeStatus} Value describing the status of the
+     * spatial edges.
+     *
+     * @ignore
+     */
+    get spatialEdges() {
+        return this._cache.spatialEdges;
+    }
+    /**
+     * Get spatialEdges$.
+     *
+     * @description Internal observable, should not be used as an API.
+     *
+     * @returns {Observable<NavigationEdgeStatus>} Observable emitting
+     * values describing the status of the spatial edges.
+     *
+     * @ignore
+     */
+    get spatialEdges$() {
+        return this._cache.spatialEdges$;
+    }
+    /**
+     * Get width.
+     *
+     * @returns {number} Width of original image, not
+     * adjusted for orientation.
+     */
+    get width() {
+        return this._spatial.width;
+    }
+    /**
+     * Cache the image and mesh assets.
+     *
+     * @description The assets are always cached internally by the
+     * library prior to setting a image as the current image.
+     *
+     * @returns {Observable<Image>} Observable emitting this image whenever the
+     * load status has changed and when the mesh or image has been fully loaded.
+     *
+     * @ignore
+     */
+    cacheAssets$() {
+        return this._cache
+            .cacheAssets$(this._spatial, this.merged)
+            .pipe(map(() => { return this; }));
+    }
+    /**
+     * Cache the image asset.
+     *
+     * @description Use for caching a differently sized image than
+     * the one currently held by the image.
+     *
+     * @returns {Observable<Image>} Observable emitting this image whenever the
+     * load status has changed and when the mesh or image has been fully loaded.
+     *
+     * @ignore
+     */
+    cacheImage$() {
+        return this._cache
+            .cacheImage$(this._spatial)
+            .pipe(map(() => { return this; }));
+    }
+    /**
+     * Cache the sequence edges.
+     *
+     * @description The sequence edges are cached asynchronously
+     * internally by the library.
+     *
+     * @param {Array<NavigationEdge>} edges - Sequence edges to cache.
+     * @ignore
+     */
+    cacheSequenceEdges(edges) {
+        this._cache.cacheSequenceEdges(edges);
+    }
+    /**
+     * Cache the spatial edges.
+     *
+     * @description The spatial edges are cached asynchronously
+     * internally by the library.
+     *
+     * @param {Array<NavigationEdge>} edges - Spatial edges to cache.
+     * @ignore
+     */
+    cacheSpatialEdges(edges) {
+        this._cache.cacheSpatialEdges(edges);
+    }
+    /**
+     * Dispose the image.
+     *
+     * @description Disposes all cached assets.
+     * @ignore
+     */
+    dispose() {
+        if (this._cache != null) {
+            this._cache.dispose();
+            this._cache = null;
+        }
+        this._core = null;
+        this._spatial = null;
+    }
+    /**
+     * Initialize the image cache.
+     *
+     * @description The image cache is initialized internally by
+     * the library.
+     *
+     * @param {ImageCache} cache - The image cache to set as cache.
+     * @ignore
+     */
+    initializeCache(cache) {
+        if (this._cache != null) {
+            throw new Error(`Image cache already initialized (${this.id}).`);
+        }
+        this._cache = cache;
+    }
+    /**
+     * Complete an image with spatial properties.
+     *
+     * @description The image is completed internally by
+     * the library.
+     *
+     * @param {SpatialImageEnt} fill - The spatial image struct.
+     * @ignore
+     */
+    makeComplete(fill) {
+        if (fill == null) {
+            throw new Error("Fill can not be null.");
+        }
+        this._spatial = fill;
+    }
+    /**
+     * Reset the sequence edges.
+     *
+     * @ignore
+     */
+    resetSequenceEdges() {
+        this._cache.resetSequenceEdges();
+    }
+    /**
+     * Reset the spatial edges.
+     *
+     * @ignore
+     */
+    resetSpatialEdges() {
+        this._cache.resetSpatialEdges();
+    }
+    /**
+     * Clears the image and mesh assets, aborts
+     * any outstanding requests and resets edges.
+     *
+     * @ignore
+     */
+    uncache() {
+        if (this._cache == null) {
+            return;
+        }
+        this._cache.dispose();
+        this._cache = null;
+    }
+}
+
+/**
+ * @class ImageCache
+ *
+ * @classdesc Represents the cached properties of a image.
+ */
+class ImageCache {
+    /**
+     * Create a new image cache instance.
+     */
+    constructor(provider) {
+        this._disposed = false;
+        this._provider = provider;
+        this._image = null;
+        this._mesh = null;
+        this._sequenceEdges = { cached: false, edges: [] };
+        this._spatialEdges = { cached: false, edges: [] };
+        this._imageChanged$ = new Subject();
+        this._image$ = this._imageChanged$.pipe(startWith(null), publishReplay(1), refCount());
+        this._iamgeSubscription = this._image$.subscribe();
+        this._sequenceEdgesChanged$ = new Subject();
+        this._sequenceEdges$ = this._sequenceEdgesChanged$.pipe(startWith(this._sequenceEdges), publishReplay(1), refCount());
+        this._sequenceEdgesSubscription = this._sequenceEdges$.subscribe(() => { });
+        this._spatialEdgesChanged$ = new Subject();
+        this._spatialEdges$ = this._spatialEdgesChanged$.pipe(startWith(this._spatialEdges), publishReplay(1), refCount());
+        this._spatialEdgesSubscription = this._spatialEdges$.subscribe(() => { });
+        this._cachingAssets$ = null;
+    }
+    /**
+     * Get image.
+     *
+     * @description Will not be set when assets have not been cached
+     * or when the object has been disposed.
+     *
+     * @returns {HTMLImageElement} Cached image element of the image.
+     */
+    get image() {
+        return this._image;
+    }
+    /**
+     * Get image$.
+     *
+     * @returns {Observable<HTMLImageElement>} Observable emitting
+     * the cached image when it is updated.
+     */
+    get image$() {
+        return this._image$;
+    }
+    /**
+     * Get mesh.
+     *
+     * @description Will not be set when assets have not been cached
+     * or when the object has been disposed.
+     *
+     * @returns {MeshContract} SfM triangulated mesh of reconstructed
+     * atomic 3D points.
+     */
+    get mesh() {
+        return this._mesh;
+    }
+    /**
+     * Get sequenceEdges.
+     *
+     * @returns {NavigationEdgeStatus} Value describing the status of the
+     * sequence edges.
+     */
+    get sequenceEdges() {
+        return this._sequenceEdges;
+    }
+    /**
+     * Get sequenceEdges$.
+     *
+     * @returns {Observable<NavigationEdgeStatus>} Observable emitting
+     * values describing the status of the sequence edges.
+     */
+    get sequenceEdges$() {
+        return this._sequenceEdges$;
+    }
+    /**
+     * Get spatialEdges.
+     *
+     * @returns {NavigationEdgeStatus} Value describing the status of the
+     * spatial edges.
+     */
+    get spatialEdges() {
+        return this._spatialEdges;
+    }
+    /**
+     * Get spatialEdges$.
+     *
+     * @returns {Observable<NavigationEdgeStatus>} Observable emitting
+     * values describing the status of the spatial edges.
+     */
+    get spatialEdges$() {
+        return this._spatialEdges$;
+    }
+    /**
+     * Cache the image and mesh assets.
+     *
+     * @param {SpatialImageEnt} spatial - Spatial props of the image to cache.
+     * @param {boolean} spherical - Value indicating whether image is a spherical.
+     * @param {boolean} merged - Value indicating whether image is merged.
+     * @returns {Observable<ImageCache>} Observable emitting this image
+     * cache whenever the load status has changed and when the mesh or image
+     * has been fully loaded.
+     */
+    cacheAssets$(spatial, merged) {
+        if (this._cachingAssets$ != null) {
+            return this._cachingAssets$;
+        }
+        this._cachingAssets$ = combineLatest(this._cacheImage$(spatial), this._cacheMesh$(spatial, merged)).pipe(map(([image, mesh]) => {
+            this._image = image;
+            this._mesh = mesh;
+            return this;
+        }), finalize(() => {
+            this._cachingAssets$ = null;
+        }), publishReplay(1), refCount());
+        this._cachingAssets$.pipe(first((imageCache) => {
+            return !!imageCache._image;
+        }))
+            .subscribe(() => {
+            this._imageChanged$.next(this._image);
+        }, () => { });
+        return this._cachingAssets$;
+    }
+    /**
+     * Cache an image with a higher resolution than the current one.
+     *
+     * @param {SpatialImageEnt} spatial - Spatial props.
+     * @returns {Observable<ImageCache>} Observable emitting a single item,
+     * the image cache, when the image has been cached. If supplied image
+     * size is not larger than the current image size the image cache is
+     * returned immediately.
+     */
+    cacheImage$(spatial) {
+        if (this._image != null) {
+            return of(this);
+        }
+        const cacheImage$ = this._cacheImage$(spatial)
+            .pipe(first((image) => {
+            return !!image;
+        }), tap((image) => {
+            this._disposeImage();
+            this._image = image;
+        }), map(() => {
+            return this;
+        }), publishReplay(1), refCount());
+        cacheImage$
+            .subscribe(() => {
+            this._imageChanged$.next(this._image);
+        }, () => { });
+        return cacheImage$;
+    }
+    /**
+     * Cache the sequence edges.
+     *
+     * @param {Array<NavigationEdge>} edges - Sequence edges to cache.
+     */
+    cacheSequenceEdges(edges) {
+        this._sequenceEdges = { cached: true, edges: edges };
+        this._sequenceEdgesChanged$.next(this._sequenceEdges);
+    }
+    /**
+     * Cache the spatial edges.
+     *
+     * @param {Array<NavigationEdge>} edges - Spatial edges to cache.
+     */
+    cacheSpatialEdges(edges) {
+        this._spatialEdges = { cached: true, edges: edges };
+        this._spatialEdgesChanged$.next(this._spatialEdges);
+    }
+    /**
+     * Dispose the image cache.
+     *
+     * @description Disposes all cached assets and unsubscribes to
+     * all streams.
+     */
+    dispose() {
+        this._iamgeSubscription.unsubscribe();
+        this._sequenceEdgesSubscription.unsubscribe();
+        this._spatialEdgesSubscription.unsubscribe();
+        this._disposeImage();
+        this._mesh = null;
+        this._sequenceEdges = { cached: false, edges: [] };
+        this._spatialEdges = { cached: false, edges: [] };
+        this._imageChanged$.next(null);
+        this._sequenceEdgesChanged$.next(this._sequenceEdges);
+        this._spatialEdgesChanged$.next(this._spatialEdges);
+        this._disposed = true;
+        if (this._imageAborter != null) {
+            this._imageAborter();
+            this._imageAborter = null;
+        }
+        if (this._meshAborter != null) {
+            this._meshAborter();
+            this._meshAborter = null;
+        }
+    }
+    /**
+     * Reset the sequence edges.
+     */
+    resetSequenceEdges() {
+        this._sequenceEdges = { cached: false, edges: [] };
+        this._sequenceEdgesChanged$.next(this._sequenceEdges);
+    }
+    /**
+     * Reset the spatial edges.
+     */
+    resetSpatialEdges() {
+        this._spatialEdges = { cached: false, edges: [] };
+        this._spatialEdgesChanged$.next(this._spatialEdges);
+    }
+    /**
+     * Cache the image.
+     *
+     * @param {SpatialImageEnt} spatial - Spatial image.
+     * @param {boolean} spherical - Value indicating whether image is a spherical.
+     * @returns {Observable<ILoadStatusObject<HTMLImageElement>>} Observable
+     * emitting a load status object every time the load status changes
+     * and completes when the image is fully loaded.
+     */
+    _cacheImage$(spatial) {
+        return Observable.create((subscriber) => {
+            const abort = new Promise((_, reject) => {
+                this._imageAborter = reject;
+            });
+            const url = spatial.thumb.url;
+            if (!url) {
+                const thumbId = spatial.thumb.id;
+                const message = `Incorrect thumb URL for ${spatial.id} ` +
+                    `(${thumbId}, ${url})`;
+                subscriber.error(new Error(message));
+                return;
+            }
+            this._provider.getImageBuffer(url, abort)
+                .then((buffer) => {
+                this._imageAborter = null;
+                const image = new Image();
+                image.crossOrigin = "Anonymous";
+                image.onload = () => {
+                    if (this._disposed) {
+                        window.URL.revokeObjectURL(image.src);
+                        const message = `Image load was aborted (${url})`;
+                        subscriber.error(new Error(message));
+                        return;
+                    }
+                    subscriber.next(image);
+                    subscriber.complete();
+                };
+                image.onerror = () => {
+                    this._imageAborter = null;
+                    subscriber.error(new Error(`Failed to load image (${url})`));
+                };
+                const blob = new Blob([buffer]);
+                image.src = window.URL.createObjectURL(blob);
+            }, (error) => {
+                this._imageAborter = null;
+                subscriber.error(error);
+            });
+        });
+    }
+    /**
+     * Cache the mesh.
+     *
+     * @param {SpatialImageEnt} spatial - Spatial props.
+     * @param {boolean} merged - Value indicating whether image is merged.
+     * @returns {Observable<ILoadStatusObject<MeshContract>>} Observable emitting
+     * a load status object every time the load status changes and completes
+     * when the mesh is fully loaded.
+     */
+    _cacheMesh$(spatial, merged) {
+        return Observable.create((subscriber) => {
+            if (!merged) {
+                subscriber.next(this._createEmptyMesh());
+                subscriber.complete();
+                return;
+            }
+            const url = spatial.mesh.url;
+            if (!url) {
+                const meshId = spatial.mesh.id;
+                const message = `Incorrect mesh URL for ${spatial.id} ` +
+                    `(${meshId}, ${url})`;
+                console.warn(message);
+                subscriber.next(this._createEmptyMesh());
+                subscriber.complete();
+                return;
+            }
+            const abort = new Promise((_, reject) => {
+                this._meshAborter = reject;
+            });
+            this._provider.getMesh(url, abort)
+                .then((mesh) => {
+                this._meshAborter = null;
+                if (this._disposed) {
+                    return;
+                }
+                subscriber.next(mesh);
+                subscriber.complete();
+            }, (error) => {
+                this._meshAborter = null;
+                console.error(error);
+                subscriber.next(this._createEmptyMesh());
+                subscriber.complete();
+            });
+        });
+    }
+    /**
+     * Create a load status object with an empty mesh.
+     *
+     * @returns {ILoadStatusObject<MeshContract>} Load status object
+     * with empty mesh.
+     */
+    _createEmptyMesh() {
+        return { faces: [], vertices: [] };
+    }
+    _disposeImage() {
+        if (this._image != null) {
+            window.URL.revokeObjectURL(this._image.src);
+        }
+        this._image = null;
+    }
+}
+
+/**
+ * @class Sequence
+ *
+ * @classdesc Represents a sequence of ordered images.
+ */
+class Sequence {
+    /**
+     * Create a new sequene instance.
+     *
+     * @param {SequenceEnt} sequence - Raw sequence data.
+     */
+    constructor(sequence) {
+        this._id = sequence.id;
+        this._imageIds = sequence.image_ids;
+    }
+    /**
+     * Get id.
+     *
+     * @returns {string} Unique sequence id.
+     */
+    get id() {
+        return this._id;
+    }
+    /**
+     * Get ids.
+     *
+     * @returns {Array<string>} Array of ordered image ids in the sequence.
+     */
+    get imageIds() {
+        return this._imageIds;
+    }
+    /**
+     * Dispose the sequence.
+     *
+     * @description Disposes all cached assets.
+     */
+    dispose() {
+        this._id = null;
+        this._imageIds = null;
+    }
+    /**
+     * Find the next image id in the sequence with respect to
+     * the provided image id.
+     *
+     * @param {string} id - Reference image id.
+     * @returns {string} Next id in sequence if it exists, null otherwise.
+     */
+    findNext(id) {
+        let i = this._imageIds.indexOf(id);
+        if ((i + 1) >= this._imageIds.length || i === -1) {
+            return null;
+        }
+        else {
+            return this._imageIds[i + 1];
+        }
+    }
+    /**
+     * Find the previous image id in the sequence with respect to
+     * the provided image id.
+     *
+     * @param {string} id - Reference image id.
+     * @returns {string} Previous id in sequence if it exists, null otherwise.
+     */
+    findPrev(id) {
+        let i = this._imageIds.indexOf(id);
+        if (i === 0 || i === -1) {
+            return null;
+        }
+        else {
+            return this._imageIds[i - 1];
+        }
+    }
+}
+
+class EdgeCalculatorCoefficients {
+    constructor() {
+        this.sphericalPreferredDistance = 2;
+        this.sphericalMotion = 2;
+        this.sphericalSequencePenalty = 1;
+        this.sphericalMergeCCPenalty = 4;
+        this.stepPreferredDistance = 4;
+        this.stepMotion = 3;
+        this.stepRotation = 4;
+        this.stepSequencePenalty = 2;
+        this.stepMergeCCPenalty = 6;
+        this.similarDistance = 2;
+        this.similarRotation = 3;
+        this.turnDistance = 4;
+        this.turnMotion = 2;
+        this.turnSequencePenalty = 1;
+        this.turnMergeCCPenalty = 4;
+    }
+}
+
+/**
+ * Enumeration for edge directions
+ * @enum {number}
+ * @readonly
+ * @description Directions for edges in image graph describing
+ * sequence, spatial and image type relations between nodes.
+ */
+var NavigationDirection;
+(function (NavigationDirection) {
+    /**
+     * Next image in the sequence.
+     */
+    NavigationDirection[NavigationDirection["Next"] = 0] = "Next";
+    /**
+     * Previous image in the sequence.
+     */
+    NavigationDirection[NavigationDirection["Prev"] = 1] = "Prev";
+    /**
+     * Step to the left keeping viewing direction.
+     */
+    NavigationDirection[NavigationDirection["StepLeft"] = 2] = "StepLeft";
+    /**
+     * Step to the right keeping viewing direction.
+     */
+    NavigationDirection[NavigationDirection["StepRight"] = 3] = "StepRight";
+    /**
+     * Step forward keeping viewing direction.
+     */
+    NavigationDirection[NavigationDirection["StepForward"] = 4] = "StepForward";
+    /**
+     * Step backward keeping viewing direction.
+     */
+    NavigationDirection[NavigationDirection["StepBackward"] = 5] = "StepBackward";
+    /**
+     * Turn 90 degrees counter clockwise.
+     */
+    NavigationDirection[NavigationDirection["TurnLeft"] = 6] = "TurnLeft";
+    /**
+     * Turn 90 degrees clockwise.
+     */
+    NavigationDirection[NavigationDirection["TurnRight"] = 7] = "TurnRight";
+    /**
+     * Turn 180 degrees.
+     */
+    NavigationDirection[NavigationDirection["TurnU"] = 8] = "TurnU";
+    /**
+     * Spherical in general direction.
+     */
+    NavigationDirection[NavigationDirection["Spherical"] = 9] = "Spherical";
+    /**
+     * Looking in roughly the same direction at rougly the same position.
+     */
+    NavigationDirection[NavigationDirection["Similar"] = 10] = "Similar";
+})(NavigationDirection || (NavigationDirection = {}));
+
+class EdgeCalculatorDirections {
+    constructor() {
+        this.steps = {};
+        this.turns = {};
+        this.spherical = {};
+        this.steps[NavigationDirection.StepForward] = {
+            direction: NavigationDirection.StepForward,
+            motionChange: 0,
+            useFallback: true,
+        };
+        this.steps[NavigationDirection.StepBackward] = {
+            direction: NavigationDirection.StepBackward,
+            motionChange: Math.PI,
+            useFallback: true,
+        };
+        this.steps[NavigationDirection.StepLeft] = {
+            direction: NavigationDirection.StepLeft,
+            motionChange: Math.PI / 2,
+            useFallback: false,
+        };
+        this.steps[NavigationDirection.StepRight] = {
+            direction: NavigationDirection.StepRight,
+            motionChange: -Math.PI / 2,
+            useFallback: false,
+        };
+        this.turns[NavigationDirection.TurnLeft] = {
+            direction: NavigationDirection.TurnLeft,
+            directionChange: Math.PI / 2,
+            motionChange: Math.PI / 4,
+        };
+        this.turns[NavigationDirection.TurnRight] = {
+            direction: NavigationDirection.TurnRight,
+            directionChange: -Math.PI / 2,
+            motionChange: -Math.PI / 4,
+        };
+        this.turns[NavigationDirection.TurnU] = {
+            direction: NavigationDirection.TurnU,
+            directionChange: Math.PI,
+            motionChange: null,
+        };
+        this.spherical[NavigationDirection.StepForward] = {
+            direction: NavigationDirection.StepForward,
+            directionChange: 0,
+            next: NavigationDirection.StepLeft,
+            prev: NavigationDirection.StepRight,
+        };
+        this.spherical[NavigationDirection.StepBackward] = {
+            direction: NavigationDirection.StepBackward,
+            directionChange: Math.PI,
+            next: NavigationDirection.StepRight,
+            prev: NavigationDirection.StepLeft,
+        };
+        this.spherical[NavigationDirection.StepLeft] = {
+            direction: NavigationDirection.StepLeft,
+            directionChange: Math.PI / 2,
+            next: NavigationDirection.StepBackward,
+            prev: NavigationDirection.StepForward,
+        };
+        this.spherical[NavigationDirection.StepRight] = {
+            direction: NavigationDirection.StepRight,
+            directionChange: -Math.PI / 2,
+            next: NavigationDirection.StepForward,
+            prev: NavigationDirection.StepBackward,
+        };
+    }
+}
+
+class EdgeCalculatorSettings {
+    constructor() {
+        this.sphericalMinDistance = 0.1;
+        this.sphericalMaxDistance = 20;
+        this.sphericalPreferredDistance = 5;
+        this.sphericalMaxItems = 4;
+        this.sphericalMaxStepTurnChange = Math.PI / 8;
+        this.rotationMaxDistance = this.turnMaxRigDistance;
+        this.rotationMaxDirectionChange = Math.PI / 6;
+        this.rotationMaxVerticalDirectionChange = Math.PI / 8;
+        this.similarMaxDirectionChange = Math.PI / 8;
+        this.similarMaxDistance = 12;
+        this.similarMinTimeDifference = 12 * 3600 * 1000;
+        this.stepMaxDistance = 20;
+        this.stepMaxDirectionChange = Math.PI / 6;
+        this.stepMaxDrift = Math.PI / 6;
+        this.stepPreferredDistance = 4;
+        this.turnMaxDistance = 15;
+        this.turnMaxDirectionChange = 2 * Math.PI / 9;
+        this.turnMaxRigDistance = 0.65;
+        this.turnMinRigDirectionChange = Math.PI / 6;
+    }
+    get maxDistance() {
+        return Math.max(this.sphericalMaxDistance, this.similarMaxDistance, this.stepMaxDistance, this.turnMaxDistance);
+    }
+}
+
+/**
+ * @class MapillaryError
+ *
+ * @classdesc Generic Mapillary error.
+ */
+class MapillaryError extends Error {
+    constructor(message) {
+        super(message);
+        Object.setPrototypeOf(this, MapillaryError.prototype);
+        this.name = "MapillaryError";
+    }
+}
+
+class ArgumentMapillaryError extends MapillaryError {
+    constructor(message) {
+        super(message != null ? message : "The argument is not valid.");
+        Object.setPrototypeOf(this, ArgumentMapillaryError.prototype);
+        this.name = "ArgumentMapillaryError";
+    }
+}
+
+/**
+ * @class Spatial
+ *
+ * @classdesc Provides methods for scalar, vector and matrix calculations.
+ */
+class Spatial {
+    constructor() {
+        this._epsilon = 1e-9;
+    }
+    /**
+     * Converts azimuthal phi rotation (counter-clockwise with origin on X-axis) to
+     * bearing (clockwise with origin at north or Y-axis).
+     *
+     * @param {number} phi - Azimuthal phi angle in radians.
+     * @returns {number} Bearing in radians.
+     */
+    azimuthalToBearing(phi) {
+        return -phi + Math.PI / 2;
+    }
+    /**
+     * Converts degrees to radians.
+     *
+     * @param {number} deg - Degrees.
+     * @returns {number} Radians.
+     */
+    degToRad(deg) {
+        return Math.PI * deg / 180;
+    }
+    /**
+     * Converts radians to degrees.
+     *
+     * @param {number} rad - Radians.
+     * @returns {number} Degrees.
+     */
+    radToDeg(rad) {
+        return 180 * rad / Math.PI;
+    }
+    /**
+     * Creates a rotation matrix from an angle-axis vector.
+     *
+     * @param {Array<number>} angleAxis - Angle-axis representation of a rotation.
+     * @returns {THREE.Matrix4} Rotation matrix.
+     */
+    rotationMatrix(angleAxis) {
+        let axis = new Vector3(angleAxis[0], angleAxis[1], angleAxis[2]);
+        let angle = axis.length();
+        if (angle > 0) {
+            axis.normalize();
+        }
+        return new Matrix4().makeRotationAxis(axis, angle);
+    }
+    /**
+     * Rotates a vector according to a angle-axis rotation vector.
+     *
+     * @param {Array<number>} vector - Vector to rotate.
+     * @param {Array<number>} angleAxis - Angle-axis representation of a rotation.
+     * @returns {THREE.Vector3} Rotated vector.
+     */
+    rotate(vector, angleAxis) {
+        let v = new Vector3(vector[0], vector[1], vector[2]);
+        let rotationMatrix = this.rotationMatrix(angleAxis);
+        v.applyMatrix4(rotationMatrix);
+        return v;
+    }
+    /**
+     * Calculates the optical center from a rotation vector
+     * on the angle-axis representation and a translation vector
+     * according to C = -R^T t.
+     *
+     * @param {Array<number>} rotation - Angle-axis representation of a rotation.
+     * @param {Array<number>} translation - Translation vector.
+     * @returns {THREE.Vector3} Optical center.
+     */
+    opticalCenter(rotation, translation) {
+        let angleAxis = [-rotation[0], -rotation[1], -rotation[2]];
+        let vector = [-translation[0], -translation[1], -translation[2]];
+        return this.rotate(vector, angleAxis);
+    }
+    /**
+     * Calculates the viewing direction from a rotation vector
+     * on the angle-axis representation.
+     *
+     * @param {number[]} rotation - Angle-axis representation of a rotation.
+     * @returns {THREE.Vector3} Viewing direction.
+     */
+    viewingDirection(rotation) {
+        let angleAxis = [-rotation[0], -rotation[1], -rotation[2]];
+        return this.rotate([0, 0, 1], angleAxis);
+    }
+    /**
+     * Wrap a number on the interval [min, max].
+     *
+     * @param {number} value - Value to wrap.
+     * @param {number} min - Lower endpoint of interval.
+     * @param {number} max - Upper endpoint of interval.
+     * @returns {number} The wrapped number.
+     */
+    wrap(value, min, max) {
+        if (max < min) {
+            throw new Error("Invalid arguments: max must be larger than min.");
+        }
+        let interval = (max - min);
+        while (value > max || value < min) {
+            if (value > max) {
+                value = value - interval;
+            }
+            else if (value < min) {
+                value = value + interval;
+            }
+        }
+        return value;
+    }
+    /**
+     * Wrap an angle on the interval [-Pi, Pi].
+     *
+     * @param {number} angle - Value to wrap.
+     * @returns {number} Wrapped angle.
+     */
+    wrapAngle(angle) {
+        return this.wrap(angle, -Math.PI, Math.PI);
+    }
+    /**
+     * Limit the value to the interval [min, max] by changing the value to
+     * the nearest available one when it is outside the interval.
+     *
+     * @param {number} value - Value to clamp.
+     * @param {number} min - Minimum of the interval.
+     * @param {number} max - Maximum of the interval.
+     * @returns {number} Clamped value.
+     */
+    clamp(value, min, max) {
+        if (value < min) {
+            return min;
+        }
+        if (value > max) {
+            return max;
+        }
+        return value;
+    }
+    /**
+     * Calculates the counter-clockwise angle from the first
+     * vector (x1, y1)^T to the second (x2, y2)^T.
+     *
+     * @param {number} x1 - X coordinate of first vector.
+     * @param {number} y1 - Y coordinate of first vector.
+     * @param {number} x2 - X coordinate of second vector.
+     * @param {number} y2 - Y coordinate of second vector.
+     * @returns {number} Counter clockwise angle between the vectors.
+     */
+    angleBetweenVector2(x1, y1, x2, y2) {
+        let angle = Math.atan2(y2, x2) - Math.atan2(y1, x1);
+        return this.wrapAngle(angle);
+    }
+    /**
+     * Calculates the minimum (absolute) angle change for rotation
+     * from one angle to another on the [-Pi, Pi] interval.
+     *
+     * @param {number} angle1 - Start angle.
+     * @param {number} angle2 - Destination angle.
+     * @returns {number} Absolute angle change between angles.
+     */
+    angleDifference(angle1, angle2) {
+        let angle = angle2 - angle1;
+        return this.wrapAngle(angle);
+    }
+    /**
+     * Calculates the relative rotation angle between two
+     * angle-axis vectors.
+     *
+     * @param {number} rotation1 - First angle-axis vector.
+     * @param {number} rotation2 - Second angle-axis vector.
+     * @returns {number} Relative rotation angle.
+     */
+    relativeRotationAngle(rotation1, rotation2) {
+        let R1T = this.rotationMatrix([-rotation1[0], -rotation1[1], -rotation1[2]]);
+        let R2 = this.rotationMatrix(rotation2);
+        let R = R1T.multiply(R2);
+        let elements = R.elements;
+        // from Tr(R) = 1 + 2 * cos(theta)
+        let tr = elements[0] + elements[5] + elements[10];
+        let theta = Math.acos(Math.max(Math.min((tr - 1) / 2, 1), -1));
+        return theta;
+    }
+    /**
+     * Calculates the angle from a vector to a plane.
+     *
+     * @param {Array<number>} vector - The vector.
+     * @param {Array<number>} planeNormal - Normal of the plane.
+     * @returns {number} Angle from between plane and vector.
+     */
+    angleToPlane(vector, planeNormal) {
+        let v = new Vector3().fromArray(vector);
+        let norm = v.length();
+        if (norm < this._epsilon) {
+            return 0;
+        }
+        let projection = v.dot(new Vector3().fromArray(planeNormal));
+        return Math.asin(projection / norm);
+    }
+    azimuthal(direction, up) {
+        const directionVector = new Vector3().fromArray(direction);
+        const upVector = new Vector3().fromArray(up);
+        const upProjection = directionVector.clone().dot(upVector);
+        const planeProjection = directionVector.clone().sub(upVector.clone().multiplyScalar(upProjection));
+        return Math.atan2(planeProjection.y, planeProjection.x);
+    }
+    /**
+     * Calculates the distance between two coordinates
+     * (longitude, latitude pairs) in meters according to
+     * the haversine formula.
+     *
+     * @param {number} lat1 - Latitude of the first coordinate in degrees.
+     * @param {number} lng1 - Longitude of the first coordinate in degrees.
+     * @param {number} lat2 - Latitude of the second coordinate in degrees.
+     * @param {number} lng2 - Longitude of the second coordinate in degrees.
+     * @returns {number} Distance between lat lon positions in meters.
+     */
+    distanceFromLngLat(lng1, lat1, lng2, lat2) {
+        let r = 6371000;
+        let dLat = this.degToRad(lat2 - lat1);
+        let dLng = this.degToRad(lng2 - lng1);
+        let hav = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
+            Math.cos(this.degToRad(lat1)) * Math.cos(this.degToRad(lat2)) *
+                Math.sin(dLng / 2) * Math.sin(dLng / 2);
+        let d = 2 * r * Math.atan2(Math.sqrt(hav), Math.sqrt(1 - hav));
+        return d;
+    }
+}
+
+const spatial = new Spatial();
+function isSpherical(cameraType) {
+    return cameraType === "spherical";
+}
+function isFisheye(cameraType) {
+    return cameraType === "fisheye";
+}
+function computeTranslation(position, rotation, reference) {
+    const C = geodeticToEnu(position.lng, position.lat, position.alt, reference.lng, reference.lat, reference.alt);
+    const RC = spatial.rotate(C, rotation);
+    const translation = [-RC.x, -RC.y, -RC.z];
+    return translation;
+}
+function computeProjectedPoints(transform, basicVertices, basicDirections, pointsPerLine, viewportCoords) {
+    const basicPoints = [];
+    for (let side = 0; side < basicVertices.length; ++side) {
+        const v = basicVertices[side];
+        const d = basicDirections[side];
+        for (let i = 0; i <= pointsPerLine; ++i) {
+            basicPoints.push([v[0] + d[0] * i / pointsPerLine,
+                v[1] + d[1] * i / pointsPerLine]);
+        }
+    }
+    const camera = new Camera$1();
+    camera.up.copy(transform.upVector());
+    camera.position.copy(new Vector3().fromArray(transform.unprojectSfM([0, 0], 0)));
+    camera.lookAt(new Vector3().fromArray(transform.unprojectSfM([0, 0], 10)));
+    camera.updateMatrix();
+    camera.updateMatrixWorld(true);
+    const projectedPoints = basicPoints
+        .map((basicPoint) => {
+        const worldPoint = transform.unprojectBasic(basicPoint, 10000);
+        const cameraPoint = viewportCoords.worldToCamera(worldPoint, camera);
+        return [
+            Math.abs(cameraPoint[0] / cameraPoint[2]),
+            Math.abs(cameraPoint[1] / cameraPoint[2]),
+        ];
+    });
+    return projectedPoints;
+}
+
+/**
+ * @class EdgeCalculator
+ *
+ * @classdesc Represents a class for calculating node edges.
+ */
+class EdgeCalculator {
+    /**
+     * Create a new edge calculator instance.
+     *
+     * @param {EdgeCalculatorSettings} settings - Settings struct.
+     * @param {EdgeCalculatorDirections} directions - Directions struct.
+     * @param {EdgeCalculatorCoefficients} coefficients - Coefficients struct.
+     */
+    constructor(settings, directions, coefficients) {
+        this._spatial = new Spatial();
+        this._settings = settings != null ? settings : new EdgeCalculatorSettings();
+        this._directions = directions != null ? directions : new EdgeCalculatorDirections();
+        this._coefficients = coefficients != null ? coefficients : new EdgeCalculatorCoefficients();
+    }
+    /**
+     * Returns the potential edges to destination nodes for a set
+     * of nodes with respect to a source node.
+     *
+     * @param {Image} node - Source node.
+     * @param {Array<Image>} nodes - Potential destination nodes.
+     * @param {Array<string>} fallbackIds - Ids for destination nodes
+     * that should be returned even if they do not meet the
+     * criteria for a potential edge.
+     * @throws {ArgumentMapillaryError} If node is not full.
+     */
+    getPotentialEdges(node, potentialImages, fallbackIds) {
+        if (!node.complete) {
+            throw new ArgumentMapillaryError("Image has to be full.");
+        }
+        if (!node.merged) {
+            return [];
+        }
+        let currentDirection = this._spatial.viewingDirection(node.rotation);
+        let currentVerticalDirection = this._spatial.angleToPlane(currentDirection.toArray(), [0, 0, 1]);
+        let potentialEdges = [];
+        for (let potential of potentialImages) {
+            if (!potential.merged ||
+                potential.id === node.id) {
+                continue;
+            }
+            let enu = geodeticToEnu(potential.lngLat.lng, potential.lngLat.lat, potential.computedAltitude, node.lngLat.lng, node.lngLat.lat, node.computedAltitude);
+            let motion = new Vector3(enu[0], enu[1], enu[2]);
+            let distance = motion.length();
+            if (distance > this._settings.maxDistance &&
+                fallbackIds.indexOf(potential.id) < 0) {
+                continue;
+            }
+            let motionChange = this._spatial.angleBetweenVector2(currentDirection.x, currentDirection.y, motion.x, motion.y);
+            let verticalMotion = this._spatial.angleToPlane(motion.toArray(), [0, 0, 1]);
+            let direction = this._spatial.viewingDirection(potential.rotation);
+            let directionChange = this._spatial.angleBetweenVector2(currentDirection.x, currentDirection.y, direction.x, direction.y);
+            let verticalDirection = this._spatial.angleToPlane(direction.toArray(), [0, 0, 1]);
+            let verticalDirectionChange = verticalDirection - currentVerticalDirection;
+            let rotation = this._spatial.relativeRotationAngle(node.rotation, potential.rotation);
+            let worldMotionAzimuth = this._spatial.angleBetweenVector2(1, 0, motion.x, motion.y);
+            let sameSequence = potential.sequenceId != null &&
+                node.sequenceId != null &&
+                potential.sequenceId === node.sequenceId;
+            let sameMergeCC = potential.mergeId === node.mergeId;
+            let sameUser = potential.creatorId === node.creatorId;
+            let potentialEdge = {
+                capturedAt: potential.capturedAt,
+                directionChange: directionChange,
+                distance: distance,
+                spherical: isSpherical(potential.cameraType),
+                id: potential.id,
+                motionChange: motionChange,
+                rotation: rotation,
+                sameMergeCC: sameMergeCC,
+                sameSequence: sameSequence,
+                sameUser: sameUser,
+                sequenceId: potential.sequenceId,
+                verticalDirectionChange: verticalDirectionChange,
+                verticalMotion: verticalMotion,
+                worldMotionAzimuth: worldMotionAzimuth,
+            };
+            potentialEdges.push(potentialEdge);
+        }
+        return potentialEdges;
+    }
+    /**
+     * Computes the sequence edges for a node.
+     *
+     * @param {Image} node - Source node.
+     * @throws {ArgumentMapillaryError} If node is not full.
+     */
+    computeSequenceEdges(node, sequence) {
+        if (!node.complete) {
+            throw new ArgumentMapillaryError("Image has to be full.");
+        }
+        if (node.sequenceId !== sequence.id) {
+            throw new ArgumentMapillaryError("Image and sequence does not correspond.");
+        }
+        let edges = [];
+        let nextId = sequence.findNext(node.id);
+        if (nextId != null) {
+            edges.push({
+                data: {
+                    direction: NavigationDirection.Next,
+                    worldMotionAzimuth: Number.NaN,
+                },
+                source: node.id,
+                target: nextId,
+            });
+        }
+        let prevId = sequence.findPrev(node.id);
+        if (prevId != null) {
+            edges.push({
+                data: {
+                    direction: NavigationDirection.Prev,
+                    worldMotionAzimuth: Number.NaN,
+                },
+                source: node.id,
+                target: prevId,
+            });
+        }
+        return edges;
+    }
+    /**
+     * Computes the similar edges for a node.
+     *
+     * @description Similar edges for perspective images
+     * look roughly in the same direction and are positioned closed to the node.
+     * Similar edges for spherical only target other spherical.
+     *
+     * @param {Image} node - Source node.
+     * @param {Array<PotentialEdge>} potentialEdges - Potential edges.
+     * @throws {ArgumentMapillaryError} If node is not full.
+     */
+    computeSimilarEdges(node, potentialEdges) {
+        if (!node.complete) {
+            throw new ArgumentMapillaryError("Image has to be full.");
+        }
+        let nodeSpherical = isSpherical(node.cameraType);
+        let sequenceGroups = {};
+        for (let potentialEdge of potentialEdges) {
+            if (potentialEdge.sequenceId == null) {
+                continue;
+            }
+            if (potentialEdge.sameSequence) {
+                continue;
+            }
+            if (nodeSpherical) {
+                if (!potentialEdge.spherical) {
+                    continue;
+                }
+            }
+            else {
+                if (!potentialEdge.spherical &&
+                    Math.abs(potentialEdge.directionChange) > this._settings.similarMaxDirectionChange) {
+                    continue;
+                }
+            }
+            if (potentialEdge.distance > this._settings.similarMaxDistance) {
+                continue;
+            }
+            if (potentialEdge.sameUser &&
+                Math.abs(potentialEdge.capturedAt - node.capturedAt) <
+                    this._settings.similarMinTimeDifference) {
+                continue;
+            }
+            if (sequenceGroups[potentialEdge.sequenceId] == null) {
+                sequenceGroups[potentialEdge.sequenceId] = [];
+            }
+            sequenceGroups[potentialEdge.sequenceId].push(potentialEdge);
+        }
+        let similarEdges = [];
+        let calculateScore = isSpherical(node.cameraType) ?
+            (potentialEdge) => {
+                return potentialEdge.distance;
+            } :
+            (potentialEdge) => {
+                return this._coefficients.similarDistance * potentialEdge.distance +
+                    this._coefficients.similarRotation * potentialEdge.rotation;
+            };
+        for (let sequenceId in sequenceGroups) {
+            if (!sequenceGroups.hasOwnProperty(sequenceId)) {
+                continue;
+            }
+            let lowestScore = Number.MAX_VALUE;
+            let similarEdge = null;
+            for (let potentialEdge of sequenceGroups[sequenceId]) {
+                let score = calculateScore(potentialEdge);
+                if (score < lowestScore) {
+                    lowestScore = score;
+                    similarEdge = potentialEdge;
+                }
+            }
+            if (similarEdge == null) {
+                continue;
+            }
+            similarEdges.push(similarEdge);
+        }
+        return similarEdges
+            .map((potentialEdge) => {
+            return {
+                data: {
+                    direction: NavigationDirection.Similar,
+                    worldMotionAzimuth: potentialEdge.worldMotionAzimuth,
+                },
+                source: node.id,
+                target: potentialEdge.id,
+            };
+        });
+    }
+    /**
+     * Computes the step edges for a perspective node.
+     *
+     * @description Step edge targets can only be other perspective nodes.
+     * Returns an empty array for spherical.
+     *
+     * @param {Image} node - Source node.
+     * @param {Array<PotentialEdge>} potentialEdges - Potential edges.
+     * @param {string} prevId - Id of previous node in sequence.
+     * @param {string} nextId - Id of next node in sequence.
+     * @throws {ArgumentMapillaryError} If node is not full.
+     */
+    computeStepEdges(node, potentialEdges, prevId, nextId) {
+        if (!node.complete) {
+            throw new ArgumentMapillaryError("Image has to be full.");
+        }
+        let edges = [];
+        if (isSpherical(node.cameraType)) {
+            return edges;
+        }
+        for (let k in this._directions.steps) {
+            if (!this._directions.steps.hasOwnProperty(k)) {
+                continue;
+            }
+            let step = this._directions.steps[k];
+            let lowestScore = Number.MAX_VALUE;
+            let edge = null;
+            let fallback = null;
+            for (let potential of potentialEdges) {
+                if (potential.spherical) {
+                    continue;
+                }
+                if (Math.abs(potential.directionChange) > this._settings.stepMaxDirectionChange) {
+                    continue;
+                }
+                let motionDifference = this._spatial.angleDifference(step.motionChange, potential.motionChange);
+                let directionMotionDifference = this._spatial.angleDifference(potential.directionChange, motionDifference);
+                let drift = Math.max(Math.abs(motionDifference), Math.abs(directionMotionDifference));
+                if (Math.abs(drift) > this._settings.stepMaxDrift) {
+                    continue;
+                }
+                let potentialId = potential.id;
+                if (step.useFallback && (potentialId === prevId || potentialId === nextId)) {
+                    fallback = potential;
+                }
+                if (potential.distance > this._settings.stepMaxDistance) {
+                    continue;
+                }
+                motionDifference = Math.sqrt(motionDifference * motionDifference +
+                    potential.verticalMotion * potential.verticalMotion);
+                let score = this._coefficients.stepPreferredDistance *
+                    Math.abs(potential.distance - this._settings.stepPreferredDistance) /
+                    this._settings.stepMaxDistance +
+                    this._coefficients.stepMotion * motionDifference / this._settings.stepMaxDrift +
+                    this._coefficients.stepRotation * potential.rotation / this._settings.stepMaxDirectionChange +
+                    this._coefficients.stepSequencePenalty * (potential.sameSequence ? 0 : 1) +
+                    this._coefficients.stepMergeCCPenalty * (potential.sameMergeCC ? 0 : 1);
+                if (score < lowestScore) {
+                    lowestScore = score;
+                    edge = potential;
+                }
+            }
+            edge = edge == null ? fallback : edge;
+            if (edge != null) {
+                edges.push({
+                    data: {
+                        direction: step.direction,
+                        worldMotionAzimuth: edge.worldMotionAzimuth,
+                    },
+                    source: node.id,
+                    target: edge.id,
+                });
+            }
+        }
+        return edges;
+    }
+    /**
+     * Computes the turn edges for a perspective node.
+     *
+     * @description Turn edge targets can only be other perspective images.
+     * Returns an empty array for spherical.
+     *
+     * @param {Image} node - Source node.
+     * @param {Array<PotentialEdge>} potentialEdges - Potential edges.
+     * @throws {ArgumentMapillaryError} If node is not full.
+     */
+    computeTurnEdges(node, potentialEdges) {
+        if (!node.complete) {
+            throw new ArgumentMapillaryError("Image has to be full.");
+        }
+        let edges = [];
+        if (isSpherical(node.cameraType)) {
+            return edges;
+        }
+        for (let k in this._directions.turns) {
+            if (!this._directions.turns.hasOwnProperty(k)) {
+                continue;
+            }
+            let turn = this._directions.turns[k];
+            let lowestScore = Number.MAX_VALUE;
+            let edge = null;
+            for (let potential of potentialEdges) {
+                if (potential.spherical) {
+                    continue;
+                }
+                if (potential.distance > this._settings.turnMaxDistance) {
+                    continue;
+                }
+                let rig = turn.direction !== NavigationDirection.TurnU &&
+                    potential.distance < this._settings.turnMaxRigDistance &&
+                    Math.abs(potential.directionChange) > this._settings.turnMinRigDirectionChange;
+                let directionDifference = this._spatial.angleDifference(turn.directionChange, potential.directionChange);
+                let score;
+                if (rig &&
+                    potential.directionChange * turn.directionChange > 0 &&
+                    Math.abs(potential.directionChange) < Math.abs(turn.directionChange)) {
+                    score = -Math.PI / 2 + Math.abs(potential.directionChange);
+                }
+                else {
+                    if (Math.abs(directionDifference) > this._settings.turnMaxDirectionChange) {
+                        continue;
+                    }
+                    let motionDifference = turn.motionChange ?
+                        this._spatial.angleDifference(turn.motionChange, potential.motionChange) : 0;
+                    motionDifference = Math.sqrt(motionDifference * motionDifference +
+                        potential.verticalMotion * potential.verticalMotion);
+                    score =
+                        this._coefficients.turnDistance * potential.distance /
+                            this._settings.turnMaxDistance +
+                            this._coefficients.turnMotion * motionDifference / Math.PI +
+                            this._coefficients.turnSequencePenalty * (potential.sameSequence ? 0 : 1) +
+                            this._coefficients.turnMergeCCPenalty * (potential.sameMergeCC ? 0 : 1);
+                }
+                if (score < lowestScore) {
+                    lowestScore = score;
+                    edge = potential;
+                }
+            }
+            if (edge != null) {
+                edges.push({
+                    data: {
+                        direction: turn.direction,
+                        worldMotionAzimuth: edge.worldMotionAzimuth,
+                    },
+                    source: node.id,
+                    target: edge.id,
+                });
+            }
+        }
+        return edges;
+    }
+    /**
+     * Computes the spherical edges for a perspective node.
+     *
+     * @description Perspective to spherical edge targets can only be
+     * spherical nodes. Returns an empty array for spherical.
+     *
+     * @param {Image} node - Source node.
+     * @param {Array<PotentialEdge>} potentialEdges - Potential edges.
+     * @throws {ArgumentMapillaryError} If node is not full.
+     */
+    computePerspectiveToSphericalEdges(node, potentialEdges) {
+        if (!node.complete) {
+            throw new ArgumentMapillaryError("Image has to be full.");
+        }
+        if (isSpherical(node.cameraType)) {
+            return [];
+        }
+        let lowestScore = Number.MAX_VALUE;
+        let edge = null;
+        for (let potential of potentialEdges) {
+            if (!potential.spherical) {
+                continue;
+            }
+            let score = this._coefficients.sphericalPreferredDistance *
+                Math.abs(potential.distance - this._settings.sphericalPreferredDistance) /
+                this._settings.sphericalMaxDistance +
+                this._coefficients.sphericalMotion * Math.abs(potential.motionChange) / Math.PI +
+                this._coefficients.sphericalMergeCCPenalty * (potential.sameMergeCC ? 0 : 1);
+            if (score < lowestScore) {
+                lowestScore = score;
+                edge = potential;
+            }
+        }
+        if (edge == null) {
+            return [];
+        }
+        return [
+            {
+                data: {
+                    direction: NavigationDirection.Spherical,
+                    worldMotionAzimuth: edge.worldMotionAzimuth,
+                },
+                source: node.id,
+                target: edge.id,
+            },
+        ];
+    }
+    /**
+     * Computes the spherical and step edges for a spherical node.
+     *
+     * @description Spherical to spherical edge targets can only be
+     * spherical nodes. spherical to step edge targets can only be perspective
+     * nodes.
+     *
+     * @param {Image} node - Source node.
+     * @param {Array<PotentialEdge>} potentialEdges - Potential edges.
+     * @throws {ArgumentMapillaryError} If node is not full.
+     */
+    computeSphericalEdges(node, potentialEdges) {
+        if (!node.complete) {
+            throw new ArgumentMapillaryError("Image has to be full.");
+        }
+        if (!isSpherical(node.cameraType)) {
+            return [];
+        }
+        let sphericalEdges = [];
+        let potentialSpherical = [];
+        let potentialSteps = [];
+        for (let potential of potentialEdges) {
+            if (potential.distance > this._settings.sphericalMaxDistance) {
+                continue;
+            }
+            if (potential.spherical) {
+                if (potential.distance < this._settings.sphericalMinDistance) {
+                    continue;
+                }
+                potentialSpherical.push(potential);
+            }
+            else {
+                for (let k in this._directions.spherical) {
+                    if (!this._directions.spherical.hasOwnProperty(k)) {
+                        continue;
+                    }
+                    let spherical = this._directions.spherical[k];
+                    let turn = this._spatial.angleDifference(potential.directionChange, potential.motionChange);
+                    let turnChange = this._spatial.angleDifference(spherical.directionChange, turn);
+                    if (Math.abs(turnChange) > this._settings.sphericalMaxStepTurnChange) {
+                        continue;
+                    }
+                    potentialSteps.push([spherical.direction, potential]);
+                    // break if step direction found
+                    break;
+                }
+            }
+        }
+        let maxRotationDifference = Math.PI / this._settings.sphericalMaxItems;
+        let occupiedAngles = [];
+        let stepAngles = [];
+        for (let index = 0; index < this._settings.sphericalMaxItems; index++) {
+            let rotation = index / this._settings.sphericalMaxItems * 2 * Math.PI;
+            let lowestScore = Number.MAX_VALUE;
+            let edge = null;
+            for (let potential of potentialSpherical) {
+                let motionDifference = this._spatial.angleDifference(rotation, potential.motionChange);
+                if (Math.abs(motionDifference) > maxRotationDifference) {
+                    continue;
+                }
+                let occupiedDifference = Number.MAX_VALUE;
+                for (let occupiedAngle of occupiedAngles) {
+                    let difference = Math.abs(this._spatial.angleDifference(occupiedAngle, potential.motionChange));
+                    if (difference < occupiedDifference) {
+                        occupiedDifference = difference;
+                    }
+                }
+                if (occupiedDifference <= maxRotationDifference) {
+                    continue;
+                }
+                let score = this._coefficients.sphericalPreferredDistance *
+                    Math.abs(potential.distance - this._settings.sphericalPreferredDistance) /
+                    this._settings.sphericalMaxDistance +
+                    this._coefficients.sphericalMotion * Math.abs(motionDifference) / maxRotationDifference +
+                    this._coefficients.sphericalSequencePenalty * (potential.sameSequence ? 0 : 1) +
+                    this._coefficients.sphericalMergeCCPenalty * (potential.sameMergeCC ? 0 : 1);
+                if (score < lowestScore) {
+                    lowestScore = score;
+                    edge = potential;
+                }
+            }
+            if (edge != null) {
+                occupiedAngles.push(edge.motionChange);
+                sphericalEdges.push({
+                    data: {
+                        direction: NavigationDirection.Spherical,
+                        worldMotionAzimuth: edge.worldMotionAzimuth,
+                    },
+                    source: node.id,
+                    target: edge.id,
+                });
+            }
+            else {
+                stepAngles.push(rotation);
+            }
+        }
+        let occupiedStepAngles = {};
+        occupiedStepAngles[NavigationDirection.Spherical] = occupiedAngles;
+        occupiedStepAngles[NavigationDirection.StepForward] = [];
+        occupiedStepAngles[NavigationDirection.StepLeft] = [];
+        occupiedStepAngles[NavigationDirection.StepBackward] = [];
+        occupiedStepAngles[NavigationDirection.StepRight] = [];
+        for (let stepAngle of stepAngles) {
+            let occupations = [];
+            for (let k in this._directions.spherical) {
+                if (!this._directions.spherical.hasOwnProperty(k)) {
+                    continue;
+                }
+                let spherical = this._directions.spherical[k];
+                let allOccupiedAngles = occupiedStepAngles[NavigationDirection.Spherical]
+                    .concat(occupiedStepAngles[spherical.direction])
+                    .concat(occupiedStepAngles[spherical.prev])
+                    .concat(occupiedStepAngles[spherical.next]);
+                let lowestScore = Number.MAX_VALUE;
+                let edge = null;
+                for (let potential of potentialSteps) {
+                    if (potential[0] !== spherical.direction) {
+                        continue;
+                    }
+                    let motionChange = this._spatial.angleDifference(stepAngle, potential[1].motionChange);
+                    if (Math.abs(motionChange) > maxRotationDifference) {
+                        continue;
+                    }
+                    let minOccupiedDifference = Number.MAX_VALUE;
+                    for (let occupiedAngle of allOccupiedAngles) {
+                        let occupiedDifference = Math.abs(this._spatial.angleDifference(occupiedAngle, potential[1].motionChange));
+                        if (occupiedDifference < minOccupiedDifference) {
+                            minOccupiedDifference = occupiedDifference;
+                        }
+                    }
+                    if (minOccupiedDifference <= maxRotationDifference) {
+                        continue;
+                    }
+                    let score = this._coefficients.sphericalPreferredDistance *
+                        Math.abs(potential[1].distance - this._settings.sphericalPreferredDistance) /
+                        this._settings.sphericalMaxDistance +
+                        this._coefficients.sphericalMotion * Math.abs(motionChange) / maxRotationDifference +
+                        this._coefficients.sphericalMergeCCPenalty * (potential[1].sameMergeCC ? 0 : 1);
+                    if (score < lowestScore) {
+                        lowestScore = score;
+                        edge = potential;
+                    }
+                }
+                if (edge != null) {
+                    occupations.push(edge);
+                    sphericalEdges.push({
+                        data: {
+                            direction: edge[0],
+                            worldMotionAzimuth: edge[1].worldMotionAzimuth,
+                        },
+                        source: node.id,
+                        target: edge[1].id,
+                    });
+                }
+            }
+            for (let occupation of occupations) {
+                occupiedStepAngles[occupation[0]].push(occupation[1].motionChange);
+            }
+        }
+        return sphericalEdges;
+    }
+}
+
+class GraphMapillaryError extends MapillaryError {
+    constructor(message) {
+        super(message);
+        Object.setPrototypeOf(this, GraphMapillaryError.prototype);
+        this.name = "GraphMapillaryError";
+    }
+}
+
+/**
+ * @class Graph
+ *
+ * @classdesc Represents a graph of nodes with edges.
+ */
+class Graph {
+    /**
+     * Create a new graph instance.
+     *
+     * @param {APIWrapper} [api] - API instance for retrieving data.
+     * @param {rbush.RBush<NodeIndexItem>} [nodeIndex] - Node index for fast spatial retreival.
+     * @param {GraphCalculator} [graphCalculator] - Instance for graph calculations.
+     * @param {EdgeCalculator} [edgeCalculator] - Instance for edge calculations.
+     * @param {FilterCreator} [filterCreator] - Instance for  filter creation.
+     * @param {GraphConfiguration} [configuration] - Configuration struct.
+     */
+    constructor(api, nodeIndex, graphCalculator, edgeCalculator, filterCreator, configuration) {
+        this._api = api;
+        this._cachedNodes = {};
+        this._cachedNodeTiles = {};
+        this._cachedSequenceNodes = {};
+        this._cachedSpatialEdges = {};
+        this._cachedTiles = {};
+        this._cachingFill$ = {};
+        this._cachingFull$ = {};
+        this._cachingSequenceNodes$ = {};
+        this._cachingSequences$ = {};
+        this._cachingSpatialArea$ = {};
+        this._cachingTiles$ = {};
+        this._changed$ = new Subject();
+        this._filterCreator = filterCreator !== null && filterCreator !== void 0 ? filterCreator : new FilterCreator();
+        this._filter = this._filterCreator.createFilter(undefined);
+        this._filterSubject$ = new Subject();
+        this._filter$ =
+            concat(of(this._filter), this._filterSubject$).pipe(publishReplay(1), refCount());
+        this._filterSubscription = this._filter$.subscribe(() => { });
+        this._defaultAlt = 2;
+        this._edgeCalculator = edgeCalculator !== null && edgeCalculator !== void 0 ? edgeCalculator : new EdgeCalculator();
+        this._graphCalculator = graphCalculator !== null && graphCalculator !== void 0 ? graphCalculator : new GraphCalculator();
+        this._configuration = configuration !== null && configuration !== void 0 ? configuration : {
+            maxSequences: 50,
+            maxUnusedImages: 100,
+            maxUnusedPreStoredImages: 30,
+            maxUnusedTiles: 20,
+        };
+        this._nodes = {};
+        this._nodeIndex = nodeIndex !== null && nodeIndex !== void 0 ? nodeIndex : new Graph._spatialIndex(16);
+        this._nodeIndexTiles = {};
+        this._nodeToTile = {};
+        this._preStored = {};
+        this._requiredNodeTiles = {};
+        this._requiredSpatialArea = {};
+        this._sequences = {};
+        this._tileThreshold = 20;
+    }
+    static register(spatialIndex) {
+        Graph._spatialIndex = spatialIndex;
+    }
+    /**
+     * Get api.
+     *
+     * @returns {APIWrapper} The API instance used by
+     * the graph.
+     */
+    get api() {
+        return this._api;
+    }
+    /**
+     * Get changed$.
+     *
+     * @returns {Observable<Graph>} Observable emitting
+     * the graph every time it has changed.
+     */
+    get changed$() {
+        return this._changed$;
+    }
+    /**
+     * Get filter$.
+     *
+     * @returns {Observable<FilterFunction>} Observable emitting
+     * the filter every time it has changed.
+     */
+    get filter$() {
+        return this._filter$;
+    }
+    /**
+     * Caches the full node data for all images within a bounding
+     * box.
+     *
+     * @description The node assets are not cached.
+     *
+     * @param {LngLat} sw - South west corner of bounding box.
+     * @param {LngLat} ne - North east corner of bounding box.
+     * @returns {Observable<Array<Image>>} Observable emitting
+     * the full nodes in the bounding box.
+     */
+    cacheBoundingBox$(sw, ne) {
+        const cacheTiles$ = this._api.data.geometry.bboxToCellIds(sw, ne)
+            .filter((h) => {
+            return !(h in this._cachedTiles);
+        })
+            .map((h) => {
+            return h in this._cachingTiles$ ?
+                this._cachingTiles$[h] :
+                this._cacheTile$(h);
+        });
+        if (cacheTiles$.length === 0) {
+            cacheTiles$.push(of(this));
+        }
+        return from(cacheTiles$).pipe(mergeAll(), last(), mergeMap(() => {
+            const nodes = this._nodeIndex
+                .search({
+                maxX: ne.lng,
+                maxY: ne.lat,
+                minX: sw.lng,
+                minY: sw.lat,
+            })
+                .map((item) => {
+                return item.node;
+            });
+            const fullNodes = [];
+            const coreNodes = [];
+            for (const node of nodes) {
+                if (node.complete) {
+                    fullNodes.push(node);
+                }
+                else {
+                    coreNodes.push(node.id);
+                }
+            }
+            const coreNodeBatches = [];
+            const batchSize = 200;
+            while (coreNodes.length > 0) {
+                coreNodeBatches.push(coreNodes.splice(0, batchSize));
+            }
+            const fullNodes$ = of(fullNodes);
+            const fillNodes$ = coreNodeBatches
+                .map((batch) => {
+                return this._api
+                    .getSpatialImages$(batch)
+                    .pipe(map((items) => {
+                    const result = [];
+                    for (const item of items) {
+                        const exists = this
+                            .hasNode(item.node_id);
+                        if (!exists) {
+                            continue;
+                        }
+                        const node = this
+                            .getNode(item.node_id);
+                        if (!node.complete) {
+                            this._makeFull(node, item.node);
+                        }
+                        result.push(node);
+                    }
+                    return result;
+                }));
+            });
+            return merge(fullNodes$, from(fillNodes$).pipe(mergeAll()));
+        }), reduce((acc, value) => {
+            return acc.concat(value);
+        }));
+    }
+    /**
+     * Caches the full node data for all images of a cell.
+     *
+     * @description The node assets are not cached.
+     *
+     * @param {string} cellId - Cell id.
+     * @returns {Observable<Array<Image>>} Observable
+     * emitting the full nodes of the cell.
+     */
+    cacheCell$(cellId) {
+        const cacheCell$ = cellId in this._cachedTiles ?
+            of(this) :
+            cellId in this._cachingTiles$ ?
+                this._cachingTiles$[cellId] :
+                this._cacheTile$(cellId);
+        return cacheCell$.pipe(mergeMap(() => {
+            const cachedCell = this._cachedTiles[cellId];
+            cachedCell.accessed = new Date().getTime();
+            const cellNodes = cachedCell.nodes;
+            const fullNodes = [];
+            const coreNodes = [];
+            for (const node of cellNodes) {
+                if (node.complete) {
+                    fullNodes.push(node);
+                }
+                else {
+                    coreNodes.push(node.id);
+                }
+            }
+            const coreNodeBatches = [];
+            const batchSize = 200;
+            while (coreNodes.length > 0) {
+                coreNodeBatches.push(coreNodes.splice(0, batchSize));
+            }
+            const fullNodes$ = of(fullNodes);
+            const fillNodes$ = coreNodeBatches
+                .map((batch) => {
+                return this._api.getSpatialImages$(batch).pipe(map((items) => {
+                    const filled = [];
+                    for (const item of items) {
+                        if (!item.node) {
+                            console.warn(`Image is empty (${item.node})`);
+                            continue;
+                        }
+                        const id = item.node_id;
+                        if (!this.hasNode(id)) {
+                            continue;
+                        }
+                        const node = this.getNode(id);
+                        if (!node.complete) {
+                            this._makeFull(node, item.node);
+                        }
+                        filled.push(node);
+                    }
+                    return filled;
+                }));
+            });
+            return merge(fullNodes$, from(fillNodes$).pipe(mergeAll()));
+        }), reduce((acc, value) => {
+            return acc.concat(value);
+        }));
+    }
+    /**
+     * Retrieve and cache node fill properties.
+     *
+     * @param {string} key - Key of node to fill.
+     * @returns {Observable<Graph>} Observable emitting the graph
+     * when the node has been updated.
+     * @throws {GraphMapillaryError} When the operation is not valid on the
+     * current graph.
+     */
+    cacheFill$(key) {
+        if (key in this._cachingFull$) {
+            throw new GraphMapillaryError(`Cannot fill node while caching full (${key}).`);
+        }
+        if (!this.hasNode(key)) {
+            throw new GraphMapillaryError(`Cannot fill node that does not exist in graph (${key}).`);
+        }
+        if (key in this._cachingFill$) {
+            return this._cachingFill$[key];
+        }
+        const node = this.getNode(key);
+        if (node.complete) {
+            throw new GraphMapillaryError(`Cannot fill node that is already full (${key}).`);
+        }
+        this._cachingFill$[key] = this._api.getSpatialImages$([key]).pipe(tap((items) => {
+            for (const item of items) {
+                if (!item.node) {
+                    console.warn(`Image is empty ${item.node_id}`);
+                }
+                if (!node.complete) {
+                    this._makeFull(node, item.node);
+                }
+                delete this._cachingFill$[item.node_id];
+            }
+        }), map(() => { return this; }), finalize(() => {
+            if (key in this._cachingFill$) {
+                delete this._cachingFill$[key];
+            }
+            this._changed$.next(this);
+        }), publish(), refCount());
+        return this._cachingFill$[key];
+    }
+    /**
+     * Retrieve and cache full node properties.
+     *
+     * @param {string} key - Key of node to fill.
+     * @returns {Observable<Graph>} Observable emitting the graph
+     * when the node has been updated.
+     * @throws {GraphMapillaryError} When the operation is not valid on the
+     * current graph.
+     */
+    cacheFull$(key) {
+        if (key in this._cachingFull$) {
+            return this._cachingFull$[key];
+        }
+        if (this.hasNode(key)) {
+            throw new GraphMapillaryError(`Cannot cache full node that already exist in graph (${key}).`);
+        }
+        this._cachingFull$[key] = this._api.getImages$([key]).pipe(tap((items) => {
+            for (const item of items) {
+                if (!item.node) {
+                    throw new GraphMapillaryError(`Image does not exist (${key}, ${item.node}).`);
+                }
+                const id = item.node_id;
+                if (this.hasNode(id)) {
+                    const node = this.getNode(key);
+                    if (!node.complete) {
+                        this._makeFull(node, item.node);
+                    }
+                }
+                else {
+                    if (item.node.sequence.id == null) {
+                        throw new GraphMapillaryError(`Image has no sequence key (${key}).`);
+                    }
+                    const node = new Image$1(item.node);
+                    this._makeFull(node, item.node);
+                    const cellId = this._api.data.geometry
+                        .lngLatToCellId(node.originalLngLat);
+                    this._preStore(cellId, node);
+                    this._setNode(node);
+                    delete this._cachingFull$[id];
+                }
+            }
+        }), map(() => this), finalize(() => {
+            if (key in this._cachingFull$) {
+                delete this._cachingFull$[key];
+            }
+            this._changed$.next(this);
+        }), publish(), refCount());
+        return this._cachingFull$[key];
+    }
+    /**
+     * Retrieve and cache a node sequence.
+     *
+     * @param {string} key - Key of node for which to retrieve sequence.
+     * @returns {Observable<Graph>} Observable emitting the graph
+     * when the sequence has been retrieved.
+     * @throws {GraphMapillaryError} When the operation is not valid on the
+     * current graph.
+     */
+    cacheNodeSequence$(key) {
+        if (!this.hasNode(key)) {
+            throw new GraphMapillaryError(`Cannot cache sequence edges of node that does not exist in graph (${key}).`);
+        }
+        let node = this.getNode(key);
+        if (node.sequenceId in this._sequences) {
+            throw new GraphMapillaryError(`Sequence already cached (${key}), (${node.sequenceId}).`);
+        }
+        return this._cacheSequence$(node.sequenceId);
+    }
+    /**
+     * Retrieve and cache a sequence.
+     *
+     * @param {string} sequenceKey - Key of sequence to cache.
+     * @returns {Observable<Graph>} Observable emitting the graph
+     * when the sequence has been retrieved.
+     * @throws {GraphMapillaryError} When the operation is not valid on the
+     * current graph.
+     */
+    cacheSequence$(sequenceKey) {
+        if (sequenceKey in this._sequences) {
+            throw new GraphMapillaryError(`Sequence already cached (${sequenceKey})`);
+        }
+        return this._cacheSequence$(sequenceKey);
+    }
+    /**
+     * Cache sequence edges for a node.
+     *
+     * @param {string} key - Key of node.
+     * @throws {GraphMapillaryError} When the operation is not valid on the
+     * current graph.
+     */
+    cacheSequenceEdges(key) {
+        let node = this.getNode(key);
+        if (!(node.sequenceId in this._sequences)) {
+            throw new GraphMapillaryError(`Sequence is not cached (${key}), (${node.sequenceId})`);
+        }
+        let sequence = this._sequences[node.sequenceId].sequence;
+        let edges = this._edgeCalculator.computeSequenceEdges(node, sequence);
+        node.cacheSequenceEdges(edges);
+    }
+    /**
+     * Retrieve and cache full nodes for all keys in a sequence.
+     *
+     * @param {string} sequenceKey - Key of sequence.
+     * @param {string} referenceNodeKey - Key of node to use as reference
+     * for optimized caching.
+     * @returns {Observable<Graph>} Observable emitting the graph
+     * when the nodes of the sequence has been cached.
+     */
+    cacheSequenceNodes$(sequenceKey, referenceNodeKey) {
+        if (!this.hasSequence(sequenceKey)) {
+            throw new GraphMapillaryError(`Cannot cache sequence nodes of sequence that does not exist in graph (${sequenceKey}).`);
+        }
+        if (this.hasSequenceNodes(sequenceKey)) {
+            throw new GraphMapillaryError(`Sequence nodes already cached (${sequenceKey}).`);
+        }
+        const sequence = this.getSequence(sequenceKey);
+        if (sequence.id in this._cachingSequenceNodes$) {
+            return this._cachingSequenceNodes$[sequence.id];
+        }
+        const batches = [];
+        const keys = sequence.imageIds.slice();
+        const referenceBatchSize = 50;
+        if (!!referenceNodeKey && keys.length > referenceBatchSize) {
+            const referenceIndex = keys.indexOf(referenceNodeKey);
+            const startIndex = Math.max(0, Math.min(referenceIndex - referenceBatchSize / 2, keys.length - referenceBatchSize));
+            batches.push(keys.splice(startIndex, referenceBatchSize));
+        }
+        const batchSize = 200;
+        while (keys.length > 0) {
+            batches.push(keys.splice(0, batchSize));
+        }
+        let batchesToCache = batches.length;
+        const sequenceNodes$ = from(batches).pipe(mergeMap((batch) => {
+            return this._api.getImages$(batch).pipe(tap((items) => {
+                for (const item of items) {
+                    if (!item.node) {
+                        console.warn(`Image empty (${item.node_id})`);
+                        continue;
+                    }
+                    const id = item.node_id;
+                    if (this.hasNode(id)) {
+                        const node = this.getNode(id);
+                        if (!node.complete) {
+                            this._makeFull(node, item.node);
+                        }
+                    }
+                    else {
+                        if (item.node.sequence.id == null) {
+                            console.warn(`Sequence missing, discarding node (${item.node_id})`);
+                        }
+                        const node = new Image$1(item.node);
+                        this._makeFull(node, item.node);
+                        const cellId = this._api.data.geometry
+                            .lngLatToCellId(node.originalLngLat);
+                        this._preStore(cellId, node);
+                        this._setNode(node);
+                    }
+                }
+                batchesToCache--;
+            }), map(() => this));
+        }, 6), last(), finalize(() => {
+            delete this._cachingSequenceNodes$[sequence.id];
+            if (batchesToCache === 0) {
+                this._cachedSequenceNodes[sequence.id] = true;
+            }
+        }), publish(), refCount());
+        this._cachingSequenceNodes$[sequence.id] = sequenceNodes$;
+        return sequenceNodes$;
+    }
+    /**
+     * Retrieve and cache full nodes for a node spatial area.
+     *
+     * @param {string} key - Key of node for which to retrieve sequence.
+     * @returns {Observable<Graph>} Observable emitting the graph
+     * when the nodes in the spatial area has been made full.
+     * @throws {GraphMapillaryError} When the operation is not valid on the
+     * current graph.
+     */
+    cacheSpatialArea$(key) {
+        if (!this.hasNode(key)) {
+            throw new GraphMapillaryError(`Cannot cache spatial area of node that does not exist in graph (${key}).`);
+        }
+        if (key in this._cachedSpatialEdges) {
+            throw new GraphMapillaryError(`Image already spatially cached (${key}).`);
+        }
+        if (!(key in this._requiredSpatialArea)) {
+            throw new GraphMapillaryError(`Spatial area not determined (${key}).`);
+        }
+        let spatialArea = this._requiredSpatialArea[key];
+        if (Object.keys(spatialArea.cacheNodes).length === 0) {
+            throw new GraphMapillaryError(`Spatial nodes already cached (${key}).`);
+        }
+        if (key in this._cachingSpatialArea$) {
+            return this._cachingSpatialArea$[key];
+        }
+        let batches = [];
+        while (spatialArea.cacheKeys.length > 0) {
+            batches.push(spatialArea.cacheKeys.splice(0, 200));
+        }
+        let batchesToCache = batches.length;
+        let spatialNodes$ = [];
+        for (let batch of batches) {
+            let spatialNodeBatch$ = this._api.getSpatialImages$(batch).pipe(tap((items) => {
+                for (const item of items) {
+                    if (!item.node) {
+                        console.warn(`Image is empty (${item.node_id})`);
+                        continue;
+                    }
+                    const id = item.node_id;
+                    const spatialNode = spatialArea.cacheNodes[id];
+                    if (spatialNode.complete) {
+                        delete spatialArea.cacheNodes[id];
+                        continue;
+                    }
+                    this._makeFull(spatialNode, item.node);
+                    delete spatialArea.cacheNodes[id];
+                }
+                if (--batchesToCache === 0) {
+                    delete this._cachingSpatialArea$[key];
+                }
+            }), map(() => { return this; }), catchError((error) => {
+                for (let batchKey of batch) {
+                    if (batchKey in spatialArea.all) {
+                        delete spatialArea.all[batchKey];
+                    }
+                    if (batchKey in spatialArea.cacheNodes) {
+                        delete spatialArea.cacheNodes[batchKey];
+                    }
+                }
+                if (--batchesToCache === 0) {
+                    delete this._cachingSpatialArea$[key];
+                }
+                throw error;
+            }), finalize(() => {
+                if (Object.keys(spatialArea.cacheNodes).length === 0) {
+                    this._changed$.next(this);
+                }
+            }), publish(), refCount());
+            spatialNodes$.push(spatialNodeBatch$);
+        }
+        this._cachingSpatialArea$[key] = spatialNodes$;
+        return spatialNodes$;
+    }
+    /**
+     * Cache spatial edges for a node.
+     *
+     * @param {string} key - Key of node.
+     * @throws {GraphMapillaryError} When the operation is not valid on the
+     * current graph.
+     */
+    cacheSpatialEdges(key) {
+        if (key in this._cachedSpatialEdges) {
+            throw new GraphMapillaryError(`Spatial edges already cached (${key}).`);
+        }
+        let node = this.getNode(key);
+        let sequence = this._sequences[node.sequenceId].sequence;
+        let fallbackKeys = [];
+        let prevKey = sequence.findPrev(node.id);
+        if (prevKey != null) {
+            fallbackKeys.push(prevKey);
+        }
+        let nextKey = sequence.findNext(node.id);
+        if (nextKey != null) {
+            fallbackKeys.push(nextKey);
+        }
+        let allSpatialNodes = this._requiredSpatialArea[key].all;
+        let potentialNodes = [];
+        let filter = this._filter;
+        for (let spatialNodeKey in allSpatialNodes) {
+            if (!allSpatialNodes.hasOwnProperty(spatialNodeKey)) {
+                continue;
+            }
+            let spatialNode = allSpatialNodes[spatialNodeKey];
+            if (filter(spatialNode)) {
+                potentialNodes.push(spatialNode);
+            }
+        }
+        let potentialEdges = this._edgeCalculator.getPotentialEdges(node, potentialNodes, fallbackKeys);
+        let edges = this._edgeCalculator.computeStepEdges(node, potentialEdges, prevKey, nextKey);
+        edges = edges.concat(this._edgeCalculator.computeTurnEdges(node, potentialEdges));
+        edges = edges.concat(this._edgeCalculator.computeSphericalEdges(node, potentialEdges));
+        edges = edges.concat(this._edgeCalculator.computePerspectiveToSphericalEdges(node, potentialEdges));
+        edges = edges.concat(this._edgeCalculator.computeSimilarEdges(node, potentialEdges));
+        node.cacheSpatialEdges(edges);
+        this._cachedSpatialEdges[key] = node;
+        delete this._requiredSpatialArea[key];
+        delete this._cachedNodeTiles[key];
+    }
+    /**
+     * Retrieve and cache tiles for a node.
+     *
+     * @param {string} key - Key of node for which to retrieve tiles.
+     * @returns {Array<Observable<Graph>>} Array of observables emitting
+     * the graph for each tile required for the node has been cached.
+     * @throws {GraphMapillaryError} When the operation is not valid on the
+     * current graph.
+     */
+    cacheTiles$(key) {
+        if (key in this._cachedNodeTiles) {
+            throw new GraphMapillaryError(`Tiles already cached (${key}).`);
+        }
+        if (key in this._cachedSpatialEdges) {
+            throw new GraphMapillaryError(`Spatial edges already cached so tiles considered cached (${key}).`);
+        }
+        if (!(key in this._requiredNodeTiles)) {
+            throw new GraphMapillaryError(`Tiles have not been determined (${key}).`);
+        }
+        let nodeTiles = this._requiredNodeTiles[key];
+        if (nodeTiles.cache.length === 0 &&
+            nodeTiles.caching.length === 0) {
+            throw new GraphMapillaryError(`Tiles already cached (${key}).`);
+        }
+        if (!this.hasNode(key)) {
+            throw new GraphMapillaryError(`Cannot cache tiles of node that does not exist in graph (${key}).`);
+        }
+        let hs = nodeTiles.cache.slice();
+        nodeTiles.caching = this._requiredNodeTiles[key].caching.concat(hs);
+        nodeTiles.cache = [];
+        let cacheTiles$ = [];
+        for (let h of nodeTiles.caching) {
+            const cacheTile$ = h in this._cachingTiles$ ?
+                this._cachingTiles$[h] :
+                this._cacheTile$(h);
+            cacheTiles$.push(cacheTile$.pipe(tap((graph) => {
+                let index = nodeTiles.caching.indexOf(h);
+                if (index > -1) {
+                    nodeTiles.caching.splice(index, 1);
+                }
+                if (nodeTiles.caching.length === 0 &&
+                    nodeTiles.cache.length === 0) {
+                    delete this._requiredNodeTiles[key];
+                    this._cachedNodeTiles[key] = true;
+                }
+            }), catchError((error) => {
+                let index = nodeTiles.caching.indexOf(h);
+                if (index > -1) {
+                    nodeTiles.caching.splice(index, 1);
+                }
+                if (nodeTiles.caching.length === 0 &&
+                    nodeTiles.cache.length === 0) {
+                    delete this._requiredNodeTiles[key];
+                    this._cachedNodeTiles[key] = true;
+                }
+                throw error;
+            }), finalize(() => {
+                this._changed$.next(this);
+            }), publish(), refCount()));
+        }
+        return cacheTiles$;
+    }
+    /**
+     * Initialize the cache for a node.
+     *
+     * @param {string} key - Key of node.
+     * @throws {GraphMapillaryError} When the operation is not valid on the
+     * current graph.
+     */
+    initializeCache(key) {
+        if (key in this._cachedNodes) {
+            throw new GraphMapillaryError(`Image already in cache (${key}).`);
+        }
+        const node = this.getNode(key);
+        const provider = this._api.data;
+        node.initializeCache(new ImageCache(provider));
+        const accessed = new Date().getTime();
+        this._cachedNodes[key] = { accessed: accessed, node: node };
+        this._updateCachedTileAccess(key, accessed);
+    }
+    /**
+     * Get a value indicating if the graph is fill caching a node.
+     *
+     * @param {string} key - Key of node.
+     * @returns {boolean} Value indicating if the node is being fill cached.
+     */
+    isCachingFill(key) {
+        return key in this._cachingFill$;
+    }
+    /**
+     * Get a value indicating if the graph is fully caching a node.
+     *
+     * @param {string} key - Key of node.
+     * @returns {boolean} Value indicating if the node is being fully cached.
+     */
+    isCachingFull(key) {
+        return key in this._cachingFull$;
+    }
+    /**
+     * Get a value indicating if the graph is caching a sequence of a node.
+     *
+     * @param {string} key - Key of node.
+     * @returns {boolean} Value indicating if the sequence of a node is
+     * being cached.
+     */
+    isCachingNodeSequence(key) {
+        let node = this.getNode(key);
+        return node.sequenceId in this._cachingSequences$;
+    }
+    /**
+     * Get a value indicating if the graph is caching a sequence.
+     *
+     * @param {string} sequenceKey - Key of sequence.
+     * @returns {boolean} Value indicating if the sequence is
+     * being cached.
+     */
+    isCachingSequence(sequenceKey) {
+        return sequenceKey in this._cachingSequences$;
+    }
+    /**
+     * Get a value indicating if the graph is caching sequence nodes.
+     *
+     * @param {string} sequenceKey - Key of sequence.
+     * @returns {boolean} Value indicating if the sequence nodes are
+     * being cached.
+     */
+    isCachingSequenceNodes(sequenceKey) {
+        return sequenceKey in this._cachingSequenceNodes$;
+    }
+    /**
+     * Get a value indicating if the graph is caching the tiles
+     * required for calculating spatial edges of a node.
+     *
+     * @param {string} key - Key of node.
+     * @returns {boolean} Value indicating if the tiles of
+     * a node are being cached.
+     */
+    isCachingTiles(key) {
+        return key in this._requiredNodeTiles &&
+            this._requiredNodeTiles[key].cache.length === 0 &&
+            this._requiredNodeTiles[key].caching.length > 0;
+    }
+    /**
+     * Get a value indicating if the cache has been initialized
+     * for a node.
+     *
+     * @param {string} key - Key of node.
+     * @returns {boolean} Value indicating if the cache has been
+     * initialized for a node.
+     */
+    hasInitializedCache(key) {
+        return key in this._cachedNodes;
+    }
+    /**
+     * Get a value indicating if a node exist in the graph.
+     *
+     * @param {string} key - Key of node.
+     * @returns {boolean} Value indicating if a node exist in the graph.
+     */
+    hasNode(key) {
+        let accessed = new Date().getTime();
+        this._updateCachedNodeAccess(key, accessed);
+        this._updateCachedTileAccess(key, accessed);
+        return key in this._nodes;
+    }
+    /**
+     * Get a value indicating if a node sequence exist in the graph.
+     *
+     * @param {string} key - Key of node.
+     * @returns {boolean} Value indicating if a node sequence exist
+     * in the graph.
+     */
+    hasNodeSequence(key) {
+        let node = this.getNode(key);
+        let sequenceKey = node.sequenceId;
+        let hasNodeSequence = sequenceKey in this._sequences;
+        if (hasNodeSequence) {
+            this._sequences[sequenceKey].accessed = new Date().getTime();
+        }
+        return hasNodeSequence;
+    }
+    /**
+     * Get a value indicating if a sequence exist in the graph.
+     *
+     * @param {string} sequenceKey - Key of sequence.
+     * @returns {boolean} Value indicating if a sequence exist
+     * in the graph.
+     */
+    hasSequence(sequenceKey) {
+        let hasSequence = sequenceKey in this._sequences;
+        if (hasSequence) {
+            this._sequences[sequenceKey].accessed = new Date().getTime();
+        }
+        return hasSequence;
+    }
+    /**
+     * Get a value indicating if sequence nodes has been cached in the graph.
+     *
+     * @param {string} sequenceKey - Key of sequence.
+     * @returns {boolean} Value indicating if a sequence nodes has been
+     * cached in the graph.
+     */
+    hasSequenceNodes(sequenceKey) {
+        return sequenceKey in this._cachedSequenceNodes;
+    }
+    /**
+     * Get a value indicating if the graph has fully cached
+     * all nodes in the spatial area of a node.
+     *
+     * @param {string} key - Key of node.
+     * @returns {boolean} Value indicating if the spatial area
+     * of a node has been cached.
+     */
+    hasSpatialArea(key) {
+        if (!this.hasNode(key)) {
+            throw new GraphMapillaryError(`Spatial area nodes cannot be determined if node not in graph (${key}).`);
+        }
+        if (key in this._cachedSpatialEdges) {
+            return true;
+        }
+        if (key in this._requiredSpatialArea) {
+            return Object
+                .keys(this._requiredSpatialArea[key].cacheNodes)
+                .length === 0;
+        }
+        let node = this.getNode(key);
+        let bbox = this._graphCalculator
+            .boundingBoxCorners(node.lngLat, this._tileThreshold);
+        let spatialItems = this._nodeIndex
+            .search({
+            maxX: bbox[1].lng,
+            maxY: bbox[1].lat,
+            minX: bbox[0].lng,
+            minY: bbox[0].lat,
+        });
+        let spatialNodes = {
+            all: {},
+            cacheKeys: [],
+            cacheNodes: {},
+        };
+        for (let spatialItem of spatialItems) {
+            spatialNodes.all[spatialItem.node.id] = spatialItem.node;
+            if (!spatialItem.node.complete) {
+                spatialNodes.cacheKeys.push(spatialItem.node.id);
+                spatialNodes.cacheNodes[spatialItem.node.id] = spatialItem.node;
+            }
+        }
+        this._requiredSpatialArea[key] = spatialNodes;
+        return spatialNodes.cacheKeys.length === 0;
+    }
+    /**
+     * Get a value indicating if the graph has a tiles required
+     * for a node.
+     *
+     * @param {string} key - Key of node.
+     * @returns {boolean} Value indicating if the the tiles required
+     * by a node has been cached.
+     */
+    hasTiles(key) {
+        if (key in this._cachedNodeTiles) {
+            return true;
+        }
+        if (key in this._cachedSpatialEdges) {
+            return true;
+        }
+        if (!this.hasNode(key)) {
+            throw new GraphMapillaryError(`Image does not exist in graph (${key}).`);
+        }
+        let nodeTiles = { cache: [], caching: [] };
+        if (!(key in this._requiredNodeTiles)) {
+            const node = this.getNode(key);
+            const [sw, ne] = this._graphCalculator
+                .boundingBoxCorners(node.lngLat, this._tileThreshold);
+            nodeTiles.cache = this._api.data.geometry
+                .bboxToCellIds(sw, ne)
+                .filter((h) => {
+                return !(h in this._cachedTiles);
+            });
+            if (nodeTiles.cache.length > 0) {
+                this._requiredNodeTiles[key] = nodeTiles;
+            }
+        }
+        else {
+            nodeTiles = this._requiredNodeTiles[key];
+        }
+        return nodeTiles.cache.length === 0 && nodeTiles.caching.length === 0;
+    }
+    /**
+     * Get a node.
+     *
+     * @param {string} key - Key of node.
+     * @returns {Image} Retrieved node.
+     */
+    getNode(key) {
+        let accessed = new Date().getTime();
+        this._updateCachedNodeAccess(key, accessed);
+        this._updateCachedTileAccess(key, accessed);
+        return this._nodes[key];
+    }
+    /**
+     * Get a sequence.
+     *
+     * @param {string} sequenceKey - Key of sequence.
+     * @returns {Image} Retrieved sequence.
+     */
+    getSequence(sequenceKey) {
+        let sequenceAccess = this._sequences[sequenceKey];
+        sequenceAccess.accessed = new Date().getTime();
+        return sequenceAccess.sequence;
+    }
+    /**
+     * Reset all spatial edges of the graph nodes.
+     */
+    resetSpatialEdges() {
+        let cachedKeys = Object.keys(this._cachedSpatialEdges);
+        for (let cachedKey of cachedKeys) {
+            let node = this._cachedSpatialEdges[cachedKey];
+            node.resetSpatialEdges();
+            delete this._cachedSpatialEdges[cachedKey];
+        }
+    }
+    /**
+     * Reset the complete graph but keep the nodes corresponding
+     * to the supplied keys. All other nodes will be disposed.
+     *
+     * @param {Array<string>} keepKeys - Keys for nodes to keep
+     * in graph after reset.
+     */
+    reset(keepKeys) {
+        const nodes = [];
+        for (const key of keepKeys) {
+            if (!this.hasNode(key)) {
+                throw new Error(`Image does not exist ${key}`);
+            }
+            const node = this.getNode(key);
+            node.resetSequenceEdges();
+            node.resetSpatialEdges();
+            nodes.push(node);
+        }
+        for (let cachedKey of Object.keys(this._cachedNodes)) {
+            if (keepKeys.indexOf(cachedKey) !== -1) {
+                continue;
+            }
+            this._cachedNodes[cachedKey].node.dispose();
+            delete this._cachedNodes[cachedKey];
+        }
+        this._cachedNodeTiles = {};
+        this._cachedSpatialEdges = {};
+        this._cachedTiles = {};
+        this._cachingFill$ = {};
+        this._cachingFull$ = {};
+        this._cachingSequences$ = {};
+        this._cachingSpatialArea$ = {};
+        this._cachingTiles$ = {};
+        this._nodes = {};
+        this._nodeToTile = {};
+        this._preStored = {};
+        for (const node of nodes) {
+            this._nodes[node.id] = node;
+            const h = this._api.data.geometry.lngLatToCellId(node.originalLngLat);
+            this._preStore(h, node);
+        }
+        this._requiredNodeTiles = {};
+        this._requiredSpatialArea = {};
+        this._sequences = {};
+        this._nodeIndexTiles = {};
+        this._nodeIndex.clear();
+    }
+    /**
+     * Set the spatial node filter.
+     *
+     * @emits FilterFunction The filter function to the {@link Graph.filter$}
+     * observable.
+     *
+     * @param {FilterExpression} filter - Filter expression to be applied
+     * when calculating spatial edges.
+     */
+    setFilter(filter) {
+        this._filter = this._filterCreator.createFilter(filter);
+        this._filterSubject$.next(this._filter);
+    }
+    /**
+     * Uncache the graph according to the graph configuration.
+     *
+     * @description Uncaches unused tiles, unused nodes and
+     * sequences according to the numbers specified in the
+     * graph configuration. Sequences does not have a direct
+     * reference to either tiles or nodes and may be uncached
+     * even if they are related to the nodes that should be kept.
+     *
+     * @param {Array<string>} keepIds - Ids of nodes to keep in
+     * graph unrelated to last access. Tiles related to those keys
+     * will also be kept in graph.
+     * @param {Array<string>} keepCellIds - Ids of cells to keep in
+     * graph unrelated to last access. The nodes of the cells may
+     * still be uncached if not specified in the keep ids param
+     * but are guaranteed to not be disposed.
+     * @param {string} keepSequenceId - Optional id of sequence
+     * for which the belonging nodes should not be disposed or
+     * removed from the graph. These nodes may still be uncached if
+     * not specified in keep ids param but are guaranteed to not
+     * be disposed.
+     */
+    uncache(keepIds, keepCellIds, keepSequenceId) {
+        const idsInUse = {};
+        this._addNewKeys(idsInUse, this._cachingFull$);
+        this._addNewKeys(idsInUse, this._cachingFill$);
+        this._addNewKeys(idsInUse, this._cachingSpatialArea$);
+        this._addNewKeys(idsInUse, this._requiredNodeTiles);
+        this._addNewKeys(idsInUse, this._requiredSpatialArea);
+        for (const key of keepIds) {
+            if (key in idsInUse) {
+                continue;
+            }
+            idsInUse[key] = true;
+        }
+        const tileThreshold = this._tileThreshold;
+        const calculator = this._graphCalculator;
+        const geometry = this._api.data.geometry;
+        const keepCells = new Set(keepCellIds);
+        for (let id in idsInUse) {
+            if (!idsInUse.hasOwnProperty(id)) {
+                continue;
+            }
+            const node = this._nodes[id];
+            const [sw, ne] = calculator
+                .boundingBoxCorners(node.lngLat, tileThreshold);
+            const nodeCellIds = geometry.bboxToCellIds(sw, ne);
+            for (const nodeCellId of nodeCellIds) {
+                if (!keepCells.has(nodeCellId)) {
+                    keepCells.add(nodeCellId);
+                }
+            }
+        }
+        const potentialCells = [];
+        for (let cellId in this._cachedTiles) {
+            if (!this._cachedTiles.hasOwnProperty(cellId) ||
+                keepCells.has(cellId)) {
+                continue;
+            }
+            potentialCells.push([cellId, this._cachedTiles[cellId]]);
+        }
+        const uncacheCells = potentialCells
+            .sort((h1, h2) => {
+            return h2[1].accessed - h1[1].accessed;
+        })
+            .slice(this._configuration.maxUnusedTiles)
+            .map((h) => {
+            return h[0];
+        });
+        for (let uncacheCell of uncacheCells) {
+            this._uncacheTile(uncacheCell, keepSequenceId);
+        }
+        const potentialPreStored = [];
+        const nonCachedPreStored = [];
+        for (let cellId in this._preStored) {
+            if (!this._preStored.hasOwnProperty(cellId) ||
+                cellId in this._cachingTiles$) {
+                continue;
+            }
+            const prestoredNodes = this._preStored[cellId];
+            for (let id in prestoredNodes) {
+                if (!prestoredNodes.hasOwnProperty(id) || id in idsInUse) {
+                    continue;
+                }
+                if (prestoredNodes[id].sequenceId === keepSequenceId) {
+                    continue;
+                }
+                if (id in this._cachedNodes) {
+                    potentialPreStored.push([this._cachedNodes[id], cellId]);
+                }
+                else {
+                    nonCachedPreStored.push([id, cellId]);
+                }
+            }
+        }
+        const uncachePreStored = potentialPreStored
+            .sort(([na1], [na2]) => {
+            return na2.accessed - na1.accessed;
+        })
+            .slice(this._configuration.maxUnusedPreStoredImages)
+            .map(([na, h]) => {
+            return [na.node.id, h];
+        });
+        this._uncachePreStored(nonCachedPreStored);
+        this._uncachePreStored(uncachePreStored);
+        const potentialNodes = [];
+        for (let id in this._cachedNodes) {
+            if (!this._cachedNodes.hasOwnProperty(id) || id in idsInUse) {
+                continue;
+            }
+            potentialNodes.push(this._cachedNodes[id]);
+        }
+        const uncacheNodes = potentialNodes
+            .sort((n1, n2) => {
+            return n2.accessed - n1.accessed;
+        })
+            .slice(this._configuration.maxUnusedImages);
+        for (const nodeAccess of uncacheNodes) {
+            nodeAccess.node.uncache();
+            const id = nodeAccess.node.id;
+            delete this._cachedNodes[id];
+            if (id in this._cachedNodeTiles) {
+                delete this._cachedNodeTiles[id];
+            }
+            if (id in this._cachedSpatialEdges) {
+                delete this._cachedSpatialEdges[id];
+            }
+        }
+        const potentialSequences = [];
+        for (let sequenceId in this._sequences) {
+            if (!this._sequences.hasOwnProperty(sequenceId) ||
+                sequenceId in this._cachingSequences$ ||
+                sequenceId === keepSequenceId) {
+                continue;
+            }
+            potentialSequences.push(this._sequences[sequenceId]);
+        }
+        const uncacheSequences = potentialSequences
+            .sort((s1, s2) => {
+            return s2.accessed - s1.accessed;
+        })
+            .slice(this._configuration.maxSequences);
+        for (const sequenceAccess of uncacheSequences) {
+            const sequenceId = sequenceAccess.sequence.id;
+            delete this._sequences[sequenceId];
+            if (sequenceId in this._cachedSequenceNodes) {
+                delete this._cachedSequenceNodes[sequenceId];
+            }
+            sequenceAccess.sequence.dispose();
+        }
+    }
+    /**
+     * Updates existing cells with new core nodes.
+     *
+     * @description Non-existing cells are discarded
+     * and not requested at all.
+     *
+     * Existing nodes are not changed.
+     *
+     * New nodes are not made full or getting assets
+     * cached.
+     *
+     * @param {Array<string>} cellIds - Cell ids.
+     * @returns {Observable<Array<Image>>} Observable
+     * emitting the updated cells.
+     */
+    updateCells$(cellIds) {
+        const cachedCells = this._cachedTiles;
+        const cachingCells = this._cachingTiles$;
+        return from(cellIds)
+            .pipe(mergeMap((cellId) => {
+            if (cellId in cachedCells) {
+                return this._updateCell$(cellId);
+            }
+            if (cellId in cachingCells) {
+                return cachingCells[cellId]
+                    .pipe(catchError(() => {
+                    return of(this);
+                }), mergeMap(() => this._updateCell$(cellId)));
+            }
+            return empty();
+        }));
+    }
+    /**
+     * Unsubscribes all subscriptions.
+     *
+     * @description Afterwards, you must not call any other methods
+     * on the graph instance.
+     */
+    unsubscribe() {
+        this._filterSubscription.unsubscribe();
+    }
+    _addNewKeys(keys, dict) {
+        for (let key in dict) {
+            if (!dict.hasOwnProperty(key) || !this.hasNode(key)) {
+                continue;
+            }
+            if (!(key in keys)) {
+                keys[key] = true;
+            }
+        }
+    }
+    _cacheSequence$(sequenceId) {
+        if (sequenceId in this._cachingSequences$) {
+            return this._cachingSequences$[sequenceId];
+        }
+        this._cachingSequences$[sequenceId] = this._api
+            .getSequence$(sequenceId)
+            .pipe(tap((sequence) => {
+            if (!sequence) {
+                console.warn(`Sequence does not exist ` +
+                    `(${sequenceId})`);
+            }
+            else {
+                if (!(sequence.id in this._sequences)) {
+                    this._sequences[sequence.id] = {
+                        accessed: new Date().getTime(),
+                        sequence: new Sequence(sequence),
+                    };
+                }
+                delete this._cachingSequences$[sequenceId];
+            }
+        }), map(() => { return this; }), finalize(() => {
+            if (sequenceId in this._cachingSequences$) {
+                delete this._cachingSequences$[sequenceId];
+            }
+            this._changed$.next(this);
+        }), publish(), refCount());
+        return this._cachingSequences$[sequenceId];
+    }
+    _cacheTile$(cellId) {
+        this._cachingTiles$[cellId] = this._api
+            .getCoreImages$(cellId)
+            .pipe(tap((contract) => {
+            if (cellId in this._cachedTiles) {
+                return;
+            }
+            const cores = contract.images;
+            this._nodeIndexTiles[cellId] = [];
+            this._cachedTiles[cellId] = {
+                accessed: new Date().getTime(),
+                nodes: [],
+            };
+            const hCache = this._cachedTiles[cellId].nodes;
+            const preStored = this._removeFromPreStore(cellId);
+            for (const core of cores) {
+                if (!core) {
+                    break;
+                }
+                if (core.sequence.id == null) {
+                    console.warn(`Sequence missing, discarding ` +
+                        `node (${core.id})`);
+                    continue;
+                }
+                if (preStored != null && core.id in preStored) {
+                    const preStoredNode = preStored[core.id];
+                    delete preStored[core.id];
+                    hCache.push(preStoredNode);
+                    const preStoredNodeIndexItem = {
+                        lat: preStoredNode.lngLat.lat,
+                        lng: preStoredNode.lngLat.lng,
+                        node: preStoredNode,
+                    };
+                    this._nodeIndex.insert(preStoredNodeIndexItem);
+                    this._nodeIndexTiles[cellId]
+                        .push(preStoredNodeIndexItem);
+                    this._nodeToTile[preStoredNode.id] = cellId;
+                    continue;
+                }
+                const node = new Image$1(core);
+                hCache.push(node);
+                const nodeIndexItem = {
+                    lat: node.lngLat.lat,
+                    lng: node.lngLat.lng,
+                    node: node,
+                };
+                this._nodeIndex.insert(nodeIndexItem);
+                this._nodeIndexTiles[cellId].push(nodeIndexItem);
+                this._nodeToTile[node.id] = cellId;
+                this._setNode(node);
+            }
+            delete this._cachingTiles$[cellId];
+        }), map(() => this), catchError((error) => {
+            delete this._cachingTiles$[cellId];
+            throw error;
+        }), publish(), refCount());
+        return this._cachingTiles$[cellId];
+    }
+    _makeFull(node, fillNode) {
+        if (fillNode.computed_altitude == null) {
+            fillNode.computed_altitude = this._defaultAlt;
+        }
+        if (fillNode.computed_rotation == null) {
+            fillNode.computed_rotation = this._graphCalculator.rotationFromCompass(fillNode.compass_angle, fillNode.exif_orientation);
+        }
+        node.makeComplete(fillNode);
+    }
+    _preStore(h, node) {
+        if (!(h in this._preStored)) {
+            this._preStored[h] = {};
+        }
+        this._preStored[h][node.id] = node;
+    }
+    _removeFromPreStore(h) {
+        let preStored = null;
+        if (h in this._preStored) {
+            preStored = this._preStored[h];
+            delete this._preStored[h];
+        }
+        return preStored;
+    }
+    _setNode(node) {
+        let key = node.id;
+        if (this.hasNode(key)) {
+            throw new GraphMapillaryError(`Image already exist (${key}).`);
+        }
+        this._nodes[key] = node;
+    }
+    _uncacheTile(h, keepSequenceKey) {
+        for (let node of this._cachedTiles[h].nodes) {
+            let key = node.id;
+            delete this._nodeToTile[key];
+            if (key in this._cachedNodes) {
+                delete this._cachedNodes[key];
+            }
+            if (key in this._cachedNodeTiles) {
+                delete this._cachedNodeTiles[key];
+            }
+            if (key in this._cachedSpatialEdges) {
+                delete this._cachedSpatialEdges[key];
+            }
+            if (node.sequenceId === keepSequenceKey) {
+                this._preStore(h, node);
+                node.uncache();
+            }
+            else {
+                delete this._nodes[key];
+                if (node.sequenceId in this._cachedSequenceNodes) {
+                    delete this._cachedSequenceNodes[node.sequenceId];
+                }
+                node.dispose();
+            }
+        }
+        for (let nodeIndexItem of this._nodeIndexTiles[h]) {
+            this._nodeIndex.remove(nodeIndexItem);
+        }
+        delete this._nodeIndexTiles[h];
+        delete this._cachedTiles[h];
+    }
+    _uncachePreStored(preStored) {
+        let hs = {};
+        for (let [key, h] of preStored) {
+            if (key in this._nodes) {
+                delete this._nodes[key];
+            }
+            if (key in this._cachedNodes) {
+                delete this._cachedNodes[key];
+            }
+            let node = this._preStored[h][key];
+            if (node.sequenceId in this._cachedSequenceNodes) {
+                delete this._cachedSequenceNodes[node.sequenceId];
+            }
+            delete this._preStored[h][key];
+            node.dispose();
+            hs[h] = true;
+        }
+        for (let h in hs) {
+            if (!hs.hasOwnProperty(h)) {
+                continue;
+            }
+            if (Object.keys(this._preStored[h]).length === 0) {
+                delete this._preStored[h];
+            }
+        }
+    }
+    _updateCachedTileAccess(key, accessed) {
+        if (key in this._nodeToTile) {
+            this._cachedTiles[this._nodeToTile[key]].accessed = accessed;
+        }
+    }
+    _updateCachedNodeAccess(key, accessed) {
+        if (key in this._cachedNodes) {
+            this._cachedNodes[key].accessed = accessed;
+        }
+    }
+    _updateCell$(cellId) {
+        return this._api.getCoreImages$(cellId).pipe(mergeMap((contract) => {
+            if (!(cellId in this._cachedTiles)) {
+                return empty();
+            }
+            const nodeIndex = this._nodeIndex;
+            const nodeIndexCell = this._nodeIndexTiles[cellId];
+            const nodeToCell = this._nodeToTile;
+            const cell = this._cachedTiles[cellId];
+            cell.accessed = new Date().getTime();
+            const cellNodes = cell.nodes;
+            const cores = contract.images;
+            for (const core of cores) {
+                if (core == null) {
+                    break;
+                }
+                if (this.hasNode(core.id)) {
+                    continue;
+                }
+                if (core.sequence.id == null) {
+                    console.warn(`Sequence missing, discarding ` +
+                        `node (${core.id})`);
+                    continue;
+                }
+                const node = new Image$1(core);
+                cellNodes.push(node);
+                const nodeIndexItem = {
+                    lat: node.lngLat.lat,
+                    lng: node.lngLat.lng,
+                    node: node,
+                };
+                nodeIndex.insert(nodeIndexItem);
+                nodeIndexCell.push(nodeIndexItem);
+                nodeToCell[node.id] = cellId;
+                this._setNode(node);
+            }
+            return of(cellId);
+        }), catchError((error) => {
+            console.error(error);
+            return empty();
+        }));
+    }
+}
+
+class MarkerSet {
+    constructor() {
+        this._hash = {};
+        this._index = new MarkerSet._spatialIndex(16);
+        this._indexChanged$ = new Subject();
+        this._updated$ = new Subject();
+    }
+    static register(spatialIndex) {
+        MarkerSet._spatialIndex = spatialIndex;
+    }
+    get changed$() {
+        return this._indexChanged$;
+    }
+    get updated$() {
+        return this._updated$;
+    }
+    add(markers) {
+        const updated = [];
+        const hash = this._hash;
+        const index = this._index;
+        for (const marker of markers) {
+            const id = marker.id;
+            if (id in hash) {
+                index.remove(hash[id]);
+                updated.push(marker);
+            }
+            const item = {
+                lat: marker.lngLat.lat,
+                lng: marker.lngLat.lng,
+                marker: marker,
+            };
+            hash[id] = item;
+            index.insert(item);
+        }
+        if (updated.length > 0) {
+            this._updated$.next(updated);
+        }
+        if (markers.length > updated.length) {
+            this._indexChanged$.next(this);
+        }
+    }
+    has(id) {
+        return id in this._hash;
+    }
+    get(id) {
+        return this.has(id) ? this._hash[id].marker : undefined;
+    }
+    getAll() {
+        return this._index
+            .all()
+            .map((indexItem) => {
+            return indexItem.marker;
+        });
+    }
+    remove(ids) {
+        const hash = this._hash;
+        const index = this._index;
+        let changed = false;
+        for (const id of ids) {
+            if (!(id in hash)) {
+                continue;
+            }
+            const item = hash[id];
+            index.remove(item);
+            delete hash[id];
+            changed = true;
+        }
+        if (changed) {
+            this._indexChanged$.next(this);
+        }
+    }
+    removeAll() {
+        this._hash = {};
+        this._index.clear();
+        this._indexChanged$.next(this);
+    }
+    search([sw, ne]) {
+        return this._index
+            .search({
+            maxX: ne.lng,
+            maxY: ne.lat,
+            minX: sw.lng,
+            minY: sw.lat,
+        })
+            .map((indexItem) => {
+            return indexItem.marker;
+        });
+    }
+    update(marker) {
+        const hash = this._hash;
+        const index = this._index;
+        const id = marker.id;
+        if (!(id in hash)) {
+            return;
+        }
+        index.remove(hash[id]);
+        const item = {
+            lat: marker.lngLat.lat,
+            lng: marker.lngLat.lng,
+            marker: marker,
+        };
+        hash[id] = item;
+        index.insert(item);
+    }
+}
+
+function quickselect(arr, k, left, right, compare) {
+    quickselectStep(arr, k, left || 0, right || (arr.length - 1), compare || defaultCompare$2);
+}
+
+function quickselectStep(arr, k, left, right, compare) {
+
+    while (right > left) {
+        if (right - left > 600) {
+            var n = right - left + 1;
+            var m = k - left + 1;
+            var z = Math.log(n);
+            var s = 0.5 * Math.exp(2 * z / 3);
+            var sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1);
+            var newLeft = Math.max(left, Math.floor(k - m * s / n + sd));
+            var newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd));
+            quickselectStep(arr, k, newLeft, newRight, compare);
+        }
+
+        var t = arr[k];
+        var i = left;
+        var j = right;
+
+        swap(arr, left, k);
+        if (compare(arr[right], t) > 0) swap(arr, left, right);
+
+        while (i < j) {
+            swap(arr, i, j);
+            i++;
+            j--;
+            while (compare(arr[i], t) < 0) i++;
+            while (compare(arr[j], t) > 0) j--;
+        }
+
+        if (compare(arr[left], t) === 0) swap(arr, left, j);
+        else {
+            j++;
+            swap(arr, j, right);
+        }
+
+        if (j <= k) left = j + 1;
+        if (k <= j) right = j - 1;
+    }
+}
+
+function swap(arr, i, j) {
+    var tmp = arr[i];
+    arr[i] = arr[j];
+    arr[j] = tmp;
+}
+
+function defaultCompare$2(a, b) {
+    return a < b ? -1 : a > b ? 1 : 0;
+}
+
+class RBush {
+    constructor(maxEntries = 9) {
+        // max entries in a node is 9 by default; min node fill is 40% for best performance
+        this._maxEntries = Math.max(4, maxEntries);
+        this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4));
+        this.clear();
+    }
+
+    all() {
+        return this._all(this.data, []);
+    }
+
+    search(bbox) {
+        let node = this.data;
+        const result = [];
+
+        if (!intersects$1(bbox, node)) return result;
+
+        const toBBox = this.toBBox;
+        const nodesToSearch = [];
+
+        while (node) {
+            for (let i = 0; i < node.children.length; i++) {
+                const child = node.children[i];
+                const childBBox = node.leaf ? toBBox(child) : child;
+
+                if (intersects$1(bbox, childBBox)) {
+                    if (node.leaf) result.push(child);
+                    else if (contains(bbox, childBBox)) this._all(child, result);
+                    else nodesToSearch.push(child);
+                }
+            }
+            node = nodesToSearch.pop();
+        }
+
+        return result;
+    }
+
+    collides(bbox) {
+        let node = this.data;
+
+        if (!intersects$1(bbox, node)) return false;
+
+        const nodesToSearch = [];
+        while (node) {
+            for (let i = 0; i < node.children.length; i++) {
+                const child = node.children[i];
+                const childBBox = node.leaf ? this.toBBox(child) : child;
+
+                if (intersects$1(bbox, childBBox)) {
+                    if (node.leaf || contains(bbox, childBBox)) return true;
+                    nodesToSearch.push(child);
+                }
+            }
+            node = nodesToSearch.pop();
+        }
+
+        return false;
+    }
+
+    load(data) {
+        if (!(data && data.length)) return this;
+
+        if (data.length < this._minEntries) {
+            for (let i = 0; i < data.length; i++) {
+                this.insert(data[i]);
+            }
+            return this;
+        }
+
+        // recursively build the tree with the given data from scratch using OMT algorithm
+        let node = this._build(data.slice(), 0, data.length - 1, 0);
+
+        if (!this.data.children.length) {
+            // save as is if tree is empty
+            this.data = node;
+
+        } else if (this.data.height === node.height) {
+            // split root if trees have the same height
+            this._splitRoot(this.data, node);
+
+        } else {
+            if (this.data.height < node.height) {
+                // swap trees if inserted one is bigger
+                const tmpNode = this.data;
+                this.data = node;
+                node = tmpNode;
+            }
+
+            // insert the small tree into the large tree at appropriate level
+            this._insert(node, this.data.height - node.height - 1, true);
+        }
+
+        return this;
+    }
+
+    insert(item) {
+        if (item) this._insert(item, this.data.height - 1);
+        return this;
+    }
+
+    clear() {
+        this.data = createNode([]);
+        return this;
+    }
+
+    remove(item, equalsFn) {
+        if (!item) return this;
+
+        let node = this.data;
+        const bbox = this.toBBox(item);
+        const path = [];
+        const indexes = [];
+        let i, parent, goingUp;
+
+        // depth-first iterative tree traversal
+        while (node || path.length) {
+
+            if (!node) { // go up
+                node = path.pop();
+                parent = path[path.length - 1];
+                i = indexes.pop();
+                goingUp = true;
+            }
+
+            if (node.leaf) { // check current node
+                const index = findItem(item, node.children, equalsFn);
+
+                if (index !== -1) {
+                    // item found, remove the item and condense tree upwards
+                    node.children.splice(index, 1);
+                    path.push(node);
+                    this._condense(path);
+                    return this;
+                }
+            }
+
+            if (!goingUp && !node.leaf && contains(node, bbox)) { // go down
+                path.push(node);
+                indexes.push(i);
+                i = 0;
+                parent = node;
+                node = node.children[0];
+
+            } else if (parent) { // go right
+                i++;
+                node = parent.children[i];
+                goingUp = false;
+
+            } else node = null; // nothing found
+        }
+
+        return this;
+    }
+
+    toBBox(item) { return item; }
+
+    compareMinX(a, b) { return a.minX - b.minX; }
+    compareMinY(a, b) { return a.minY - b.minY; }
+
+    toJSON() { return this.data; }
+
+    fromJSON(data) {
+        this.data = data;
+        return this;
+    }
+
+    _all(node, result) {
+        const nodesToSearch = [];
+        while (node) {
+            if (node.leaf) result.push(...node.children);
+            else nodesToSearch.push(...node.children);
+
+            node = nodesToSearch.pop();
+        }
+        return result;
+    }
+
+    _build(items, left, right, height) {
+
+        const N = right - left + 1;
+        let M = this._maxEntries;
+        let node;
+
+        if (N <= M) {
+            // reached leaf level; return leaf
+            node = createNode(items.slice(left, right + 1));
+            calcBBox(node, this.toBBox);
+            return node;
+        }
+
+        if (!height) {
+            // target height of the bulk-loaded tree
+            height = Math.ceil(Math.log(N) / Math.log(M));
+
+            // target number of root entries to maximize storage utilization
+            M = Math.ceil(N / Math.pow(M, height - 1));
+        }
+
+        node = createNode([]);
+        node.leaf = false;
+        node.height = height;
+
+        // split the items into M mostly square tiles
+
+        const N2 = Math.ceil(N / M);
+        const N1 = N2 * Math.ceil(Math.sqrt(M));
+
+        multiSelect(items, left, right, N1, this.compareMinX);
+
+        for (let i = left; i <= right; i += N1) {
+
+            const right2 = Math.min(i + N1 - 1, right);
+
+            multiSelect(items, i, right2, N2, this.compareMinY);
+
+            for (let j = i; j <= right2; j += N2) {
+
+                const right3 = Math.min(j + N2 - 1, right2);
+
+                // pack each entry recursively
+                node.children.push(this._build(items, j, right3, height - 1));
+            }
+        }
+
+        calcBBox(node, this.toBBox);
+
+        return node;
+    }
+
+    _chooseSubtree(bbox, node, level, path) {
+        while (true) {
+            path.push(node);
+
+            if (node.leaf || path.length - 1 === level) break;
+
+            let minArea = Infinity;
+            let minEnlargement = Infinity;
+            let targetNode;
+
+            for (let i = 0; i < node.children.length; i++) {
+                const child = node.children[i];
+                const area = bboxArea(child);
+                const enlargement = enlargedArea(bbox, child) - area;
+
+                // choose entry with the least area enlargement
+                if (enlargement < minEnlargement) {
+                    minEnlargement = enlargement;
+                    minArea = area < minArea ? area : minArea;
+                    targetNode = child;
+
+                } else if (enlargement === minEnlargement) {
+                    // otherwise choose one with the smallest area
+                    if (area < minArea) {
+                        minArea = area;
+                        targetNode = child;
+                    }
+                }
+            }
+
+            node = targetNode || node.children[0];
+        }
+
+        return node;
+    }
+
+    _insert(item, level, isNode) {
+        const bbox = isNode ? item : this.toBBox(item);
+        const insertPath = [];
+
+        // find the best node for accommodating the item, saving all nodes along the path too
+        const node = this._chooseSubtree(bbox, this.data, level, insertPath);
+
+        // put the item into the node
+        node.children.push(item);
+        extend(node, bbox);
+
+        // split on node overflow; propagate upwards if necessary
+        while (level >= 0) {
+            if (insertPath[level].children.length > this._maxEntries) {
+                this._split(insertPath, level);
+                level--;
+            } else break;
+        }
+
+        // adjust bboxes along the insertion path
+        this._adjustParentBBoxes(bbox, insertPath, level);
+    }
+
+    // split overflowed node into two
+    _split(insertPath, level) {
+        const node = insertPath[level];
+        const M = node.children.length;
+        const m = this._minEntries;
+
+        this._chooseSplitAxis(node, m, M);
+
+        const splitIndex = this._chooseSplitIndex(node, m, M);
+
+        const newNode = createNode(node.children.splice(splitIndex, node.children.length - splitIndex));
+        newNode.height = node.height;
+        newNode.leaf = node.leaf;
+
+        calcBBox(node, this.toBBox);
+        calcBBox(newNode, this.toBBox);
+
+        if (level) insertPath[level - 1].children.push(newNode);
+        else this._splitRoot(node, newNode);
+    }
+
+    _splitRoot(node, newNode) {
+        // split root node
+        this.data = createNode([node, newNode]);
+        this.data.height = node.height + 1;
+        this.data.leaf = false;
+        calcBBox(this.data, this.toBBox);
+    }
+
+    _chooseSplitIndex(node, m, M) {
+        let index;
+        let minOverlap = Infinity;
+        let minArea = Infinity;
+
+        for (let i = m; i <= M - m; i++) {
+            const bbox1 = distBBox(node, 0, i, this.toBBox);
+            const bbox2 = distBBox(node, i, M, this.toBBox);
+
+            const overlap = intersectionArea(bbox1, bbox2);
+            const area = bboxArea(bbox1) + bboxArea(bbox2);
+
+            // choose distribution with minimum overlap
+            if (overlap < minOverlap) {
+                minOverlap = overlap;
+                index = i;
+
+                minArea = area < minArea ? area : minArea;
+
+            } else if (overlap === minOverlap) {
+                // otherwise choose distribution with minimum area
+                if (area < minArea) {
+                    minArea = area;
+                    index = i;
+                }
+            }
+        }
+
+        return index || M - m;
+    }
+
+    // sorts node children by the best axis for split
+    _chooseSplitAxis(node, m, M) {
+        const compareMinX = node.leaf ? this.compareMinX : compareNodeMinX;
+        const compareMinY = node.leaf ? this.compareMinY : compareNodeMinY;
+        const xMargin = this._allDistMargin(node, m, M, compareMinX);
+        const yMargin = this._allDistMargin(node, m, M, compareMinY);
+
+        // if total distributions margin value is minimal for x, sort by minX,
+        // otherwise it's already sorted by minY
+        if (xMargin < yMargin) node.children.sort(compareMinX);
+    }
+
+    // total margin of all possible split distributions where each node is at least m full
+    _allDistMargin(node, m, M, compare) {
+        node.children.sort(compare);
+
+        const toBBox = this.toBBox;
+        const leftBBox = distBBox(node, 0, m, toBBox);
+        const rightBBox = distBBox(node, M - m, M, toBBox);
+        let margin = bboxMargin(leftBBox) + bboxMargin(rightBBox);
+
+        for (let i = m; i < M - m; i++) {
+            const child = node.children[i];
+            extend(leftBBox, node.leaf ? toBBox(child) : child);
+            margin += bboxMargin(leftBBox);
+        }
+
+        for (let i = M - m - 1; i >= m; i--) {
+            const child = node.children[i];
+            extend(rightBBox, node.leaf ? toBBox(child) : child);
+            margin += bboxMargin(rightBBox);
+        }
+
+        return margin;
+    }
+
+    _adjustParentBBoxes(bbox, path, level) {
+        // adjust bboxes along the given tree path
+        for (let i = level; i >= 0; i--) {
+            extend(path[i], bbox);
+        }
+    }
+
+    _condense(path) {
+        // go through the path, removing empty nodes and updating bboxes
+        for (let i = path.length - 1, siblings; i >= 0; i--) {
+            if (path[i].children.length === 0) {
+                if (i > 0) {
+                    siblings = path[i - 1].children;
+                    siblings.splice(siblings.indexOf(path[i]), 1);
+
+                } else this.clear();
+
+            } else calcBBox(path[i], this.toBBox);
+        }
+    }
+}
+
+function findItem(item, items, equalsFn) {
+    if (!equalsFn) return items.indexOf(item);
+
+    for (let i = 0; i < items.length; i++) {
+        if (equalsFn(item, items[i])) return i;
+    }
+    return -1;
+}
+
+// calculate node's bbox from bboxes of its children
+function calcBBox(node, toBBox) {
+    distBBox(node, 0, node.children.length, toBBox, node);
+}
+
+// min bounding rectangle of node children from k to p-1
+function distBBox(node, k, p, toBBox, destNode) {
+    if (!destNode) destNode = createNode(null);
+    destNode.minX = Infinity;
+    destNode.minY = Infinity;
+    destNode.maxX = -Infinity;
+    destNode.maxY = -Infinity;
+
+    for (let i = k; i < p; i++) {
+        const child = node.children[i];
+        extend(destNode, node.leaf ? toBBox(child) : child);
+    }
+
+    return destNode;
+}
+
+function extend(a, b) {
+    a.minX = Math.min(a.minX, b.minX);
+    a.minY = Math.min(a.minY, b.minY);
+    a.maxX = Math.max(a.maxX, b.maxX);
+    a.maxY = Math.max(a.maxY, b.maxY);
+    return a;
+}
+
+function compareNodeMinX(a, b) { return a.minX - b.minX; }
+function compareNodeMinY(a, b) { return a.minY - b.minY; }
+
+function bboxArea(a)   { return (a.maxX - a.minX) * (a.maxY - a.minY); }
+function bboxMargin(a) { return (a.maxX - a.minX) + (a.maxY - a.minY); }
+
+function enlargedArea(a, b) {
+    return (Math.max(b.maxX, a.maxX) - Math.min(b.minX, a.minX)) *
+           (Math.max(b.maxY, a.maxY) - Math.min(b.minY, a.minY));
+}
+
+function intersectionArea(a, b) {
+    const minX = Math.max(a.minX, b.minX);
+    const minY = Math.max(a.minY, b.minY);
+    const maxX = Math.min(a.maxX, b.maxX);
+    const maxY = Math.min(a.maxY, b.maxY);
+
+    return Math.max(0, maxX - minX) *
+           Math.max(0, maxY - minY);
+}
+
+function contains(a, b) {
+    return a.minX <= b.minX &&
+           a.minY <= b.minY &&
+           b.maxX <= a.maxX &&
+           b.maxY <= a.maxY;
+}
+
+function intersects$1(a, b) {
+    return b.minX <= a.maxX &&
+           b.minY <= a.maxY &&
+           b.maxX >= a.minX &&
+           b.maxY >= a.minY;
+}
+
+function createNode(children) {
+    return {
+        children,
+        height: 1,
+        leaf: true,
+        minX: Infinity,
+        minY: Infinity,
+        maxX: -Infinity,
+        maxY: -Infinity
+    };
+}
+
+// sort an array so that items come in groups of n unsorted items, with groups sorted between each other;
+// combines selection algorithm with binary divide & conquer approach
+
+function multiSelect(arr, left, right, n, compare) {
+    const stack = [left, right];
+
+    while (stack.length) {
+        right = stack.pop();
+        left = stack.pop();
+
+        if (right - left <= n) continue;
+
+        const mid = left + Math.ceil((right - left) / n / 2) * n;
+        quickselect(arr, mid, left, right, compare);
+
+        stack.push(left, mid, mid, right);
+    }
+}
+
+class GeoRBush extends RBush {
+    compareMinX(a, b) {
+        return a.lng - b.lng;
+    }
+    compareMinY(a, b) {
+        return a.lat - b.lat;
+    }
+    toBBox(item) {
+        return {
+            minX: item.lng,
+            minY: item.lat,
+            maxX: item.lng,
+            maxY: item.lat,
+        };
+    }
+}
+
+/*
+ * Copyright (C) 2008 Apple Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Ported from Webkit
+ * http://svn.webkit.org/repository/webkit/trunk/Source/WebCore/platform/graphics/UnitBezier.h
+ */
+var unitbezier = UnitBezier;
+
+function UnitBezier(p1x, p1y, p2x, p2y) {
+    // Calculate the polynomial coefficients, implicit first and last control points are (0,0) and (1,1).
+    this.cx = 3.0 * p1x;
+    this.bx = 3.0 * (p2x - p1x) - this.cx;
+    this.ax = 1.0 - this.cx - this.bx;
+
+    this.cy = 3.0 * p1y;
+    this.by = 3.0 * (p2y - p1y) - this.cy;
+    this.ay = 1.0 - this.cy - this.by;
+
+    this.p1x = p1x;
+    this.p1y = p2y;
+    this.p2x = p2x;
+    this.p2y = p2y;
+}
+
+UnitBezier.prototype.sampleCurveX = function(t) {
+    // `ax t^3 + bx t^2 + cx t' expanded using Horner's rule.
+    return ((this.ax * t + this.bx) * t + this.cx) * t;
+};
+
+UnitBezier.prototype.sampleCurveY = function(t) {
+    return ((this.ay * t + this.by) * t + this.cy) * t;
+};
+
+UnitBezier.prototype.sampleCurveDerivativeX = function(t) {
+    return (3.0 * this.ax * t + 2.0 * this.bx) * t + this.cx;
+};
+
+UnitBezier.prototype.solveCurveX = function(x, epsilon) {
+    if (typeof epsilon === 'undefined') epsilon = 1e-6;
+
+    var t0, t1, t2, x2, i;
+
+    // First try a few iterations of Newton's method -- normally very fast.
+    for (t2 = x, i = 0; i < 8; i++) {
+
+        x2 = this.sampleCurveX(t2) - x;
+        if (Math.abs(x2) < epsilon) return t2;
+
+        var d2 = this.sampleCurveDerivativeX(t2);
+        if (Math.abs(d2) < 1e-6) break;
+
+        t2 = t2 - x2 / d2;
+    }
+
+    // Fall back to the bisection method for reliability.
+    t0 = 0.0;
+    t1 = 1.0;
+    t2 = x;
+
+    if (t2 < t0) return t0;
+    if (t2 > t1) return t1;
+
+    while (t0 < t1) {
+
+        x2 = this.sampleCurveX(t2);
+        if (Math.abs(x2 - x) < epsilon) return t2;
+
+        if (x > x2) {
+            t0 = t2;
+        } else {
+            t1 = t2;
+        }
+
+        t2 = (t1 - t0) * 0.5 + t0;
+    }
+
+    // Failure.
+    return t2;
+};
+
+UnitBezier.prototype.solve = function(x, epsilon) {
+    return this.sampleCurveY(this.solveCurveX(x, epsilon));
+};
+
+/**
+ * Enumeration for transition mode
+ * @enum {number}
+ * @readonly
+ * @description Modes for specifying how transitions
+ * between images are performed.
+ */
+var TransitionMode;
+(function (TransitionMode) {
+    /**
+     * Default transitions.
+     *
+     * @description The viewer dynamically determines
+     * whether transitions should be performed with or
+     * without motion and blending for each transition
+     * based on the underlying data.
+     */
+    TransitionMode[TransitionMode["Default"] = 0] = "Default";
+    /**
+     * Instantaneous transitions.
+     *
+     * @description All transitions are performed
+     * without motion or blending.
+     */
+    TransitionMode[TransitionMode["Instantaneous"] = 1] = "Instantaneous";
+})(TransitionMode || (TransitionMode = {}));
+
+/**
+ * @class Camera
+ *
+ * @classdesc Holds information about a camera.
+ */
+class Camera {
+    /**
+     * Create a new camera instance.
+     * @param {Transform} [transform] - Optional transform instance.
+     */
+    constructor(transform) {
+        if (transform != null) {
+            this._position = new Vector3().fromArray(transform.unprojectSfM([0, 0], 0));
+            this._lookat = new Vector3().fromArray(transform.unprojectSfM([0, 0], 10));
+            this._up = transform.upVector();
+            this._focal = this._getFocal(transform);
+        }
+        else {
+            this._position = new Vector3(0, 0, 0);
+            this._lookat = new Vector3(1, 0, 0);
+            this._up = new Vector3(0, 0, 1);
+            this._focal = 1;
+        }
+    }
+    /**
+     * Get position.
+     * @returns {THREE.Vector3} The position vector.
+     */
+    get position() {
+        return this._position;
+    }
+    /**
+     * Get lookat.
+     * @returns {THREE.Vector3} The lookat vector.
+     */
+    get lookat() {
+        return this._lookat;
+    }
+    /**
+     * Get up.
+     * @returns {THREE.Vector3} The up vector.
+     */
+    get up() {
+        return this._up;
+    }
+    /**
+     * Get focal.
+     * @returns {number} The focal length.
+     */
+    get focal() {
+        return this._focal;
+    }
+    /**
+     * Set focal.
+     */
+    set focal(value) {
+        this._focal = value;
+    }
+    /**
+     * Update this camera to the linearly interpolated value of two other cameras.
+     *
+     * @param {Camera} a - First camera.
+     * @param {Camera} b - Second camera.
+     * @param {number} alpha - Interpolation value on the interval [0, 1].
+     */
+    lerpCameras(a, b, alpha) {
+        this._position.subVectors(b.position, a.position).multiplyScalar(alpha).add(a.position);
+        this._lookat.subVectors(b.lookat, a.lookat).multiplyScalar(alpha).add(a.lookat);
+        this._up.subVectors(b.up, a.up).multiplyScalar(alpha).add(a.up);
+        this._focal = (1 - alpha) * a.focal + alpha * b.focal;
+    }
+    /**
+     * Copy the properties of another camera to this camera.
+     *
+     * @param {Camera} other - Another camera.
+     */
+    copy(other) {
+        this._position.copy(other.position);
+        this._lookat.copy(other.lookat);
+        this._up.copy(other.up);
+        this._focal = other.focal;
+    }
+    /**
+     * Clone this camera.
+     *
+     * @returns {Camera} A camera with cloned properties equal to this camera.
+     */
+    clone() {
+        let camera = new Camera();
+        camera.position.copy(this._position);
+        camera.lookat.copy(this._lookat);
+        camera.up.copy(this._up);
+        camera.focal = this._focal;
+        return camera;
+    }
+    /**
+     * Determine the distance between this camera and another camera.
+     *
+     * @param {Camera} other - Another camera.
+     * @returns {number} The distance between the cameras.
+     */
+    diff(other) {
+        let pd = this._position.distanceToSquared(other.position);
+        let ld = this._lookat.distanceToSquared(other.lookat);
+        let ud = this._up.distanceToSquared(other.up);
+        let fd = 100 * Math.abs(this._focal - other.focal);
+        return Math.max(pd, ld, ud, fd);
+    }
+    /**
+     * Get the focal length based on the transform.
+     *
+     * @description Returns the focal length corresponding
+     * to a 90 degree field of view for spherical
+     * transforms.
+     *
+     * Returns the transform focal length for other
+     * projection types.
+     *
+     * @returns {number} Focal length.
+     */
+    _getFocal(transform) {
+        if (!isSpherical(transform.cameraType)) {
+            return transform.focal;
+        }
+        return 0.5 / Math.tan(Math.PI / 2);
+    }
+}
+
+const EPSILON = 1e-8;
+/**
+ * @class Transform
+ *
+ * @classdesc Class used for calculating coordinate transformations
+ * and projections.
+ */
+class Transform {
+    /**
+     * Create a new transform instance.
+     * @param {number} orientation - Image orientation.
+     * @param {number} width - Image height.
+     * @param {number} height - Image width.
+     * @param {number} focal - Focal length.
+     * @param {number} scale - Atomic scale.
+     * @param {Array<number>} rotation - Rotation vector in three dimensions.
+     * @param {Array<number>} translation - Translation vector in three dimensions.
+     * @param {HTMLImageElement} image - Image for fallback size calculations.
+     */
+    constructor(orientation, width, height, scale, rotation, translation, image, textureScale, cameraParameters, cameraType) {
+        this._orientation = this._getValue(orientation, 1);
+        let imageWidth = image != null ? image.width : 4;
+        let imageHeight = image != null ? image.height : 3;
+        let keepOrientation = this._orientation < 5;
+        this._width = this._getValue(width, keepOrientation ? imageWidth : imageHeight);
+        this._height = this._getValue(height, keepOrientation ? imageHeight : imageWidth);
+        this._basicAspect = keepOrientation ?
+            this._width / this._height :
+            this._height / this._width;
+        this._basicWidth = keepOrientation ? width : height;
+        this._basicHeight = keepOrientation ? height : width;
+        const parameters = this._getCameraParameters(cameraParameters, cameraType);
+        const focal = parameters[0];
+        const ck1 = parameters[1];
+        const ck2 = parameters[2];
+        this._focal = this._getValue(focal, 1);
+        this._scale = this._getValue(scale, 0);
+        this._worldToCamera = this.createWorldToCamera(rotation, translation);
+        this._worldToCameraInverse = new Matrix4()
+            .copy(this._worldToCamera)
+            .invert();
+        this._scaledWorldToCamera =
+            this._createScaledWorldToCamera(this._worldToCamera, this._scale);
+        this._scaledWorldToCameraInverse = new Matrix4()
+            .copy(this._scaledWorldToCamera)
+            .invert();
+        this._basicWorldToCamera = this._createBasicWorldToCamera(this._worldToCamera, orientation);
+        this._textureScale = !!textureScale ? textureScale : [1, 1];
+        this._ck1 = !!ck1 ? ck1 : 0;
+        this._ck2 = !!ck2 ? ck2 : 0;
+        this._cameraType = !!cameraType ?
+            cameraType :
+            "perspective";
+        this._radialPeak = this._getRadialPeak(this._ck1, this._ck2);
+    }
+    get ck1() {
+        return this._ck1;
+    }
+    get ck2() {
+        return this._ck2;
+    }
+    get cameraType() {
+        return this._cameraType;
+    }
+    /**
+     * Get basic aspect.
+     * @returns {number} The orientation adjusted aspect ratio.
+     */
+    get basicAspect() {
+        return this._basicAspect;
+    }
+    /**
+     * Get basic height.
+     *
+     * @description Does not fall back to image image height but
+     * uses original value from API so can be faulty.
+     *
+     * @returns {number} The height of the basic version image
+     * (adjusted for orientation).
+     */
+    get basicHeight() {
+        return this._basicHeight;
+    }
+    get basicRt() {
+        return this._basicWorldToCamera;
+    }
+    /**
+     * Get basic width.
+     *
+     * @description Does not fall back to image image width but
+     * uses original value from API so can be faulty.
+     *
+     * @returns {number} The width of the basic version image
+     * (adjusted for orientation).
+     */
+    get basicWidth() {
+        return this._basicWidth;
+    }
+    /**
+     * Get focal.
+     * @returns {number} The image focal length.
+     */
+    get focal() {
+        return this._focal;
+    }
+    /**
+     * Get height.
+     *
+     * @description Falls back to the image image height if
+     * the API data is faulty.
+     *
+     * @returns {number} The orientation adjusted image height.
+     */
+    get height() {
+        return this._height;
+    }
+    /**
+     * Get orientation.
+     * @returns {number} The image orientation.
+     */
+    get orientation() {
+        return this._orientation;
+    }
+    /**
+     * Get rt.
+     * @returns {THREE.Matrix4} The extrinsic camera matrix.
+     */
+    get rt() {
+        return this._worldToCamera;
+    }
+    /**
+     * Get srt.
+     * @returns {THREE.Matrix4} The scaled extrinsic camera matrix.
+     */
+    get srt() {
+        return this._scaledWorldToCamera;
+    }
+    /**
+     * Get srtInverse.
+     * @returns {THREE.Matrix4} The scaled extrinsic camera matrix.
+     */
+    get srtInverse() {
+        return this._scaledWorldToCameraInverse;
+    }
+    /**
+     * Get scale.
+     * @returns {number} The image atomic reconstruction scale.
+     */
+    get scale() {
+        return this._scale;
+    }
+    /**
+     * Get has valid scale.
+     * @returns {boolean} Value indicating if the scale of the transform is valid.
+     */
+    get hasValidScale() {
+        return this._scale > 1e-2 && this._scale < 50;
+    }
+    /**
+     * Get radial peak.
+     * @returns {number} Value indicating the radius where the radial
+     * undistortion function peaks.
+     */
+    get radialPeak() {
+        return this._radialPeak;
+    }
+    /**
+     * Get width.
+     *
+     * @description Falls back to the image image width if
+     * the API data is faulty.
+     *
+     * @returns {number} The orientation adjusted image width.
+     */
+    get width() {
+        return this._width;
+    }
+    /**
+     * Calculate the up vector for the image transform.
+     *
+     * @returns {THREE.Vector3} Normalized and orientation adjusted up vector.
+     */
+    upVector() {
+        let rte = this._worldToCamera.elements;
+        switch (this._orientation) {
+            case 1:
+                return new Vector3(-rte[1], -rte[5], -rte[9]);
+            case 3:
+                return new Vector3(rte[1], rte[5], rte[9]);
+            case 6:
+                return new Vector3(-rte[0], -rte[4], -rte[8]);
+            case 8:
+                return new Vector3(rte[0], rte[4], rte[8]);
+            default:
+                return new Vector3(-rte[1], -rte[5], -rte[9]);
+        }
+    }
+    /**
+     * Calculate projector matrix for projecting 3D points to texture map
+     * coordinates (u and v).
+     *
+     * @returns {THREE.Matrix4} Projection matrix for 3D point to texture
+     * map coordinate calculations.
+     */
+    projectorMatrix() {
+        let projector = this._normalizedToTextureMatrix();
+        let f = this._focal;
+        let projection = new Matrix4().set(f, 0, 0, 0, 0, f, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);
+        projector.multiply(projection);
+        projector.multiply(this._worldToCamera);
+        return projector;
+    }
+    /**
+     * Project 3D world coordinates to basic coordinates.
+     *
+     * @param {Array<number>} point3d - 3D world coordinates.
+     * @return {Array<number>} 2D basic coordinates.
+     */
+    projectBasic(point3d) {
+        let sfm = this.projectSfM(point3d);
+        return this._sfmToBasic(sfm);
+    }
+    /**
+     * Unproject basic coordinates to 3D world coordinates.
+     *
+     * @param {Array<number>} basic - 2D basic coordinates.
+     * @param {Array<number>} distance - Distance to unproject from camera center.
+     * @param {boolean} [depth] - Treat the distance value as depth from camera center.
+     *                            Only applicable for perspective images. Will be
+     *                            ignored for spherical.
+     * @returns {Array<number>} Unprojected 3D world coordinates.
+     */
+    unprojectBasic(basic, distance, depth) {
+        let sfm = this._basicToSfm(basic);
+        return this.unprojectSfM(sfm, distance, depth);
+    }
+    /**
+     * Project 3D world coordinates to SfM coordinates.
+     *
+     * @param {Array<number>} point3d - 3D world coordinates.
+     * @return {Array<number>} 2D SfM coordinates.
+     */
+    projectSfM(point3d) {
+        let v = new Vector4(point3d[0], point3d[1], point3d[2], 1);
+        v.applyMatrix4(this._worldToCamera);
+        return this._bearingToSfm([v.x, v.y, v.z]);
+    }
+    /**
+     * Unproject SfM coordinates to a 3D world coordinates.
+     *
+     * @param {Array<number>} sfm - 2D SfM coordinates.
+     * @param {Array<number>} distance - Distance to unproject
+     * from camera center.
+     * @param {boolean} [depth] - Treat the distance value as
+     * depth from camera center. Only applicable for perspective
+     * images. Will be ignored for spherical.
+     * @returns {Array<number>} Unprojected 3D world coordinates.
+     */
+    unprojectSfM(sfm, distance, depth) {
+        const bearing = this._sfmToBearing(sfm);
+        const unprojectedCamera = depth && !isSpherical(this._cameraType) ?
+            new Vector4(distance * bearing[0] / bearing[2], distance * bearing[1] / bearing[2], distance, 1) :
+            new Vector4(distance * bearing[0], distance * bearing[1], distance * bearing[2], 1);
+        const unprojectedWorld = unprojectedCamera
+            .applyMatrix4(this._worldToCameraInverse);
+        return [
+            unprojectedWorld.x / unprojectedWorld.w,
+            unprojectedWorld.y / unprojectedWorld.w,
+            unprojectedWorld.z / unprojectedWorld.w,
+        ];
+    }
+    /**
+     * Transform SfM coordinates to bearing vector (3D cartesian
+     * coordinates on the unit sphere).
+     *
+     * @param {Array<number>} sfm - 2D SfM coordinates.
+     * @returns {Array<number>} Bearing vector (3D cartesian coordinates
+     * on the unit sphere).
+     */
+    _sfmToBearing(sfm) {
+        if (isSpherical(this._cameraType)) {
+            let lng = sfm[0] * 2 * Math.PI;
+            let lat = -sfm[1] * 2 * Math.PI;
+            let x = Math.cos(lat) * Math.sin(lng);
+            let y = -Math.sin(lat);
+            let z = Math.cos(lat) * Math.cos(lng);
+            return [x, y, z];
+        }
+        else if (isFisheye(this._cameraType)) {
+            let [dxn, dyn] = [sfm[0] / this._focal, sfm[1] / this._focal];
+            const dTheta = Math.sqrt(dxn * dxn + dyn * dyn);
+            let d = this._distortionFromDistortedRadius(dTheta, this._ck1, this._ck2, this._radialPeak);
+            let theta = dTheta / d;
+            let z = Math.cos(theta);
+            let r = Math.sin(theta);
+            const denomTheta = dTheta > EPSILON ? 1 / dTheta : 1;
+            let x = r * dxn * denomTheta;
+            let y = r * dyn * denomTheta;
+            return [x, y, z];
+        }
+        else {
+            let [dxn, dyn] = [sfm[0] / this._focal, sfm[1] / this._focal];
+            const dr = Math.sqrt(dxn * dxn + dyn * dyn);
+            let d = this._distortionFromDistortedRadius(dr, this._ck1, this._ck2, this._radialPeak);
+            const xn = dxn / d;
+            const yn = dyn / d;
+            let v = new Vector3(xn, yn, 1);
+            v.normalize();
+            return [v.x, v.y, v.z];
+        }
+    }
+    /** Compute distortion given the distorted radius.
+     *
+     *  Solves for d in the equation
+     *    y = d(x, k1, k2) * x
+     * given the distorted radius, y.
+     */
+    _distortionFromDistortedRadius(distortedRadius, k1, k2, radialPeak) {
+        let d = 1.0;
+        for (let i = 0; i < 10; i++) {
+            let radius = distortedRadius / d;
+            if (radius > radialPeak) {
+                radius = radialPeak;
+            }
+            d = 1 + k1 * Math.pow(radius, 2) + k2 * Math.pow(radius, 4);
+        }
+        return d;
+    }
+    /**
+     * Transform bearing vector (3D cartesian coordiantes on the unit sphere) to
+     * SfM coordinates.
+     *
+     * @param {Array<number>} bearing - Bearing vector (3D cartesian coordinates on the
+     * unit sphere).
+     * @returns {Array<number>} 2D SfM coordinates.
+     */
+    _bearingToSfm(bearing) {
+        if (isSpherical(this._cameraType)) {
+            let x = bearing[0];
+            let y = bearing[1];
+            let z = bearing[2];
+            let lng = Math.atan2(x, z);
+            let lat = Math.atan2(-y, Math.sqrt(x * x + z * z));
+            return [lng / (2 * Math.PI), -lat / (2 * Math.PI)];
+        }
+        else if (isFisheye(this._cameraType)) {
+            if (bearing[2] > 0) {
+                const [x, y, z] = bearing;
+                const r = Math.sqrt(x * x + y * y);
+                let theta = Math.atan2(r, z);
+                if (theta > this._radialPeak) {
+                    theta = this._radialPeak;
+                }
+                const distortion = 1.0 + Math.pow(theta, 2) * (this._ck1 + Math.pow(theta, 2) * this._ck2);
+                const s = this._focal * distortion * theta / r;
+                return [s * x, s * y];
+            }
+            else {
+                return [
+                    bearing[0] < 0 ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY,
+                    bearing[1] < 0 ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY,
+                ];
+            }
+        }
+        else {
+            if (bearing[2] > 0) {
+                let [xn, yn] = [bearing[0] / bearing[2], bearing[1] / bearing[2]];
+                let r2 = xn * xn + yn * yn;
+                const rp2 = Math.pow(this._radialPeak, 2);
+                if (r2 > rp2) {
+                    r2 = rp2;
+                }
+                const d = 1 + this._ck1 * r2 + this._ck2 * Math.pow(r2, 2);
+                return [
+                    this._focal * d * xn,
+                    this._focal * d * yn,
+                ];
+            }
+            else {
+                return [
+                    bearing[0] < 0 ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY,
+                    bearing[1] < 0 ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY,
+                ];
+            }
+        }
+    }
+    /**
+     * Convert basic coordinates to SfM coordinates.
+     *
+     * @param {Array<number>} basic - 2D basic coordinates.
+     * @returns {Array<number>} 2D SfM coordinates.
+     */
+    _basicToSfm(basic) {
+        let rotatedX;
+        let rotatedY;
+        switch (this._orientation) {
+            case 1:
+                rotatedX = basic[0];
+                rotatedY = basic[1];
+                break;
+            case 3:
+                rotatedX = 1 - basic[0];
+                rotatedY = 1 - basic[1];
+                break;
+            case 6:
+                rotatedX = basic[1];
+                rotatedY = 1 - basic[0];
+                break;
+            case 8:
+                rotatedX = 1 - basic[1];
+                rotatedY = basic[0];
+                break;
+            default:
+                rotatedX = basic[0];
+                rotatedY = basic[1];
+                break;
+        }
+        let w = this._width;
+        let h = this._height;
+        let s = Math.max(w, h);
+        let sfmX = rotatedX * w / s - w / s / 2;
+        let sfmY = rotatedY * h / s - h / s / 2;
+        return [sfmX, sfmY];
+    }
+    /**
+     * Convert SfM coordinates to basic coordinates.
+     *
+     * @param {Array<number>} sfm - 2D SfM coordinates.
+     * @returns {Array<number>} 2D basic coordinates.
+     */
+    _sfmToBasic(sfm) {
+        let w = this._width;
+        let h = this._height;
+        let s = Math.max(w, h);
+        let rotatedX = (sfm[0] + w / s / 2) / w * s;
+        let rotatedY = (sfm[1] + h / s / 2) / h * s;
+        let basicX;
+        let basicY;
+        switch (this._orientation) {
+            case 1:
+                basicX = rotatedX;
+                basicY = rotatedY;
+                break;
+            case 3:
+                basicX = 1 - rotatedX;
+                basicY = 1 - rotatedY;
+                break;
+            case 6:
+                basicX = 1 - rotatedY;
+                basicY = rotatedX;
+                break;
+            case 8:
+                basicX = rotatedY;
+                basicY = 1 - rotatedX;
+                break;
+            default:
+                basicX = rotatedX;
+                basicY = rotatedY;
+                break;
+        }
+        return [basicX, basicY];
+    }
+    /**
+     * Checks a value and returns it if it exists and is larger than 0.
+     * Fallbacks if it is null.
+     *
+     * @param {number} value - Value to check.
+     * @param {number} fallback - Value to fall back to.
+     * @returns {number} The value or its fallback value if it is not defined or negative.
+     */
+    _getValue(value, fallback) {
+        return value != null && value > 0 ? value : fallback;
+    }
+    _getCameraParameters(value, cameraType) {
+        if (isSpherical(cameraType)) {
+            return [];
+        }
+        if (!value || value.length === 0) {
+            return [1, 0, 0];
+        }
+        const padding = 3 - value.length;
+        if (padding <= 0) {
+            return value;
+        }
+        return value
+            .concat(new Array(padding)
+            .fill(0));
+    }
+    /**
+     * Creates the extrinsic camera matrix [ R | t ].
+     *
+     * @param {Array<number>} rotation - Rotation vector in angle axis representation.
+     * @param {Array<number>} translation - Translation vector.
+     * @returns {THREE.Matrix4} Extrisic camera matrix.
+     */
+    createWorldToCamera(rotation, translation) {
+        const axis = new Vector3(rotation[0], rotation[1], rotation[2]);
+        const angle = axis.length();
+        if (angle > 0) {
+            axis.normalize();
+        }
+        const worldToCamera = new Matrix4();
+        worldToCamera.makeRotationAxis(axis, angle);
+        worldToCamera.setPosition(new Vector3(translation[0], translation[1], translation[2]));
+        return worldToCamera;
+    }
+    /**
+     * Calculates the scaled extrinsic camera matrix scale * [ R | t ].
+     *
+     * @param {THREE.Matrix4} worldToCamera - Extrisic camera matrix.
+     * @param {number} scale - Scale factor.
+     * @returns {THREE.Matrix4} Scaled extrisic camera matrix.
+     */
+    _createScaledWorldToCamera(worldToCamera, scale) {
+        const scaledWorldToCamera = worldToCamera.clone();
+        const elements = scaledWorldToCamera.elements;
+        elements[12] = scale * elements[12];
+        elements[13] = scale * elements[13];
+        elements[14] = scale * elements[14];
+        scaledWorldToCamera.scale(new Vector3(scale, scale, scale));
+        return scaledWorldToCamera;
+    }
+    _createBasicWorldToCamera(rt, orientation) {
+        const axis = new Vector3(0, 0, 1);
+        let angle = 0;
+        switch (orientation) {
+            case 3:
+                angle = Math.PI;
+                break;
+            case 6:
+                angle = Math.PI / 2;
+                break;
+            case 8:
+                angle = 3 * Math.PI / 2;
+                break;
+        }
+        return new Matrix4()
+            .makeRotationAxis(axis, angle)
+            .multiply(rt);
+    }
+    _getRadialPeak(k1, k2) {
+        const a = 5 * k2;
+        const b = 3 * k1;
+        const c = 1;
+        const d = Math.pow(b, 2) - 4 * a * c;
+        if (d < 0) {
+            return undefined;
+        }
+        const root1 = (-b - Math.sqrt(d)) / 2 / a;
+        const root2 = (-b + Math.sqrt(d)) / 2 / a;
+        const minRoot = Math.min(root1, root2);
+        const maxRoot = Math.max(root1, root2);
+        return minRoot > 0 ?
+            Math.sqrt(minRoot) :
+            maxRoot > 0 ?
+                Math.sqrt(maxRoot) :
+                undefined;
+    }
+    /**
+     * Calculate a transformation matrix from normalized coordinates for
+     * texture map coordinates.
+     *
+     * @returns {THREE.Matrix4} Normalized coordinates to texture map
+     * coordinates transformation matrix.
+     */
+    _normalizedToTextureMatrix() {
+        const size = Math.max(this._width, this._height);
+        const scaleX = this._orientation < 5 ? this._textureScale[0] : this._textureScale[1];
+        const scaleY = this._orientation < 5 ? this._textureScale[1] : this._textureScale[0];
+        const w = size / this._width * scaleX;
+        const h = size / this._height * scaleY;
+        switch (this._orientation) {
+            case 1:
+                return new Matrix4().set(w, 0, 0, 0.5, 0, -h, 0, 0.5, 0, 0, 1, 0, 0, 0, 0, 1);
+            case 3:
+                return new Matrix4().set(-w, 0, 0, 0.5, 0, h, 0, 0.5, 0, 0, 1, 0, 0, 0, 0, 1);
+            case 6:
+                return new Matrix4().set(0, -h, 0, 0.5, -w, 0, 0, 0.5, 0, 0, 1, 0, 0, 0, 0, 1);
+            case 8:
+                return new Matrix4().set(0, h, 0, 0.5, w, 0, 0, 0.5, 0, 0, 1, 0, 0, 0, 0, 1);
+            default:
+                return new Matrix4().set(w, 0, 0, 0.5, 0, -h, 0, 0.5, 0, 0, 1, 0, 0, 0, 0, 1);
+        }
+    }
+}
+
+class StateBase {
+    constructor(state) {
+        this._spatial = new Spatial();
+        this._referenceThreshold = 0.01;
+        this._transitionMode = state.transitionMode;
+        this._reference = state.reference;
+        this._alpha = state.alpha;
+        this._camera = state.camera.clone();
+        this._zoom = state.zoom;
+        this._currentIndex = state.currentIndex;
+        this._trajectory = state.trajectory.slice();
+        this._trajectoryTransforms = [];
+        this._trajectoryCameras = [];
+        for (let image of this._trajectory) {
+            let translation = this._imageToTranslation(image, this._reference);
+            let transform = new Transform(image.exifOrientation, image.width, image.height, image.scale, image.rotation, translation, image.image, undefined, image.cameraParameters, image.cameraType);
+            this._trajectoryTransforms.push(transform);
+            this._trajectoryCameras.push(new Camera(transform));
+        }
+        this._currentImage = this._trajectory.length > 0 ?
+            this._trajectory[this._currentIndex] :
+            null;
+        this._previousImage = this._trajectory.length > 1 && this.currentIndex > 0 ?
+            this._trajectory[this._currentIndex - 1] :
+            null;
+        this._currentCamera = this._trajectoryCameras.length > 0 ?
+            this._trajectoryCameras[this._currentIndex].clone() :
+            new Camera();
+        this._previousCamera = this._trajectoryCameras.length > 1 && this.currentIndex > 0 ?
+            this._trajectoryCameras[this._currentIndex - 1].clone() :
+            this._currentCamera.clone();
+    }
+    get reference() {
+        return this._reference;
+    }
+    get alpha() {
+        return this._getAlpha();
+    }
+    get camera() {
+        return this._camera;
+    }
+    get zoom() {
+        return this._zoom;
+    }
+    get trajectory() {
+        return this._trajectory;
+    }
+    get currentIndex() {
+        return this._currentIndex;
+    }
+    get currentImage() {
+        return this._currentImage;
+    }
+    get previousImage() {
+        return this._previousImage;
+    }
+    get currentCamera() {
+        return this._currentCamera;
+    }
+    get currentTransform() {
+        return this._trajectoryTransforms.length > 0 ?
+            this._trajectoryTransforms[this.currentIndex] : null;
+    }
+    get previousTransform() {
+        return this._trajectoryTransforms.length > 1 && this.currentIndex > 0 ?
+            this._trajectoryTransforms[this.currentIndex - 1] : null;
+    }
+    get motionless() {
+        return this._motionless;
+    }
+    get transitionMode() {
+        return this._transitionMode;
+    }
+    move(delta) { }
+    moveTo(position) { }
+    rotate(delta) { }
+    rotateUnbounded(delta) { }
+    rotateWithoutInertia(delta) { }
+    rotateBasic(basicRotation) { }
+    rotateBasicUnbounded(basicRotation) { }
+    rotateBasicWithoutInertia(basicRotation) { }
+    rotateToBasic(basic) { }
+    setSpeed(speed) { }
+    zoomIn(delta, reference) { }
+    update(fps) { }
+    setCenter(center) { }
+    setZoom(zoom) { }
+    dolly(delta) { }
+    orbit(rotation) { }
+    setViewMatrix(matrix) { }
+    truck(direction) { }
+    append(images) {
+        if (images.length < 1) {
+            throw Error("Trajectory can not be empty");
+        }
+        if (this._currentIndex < 0) {
+            this.set(images);
+        }
+        else {
+            this._trajectory = this._trajectory.concat(images);
+            this._appendToTrajectories(images);
+        }
+    }
+    prepend(images) {
+        if (images.length < 1) {
+            throw Error("Trajectory can not be empty");
+        }
+        this._trajectory = images.slice().concat(this._trajectory);
+        this._currentIndex += images.length;
+        this._setCurrentImage();
+        let referenceReset = this._setReference(this._currentImage);
+        if (referenceReset) {
+            this._setTrajectories();
+        }
+        else {
+            this._prependToTrajectories(images);
+        }
+        this._setCurrentCamera();
+    }
+    remove(n) {
+        if (n < 0) {
+            throw Error("n must be a positive integer");
+        }
+        if (this._currentIndex - 1 < n) {
+            throw Error("Current and previous images can not be removed");
+        }
+        for (let i = 0; i < n; i++) {
+            this._trajectory.shift();
+            this._trajectoryTransforms.shift();
+            this._trajectoryCameras.shift();
+            this._currentIndex--;
+        }
+        this._setCurrentImage();
+    }
+    clearPrior() {
+        if (this._currentIndex > 0) {
+            this.remove(this._currentIndex - 1);
+        }
+    }
+    clear() {
+        this.cut();
+        if (this._currentIndex > 0) {
+            this.remove(this._currentIndex - 1);
+        }
+    }
+    cut() {
+        while (this._trajectory.length - 1 > this._currentIndex) {
+            this._trajectory.pop();
+            this._trajectoryTransforms.pop();
+            this._trajectoryCameras.pop();
+        }
+    }
+    set(images) {
+        this._setTrajectory(images);
+        this._setCurrentImage();
+        this._setReference(this._currentImage);
+        this._setTrajectories();
+        this._setCurrentCamera();
+    }
+    getCenter() {
+        return this._currentImage != null ?
+            this.currentTransform.projectBasic(this._camera.lookat.toArray()) :
+            [0.5, 0.5];
+    }
+    setTransitionMode(mode) {
+        this._transitionMode = mode;
+    }
+    _getAlpha() { return 1; }
+    _setCurrent() {
+        this._setCurrentImage();
+        let referenceReset = this._setReference(this._currentImage);
+        if (referenceReset) {
+            this._setTrajectories();
+        }
+        this._setCurrentCamera();
+    }
+    _setCurrentCamera() {
+        this._currentCamera = this._trajectoryCameras[this._currentIndex].clone();
+        this._previousCamera = this._currentIndex > 0 ?
+            this._trajectoryCameras[this._currentIndex - 1].clone() :
+            this._currentCamera.clone();
+    }
+    _motionlessTransition() {
+        let imagesSet = this._currentImage != null && this._previousImage != null;
+        return imagesSet && (this._transitionMode === TransitionMode.Instantaneous || !(this._currentImage.merged &&
+            this._previousImage.merged &&
+            this._withinOriginalDistance() &&
+            this._sameConnectedComponent()));
+    }
+    _setReference(image) {
+        // do not reset reference if image is within threshold distance
+        if (Math.abs(image.lngLat.lat - this.reference.lat) < this._referenceThreshold &&
+            Math.abs(image.lngLat.lng - this.reference.lng) < this._referenceThreshold) {
+            return false;
+        }
+        // do not reset reference if previous image exist and transition is with motion
+        if (this._previousImage != null && !this._motionlessTransition()) {
+            return false;
+        }
+        this._reference.lat = image.lngLat.lat;
+        this._reference.lng = image.lngLat.lng;
+        this._reference.alt = image.computedAltitude;
+        return true;
+    }
+    _setCurrentImage() {
+        this._currentImage = this._trajectory.length > 0 ?
+            this._trajectory[this._currentIndex] :
+            null;
+        this._previousImage = this._currentIndex > 0 ?
+            this._trajectory[this._currentIndex - 1] :
+            null;
+    }
+    _setTrajectory(images) {
+        if (images.length < 1) {
+            throw new ArgumentMapillaryError("Trajectory can not be empty");
+        }
+        if (this._currentImage != null) {
+            this._trajectory = [this._currentImage].concat(images);
+            this._currentIndex = 1;
+        }
+        else {
+            this._trajectory = images.slice();
+            this._currentIndex = 0;
+        }
+    }
+    _setTrajectories() {
+        this._trajectoryTransforms.length = 0;
+        this._trajectoryCameras.length = 0;
+        this._appendToTrajectories(this._trajectory);
+    }
+    _appendToTrajectories(images) {
+        for (let image of images) {
+            if (!image.assetsCached) {
+                throw new ArgumentMapillaryError("Assets must be cached when image is added to trajectory");
+            }
+            let translation = this._imageToTranslation(image, this.reference);
+            let transform = new Transform(image.exifOrientation, image.width, image.height, image.scale, image.rotation, translation, image.image, undefined, image.cameraParameters, image.cameraType);
+            this._trajectoryTransforms.push(transform);
+            this._trajectoryCameras.push(new Camera(transform));
+        }
+    }
+    _prependToTrajectories(images) {
+        for (let image of images.reverse()) {
+            if (!image.assetsCached) {
+                throw new ArgumentMapillaryError("Assets must be cached when added to trajectory");
+            }
+            let translation = this._imageToTranslation(image, this.reference);
+            let transform = new Transform(image.exifOrientation, image.width, image.height, image.scale, image.rotation, translation, image.image, undefined, image.cameraParameters, image.cameraType);
+            this._trajectoryTransforms.unshift(transform);
+            this._trajectoryCameras.unshift(new Camera(transform));
+        }
+    }
+    _imageToTranslation(image, reference) {
+        return computeTranslation({ alt: image.computedAltitude, lat: image.lngLat.lat, lng: image.lngLat.lng }, image.rotation, reference);
+    }
+    _sameConnectedComponent() {
+        let current = this._currentImage;
+        let previous = this._previousImage;
+        return !!current && !!previous &&
+            current.mergeId === previous.mergeId;
+    }
+    _withinOriginalDistance() {
+        let current = this._currentImage;
+        let previous = this._previousImage;
+        if (!current || !previous) {
+            return true;
+        }
+        // 50 km/h moves 28m in 2s
+        let distance = this._spatial.distanceFromLngLat(current.originalLngLat.lng, current.originalLngLat.lat, previous.originalLngLat.lng, previous.originalLngLat.lat);
+        return distance < 25;
+    }
+}
+
+class EulerRotationDelta {
+    constructor(phi, theta) {
+        this._phi = phi;
+        this._theta = theta;
+    }
+    get phi() {
+        return this._phi;
+    }
+    set phi(value) {
+        this._phi = value;
+    }
+    get theta() {
+        return this._theta;
+    }
+    set theta(value) {
+        this._theta = value;
+    }
+    get isZero() {
+        return this._phi === 0 && this._theta === 0;
+    }
+    copy(delta) {
+        this._phi = delta.phi;
+        this._theta = delta.theta;
+    }
+    lerp(other, alpha) {
+        this._phi = (1 - alpha) * this._phi + alpha * other.phi;
+        this._theta = (1 - alpha) * this._theta + alpha * other.theta;
+    }
+    multiply(value) {
+        this._phi *= value;
+        this._theta *= value;
+    }
+    threshold(value) {
+        this._phi = Math.abs(this._phi) > value ? this._phi : 0;
+        this._theta = Math.abs(this._theta) > value ? this._theta : 0;
+    }
+    lengthSquared() {
+        return this._phi * this._phi + this._theta * this._theta;
+    }
+    reset() {
+        this._phi = 0;
+        this._theta = 0;
+    }
+}
+
+class InteractiveStateBase extends StateBase {
+    constructor(state) {
+        super(state);
+        this._animationSpeed = 1 / 40;
+        this._rotationDelta = new EulerRotationDelta(0, 0);
+        this._requestedRotationDelta = null;
+        this._basicRotation = [0, 0];
+        this._requestedBasicRotation = null;
+        this._requestedBasicRotationUnbounded = null;
+        this._rotationAcceleration = 0.86;
+        this._rotationIncreaseAlpha = 0.97;
+        this._rotationDecreaseAlpha = 0.9;
+        this._rotationThreshold = 1e-3;
+        this._unboundedRotationAlpha = 0.8;
+        this._desiredZoom = state.zoom;
+        this._minZoom = 0;
+        this._maxZoom = 3;
+        this._lookatDepth = 10;
+        this._desiredLookat = null;
+        this._desiredCenter = null;
+    }
+    rotate(rotationDelta) {
+        if (this._currentImage == null) {
+            return;
+        }
+        if (rotationDelta.phi === 0 && rotationDelta.theta === 0) {
+            return;
+        }
+        this._desiredZoom = this._zoom;
+        this._desiredLookat = null;
+        this._requestedBasicRotation = null;
+        if (this._requestedRotationDelta != null) {
+            this._requestedRotationDelta.phi = this._requestedRotationDelta.phi + rotationDelta.phi;
+            this._requestedRotationDelta.theta = this._requestedRotationDelta.theta + rotationDelta.theta;
+        }
+        else {
+            this._requestedRotationDelta = new EulerRotationDelta(rotationDelta.phi, rotationDelta.theta);
+        }
+    }
+    rotateUnbounded(delta) {
+        if (this._currentImage == null) {
+            return;
+        }
+        this._requestedBasicRotation = null;
+        this._requestedRotationDelta = null;
+        this._applyRotation(delta, this._currentCamera);
+        this._applyRotation(delta, this._previousCamera);
+        if (!this._desiredLookat) {
+            return;
+        }
+        const q = new Quaternion().setFromUnitVectors(this._currentCamera.up, new Vector3(0, 0, 1));
+        const qInverse = q.clone().invert();
+        const offset = new Vector3()
+            .copy(this._desiredLookat)
+            .sub(this._camera.position)
+            .applyQuaternion(q);
+        const length = offset.length();
+        let phi = Math.atan2(offset.y, offset.x);
+        phi += delta.phi;
+        let theta = Math.atan2(Math.sqrt(offset.x * offset.x + offset.y * offset.y), offset.z);
+        theta += delta.theta;
+        theta = Math.max(0.1, Math.min(Math.PI - 0.1, theta));
+        offset.x = Math.sin(theta) * Math.cos(phi);
+        offset.y = Math.sin(theta) * Math.sin(phi);
+        offset.z = Math.cos(theta);
+        offset.applyQuaternion(qInverse);
+        this._desiredLookat
+            .copy(this._camera.position)
+            .add(offset.multiplyScalar(length));
+    }
+    rotateWithoutInertia(rotationDelta) {
+        if (this._currentImage == null) {
+            return;
+        }
+        this._desiredZoom = this._zoom;
+        this._desiredLookat = null;
+        this._requestedBasicRotation = null;
+        this._requestedRotationDelta = null;
+        const threshold = Math.PI / (10 * Math.pow(2, this._zoom));
+        const delta = {
+            phi: this._spatial.clamp(rotationDelta.phi, -threshold, threshold),
+            theta: this._spatial.clamp(rotationDelta.theta, -threshold, threshold),
+        };
+        this._applyRotation(delta, this._currentCamera);
+        this._applyRotation(delta, this._previousCamera);
+    }
+    rotateBasic(basicRotation) {
+        if (this._currentImage == null) {
+            return;
+        }
+        this._desiredZoom = this._zoom;
+        this._desiredLookat = null;
+        this._requestedRotationDelta = null;
+        if (this._requestedBasicRotation != null) {
+            this._requestedBasicRotation[0] += basicRotation[0];
+            this._requestedBasicRotation[1] += basicRotation[1];
+            let threshold = 0.05 / Math.pow(2, this._zoom);
+            this._requestedBasicRotation[0] =
+                this._spatial.clamp(this._requestedBasicRotation[0], -threshold, threshold);
+            this._requestedBasicRotation[1] =
+                this._spatial.clamp(this._requestedBasicRotation[1], -threshold, threshold);
+        }
+        else {
+            this._requestedBasicRotation = basicRotation.slice();
+        }
+    }
+    rotateBasicUnbounded(basicRotation) {
+        if (this._currentImage == null) {
+            return;
+        }
+        if (this._requestedBasicRotationUnbounded != null) {
+            this._requestedBasicRotationUnbounded[0] += basicRotation[0];
+            this._requestedBasicRotationUnbounded[1] += basicRotation[1];
+        }
+        else {
+            this._requestedBasicRotationUnbounded = basicRotation.slice();
+        }
+    }
+    rotateBasicWithoutInertia(basic) {
+        if (this._currentImage == null) {
+            return;
+        }
+        this._desiredZoom = this._zoom;
+        this._desiredLookat = null;
+        this._requestedRotationDelta = null;
+        this._requestedBasicRotation = null;
+        const threshold = 0.05 / Math.pow(2, this._zoom);
+        const basicRotation = basic.slice();
+        basicRotation[0] = this._spatial.clamp(basicRotation[0], -threshold, threshold);
+        basicRotation[1] = this._spatial.clamp(basicRotation[1], -threshold, threshold);
+        this._applyRotationBasic(basicRotation);
+    }
+    rotateToBasic(basic) {
+        if (this._currentImage == null) {
+            return;
+        }
+        this._desiredZoom = this._zoom;
+        this._desiredLookat = null;
+        basic[0] = this._spatial.clamp(basic[0], 0, 1);
+        basic[1] = this._spatial.clamp(basic[1], 0, 1);
+        let lookat = this.currentTransform.unprojectBasic(basic, this._lookatDepth);
+        this._currentCamera.lookat.fromArray(lookat);
+    }
+    zoomIn(delta, reference) {
+        if (this._currentImage == null) {
+            return;
+        }
+        this._desiredZoom = Math.max(this._minZoom, Math.min(this._maxZoom, this._desiredZoom + delta));
+        let currentCenter = this.currentTransform.projectBasic(this._currentCamera.lookat.toArray());
+        let currentCenterX = currentCenter[0];
+        let currentCenterY = currentCenter[1];
+        let zoom0 = Math.pow(2, this._zoom);
+        let zoom1 = Math.pow(2, this._desiredZoom);
+        let refX = reference[0];
+        let refY = reference[1];
+        if (isSpherical(this.currentTransform.cameraType)) {
+            if (refX - currentCenterX > 0.5) {
+                refX = refX - 1;
+            }
+            else if (currentCenterX - refX > 0.5) {
+                refX = 1 + refX;
+            }
+        }
+        let newCenterX = refX - zoom0 / zoom1 * (refX - currentCenterX);
+        let newCenterY = refY - zoom0 / zoom1 * (refY - currentCenterY);
+        if (isSpherical(this._currentImage.cameraType)) {
+            newCenterX = this._spatial
+                .wrap(newCenterX + this._basicRotation[0], 0, 1);
+            newCenterY = this._spatial
+                .clamp(newCenterY + this._basicRotation[1], 0.05, 0.95);
+        }
+        else {
+            newCenterX = this._spatial.clamp(newCenterX, 0, 1);
+            newCenterY = this._spatial.clamp(newCenterY, 0, 1);
+        }
+        this._desiredLookat = new Vector3()
+            .fromArray(this.currentTransform.unprojectBasic([newCenterX, newCenterY], this._lookatDepth));
+    }
+    setCenter(center) {
+        this._desiredLookat = null;
+        this._requestedRotationDelta = null;
+        this._requestedBasicRotation = null;
+        this._desiredZoom = this._zoom;
+        let clamped = [
+            this._spatial.clamp(center[0], 0, 1),
+            this._spatial.clamp(center[1], 0, 1),
+        ];
+        if (this._currentImage == null) {
+            this._desiredCenter = clamped;
+            return;
+        }
+        this._desiredCenter = null;
+        let currentLookat = new Vector3()
+            .fromArray(this.currentTransform.unprojectBasic(clamped, this._lookatDepth));
+        let previousTransform = this.previousTransform != null ?
+            this.previousTransform :
+            this.currentTransform;
+        let previousLookat = new Vector3()
+            .fromArray(previousTransform.unprojectBasic(clamped, this._lookatDepth));
+        this._currentCamera.lookat.copy(currentLookat);
+        this._previousCamera.lookat.copy(previousLookat);
+    }
+    setZoom(zoom) {
+        this._desiredLookat = null;
+        this._requestedRotationDelta = null;
+        this._requestedBasicRotation = null;
+        this._zoom = this._spatial.clamp(zoom, this._minZoom, this._maxZoom);
+        this._desiredZoom = this._zoom;
+    }
+    _applyRotation(delta, camera) {
+        if (camera == null) {
+            return;
+        }
+        let q = new Quaternion().setFromUnitVectors(camera.up, new Vector3(0, 0, 1));
+        let qInverse = q.clone().invert();
+        let offset = new Vector3();
+        offset.copy(camera.lookat).sub(camera.position);
+        offset.applyQuaternion(q);
+        let length = offset.length();
+        let phi = Math.atan2(offset.y, offset.x);
+        phi += delta.phi;
+        let theta = Math.atan2(Math.sqrt(offset.x * offset.x + offset.y * offset.y), offset.z);
+        theta += delta.theta;
+        theta = Math.max(0.1, Math.min(Math.PI - 0.1, theta));
+        offset.x = Math.sin(theta) * Math.cos(phi);
+        offset.y = Math.sin(theta) * Math.sin(phi);
+        offset.z = Math.cos(theta);
+        offset.applyQuaternion(qInverse);
+        camera.lookat.copy(camera.position).add(offset.multiplyScalar(length));
+    }
+    _applyRotationBasic(basicRotation) {
+        let currentImage = this._currentImage;
+        let previousImage = this._previousImage != null ?
+            this.previousImage :
+            this.currentImage;
+        let currentCamera = this._currentCamera;
+        let previousCamera = this._previousCamera;
+        let currentTransform = this.currentTransform;
+        let previousTransform = this.previousTransform != null ?
+            this.previousTransform :
+            this.currentTransform;
+        let currentBasic = currentTransform.projectBasic(currentCamera.lookat.toArray());
+        let previousBasic = previousTransform.projectBasic(previousCamera.lookat.toArray());
+        if (isSpherical(currentImage.cameraType)) {
+            currentBasic[0] = this._spatial.wrap(currentBasic[0] + basicRotation[0], 0, 1);
+            currentBasic[1] = this._spatial.clamp(currentBasic[1] + basicRotation[1], 0.05, 0.95);
+        }
+        else {
+            currentBasic[0] = this._spatial.clamp(currentBasic[0] + basicRotation[0], 0, 1);
+            currentBasic[1] = this._spatial.clamp(currentBasic[1] + basicRotation[1], 0, 1);
+        }
+        if (isSpherical(previousImage.cameraType)) {
+            previousBasic[0] = this._spatial.wrap(previousBasic[0] + basicRotation[0], 0, 1);
+            previousBasic[1] = this._spatial.clamp(previousBasic[1] + basicRotation[1], 0.05, 0.95);
+        }
+        else {
+            previousBasic[0] = this._spatial.clamp(previousBasic[0] + basicRotation[0], 0, 1);
+            previousBasic[1] = this._spatial.clamp(currentBasic[1] + basicRotation[1], 0, 1);
+        }
+        let currentLookat = currentTransform.unprojectBasic(currentBasic, this._lookatDepth);
+        currentCamera.lookat.fromArray(currentLookat);
+        let previousLookat = previousTransform.unprojectBasic(previousBasic, this._lookatDepth);
+        previousCamera.lookat.fromArray(previousLookat);
+    }
+    _updateZoom(animationSpeed) {
+        let diff = this._desiredZoom - this._zoom;
+        let sign = diff > 0 ? 1 : diff < 0 ? -1 : 0;
+        if (diff === 0) {
+            return;
+        }
+        else if (Math.abs(diff) < 2e-3) {
+            this._zoom = this._desiredZoom;
+            if (this._desiredLookat != null) {
+                this._desiredLookat = null;
+            }
+        }
+        else {
+            this._zoom += sign * Math.max(Math.abs(5 * animationSpeed * diff), 2e-3);
+        }
+    }
+    _updateLookat(animationSpeed) {
+        if (this._desiredLookat === null) {
+            return;
+        }
+        let diff = this._desiredLookat.distanceToSquared(this._currentCamera.lookat);
+        if (Math.abs(diff) < 1e-6) {
+            this._currentCamera.lookat.copy(this._desiredLookat);
+            this._desiredLookat = null;
+        }
+        else {
+            this._currentCamera.lookat.lerp(this._desiredLookat, 5 * animationSpeed);
+        }
+    }
+    _updateRotation() {
+        if (this._requestedRotationDelta != null) {
+            let length = this._rotationDelta.lengthSquared();
+            let requestedLength = this._requestedRotationDelta.lengthSquared();
+            if (requestedLength > length) {
+                this._rotationDelta.lerp(this._requestedRotationDelta, this._rotationIncreaseAlpha);
+            }
+            else {
+                this._rotationDelta.lerp(this._requestedRotationDelta, this._rotationDecreaseAlpha);
+            }
+            this._requestedRotationDelta = null;
+            return;
+        }
+        if (this._rotationDelta.isZero) {
+            return;
+        }
+        const alpha = isSpherical(this.currentImage.cameraType) ?
+            1 : this._alpha;
+        this._rotationDelta.multiply(this._rotationAcceleration * alpha);
+        this._rotationDelta.threshold(this._rotationThreshold);
+    }
+    _updateRotationBasic() {
+        if (this._requestedBasicRotation != null) {
+            let x = this._basicRotation[0];
+            let y = this._basicRotation[1];
+            let reqX = this._requestedBasicRotation[0];
+            let reqY = this._requestedBasicRotation[1];
+            if (Math.abs(reqX) > Math.abs(x)) {
+                this._basicRotation[0] = (1 - this._rotationIncreaseAlpha) * x + this._rotationIncreaseAlpha * reqX;
+            }
+            else {
+                this._basicRotation[0] = (1 - this._rotationDecreaseAlpha) * x + this._rotationDecreaseAlpha * reqX;
+            }
+            if (Math.abs(reqY) > Math.abs(y)) {
+                this._basicRotation[1] = (1 - this._rotationIncreaseAlpha) * y + this._rotationIncreaseAlpha * reqY;
+            }
+            else {
+                this._basicRotation[1] = (1 - this._rotationDecreaseAlpha) * y + this._rotationDecreaseAlpha * reqY;
+            }
+            this._requestedBasicRotation = null;
+            return;
+        }
+        if (this._requestedBasicRotationUnbounded != null) {
+            let reqX = this._requestedBasicRotationUnbounded[0];
+            let reqY = this._requestedBasicRotationUnbounded[1];
+            if (Math.abs(reqX) > 0) {
+                this._basicRotation[0] = (1 - this._unboundedRotationAlpha) * this._basicRotation[0] + this._unboundedRotationAlpha * reqX;
+            }
+            if (Math.abs(reqY) > 0) {
+                this._basicRotation[1] = (1 - this._unboundedRotationAlpha) * this._basicRotation[1] + this._unboundedRotationAlpha * reqY;
+            }
+            if (this._desiredLookat != null) {
+                let desiredBasicLookat = this.currentTransform.projectBasic(this._desiredLookat.toArray());
+                desiredBasicLookat[0] += reqX;
+                desiredBasicLookat[1] += reqY;
+                this._desiredLookat = new Vector3()
+                    .fromArray(this.currentTransform.unprojectBasic(desiredBasicLookat, this._lookatDepth));
+            }
+            this._requestedBasicRotationUnbounded = null;
+        }
+        if (this._basicRotation[0] === 0 && this._basicRotation[1] === 0) {
+            return;
+        }
+        this._basicRotation[0] = this._rotationAcceleration * this._basicRotation[0];
+        this._basicRotation[1] = this._rotationAcceleration * this._basicRotation[1];
+        if (Math.abs(this._basicRotation[0]) < this._rotationThreshold / Math.pow(2, this._zoom) &&
+            Math.abs(this._basicRotation[1]) < this._rotationThreshold / Math.pow(2, this._zoom)) {
+            this._basicRotation = [0, 0];
+        }
+    }
+    _clearRotation() {
+        if (isSpherical(this._currentImage.cameraType)) {
+            return;
+        }
+        if (this._requestedRotationDelta != null) {
+            this._requestedRotationDelta = null;
+        }
+        if (!this._rotationDelta.isZero) {
+            this._rotationDelta.reset();
+        }
+        if (this._requestedBasicRotation != null) {
+            this._requestedBasicRotation = null;
+        }
+        if (this._basicRotation[0] > 0 || this._basicRotation[1] > 0) {
+            this._basicRotation = [0, 0];
+        }
+    }
+    _setDesiredCenter() {
+        if (this._desiredCenter == null) {
+            return;
+        }
+        let lookatDirection = new Vector3()
+            .fromArray(this.currentTransform.unprojectBasic(this._desiredCenter, this._lookatDepth))
+            .sub(this._currentCamera.position);
+        this._currentCamera.lookat.copy(this._currentCamera.position.clone().add(lookatDirection));
+        this._previousCamera.lookat.copy(this._previousCamera.position.clone().add(lookatDirection));
+        this._desiredCenter = null;
+    }
+    _setDesiredZoom() {
+        this._desiredZoom =
+            isSpherical(this._currentImage.cameraType) ||
+                this._previousImage == null ?
+                this._zoom : 0;
+    }
+}
+
+class TraversingState extends InteractiveStateBase {
+    constructor(state) {
+        super(state);
+        this._adjustCameras();
+        this._motionless = this._motionlessTransition();
+        this._baseAlpha = this._alpha;
+        this._speedCoefficient = 1;
+        this._unitBezier =
+            new TraversingState._interpolator(0.74, 0.67, 0.38, 0.96);
+        this._useBezier = false;
+    }
+    static register(interpolator) {
+        TraversingState._interpolator = interpolator;
+    }
+    append(images) {
+        let emptyTrajectory = this._trajectory.length === 0;
+        if (emptyTrajectory) {
+            this._resetTransition();
+        }
+        super.append(images);
+        if (emptyTrajectory) {
+            this._setDesiredCenter();
+            this._setDesiredZoom();
+        }
+    }
+    prepend(images) {
+        let emptyTrajectory = this._trajectory.length === 0;
+        if (emptyTrajectory) {
+            this._resetTransition();
+        }
+        super.prepend(images);
+        if (emptyTrajectory) {
+            this._setDesiredCenter();
+            this._setDesiredZoom();
+        }
+    }
+    set(images) {
+        super.set(images);
+        this._desiredLookat = null;
+        this._resetTransition();
+        this._clearRotation();
+        this._setDesiredCenter();
+        this._setDesiredZoom();
+        if (this._trajectory.length < 3) {
+            this._useBezier = true;
+        }
+    }
+    setSpeed(speed) {
+        this._speedCoefficient = this._spatial.clamp(speed, 0, 10);
+    }
+    update(fps) {
+        if (this._alpha === 1 && this._currentIndex + this._alpha < this._trajectory.length) {
+            this._currentIndex += 1;
+            this._useBezier = this._trajectory.length < 3 &&
+                this._currentIndex + 1 === this._trajectory.length;
+            this._setCurrent();
+            this._resetTransition();
+            this._clearRotation();
+            this._desiredZoom =
+                isSpherical(this._currentImage.cameraType) ?
+                    this._zoom : 0;
+            this._desiredLookat = null;
+        }
+        let animationSpeed = this._animationSpeed * (60 / fps);
+        this._baseAlpha = Math.min(1, this._baseAlpha + this._speedCoefficient * animationSpeed);
+        if (this._useBezier) {
+            this._alpha = this._unitBezier.solve(this._baseAlpha);
+        }
+        else {
+            this._alpha = this._baseAlpha;
+        }
+        this._updateRotation();
+        if (!this._rotationDelta.isZero) {
+            this._applyRotation(this._rotationDelta, this._previousCamera);
+            this._applyRotation(this._rotationDelta, this._currentCamera);
+        }
+        this._updateRotationBasic();
+        if (this._basicRotation[0] !== 0 || this._basicRotation[1] !== 0) {
+            this._applyRotationBasic(this._basicRotation);
+        }
+        this._updateZoom(animationSpeed);
+        this._updateLookat(animationSpeed);
+        this._camera.lerpCameras(this._previousCamera, this._currentCamera, this.alpha);
+    }
+    _getAlpha() {
+        return this._motionless ? Math.ceil(this._alpha) : this._alpha;
+    }
+    _setCurrentCamera() {
+        super._setCurrentCamera();
+        this._adjustCameras();
+    }
+    _adjustCameras() {
+        if (this._previousImage == null) {
+            return;
+        }
+        let lookat = this._camera.lookat.clone().sub(this._camera.position);
+        this._previousCamera.lookat.copy(lookat.clone().add(this._previousCamera.position));
+        if (isSpherical(this._currentImage.cameraType)) {
+            this._currentCamera.lookat.copy(lookat.clone().add(this._currentCamera.position));
+        }
+    }
+    _resetTransition() {
+        this._alpha = 0;
+        this._baseAlpha = 0;
+        this._motionless = this._motionlessTransition();
+    }
+}
+
+class ComponentService {
+    constructor(container, navigator) {
+        this._components = {};
+        for (const componentName in ComponentService.registeredComponents) {
+            if (!ComponentService.registeredComponents.hasOwnProperty(componentName)) {
+                continue;
+            }
+            const component = ComponentService.registeredComponents[componentName];
+            this._components[componentName] = {
+                active: false,
+                component: new component(componentName, container, navigator),
+            };
+        }
+        this._coverComponent = new ComponentService.registeredCoverComponent("cover", container, navigator);
+        this._coverComponent.activate();
+        this._coverActivated = true;
+    }
+    static register(component) {
+        if (ComponentService.registeredComponents[component.componentName] === undefined) {
+            ComponentService.registeredComponents[component.componentName] = component;
+        }
+    }
+    static registerCover(coverComponent) {
+        ComponentService.registeredCoverComponent = coverComponent;
+    }
+    get coverActivated() {
+        return this._coverActivated;
+    }
+    activateCover() {
+        if (this._coverActivated) {
+            return;
+        }
+        this._coverActivated = true;
+        for (const componentName in this._components) {
+            if (!this._components.hasOwnProperty(componentName)) {
+                continue;
+            }
+            const component = this._components[componentName];
+            if (component.active) {
+                component.component.deactivate();
+            }
+        }
+    }
+    deactivateCover() {
+        if (!this._coverActivated) {
+            return;
+        }
+        this._coverActivated = false;
+        for (const componentName in this._components) {
+            if (!this._components.hasOwnProperty(componentName)) {
+                continue;
+            }
+            const component = this._components[componentName];
+            if (component.active) {
+                component.component.activate();
+            }
+        }
+    }
+    activate(name) {
+        this._checkName(name);
+        this._components[name].active = true;
+        if (!this._coverActivated) {
+            this.get(name).activate();
+        }
+    }
+    configure(name, conf) {
+        this._checkName(name);
+        this.get(name).configure(conf);
+    }
+    deactivate(name) {
+        this._checkName(name);
+        this._components[name].active = false;
+        if (!this._coverActivated) {
+            this.get(name).deactivate();
+        }
+    }
+    get(name) {
+        return this._components[name].component;
+    }
+    getCover() {
+        return this._coverComponent;
+    }
+    remove() {
+        this._coverComponent.deactivate();
+        for (const componentName in this._components) {
+            if (!this._components.hasOwnProperty(componentName)) {
+                continue;
+            }
+            this._components[componentName].component.deactivate();
+        }
+    }
+    _checkName(name) {
+        if (!(name in this._components)) {
+            throw new ArgumentMapillaryError(`Component does not exist: ${name}`);
+        }
+    }
+}
+ComponentService.registeredComponents = {};
+
+var nativeIsArray = Array.isArray;
+var toString$2 = Object.prototype.toString;
+
+var xIsArray = nativeIsArray || isArray;
+
+function isArray(obj) {
+    return toString$2.call(obj) === "[object Array]"
+}
+
+var version = "2";
+
+VirtualPatch.NONE = 0;
+VirtualPatch.VTEXT = 1;
+VirtualPatch.VNODE = 2;
+VirtualPatch.WIDGET = 3;
+VirtualPatch.PROPS = 4;
+VirtualPatch.ORDER = 5;
+VirtualPatch.INSERT = 6;
+VirtualPatch.REMOVE = 7;
+VirtualPatch.THUNK = 8;
+
+var vpatch = VirtualPatch;
+
+function VirtualPatch(type, vNode, patch) {
+    this.type = Number(type);
+    this.vNode = vNode;
+    this.patch = patch;
+}
+
+VirtualPatch.prototype.version = version;
+VirtualPatch.prototype.type = "VirtualPatch";
+
+var isVnode = isVirtualNode;
+
+function isVirtualNode(x) {
+    return x && x.type === "VirtualNode" && x.version === version
+}
+
+var isVtext = isVirtualText;
+
+function isVirtualText(x) {
+    return x && x.type === "VirtualText" && x.version === version
+}
+
+var isWidget_1 = isWidget;
+
+function isWidget(w) {
+    return w && w.type === "Widget"
+}
+
+var isThunk_1 = isThunk;
+
+function isThunk(t) {
+    return t && t.type === "Thunk"
+}
+
+var handleThunk_1 = handleThunk;
+
+function handleThunk(a, b) {
+    var renderedA = a;
+    var renderedB = b;
+
+    if (isThunk_1(b)) {
+        renderedB = renderThunk(b, a);
+    }
+
+    if (isThunk_1(a)) {
+        renderedA = renderThunk(a, null);
+    }
+
+    return {
+        a: renderedA,
+        b: renderedB
+    }
+}
+
+function renderThunk(thunk, previous) {
+    var renderedThunk = thunk.vnode;
+
+    if (!renderedThunk) {
+        renderedThunk = thunk.vnode = thunk.render(previous);
+    }
+
+    if (!(isVnode(renderedThunk) ||
+            isVtext(renderedThunk) ||
+            isWidget_1(renderedThunk))) {
+        throw new Error("thunk did not return a valid node");
+    }
+
+    return renderedThunk
+}
+
+var isObject = function isObject(x) {
+       return typeof x === 'object' && x !== null;
+};
+
+var isVhook = isHook;
+
+function isHook(hook) {
+    return hook &&
+      (typeof hook.hook === "function" && !hook.hasOwnProperty("hook") ||
+       typeof hook.unhook === "function" && !hook.hasOwnProperty("unhook"))
+}
+
+var diffProps_1 = diffProps;
+
+function diffProps(a, b) {
+    var diff;
+
+    for (var aKey in a) {
+        if (!(aKey in b)) {
+            diff = diff || {};
+            diff[aKey] = undefined;
+        }
+
+        var aValue = a[aKey];
+        var bValue = b[aKey];
+
+        if (aValue === bValue) {
+            continue
+        } else if (isObject(aValue) && isObject(bValue)) {
+            if (getPrototype$1(bValue) !== getPrototype$1(aValue)) {
+                diff = diff || {};
+                diff[aKey] = bValue;
+            } else if (isVhook(bValue)) {
+                 diff = diff || {};
+                 diff[aKey] = bValue;
+            } else {
+                var objectDiff = diffProps(aValue, bValue);
+                if (objectDiff) {
+                    diff = diff || {};
+                    diff[aKey] = objectDiff;
+                }
+            }
+        } else {
+            diff = diff || {};
+            diff[aKey] = bValue;
+        }
+    }
+
+    for (var bKey in b) {
+        if (!(bKey in a)) {
+            diff = diff || {};
+            diff[bKey] = b[bKey];
+        }
+    }
+
+    return diff
+}
+
+function getPrototype$1(value) {
+  if (Object.getPrototypeOf) {
+    return Object.getPrototypeOf(value)
+  } else if (value.__proto__) {
+    return value.__proto__
+  } else if (value.constructor) {
+    return value.constructor.prototype
+  }
+}
+
+var diff_1$1 = diff;
+
+function diff(a, b) {
+    var patch = { a: a };
+    walk(a, b, patch, 0);
+    return patch
+}
+
+function walk(a, b, patch, index) {
+    if (a === b) {
+        return
+    }
+
+    var apply = patch[index];
+    var applyClear = false;
+
+    if (isThunk_1(a) || isThunk_1(b)) {
+        thunks(a, b, patch, index);
+    } else if (b == null) {
+
+        // If a is a widget we will add a remove patch for it
+        // Otherwise any child widgets/hooks must be destroyed.
+        // This prevents adding two remove patches for a widget.
+        if (!isWidget_1(a)) {
+            clearState(a, patch, index);
+            apply = patch[index];
+        }
+
+        apply = appendPatch(apply, new vpatch(vpatch.REMOVE, a, b));
+    } else if (isVnode(b)) {
+        if (isVnode(a)) {
+            if (a.tagName === b.tagName &&
+                a.namespace === b.namespace &&
+                a.key === b.key) {
+                var propsPatch = diffProps_1(a.properties, b.properties);
+                if (propsPatch) {
+                    apply = appendPatch(apply,
+                        new vpatch(vpatch.PROPS, a, propsPatch));
+                }
+                apply = diffChildren(a, b, patch, apply, index);
+            } else {
+                apply = appendPatch(apply, new vpatch(vpatch.VNODE, a, b));
+                applyClear = true;
+            }
+        } else {
+            apply = appendPatch(apply, new vpatch(vpatch.VNODE, a, b));
+            applyClear = true;
+        }
+    } else if (isVtext(b)) {
+        if (!isVtext(a)) {
+            apply = appendPatch(apply, new vpatch(vpatch.VTEXT, a, b));
+            applyClear = true;
+        } else if (a.text !== b.text) {
+            apply = appendPatch(apply, new vpatch(vpatch.VTEXT, a, b));
+        }
+    } else if (isWidget_1(b)) {
+        if (!isWidget_1(a)) {
+            applyClear = true;
+        }
+
+        apply = appendPatch(apply, new vpatch(vpatch.WIDGET, a, b));
+    }
+
+    if (apply) {
+        patch[index] = apply;
+    }
+
+    if (applyClear) {
+        clearState(a, patch, index);
+    }
+}
+
+function diffChildren(a, b, patch, apply, index) {
+    var aChildren = a.children;
+    var orderedSet = reorder(aChildren, b.children);
+    var bChildren = orderedSet.children;
+
+    var aLen = aChildren.length;
+    var bLen = bChildren.length;
+    var len = aLen > bLen ? aLen : bLen;
+
+    for (var i = 0; i < len; i++) {
+        var leftNode = aChildren[i];
+        var rightNode = bChildren[i];
+        index += 1;
+
+        if (!leftNode) {
+            if (rightNode) {
+                // Excess nodes in b need to be added
+                apply = appendPatch(apply,
+                    new vpatch(vpatch.INSERT, null, rightNode));
+            }
+        } else {
+            walk(leftNode, rightNode, patch, index);
+        }
+
+        if (isVnode(leftNode) && leftNode.count) {
+            index += leftNode.count;
+        }
+    }
+
+    if (orderedSet.moves) {
+        // Reorder nodes last
+        apply = appendPatch(apply, new vpatch(
+            vpatch.ORDER,
+            a,
+            orderedSet.moves
+        ));
+    }
+
+    return apply
+}
+
+function clearState(vNode, patch, index) {
+    // TODO: Make this a single walk, not two
+    unhook(vNode, patch, index);
+    destroyWidgets(vNode, patch, index);
+}
+
+// Patch records for all destroyed widgets must be added because we need
+// a DOM node reference for the destroy function
+function destroyWidgets(vNode, patch, index) {
+    if (isWidget_1(vNode)) {
+        if (typeof vNode.destroy === "function") {
+            patch[index] = appendPatch(
+                patch[index],
+                new vpatch(vpatch.REMOVE, vNode, null)
+            );
+        }
+    } else if (isVnode(vNode) && (vNode.hasWidgets || vNode.hasThunks)) {
+        var children = vNode.children;
+        var len = children.length;
+        for (var i = 0; i < len; i++) {
+            var child = children[i];
+            index += 1;
+
+            destroyWidgets(child, patch, index);
+
+            if (isVnode(child) && child.count) {
+                index += child.count;
+            }
+        }
+    } else if (isThunk_1(vNode)) {
+        thunks(vNode, null, patch, index);
+    }
+}
+
+// Create a sub-patch for thunks
+function thunks(a, b, patch, index) {
+    var nodes = handleThunk_1(a, b);
+    var thunkPatch = diff(nodes.a, nodes.b);
+    if (hasPatches(thunkPatch)) {
+        patch[index] = new vpatch(vpatch.THUNK, null, thunkPatch);
+    }
+}
+
+function hasPatches(patch) {
+    for (var index in patch) {
+        if (index !== "a") {
+            return true
+        }
+    }
+
+    return false
+}
+
+// Execute hooks when two nodes are identical
+function unhook(vNode, patch, index) {
+    if (isVnode(vNode)) {
+        if (vNode.hooks) {
+            patch[index] = appendPatch(
+                patch[index],
+                new vpatch(
+                    vpatch.PROPS,
+                    vNode,
+                    undefinedKeys(vNode.hooks)
+                )
+            );
+        }
+
+        if (vNode.descendantHooks || vNode.hasThunks) {
+            var children = vNode.children;
+            var len = children.length;
+            for (var i = 0; i < len; i++) {
+                var child = children[i];
+                index += 1;
+
+                unhook(child, patch, index);
+
+                if (isVnode(child) && child.count) {
+                    index += child.count;
+                }
+            }
+        }
+    } else if (isThunk_1(vNode)) {
+        thunks(vNode, null, patch, index);
+    }
+}
+
+function undefinedKeys(obj) {
+    var result = {};
+
+    for (var key in obj) {
+        result[key] = undefined;
+    }
+
+    return result
+}
+
+// List diff, naive left to right reordering
+function reorder(aChildren, bChildren) {
+    // O(M) time, O(M) memory
+    var bChildIndex = keyIndex(bChildren);
+    var bKeys = bChildIndex.keys;
+    var bFree = bChildIndex.free;
+
+    if (bFree.length === bChildren.length) {
+        return {
+            children: bChildren,
+            moves: null
+        }
+    }
+
+    // O(N) time, O(N) memory
+    var aChildIndex = keyIndex(aChildren);
+    var aKeys = aChildIndex.keys;
+    var aFree = aChildIndex.free;
+
+    if (aFree.length === aChildren.length) {
+        return {
+            children: bChildren,
+            moves: null
+        }
+    }
+
+    // O(MAX(N, M)) memory
+    var newChildren = [];
+
+    var freeIndex = 0;
+    var freeCount = bFree.length;
+    var deletedItems = 0;
+
+    // Iterate through a and match a node in b
+    // O(N) time,
+    for (var i = 0 ; i < aChildren.length; i++) {
+        var aItem = aChildren[i];
+        var itemIndex;
+
+        if (aItem.key) {
+            if (bKeys.hasOwnProperty(aItem.key)) {
+                // Match up the old keys
+                itemIndex = bKeys[aItem.key];
+                newChildren.push(bChildren[itemIndex]);
+
+            } else {
+                // Remove old keyed items
+                itemIndex = i - deletedItems++;
+                newChildren.push(null);
+            }
+        } else {
+            // Match the item in a with the next free item in b
+            if (freeIndex < freeCount) {
+                itemIndex = bFree[freeIndex++];
+                newChildren.push(bChildren[itemIndex]);
+            } else {
+                // There are no free items in b to match with
+                // the free items in a, so the extra free nodes
+                // are deleted.
+                itemIndex = i - deletedItems++;
+                newChildren.push(null);
+            }
+        }
+    }
+
+    var lastFreeIndex = freeIndex >= bFree.length ?
+        bChildren.length :
+        bFree[freeIndex];
+
+    // Iterate through b and append any new keys
+    // O(M) time
+    for (var j = 0; j < bChildren.length; j++) {
+        var newItem = bChildren[j];
+
+        if (newItem.key) {
+            if (!aKeys.hasOwnProperty(newItem.key)) {
+                // Add any new keyed items
+                // We are adding new items to the end and then sorting them
+                // in place. In future we should insert new items in place.
+                newChildren.push(newItem);
+            }
+        } else if (j >= lastFreeIndex) {
+            // Add any leftover non-keyed items
+            newChildren.push(newItem);
+        }
+    }
+
+    var simulate = newChildren.slice();
+    var simulateIndex = 0;
+    var removes = [];
+    var inserts = [];
+    var simulateItem;
+
+    for (var k = 0; k < bChildren.length;) {
+        var wantedItem = bChildren[k];
+        simulateItem = simulate[simulateIndex];
+
+        // remove items
+        while (simulateItem === null && simulate.length) {
+            removes.push(remove(simulate, simulateIndex, null));
+            simulateItem = simulate[simulateIndex];
+        }
+
+        if (!simulateItem || simulateItem.key !== wantedItem.key) {
+            // if we need a key in this position...
+            if (wantedItem.key) {
+                if (simulateItem && simulateItem.key) {
+                    // if an insert doesn't put this key in place, it needs to move
+                    if (bKeys[simulateItem.key] !== k + 1) {
+                        removes.push(remove(simulate, simulateIndex, simulateItem.key));
+                        simulateItem = simulate[simulateIndex];
+                        // if the remove didn't put the wanted item in place, we need to insert it
+                        if (!simulateItem || simulateItem.key !== wantedItem.key) {
+                            inserts.push({key: wantedItem.key, to: k});
+                        }
+                        // items are matching, so skip ahead
+                        else {
+                            simulateIndex++;
+                        }
+                    }
+                    else {
+                        inserts.push({key: wantedItem.key, to: k});
+                    }
+                }
+                else {
+                    inserts.push({key: wantedItem.key, to: k});
+                }
+                k++;
+            }
+            // a key in simulate has no matching wanted key, remove it
+            else if (simulateItem && simulateItem.key) {
+                removes.push(remove(simulate, simulateIndex, simulateItem.key));
+            }
+        }
+        else {
+            simulateIndex++;
+            k++;
+        }
+    }
+
+    // remove all the remaining nodes from simulate
+    while(simulateIndex < simulate.length) {
+        simulateItem = simulate[simulateIndex];
+        removes.push(remove(simulate, simulateIndex, simulateItem && simulateItem.key));
+    }
+
+    // If the only moves we have are deletes then we can just
+    // let the delete patch remove these items.
+    if (removes.length === deletedItems && !inserts.length) {
+        return {
+            children: newChildren,
+            moves: null
+        }
+    }
+
+    return {
+        children: newChildren,
+        moves: {
+            removes: removes,
+            inserts: inserts
+        }
+    }
+}
+
+function remove(arr, index, key) {
+    arr.splice(index, 1);
+
+    return {
+        from: index,
+        key: key
+    }
+}
+
+function keyIndex(children) {
+    var keys = {};
+    var free = [];
+    var length = children.length;
+
+    for (var i = 0; i < length; i++) {
+        var child = children[i];
+
+        if (child.key) {
+            keys[child.key] = i;
+        } else {
+            free.push(i);
+        }
+    }
+
+    return {
+        keys: keys,     // A hash of key name to index
+        free: free      // An array of unkeyed item indices
+    }
+}
+
+function appendPatch(apply, patch) {
+    if (apply) {
+        if (xIsArray(apply)) {
+            apply.push(patch);
+        } else {
+            apply = [apply, patch];
+        }
+
+        return apply
+    } else {
+        return patch
+    }
+}
+
+var diff_1 = diff_1$1;
+
+var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
+
+function getAugmentedNamespace(n) {
+       if (n.__esModule) return n;
+       var a = Object.defineProperty({}, '__esModule', {value: true});
+       Object.keys(n).forEach(function (k) {
+               var d = Object.getOwnPropertyDescriptor(n, k);
+               Object.defineProperty(a, k, d.get ? d : {
+                       enumerable: true,
+                       get: function () {
+                               return n[k];
+                       }
+               });
+       });
+       return a;
+}
+
+function createCommonjsModule(fn) {
+  var module = { exports: {} };
+       return fn(module, module.exports), module.exports;
+}
+
+function commonjsRequire (target) {
+       throw new Error('Could not dynamically require "' + target + '". Please configure the dynamicRequireTargets option of @rollup/plugin-commonjs appropriately for this require call to behave properly.');
+}
+
+var slice = Array.prototype.slice;
+
+var domWalk = iterativelyWalk;
+
+function iterativelyWalk(nodes, cb) {
+    if (!('length' in nodes)) {
+        nodes = [nodes];
+    }
+    
+    nodes = slice.call(nodes);
+
+    while(nodes.length) {
+        var node = nodes.shift(),
+            ret = cb(node);
+
+        if (ret) {
+            return ret
+        }
+
+        if (node.childNodes && node.childNodes.length) {
+            nodes = slice.call(node.childNodes).concat(nodes);
+        }
+    }
+}
+
+var domComment = Comment;
+
+function Comment(data, owner) {
+    if (!(this instanceof Comment)) {
+        return new Comment(data, owner)
+    }
+
+    this.data = data;
+    this.nodeValue = data;
+    this.length = data.length;
+    this.ownerDocument = owner || null;
+}
+
+Comment.prototype.nodeType = 8;
+Comment.prototype.nodeName = "#comment";
+
+Comment.prototype.toString = function _Comment_toString() {
+    return "[object Comment]"
+};
+
+var domText = DOMText;
+
+function DOMText(value, owner) {
+    if (!(this instanceof DOMText)) {
+        return new DOMText(value)
+    }
+
+    this.data = value || "";
+    this.length = this.data.length;
+    this.ownerDocument = owner || null;
+}
+
+DOMText.prototype.type = "DOMTextNode";
+DOMText.prototype.nodeType = 3;
+DOMText.prototype.nodeName = "#text";
+
+DOMText.prototype.toString = function _Text_toString() {
+    return this.data
+};
+
+DOMText.prototype.replaceData = function replaceData(index, length, value) {
+    var current = this.data;
+    var left = current.substring(0, index);
+    var right = current.substring(index + length, current.length);
+    this.data = left + value + right;
+    this.length = this.data.length;
+};
+
+var dispatchEvent_1 = dispatchEvent;
+
+function dispatchEvent(ev) {
+    var elem = this;
+    var type = ev.type;
+
+    if (!ev.target) {
+        ev.target = elem;
+    }
+
+    if (!elem.listeners) {
+        elem.listeners = {};
+    }
+
+    var listeners = elem.listeners[type];
+
+    if (listeners) {
+        return listeners.forEach(function (listener) {
+            ev.currentTarget = elem;
+            if (typeof listener === 'function') {
+                listener(ev);
+            } else {
+                listener.handleEvent(ev);
+            }
+        })
+    }
+
+    if (elem.parentNode) {
+        elem.parentNode.dispatchEvent(ev);
+    }
+}
+
+var addEventListener_1 = addEventListener;
+
+function addEventListener(type, listener) {
+    var elem = this;
+
+    if (!elem.listeners) {
+        elem.listeners = {};
+    }
+
+    if (!elem.listeners[type]) {
+        elem.listeners[type] = [];
+    }
+
+    if (elem.listeners[type].indexOf(listener) === -1) {
+        elem.listeners[type].push(listener);
+    }
+}
+
+var removeEventListener_1 = removeEventListener;
+
+function removeEventListener(type, listener) {
+    var elem = this;
+
+    if (!elem.listeners) {
+        return
+    }
+
+    if (!elem.listeners[type]) {
+        return
+    }
+
+    var list = elem.listeners[type];
+    var index = list.indexOf(listener);
+    if (index !== -1) {
+        list.splice(index, 1);
+    }
+}
+
+var serialize = serializeNode;
+
+var voidElements = ["area","base","br","col","embed","hr","img","input","keygen","link","menuitem","meta","param","source","track","wbr"];
+
+function serializeNode(node) {
+    switch (node.nodeType) {
+        case 3:
+            return escapeText(node.data)
+        case 8:
+            return "<!--" + node.data + "-->"
+        default:
+            return serializeElement(node)
+    }
+}
+
+function serializeElement(elem) {
+    var strings = [];
+
+    var tagname = elem.tagName;
+
+    if (elem.namespaceURI === "http://www.w3.org/1999/xhtml") {
+        tagname = tagname.toLowerCase();
+    }
+
+    strings.push("<" + tagname + properties(elem) + datasetify(elem));
+
+    if (voidElements.indexOf(tagname) > -1) {
+        strings.push(" />");
+    } else {
+        strings.push(">");
+
+        if (elem.childNodes.length) {
+            strings.push.apply(strings, elem.childNodes.map(serializeNode));
+        } else if (elem.textContent || elem.innerText) {
+            strings.push(escapeText(elem.textContent || elem.innerText));
+        } else if (elem.innerHTML) {
+            strings.push(elem.innerHTML);
+        }
+
+        strings.push("</" + tagname + ">");
+    }
+
+    return strings.join("")
+}
+
+function isProperty(elem, key) {
+    var type = typeof elem[key];
+
+    if (key === "style" && Object.keys(elem.style).length > 0) {
+      return true
+    }
+
+    return elem.hasOwnProperty(key) &&
+        (type === "string" || type === "boolean" || type === "number") &&
+        key !== "nodeName" && key !== "className" && key !== "tagName" &&
+        key !== "textContent" && key !== "innerText" && key !== "namespaceURI" &&  key !== "innerHTML"
+}
+
+function stylify(styles) {
+    if (typeof styles === 'string') return styles
+    var attr = "";
+    Object.keys(styles).forEach(function (key) {
+        var value = styles[key];
+        key = key.replace(/[A-Z]/g, function(c) {
+            return "-" + c.toLowerCase();
+        });
+        attr += key + ":" + value + ";";
+    });
+    return attr
+}
+
+function datasetify(elem) {
+    var ds = elem.dataset;
+    var props = [];
+
+    for (var key in ds) {
+        props.push({ name: "data-" + key, value: ds[key] });
+    }
+
+    return props.length ? stringify(props) : ""
+}
+
+function stringify(list) {
+    var attributes = [];
+    list.forEach(function (tuple) {
+        var name = tuple.name;
+        var value = tuple.value;
+
+        if (name === "style") {
+            value = stylify(value);
+        }
+
+        attributes.push(name + "=" + "\"" + escapeAttributeValue(value) + "\"");
+    });
+
+    return attributes.length ? " " + attributes.join(" ") : ""
+}
+
+function properties(elem) {
+    var props = [];
+    for (var key in elem) {
+        if (isProperty(elem, key)) {
+            props.push({ name: key, value: elem[key] });
+        }
+    }
+
+    for (var ns in elem._attributes) {
+      for (var attribute in elem._attributes[ns]) {
+        var prop = elem._attributes[ns][attribute];
+        var name = (prop.prefix ? prop.prefix + ":" : "") + attribute;
+        props.push({ name: name, value: prop.value });
+      }
+    }
+
+    if (elem.className) {
+        props.push({ name: "class", value: elem.className });
+    }
+
+    return props.length ? stringify(props) : ""
+}
+
+function escapeText(s) {
+    var str = '';
+
+    if (typeof(s) === 'string') { 
+        str = s; 
+    } else if (s) {
+        str = s.toString();
+    }
+
+    return str
+        .replace(/&/g, "&amp;")
+        .replace(/</g, "&lt;")
+        .replace(/>/g, "&gt;")
+}
+
+function escapeAttributeValue(str) {
+    return escapeText(str).replace(/"/g, "&quot;")
+}
+
+var htmlns = "http://www.w3.org/1999/xhtml";
+
+var domElement = DOMElement;
+
+function DOMElement(tagName, owner, namespace) {
+    if (!(this instanceof DOMElement)) {
+        return new DOMElement(tagName)
+    }
+
+    var ns = namespace === undefined ? htmlns : (namespace || null);
+
+    this.tagName = ns === htmlns ? String(tagName).toUpperCase() : tagName;
+    this.nodeName = this.tagName;
+    this.className = "";
+    this.dataset = {};
+    this.childNodes = [];
+    this.parentNode = null;
+    this.style = {};
+    this.ownerDocument = owner || null;
+    this.namespaceURI = ns;
+    this._attributes = {};
+
+    if (this.tagName === 'INPUT') {
+      this.type = 'text';
+    }
+}
+
+DOMElement.prototype.type = "DOMElement";
+DOMElement.prototype.nodeType = 1;
+
+DOMElement.prototype.appendChild = function _Element_appendChild(child) {
+    if (child.parentNode) {
+        child.parentNode.removeChild(child);
+    }
+
+    this.childNodes.push(child);
+    child.parentNode = this;
+
+    return child
+};
+
+DOMElement.prototype.replaceChild =
+    function _Element_replaceChild(elem, needle) {
+        // TODO: Throw NotFoundError if needle.parentNode !== this
+
+        if (elem.parentNode) {
+            elem.parentNode.removeChild(elem);
+        }
+
+        var index = this.childNodes.indexOf(needle);
+
+        needle.parentNode = null;
+        this.childNodes[index] = elem;
+        elem.parentNode = this;
+
+        return needle
+    };
+
+DOMElement.prototype.removeChild = function _Element_removeChild(elem) {
+    // TODO: Throw NotFoundError if elem.parentNode !== this
+
+    var index = this.childNodes.indexOf(elem);
+    this.childNodes.splice(index, 1);
+
+    elem.parentNode = null;
+    return elem
+};
+
+DOMElement.prototype.insertBefore =
+    function _Element_insertBefore(elem, needle) {
+        // TODO: Throw NotFoundError if referenceElement is a dom node
+        // and parentNode !== this
+
+        if (elem.parentNode) {
+            elem.parentNode.removeChild(elem);
+        }
+
+        var index = needle === null || needle === undefined ?
+            -1 :
+            this.childNodes.indexOf(needle);
+
+        if (index > -1) {
+            this.childNodes.splice(index, 0, elem);
+        } else {
+            this.childNodes.push(elem);
+        }
+
+        elem.parentNode = this;
+        return elem
+    };
+
+DOMElement.prototype.setAttributeNS =
+    function _Element_setAttributeNS(namespace, name, value) {
+        var prefix = null;
+        var localName = name;
+        var colonPosition = name.indexOf(":");
+        if (colonPosition > -1) {
+            prefix = name.substr(0, colonPosition);
+            localName = name.substr(colonPosition + 1);
+        }
+        if (this.tagName === 'INPUT' && name === 'type') {
+          this.type = value;
+        }
+        else {
+          var attributes = this._attributes[namespace] || (this._attributes[namespace] = {});
+          attributes[localName] = {value: value, prefix: prefix};
+        }
+    };
+
+DOMElement.prototype.getAttributeNS =
+    function _Element_getAttributeNS(namespace, name) {
+        var attributes = this._attributes[namespace];
+        var value = attributes && attributes[name] && attributes[name].value;
+        if (this.tagName === 'INPUT' && name === 'type') {
+          return this.type;
+        }
+        if (typeof value !== "string") {
+            return null
+        }
+        return value
+    };
+
+DOMElement.prototype.removeAttributeNS =
+    function _Element_removeAttributeNS(namespace, name) {
+        var attributes = this._attributes[namespace];
+        if (attributes) {
+            delete attributes[name];
+        }
+    };
+
+DOMElement.prototype.hasAttributeNS =
+    function _Element_hasAttributeNS(namespace, name) {
+        var attributes = this._attributes[namespace];
+        return !!attributes && name in attributes;
+    };
+
+DOMElement.prototype.setAttribute = function _Element_setAttribute(name, value) {
+    return this.setAttributeNS(null, name, value)
+};
+
+DOMElement.prototype.getAttribute = function _Element_getAttribute(name) {
+    return this.getAttributeNS(null, name)
+};
+
+DOMElement.prototype.removeAttribute = function _Element_removeAttribute(name) {
+    return this.removeAttributeNS(null, name)
+};
+
+DOMElement.prototype.hasAttribute = function _Element_hasAttribute(name) {
+    return this.hasAttributeNS(null, name)
+};
+
+DOMElement.prototype.removeEventListener = removeEventListener_1;
+DOMElement.prototype.addEventListener = addEventListener_1;
+DOMElement.prototype.dispatchEvent = dispatchEvent_1;
+
+// Un-implemented
+DOMElement.prototype.focus = function _Element_focus() {
+    return void 0
+};
+
+DOMElement.prototype.toString = function _Element_toString() {
+    return serialize(this)
+};
+
+DOMElement.prototype.getElementsByClassName = function _Element_getElementsByClassName(classNames) {
+    var classes = classNames.split(" ");
+    var elems = [];
+
+    domWalk(this, function (node) {
+        if (node.nodeType === 1) {
+            var nodeClassName = node.className || "";
+            var nodeClasses = nodeClassName.split(" ");
+
+            if (classes.every(function (item) {
+                return nodeClasses.indexOf(item) !== -1
+            })) {
+                elems.push(node);
+            }
+        }
+    });
+
+    return elems
+};
+
+DOMElement.prototype.getElementsByTagName = function _Element_getElementsByTagName(tagName) {
+    tagName = tagName.toLowerCase();
+    var elems = [];
+
+    domWalk(this.childNodes, function (node) {
+        if (node.nodeType === 1 && (tagName === '*' || node.tagName.toLowerCase() === tagName)) {
+            elems.push(node);
+        }
+    });
+
+    return elems
+};
+
+DOMElement.prototype.contains = function _Element_contains(element) {
+    return domWalk(this, function (node) {
+        return element === node
+    }) || false
+};
+
+var domFragment = DocumentFragment;
+
+function DocumentFragment(owner) {
+    if (!(this instanceof DocumentFragment)) {
+        return new DocumentFragment()
+    }
+
+    this.childNodes = [];
+    this.parentNode = null;
+    this.ownerDocument = owner || null;
+}
+
+DocumentFragment.prototype.type = "DocumentFragment";
+DocumentFragment.prototype.nodeType = 11;
+DocumentFragment.prototype.nodeName = "#document-fragment";
+
+DocumentFragment.prototype.appendChild  = domElement.prototype.appendChild;
+DocumentFragment.prototype.replaceChild = domElement.prototype.replaceChild;
+DocumentFragment.prototype.removeChild  = domElement.prototype.removeChild;
+
+DocumentFragment.prototype.toString =
+    function _DocumentFragment_toString() {
+        return this.childNodes.map(function (node) {
+            return String(node)
+        }).join("")
+    };
+
+var event = Event;
+
+function Event(family) {}
+
+Event.prototype.initEvent = function _Event_initEvent(type, bubbles, cancelable) {
+    this.type = type;
+    this.bubbles = bubbles;
+    this.cancelable = cancelable;
+};
+
+Event.prototype.preventDefault = function _Event_preventDefault() {
+    
+};
+
+var document$1 = Document;
+
+function Document() {
+    if (!(this instanceof Document)) {
+        return new Document();
+    }
+
+    this.head = this.createElement("head");
+    this.body = this.createElement("body");
+    this.documentElement = this.createElement("html");
+    this.documentElement.appendChild(this.head);
+    this.documentElement.appendChild(this.body);
+    this.childNodes = [this.documentElement];
+    this.nodeType = 9;
+}
+
+var proto = Document.prototype;
+proto.createTextNode = function createTextNode(value) {
+    return new domText(value, this)
+};
+
+proto.createElementNS = function createElementNS(namespace, tagName) {
+    var ns = namespace === null ? null : String(namespace);
+    return new domElement(tagName, this, ns)
+};
+
+proto.createElement = function createElement(tagName) {
+    return new domElement(tagName, this)
+};
+
+proto.createDocumentFragment = function createDocumentFragment() {
+    return new domFragment(this)
+};
+
+proto.createEvent = function createEvent(family) {
+    return new event(family)
+};
+
+proto.createComment = function createComment(data) {
+    return new domComment(data, this)
+};
+
+proto.getElementById = function getElementById(id) {
+    id = String(id);
+
+    var result = domWalk(this.childNodes, function (node) {
+        if (String(node.id) === id) {
+            return node
+        }
+    });
+
+    return result || null
+};
+
+proto.getElementsByClassName = domElement.prototype.getElementsByClassName;
+proto.getElementsByTagName = domElement.prototype.getElementsByTagName;
+proto.contains = domElement.prototype.contains;
+
+proto.removeEventListener = removeEventListener_1;
+proto.addEventListener = addEventListener_1;
+proto.dispatchEvent = dispatchEvent_1;
+
+var minDocument = new document$1();
+
+var topLevel = typeof commonjsGlobal !== 'undefined' ? commonjsGlobal :
+    typeof window !== 'undefined' ? window : {};
+
+
+var doccy;
+
+if (typeof document !== 'undefined') {
+    doccy = document;
+} else {
+    doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'];
+
+    if (!doccy) {
+        doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'] = minDocument;
+    }
+}
+
+var document_1 = doccy;
+
+var applyProperties_1 = applyProperties;
+
+function applyProperties(node, props, previous) {
+    for (var propName in props) {
+        var propValue = props[propName];
+
+        if (propValue === undefined) {
+            removeProperty(node, propName, propValue, previous);
+        } else if (isVhook(propValue)) {
+            removeProperty(node, propName, propValue, previous);
+            if (propValue.hook) {
+                propValue.hook(node,
+                    propName,
+                    previous ? previous[propName] : undefined);
+            }
+        } else {
+            if (isObject(propValue)) {
+                patchObject(node, props, previous, propName, propValue);
+            } else {
+                node[propName] = propValue;
+            }
+        }
+    }
+}
+
+function removeProperty(node, propName, propValue, previous) {
+    if (previous) {
+        var previousValue = previous[propName];
+
+        if (!isVhook(previousValue)) {
+            if (propName === "attributes") {
+                for (var attrName in previousValue) {
+                    node.removeAttribute(attrName);
+                }
+            } else if (propName === "style") {
+                for (var i in previousValue) {
+                    node.style[i] = "";
+                }
+            } else if (typeof previousValue === "string") {
+                node[propName] = "";
+            } else {
+                node[propName] = null;
+            }
+        } else if (previousValue.unhook) {
+            previousValue.unhook(node, propName, propValue);
+        }
+    }
+}
+
+function patchObject(node, props, previous, propName, propValue) {
+    var previousValue = previous ? previous[propName] : undefined;
+
+    // Set attributes
+    if (propName === "attributes") {
+        for (var attrName in propValue) {
+            var attrValue = propValue[attrName];
+
+            if (attrValue === undefined) {
+                node.removeAttribute(attrName);
+            } else {
+                node.setAttribute(attrName, attrValue);
+            }
+        }
+
+        return
+    }
+
+    if(previousValue && isObject(previousValue) &&
+        getPrototype(previousValue) !== getPrototype(propValue)) {
+        node[propName] = propValue;
+        return
+    }
+
+    if (!isObject(node[propName])) {
+        node[propName] = {};
+    }
+
+    var replacer = propName === "style" ? "" : undefined;
+
+    for (var k in propValue) {
+        var value = propValue[k];
+        node[propName][k] = (value === undefined) ? replacer : value;
+    }
+}
+
+function getPrototype(value) {
+    if (Object.getPrototypeOf) {
+        return Object.getPrototypeOf(value)
+    } else if (value.__proto__) {
+        return value.__proto__
+    } else if (value.constructor) {
+        return value.constructor.prototype
+    }
+}
+
+var createElement_1$1 = createElement;
+
+function createElement(vnode, opts) {
+    var doc = opts ? opts.document || document_1 : document_1;
+    var warn = opts ? opts.warn : null;
+
+    vnode = handleThunk_1(vnode).a;
+
+    if (isWidget_1(vnode)) {
+        return vnode.init()
+    } else if (isVtext(vnode)) {
+        return doc.createTextNode(vnode.text)
+    } else if (!isVnode(vnode)) {
+        if (warn) {
+            warn("Item is not a valid virtual dom node", vnode);
+        }
+        return null
+    }
+
+    var node = (vnode.namespace === null) ?
+        doc.createElement(vnode.tagName) :
+        doc.createElementNS(vnode.namespace, vnode.tagName);
+
+    var props = vnode.properties;
+    applyProperties_1(node, props);
+
+    var children = vnode.children;
+
+    for (var i = 0; i < children.length; i++) {
+        var childNode = createElement(children[i], opts);
+        if (childNode) {
+            node.appendChild(childNode);
+        }
+    }
+
+    return node
+}
+
+// Maps a virtual DOM tree onto a real DOM tree in an efficient manner.
+// We don't want to read all of the DOM nodes in the tree so we use
+// the in-order tree indexing to eliminate recursion down certain branches.
+// We only recurse into a DOM node if we know that it contains a child of
+// interest.
+
+var noChild = {};
+
+var domIndex_1 = domIndex;
+
+function domIndex(rootNode, tree, indices, nodes) {
+    if (!indices || indices.length === 0) {
+        return {}
+    } else {
+        indices.sort(ascending);
+        return recurse(rootNode, tree, indices, nodes, 0)
+    }
+}
+
+function recurse(rootNode, tree, indices, nodes, rootIndex) {
+    nodes = nodes || {};
+
+
+    if (rootNode) {
+        if (indexInRange(indices, rootIndex, rootIndex)) {
+            nodes[rootIndex] = rootNode;
+        }
+
+        var vChildren = tree.children;
+
+        if (vChildren) {
+
+            var childNodes = rootNode.childNodes;
+
+            for (var i = 0; i < tree.children.length; i++) {
+                rootIndex += 1;
+
+                var vChild = vChildren[i] || noChild;
+                var nextIndex = rootIndex + (vChild.count || 0);
+
+                // skip recursion down the tree if there are no nodes down here
+                if (indexInRange(indices, rootIndex, nextIndex)) {
+                    recurse(childNodes[i], vChild, indices, nodes, rootIndex);
+                }
+
+                rootIndex = nextIndex;
+            }
+        }
+    }
+
+    return nodes
+}
+
+// Binary search for an index in the interval [left, right]
+function indexInRange(indices, left, right) {
+    if (indices.length === 0) {
+        return false
+    }
+
+    var minIndex = 0;
+    var maxIndex = indices.length - 1;
+    var currentIndex;
+    var currentItem;
+
+    while (minIndex <= maxIndex) {
+        currentIndex = ((maxIndex + minIndex) / 2) >> 0;
+        currentItem = indices[currentIndex];
+
+        if (minIndex === maxIndex) {
+            return currentItem >= left && currentItem <= right
+        } else if (currentItem < left) {
+            minIndex = currentIndex + 1;
+        } else  if (currentItem > right) {
+            maxIndex = currentIndex - 1;
+        } else {
+            return true
+        }
+    }
+
+    return false;
+}
+
+function ascending(a, b) {
+    return a > b ? 1 : -1
+}
+
+var updateWidget_1 = updateWidget;
+
+function updateWidget(a, b) {
+    if (isWidget_1(a) && isWidget_1(b)) {
+        if ("name" in a && "name" in b) {
+            return a.id === b.id
+        } else {
+            return a.init === b.init
+        }
+    }
+
+    return false
+}
+
+var patchOp = applyPatch$1;
+
+function applyPatch$1(vpatch$1, domNode, renderOptions) {
+    var type = vpatch$1.type;
+    var vNode = vpatch$1.vNode;
+    var patch = vpatch$1.patch;
+
+    switch (type) {
+        case vpatch.REMOVE:
+            return removeNode$1(domNode, vNode)
+        case vpatch.INSERT:
+            return insertNode$1(domNode, patch, renderOptions)
+        case vpatch.VTEXT:
+            return stringPatch(domNode, vNode, patch, renderOptions)
+        case vpatch.WIDGET:
+            return widgetPatch(domNode, vNode, patch, renderOptions)
+        case vpatch.VNODE:
+            return vNodePatch(domNode, vNode, patch, renderOptions)
+        case vpatch.ORDER:
+            reorderChildren(domNode, patch);
+            return domNode
+        case vpatch.PROPS:
+            applyProperties_1(domNode, patch, vNode.properties);
+            return domNode
+        case vpatch.THUNK:
+            return replaceRoot(domNode,
+                renderOptions.patch(domNode, patch, renderOptions))
+        default:
+            return domNode
+    }
+}
+
+function removeNode$1(domNode, vNode) {
+    var parentNode = domNode.parentNode;
+
+    if (parentNode) {
+        parentNode.removeChild(domNode);
+    }
+
+    destroyWidget(domNode, vNode);
+
+    return null
+}
+
+function insertNode$1(parentNode, vNode, renderOptions) {
+    var newNode = renderOptions.render(vNode, renderOptions);
+
+    if (parentNode) {
+        parentNode.appendChild(newNode);
+    }
+
+    return parentNode
+}
+
+function stringPatch(domNode, leftVNode, vText, renderOptions) {
+    var newNode;
+
+    if (domNode.nodeType === 3) {
+        domNode.replaceData(0, domNode.length, vText.text);
+        newNode = domNode;
+    } else {
+        var parentNode = domNode.parentNode;
+        newNode = renderOptions.render(vText, renderOptions);
+
+        if (parentNode && newNode !== domNode) {
+            parentNode.replaceChild(newNode, domNode);
+        }
+    }
+
+    return newNode
+}
+
+function widgetPatch(domNode, leftVNode, widget, renderOptions) {
+    var updating = updateWidget_1(leftVNode, widget);
+    var newNode;
+
+    if (updating) {
+        newNode = widget.update(leftVNode, domNode) || domNode;
+    } else {
+        newNode = renderOptions.render(widget, renderOptions);
+    }
+
+    var parentNode = domNode.parentNode;
+
+    if (parentNode && newNode !== domNode) {
+        parentNode.replaceChild(newNode, domNode);
+    }
+
+    if (!updating) {
+        destroyWidget(domNode, leftVNode);
+    }
+
+    return newNode
+}
+
+function vNodePatch(domNode, leftVNode, vNode, renderOptions) {
+    var parentNode = domNode.parentNode;
+    var newNode = renderOptions.render(vNode, renderOptions);
+
+    if (parentNode && newNode !== domNode) {
+        parentNode.replaceChild(newNode, domNode);
+    }
+
+    return newNode
+}
+
+function destroyWidget(domNode, w) {
+    if (typeof w.destroy === "function" && isWidget_1(w)) {
+        w.destroy(domNode);
+    }
+}
+
+function reorderChildren(domNode, moves) {
+    var childNodes = domNode.childNodes;
+    var keyMap = {};
+    var node;
+    var remove;
+    var insert;
+
+    for (var i = 0; i < moves.removes.length; i++) {
+        remove = moves.removes[i];
+        node = childNodes[remove.from];
+        if (remove.key) {
+            keyMap[remove.key] = node;
+        }
+        domNode.removeChild(node);
+    }
+
+    var length = childNodes.length;
+    for (var j = 0; j < moves.inserts.length; j++) {
+        insert = moves.inserts[j];
+        node = keyMap[insert.key];
+        // this is the weirdest bug i've ever seen in webkit
+        domNode.insertBefore(node, insert.to >= length++ ? null : childNodes[insert.to]);
+    }
+}
+
+function replaceRoot(oldRoot, newRoot) {
+    if (oldRoot && newRoot && oldRoot !== newRoot && oldRoot.parentNode) {
+        oldRoot.parentNode.replaceChild(newRoot, oldRoot);
+    }
+
+    return newRoot;
+}
+
+var patch_1$1 = patch;
+
+function patch(rootNode, patches, renderOptions) {
+    renderOptions = renderOptions || {};
+    renderOptions.patch = renderOptions.patch && renderOptions.patch !== patch
+        ? renderOptions.patch
+        : patchRecursive;
+    renderOptions.render = renderOptions.render || createElement_1$1;
+
+    return renderOptions.patch(rootNode, patches, renderOptions)
+}
+
+function patchRecursive(rootNode, patches, renderOptions) {
+    var indices = patchIndices(patches);
+
+    if (indices.length === 0) {
+        return rootNode
+    }
+
+    var index = domIndex_1(rootNode, patches.a, indices);
+    var ownerDocument = rootNode.ownerDocument;
+
+    if (!renderOptions.document && ownerDocument !== document_1) {
+        renderOptions.document = ownerDocument;
+    }
+
+    for (var i = 0; i < indices.length; i++) {
+        var nodeIndex = indices[i];
+        rootNode = applyPatch(rootNode,
+            index[nodeIndex],
+            patches[nodeIndex],
+            renderOptions);
+    }
+
+    return rootNode
+}
+
+function applyPatch(rootNode, domNode, patchList, renderOptions) {
+    if (!domNode) {
+        return rootNode
+    }
+
+    var newNode;
+
+    if (xIsArray(patchList)) {
+        for (var i = 0; i < patchList.length; i++) {
+            newNode = patchOp(patchList[i], domNode, renderOptions);
+
+            if (domNode === rootNode) {
+                rootNode = newNode;
+            }
+        }
+    } else {
+        newNode = patchOp(patchList, domNode, renderOptions);
+
+        if (domNode === rootNode) {
+            rootNode = newNode;
+        }
+    }
+
+    return rootNode
+}
+
+function patchIndices(patches) {
+    var indices = [];
+
+    for (var key in patches) {
+        if (key !== "a") {
+            indices.push(Number(key));
+        }
+    }
+
+    return indices
+}
+
+var patch_1 = patch_1$1;
+
+var vnode = VirtualNode;
+
+var noProperties = {};
+var noChildren = [];
+
+function VirtualNode(tagName, properties, children, key, namespace) {
+    this.tagName = tagName;
+    this.properties = properties || noProperties;
+    this.children = children || noChildren;
+    this.key = key != null ? String(key) : undefined;
+    this.namespace = (typeof namespace === "string") ? namespace : null;
+
+    var count = (children && children.length) || 0;
+    var descendants = 0;
+    var hasWidgets = false;
+    var hasThunks = false;
+    var descendantHooks = false;
+    var hooks;
+
+    for (var propName in properties) {
+        if (properties.hasOwnProperty(propName)) {
+            var property = properties[propName];
+            if (isVhook(property) && property.unhook) {
+                if (!hooks) {
+                    hooks = {};
+                }
+
+                hooks[propName] = property;
+            }
+        }
+    }
+
+    for (var i = 0; i < count; i++) {
+        var child = children[i];
+        if (isVnode(child)) {
+            descendants += child.count || 0;
+
+            if (!hasWidgets && child.hasWidgets) {
+                hasWidgets = true;
+            }
+
+            if (!hasThunks && child.hasThunks) {
+                hasThunks = true;
+            }
+
+            if (!descendantHooks && (child.hooks || child.descendantHooks)) {
+                descendantHooks = true;
+            }
+        } else if (!hasWidgets && isWidget_1(child)) {
+            if (typeof child.destroy === "function") {
+                hasWidgets = true;
+            }
+        } else if (!hasThunks && isThunk_1(child)) {
+            hasThunks = true;
+        }
+    }
+
+    this.count = count + descendants;
+    this.hasWidgets = hasWidgets;
+    this.hasThunks = hasThunks;
+    this.hooks = hooks;
+    this.descendantHooks = descendantHooks;
+}
+
+VirtualNode.prototype.version = version;
+VirtualNode.prototype.type = "VirtualNode";
+
+var vtext = VirtualText;
+
+function VirtualText(text) {
+    this.text = String(text);
+}
+
+VirtualText.prototype.version = version;
+VirtualText.prototype.type = "VirtualText";
+
+/*!
+ * Cross-Browser Split 1.1.1
+ * Copyright 2007-2012 Steven Levithan <stevenlevithan.com>
+ * Available under the MIT License
+ * ECMAScript compliant, uniform cross-browser split method
+ */
+/**
+ * Splits a string into an array of strings using a regex or string separator. Matches of the
+ * separator are not included in the result array. However, if `separator` is a regex that contains
+ * capturing groups, backreferences are spliced into the result each time `separator` is matched.
+ * Fixes browser bugs compared to the native `String.prototype.split` and can be used reliably
+ * cross-browser.
+ * @param {String} str String to split.
+ * @param {RegExp|String} separator Regex or string to use for separating the string.
+ * @param {Number} [limit] Maximum number of items to include in the result array.
+ * @returns {Array} Array of substrings.
+ * @example
+ *
+ * // Basic use
+ * split('a b c d', ' ');
+ * // -> ['a', 'b', 'c', 'd']
+ *
+ * // With limit
+ * split('a b c d', ' ', 2);
+ * // -> ['a', 'b']
+ *
+ * // Backreferences in result array
+ * split('..word1 word2..', /([a-z]+)(\d+)/i);
+ * // -> ['..', 'word', '1', ' ', 'word', '2', '..']
+ */
+var browserSplit = (function split(undef) {
+
+  var nativeSplit = String.prototype.split,
+    compliantExecNpcg = /()??/.exec("")[1] === undef,
+    // NPCG: nonparticipating capturing group
+    self;
+
+  self = function(str, separator, limit) {
+    // If `separator` is not a regex, use `nativeSplit`
+    if (Object.prototype.toString.call(separator) !== "[object RegExp]") {
+      return nativeSplit.call(str, separator, limit);
+    }
+    var output = [],
+      flags = (separator.ignoreCase ? "i" : "") + (separator.multiline ? "m" : "") + (separator.extended ? "x" : "") + // Proposed for ES6
+      (separator.sticky ? "y" : ""),
+      // Firefox 3+
+      lastLastIndex = 0,
+      // Make `global` and avoid `lastIndex` issues by working with a copy
+      separator = new RegExp(separator.source, flags + "g"),
+      separator2, match, lastIndex, lastLength;
+    str += ""; // Type-convert
+    if (!compliantExecNpcg) {
+      // Doesn't need flags gy, but they don't hurt
+      separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags);
+    }
+    /* Values for `limit`, per the spec:
+     * If undefined: 4294967295 // Math.pow(2, 32) - 1
+     * If 0, Infinity, or NaN: 0
+     * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;
+     * If negative number: 4294967296 - Math.floor(Math.abs(limit))
+     * If other: Type-convert, then use the above rules
+     */
+    limit = limit === undef ? -1 >>> 0 : // Math.pow(2, 32) - 1
+    limit >>> 0; // ToUint32(limit)
+    while (match = separator.exec(str)) {
+      // `separator.lastIndex` is not reliable cross-browser
+      lastIndex = match.index + match[0].length;
+      if (lastIndex > lastLastIndex) {
+        output.push(str.slice(lastLastIndex, match.index));
+        // Fix browsers whose `exec` methods don't consistently return `undefined` for
+        // nonparticipating capturing groups
+        if (!compliantExecNpcg && match.length > 1) {
+          match[0].replace(separator2, function() {
+            for (var i = 1; i < arguments.length - 2; i++) {
+              if (arguments[i] === undef) {
+                match[i] = undef;
+              }
+            }
+          });
+        }
+        if (match.length > 1 && match.index < str.length) {
+          Array.prototype.push.apply(output, match.slice(1));
+        }
+        lastLength = match[0].length;
+        lastLastIndex = lastIndex;
+        if (output.length >= limit) {
+          break;
+        }
+      }
+      if (separator.lastIndex === match.index) {
+        separator.lastIndex++; // Avoid an infinite loop
+      }
+    }
+    if (lastLastIndex === str.length) {
+      if (lastLength || !separator.test("")) {
+        output.push("");
+      }
+    } else {
+      output.push(str.slice(lastLastIndex));
+    }
+    return output.length > limit ? output.slice(0, limit) : output;
+  };
+
+  return self;
+})();
+
+var classIdSplit = /([\.#]?[a-zA-Z0-9\u007F-\uFFFF_:-]+)/;
+var notClassId = /^\.|#/;
+
+var parseTag_1 = parseTag;
+
+function parseTag(tag, props) {
+    if (!tag) {
+        return 'DIV';
+    }
+
+    var noId = !(props.hasOwnProperty('id'));
+
+    var tagParts = browserSplit(tag, classIdSplit);
+    var tagName = null;
+
+    if (notClassId.test(tagParts[1])) {
+        tagName = 'DIV';
+    }
+
+    var classes, part, type, i;
+
+    for (i = 0; i < tagParts.length; i++) {
+        part = tagParts[i];
+
+        if (!part) {
+            continue;
+        }
+
+        type = part.charAt(0);
+
+        if (!tagName) {
+            tagName = part;
+        } else if (type === '.') {
+            classes = classes || [];
+            classes.push(part.substring(1, part.length));
+        } else if (type === '#' && noId) {
+            props.id = part.substring(1, part.length);
+        }
+    }
+
+    if (classes) {
+        if (props.className) {
+            classes.push(props.className);
+        }
+
+        props.className = classes.join(' ');
+    }
+
+    return props.namespace ? tagName : tagName.toUpperCase();
+}
+
+var softSetHook = SoftSetHook;
+
+function SoftSetHook(value) {
+    if (!(this instanceof SoftSetHook)) {
+        return new SoftSetHook(value);
+    }
+
+    this.value = value;
+}
+
+SoftSetHook.prototype.hook = function (node, propertyName) {
+    if (node[propertyName] !== this.value) {
+        node[propertyName] = this.value;
+    }
+};
+
+/*global window, global*/
+
+var root = typeof window !== 'undefined' ?
+    window : typeof commonjsGlobal !== 'undefined' ?
+    commonjsGlobal : {};
+
+var individual = Individual;
+
+function Individual(key, value) {
+    if (key in root) {
+        return root[key];
+    }
+
+    root[key] = value;
+
+    return value;
+}
+
+var oneVersion = OneVersion;
+
+function OneVersion(moduleName, version, defaultValue) {
+    var key = '__INDIVIDUAL_ONE_VERSION_' + moduleName;
+    var enforceKey = key + '_ENFORCE_SINGLETON';
+
+    var versionValue = individual(enforceKey, version);
+
+    if (versionValue !== version) {
+        throw new Error('Can only have one copy of ' +
+            moduleName + '.\n' +
+            'You already have version ' + versionValue +
+            ' installed.\n' +
+            'This means you cannot install version ' + version);
+    }
+
+    return individual(key, defaultValue);
+}
+
+var MY_VERSION = '7';
+oneVersion('ev-store', MY_VERSION);
+
+var hashKey = '__EV_STORE_KEY@' + MY_VERSION;
+
+var evStore = EvStore;
+
+function EvStore(elem) {
+    var hash = elem[hashKey];
+
+    if (!hash) {
+        hash = elem[hashKey] = {};
+    }
+
+    return hash;
+}
+
+var evHook = EvHook;
+
+function EvHook(value) {
+    if (!(this instanceof EvHook)) {
+        return new EvHook(value);
+    }
+
+    this.value = value;
+}
+
+EvHook.prototype.hook = function (node, propertyName) {
+    var es = evStore(node);
+    var propName = propertyName.substr(3);
+
+    es[propName] = this.value;
+};
+
+EvHook.prototype.unhook = function(node, propertyName) {
+    var es = evStore(node);
+    var propName = propertyName.substr(3);
+
+    es[propName] = undefined;
+};
+
+var virtualHyperscript = h;
+
+function h(tagName, properties, children) {
+    var childNodes = [];
+    var tag, props, key, namespace;
+
+    if (!children && isChildren(properties)) {
+        children = properties;
+        props = {};
+    }
+
+    props = props || properties || {};
+    tag = parseTag_1(tagName, props);
+
+    // support keys
+    if (props.hasOwnProperty('key')) {
+        key = props.key;
+        props.key = undefined;
+    }
+
+    // support namespace
+    if (props.hasOwnProperty('namespace')) {
+        namespace = props.namespace;
+        props.namespace = undefined;
+    }
+
+    // fix cursor bug
+    if (tag === 'INPUT' &&
+        !namespace &&
+        props.hasOwnProperty('value') &&
+        props.value !== undefined &&
+        !isVhook(props.value)
+    ) {
+        props.value = softSetHook(props.value);
+    }
+
+    transformProperties(props);
+
+    if (children !== undefined && children !== null) {
+        addChild(children, childNodes, tag, props);
+    }
+
+
+    return new vnode(tag, props, childNodes, key, namespace);
+}
+
+function addChild(c, childNodes, tag, props) {
+    if (typeof c === 'string') {
+        childNodes.push(new vtext(c));
+    } else if (typeof c === 'number') {
+        childNodes.push(new vtext(String(c)));
+    } else if (isChild(c)) {
+        childNodes.push(c);
+    } else if (xIsArray(c)) {
+        for (var i = 0; i < c.length; i++) {
+            addChild(c[i], childNodes, tag, props);
+        }
+    } else if (c === null || c === undefined) {
+        return;
+    } else {
+        throw UnexpectedVirtualElement({
+            foreignObject: c,
+            parentVnode: {
+                tagName: tag,
+                properties: props
+            }
+        });
+    }
+}
+
+function transformProperties(props) {
+    for (var propName in props) {
+        if (props.hasOwnProperty(propName)) {
+            var value = props[propName];
+
+            if (isVhook(value)) {
+                continue;
+            }
+
+            if (propName.substr(0, 3) === 'ev-') {
+                // add ev-foo support
+                props[propName] = evHook(value);
+            }
+        }
+    }
+}
+
+function isChild(x) {
+    return isVnode(x) || isVtext(x) || isWidget_1(x) || isThunk_1(x);
+}
+
+function isChildren(x) {
+    return typeof x === 'string' || xIsArray(x) || isChild(x);
+}
+
+function UnexpectedVirtualElement(data) {
+    var err = new Error();
+
+    err.type = 'virtual-hyperscript.unexpected.virtual-element';
+    err.message = 'Unexpected virtual child passed to h().\n' +
+        'Expected a VNode / Vthunk / VWidget / string but:\n' +
+        'got:\n' +
+        errorString(data.foreignObject) +
+        '.\n' +
+        'The parent vnode is:\n' +
+        errorString(data.parentVnode);
+    err.foreignObject = data.foreignObject;
+    err.parentVnode = data.parentVnode;
+
+    return err;
+}
+
+function errorString(obj) {
+    try {
+        return JSON.stringify(obj, null, '    ');
+    } catch (e) {
+        return String(obj);
+    }
+}
+
+var h_1 = virtualHyperscript;
+
+var createElement_1 = createElement_1$1;
+
+var virtualDom = {
+    diff: diff_1,
+    patch: patch_1,
+    h: h_1,
+    create: createElement_1,
+    VNode: vnode,
+    VText: vtext
+};
+
+class EventEmitter {
+    constructor() { this._events = {}; }
+    /**
+     * Subscribe to an event by its name.
+     * @param {string} type - The name of the event
+     * to subscribe to.
+     * @param {(event: T) => void} handler - The
+     * handler called when the event occurs.
+     */
+    on(type, handler) {
+        this._events[type] = this._events[type] || [];
+        this._events[type].push(handler);
+    }
+    /**
+     * Unsubscribe from an event by its name.
+     * @param {string} type - The name of the event
+     * to unsubscribe from.
+     * @param {(event: T) => void} handler - The
+     * handler to remove.
+     */
+    off(type, handler) {
+        if (!type) {
+            this._events = {};
+            return;
+        }
+        if (this._listens(type)) {
+            const index = this._events[type].indexOf(handler);
+            if (index >= 0) {
+                this._events[type].splice(index, 1);
+            }
+            if (!this._events[type].length) {
+                delete this._events[type];
+            }
+        }
+    }
+    /**
+     * @ignore
+     */
+    fire(type, event) {
+        if (!this._listens(type)) {
+            return;
+        }
+        for (const handler of this._events[type]) {
+            handler(event);
+        }
+    }
+    _listens(eventType) {
+        return eventType in this._events;
+    }
+}
+
+class SubscriptionHolder {
+    constructor() {
+        this._subscriptions = [];
+    }
+    push(subscription) {
+        this._subscriptions.push(subscription);
+    }
+    unsubscribe() {
+        for (const sub of this._subscriptions) {
+            sub.unsubscribe();
+        }
+        this._subscriptions = [];
+    }
+}
+
+class Component extends EventEmitter {
+    constructor(name, container, navigator) {
+        super();
+        this._activated$ = new BehaviorSubject(false);
+        this._configurationSubject$ = new Subject();
+        this._activated = false;
+        this._container = container;
+        this._name = name;
+        this._navigator = navigator;
+        this._subscriptions = new SubscriptionHolder();
+        this._configuration$ =
+            this._configurationSubject$.pipe(startWith(this.defaultConfiguration), scan((conf, newConf) => {
+                for (let key in newConf) {
+                    if (newConf.hasOwnProperty(key)) {
+                        conf[key] = newConf[key];
+                    }
+                }
+                return conf;
+            }), publishReplay(1), refCount());
+        this._configuration$.subscribe(() => { });
+    }
+    /**
+     * Get activated.
+     *
+     * @returns {boolean} Value indicating if the component is
+     * currently active.
+     */
+    get activated() {
+        return this._activated;
+    }
+    /** @ignore */
+    get activated$() {
+        return this._activated$;
+    }
+    /**
+     * Get default configuration.
+     *
+     * @returns {TConfiguration} Default configuration for component.
+     */
+    get defaultConfiguration() {
+        return this._getDefaultConfiguration();
+    }
+    /** @ignore */
+    get configuration$() {
+        return this._configuration$;
+    }
+    /**
+     * Get name.
+     *
+     * @description The name of the component. Used when interacting with the
+     * component through the Viewer's API.
+     */
+    get name() {
+        return this._name;
+    }
+    /** @ignore */
+    activate(conf) {
+        if (this._activated) {
+            return;
+        }
+        if (conf !== undefined) {
+            this._configurationSubject$.next(conf);
+        }
+        this._activated = true;
+        this._activate();
+        this._activated$.next(true);
+    }
+    /**
+     * Configure the component.
+     *
+     * @param configuration Component configuration.
+     */
+    configure(configuration) {
+        this._configurationSubject$.next(configuration);
+    }
+    /** @ignore */
+    deactivate() {
+        if (!this._activated) {
+            return;
+        }
+        this._activated = false;
+        this._deactivate();
+        this._container.domRenderer.clear(this._name);
+        this._container.glRenderer.clear(this._name);
+        this._activated$.next(false);
+    }
+    /** @inheritdoc */
+    fire(type, event) {
+        super.fire(type, event);
+    }
+    /** @inheritdoc */
+    off(type, handler) {
+        super.off(type, handler);
+    }
+    /** @inheritdoc */
+    on(type, handler) {
+        super.on(type, handler);
+    }
+    /**
+     * Detect the viewer's new width and height and resize the component's
+     * rendered elements accordingly if applicable.
+     *
+     * @ignore
+     */
+    resize() { return; }
+}
+
+var CoverState;
+(function (CoverState) {
+    CoverState[CoverState["Hidden"] = 0] = "Hidden";
+    CoverState[CoverState["Loading"] = 1] = "Loading";
+    CoverState[CoverState["Visible"] = 2] = "Visible";
+})(CoverState || (CoverState = {}));
+
+class CoverComponent extends Component {
+    constructor(name, container, navigator) {
+        super(name, container, navigator);
+    }
+    _activate() {
+        const originalSrc$ = this.configuration$.pipe(first((c) => {
+            return !!c.id;
+        }), filter((c) => {
+            return !c.src;
+        }), switchMap((c) => {
+            return this._getImageSrc$(c.id).pipe(catchError((error) => {
+                console.error(error);
+                return empty();
+            }));
+        }), publishReplay(1), refCount());
+        const subs = this._subscriptions;
+        subs.push(originalSrc$.pipe(map((src) => {
+            return { src: src };
+        }))
+            .subscribe((c) => {
+            this._configurationSubject$.next(c);
+        }));
+        subs.push(combineLatest(this.configuration$, originalSrc$).pipe(filter(([c, src]) => {
+            return !!c.src && c.src !== src;
+        }), first())
+            .subscribe(([, src]) => {
+            window.URL.revokeObjectURL(src);
+        }));
+        subs.push(this._configuration$.pipe(distinctUntilChanged(undefined, (configuration) => {
+            return configuration.state;
+        }), switchMap((configuration) => {
+            return combineLatest(of(configuration.state), this._navigator.stateService.currentImage$);
+        }), switchMap(([state, image]) => {
+            const keySrc$ = combineLatest(of(image.id), image.image$.pipe(filter((imageElement) => {
+                return !!imageElement;
+            }), map((imageElement) => {
+                return imageElement.src;
+            })));
+            return state === CoverState.Visible ? keySrc$.pipe(first()) : keySrc$;
+        }), distinctUntilChanged(([k1, s1], [k2, s2]) => {
+            return k1 === k2 && s1 === s2;
+        }), map(([key, src]) => {
+            return { id: key, src: src };
+        }))
+            .subscribe(this._configurationSubject$));
+        subs.push(combineLatest(this._configuration$, this._container.configurationService.exploreUrl$, this._container.renderService.size$).pipe(map(([configuration, exploreUrl, size]) => {
+            if (!configuration.src) {
+                return { name: this._name, vNode: virtualDom.h("div", []) };
+            }
+            const compactClass = size.width <= 640 || size.height <= 480 ? ".mapillary-cover-compact" : "";
+            if (configuration.state === CoverState.Hidden) {
+                const doneContainer = virtualDom.h("div.mapillary-cover-container.mapillary-cover-done" + compactClass, [this._getCoverBackgroundVNode(configuration)]);
+                return { name: this._name, vNode: doneContainer };
+            }
+            const container = virtualDom.h("div.mapillary-cover-container" + compactClass, [this._getCoverButtonVNode(configuration, exploreUrl)]);
+            return { name: this._name, vNode: container };
+        }))
+            .subscribe(this._container.domRenderer.render$));
+    }
+    _deactivate() {
+        this._subscriptions.unsubscribe();
+    }
+    _getDefaultConfiguration() {
+        return { state: CoverState.Visible };
+    }
+    _getCoverButtonVNode(configuration, exploreUrl) {
+        const cover = configuration.state === CoverState.Loading ? "div.mapillary-cover.mapillary-cover-loading" : "div.mapillary-cover";
+        const coverButton = virtualDom.h("div.mapillary-cover-button", [virtualDom.h("div.mapillary-cover-button-icon", [])]);
+        const coverLogo = virtualDom.h("a.mapillary-cover-logo", { href: exploreUrl, target: "_blank" }, []);
+        const coverIndicator = virtualDom.h("div.mapillary-cover-indicator", { onclick: () => { this.configure({ state: CoverState.Loading }); } }, []);
+        return virtualDom.h(cover, [
+            this._getCoverBackgroundVNode(configuration),
+            coverIndicator,
+            coverButton,
+            coverLogo,
+        ]);
+    }
+    _getCoverBackgroundVNode(conf) {
+        const properties = {
+            style: { backgroundImage: `url(${conf.src})` },
+        };
+        const children = [];
+        if (conf.state === CoverState.Loading) {
+            children.push(virtualDom.h("div.mapillary-cover-spinner", {}, []));
+        }
+        return virtualDom.h("div.mapillary-cover-background", properties, children);
+    }
+    _getImageSrc$(id) {
+        return Observable.create((subscriber) => {
+            this._navigator.api.getImages$([id])
+                .subscribe((items) => {
+                for (const item of items) {
+                    if (item.node_id !== id) {
+                        continue;
+                    }
+                    this._navigator.api.data
+                        .getImageBuffer(item.node.thumb.url)
+                        .then((buffer) => {
+                        const image = new Image();
+                        image.crossOrigin = "Anonymous";
+                        image.onload = () => {
+                            subscriber.next(image.src);
+                            subscriber.complete();
+                        };
+                        image.onerror = () => {
+                            subscriber.error(new Error(`Failed to load cover ` +
+                                `image (${id})`));
+                        };
+                        const blob = new Blob([buffer]);
+                        image.src = window.URL
+                            .createObjectURL(blob);
+                    }, (error) => {
+                        subscriber.error(error);
+                    });
+                    return;
+                }
+                subscriber.error(new MapillaryError(`Non existent cover key: ${id}`));
+            }, (error) => {
+                subscriber.error(error);
+            });
+        });
+    }
+}
+CoverComponent.componentName = "cover";
+
+class AttributionComponent extends Component {
+    _activate() {
+        this._subscriptions.push(combineLatest(this._container.configurationService.exploreUrl$, this._navigator.stateService.currentImage$, this._container.renderService.size$).pipe(map(([exploreUrl, image, size]) => {
+            const attribution = this._makeAttribution(image.creatorUsername, exploreUrl, image.id, image.capturedAt, size.width);
+            return {
+                name: this._name,
+                vNode: attribution,
+            };
+        }))
+            .subscribe(this._container.domRenderer.render$));
+    }
+    _deactivate() {
+        this._subscriptions.unsubscribe();
+    }
+    _getDefaultConfiguration() {
+        return {};
+    }
+    makeImageUrl(exploreUrl, id) {
+        return `${exploreUrl}/app/?pKey=${id}&focus=photo`;
+    }
+    _makeAttribution(creatorUsername, exploreUrl, imageId, capturedAt, viewportWidth) {
+        const compact = viewportWidth <= 640;
+        const date = this._makeDate(capturedAt, compact);
+        const by = this._makeBy(creatorUsername, exploreUrl, imageId, compact);
+        const compactClass = compact ?
+            ".mapillary-attribution-compact" : "";
+        return virtualDom.h("div.mapillary-attribution-container" + compactClass, {}, [...by, date]);
+    }
+    _makeBy(creatorUsername, exploreUrl, imageId, compact) {
+        const icon = virtualDom.h("div.mapillary-attribution-logo", []);
+        return creatorUsername ?
+            this._makeCreatorBy(icon, creatorUsername, exploreUrl, imageId, compact) :
+            this._makeGeneralBy(icon, exploreUrl, imageId, compact);
+    }
+    _makeCreatorBy(icon, creatorUsername, exploreUrl, imageId, compact) {
+        const mapillary = virtualDom.h("a.mapillary-attribution-icon-container", { href: exploreUrl, rel: "noreferrer", target: "_blank" }, [icon]);
+        const content = compact ?
+            `${creatorUsername}` : `image by ${creatorUsername}`;
+        const imageBy = virtualDom.h("div.mapillary-attribution-username", { textContent: content }, []);
+        const image = virtualDom.h("a.mapillary-attribution-image-container", {
+            href: this.makeImageUrl(exploreUrl, imageId),
+            rel: "noreferrer",
+            target: "_blank",
+        }, [imageBy]);
+        return [mapillary, image];
+    }
+    _makeGeneralBy(icon, exploreUrl, imageId, compact) {
+        const imagesBy = virtualDom.h("div.mapillary-attribution-username", { textContent: 'images by' }, []);
+        const mapillary = virtualDom.h("div.mapillary-attribution-icon-container", {}, [icon]);
+        const contributors = virtualDom.h("div.mapillary-attribution-username", { textContent: 'contributors' }, []);
+        const children = [mapillary, contributors];
+        if (!compact) {
+            children.unshift(imagesBy);
+        }
+        const image = virtualDom.h("a.mapillary-attribution-image-container", {
+            href: this.makeImageUrl(exploreUrl, imageId),
+            rel: "noreferrer",
+            target: "_blank",
+        }, children);
+        return [image];
+    }
+    _makeDate(capturedAt, compact) {
+        const date = new Date(capturedAt)
+            .toDateString()
+            .split(" ");
+        const formatted = (date.length > 3 ?
+            compact ?
+                [date[3]] :
+                [date[1], date[2] + ",", date[3]] :
+            date).join(" ");
+        return virtualDom.h("div.mapillary-attribution-date", { textContent: formatted }, []);
+    }
+}
+AttributionComponent.componentName = "attribution";
+
+/**
+ * @class ViewportCoords
+ *
+ * @classdesc Provides methods for calculating 2D coordinate conversions
+ * as well as 3D projection and unprojection.
+ *
+ * Basic coordinates are 2D coordinates on the [0, 1] interval and
+ * have the origin point, (0, 0), at the top left corner and the
+ * maximum value, (1, 1), at the bottom right corner of the original
+ * image.
+ *
+ * Viewport coordinates are 2D coordinates on the [-1, 1] interval and
+ * have the origin point in the center. The bottom left corner point is
+ * (-1, -1) and the top right corner point is (1, 1).
+ *
+ * Canvas coordiantes are 2D pixel coordinates on the [0, canvasWidth] and
+ * [0, canvasHeight] intervals. The origin point (0, 0) is in the top left
+ * corner and the maximum value is (canvasWidth, canvasHeight) is in the
+ * bottom right corner.
+ *
+ * 3D coordinates are in the topocentric world reference frame.
+ */
+class ViewportCoords {
+    constructor() {
+        this._unprojectDepth = 200;
+    }
+    /**
+     * Convert basic coordinates to canvas coordinates.
+     *
+     * @description Transform origin and camera position needs to be the
+     * equal for reliable return value.
+     *
+     * @param {number} basicX - Basic X coordinate.
+     * @param {number} basicY - Basic Y coordinate.
+     * @param {HTMLElement} container - The viewer container.
+     * @param {Transform} transform - Transform of the image to unproject from.
+     * @param {THREE.Camera} camera - Camera used in rendering.
+     * @returns {Array<number>} 2D canvas coordinates.
+     */
+    basicToCanvas(basicX, basicY, container, transform, camera) {
+        const point3d = transform.unprojectBasic([basicX, basicY], this._unprojectDepth);
+        const canvas = this.projectToCanvas(point3d, container, camera);
+        return canvas;
+    }
+    /**
+     * Convert basic coordinates to canvas coordinates safely. If 3D point is
+     * behind camera null will be returned.
+     *
+     * @description Transform origin and camera position needs to be the
+     * equal for reliable return value.
+     *
+     * @param {number} basicX - Basic X coordinate.
+     * @param {number} basicY - Basic Y coordinate.
+     * @param {HTMLElement} container - The viewer container.
+     * @param {Transform} transform - Transform of the image to unproject from.
+     * @param {THREE.Camera} camera - Camera used in rendering.
+     * @returns {Array<number>} 2D canvas coordinates if the basic point represents a 3D point
+     * in front of the camera, otherwise null.
+     */
+    basicToCanvasSafe(basicX, basicY, container, transform, camera) {
+        const viewport = this.basicToViewportSafe(basicX, basicY, transform, camera);
+        if (viewport === null) {
+            return null;
+        }
+        const canvas = this.viewportToCanvas(viewport[0], viewport[1], container);
+        return canvas;
+    }
+    /**
+     * Convert basic coordinates to viewport coordinates.
+     *
+     * @description Transform origin and camera position needs to be the
+     * equal for reliable return value.
+     *
+     * @param {number} basicX - Basic X coordinate.
+     * @param {number} basicY - Basic Y coordinate.
+     * @param {Transform} transform - Transform of the image to unproject from.
+     * @param {THREE.Camera} camera - Camera used in rendering.
+     * @returns {Array<number>} 2D viewport coordinates.
+     */
+    basicToViewport(basicX, basicY, transform, camera) {
+        const point3d = transform.unprojectBasic([basicX, basicY], this._unprojectDepth);
+        const viewport = this.projectToViewport(point3d, camera);
+        return viewport;
+    }
+    /**
+     * Convert basic coordinates to viewport coordinates safely. If 3D point is
+     * behind camera null will be returned.
+     *
+     * @description Transform origin and camera position needs to be the
+     * equal for reliable return value.
+     *
+     * @param {number} basicX - Basic X coordinate.
+     * @param {number} basicY - Basic Y coordinate.
+     * @param {Transform} transform - Transform of the image to unproject from.
+     * @param {THREE.Camera} camera - Camera used in rendering.
+     * @returns {Array<number>} 2D viewport coordinates.
+     */
+    basicToViewportSafe(basicX, basicY, transform, camera) {
+        const point3d = transform.unprojectBasic([basicX, basicY], this._unprojectDepth);
+        const pointCamera = this.worldToCamera(point3d, camera);
+        if (pointCamera[2] > 0) {
+            return null;
+        }
+        const viewport = this.projectToViewport(point3d, camera);
+        return viewport;
+    }
+    /**
+     * Convert camera 3D coordinates to viewport coordinates.
+     *
+     * @param {number} pointCamera - 3D point in camera coordinate system.
+     * @param {THREE.Camera} camera - Camera used in rendering.
+     * @returns {Array<number>} 2D viewport coordinates.
+     */
+    cameraToViewport(pointCamera, camera) {
+        const viewport = new Vector3().fromArray(pointCamera)
+            .applyMatrix4(camera.projectionMatrix);
+        return [viewport.x, viewport.y];
+    }
+    /**
+     * Get canvas pixel position from event.
+     *
+     * @param {Event} event - Event containing clientX and clientY properties.
+     * @param {HTMLElement} element - HTML element.
+     * @returns {Array<number>} 2D canvas coordinates.
+     */
+    canvasPosition(event, element) {
+        const clientRect = element.getBoundingClientRect();
+        const canvasX = event.clientX - clientRect.left - element.clientLeft;
+        const canvasY = event.clientY - clientRect.top - element.clientTop;
+        return [canvasX, canvasY];
+    }
+    /**
+     * Convert canvas coordinates to basic coordinates.
+     *
+     * @description Transform origin and camera position needs to be the
+     * equal for reliable return value.
+     *
+     * @param {number} canvasX - Canvas X coordinate.
+     * @param {number} canvasY - Canvas Y coordinate.
+     * @param {HTMLElement} container - The viewer container.
+     * @param {Transform} transform - Transform of the image to unproject from.
+     * @param {THREE.Camera} camera - Camera used in rendering.
+     * @returns {Array<number>} 2D basic coordinates.
+     */
+    canvasToBasic(canvasX, canvasY, container, transform, camera) {
+        const point3d = this.unprojectFromCanvas(canvasX, canvasY, container, camera)
+            .toArray();
+        const basic = transform.projectBasic(point3d);
+        return basic;
+    }
+    /**
+     * Convert canvas coordinates to viewport coordinates.
+     *
+     * @param {number} canvasX - Canvas X coordinate.
+     * @param {number} canvasY - Canvas Y coordinate.
+     * @param {HTMLElement} container - The viewer container.
+     * @returns {Array<number>} 2D viewport coordinates.
+     */
+    canvasToViewport(canvasX, canvasY, container) {
+        const [canvasWidth, canvasHeight] = this.containerToCanvas(container);
+        const viewportX = 2 * canvasX / canvasWidth - 1;
+        const viewportY = 1 - 2 * canvasY / canvasHeight;
+        return [viewportX, viewportY];
+    }
+    /**
+     * Determines the width and height of the container in canvas coordinates.
+     *
+     * @param {HTMLElement} container - The viewer container.
+     * @returns {Array<number>} 2D canvas coordinates.
+     */
+    containerToCanvas(container) {
+        return [container.offsetWidth, container.offsetHeight];
+    }
+    /**
+     * Determine basic distances from image to canvas corners.
+     *
+     * @description Transform origin and camera position needs to be the
+     * equal for reliable return value.
+     *
+     * Determines the smallest basic distance for every side of the canvas.
+     *
+     * @param {Transform} transform - Transform of the image to unproject from.
+     * @param {THREE.Camera} camera - Camera used in rendering.
+     * @returns {Array<number>} Array of basic distances as [top, right, bottom, left].
+     */
+    getBasicDistances(transform, camera) {
+        const topLeftBasic = this.viewportToBasic(-1, 1, transform, camera);
+        const topRightBasic = this.viewportToBasic(1, 1, transform, camera);
+        const bottomRightBasic = this.viewportToBasic(1, -1, transform, camera);
+        const bottomLeftBasic = this.viewportToBasic(-1, -1, transform, camera);
+        let topBasicDistance = 0;
+        let rightBasicDistance = 0;
+        let bottomBasicDistance = 0;
+        let leftBasicDistance = 0;
+        if (topLeftBasic[1] < 0 && topRightBasic[1] < 0) {
+            topBasicDistance = topLeftBasic[1] > topRightBasic[1] ?
+                -topLeftBasic[1] :
+                -topRightBasic[1];
+        }
+        if (topRightBasic[0] > 1 && bottomRightBasic[0] > 1) {
+            rightBasicDistance = topRightBasic[0] < bottomRightBasic[0] ?
+                topRightBasic[0] - 1 :
+                bottomRightBasic[0] - 1;
+        }
+        if (bottomRightBasic[1] > 1 && bottomLeftBasic[1] > 1) {
+            bottomBasicDistance = bottomRightBasic[1] < bottomLeftBasic[1] ?
+                bottomRightBasic[1] - 1 :
+                bottomLeftBasic[1] - 1;
+        }
+        if (bottomLeftBasic[0] < 0 && topLeftBasic[0] < 0) {
+            leftBasicDistance = bottomLeftBasic[0] > topLeftBasic[0] ?
+                -bottomLeftBasic[0] :
+                -topLeftBasic[0];
+        }
+        return [topBasicDistance, rightBasicDistance, bottomBasicDistance, leftBasicDistance];
+    }
+    /**
+     * Determine pixel distances from image to canvas corners.
+     *
+     * @description Transform origin and camera position needs to be the
+     * equal for reliable return value.
+     *
+     * Determines the smallest pixel distance for every side of the canvas.
+     *
+     * @param {HTMLElement} container - The viewer container.
+     * @param {Transform} transform - Transform of the image to unproject from.
+     * @param {THREE.Camera} camera - Camera used in rendering.
+     * @returns {Array<number>} Array of pixel distances as [top, right, bottom, left].
+     */
+    getPixelDistances(container, transform, camera) {
+        const topLeftBasic = this.viewportToBasic(-1, 1, transform, camera);
+        const topRightBasic = this.viewportToBasic(1, 1, transform, camera);
+        const bottomRightBasic = this.viewportToBasic(1, -1, transform, camera);
+        const bottomLeftBasic = this.viewportToBasic(-1, -1, transform, camera);
+        let topPixelDistance = 0;
+        let rightPixelDistance = 0;
+        let bottomPixelDistance = 0;
+        let leftPixelDistance = 0;
+        const [canvasWidth, canvasHeight] = this.containerToCanvas(container);
+        if (topLeftBasic[1] < 0 && topRightBasic[1] < 0) {
+            const basicX = topLeftBasic[1] > topRightBasic[1] ?
+                topLeftBasic[0] :
+                topRightBasic[0];
+            const canvas = this.basicToCanvas(basicX, 0, container, transform, camera);
+            topPixelDistance = canvas[1] > 0 ? canvas[1] : 0;
+        }
+        if (topRightBasic[0] > 1 && bottomRightBasic[0] > 1) {
+            const basicY = topRightBasic[0] < bottomRightBasic[0] ?
+                topRightBasic[1] :
+                bottomRightBasic[1];
+            const canvas = this.basicToCanvas(1, basicY, container, transform, camera);
+            rightPixelDistance = canvas[0] < canvasWidth ? canvasWidth - canvas[0] : 0;
+        }
+        if (bottomRightBasic[1] > 1 && bottomLeftBasic[1] > 1) {
+            const basicX = bottomRightBasic[1] < bottomLeftBasic[1] ?
+                bottomRightBasic[0] :
+                bottomLeftBasic[0];
+            const canvas = this.basicToCanvas(basicX, 1, container, transform, camera);
+            bottomPixelDistance = canvas[1] < canvasHeight ? canvasHeight - canvas[1] : 0;
+        }
+        if (bottomLeftBasic[0] < 0 && topLeftBasic[0] < 0) {
+            const basicY = bottomLeftBasic[0] > topLeftBasic[0] ?
+                bottomLeftBasic[1] :
+                topLeftBasic[1];
+            const canvas = this.basicToCanvas(0, basicY, container, transform, camera);
+            leftPixelDistance = canvas[0] > 0 ? canvas[0] : 0;
+        }
+        return [topPixelDistance, rightPixelDistance, bottomPixelDistance, leftPixelDistance];
+    }
+    /**
+     * Determine if an event occured inside an element.
+     *
+     * @param {Event} event - Event containing clientX and clientY properties.
+     * @param {HTMLElement} element - HTML element.
+     * @returns {boolean} Value indicating if the event occured inside the element or not.
+     */
+    insideElement(event, element) {
+        const clientRect = element.getBoundingClientRect();
+        const minX = clientRect.left + element.clientLeft;
+        const maxX = minX + element.clientWidth;
+        const minY = clientRect.top + element.clientTop;
+        const maxY = minY + element.clientHeight;
+        return event.clientX > minX &&
+            event.clientX < maxX &&
+            event.clientY > minY &&
+            event.clientY < maxY;
+    }
+    /**
+     * Project 3D world coordinates to canvas coordinates.
+     *
+     * @param {Array<number>} point3D - 3D world coordinates.
+     * @param {HTMLElement} container - The viewer container.
+     * @param {THREE.Camera} camera - Camera used in rendering.
+     * @returns {Array<number>} 2D canvas coordinates.
+     */
+    projectToCanvas(point3d, container, camera) {
+        const viewport = this.projectToViewport(point3d, camera);
+        const canvas = this.viewportToCanvas(viewport[0], viewport[1], container);
+        return canvas;
+    }
+    /**
+     * Project 3D world coordinates to canvas coordinates safely. If 3D
+     * point is behind camera null will be returned.
+     *
+     * @param {Array<number>} point3D - 3D world coordinates.
+     * @param {HTMLElement} container - The viewer container.
+     * @param {THREE.Camera} camera - Camera used in rendering.
+     * @returns {Array<number>} 2D canvas coordinates.
+     */
+    projectToCanvasSafe(point3d, container, camera) {
+        const pointCamera = this.worldToCamera(point3d, camera);
+        if (pointCamera[2] > 0) {
+            return null;
+        }
+        const viewport = this.projectToViewport(point3d, camera);
+        const canvas = this.viewportToCanvas(viewport[0], viewport[1], container);
+        return canvas;
+    }
+    /**
+     * Project 3D world coordinates to viewport coordinates.
+     *
+     * @param {Array<number>} point3D - 3D world coordinates.
+     * @param {THREE.Camera} camera - Camera used in rendering.
+     * @returns {Array<number>} 2D viewport coordinates.
+     */
+    projectToViewport(point3d, camera) {
+        const viewport = new Vector3(point3d[0], point3d[1], point3d[2])
+            .project(camera);
+        return [viewport.x, viewport.y];
+    }
+    /**
+     * Uproject canvas coordinates to 3D world coordinates.
+     *
+     * @param {number} canvasX - Canvas X coordinate.
+     * @param {number} canvasY - Canvas Y coordinate.
+     * @param {HTMLElement} container - The viewer container.
+     * @param {THREE.Camera} camera - Camera used in rendering.
+     * @returns {Array<number>} 3D world coordinates.
+     */
+    unprojectFromCanvas(canvasX, canvasY, container, camera) {
+        const viewport = this.canvasToViewport(canvasX, canvasY, container);
+        const point3d = this.unprojectFromViewport(viewport[0], viewport[1], camera);
+        return point3d;
+    }
+    /**
+     * Unproject viewport coordinates to 3D world coordinates.
+     *
+     * @param {number} viewportX - Viewport X coordinate.
+     * @param {number} viewportY - Viewport Y coordinate.
+     * @param {THREE.Camera} camera - Camera used in rendering.
+     * @returns {Array<number>} 3D world coordinates.
+     */
+    unprojectFromViewport(viewportX, viewportY, camera) {
+        const point3d = new Vector3(viewportX, viewportY, 1)
+            .unproject(camera);
+        return point3d;
+    }
+    /**
+     * Convert viewport coordinates to basic coordinates.
+     *
+     * @description Transform origin and camera position needs to be the
+     * equal for reliable return value.
+     *
+     * @param {number} viewportX - Viewport X coordinate.
+     * @param {number} viewportY - Viewport Y coordinate.
+     * @param {Transform} transform - Transform of the image to unproject from.
+     * @param {THREE.Camera} camera - Camera used in rendering.
+     * @returns {Array<number>} 2D basic coordinates.
+     */
+    viewportToBasic(viewportX, viewportY, transform, camera) {
+        const point3d = new Vector3(viewportX, viewportY, 1)
+            .unproject(camera)
+            .toArray();
+        const basic = transform.projectBasic(point3d);
+        return basic;
+    }
+    /**
+     * Convert viewport coordinates to canvas coordinates.
+     *
+     * @param {number} viewportX - Viewport X coordinate.
+     * @param {number} viewportY - Viewport Y coordinate.
+     * @param {HTMLElement} container - The viewer container.
+     * @returns {Array<number>} 2D canvas coordinates.
+     */
+    viewportToCanvas(viewportX, viewportY, container) {
+        const [canvasWidth, canvasHeight] = this.containerToCanvas(container);
+        const canvasX = canvasWidth * (viewportX + 1) / 2;
+        const canvasY = -canvasHeight * (viewportY - 1) / 2;
+        return [canvasX, canvasY];
+    }
+    /**
+     * Convert 3D world coordinates to 3D camera coordinates.
+     *
+     * @param {number} point3D - 3D point in world coordinate system.
+     * @param {THREE.Camera} camera - Camera used in rendering.
+     * @returns {Array<number>} 3D camera coordinates.
+     */
+    worldToCamera(point3d, camera) {
+        const pointCamera = new Vector3(point3d[0], point3d[1], point3d[2])
+            .applyMatrix4(camera.matrixWorldInverse);
+        return pointCamera.toArray();
+    }
+}
+
+/**
+ * Enumeration for component size.
+ * @enum {number}
+ * @readonly
+ * @description May be used by a component to allow for resizing
+ * of the UI elements rendered by the component.
+ */
+var ComponentSize;
+(function (ComponentSize) {
+    /**
+     * Automatic size. The size of the elements will automatically
+     * change at a predefined threshold.
+     */
+    ComponentSize[ComponentSize["Automatic"] = 0] = "Automatic";
+    /**
+     * Large size. The size of the elements will be fixed until another
+     * component size is configured.
+     */
+    ComponentSize[ComponentSize["Large"] = 1] = "Large";
+    /**
+     * Small size. The size of the elements will be fixed until another
+     * component size is configured.
+     */
+    ComponentSize[ComponentSize["Small"] = 2] = "Small";
+})(ComponentSize || (ComponentSize = {}));
+
+/**
+ * @class BearingComponent
+ *
+ * @classdesc Component for indicating bearing and field of view.
+ *
+ * @example
+ * ```js
+ * var viewer = new Viewer({ ... });
+ * var bearingComponent = viewer.getComponent("bearing");
+ * bearingComponent.configure({ size: ComponentSize.Small });
+ * ```
+ */
+class BearingComponent extends Component {
+    /** @ignore */
+    constructor(name, container, navigator) {
+        super(name, container, navigator);
+        this._spatial = new Spatial();
+        this._viewportCoords = new ViewportCoords();
+        this._svgNamespace = "http://www.w3.org/2000/svg";
+        this._distinctThreshold = Math.PI / 360;
+        this._animationSpeed = 0.075;
+        this._unitBezier = new unitbezier(0.74, 0.67, 0.38, 0.96);
+    }
+    _activate() {
+        const subs = this._subscriptions;
+        const cameraBearingFov$ = this._container.renderService.renderCamera$.pipe(map((rc) => {
+            let vFov = this._spatial.degToRad(rc.perspective.fov);
+            let hFov = rc.perspective.aspect === Number.POSITIVE_INFINITY ?
+                Math.PI :
+                Math.atan(rc.perspective.aspect * Math.tan(0.5 * vFov)) * 2;
+            return [this._spatial.azimuthalToBearing(rc.rotation.phi), hFov];
+        }), distinctUntilChanged((a1, a2) => {
+            return Math.abs(a2[0] - a1[0]) < this._distinctThreshold &&
+                Math.abs(a2[1] - a1[1]) < this._distinctThreshold;
+        }));
+        const imageFov$ = combineLatest(this._navigator.stateService.currentState$.pipe(distinctUntilChanged(undefined, (frame) => {
+            return frame.state.currentImage.id;
+        })), this._navigator.panService.panImages$).pipe(map(([frame, panImages]) => {
+            const image = frame.state.currentImage;
+            const transform = frame.state.currentTransform;
+            if (isSpherical(image.cameraType)) {
+                return [Math.PI, Math.PI];
+            }
+            const currentProjectedPoints = this._computeProjectedPoints(transform);
+            const hFov = this._spatial
+                .degToRad(this._computeHorizontalFov(currentProjectedPoints));
+            let hFovLeft = hFov / 2;
+            let hFovRight = hFov / 2;
+            for (const [n, , f] of panImages) {
+                const diff = this._spatial.wrap(n.compassAngle - image.compassAngle, -180, 180);
+                if (diff < 0) {
+                    hFovLeft = this._spatial.degToRad(Math.abs(diff)) + f / 2;
+                }
+                else {
+                    hFovRight = this._spatial.degToRad(Math.abs(diff)) + f / 2;
+                }
+            }
+            return [hFovLeft, hFovRight];
+        }), distinctUntilChanged(([hFovLeft1, hFovRight1], [hFovLeft2, hFovRight2]) => {
+            return Math.abs(hFovLeft2 - hFovLeft1) < this._distinctThreshold &&
+                Math.abs(hFovRight2 - hFovRight1) < this._distinctThreshold;
+        }));
+        const offset$ = combineLatest(this._navigator.stateService.currentState$.pipe(distinctUntilChanged(undefined, (frame) => {
+            return frame.state.currentImage.id;
+        })), this._container.renderService.bearing$).pipe(map(([frame, bearing]) => {
+            const offset = this._spatial.degToRad(frame.state.currentImage.compassAngle - bearing);
+            return offset;
+        }));
+        const imageFovOperation$ = new Subject();
+        const smoothImageFov$ = imageFovOperation$.pipe(scan((state, operation) => {
+            return operation(state);
+        }, { alpha: 0, curr: [0, 0, 0], prev: [0, 0, 0] }), map((state) => {
+            const alpha = this._unitBezier.solve(state.alpha);
+            const curr = state.curr;
+            const prev = state.prev;
+            return [
+                this._interpolate(prev[0], curr[0], alpha),
+                this._interpolate(prev[1], curr[1], alpha),
+            ];
+        }));
+        subs.push(imageFov$.pipe(map((nbf) => {
+            return (state) => {
+                const a = this._unitBezier.solve(state.alpha);
+                const c = state.curr;
+                const p = state.prev;
+                const prev = [
+                    this._interpolate(p[0], c[0], a),
+                    this._interpolate(p[1], c[1], a),
+                ];
+                const curr = nbf.slice();
+                return {
+                    alpha: 0,
+                    curr: curr,
+                    prev: prev,
+                };
+            };
+        }))
+            .subscribe(imageFovOperation$));
+        subs.push(imageFov$.pipe(switchMap(() => {
+            return this._container.renderService.renderCameraFrame$.pipe(skip(1), scan((alpha) => {
+                return alpha + this._animationSpeed;
+            }, 0), takeWhile((alpha) => {
+                return alpha <= 1 + this._animationSpeed;
+            }), map((alpha) => {
+                return Math.min(alpha, 1);
+            }));
+        }), map((alpha) => {
+            return (nbfState) => {
+                return {
+                    alpha: alpha,
+                    curr: nbfState.curr.slice(),
+                    prev: nbfState.prev.slice(),
+                };
+            };
+        }))
+            .subscribe(imageFovOperation$));
+        const imageBearingFov$ = combineLatest(offset$, smoothImageFov$).pipe(map(([offset, fov]) => {
+            return [offset, fov[0], fov[1]];
+        }));
+        subs.push(combineLatest(cameraBearingFov$, imageBearingFov$, this._configuration$, this._container.renderService.size$).pipe(map(([[cb, cf], [no, nfl, nfr], configuration, size]) => {
+            const background = this._createBackground(cb);
+            const fovIndicator = this._createFovIndicator(nfl, nfr, no);
+            const north = this._createNorth(cb);
+            const cameraSector = this._createCircleSectorCompass(this._createCircleSector(Math.max(Math.PI / 20, cf), "#FFF"));
+            const compact = configuration.size === ComponentSize.Small ||
+                configuration.size === ComponentSize.Automatic && size.width < 640 ?
+                ".mapillary-bearing-compact" : "";
+            return {
+                name: this._name,
+                vNode: virtualDom.h("div.mapillary-bearing-indicator-container" + compact, { oncontextmenu: (event) => { event.preventDefault(); } }, [
+                    background,
+                    fovIndicator,
+                    north,
+                    cameraSector,
+                ]),
+            };
+        }))
+            .subscribe(this._container.domRenderer.render$));
+    }
+    _deactivate() {
+        this._subscriptions.unsubscribe();
+    }
+    _getDefaultConfiguration() {
+        return { size: ComponentSize.Automatic };
+    }
+    _createFovIndicator(fovLeft, fovRigth, offset) {
+        const arc = this._createFovArc(fovLeft, fovRigth);
+        const group = virtualDom.h("g", {
+            attributes: { transform: "translate(18,18)" },
+            namespace: this._svgNamespace,
+        }, [arc]);
+        const svg = virtualDom.h("svg", {
+            attributes: { viewBox: "0 0 36 36" },
+            namespace: this._svgNamespace,
+            style: {
+                height: "36px",
+                left: "2px",
+                position: "absolute",
+                top: "2px",
+                transform: `rotateZ(${this._spatial.radToDeg(offset)}deg)`,
+                width: "36px",
+            },
+        }, [group]);
+        return svg;
+    }
+    _createFovArc(fovLeft, fovRigth) {
+        const radius = 16.75;
+        const strokeWidth = 2.5;
+        const fov = fovLeft + fovRigth;
+        if (fov > 2 * Math.PI - Math.PI / 90) {
+            return virtualDom.h("circle", {
+                attributes: {
+                    cx: "0",
+                    cy: "0",
+                    "fill-opacity": "0",
+                    r: `${radius}`,
+                    stroke: "#FFF",
+                    "stroke-width": `${strokeWidth}`,
+                },
+                namespace: this._svgNamespace,
+            }, []);
+        }
+        let arcStart = -Math.PI / 2 - fovLeft;
+        let arcEnd = arcStart + fov;
+        let startX = radius * Math.cos(arcStart);
+        let startY = radius * Math.sin(arcStart);
+        let endX = radius * Math.cos(arcEnd);
+        let endY = radius * Math.sin(arcEnd);
+        let largeArc = fov >= Math.PI ? 1 : 0;
+        let description = `M ${startX} ${startY} A ${radius} ${radius} 0 ${largeArc} 1 ${endX} ${endY}`;
+        return virtualDom.h("path", {
+            attributes: {
+                d: description,
+                "fill-opacity": "0",
+                stroke: "#FFF",
+                "stroke-width": `${strokeWidth}`,
+            },
+            namespace: this._svgNamespace,
+        }, []);
+    }
+    _createCircleSectorCompass(cameraSector) {
+        let group = virtualDom.h("g", {
+            attributes: { transform: "translate(1,1)" },
+            namespace: this._svgNamespace,
+        }, [cameraSector]);
+        let svg = virtualDom.h("svg", {
+            attributes: { viewBox: "0 0 2 2" },
+            namespace: this._svgNamespace,
+            style: {
+                height: "26px",
+                left: "7px",
+                position: "absolute",
+                top: "7px",
+                width: "26px",
+            },
+        }, [group]);
+        return svg;
+    }
+    _createCircleSector(fov, fill) {
+        if (fov > 2 * Math.PI - Math.PI / 90) {
+            return virtualDom.h("circle", {
+                attributes: { cx: "0", cy: "0", fill: fill, r: "1" },
+                namespace: this._svgNamespace,
+            }, []);
+        }
+        let arcStart = -Math.PI / 2 - fov / 2;
+        let arcEnd = arcStart + fov;
+        let startX = Math.cos(arcStart);
+        let startY = Math.sin(arcStart);
+        let endX = Math.cos(arcEnd);
+        let endY = Math.sin(arcEnd);
+        let largeArc = fov >= Math.PI ? 1 : 0;
+        let description = `M 0 0 ${startX} ${startY} A 1 1 0 ${largeArc} 1 ${endX} ${endY}`;
+        return virtualDom.h("path", {
+            attributes: { d: description, fill: fill },
+            namespace: this._svgNamespace,
+        }, []);
+    }
+    _createNorth(bearing) {
+        const north = virtualDom.h("div.mapillary-bearing-north", []);
+        const container = virtualDom.h("div.mapillary-bearing-north-container", { style: { transform: `rotateZ(${this._spatial.radToDeg(-bearing)}deg)` } }, [north]);
+        return container;
+    }
+    _createBackground(bearing) {
+        return virtualDom.h("div.mapillary-bearing-indicator-background", { style: { transform: `rotateZ(${this._spatial.radToDeg(-bearing)}deg)` } }, [
+            virtualDom.h("div.mapillary-bearing-indicator-background-circle", []),
+            virtualDom.h("div.mapillary-bearing-indicator-background-arrow-container", [
+                virtualDom.h("div.mapillary-bearing-indicator-background-arrow", []),
+            ]),
+        ]);
+    }
+    _computeProjectedPoints(transform) {
+        const vertices = [[1, 0]];
+        const directions = [[0, 0.5]];
+        const pointsPerLine = 12;
+        return computeProjectedPoints(transform, vertices, directions, pointsPerLine, this._viewportCoords);
+    }
+    _computeHorizontalFov(projectedPoints) {
+        const fovs = projectedPoints
+            .map((projectedPoint) => {
+            return this._coordToFov(projectedPoint[0]);
+        });
+        const fov = Math.min(...fovs);
+        return fov;
+    }
+    _coordToFov(x) {
+        return this._spatial.radToDeg(2 * Math.atan(x));
+    }
+    _interpolate(x1, x2, alpha) {
+        return (1 - alpha) * x1 + alpha * x2;
+    }
+}
+BearingComponent.componentName = "bearing";
+
+class CacheComponent extends Component {
+    /** @ignore */
+    constructor(name, container, navigator) {
+        super(name, container, navigator);
+    }
+    _activate() {
+        const subs = this._subscriptions;
+        subs.push(combineLatest(this._navigator.stateService.currentImage$.pipe(switchMap((image) => {
+            return image.sequenceEdges$;
+        }), filter((status) => {
+            return status.cached;
+        })), this._configuration$).pipe(switchMap((nc) => {
+            let status = nc[0];
+            let configuration = nc[1];
+            let sequenceDepth = Math.max(0, Math.min(4, configuration.depth.sequence));
+            let next$ = this._cache$(status.edges, NavigationDirection.Next, sequenceDepth);
+            let prev$ = this._cache$(status.edges, NavigationDirection.Prev, sequenceDepth);
+            return merge(next$, prev$).pipe(catchError((error) => {
+                console.error("Failed to cache sequence edges.", error);
+                return empty();
+            }));
+        }))
+            .subscribe(() => { }));
+        subs.push(combineLatest(this._navigator.stateService.currentImage$.pipe(switchMap((image) => {
+            return combineLatest(of(image), image.spatialEdges$.pipe(filter((status) => {
+                return status.cached;
+            })));
+        })), this._configuration$).pipe(switchMap(([[image, edgeStatus], configuration]) => {
+            let edges = edgeStatus.edges;
+            let depth = configuration.depth;
+            let sphericalDepth = Math.max(0, Math.min(2, depth.spherical));
+            let stepDepth = isSpherical(image.cameraType) ?
+                0 : Math.max(0, Math.min(3, depth.step));
+            let turnDepth = isSpherical(image.cameraType) ?
+                0 : Math.max(0, Math.min(1, depth.turn));
+            let spherical$ = this._cache$(edges, NavigationDirection.Spherical, sphericalDepth);
+            let forward$ = this._cache$(edges, NavigationDirection.StepForward, stepDepth);
+            let backward$ = this._cache$(edges, NavigationDirection.StepBackward, stepDepth);
+            let left$ = this._cache$(edges, NavigationDirection.StepLeft, stepDepth);
+            let right$ = this._cache$(edges, NavigationDirection.StepRight, stepDepth);
+            let turnLeft$ = this._cache$(edges, NavigationDirection.TurnLeft, turnDepth);
+            let turnRight$ = this._cache$(edges, NavigationDirection.TurnRight, turnDepth);
+            let turnU$ = this._cache$(edges, NavigationDirection.TurnU, turnDepth);
+            return merge(forward$, backward$, left$, right$, spherical$, turnLeft$, turnRight$, turnU$).pipe(catchError((error) => {
+                console.error("Failed to cache spatial edges.", error);
+                return empty();
+            }));
+        }))
+            .subscribe(() => { }));
+    }
+    _deactivate() {
+        this._subscriptions.unsubscribe();
+    }
+    _getDefaultConfiguration() {
+        return { depth: { spherical: 1, sequence: 2, step: 1, turn: 0 } };
+    }
+    _cache$(edges, direction, depth) {
+        return zip(of(edges), of(depth)).pipe(expand((ed) => {
+            let es = ed[0];
+            let d = ed[1];
+            let edgesDepths$ = [];
+            if (d > 0) {
+                for (let edge of es) {
+                    if (edge.data.direction === direction) {
+                        edgesDepths$.push(zip(this._navigator.graphService.cacheImage$(edge.target).pipe(mergeMap((n) => {
+                            return this._imageToEdges$(n, direction);
+                        })), of(d - 1)));
+                    }
+                }
+            }
+            return from(edgesDepths$).pipe(mergeAll());
+        }), skip(1));
+    }
+    _imageToEdges$(image, direction) {
+        return ([NavigationDirection.Next, NavigationDirection.Prev].indexOf(direction) > -1 ?
+            image.sequenceEdges$ :
+            image.spatialEdges$).pipe(first((status) => {
+            return status.cached;
+        }), map((status) => {
+            return status.edges;
+        }));
+    }
+}
+CacheComponent.componentName = "cache";
+
+/**
+ * @class CancelMapillaryError
+ *
+ * @classdesc Error thrown when a move to request has been
+ * cancelled before completing because of a subsequent request.
+ */
+class CancelMapillaryError extends MapillaryError {
+    constructor(message) {
+        super(message != null ? message : "The request was cancelled.");
+        Object.setPrototypeOf(this, CancelMapillaryError.prototype);
+        this.name = "CancelMapillaryError";
+    }
+}
+
+/**
+ * @class DirectionDOMCalculator
+ * @classdesc Helper class for calculating DOM CSS properties.
+ */
+class DirectionDOMCalculator {
+    constructor(configuration, size) {
+        this._spatial = new Spatial();
+        this._minThresholdWidth = 320;
+        this._maxThresholdWidth = 1480;
+        this._minThresholdHeight = 240;
+        this._maxThresholdHeight = 820;
+        this._configure(configuration);
+        this._resize(size);
+        this._reset();
+    }
+    get minWidth() {
+        return this._minWidth;
+    }
+    get maxWidth() {
+        return this._maxWidth;
+    }
+    get containerWidth() {
+        return this._containerWidth;
+    }
+    get containerWidthCss() {
+        return this._containerWidthCss;
+    }
+    get containerMarginCss() {
+        return this._containerMarginCss;
+    }
+    get containerLeftCss() {
+        return this._containerLeftCss;
+    }
+    get containerHeight() {
+        return this._containerHeight;
+    }
+    get containerHeightCss() {
+        return this._containerHeightCss;
+    }
+    get containerBottomCss() {
+        return this._containerBottomCss;
+    }
+    get stepCircleSize() {
+        return this._stepCircleSize;
+    }
+    get stepCircleSizeCss() {
+        return this._stepCircleSizeCss;
+    }
+    get stepCircleMarginCss() {
+        return this._stepCircleMarginCss;
+    }
+    get turnCircleSize() {
+        return this._turnCircleSize;
+    }
+    get turnCircleSizeCss() {
+        return this._turnCircleSizeCss;
+    }
+    get outerRadius() {
+        return this._outerRadius;
+    }
+    get innerRadius() {
+        return this._innerRadius;
+    }
+    get shadowOffset() {
+        return this._shadowOffset;
+    }
+    /**
+     * Configures the min and max width values.
+     *
+     * @param {DirectionConfiguration} configuration Configuration
+     * with min and max width values.
+     */
+    configure(configuration) {
+        this._configure(configuration);
+        this._reset();
+    }
+    /**
+     * Resizes all properties according to the width and height
+     * of the size object.
+     *
+     * @param {ViewportSize} size The size of the container element.
+     */
+    resize(size) {
+        this._resize(size);
+        this._reset();
+    }
+    /**
+     * Calculates the coordinates on the unit circle for an angle.
+     *
+     * @param {number} angle Angle in radians.
+     * @returns {Array<number>} The x and y coordinates on the unit circle.
+     */
+    angleToCoordinates(angle) {
+        return [Math.cos(angle), Math.sin(angle)];
+    }
+    /**
+     * Calculates the coordinates on the unit circle for the
+     * relative angle between the first and second angle.
+     *
+     * @param {number} first Angle in radians.
+     * @param {number} second Angle in radians.
+     * @returns {Array<number>} The x and y coordinates on the unit circle
+     * for the relative angle between the first and second angle.
+     */
+    relativeAngleToCoordiantes(first, second) {
+        let relativeAngle = this._spatial.wrapAngle(first - second);
+        return this.angleToCoordinates(relativeAngle);
+    }
+    _configure(configuration) {
+        this._minWidth = configuration.minWidth;
+        this._maxWidth = this._getMaxWidth(configuration.minWidth, configuration.maxWidth);
+    }
+    _resize(size) {
+        this._elementWidth = size.width;
+        this._elementHeight = size.height;
+    }
+    _reset() {
+        this._containerWidth = this._getContainerWidth(this._elementWidth, this._elementHeight);
+        this._containerHeight = this._getContainerHeight(this.containerWidth);
+        this._stepCircleSize = this._getStepCircleDiameter(this._containerHeight);
+        this._turnCircleSize = this._getTurnCircleDiameter(this.containerHeight);
+        this._outerRadius = this._getOuterRadius(this._containerHeight);
+        this._innerRadius = this._getInnerRadius(this._containerHeight);
+        this._shadowOffset = 3;
+        this._containerWidthCss = this._numberToCssPixels(this._containerWidth);
+        this._containerMarginCss = this._numberToCssPixels(-0.5 * this._containerWidth);
+        this._containerLeftCss = this._numberToCssPixels(Math.floor(0.5 * this._elementWidth));
+        this._containerHeightCss = this._numberToCssPixels(this._containerHeight);
+        this._containerBottomCss = this._numberToCssPixels(Math.floor(-0.08 * this._containerHeight));
+        this._stepCircleSizeCss = this._numberToCssPixels(this._stepCircleSize);
+        this._stepCircleMarginCss = this._numberToCssPixels(-0.5 * this._stepCircleSize);
+        this._turnCircleSizeCss = this._numberToCssPixels(this._turnCircleSize);
+    }
+    _getContainerWidth(elementWidth, elementHeight) {
+        let relativeWidth = (elementWidth - this._minThresholdWidth) / (this._maxThresholdWidth - this._minThresholdWidth);
+        let relativeHeight = (elementHeight - this._minThresholdHeight) / (this._maxThresholdHeight - this._minThresholdHeight);
+        let coeff = Math.max(0, Math.min(1, Math.min(relativeWidth, relativeHeight)));
+        coeff = 0.04 * Math.round(25 * coeff);
+        return this._minWidth + coeff * (this._maxWidth - this._minWidth);
+    }
+    _getContainerHeight(containerWidth) {
+        return 0.77 * containerWidth;
+    }
+    _getStepCircleDiameter(containerHeight) {
+        return 0.34 * containerHeight;
+    }
+    _getTurnCircleDiameter(containerHeight) {
+        return 0.3 * containerHeight;
+    }
+    _getOuterRadius(containerHeight) {
+        return 0.31 * containerHeight;
+    }
+    _getInnerRadius(containerHeight) {
+        return 0.125 * containerHeight;
+    }
+    _numberToCssPixels(value) {
+        return value + "px";
+    }
+    _getMaxWidth(value, minWidth) {
+        return value > minWidth ? value : minWidth;
+    }
+}
+
+/**
+ * @class DirectionDOMRenderer
+ * @classdesc DOM renderer for direction arrows.
+ */
+class DirectionDOMRenderer {
+    constructor(configuration, size) {
+        this._isEdge = false;
+        this._spatial = new Spatial();
+        this._calculator = new DirectionDOMCalculator(configuration, size);
+        this._image = null;
+        this._rotation = { phi: 0, theta: 0 };
+        this._epsilon = 0.5 * Math.PI / 180;
+        this._highlightKey = null;
+        this._distinguishSequence = false;
+        this._needsRender = false;
+        this._stepEdges = [];
+        this._turnEdges = [];
+        this._sphericalEdges = [];
+        this._sequenceEdgeKeys = [];
+        this._stepDirections = [
+            NavigationDirection.StepForward,
+            NavigationDirection.StepBackward,
+            NavigationDirection.StepLeft,
+            NavigationDirection.StepRight,
+        ];
+        this._turnDirections = [
+            NavigationDirection.TurnLeft,
+            NavigationDirection.TurnRight,
+            NavigationDirection.TurnU,
+        ];
+        this._turnNames = {};
+        this._turnNames[NavigationDirection.TurnLeft] = "mapillary-direction-turn-left";
+        this._turnNames[NavigationDirection.TurnRight] = "mapillary-direction-turn-right";
+        this._turnNames[NavigationDirection.TurnU] = "mapillary-direction-turn-around";
+        // detects IE 8-11, then Edge 20+.
+        let isIE = !!document.documentMode;
+        this._isEdge = !isIE && !!window.StyleMedia;
+    }
+    /**
+     * Get needs render.
+     *
+     * @returns {boolean} Value indicating whether render should be called.
+     */
+    get needsRender() {
+        return this._needsRender;
+    }
+    /**
+     * Renders virtual DOM elements.
+     *
+     * @description Calling render resets the needs render property.
+     */
+    render(navigator) {
+        this._needsRender = false;
+        let rotation = this._rotation;
+        let steps = [];
+        let turns = [];
+        if (isSpherical(this._image.cameraType)) {
+            steps = steps.concat(this._createSphericalArrows(navigator, rotation));
+        }
+        else {
+            steps = steps.concat(this._createPerspectiveToSphericalArrows(navigator, rotation));
+            steps = steps.concat(this._createStepArrows(navigator, rotation));
+            turns = turns.concat(this._createTurnArrows(navigator));
+        }
+        return this._getContainer(steps, turns, rotation);
+    }
+    setEdges(edgeStatus, sequence) {
+        this._setEdges(edgeStatus, sequence);
+        this._setNeedsRender();
+    }
+    /**
+     * Set image for which to show edges.
+     *
+     * @param {Image} image
+     */
+    setImage(image) {
+        this._image = image;
+        this._clearEdges();
+        this._setNeedsRender();
+    }
+    /**
+     * Set the render camera to use for calculating rotations.
+     *
+     * @param {RenderCamera} renderCamera
+     */
+    setRenderCamera(renderCamera) {
+        let rotation = renderCamera.rotation;
+        if (Math.abs(rotation.phi - this._rotation.phi) < this._epsilon) {
+            return;
+        }
+        this._rotation = rotation;
+        this._setNeedsRender();
+    }
+    /**
+     * Set configuration values.
+     *
+     * @param {DirectionConfiguration} configuration
+     */
+    setConfiguration(configuration) {
+        let needsRender = false;
+        if (this._highlightKey !== configuration.highlightId ||
+            this._distinguishSequence !== configuration.distinguishSequence) {
+            this._highlightKey = configuration.highlightId;
+            this._distinguishSequence = configuration.distinguishSequence;
+            needsRender = true;
+        }
+        if (this._calculator.minWidth !== configuration.minWidth ||
+            this._calculator.maxWidth !== configuration.maxWidth) {
+            this._calculator.configure(configuration);
+            needsRender = true;
+        }
+        if (needsRender) {
+            this._setNeedsRender();
+        }
+    }
+    /**
+     * Detect the element's width and height and resize
+     * elements accordingly.
+     *
+     * @param {ViewportSize} size Size of vßiewer container element.
+     */
+    resize(size) {
+        this._calculator.resize(size);
+        this._setNeedsRender();
+    }
+    _setNeedsRender() {
+        if (this._image != null) {
+            this._needsRender = true;
+        }
+    }
+    _clearEdges() {
+        this._stepEdges = [];
+        this._turnEdges = [];
+        this._sphericalEdges = [];
+        this._sequenceEdgeKeys = [];
+    }
+    _setEdges(edgeStatus, sequence) {
+        this._stepEdges = [];
+        this._turnEdges = [];
+        this._sphericalEdges = [];
+        this._sequenceEdgeKeys = [];
+        for (let edge of edgeStatus.edges) {
+            let direction = edge.data.direction;
+            if (this._stepDirections.indexOf(direction) > -1) {
+                this._stepEdges.push(edge);
+                continue;
+            }
+            if (this._turnDirections.indexOf(direction) > -1) {
+                this._turnEdges.push(edge);
+                continue;
+            }
+            if (edge.data.direction === NavigationDirection.Spherical) {
+                this._sphericalEdges.push(edge);
+            }
+        }
+        if (this._distinguishSequence && sequence != null) {
+            let edges = this._sphericalEdges
+                .concat(this._stepEdges)
+                .concat(this._turnEdges);
+            for (let edge of edges) {
+                let edgeKey = edge.target;
+                for (let sequenceKey of sequence.imageIds) {
+                    if (sequenceKey === edgeKey) {
+                        this._sequenceEdgeKeys.push(edgeKey);
+                        break;
+                    }
+                }
+            }
+        }
+    }
+    _createSphericalArrows(navigator, rotation) {
+        let arrows = [];
+        for (let sphericalEdge of this._sphericalEdges) {
+            arrows.push(this._createVNodeByKey(navigator, sphericalEdge.target, sphericalEdge.data.worldMotionAzimuth, rotation, this._calculator.outerRadius, "mapillary-direction-arrow-spherical"));
+        }
+        for (let stepEdge of this._stepEdges) {
+            arrows.push(this._createSphericalToPerspectiveArrow(navigator, stepEdge.target, stepEdge.data.worldMotionAzimuth, rotation, stepEdge.data.direction));
+        }
+        return arrows;
+    }
+    _createSphericalToPerspectiveArrow(navigator, key, azimuth, rotation, direction) {
+        let threshold = Math.PI / 8;
+        let relativePhi = rotation.phi;
+        switch (direction) {
+            case NavigationDirection.StepBackward:
+                relativePhi = rotation.phi - Math.PI;
+                break;
+            case NavigationDirection.StepLeft:
+                relativePhi = rotation.phi + Math.PI / 2;
+                break;
+            case NavigationDirection.StepRight:
+                relativePhi = rotation.phi - Math.PI / 2;
+                break;
+        }
+        if (Math.abs(this._spatial.wrapAngle(azimuth - relativePhi)) < threshold) {
+            return this._createVNodeByKey(navigator, key, azimuth, rotation, this._calculator.outerRadius, "mapillary-direction-arrow-step");
+        }
+        return this._createVNodeInactive(key, azimuth, rotation);
+    }
+    _createPerspectiveToSphericalArrows(navigator, rotation) {
+        let arrows = [];
+        for (let sphericalEdge of this._sphericalEdges) {
+            arrows.push(this._createVNodeByKey(navigator, sphericalEdge.target, sphericalEdge.data.worldMotionAzimuth, rotation, this._calculator.innerRadius, "mapillary-direction-arrow-spherical", true));
+        }
+        return arrows;
+    }
+    _createStepArrows(navigator, rotation) {
+        let arrows = [];
+        for (let stepEdge of this._stepEdges) {
+            arrows.push(this._createVNodeByDirection(navigator, stepEdge.target, stepEdge.data.worldMotionAzimuth, rotation, stepEdge.data.direction));
+        }
+        return arrows;
+    }
+    _createTurnArrows(navigator) {
+        let turns = [];
+        for (let turnEdge of this._turnEdges) {
+            let direction = turnEdge.data.direction;
+            let name = this._turnNames[direction];
+            turns.push(this._createVNodeByTurn(navigator, turnEdge.target, name, direction));
+        }
+        return turns;
+    }
+    _createVNodeByKey(navigator, key, azimuth, rotation, offset, className, shiftVertically) {
+        let onClick = (e) => {
+            navigator.moveTo$(key)
+                .subscribe(undefined, (error) => {
+                if (!(error instanceof CancelMapillaryError)) {
+                    console.error(error);
+                }
+            });
+        };
+        return this._createVNode(key, azimuth, rotation, offset, className, "mapillary-direction-circle", onClick, shiftVertically);
+    }
+    _createVNodeByDirection(navigator, key, azimuth, rotation, direction) {
+        let onClick = (e) => {
+            navigator.moveDir$(direction)
+                .subscribe(undefined, (error) => {
+                if (!(error instanceof CancelMapillaryError)) {
+                    console.error(error);
+                }
+            });
+        };
+        return this._createVNode(key, azimuth, rotation, this._calculator.outerRadius, "mapillary-direction-arrow-step", "mapillary-direction-circle", onClick);
+    }
+    _createVNodeByTurn(navigator, key, className, direction) {
+        let onClick = (e) => {
+            navigator.moveDir$(direction)
+                .subscribe(undefined, (error) => {
+                if (!(error instanceof CancelMapillaryError)) {
+                    console.error(error);
+                }
+            });
+        };
+        let style = {
+            height: this._calculator.turnCircleSizeCss,
+            transform: "rotate(0)",
+            width: this._calculator.turnCircleSizeCss,
+        };
+        switch (direction) {
+            case NavigationDirection.TurnLeft:
+                style.left = "5px";
+                style.top = "5px";
+                break;
+            case NavigationDirection.TurnRight:
+                style.right = "5px";
+                style.top = "5px";
+                break;
+            case NavigationDirection.TurnU:
+                style.left = "5px";
+                style.bottom = "5px";
+                break;
+        }
+        let circleProperties = {
+            attributes: {
+                "data-id": key,
+            },
+            onclick: onClick,
+            style: style,
+        };
+        let circleClassName = "mapillary-direction-turn-circle";
+        if (this._sequenceEdgeKeys.indexOf(key) > -1) {
+            circleClassName += "-sequence";
+        }
+        if (this._highlightKey === key) {
+            circleClassName += "-highlight";
+        }
+        let turn = virtualDom.h(`div.${className}`, {}, []);
+        return virtualDom.h("div." + circleClassName, circleProperties, [turn]);
+    }
+    _createVNodeInactive(key, azimuth, rotation) {
+        return this._createVNode(key, azimuth, rotation, this._calculator.outerRadius, "mapillary-direction-arrow-inactive", "mapillary-direction-circle-inactive");
+    }
+    _createVNode(key, azimuth, rotation, radius, className, circleClassName, onClick, shiftVertically) {
+        let translation = this._calculator.angleToCoordinates(azimuth - rotation.phi);
+        // rotate 90 degrees clockwise and flip over X-axis
+        let translationX = Math.round(-radius * translation[1] + 0.5 * this._calculator.containerWidth);
+        let translationY = Math.round(-radius * translation[0] + 0.5 * this._calculator.containerHeight);
+        let shadowTranslation = this._calculator.relativeAngleToCoordiantes(azimuth, rotation.phi);
+        let shadowOffset = this._calculator.shadowOffset;
+        let shadowTranslationX = -shadowOffset * shadowTranslation[1];
+        let shadowTranslationY = shadowOffset * shadowTranslation[0];
+        let filter = `drop-shadow(${shadowTranslationX}px ${shadowTranslationY}px 1px rgba(0,0,0,0.8))`;
+        let properties = {
+            style: {
+                "-webkit-filter": filter,
+                filter: filter,
+            },
+        };
+        let chevron = virtualDom.h("div." + className, properties, []);
+        let azimuthDeg = -this._spatial.radToDeg(azimuth - rotation.phi);
+        let circleTransform = shiftVertically ?
+            `translate(${translationX}px, ${translationY}px) rotate(${azimuthDeg}deg) translateZ(-0.01px)` :
+            `translate(${translationX}px, ${translationY}px) rotate(${azimuthDeg}deg)`;
+        let circleProperties = {
+            attributes: { "data-id": key },
+            onclick: onClick,
+            style: {
+                height: this._calculator.stepCircleSizeCss,
+                marginLeft: this._calculator.stepCircleMarginCss,
+                marginTop: this._calculator.stepCircleMarginCss,
+                transform: circleTransform,
+                width: this._calculator.stepCircleSizeCss,
+            },
+        };
+        if (this._sequenceEdgeKeys.indexOf(key) > -1) {
+            circleClassName += "-sequence";
+        }
+        if (this._highlightKey === key) {
+            circleClassName += "-highlight";
+        }
+        return virtualDom.h("div." + circleClassName, circleProperties, [chevron]);
+    }
+    _getContainer(steps, turns, rotation) {
+        // edge does not handle hover on perspective transforms.
+        let transform = this._isEdge ?
+            "rotateX(60deg)" :
+            `perspective(${this._calculator.containerWidthCss}) rotateX(60deg)`;
+        let properties = {
+            oncontextmenu: (event) => { event.preventDefault(); },
+            style: {
+                bottom: this._calculator.containerBottomCss,
+                height: this._calculator.containerHeightCss,
+                left: this._calculator.containerLeftCss,
+                marginLeft: this._calculator.containerMarginCss,
+                transform: transform,
+                width: this._calculator.containerWidthCss,
+            },
+        };
+        return virtualDom.h("div.mapillary-direction-perspective", properties, turns.concat(steps));
+    }
+}
+
+/**
+ * @class DirectionComponent
+ * @classdesc Component showing navigation arrows for steps and turns.
+ */
+class DirectionComponent extends Component {
+    /** @ignore */
+    constructor(name, container, navigator, directionDOMRenderer) {
+        super(name, container, navigator);
+        this._renderer = !!directionDOMRenderer ?
+            directionDOMRenderer :
+            new DirectionDOMRenderer(this.defaultConfiguration, { height: container.container.offsetHeight, width: container.container.offsetWidth });
+        this._hoveredIdSubject$ = new Subject();
+        this._hoveredId$ = this._hoveredIdSubject$.pipe(share());
+    }
+    fire(type, event) {
+        super.fire(type, event);
+    }
+    off(type, handler) {
+        super.off(type, handler);
+    }
+    on(type, handler) {
+        super.on(type, handler);
+    }
+    _activate() {
+        const subs = this._subscriptions;
+        subs.push(this._configuration$
+            .subscribe((configuration) => {
+            this._renderer.setConfiguration(configuration);
+        }));
+        subs.push(this._container.renderService.size$
+            .subscribe((size) => {
+            this._renderer.resize(size);
+        }));
+        subs.push(this._navigator.stateService.currentImage$.pipe(tap((image) => {
+            this._container.domRenderer.render$.next({ name: this._name, vNode: virtualDom.h("div", {}, []) });
+            this._renderer.setImage(image);
+        }), withLatestFrom(this._configuration$), switchMap(([image, configuration]) => {
+            return combineLatest(image.spatialEdges$, configuration.distinguishSequence ?
+                this._navigator.graphService
+                    .cacheSequence$(image.sequenceId).pipe(catchError((error) => {
+                    console.error(`Failed to cache sequence (${image.sequenceId})`, error);
+                    return of(null);
+                })) :
+                of(null));
+        }))
+            .subscribe(([edgeStatus, sequence]) => {
+            this._renderer.setEdges(edgeStatus, sequence);
+        }));
+        subs.push(this._container.renderService.renderCameraFrame$.pipe(tap((renderCamera) => {
+            this._renderer.setRenderCamera(renderCamera);
+        }), map(() => {
+            return this._renderer;
+        }), filter((renderer) => {
+            return renderer.needsRender;
+        }), map((renderer) => {
+            return { name: this._name, vNode: renderer.render(this._navigator) };
+        }))
+            .subscribe(this._container.domRenderer.render$));
+        subs.push(combineLatest(this._container.domRenderer.element$, this._container.renderService.renderCamera$, this._container.mouseService.mouseMove$.pipe(startWith(null)), this._container.mouseService.mouseUp$.pipe(startWith(null))).pipe(map(([element]) => {
+            let elements = element.getElementsByClassName("mapillary-direction-perspective");
+            for (let i = 0; i < elements.length; i++) {
+                let hovered = elements.item(i).querySelector(":hover");
+                if (hovered != null && hovered.hasAttribute("data-id")) {
+                    return hovered.getAttribute("data-id");
+                }
+            }
+            return null;
+        }), distinctUntilChanged())
+            .subscribe(this._hoveredIdSubject$));
+        subs.push(this._hoveredId$
+            .subscribe((id) => {
+            const type = "hover";
+            const event = {
+                id,
+                target: this,
+                type,
+            };
+            this.fire(type, event);
+        }));
+    }
+    _deactivate() {
+        this._subscriptions.unsubscribe();
+    }
+    _getDefaultConfiguration() {
+        return {
+            distinguishSequence: false,
+            maxWidth: 460,
+            minWidth: 260,
+        };
+    }
+}
+/** @inheritdoc */
+DirectionComponent.componentName = "direction";
+
+const sphericalFrag = `
+#ifdef GL_FRAGMENT_PRECISION_HIGH
+precision highp float;
+#else
+precision mediump float;
+#endif
+
+#define tau 6.28318530718
+
+uniform sampler2D projectorTex;
+uniform float opacity;
+
+varying vec4 vRstq;
+
+void main()
+{
+    vec3 b = normalize(vRstq.xyz);
+    float lat = -asin(b.y);
+    float lng = atan(b.x, b.z);
+    float x = lng / tau + 0.5;
+    float y = lat / tau * 2.0 + 0.5;
+    vec4 baseColor = texture2D(projectorTex, vec2(x, y));
+    baseColor.a = opacity;
+    gl_FragColor = baseColor;
+}
+`;
+
+const sphericalVert = `
+#ifdef GL_ES
+precision highp float;
+#endif
+
+uniform mat4 projectorMat;
+
+varying vec4 vRstq;
+
+void main()
+{
+    vRstq = projectorMat * vec4(position, 1.0);
+    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+}
+`;
+
+const sphericalCurtainFrag = `
+#ifdef GL_FRAGMENT_PRECISION_HIGH
+precision highp float;
+#else
+precision mediump float;
+#endif
+
+#define tau 6.28318530718
+
+uniform sampler2D projectorTex;
+uniform float curtain;
+uniform float opacity;
+
+varying vec4 vRstq;
+
+void main()
+{
+    vec3 b = normalize(vRstq.xyz);
+    float lat = -asin(b.y);
+    float lng = atan(b.x, b.z);
+    float x = lng / tau + 0.5;
+    float y = lat / tau * 2.0 + 0.5;
+
+    bool inverted = curtain < 0.5;
+
+    float curtainMin = inverted ? curtain + 0.5 : curtain - 0.5;
+    float curtainMax = curtain;
+
+    bool insideCurtain = inverted ?
+        x > curtainMin || x < curtainMax :
+        x > curtainMin && x < curtainMax;
+
+    vec4 baseColor;
+    if (insideCurtain) {
+        baseColor = texture2D(projectorTex, vec2(x, y));
+        baseColor.a = opacity;
+    } else {
+        baseColor = vec4(0.0, 0.0, 0.0, 0.0);
+    }
+
+    gl_FragColor = baseColor;
+}
+`;
+
+const sphericalCurtainVert = `
+#ifdef GL_ES
+precision highp float;
+#endif
+
+uniform mat4 projectorMat;
+
+varying vec4 vRstq;
+
+void main()
+{
+    vRstq = projectorMat * vec4(position, 1.0);
+    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+}
+`;
+
+const fisheyeFrag = `
+#ifdef GL_FRAGMENT_PRECISION_HIGH
+precision highp float;
+#else
+precision mediump float;
+#endif
+
+uniform sampler2D projectorTex;
+uniform float opacity;
+uniform float focal;
+uniform float k1;
+uniform float k2;
+uniform float scale_x;
+uniform float scale_y;
+uniform float radial_peak;
+
+varying vec4 vRstq;
+
+void main()
+{
+    float x = vRstq.x;
+    float y = vRstq.y;
+    float z = vRstq.z;
+
+    float r = sqrt(x * x + y * y);
+    float theta = atan(r, z);
+
+    if (radial_peak > 0. && theta > radial_peak) {
+        theta = radial_peak;
+    }
+
+    float theta2 = theta * theta;
+    float theta_d = theta * (1.0 + theta2 * (k1 + theta2 * k2));
+    float s = focal * theta_d / r;
+
+    float u = scale_x * s * x + 0.5;
+    float v = -scale_y * s * y + 0.5;
+
+    vec4 baseColor;
+    if (u >= 0. && u <= 1. && v >= 0. && v <= 1.) {
+        baseColor = texture2D(projectorTex, vec2(u, v));
+        baseColor.a = opacity;
+    } else {
+        baseColor = vec4(0.0, 0.0, 0.0, 0.0);
+    }
+
+    gl_FragColor = baseColor;
+}
+`;
+
+const fisheyeVert = `
+#ifdef GL_ES
+precision highp float;
+#endif
+
+uniform mat4 projectorMat;
+
+varying vec4 vRstq;
+
+void main()
+{
+    vRstq = projectorMat * vec4(position, 1.0);
+    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+}
+`;
+
+const fisheyeCurtainFrag = `
+#ifdef GL_FRAGMENT_PRECISION_HIGH
+precision highp float;
+#else
+precision mediump float;
+#endif
+
+uniform sampler2D projectorTex;
+uniform float opacity;
+uniform float focal;
+uniform float k1;
+uniform float k2;
+uniform float scale_x;
+uniform float scale_y;
+uniform float radial_peak;
+uniform float curtain;
+
+varying vec4 vRstq;
+
+void main()
+{
+    float x = vRstq.x;
+    float y = vRstq.y;
+    float z = vRstq.z;
+
+    float r2 = sqrt(x * x + y * y);
+    float theta = atan(r2, z);
+
+    if (radial_peak > 0. && theta > radial_peak) {
+        theta = radial_peak;
+    }
+
+    float theta2 = theta * theta;
+    float theta_d = theta * (1.0 + theta2 * (k1 + theta2 * k2));
+    float s = focal * theta_d / r2;
+
+    float u = scale_x * s * x + 0.5;
+    float v = -scale_y * s * y + 0.5;
+
+    vec4 baseColor;
+    if ((u < curtain || curtain >= 1.0) && u >= 0. && u <= 1. && v >= 0. && v <= 1.) {
+        baseColor = texture2D(projectorTex, vec2(u, v));
+        baseColor.a = opacity;
+    } else {
+        baseColor = vec4(0.0, 0.0, 0.0, 0.0);
+    }
+
+    gl_FragColor = baseColor;
+}
+`;
+
+const fisheyeCurtainVert = `
+#ifdef GL_ES
+precision highp float;
+#endif
+
+uniform mat4 projectorMat;
+
+varying vec4 vRstq;
+
+void main()
+{
+    vRstq = projectorMat * vec4(position, 1.0);
+    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+}
+`;
+
+const perspectiveFrag = `
+#ifdef GL_FRAGMENT_PRECISION_HIGH
+precision highp float;
+#else
+precision mediump float;
+#endif
+
+uniform sampler2D projectorTex;
+uniform float opacity;
+uniform float focal;
+uniform float k1;
+uniform float k2;
+uniform float scale_x;
+uniform float scale_y;
+uniform float radial_peak;
+
+varying vec4 vRstq;
+
+void main()
+{
+    float x = vRstq.x / vRstq.z;
+    float y = vRstq.y / vRstq.z;
+    float r2 = x * x + y * y;
+
+    if (radial_peak > 0. && r2 > radial_peak * sqrt(r2)) {
+        r2 = radial_peak * radial_peak;
+    }
+
+    float d = 1.0 + k1 * r2 + k2 * r2 * r2;
+    float u = scale_x * focal * d * x + 0.5;
+    float v = - scale_y * focal * d * y + 0.5;
+
+    vec4 baseColor;
+    if (u >= 0. && u <= 1. && v >= 0. && v <= 1.) {
+        baseColor = texture2D(projectorTex, vec2(u, v));
+        baseColor.a = opacity;
+    } else {
+        baseColor = vec4(0.0, 0.0, 0.0, 0.0);
+    }
+
+    gl_FragColor = baseColor;
+}
+`;
+
+const perspectiveVert = `
+#ifdef GL_ES
+precision highp float;
+#endif
+
+uniform mat4 projectorMat;
+
+varying vec4 vRstq;
+
+void main()
+{
+    vRstq = projectorMat * vec4(position, 1.0);
+    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+}
+`;
+
+const perspectiveCurtainFrag = `
+#ifdef GL_FRAGMENT_PRECISION_HIGH
+precision highp float;
+#else
+precision mediump float;
+#endif
+
+uniform sampler2D projectorTex;
+uniform float opacity;
+uniform float focal;
+uniform float k1;
+uniform float k2;
+uniform float scale_x;
+uniform float scale_y;
+uniform float radial_peak;
+uniform float curtain;
+
+varying vec4 vRstq;
+
+void main()
+{
+    float x = vRstq.x / vRstq.z;
+    float y = vRstq.y / vRstq.z;
+    float r2 = x * x + y * y;
+
+    if (radial_peak > 0. && r2 > radial_peak * sqrt(r2)) {
+        r2 = radial_peak * radial_peak;
+    }
+
+    float d = 1.0 + k1 * r2 + k2 * r2 * r2;
+    float u = scale_x * focal * d * x + 0.5;
+    float v = - scale_y * focal * d * y + 0.5;
+
+    vec4 baseColor;
+    if ((u < curtain || curtain >= 1.0) && u >= 0. && u <= 1. && v >= 0. && v <= 1.) {
+        baseColor = texture2D(projectorTex, vec2(u, v));
+        baseColor.a = opacity;
+    } else {
+        baseColor = vec4(0.0, 0.0, 0.0, 0.0);
+    }
+
+    gl_FragColor = baseColor;
+}
+`;
+
+const perspectiveCurtainVert = `
+#ifdef GL_ES
+precision highp float;
+#endif
+
+uniform mat4 projectorMat;
+
+varying vec4 vRstq;
+
+void main()
+{
+    vRstq = projectorMat * vec4(position, 1.0);
+    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+}
+`;
+
+const perspectiveDistortedFrag = `
+#ifdef GL_FRAGMENT_PRECISION_HIGH
+precision highp float;
+#else
+precision mediump float;
+#endif
+
+uniform sampler2D projectorTex;
+uniform float opacity;
+
+varying vec4 vRstq;
+
+void main()
+{
+    float u = vRstq.x / vRstq.w;
+    float v = vRstq.y / vRstq.w;
+
+    vec4 baseColor;
+    if (u >= 0. && u <= 1. && v >= 0. && v <= 1.) {
+        baseColor = texture2D(projectorTex, vec2(u, v));
+        baseColor.a = opacity;
+    } else {
+        baseColor = vec4(0.0, 0.0, 0.0, 0.0);
+    }
+
+    gl_FragColor = baseColor;
+}
+`;
+
+const perspectiveDistortedVert = `
+#ifdef GL_ES
+precision highp float;
+#endif
+
+uniform mat4 projectorMat;
+
+varying vec4 vRstq;
+
+void main()
+{
+    vRstq = projectorMat * vec4(position, 1.0);
+    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+}
+`;
+
+const perspectiveDistortedCurtainFrag = `
+#ifdef GL_FRAGMENT_PRECISION_HIGH
+precision highp float;
+#else
+precision mediump float;
+#endif
+
+uniform sampler2D projectorTex;
+uniform float opacity;
+uniform float curtain;
+
+varying vec4 vRstq;
+
+void main()
+{
+    float u = vRstq.x / vRstq.w;
+    float v = vRstq.y / vRstq.w;
+
+    vec4 baseColor;
+    if ((u < curtain || curtain >= 1.0) && u >= 0. && u <= 1. && v >= 0. && v <= 1.) {
+        baseColor = texture2D(projectorTex, vec2(u, v));
+        baseColor.a = opacity;
+    } else {
+        baseColor = vec4(0.0, 0.0, 0.0, 0.0);
+    }
+
+    gl_FragColor = baseColor;
+}
+`;
+
+const perspectiveDistortedCurtainVert = `
+#ifdef GL_ES
+precision highp float;
+#endif
+
+uniform mat4 projectorMat;
+
+varying vec4 vRstq;
+
+void main()
+{
+    vRstq = projectorMat * vec4(position, 1.0);
+    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+}
+`;
+
+class Shaders {
+}
+Shaders.fisheye = {
+    fragment: fisheyeFrag,
+    vertex: fisheyeVert,
+};
+Shaders.fisheyeCurtain = {
+    fragment: fisheyeCurtainFrag,
+    vertex: fisheyeCurtainVert,
+};
+Shaders.perspective = {
+    fragment: perspectiveFrag,
+    vertex: perspectiveVert,
+};
+Shaders.perspectiveCurtain = {
+    fragment: perspectiveCurtainFrag,
+    vertex: perspectiveCurtainVert,
+};
+Shaders.perspectiveDistorted = {
+    fragment: perspectiveDistortedFrag,
+    vertex: perspectiveDistortedVert,
+};
+Shaders.perspectiveDistortedCurtain = {
+    fragment: perspectiveDistortedCurtainFrag,
+    vertex: perspectiveDistortedCurtainVert,
+};
+Shaders.spherical = {
+    fragment: sphericalFrag,
+    vertex: sphericalVert,
+};
+Shaders.sphericalCurtain = {
+    fragment: sphericalCurtainFrag,
+    vertex: sphericalCurtainVert,
+};
+
+class MeshFactory {
+    constructor(imagePlaneDepth, imageSphereRadius) {
+        this._imagePlaneDepth = imagePlaneDepth != null ? imagePlaneDepth : 200;
+        this._imageSphereRadius = imageSphereRadius != null ? imageSphereRadius : 200;
+    }
+    createMesh(image, transform) {
+        if (isSpherical(transform.cameraType)) {
+            return this._createImageSphere(image, transform);
+        }
+        else if (isFisheye(transform.cameraType)) {
+            return this._createImagePlaneFisheye(image, transform);
+        }
+        else {
+            return this._createImagePlane(image, transform);
+        }
+    }
+    createFlatMesh(image, transform, basicX0, basicX1, basicY0, basicY1) {
+        let texture = this._createTexture(image.image);
+        let materialParameters = this._createDistortedPlaneMaterialParameters(transform, texture);
+        let material = new ShaderMaterial(materialParameters);
+        let geometry = this._getFlatImagePlaneGeoFromBasic(transform, basicX0, basicX1, basicY0, basicY1);
+        return new Mesh(geometry, material);
+    }
+    createCurtainMesh(image, transform) {
+        if (isSpherical(transform.cameraType)) {
+            return this._createSphereCurtainMesh(image, transform);
+        }
+        else if (isFisheye(transform.cameraType)) {
+            return this._createCurtainMeshFisheye(image, transform);
+        }
+        else {
+            return this._createCurtainMesh(image, transform);
+        }
+    }
+    createDistortedCurtainMesh(image, transform) {
+        return this._createDistortedCurtainMesh(image, transform);
+    }
+    _createCurtainMesh(image, transform) {
+        let texture = this._createTexture(image.image);
+        let materialParameters = this._createCurtainPlaneMaterialParameters(transform, texture);
+        let material = new ShaderMaterial(materialParameters);
+        let geometry = this._useMesh(transform, image) ?
+            this._getImagePlaneGeo(transform, image) :
+            this._getRegularFlatImagePlaneGeo(transform);
+        return new Mesh(geometry, material);
+    }
+    _createCurtainMeshFisheye(image, transform) {
+        let texture = this._createTexture(image.image);
+        let materialParameters = this._createCurtainPlaneMaterialParametersFisheye(transform, texture);
+        let material = new ShaderMaterial(materialParameters);
+        let geometry = this._useMesh(transform, image) ?
+            this._getImagePlaneGeoFisheye(transform, image) :
+            this._getRegularFlatImagePlaneGeo(transform);
+        return new Mesh(geometry, material);
+    }
+    _createDistortedCurtainMesh(image, transform) {
+        let texture = this._createTexture(image.image);
+        let materialParameters = this._createDistortedCurtainPlaneMaterialParameters(transform, texture);
+        let material = new ShaderMaterial(materialParameters);
+        let geometry = this._getRegularFlatImagePlaneGeo(transform);
+        return new Mesh(geometry, material);
+    }
+    _createSphereCurtainMesh(image, transform) {
+        let texture = this._createTexture(image.image);
+        let materialParameters = this._createCurtainSphereMaterialParameters(transform, texture);
+        let material = new ShaderMaterial(materialParameters);
+        return this._useMesh(transform, image) ?
+            new Mesh(this._getImageSphereGeo(transform, image), material) :
+            new Mesh(this._getFlatImageSphereGeo(transform), material);
+    }
+    _createImageSphere(image, transform) {
+        let texture = this._createTexture(image.image);
+        let materialParameters = this._createSphereMaterialParameters(transform, texture);
+        let material = new ShaderMaterial(materialParameters);
+        let mesh = this._useMesh(transform, image) ?
+            new Mesh(this._getImageSphereGeo(transform, image), material) :
+            new Mesh(this._getFlatImageSphereGeo(transform), material);
+        return mesh;
+    }
+    _createImagePlane(image, transform) {
+        let texture = this._createTexture(image.image);
+        let materialParameters = this._createPlaneMaterialParameters(transform, texture);
+        let material = new ShaderMaterial(materialParameters);
+        let geometry = this._useMesh(transform, image) ?
+            this._getImagePlaneGeo(transform, image) :
+            this._getRegularFlatImagePlaneGeo(transform);
+        return new Mesh(geometry, material);
+    }
+    _createImagePlaneFisheye(image, transform) {
+        let texture = this._createTexture(image.image);
+        let materialParameters = this._createPlaneMaterialParametersFisheye(transform, texture);
+        let material = new ShaderMaterial(materialParameters);
+        let geometry = this._useMesh(transform, image) ?
+            this._getImagePlaneGeoFisheye(transform, image) :
+            this._getRegularFlatImagePlaneGeoFisheye(transform);
+        return new Mesh(geometry, material);
+    }
+    _createSphereMaterialParameters(transform, texture) {
+        let materialParameters = {
+            depthWrite: false,
+            fragmentShader: Shaders.spherical.fragment,
+            side: DoubleSide,
+            transparent: true,
+            uniforms: {
+                opacity: { value: 1.0 },
+                projectorMat: { value: transform.rt },
+                projectorTex: { value: texture },
+            },
+            vertexShader: Shaders.spherical.vertex,
+        };
+        return materialParameters;
+    }
+    _createCurtainSphereMaterialParameters(transform, texture) {
+        let materialParameters = {
+            depthWrite: false,
+            fragmentShader: Shaders.sphericalCurtain.fragment,
+            side: DoubleSide,
+            transparent: true,
+            uniforms: {
+                curtain: { value: 1.0 },
+                opacity: { value: 1.0 },
+                projectorMat: { value: transform.rt },
+                projectorTex: { value: texture },
+            },
+            vertexShader: Shaders.sphericalCurtain.vertex,
+        };
+        return materialParameters;
+    }
+    _createPlaneMaterialParameters(transform, texture) {
+        let materialParameters = {
+            depthWrite: false,
+            fragmentShader: Shaders.perspective.fragment,
+            side: DoubleSide,
+            transparent: true,
+            uniforms: {
+                focal: { value: transform.focal },
+                k1: { value: transform.ck1 },
+                k2: { value: transform.ck2 },
+                opacity: { value: 1.0 },
+                projectorMat: { value: transform.basicRt },
+                projectorTex: { value: texture },
+                radial_peak: { value: !!transform.radialPeak ? transform.radialPeak : 0.0 },
+                scale_x: { value: Math.max(transform.basicHeight, transform.basicWidth) / transform.basicWidth },
+                scale_y: { value: Math.max(transform.basicWidth, transform.basicHeight) / transform.basicHeight },
+            },
+            vertexShader: Shaders.perspective.vertex,
+        };
+        return materialParameters;
+    }
+    _createPlaneMaterialParametersFisheye(transform, texture) {
+        let materialParameters = {
+            depthWrite: false,
+            fragmentShader: Shaders.fisheye.fragment,
+            side: DoubleSide,
+            transparent: true,
+            uniforms: {
+                focal: { value: transform.focal },
+                k1: { value: transform.ck1 },
+                k2: { value: transform.ck2 },
+                opacity: { value: 1.0 },
+                projectorMat: { value: transform.basicRt },
+                projectorTex: { value: texture },
+                radial_peak: { value: !!transform.radialPeak ? transform.radialPeak : 0.0 },
+                scale_x: { value: Math.max(transform.basicHeight, transform.basicWidth) / transform.basicWidth },
+                scale_y: { value: Math.max(transform.basicWidth, transform.basicHeight) / transform.basicHeight },
+            },
+            vertexShader: Shaders.fisheye.vertex,
+        };
+        return materialParameters;
+    }
+    _createCurtainPlaneMaterialParametersFisheye(transform, texture) {
+        let materialParameters = {
+            depthWrite: false,
+            fragmentShader: Shaders.fisheyeCurtain.fragment,
+            side: DoubleSide,
+            transparent: true,
+            uniforms: {
+                curtain: { value: 1.0 },
+                focal: { value: transform.focal },
+                k1: { value: transform.ck1 },
+                k2: { value: transform.ck2 },
+                opacity: { value: 1.0 },
+                projectorMat: { value: transform.basicRt },
+                projectorTex: { value: texture },
+                radial_peak: { value: !!transform.radialPeak ? transform.radialPeak : 0.0 },
+                scale_x: { value: Math.max(transform.basicHeight, transform.basicWidth) / transform.basicWidth },
+                scale_y: { value: Math.max(transform.basicWidth, transform.basicHeight) / transform.basicHeight },
+            },
+            vertexShader: Shaders.fisheyeCurtain.vertex,
+        };
+        return materialParameters;
+    }
+    _createCurtainPlaneMaterialParameters(transform, texture) {
+        let materialParameters = {
+            depthWrite: false,
+            fragmentShader: Shaders.perspectiveCurtain.fragment,
+            side: DoubleSide,
+            transparent: true,
+            uniforms: {
+                curtain: { value: 1.0 },
+                focal: { value: transform.focal },
+                k1: { value: transform.ck1 },
+                k2: { value: transform.ck2 },
+                opacity: { value: 1.0 },
+                projectorMat: { value: transform.basicRt },
+                projectorTex: { value: texture },
+                radial_peak: { value: !!transform.radialPeak ? transform.radialPeak : 0.0 },
+                scale_x: { value: Math.max(transform.basicHeight, transform.basicWidth) / transform.basicWidth },
+                scale_y: { value: Math.max(transform.basicWidth, transform.basicHeight) / transform.basicHeight },
+            },
+            vertexShader: Shaders.perspectiveCurtain.vertex,
+        };
+        return materialParameters;
+    }
+    _createDistortedCurtainPlaneMaterialParameters(transform, texture) {
+        let materialParameters = {
+            depthWrite: false,
+            fragmentShader: Shaders.perspectiveDistortedCurtain.fragment,
+            side: DoubleSide,
+            transparent: true,
+            uniforms: {
+                curtain: { value: 1.0 },
+                opacity: { value: 1.0 },
+                projectorMat: { value: transform.projectorMatrix() },
+                projectorTex: { value: texture },
+            },
+            vertexShader: Shaders.perspectiveDistortedCurtain.vertex,
+        };
+        return materialParameters;
+    }
+    _createDistortedPlaneMaterialParameters(transform, texture) {
+        let materialParameters = {
+            depthWrite: false,
+            fragmentShader: Shaders.perspectiveDistorted.fragment,
+            side: DoubleSide,
+            transparent: true,
+            uniforms: {
+                opacity: { value: 1.0 },
+                projectorMat: { value: transform.projectorMatrix() },
+                projectorTex: { value: texture },
+            },
+            vertexShader: Shaders.perspectiveDistorted.vertex,
+        };
+        return materialParameters;
+    }
+    _createTexture(image) {
+        let texture = new Texture(image);
+        texture.minFilter = LinearFilter;
+        texture.needsUpdate = true;
+        return texture;
+    }
+    _useMesh(transform, image) {
+        return image.mesh.vertices.length && transform.hasValidScale;
+    }
+    _getImageSphereGeo(transform, image) {
+        const t = transform.srtInverse;
+        // push everything at least 5 meters in front of the camera
+        let minZ = 5.0 * transform.scale;
+        let maxZ = this._imageSphereRadius * transform.scale;
+        let vertices = image.mesh.vertices;
+        let numVertices = vertices.length / 3;
+        let positions = new Float32Array(vertices.length);
+        for (let i = 0; i < numVertices; ++i) {
+            let index = 3 * i;
+            let x = vertices[index + 0];
+            let y = vertices[index + 1];
+            let z = vertices[index + 2];
+            let l = Math.sqrt(x * x + y * y + z * z);
+            let boundedL = Math.max(minZ, Math.min(l, maxZ));
+            let factor = boundedL / l;
+            let p = new Vector3(x * factor, y * factor, z * factor);
+            p.applyMatrix4(t);
+            positions[index + 0] = p.x;
+            positions[index + 1] = p.y;
+            positions[index + 2] = p.z;
+        }
+        let faces = image.mesh.faces;
+        let indices = new Uint16Array(faces.length);
+        for (let i = 0; i < faces.length; ++i) {
+            indices[i] = faces[i];
+        }
+        let geometry = new BufferGeometry();
+        geometry.setAttribute("position", new BufferAttribute(positions, 3));
+        geometry.setIndex(new BufferAttribute(indices, 1));
+        return geometry;
+    }
+    _getImagePlaneGeo(transform, image) {
+        const undistortionMarginFactor = 3;
+        const t = transform.srtInverse;
+        // push everything at least 5 meters in front of the camera
+        let minZ = 5.0 * transform.scale;
+        let maxZ = this._imagePlaneDepth * transform.scale;
+        let vertices = image.mesh.vertices;
+        let numVertices = vertices.length / 3;
+        let positions = new Float32Array(vertices.length);
+        for (let i = 0; i < numVertices; ++i) {
+            let index = 3 * i;
+            let x = vertices[index + 0];
+            let y = vertices[index + 1];
+            let z = vertices[index + 2];
+            if (i < 4) {
+                x *= undistortionMarginFactor;
+                y *= undistortionMarginFactor;
+            }
+            let boundedZ = Math.max(minZ, Math.min(z, maxZ));
+            let factor = boundedZ / z;
+            let p = new Vector3(x * factor, y * factor, boundedZ);
+            p.applyMatrix4(t);
+            positions[index + 0] = p.x;
+            positions[index + 1] = p.y;
+            positions[index + 2] = p.z;
+        }
+        let faces = image.mesh.faces;
+        let indices = new Uint16Array(faces.length);
+        for (let i = 0; i < faces.length; ++i) {
+            indices[i] = faces[i];
+        }
+        let geometry = new BufferGeometry();
+        geometry.setAttribute("position", new BufferAttribute(positions, 3));
+        geometry.setIndex(new BufferAttribute(indices, 1));
+        return geometry;
+    }
+    _getImagePlaneGeoFisheye(transform, image) {
+        const t = transform.srtInverse;
+        // push everything at least 5 meters in front of the camera
+        let minZ = 5.0 * transform.scale;
+        let maxZ = this._imagePlaneDepth * transform.scale;
+        let vertices = image.mesh.vertices;
+        let numVertices = vertices.length / 3;
+        let positions = new Float32Array(vertices.length);
+        for (let i = 0; i < numVertices; ++i) {
+            let index = 3 * i;
+            let x = vertices[index + 0];
+            let y = vertices[index + 1];
+            let z = vertices[index + 2];
+            let l = Math.sqrt(x * x + y * y + z * z);
+            let boundedL = Math.max(minZ, Math.min(l, maxZ));
+            let factor = boundedL / l;
+            let p = new Vector3(x * factor, y * factor, z * factor);
+            p.applyMatrix4(t);
+            positions[index + 0] = p.x;
+            positions[index + 1] = p.y;
+            positions[index + 2] = p.z;
+        }
+        let faces = image.mesh.faces;
+        let indices = new Uint16Array(faces.length);
+        for (let i = 0; i < faces.length; ++i) {
+            indices[i] = faces[i];
+        }
+        let geometry = new BufferGeometry();
+        geometry.setAttribute("position", new BufferAttribute(positions, 3));
+        geometry.setIndex(new BufferAttribute(indices, 1));
+        return geometry;
+    }
+    _getFlatImageSphereGeo(transform) {
+        const geometry = new SphereGeometry(this._imageSphereRadius, 20, 40);
+        const t = transform.rt
+            .clone()
+            .invert();
+        geometry.applyMatrix4(t);
+        return geometry;
+    }
+    _getRegularFlatImagePlaneGeo(transform) {
+        let width = transform.width;
+        let height = transform.height;
+        let size = Math.max(width, height);
+        let dx = width / 2.0 / size;
+        let dy = height / 2.0 / size;
+        return this._getFlatImagePlaneGeo(transform, dx, dy);
+    }
+    _getFlatImagePlaneGeo(transform, dx, dy) {
+        let vertices = [];
+        vertices.push(transform.unprojectSfM([-dx, -dy], this._imagePlaneDepth));
+        vertices.push(transform.unprojectSfM([dx, -dy], this._imagePlaneDepth));
+        vertices.push(transform.unprojectSfM([dx, dy], this._imagePlaneDepth));
+        vertices.push(transform.unprojectSfM([-dx, dy], this._imagePlaneDepth));
+        return this._createFlatGeometry(vertices);
+    }
+    _getRegularFlatImagePlaneGeoFisheye(transform) {
+        let width = transform.width;
+        let height = transform.height;
+        let size = Math.max(width, height);
+        let dx = width / 2.0 / size;
+        let dy = height / 2.0 / size;
+        return this._getFlatImagePlaneGeoFisheye(transform, dx, dy);
+    }
+    _getFlatImagePlaneGeoFisheye(transform, dx, dy) {
+        let vertices = [];
+        vertices.push(transform.unprojectSfM([-dx, -dy], this._imagePlaneDepth));
+        vertices.push(transform.unprojectSfM([dx, -dy], this._imagePlaneDepth));
+        vertices.push(transform.unprojectSfM([dx, dy], this._imagePlaneDepth));
+        vertices.push(transform.unprojectSfM([-dx, dy], this._imagePlaneDepth));
+        return this._createFlatGeometry(vertices);
+    }
+    _getFlatImagePlaneGeoFromBasic(transform, basicX0, basicX1, basicY0, basicY1) {
+        let vertices = [];
+        vertices.push(transform.unprojectBasic([basicX0, basicY0], this._imagePlaneDepth));
+        vertices.push(transform.unprojectBasic([basicX1, basicY0], this._imagePlaneDepth));
+        vertices.push(transform.unprojectBasic([basicX1, basicY1], this._imagePlaneDepth));
+        vertices.push(transform.unprojectBasic([basicX0, basicY1], this._imagePlaneDepth));
+        return this._createFlatGeometry(vertices);
+    }
+    _createFlatGeometry(vertices) {
+        let positions = new Float32Array(12);
+        for (let i = 0; i < vertices.length; i++) {
+            let index = 3 * i;
+            positions[index + 0] = vertices[i][0];
+            positions[index + 1] = vertices[i][1];
+            positions[index + 2] = vertices[i][2];
+        }
+        let indices = new Uint16Array(6);
+        indices[0] = 0;
+        indices[1] = 1;
+        indices[2] = 3;
+        indices[3] = 1;
+        indices[4] = 2;
+        indices[5] = 3;
+        let geometry = new BufferGeometry();
+        geometry.setAttribute("position", new BufferAttribute(positions, 3));
+        geometry.setIndex(new BufferAttribute(indices, 1));
+        return geometry;
+    }
+}
+
+class MeshScene {
+    constructor() {
+        this._planes = {};
+        this._planesOld = {};
+        this._planesPeriphery = {};
+        this._scene = new Scene();
+        this._sceneOld = new Scene();
+        this._scenePeriphery = new Scene();
+    }
+    get planes() {
+        return this._planes;
+    }
+    get planesOld() {
+        return this._planesOld;
+    }
+    get planesPeriphery() {
+        return this._planesPeriphery;
+    }
+    get scene() {
+        return this._scene;
+    }
+    get sceneOld() {
+        return this._sceneOld;
+    }
+    get scenePeriphery() {
+        return this._scenePeriphery;
+    }
+    updateImagePlanes(planes) {
+        this._dispose(this._planesOld, this.sceneOld);
+        for (const key in this._planes) {
+            if (!this._planes.hasOwnProperty(key)) {
+                continue;
+            }
+            const plane = this._planes[key];
+            this._scene.remove(plane);
+            this._sceneOld.add(plane);
+        }
+        for (const key in planes) {
+            if (!planes.hasOwnProperty(key)) {
+                continue;
+            }
+            this._scene.add(planes[key]);
+        }
+        this._planesOld = this._planes;
+        this._planes = planes;
+    }
+    addImagePlanes(planes) {
+        for (const key in planes) {
+            if (!planes.hasOwnProperty(key)) {
+                continue;
+            }
+            const plane = planes[key];
+            this._scene.add(plane);
+            this._planes[key] = plane;
+        }
+    }
+    addImagePlanesOld(planes) {
+        for (const key in planes) {
+            if (!planes.hasOwnProperty(key)) {
+                continue;
+            }
+            const plane = planes[key];
+            this._sceneOld.add(plane);
+            this._planesOld[key] = plane;
+        }
+    }
+    setImagePlanes(planes) {
+        this._clear();
+        this.addImagePlanes(planes);
+    }
+    addPeripheryPlanes(planes) {
+        for (const key in planes) {
+            if (!planes.hasOwnProperty(key)) {
+                continue;
+            }
+            const plane = planes[key];
+            this._scenePeriphery.add(plane);
+            this._planesPeriphery[key] = plane;
+        }
+    }
+    setPeripheryPlanes(planes) {
+        this._clearPeriphery();
+        this.addPeripheryPlanes(planes);
+    }
+    setImagePlanesOld(planes) {
+        this._clearOld();
+        this.addImagePlanesOld(planes);
+    }
+    clear() {
+        this._clear();
+        this._clearOld();
+    }
+    _clear() {
+        this._dispose(this._planes, this._scene);
+        this._planes = {};
+    }
+    _clearOld() {
+        this._dispose(this._planesOld, this._sceneOld);
+        this._planesOld = {};
+    }
+    _clearPeriphery() {
+        this._dispose(this._planesPeriphery, this._scenePeriphery);
+        this._planesPeriphery = {};
+    }
+    _dispose(planes, scene) {
+        for (const key in planes) {
+            if (!planes.hasOwnProperty(key)) {
+                continue;
+            }
+            const plane = planes[key];
+            scene.remove(plane);
+            plane.geometry.dispose();
+            plane.material.dispose();
+            let texture = plane.material.uniforms.projectorTex.value;
+            if (texture != null) {
+                texture.dispose();
+            }
+        }
+    }
+}
+
+class ImageGLRenderer {
+    constructor() {
+        this._factory = new MeshFactory();
+        this._scene = new MeshScene();
+        this._alpha = 0;
+        this._alphaOld = 0;
+        this._fadeOutSpeed = 0.05;
+        this._currentKey = null;
+        this._previousKey = null;
+        this._providerDisposers = {};
+        this._frameId = 0;
+        this._needsRender = false;
+    }
+    get frameId() {
+        return this._frameId;
+    }
+    get needsRender() {
+        return this._needsRender;
+    }
+    indicateNeedsRender() {
+        this._needsRender = true;
+    }
+    addPeripheryPlane(image, transform) {
+        const mesh = this._factory.createMesh(image, transform);
+        const planes = {};
+        planes[image.id] = mesh;
+        this._scene.addPeripheryPlanes(planes);
+        this._needsRender = true;
+    }
+    clearPeripheryPlanes() {
+        this._scene.setPeripheryPlanes({});
+        this._needsRender = true;
+    }
+    updateFrame(frame) {
+        this._updateFrameId(frame.id);
+        this._needsRender = this._updateAlpha(frame.state.alpha) || this._needsRender;
+        this._needsRender = this._updateAlphaOld(frame.state.alpha) || this._needsRender;
+        this._needsRender = this._updateImagePlanes(frame.state) || this._needsRender;
+    }
+    setTextureProvider(key, provider) {
+        if (key !== this._currentKey) {
+            return;
+        }
+        let createdSubscription = provider.textureCreated$
+            .subscribe((texture) => {
+            this._updateTexture(texture);
+        });
+        let updatedSubscription = provider.textureUpdated$
+            .subscribe((updated) => {
+            this._needsRender = true;
+        });
+        let dispose = () => {
+            createdSubscription.unsubscribe();
+            updatedSubscription.unsubscribe();
+            provider.dispose();
+        };
+        if (key in this._providerDisposers) {
+            let disposeProvider = this._providerDisposers[key];
+            disposeProvider();
+            delete this._providerDisposers[key];
+        }
+        this._providerDisposers[key] = dispose;
+    }
+    updateTextureImage(imageElement, image) {
+        this._needsRender = true;
+        const planes = this._extend({}, this._scene.planes, this._scene.planesOld, this._scene.planesPeriphery);
+        for (const key in planes) {
+            if (!planes.hasOwnProperty(key)) {
+                continue;
+            }
+            if (key !== image.id) {
+                continue;
+            }
+            const plane = planes[key];
+            let material = plane.material;
+            let texture = material.uniforms.projectorTex.value;
+            texture.image = imageElement;
+            texture.needsUpdate = true;
+        }
+    }
+    render(perspectiveCamera, renderer) {
+        const planes = this._scene.planes;
+        const planesOld = this._scene.planesOld;
+        const planesPeriphery = this._scene.planesPeriphery;
+        const planeAlpha = Object.keys(planesOld).length ? 1 : this._alpha;
+        const peripheryAlpha = Object.keys(planesOld).length ? 1 : Math.floor(this._alpha);
+        for (const key in planes) {
+            if (!planes.hasOwnProperty(key)) {
+                continue;
+            }
+            const plane = planes[key];
+            plane.material.uniforms.opacity.value = planeAlpha;
+        }
+        for (const key in planesOld) {
+            if (!planesOld.hasOwnProperty(key)) {
+                continue;
+            }
+            const plane = planesOld[key];
+            plane.material.uniforms.opacity.value = this._alphaOld;
+        }
+        for (const key in planesPeriphery) {
+            if (!planesPeriphery.hasOwnProperty(key)) {
+                continue;
+            }
+            const plane = planesPeriphery[key];
+            plane.material.uniforms.opacity.value = peripheryAlpha;
+        }
+        renderer.render(this._scene.scenePeriphery, perspectiveCamera);
+        renderer.render(this._scene.scene, perspectiveCamera);
+        renderer.render(this._scene.sceneOld, perspectiveCamera);
+        for (const key in planes) {
+            if (!planes.hasOwnProperty(key)) {
+                continue;
+            }
+            const plane = planes[key];
+            plane.material.uniforms.opacity.value = this._alpha;
+        }
+        renderer.render(this._scene.scene, perspectiveCamera);
+    }
+    clearNeedsRender() {
+        this._needsRender = false;
+    }
+    dispose() {
+        this._scene.clear();
+    }
+    _updateFrameId(frameId) {
+        this._frameId = frameId;
+    }
+    _updateAlpha(alpha) {
+        if (alpha === this._alpha) {
+            return false;
+        }
+        this._alpha = alpha;
+        return true;
+    }
+    _updateAlphaOld(alpha) {
+        if (alpha < 1 || this._alphaOld === 0) {
+            return false;
+        }
+        this._alphaOld = Math.max(0, this._alphaOld - this._fadeOutSpeed);
+        return true;
+    }
+    _updateImagePlanes(state) {
+        if (state.currentImage == null ||
+            state.currentImage.id === this._currentKey) {
+            return false;
+        }
+        let previousKey = state.previousImage != null ? state.previousImage.id : null;
+        let currentKey = state.currentImage.id;
+        if (this._previousKey !== previousKey &&
+            this._previousKey !== currentKey &&
+            this._previousKey in this._providerDisposers) {
+            let disposeProvider = this._providerDisposers[this._previousKey];
+            disposeProvider();
+            delete this._providerDisposers[this._previousKey];
+        }
+        if (previousKey != null) {
+            if (previousKey !== this._currentKey && previousKey !== this._previousKey) {
+                let previousMesh = this._factory.createMesh(state.previousImage, state.previousTransform);
+                const previousPlanes = {};
+                previousPlanes[previousKey] = previousMesh;
+                this._scene.updateImagePlanes(previousPlanes);
+            }
+            this._previousKey = previousKey;
+        }
+        this._currentKey = currentKey;
+        let currentMesh = this._factory.createMesh(state.currentImage, state.currentTransform);
+        const planes = {};
+        planes[currentKey] = currentMesh;
+        this._scene.updateImagePlanes(planes);
+        this._alphaOld = 1;
+        return true;
+    }
+    _updateTexture(texture) {
+        this._needsRender = true;
+        const planes = this._scene.planes;
+        for (const key in planes) {
+            if (!planes.hasOwnProperty(key)) {
+                continue;
+            }
+            const plane = planes[key];
+            let material = plane.material;
+            let oldTexture = material.uniforms.projectorTex.value;
+            material.uniforms.projectorTex.value = null;
+            oldTexture.dispose();
+            material.uniforms.projectorTex.value = texture;
+        }
+    }
+    _extend(dest, ...sources) {
+        for (const src of sources) {
+            for (const k in src) {
+                if (!src.hasOwnProperty(k)) {
+                    continue;
+                }
+                dest[k] = src[k];
+            }
+        }
+        return dest;
+    }
+}
+
+var RenderPass$1;
+(function (RenderPass) {
+    RenderPass[RenderPass["Background"] = 0] = "Background";
+    RenderPass[RenderPass["Opaque"] = 1] = "Opaque";
+})(RenderPass$1 || (RenderPass$1 = {}));
+
+/**
+ * @class ImageTileLoader
+ *
+ * @classdesc Represents a loader of image tiles.
+ */
+class TileLoader {
+    /**
+     * Create a new image image tile loader instance.
+     *
+     * @param {APIWrapper} _api - API wrapper.
+     */
+    constructor(_api) {
+        this._api = _api;
+        this._urls$ = new Map();
+    }
+    /**
+     * Retrieve an image tile.
+     *
+     * @param {string} url - URL to the image tile resource
+     */
+    getImage$(url) {
+        let aborter;
+        const abort = new Promise((_, reject) => {
+            aborter = reject;
+        });
+        return [Observable.create((subscriber) => {
+                this._api.data
+                    .getImageBuffer(url, abort)
+                    .then((buffer) => {
+                    aborter = null;
+                    const image = new Image();
+                    image.crossOrigin = "Anonymous";
+                    image.onload = () => {
+                        window.URL.revokeObjectURL(image.src);
+                        subscriber.next(image);
+                        subscriber.complete();
+                    };
+                    image.onerror = () => {
+                        aborter = null;
+                        window.URL.revokeObjectURL(image.src);
+                        subscriber.error(new Error(`Failed to load image tile`));
+                    };
+                    const blob = new Blob([buffer]);
+                    image.src = window.URL.createObjectURL(blob);
+                }, (error) => {
+                    aborter = null;
+                    subscriber.error(error);
+                });
+            }),
+            () => {
+                if (!!aborter) {
+                    aborter();
+                }
+            }];
+    }
+    getURLs$(imageId, level) {
+        const uniqueId = this._inventId(imageId, level);
+        if (this._urls$.has(uniqueId)) {
+            return this._urls$.get(uniqueId);
+        }
+        const request = { imageId, z: level };
+        const urls$ = this._api
+            .getImageTiles$(request)
+            .pipe(map(contract => contract.node), finalize(() => {
+            this._urls$.delete(uniqueId);
+        }), publish(), refCount());
+        this._urls$.set(uniqueId, urls$);
+        return urls$;
+    }
+    _inventId(imageId, level) {
+        return `${imageId}-${level}`;
+    }
+}
+
+/**
+ * @class ImageTileStore
+ *
+ * @classdesc Represents a store for image tiles.
+ */
+class TileStore {
+    /**
+     * Create a new image image tile store instance.
+     */
+    constructor() {
+        this._tiles = new Map();
+        this._urlLevels = new Set();
+        this._urls = new Map();
+    }
+    /**
+     * Add an image tile to the store.
+     *
+     * @param {string} id - The identifier for the image tile.
+     * @param {HTMLImageElement} image - The image tile.
+     */
+    add(id, image) {
+        if (this._tiles.has(id)) {
+            throw new Error(`Image tile already stored (${id})`);
+        }
+        this._tiles.set(id, image);
+    }
+    addURLs(level, ents) {
+        const urls = this._urls;
+        for (const ent of ents) {
+            const id = this.inventId(ent);
+            if (this._urls.has(id)) {
+                throw new Error(`URL already stored (${id})`);
+            }
+            urls.set(id, ent.url);
+        }
+        this._urlLevels.add(level);
+    }
+    /**
+     * Dispose the store.
+     *
+     * @description Disposes all cached assets.
+     */
+    dispose() {
+        this._tiles
+            .forEach(image => window.URL.revokeObjectURL(image.src));
+        this._tiles.clear();
+        this._urls.clear();
+        this._urlLevels.clear();
+    }
+    /**
+     * Get an image tile from the store.
+     *
+     * @param {string} id - The identifier for the tile.
+     * @param {number} level - The level of the tile.
+     */
+    get(id) {
+        return this._tiles.get(id);
+    }
+    getURL(id) {
+        return this._urls.get(id);
+    }
+    /**
+     * Check if an image tile exist in the store.
+     *
+     * @param {string} id - The identifier for the tile.
+     * @param {number} level - The level of the tile.
+     */
+    has(id) {
+        return this._tiles.has(id);
+    }
+    hasURL(id) {
+        return this._urls.has(id);
+    }
+    hasURLLevel(level) {
+        return this._urlLevels.has(level);
+    }
+    /**
+     * Create a unique tile id from a tile.
+     *
+     * @description Tile ids are used as a hash for
+     * storing the tile in a dictionary.
+     *
+     * @param {ImageTileEnt} tile - The tile.
+     * @returns {string} Unique id.
+     */
+    inventId(tile) {
+        return `${tile.z}-${tile.x}-${tile.y}`;
+    }
+}
+
+/**
+ * @class RegionOfInterestCalculator
+ *
+ * @classdesc Represents a calculator for regions of interest.
+ */
+class RegionOfInterestCalculator {
+    constructor() {
+        this._viewportCoords = new ViewportCoords();
+    }
+    /**
+     * Compute a region of interest based on the current render camera
+     * and the viewport size.
+     *
+     * @param {RenderCamera} renderCamera - Render camera used for unprojections.
+     * @param {ViewportSize} size - Viewport size in pixels.
+     * @param {Transform} transform - Transform used for projections.
+     *
+     * @returns {TileRegionOfInterest} A region of interest.
+     */
+    computeRegionOfInterest(renderCamera, size, transform) {
+        const viewportBoundaryPoints = this._viewportBoundaryPoints(4);
+        const bbox = this._viewportPointsBoundingBox(viewportBoundaryPoints, renderCamera, transform);
+        this._clipBoundingBox(bbox);
+        const viewportPixelWidth = 2 / size.width;
+        const viewportPixelHeight = 2 / size.height;
+        const centralViewportPixel = [
+            [-0.5 * viewportPixelWidth, 0.5 * viewportPixelHeight],
+            [0.5 * viewportPixelWidth, 0.5 * viewportPixelHeight],
+            [0.5 * viewportPixelWidth, -0.5 * viewportPixelHeight],
+            [-0.5 * viewportPixelWidth, -0.5 * viewportPixelHeight],
+        ];
+        const cpbox = this._viewportPointsBoundingBox(centralViewportPixel, renderCamera, transform);
+        const inverted = cpbox.minX < cpbox.maxX;
+        return {
+            bbox: bbox,
+            pixelHeight: cpbox.maxY - cpbox.minY,
+            pixelWidth: cpbox.maxX - cpbox.minX + (inverted ? 0 : 1),
+        };
+    }
+    _viewportBoundaryPoints(pointsPerSide) {
+        const points = [];
+        const os = [[-1, 1], [1, 1], [1, -1], [-1, -1]];
+        const ds = [[2, 0], [0, -2], [-2, 0], [0, 2]];
+        for (let side = 0; side < 4; ++side) {
+            const o = os[side];
+            const d = ds[side];
+            for (let i = 0; i < pointsPerSide; ++i) {
+                points.push([o[0] + d[0] * i / pointsPerSide,
+                    o[1] + d[1] * i / pointsPerSide]);
+            }
+        }
+        return points;
+    }
+    _viewportPointsBoundingBox(viewportPoints, renderCamera, transform) {
+        const basicPoints = viewportPoints
+            .map((point) => {
+            return this._viewportCoords
+                .viewportToBasic(point[0], point[1], transform, renderCamera.perspective);
+        });
+        if (isSpherical(transform.cameraType)) {
+            return this._boundingBoxSpherical(basicPoints);
+        }
+        else {
+            return this._boundingBox(basicPoints);
+        }
+    }
+    _boundingBox(points) {
+        const bbox = {
+            maxX: Number.NEGATIVE_INFINITY,
+            maxY: Number.NEGATIVE_INFINITY,
+            minX: Number.POSITIVE_INFINITY,
+            minY: Number.POSITIVE_INFINITY,
+        };
+        for (let i = 0; i < points.length; ++i) {
+            bbox.minX = Math.min(bbox.minX, points[i][0]);
+            bbox.maxX = Math.max(bbox.maxX, points[i][0]);
+            bbox.minY = Math.min(bbox.minY, points[i][1]);
+            bbox.maxY = Math.max(bbox.maxY, points[i][1]);
+        }
+        return bbox;
+    }
+    _boundingBoxSpherical(points) {
+        const xs = [];
+        const ys = [];
+        for (let i = 0; i < points.length; ++i) {
+            xs.push(points[i][0]);
+            ys.push(points[i][1]);
+        }
+        xs.sort((a, b) => { return this._sign(a - b); });
+        ys.sort((a, b) => { return this._sign(a - b); });
+        const intervalX = this._intervalSpherical(xs);
+        return {
+            maxX: intervalX[1],
+            maxY: ys[ys.length - 1],
+            minX: intervalX[0],
+            minY: ys[0],
+        };
+    }
+    /**
+     * Find the max interval between consecutive numbers.
+     * Assumes numbers are between 0 and 1, sorted and that
+     * x is equivalent to x + 1.
+     */
+    _intervalSpherical(xs) {
+        let maxdx = 0;
+        let maxi = -1;
+        for (let i = 0; i < xs.length - 1; ++i) {
+            const dx = xs[i + 1] - xs[i];
+            if (dx > maxdx) {
+                maxdx = dx;
+                maxi = i;
+            }
+        }
+        const loopdx = xs[0] + 1 - xs[xs.length - 1];
+        if (loopdx > maxdx) {
+            return [xs[0], xs[xs.length - 1]];
+        }
+        else {
+            return [xs[maxi + 1], xs[maxi]];
+        }
+    }
+    _clipBoundingBox(bbox) {
+        bbox.minX = Math.max(0, Math.min(1, bbox.minX));
+        bbox.maxX = Math.max(0, Math.min(1, bbox.maxX));
+        bbox.minY = Math.max(0, Math.min(1, bbox.minY));
+        bbox.maxY = Math.max(0, Math.min(1, bbox.maxY));
+    }
+    _sign(n) {
+        return n > 0 ? 1 : n < 0 ? -1 : 0;
+    }
+}
+
+const TILE_MIN_REQUEST_LEVEL = 11;
+const TILE_SIZE = 1024;
+
+function clamp(value, min, max) {
+    return Math.max(min, Math.min(max, value));
+}
+function levelTilePixelSize(level) {
+    return TILE_SIZE / levelScale(level);
+}
+function levelScale(level) {
+    return Math.pow(2, level.z - level.max);
+}
+function rawImageLevel(size) {
+    const s = Math.max(size.w, size.h);
+    return Math.log(s) / Math.log(2);
+}
+function baseImageLevel(size) {
+    return Math.ceil(rawImageLevel(size));
+}
+function clampedImageLevel(size, min, max) {
+    return Math.max(min, Math.min(max, baseImageLevel(size)));
+}
+function basicToTileCoords2D(basic, size, level) {
+    const tilePixelSize = levelTilePixelSize(level);
+    const w = size.w;
+    const h = size.h;
+    const maxX = Math.ceil(w / tilePixelSize) - 1;
+    const maxY = Math.ceil(h / tilePixelSize) - 1;
+    const x = clamp(Math.floor(w * basic[0] / tilePixelSize), 0, maxX);
+    const y = clamp(Math.floor(h * basic[1] / tilePixelSize), 0, maxY);
+    return { x, y };
+}
+function tileToPixelCoords2D(tile, size, level) {
+    const scale = 1 / levelScale(level);
+    const scaledTS = scale * TILE_SIZE;
+    const x = scaledTS * tile.x;
+    const y = scaledTS * tile.y;
+    const w = Math.min(scaledTS, size.w - x);
+    const h = Math.min(scaledTS, size.h - y);
+    return { h, x, y, w };
+}
+function hasOverlap1D(low, base, scale) {
+    return (scale * low <= base &&
+        base < scale * (low + 1));
+}
+function hasOverlap2D(tile1, tile2) {
+    if (tile1.z === tile2.z) {
+        return tile1.x === tile2.x && tile1.y === tile2.y;
+    }
+    const low = tile1.z < tile2.z ? tile1 : tile2;
+    const base = tile1.z < tile2.z ? tile2 : tile1;
+    const scale = 1 / levelScale({ max: base.z, z: low.z });
+    const overlapX = hasOverlap1D(low.x, base.x, scale);
+    const overlapY = hasOverlap1D(low.y, base.y, scale);
+    return overlapX && overlapY;
+}
+function cornersToTilesCoords2D(topLeft, bottomRight, size, level) {
+    const xs = [];
+    if (topLeft.x > bottomRight.x) {
+        const tilePixelSize = levelTilePixelSize(level);
+        const maxX = Math.ceil(size.w / tilePixelSize) - 1;
+        for (let x = topLeft.x; x <= maxX; x++) {
+            xs.push(x);
+        }
+        for (let x = 0; x <= bottomRight.x; x++) {
+            xs.push(x);
+        }
+    }
+    else {
+        for (let x = topLeft.x; x <= bottomRight.x; x++) {
+            xs.push(x);
+        }
+    }
+    const tiles = [];
+    for (const x of xs) {
+        for (let y = topLeft.y; y <= bottomRight.y; y++) {
+            tiles.push({ x, y });
+        }
+    }
+    return tiles;
+}
+function verifySize(size) {
+    return size.w > 0 && size.h > 0;
+}
+
+/**
+ * @class TextureProvider
+ *
+ * @classdesc Represents a provider of textures.
+ */
+class TextureProvider {
+    /**
+     * Create a new image texture provider instance.
+     *
+     * @param {string} imageId - The identifier of the image for which to request tiles.
+     * @param {number} width - The full width of the original image.
+     * @param {number} height - The full height of the original image.
+     * @param {HTMLImageElement} background - Image to use as background.
+     * @param {TileLoader} loader - Loader for retrieving tiles.
+     * @param {TileStore} store - Store for saving tiles.
+     * @param {THREE.WebGLRenderer} renderer - Renderer used for rendering tiles to texture.
+     */
+    constructor(imageId, width, height, background, loader, store, renderer) {
+        const size = { h: height, w: width };
+        if (!verifySize(size)) {
+            console.warn(`Original image size (${width}, ${height}) ` +
+                `is invalid (${imageId}). Tiles will not be loaded.`);
+        }
+        this._imageId = imageId;
+        this._size = size;
+        this._level = {
+            max: baseImageLevel(this._size),
+            z: -1,
+        };
+        this._holder = new SubscriptionHolder();
+        this._updated$ = new Subject();
+        this._createdSubject$ = new Subject();
+        this._created$ = this._createdSubject$
+            .pipe(publishReplay(1), refCount());
+        this._holder.push(this._created$.subscribe(() => { }));
+        this._hasSubject$ = new Subject();
+        this._has$ = this._hasSubject$
+            .pipe(startWith(false), publishReplay(1), refCount());
+        this._holder.push(this._has$.subscribe(() => { }));
+        this._renderedLevel = new Set();
+        this._rendered = new Map();
+        this._subscriptions = new Map();
+        this._urlSubscriptions = new Map();
+        this._loader = loader;
+        this._store = store;
+        this._background = background;
+        this._renderer = renderer;
+        this._aborts = [];
+        this._render = null;
+        this._disposed = false;
+    }
+    /**
+     * Get disposed.
+     *
+     * @returns {boolean} Value indicating whether provider has
+     * been disposed.
+     */
+    get disposed() {
+        return this._disposed;
+    }
+    /**
+     * Get hasTexture$.
+     *
+     * @returns {Observable<boolean>} Observable emitting
+     * values indicating when the existance of a texture
+     * changes.
+     */
+    get hasTexture$() {
+        return this._has$;
+    }
+    /**
+     * Get id.
+     *
+     * @returns {boolean} The identifier of the image for
+     * which to render textures.
+     */
+    get id() {
+        return this._imageId;
+    }
+    /**
+     * Get textureUpdated$.
+     *
+     * @returns {Observable<boolean>} Observable emitting
+     * values when an existing texture has been updated.
+     */
+    get textureUpdated$() {
+        return this._updated$;
+    }
+    /**
+     * Get textureCreated$.
+     *
+     * @returns {Observable<boolean>} Observable emitting
+     * values when a new texture has been created.
+     */
+    get textureCreated$() {
+        return this._created$;
+    }
+    /**
+     * Abort all outstanding image tile requests.
+     */
+    abort() {
+        this._subscriptions.forEach(sub => sub.unsubscribe());
+        this._subscriptions.clear();
+        for (const abort of this._aborts) {
+            abort();
+        }
+        this._aborts = [];
+    }
+    /**
+     * Dispose the provider.
+     *
+     * @description Disposes all cached assets and
+     * aborts all outstanding image tile requests.
+     */
+    dispose() {
+        if (this._disposed) {
+            console.warn(`Texture already disposed (${this._imageId})`);
+            return;
+        }
+        this._urlSubscriptions.forEach(sub => sub.unsubscribe());
+        this._urlSubscriptions.clear();
+        this.abort();
+        if (this._render != null) {
+            this._render.target.dispose();
+            this._render.target = null;
+            this._render.camera = null;
+            this._render = null;
+        }
+        this._store.dispose();
+        this._holder.unsubscribe();
+        this._renderedLevel.clear();
+        this._background = null;
+        this._renderer = null;
+        this._disposed = true;
+    }
+    /**
+     * Set the region of interest.
+     *
+     * @description When the region of interest is set the
+     * the tile level is determined and tiles for the region
+     * are fetched from the store or the loader and renderedLevel
+     * to the texture.
+     *
+     * @param {TileRegionOfInterest} roi - Spatial edges to cache.
+     */
+    setRegionOfInterest(roi) {
+        if (!verifySize(this._size)) {
+            return;
+        }
+        const virtualWidth = 1 / roi.pixelWidth;
+        const virtualHeight = 1 / roi.pixelHeight;
+        const level = clampedImageLevel({ h: virtualHeight, w: virtualWidth }, TILE_MIN_REQUEST_LEVEL, this._level.max);
+        if (level !== this._level.z) {
+            this.abort();
+            this._level.z = level;
+            this._renderedLevel.clear();
+            this._rendered
+                .forEach((tile, id) => {
+                if (tile.z !== level) {
+                    return;
+                }
+                this._renderedLevel.add(id);
+            });
+        }
+        if (this._render == null) {
+            this._initRender();
+        }
+        const topLeft = basicToTileCoords2D([roi.bbox.minX, roi.bbox.minY], this._size, this._level);
+        const bottomRight = basicToTileCoords2D([roi.bbox.maxX, roi.bbox.maxY], this._size, this._level);
+        const tiles = cornersToTilesCoords2D(topLeft, bottomRight, this._size, this._level);
+        this._fetchTiles(level, tiles);
+    }
+    /**
+     * Retrieve an image tile.
+     *
+     * @description Retrieve an image tile and render it to the
+     * texture. Add the tile to the store and emit to the updated
+     * observable.
+     *
+     * @param {ImageTileEnt} tile - The tile ent.
+     */
+    _fetchTile(tile) {
+        const getTile = this._loader.getImage$(tile.url);
+        const tile$ = getTile[0];
+        const abort = getTile[1];
+        this._aborts.push(abort);
+        const tileId = this._store.inventId(tile);
+        const subscription = tile$.subscribe((image) => {
+            const pixels = tileToPixelCoords2D(tile, this._size, this._level);
+            this._renderToTarget(pixels, image);
+            this._subscriptions.delete(tileId);
+            this._removeFromArray(abort, this._aborts);
+            this._markRendered(tile);
+            this._store.add(tileId, image);
+            this._updated$.next(true);
+        }, (error) => {
+            this._subscriptions.delete(tileId);
+            this._removeFromArray(abort, this._aborts);
+            console.error(error);
+        });
+        if (!subscription.closed) {
+            this._subscriptions.set(tileId, subscription);
+        }
+    }
+    /**
+     * Fetch image tiles.
+     *
+     * @description Retrieve a image tiles and render them to the
+     * texture. Retrieve from store if it exists, otherwise retrieve
+     * from loader.
+     *
+     * @param {Array<TileCoords2D>} tiles - Array of tile coordinates to
+     * retrieve.
+     */
+    _fetchTiles(level, tiles) {
+        const urls$ = this._store.hasURLLevel(level) ?
+            of(undefined) :
+            this._loader
+                .getURLs$(this._imageId, level)
+                .pipe(tap(ents => {
+                if (!this._store.hasURLLevel(level)) {
+                    this._store.addURLs(level, ents);
+                }
+            }));
+        const subscription = urls$.subscribe(() => {
+            if (level !== this._level.z) {
+                return;
+            }
+            for (const tile of tiles) {
+                const ent = {
+                    x: tile.x,
+                    y: tile.y,
+                    z: level,
+                    url: null,
+                };
+                const id = this._store.inventId(ent);
+                if (this._renderedLevel.has(id) ||
+                    this._subscriptions.has(id)) {
+                    continue;
+                }
+                if (this._store.has(id)) {
+                    const pixels = tileToPixelCoords2D(tile, this._size, this._level);
+                    this._renderToTarget(pixels, this._store.get(id));
+                    this._markRendered(ent);
+                    this._updated$.next(true);
+                    continue;
+                }
+                ent.url = this._store.getURL(id);
+                this._fetchTile(ent);
+            }
+            this._urlSubscriptions.delete(level);
+        }, (error) => {
+            this._urlSubscriptions.delete(level);
+            console.error(error);
+        });
+        if (!subscription.closed) {
+            this._urlSubscriptions.set(level, subscription);
+        }
+    }
+    _initRender() {
+        const dx = this._size.w / 2;
+        const dy = this._size.h / 2;
+        const near = -1;
+        const far = 1;
+        const camera = new OrthographicCamera(-dx, dx, dy, -dy, near, far);
+        camera.position.z = 1;
+        const gl = this._renderer.getContext();
+        const maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE);
+        const backgroundSize = Math.max(this._size.w, this._size.h);
+        const scale = maxTextureSize > backgroundSize ?
+            1 : maxTextureSize / backgroundSize;
+        const targetWidth = Math.floor(scale * this._size.w);
+        const targetHeight = Math.floor(scale * this._size.h);
+        const target = new WebGLRenderTarget(targetWidth, targetHeight, {
+            depthBuffer: false,
+            format: RGBFormat,
+            magFilter: LinearFilter,
+            minFilter: LinearFilter,
+            stencilBuffer: false,
+        });
+        this._render = { camera, target };
+        const pixels = tileToPixelCoords2D({ x: 0, y: 0 }, this._size, { max: this._level.max, z: 0 });
+        this._renderToTarget(pixels, this._background);
+        this._createdSubject$.next(target.texture);
+        this._hasSubject$.next(true);
+    }
+    /**
+     * Mark a tile as rendered.
+     *
+     * @description Clears tiles marked as rendered in other
+     * levels of the tile pyramid if they overlap the
+     * newly rendered tile.
+     *
+     * @param {Arrary<number>} tile - The tile ent.
+     */
+    _markRendered(tile) {
+        const others = Array.from(this._rendered.entries())
+            .filter(([_, t]) => {
+            return t.z !== tile.z;
+        });
+        for (const [otherId, other] of others) {
+            if (hasOverlap2D(tile, other)) {
+                this._rendered.delete(otherId);
+            }
+        }
+        const id = this._store.inventId(tile);
+        this._rendered.set(id, tile);
+        this._renderedLevel.add(id);
+    }
+    /**
+     * Remove an item from an array if it exists in array.
+     *
+     * @param {T} item - Item to remove.
+     * @param {Array<T>} array - Array from which item should be removed.
+     */
+    _removeFromArray(item, array) {
+        const index = array.indexOf(item);
+        if (index !== -1) {
+            array.splice(index, 1);
+        }
+    }
+    /**
+     * Render an image tile to the target texture.
+     *
+     * @param {ImageTileEnt} tile - Tile ent.
+     * @param {HTMLImageElement} image - The image tile to render.
+     */
+    _renderToTarget(pixel, image) {
+        const texture = new Texture(image);
+        texture.minFilter = LinearFilter;
+        texture.needsUpdate = true;
+        const geometry = new PlaneGeometry(pixel.w, pixel.h);
+        const material = new MeshBasicMaterial({
+            map: texture,
+            side: FrontSide,
+        });
+        const mesh = new Mesh(geometry, material);
+        mesh.position.x = -this._size.w / 2 + pixel.x + pixel.w / 2;
+        mesh.position.y = this._size.h / 2 - pixel.y - pixel.h / 2;
+        const scene = new Scene();
+        scene.add(mesh);
+        const target = this._renderer.getRenderTarget();
+        this._renderer.resetState();
+        this._renderer.setRenderTarget(this._render.target);
+        this._renderer.render(scene, this._render.camera);
+        this._renderer.setRenderTarget(target);
+        scene.remove(mesh);
+        geometry.dispose();
+        material.dispose();
+        texture.dispose();
+    }
+}
+
+var State;
+(function (State) {
+    State[State["Custom"] = 0] = "Custom";
+    State[State["Earth"] = 1] = "Earth";
+    State[State["Traversing"] = 2] = "Traversing";
+    State[State["Waiting"] = 3] = "Waiting";
+    State[State["WaitingInteractively"] = 4] = "WaitingInteractively";
+})(State || (State = {}));
+
+class ImageComponent extends Component {
+    constructor(name, container, navigator) {
+        super(name, container, navigator);
+        this._imageTileLoader = new TileLoader(navigator.api);
+        this._roiCalculator = new RegionOfInterestCalculator();
+        this._rendererOperation$ = new Subject();
+        this._rendererCreator$ = new Subject();
+        this._rendererDisposer$ = new Subject();
+        this._renderer$ = this._rendererOperation$.pipe(scan((renderer, operation) => {
+            return operation(renderer);
+        }, null), filter((renderer) => {
+            return renderer != null;
+        }), distinctUntilChanged(undefined, (renderer) => {
+            return renderer.frameId;
+        }));
+        this._rendererCreator$.pipe(map(() => {
+            return (renderer) => {
+                if (renderer != null) {
+                    throw new Error("Multiple image plane states can not be created at the same time");
+                }
+                return new ImageGLRenderer();
+            };
+        }))
+            .subscribe(this._rendererOperation$);
+        this._rendererDisposer$.pipe(map(() => {
+            return (renderer) => {
+                renderer.dispose();
+                return null;
+            };
+        }))
+            .subscribe(this._rendererOperation$);
+    }
+    _activate() {
+        const subs = this._subscriptions;
+        subs.push(this._renderer$.pipe(map((renderer) => {
+            const renderHash = {
+                name: this._name,
+                renderer: {
+                    frameId: renderer.frameId,
+                    needsRender: renderer.needsRender,
+                    render: renderer.render.bind(renderer),
+                    pass: RenderPass$1.Background,
+                },
+            };
+            renderer.clearNeedsRender();
+            return renderHash;
+        }))
+            .subscribe(this._container.glRenderer.render$));
+        this._rendererCreator$.next(null);
+        subs.push(this._navigator.stateService.currentState$.pipe(map((frame) => {
+            return (renderer) => {
+                renderer.updateFrame(frame);
+                return renderer;
+            };
+        }))
+            .subscribe(this._rendererOperation$));
+        const textureProvider$ = this._container.configurationService.imageTiling$.pipe(switchMap((active) => {
+            return active ?
+                this._navigator.stateService.currentState$ :
+                new Subject();
+        }), distinctUntilChanged(undefined, (frame) => {
+            return frame.state.currentImage.id;
+        }), withLatestFrom(this._container.glRenderer.webGLRenderer$), map(([frame, renderer]) => {
+            const state = frame.state;
+            const currentNode = state.currentImage;
+            const currentTransform = state.currentTransform;
+            return new TextureProvider(currentNode.id, currentTransform.basicWidth, currentTransform.basicHeight, currentNode.image, this._imageTileLoader, new TileStore(), renderer);
+        }), publishReplay(1), refCount());
+        subs.push(textureProvider$.subscribe(() => { }));
+        subs.push(textureProvider$.pipe(map((provider) => {
+            return (renderer) => {
+                renderer.setTextureProvider(provider.id, provider);
+                return renderer;
+            };
+        }))
+            .subscribe(this._rendererOperation$));
+        subs.push(textureProvider$.pipe(pairwise())
+            .subscribe((pair) => {
+            const previous = pair[0];
+            previous.abort();
+        }));
+        const roiTrigger$ = this._container.configurationService.imageTiling$.pipe(switchMap((active) => {
+            return active ?
+                combineLatest(this._navigator.stateService.state$, this._navigator.stateService.inTranslation$) :
+                new Subject();
+        }), switchMap(([state, inTranslation]) => {
+            const streetState = state === State.Traversing ||
+                state === State.Waiting ||
+                state === State.WaitingInteractively;
+            const active = streetState && !inTranslation;
+            return active ?
+                this._container.renderService.renderCameraFrame$ :
+                empty();
+        }), map((camera) => {
+            return {
+                camera,
+                height: camera.size.height.valueOf(),
+                lookat: camera.camera.lookat.clone(),
+                width: camera.size.width.valueOf(),
+                zoom: camera.zoom.valueOf(),
+            };
+        }), pairwise(), map(([pl0, pl1]) => {
+            const stalled = pl0.width === pl1.width &&
+                pl0.height === pl1.height &&
+                pl0.zoom === pl1.zoom &&
+                pl0.lookat.equals(pl1.lookat);
+            return { camera: pl1.camera, stalled };
+        }), distinctUntilChanged((x, y) => {
+            return x.stalled === y.stalled;
+        }), filter((camera) => {
+            return camera.stalled;
+        }), withLatestFrom(this._container.renderService.size$, this._navigator.stateService.currentTransform$));
+        subs.push(textureProvider$.pipe(switchMap((provider) => {
+            return roiTrigger$.pipe(map(([stalled, size, transform]) => {
+                const camera = stalled.camera;
+                const basic = new ViewportCoords()
+                    .viewportToBasic(0, 0, transform, camera.perspective);
+                if (basic[0] < 0 ||
+                    basic[1] < 0 ||
+                    basic[0] > 1 ||
+                    basic[1] > 1) {
+                    return undefined;
+                }
+                return [
+                    this._roiCalculator
+                        .computeRegionOfInterest(camera, size, transform),
+                    provider,
+                ];
+            }), filter((args) => {
+                return !!args;
+            }));
+        }), filter((args) => {
+            return !args[1].disposed;
+        }))
+            .subscribe(([roi, provider]) => {
+            provider.setRegionOfInterest(roi);
+        }));
+        const hasTexture$ = textureProvider$
+            .pipe(switchMap((provider) => {
+            return provider.hasTexture$;
+        }), startWith(false), publishReplay(1), refCount());
+        subs.push(hasTexture$.subscribe(() => { }));
+        subs.push(this._navigator.panService.panImages$.pipe(filter((panNodes) => {
+            return panNodes.length === 0;
+        }), map(() => {
+            return (renderer) => {
+                renderer.clearPeripheryPlanes();
+                return renderer;
+            };
+        }))
+            .subscribe(this._rendererOperation$));
+        const cachedPanNodes$ = this._navigator.panService.panImages$.pipe(switchMap((nts) => {
+            return from(nts).pipe(mergeMap(([n, t]) => {
+                return combineLatest(this._navigator.graphService.cacheImage$(n.id).pipe(catchError((error) => {
+                    console.error(`Failed to cache periphery image (${n.id})`, error);
+                    return empty();
+                })), of(t));
+            }));
+        }), share());
+        subs.push(cachedPanNodes$.pipe(map(([n, t]) => {
+            return (renderer) => {
+                renderer.addPeripheryPlane(n, t);
+                return renderer;
+            };
+        }))
+            .subscribe(this._rendererOperation$));
+        subs.push(cachedPanNodes$.pipe(mergeMap(([n]) => {
+            return n.cacheImage$().pipe(catchError(() => {
+                return empty();
+            }));
+        }), map((n) => {
+            return (renderer) => {
+                renderer.updateTextureImage(n.image, n);
+                return renderer;
+            };
+        }))
+            .subscribe(this._rendererOperation$));
+        const inTransition$ = this._navigator.stateService.currentState$.pipe(map((frame) => {
+            return frame.state.alpha < 1;
+        }), distinctUntilChanged());
+        const panTrigger$ = combineLatest(this._container.mouseService.active$, this._container.touchService.active$, this._navigator.stateService.inMotion$, inTransition$).pipe(map(([mouseActive, touchActive, inMotion, inTransition]) => {
+            return !(mouseActive || touchActive || inMotion || inTransition);
+        }), filter((trigger) => {
+            return trigger;
+        }));
+        subs.push(this._navigator.stateService.state$
+            .pipe(switchMap(state => {
+            return state === State.Traversing ?
+                this._navigator.panService.panImages$ :
+                empty();
+        }), switchMap((nts) => {
+            return panTrigger$.pipe(withLatestFrom(this._container.renderService.renderCamera$, this._navigator.stateService.currentImage$, this._navigator.stateService.currentTransform$), mergeMap(([, renderCamera, currentNode, currentTransform]) => {
+                return of([
+                    renderCamera,
+                    currentNode,
+                    currentTransform,
+                    nts,
+                ]);
+            }));
+        }), switchMap(([camera, cn, ct, nts]) => {
+            const direction = camera.camera.lookat.clone().sub(camera.camera.position);
+            const cd = new Spatial().viewingDirection(cn.rotation);
+            const ca = cd.angleTo(direction);
+            const closest = [ca, undefined];
+            const basic = new ViewportCoords().viewportToBasic(0, 0, ct, camera.perspective);
+            if (basic[0] >= 0 && basic[0] <= 1 && basic[1] >= 0 && basic[1] <= 1) {
+                closest[0] = Number.NEGATIVE_INFINITY;
+            }
+            for (const [n] of nts) {
+                const d = new Spatial().viewingDirection(n.rotation);
+                const a = d.angleTo(direction);
+                if (a < closest[0]) {
+                    closest[0] = a;
+                    closest[1] = n.id;
+                }
+            }
+            if (!closest[1]) {
+                return empty();
+            }
+            return this._navigator.moveTo$(closest[1]).pipe(catchError(() => {
+                return empty();
+            }));
+        }))
+            .subscribe());
+    }
+    _deactivate() {
+        this._rendererDisposer$.next(null);
+        this._subscriptions.unsubscribe();
+    }
+    _getDefaultConfiguration() {
+        return {};
+    }
+}
+ImageComponent.componentName = "image";
+
+class HandlerBase {
+    /** @ignore */
+    constructor(component, container, navigator) {
+        this._component = component;
+        this._container = container;
+        this._navigator = navigator;
+        this._enabled = false;
+    }
+    /**
+     * Returns a Boolean indicating whether the interaction is enabled.
+     *
+     * @returns {boolean} `true` if the interaction is enabled.
+     */
+    get isEnabled() {
+        return this._enabled;
+    }
+    /**
+     * Enables the interaction.
+     *
+     * @example
+     * ```js
+     * <component-name>.<handler-name>.enable();
+     * ```
+     */
+    enable() {
+        if (this._enabled || !this._component.activated) {
+            return;
+        }
+        this._enable();
+        this._enabled = true;
+        this._component.configure(this._getConfiguration(true));
+    }
+    /**
+     * Disables the interaction.
+     *
+     * @example
+     * ```js
+     * <component-name>.<handler-name>.disable();
+     * ```
+     */
+    disable() {
+        if (!this._enabled) {
+            return;
+        }
+        this._disable();
+        this._enabled = false;
+        if (this._component.activated) {
+            this._component.configure(this._getConfiguration(false));
+        }
+    }
+}
+
+/**
+ * The `KeySequenceNavigationHandler` allows the user to navigate through a sequence using the
+ * following key commands:
+ *
+ * `ALT` + `Up Arrow`: Navigate to next image in the sequence.
+ * `ALT` + `Down Arrow`: Navigate to previous image in sequence.
+ *
+ * @example
+ * ```js
+ * var keyboardComponent = viewer.getComponent("keyboard");
+ *
+ * keyboardComponent.keySequenceNavigation.disable();
+ * keyboardComponent.keySequenceNavigation.enable();
+ *
+ * var isEnabled = keyboardComponent.keySequenceNavigation.isEnabled;
+ * ```
+ */
+class KeySequenceNavigationHandler extends HandlerBase {
+    _enable() {
+        const sequenceEdges$ = this._navigator.stateService.currentImage$.pipe(switchMap((image) => {
+            return image.sequenceEdges$;
+        }));
+        this._keyDownSubscription = this._container.keyboardService.keyDown$.pipe(withLatestFrom(sequenceEdges$))
+            .subscribe(([event, edgeStatus]) => {
+            let direction = null;
+            switch (event.keyCode) {
+                case 38: // up
+                    direction = NavigationDirection.Next;
+                    break;
+                case 40: // down
+                    direction = NavigationDirection.Prev;
+                    break;
+                default:
+                    return;
+            }
+            event.preventDefault();
+            if (!event.altKey || event.shiftKey || !edgeStatus.cached) {
+                return;
+            }
+            for (const edge of edgeStatus.edges) {
+                if (edge.data.direction === direction) {
+                    this._navigator.moveTo$(edge.target)
+                        .subscribe(undefined, (error) => {
+                        if (!(error instanceof CancelMapillaryError)) {
+                            console.error(error);
+                        }
+                    });
+                    return;
+                }
+            }
+        });
+    }
+    _disable() {
+        this._keyDownSubscription.unsubscribe();
+    }
+    _getConfiguration(enable) {
+        return { keySequenceNavigation: enable };
+    }
+}
+
+/**
+ * The `KeySpatialNavigationHandler` allows the user to navigate through a sequence using the
+ * following key commands:
+ *
+ * `Up Arrow`: Step forward.
+ * `Down Arrow`: Step backward.
+ * `Left Arrow`: Step to the left.
+ * `Rigth Arrow`: Step to the right.
+ * `SHIFT` + `Down Arrow`: Turn around.
+ * `SHIFT` + `Left Arrow`: Turn to the left.
+ * `SHIFT` + `Rigth Arrow`: Turn to the right.
+ *
+ * @example
+ * ```js
+ * var keyboardComponent = viewer.getComponent("keyboard");
+ *
+ * keyboardComponent.keySpatialNavigation.disable();
+ * keyboardComponent.keySpatialNavigation.enable();
+ *
+ * var isEnabled = keyboardComponent.keySpatialNavigation.isEnabled;
+ * ```
+ */
+class KeySpatialNavigationHandler extends HandlerBase {
+    /** @ignore */
+    constructor(component, container, navigator, spatial) {
+        super(component, container, navigator);
+        this._spatial = spatial;
+    }
+    _enable() {
+        const spatialEdges$ = this._navigator.stateService.currentImage$.pipe(switchMap((image) => {
+            return image.spatialEdges$;
+        }));
+        this._keyDownSubscription = this._container.keyboardService.keyDown$.pipe(withLatestFrom(spatialEdges$, this._navigator.stateService.currentState$))
+            .subscribe(([event, edgeStatus, frame]) => {
+            let spherical = isSpherical(frame.state.currentImage.cameraType);
+            let direction = null;
+            switch (event.keyCode) {
+                case 37: // left
+                    direction = event.shiftKey && !spherical ? NavigationDirection.TurnLeft : NavigationDirection.StepLeft;
+                    break;
+                case 38: // up
+                    direction = event.shiftKey && !spherical ? NavigationDirection.Spherical : NavigationDirection.StepForward;
+                    break;
+                case 39: // right
+                    direction = event.shiftKey && !spherical ? NavigationDirection.TurnRight : NavigationDirection.StepRight;
+                    break;
+                case 40: // down
+                    direction = event.shiftKey && !spherical ? NavigationDirection.TurnU : NavigationDirection.StepBackward;
+                    break;
+                default:
+                    return;
+            }
+            event.preventDefault();
+            if (event.altKey || !edgeStatus.cached ||
+                (event.shiftKey && spherical)) {
+                return;
+            }
+            if (!spherical) {
+                this._moveDir(direction, edgeStatus);
+            }
+            else {
+                const shifts = {};
+                shifts[NavigationDirection.StepBackward] = Math.PI;
+                shifts[NavigationDirection.StepForward] = 0;
+                shifts[NavigationDirection.StepLeft] = Math.PI / 2;
+                shifts[NavigationDirection.StepRight] = -Math.PI / 2;
+                const phi = this._rotationFromCamera(frame.state.camera).phi;
+                const navigationAngle = this._spatial.wrapAngle(phi + shifts[direction]);
+                const threshold = Math.PI / 4;
+                const edges = edgeStatus.edges.filter((e) => {
+                    return e.data.direction === NavigationDirection.Spherical || e.data.direction === direction;
+                });
+                let smallestAngle = Number.MAX_VALUE;
+                let toKey = null;
+                for (const edge of edges) {
+                    const angle = Math.abs(this._spatial.wrapAngle(edge.data.worldMotionAzimuth - navigationAngle));
+                    if (angle < Math.min(smallestAngle, threshold)) {
+                        smallestAngle = angle;
+                        toKey = edge.target;
+                    }
+                }
+                if (toKey == null) {
+                    return;
+                }
+                this._moveTo(toKey);
+            }
+        });
+    }
+    _disable() {
+        this._keyDownSubscription.unsubscribe();
+    }
+    _getConfiguration(enable) {
+        return { keySpatialNavigation: enable };
+    }
+    _moveDir(direction, edgeStatus) {
+        for (const edge of edgeStatus.edges) {
+            if (edge.data.direction === direction) {
+                this._moveTo(edge.target);
+                return;
+            }
+        }
+    }
+    _moveTo(id) {
+        this._navigator.moveTo$(id)
+            .subscribe(undefined, (error) => {
+            if (!(error instanceof CancelMapillaryError)) {
+                console.error(error);
+            }
+        });
+    }
+    _rotationFromCamera(camera) {
+        let direction = camera.lookat.clone().sub(camera.position);
+        let upProjection = direction.clone().dot(camera.up);
+        let planeProjection = direction.clone().sub(camera.up.clone().multiplyScalar(upProjection));
+        let phi = Math.atan2(planeProjection.y, planeProjection.x);
+        let theta = Math.PI / 2 - this._spatial.angleToPlane(direction.toArray(), [0, 0, 1]);
+        return { phi: phi, theta: theta };
+    }
+}
+
+/**
+ * The `KeyZoomHandler` allows the user to zoom in and out using the
+ * following key commands:
+ *
+ * `+`: Zoom in.
+ * `-`: Zoom out.
+ *
+ * @example
+ * ```js
+ * var keyboardComponent = viewer.getComponent("keyboard");
+ *
+ * keyboardComponent.keyZoom.disable();
+ * keyboardComponent.keyZoom.enable();
+ *
+ * var isEnabled = keyboardComponent.keyZoom.isEnabled;
+ * ```
+ */
+class KeyZoomHandler extends HandlerBase {
+    /** @ignore */
+    constructor(component, container, navigator, viewportCoords) {
+        super(component, container, navigator);
+        this._viewportCoords = viewportCoords;
+    }
+    _enable() {
+        this._keyDownSubscription = this._container.keyboardService.keyDown$.pipe(withLatestFrom(this._container.renderService.renderCamera$, this._navigator.stateService.currentTransform$))
+            .subscribe(([event, render, transform]) => {
+            if (event.altKey || event.ctrlKey || event.metaKey) {
+                return;
+            }
+            let delta = 0;
+            switch (event.key) {
+                case "+":
+                    delta = 1;
+                    break;
+                case "-":
+                    delta = -1;
+                    break;
+                default:
+                    return;
+            }
+            event.preventDefault();
+            const unprojected = this._viewportCoords.unprojectFromViewport(0, 0, render.perspective);
+            const reference = transform.projectBasic(unprojected.toArray());
+            this._navigator.stateService.zoomIn(delta, reference);
+        });
+    }
+    _disable() {
+        this._keyDownSubscription.unsubscribe();
+    }
+    _getConfiguration(enable) {
+        return { keyZoom: enable };
+    }
+}
+
+/**
+ * The `KeyPlayHandler` allows the user to control the play behavior
+ * using the following key commands:
+ *
+ * `Spacebar`: Start or stop playing.
+ * `SHIFT` + `D`: Switch direction.
+ * `<`: Decrease speed.
+ * `>`: Increase speed.
+ *
+ * @example
+ * ```js
+ * var keyboardComponent = viewer.getComponent("keyboard");
+ *
+ * keyboardComponent.keyPlay.disable();
+ * keyboardComponent.keyPlay.enable();
+ *
+ * var isEnabled = keyboardComponent.keyPlay.isEnabled;
+ * ```
+ */
+class KeyPlayHandler extends HandlerBase {
+    _enable() {
+        this._keyDownSubscription = this._container.keyboardService.keyDown$.pipe(withLatestFrom(this._navigator.playService.playing$, this._navigator.playService.direction$, this._navigator.playService.speed$, this._navigator.stateService.currentImage$.pipe(switchMap((image) => {
+            return image.sequenceEdges$;
+        })), this._navigator.stateService.state$.pipe(map((state) => {
+            return state === State.Earth;
+        }), distinctUntilChanged())))
+            .subscribe(([event, playing, direction, speed, status, earth]) => {
+            if (event.altKey || event.ctrlKey || event.metaKey) {
+                return;
+            }
+            switch (event.key) {
+                case "D":
+                    if (!event.shiftKey) {
+                        return;
+                    }
+                    const newDirection = playing ?
+                        null : direction === NavigationDirection.Next ?
+                        NavigationDirection.Prev : direction === NavigationDirection.Prev ?
+                        NavigationDirection.Next : null;
+                    if (newDirection != null) {
+                        this._navigator.playService.setDirection(newDirection);
+                    }
+                    break;
+                case " ":
+                    if (event.shiftKey) {
+                        return;
+                    }
+                    if (!earth) {
+                        if (playing) {
+                            this._navigator.playService.stop();
+                        }
+                        else {
+                            for (let edge of status.edges) {
+                                if (edge.data.direction === direction) {
+                                    this._navigator.playService.play();
+                                }
+                            }
+                        }
+                    }
+                    break;
+                case "<":
+                    this._navigator.playService.setSpeed(speed - 0.05);
+                    break;
+                case ">":
+                    this._navigator.playService.setSpeed(speed + 0.05);
+                    break;
+                default:
+                    return;
+            }
+            event.preventDefault();
+        });
+    }
+    _disable() {
+        this._keyDownSubscription.unsubscribe();
+    }
+    _getConfiguration(enable) {
+        return { keyPlay: enable };
+    }
+}
+
+/**
+ * @class KeyboardComponent
+ *
+ * @classdesc Component for keyboard event handling.
+ *
+ * To retrive and use the keyboard component
+ *
+ * @example
+ * ```js
+ * var viewer = new Viewer({ ... });
+ *
+ * var keyboardComponent = viewer.getComponent("keyboard");
+ * ```
+ */
+class KeyboardComponent extends Component {
+    /** @ignore */
+    constructor(name, container, navigator) {
+        super(name, container, navigator);
+        this._keyPlayHandler =
+            new KeyPlayHandler(this, container, navigator);
+        this._keySequenceNavigationHandler =
+            new KeySequenceNavigationHandler(this, container, navigator);
+        this._keySpatialNavigationHandler =
+            new KeySpatialNavigationHandler(this, container, navigator, new Spatial());
+        this._keyZoomHandler =
+            new KeyZoomHandler(this, container, navigator, new ViewportCoords());
+    }
+    /**
+     * Get key play.
+     *
+     * @returns {KeyPlayHandler} The key play handler.
+     */
+    get keyPlay() {
+        return this._keyPlayHandler;
+    }
+    /**
+     * Get key sequence navigation.
+     *
+     * @returns {KeySequenceNavigationHandler} The key sequence navigation handler.
+     */
+    get keySequenceNavigation() {
+        return this._keySequenceNavigationHandler;
+    }
+    /**
+     * Get spatial.
+     *
+     * @returns {KeySpatialNavigationHandler} The spatial handler.
+     */
+    get keySpatialNavigation() {
+        return this._keySpatialNavigationHandler;
+    }
+    /**
+     * Get key zoom.
+     *
+     * @returns {KeyZoomHandler} The key zoom handler.
+     */
+    get keyZoom() {
+        return this._keyZoomHandler;
+    }
+    _activate() {
+        this._subscriptions.push(this._configuration$
+            .subscribe((configuration) => {
+            if (configuration.keyPlay) {
+                this._keyPlayHandler.enable();
+            }
+            else {
+                this._keyPlayHandler.disable();
+            }
+            if (configuration.keySequenceNavigation) {
+                this._keySequenceNavigationHandler.enable();
+            }
+            else {
+                this._keySequenceNavigationHandler.disable();
+            }
+            if (configuration.keySpatialNavigation) {
+                this._keySpatialNavigationHandler.enable();
+            }
+            else {
+                this._keySpatialNavigationHandler.disable();
+            }
+            if (configuration.keyZoom) {
+                this._keyZoomHandler.enable();
+            }
+            else {
+                this._keyZoomHandler.disable();
+            }
+        }));
+    }
+    _deactivate() {
+        this._subscriptions.unsubscribe();
+        this._keyPlayHandler.disable();
+        this._keySequenceNavigationHandler.disable();
+        this._keySpatialNavigationHandler.disable();
+        this._keyZoomHandler.disable();
+    }
+    _getDefaultConfiguration() {
+        return { keyPlay: true, keySequenceNavigation: true, keySpatialNavigation: true, keyZoom: true };
+    }
+}
+KeyboardComponent.componentName = "keyboard";
+
+class MarkerScene {
+    constructor(scene, raycaster) {
+        this._needsRender = false;
+        this._interactiveObjects = [];
+        this._markers = {};
+        this._objectMarkers = {};
+        this._raycaster = !!raycaster ? raycaster : new Raycaster();
+        this._scene = !!scene ? scene : new Scene();
+    }
+    get markers() {
+        return this._markers;
+    }
+    get needsRender() {
+        return this._needsRender;
+    }
+    add(marker, position) {
+        if (marker.id in this._markers) {
+            this._dispose(marker.id);
+        }
+        marker.createGeometry(position);
+        this._scene.add(marker.geometry);
+        this._markers[marker.id] = marker;
+        for (let interactiveObject of marker.getInteractiveObjects()) {
+            this._interactiveObjects.push(interactiveObject);
+            this._objectMarkers[interactiveObject.uuid] = marker.id;
+        }
+        this._needsRender = true;
+    }
+    clear() {
+        for (const id in this._markers) {
+            if (!this._markers.hasOwnProperty) {
+                continue;
+            }
+            this._dispose(id);
+        }
+        this._needsRender = true;
+    }
+    get(id) {
+        return this._markers[id];
+    }
+    getAll() {
+        return Object
+            .keys(this._markers)
+            .map((id) => { return this._markers[id]; });
+    }
+    has(id) {
+        return id in this._markers;
+    }
+    intersectObjects([viewportX, viewportY], camera) {
+        this._raycaster.setFromCamera(new Vector2(viewportX, viewportY), camera);
+        const intersects = this._raycaster.intersectObjects(this._interactiveObjects);
+        for (const intersect of intersects) {
+            if (intersect.object.uuid in this._objectMarkers) {
+                return this._objectMarkers[intersect.object.uuid];
+            }
+        }
+        return null;
+    }
+    lerpAltitude(id, alt, alpha) {
+        if (!(id in this._markers)) {
+            return;
+        }
+        this._markers[id].lerpAltitude(alt, alpha);
+        this._needsRender = true;
+    }
+    remove(id) {
+        if (!(id in this._markers)) {
+            return;
+        }
+        this._dispose(id);
+        this._needsRender = true;
+    }
+    render(perspectiveCamera, renderer) {
+        renderer.render(this._scene, perspectiveCamera);
+        this._needsRender = false;
+    }
+    update(id, position, lngLat) {
+        if (!(id in this._markers)) {
+            return;
+        }
+        const marker = this._markers[id];
+        marker.updatePosition(position, lngLat);
+        this._needsRender = true;
+    }
+    _dispose(id) {
+        const marker = this._markers[id];
+        this._scene.remove(marker.geometry);
+        for (let interactiveObject of marker.getInteractiveObjects()) {
+            const index = this._interactiveObjects.indexOf(interactiveObject);
+            if (index !== -1) {
+                this._interactiveObjects.splice(index, 1);
+            }
+            else {
+                console.warn(`Object does not exist (${interactiveObject.id}) for ${id}`);
+            }
+            delete this._objectMarkers[interactiveObject.uuid];
+        }
+        marker.disposeGeometry();
+        delete this._markers[id];
+    }
+}
+
+/**
+ * @class MarkerComponent
+ *
+ * @classdesc Component for showing and editing 3D marker objects.
+ *
+ * The `add` method is used for adding new markers or replacing
+ * markers already in the set.
+ *
+ * If a marker already in the set has the same
+ * id as one of the markers added, the old marker will be removed and
+ * the added marker will take its place.
+ *
+ * It is not possible to update markers in the set by updating any properties
+ * directly on the marker object. Markers need to be replaced by
+ * re-adding them for updates to geographic position or configuration
+ * to be reflected.
+ *
+ * Markers added to the marker component can be either interactive
+ * or non-interactive. Different marker types define their behavior.
+ * Markers with interaction support can be configured with options
+ * to respond to dragging inside the viewer and be detected when
+ * retrieving markers from pixel points with the `getMarkerIdAt` method.
+ *
+ * To retrive and use the marker component
+ *
+ * @example
+ * ```js
+ * var viewer = new Viewer({ component: { marker: true }, ... });
+ *
+ * var markerComponent = viewer.getComponent("marker");
+ * ```
+ */
+class MarkerComponent extends Component {
+    /** @ignore */
+    constructor(name, container, navigator) {
+        super(name, container, navigator);
+        this._graphCalculator = new GraphCalculator();
+        this._markerScene = new MarkerScene();
+        this._markerSet = new MarkerSet();
+        this._viewportCoords = new ViewportCoords();
+        this._relativeGroundAltitude = -2;
+    }
+    /**
+     * Add markers to the marker set or replace markers in the marker set.
+     *
+     * @description If a marker already in the set has the same
+     * id as one of the markers added, the old marker will be removed
+     * the added marker will take its place.
+     *
+     * Any marker inside the visible bounding bbox
+     * will be initialized and placed in the viewer.
+     *
+     * @param {Array<Marker>} markers - Markers to add.
+     *
+     * @example
+     * ```js
+     * markerComponent.add([marker1, marker2]);
+     * ```
+     */
+    add(markers) {
+        this._markerSet.add(markers);
+    }
+    fire(type, event) {
+        super.fire(type, event);
+    }
+    /**
+     * Returns the marker in the marker set with the specified id, or
+     * undefined if the id matches no marker.
+     *
+     * @param {string} markerId - Id of the marker.
+     *
+     * @example
+     * ```js
+     * var marker = markerComponent.get("markerId");
+     * ```
+     *
+     */
+    get(markerId) {
+        return this._markerSet.get(markerId);
+    }
+    /**
+     * Returns an array of all markers.
+     *
+     * @example
+     * ```js
+     * var markers = markerComponent.getAll();
+     * ```
+     */
+    getAll() {
+        return this._markerSet.getAll();
+    }
+    /**
+     * Returns the id of the interactive marker closest to the current camera
+     * position at the specified point.
+     *
+     * @description Notice that the pixelPoint argument requires x, y
+     * coordinates from pixel space.
+     *
+     * With this function, you can use the coordinates provided by mouse
+     * events to get information out of the marker component.
+     *
+     * If no interactive geometry of an interactive marker exist at the pixel
+     * point, `null` will be returned.
+     *
+     * @param {Array<number>} pixelPoint - Pixel coordinates on the viewer element.
+     * @returns {string} Id of the interactive marker closest to the camera. If no
+     * interactive marker exist at the pixel point, `null` will be returned.
+     *
+     * @example
+     * ```js
+     * markerComponent.getMarkerIdAt([100, 100])
+     *     .then((markerId) => { console.log(markerId); });
+     * ```
+     */
+    getMarkerIdAt(pixelPoint) {
+        return new Promise((resolve, reject) => {
+            this._container.renderService.renderCamera$.pipe(first(), map((render) => {
+                const viewport = this._viewportCoords
+                    .canvasToViewport(pixelPoint[0], pixelPoint[1], this._container.container);
+                const id = this._markerScene.intersectObjects(viewport, render.perspective);
+                return id;
+            }))
+                .subscribe((id) => {
+                resolve(id);
+            }, (error) => {
+                reject(error);
+            });
+        });
+    }
+    /**
+     * Check if a marker exist in the marker set.
+     *
+     * @param {string} markerId - Id of the marker.
+     *
+     * @example
+     * ```js
+     * var markerExists = markerComponent.has("markerId");
+     * ```
+     */
+    has(markerId) {
+        return this._markerSet.has(markerId);
+    }
+    off(type, handler) {
+        super.off(type, handler);
+    }
+    on(type, handler) {
+        super.on(type, handler);
+    }
+    /**
+     * Remove markers with the specified ids from the marker set.
+     *
+     * @param {Array<string>} markerIds - Ids for markers to remove.
+     *
+     * @example
+     * ```js
+     * markerComponent.remove(["id-1", "id-2"]);
+     * ```
+     */
+    remove(markerIds) {
+        this._markerSet.remove(markerIds);
+    }
+    /**
+     * Remove all markers from the marker set.
+     *
+     * @example
+     * ```js
+     * markerComponent.removeAll();
+     * ```
+     */
+    removeAll() {
+        this._markerSet.removeAll();
+    }
+    _activate() {
+        const groundAltitude$ = this._navigator.stateService.currentState$.pipe(map((frame) => {
+            return frame.state.camera.position.z + this._relativeGroundAltitude;
+        }), distinctUntilChanged((a1, a2) => {
+            return Math.abs(a1 - a2) < 0.01;
+        }), publishReplay(1), refCount());
+        const geoInitiated$ = combineLatest(groundAltitude$, this._navigator.stateService.reference$).pipe(first(), map(() => { }), publishReplay(1), refCount());
+        const clampedConfiguration$ = this._configuration$.pipe(map((configuration) => {
+            return { visibleBBoxSize: Math.max(1, Math.min(200, configuration.visibleBBoxSize)) };
+        }));
+        const currentLngLat$ = this._navigator.stateService.currentImage$.pipe(map((image) => { return image.lngLat; }), publishReplay(1), refCount());
+        const visibleBBox$ = combineLatest(clampedConfiguration$, currentLngLat$).pipe(map(([configuration, lngLat]) => {
+            return this._graphCalculator
+                .boundingBoxCorners(lngLat, configuration.visibleBBoxSize / 2);
+        }), publishReplay(1), refCount());
+        const visibleMarkers$ = combineLatest(concat(of(this._markerSet), this._markerSet.changed$), visibleBBox$).pipe(map(([set, bbox]) => {
+            return set.search(bbox);
+        }));
+        const subs = this._subscriptions;
+        subs.push(geoInitiated$.pipe(switchMap(() => {
+            return visibleMarkers$.pipe(withLatestFrom(this._navigator.stateService.reference$, groundAltitude$));
+        }))
+            .subscribe(([markers, reference, alt]) => {
+            const markerScene = this._markerScene;
+            const sceneMarkers = markerScene.markers;
+            const markersToRemove = Object.assign({}, sceneMarkers);
+            for (const marker of markers) {
+                if (marker.id in sceneMarkers) {
+                    delete markersToRemove[marker.id];
+                }
+                else {
+                    const point3d = geodeticToEnu(marker.lngLat.lng, marker.lngLat.lat, reference.alt + alt, reference.lng, reference.lat, reference.alt);
+                    markerScene.add(marker, point3d);
+                }
+            }
+            for (const id in markersToRemove) {
+                if (!markersToRemove.hasOwnProperty(id)) {
+                    continue;
+                }
+                markerScene.remove(id);
+            }
+        }));
+        subs.push(geoInitiated$.pipe(switchMap(() => {
+            return this._markerSet.updated$.pipe(withLatestFrom(visibleBBox$, this._navigator.stateService.reference$, groundAltitude$));
+        }))
+            .subscribe(([markers, [sw, ne], reference, alt]) => {
+            const markerScene = this._markerScene;
+            for (const marker of markers) {
+                const exists = markerScene.has(marker.id);
+                const visible = marker.lngLat.lat > sw.lat &&
+                    marker.lngLat.lat < ne.lat &&
+                    marker.lngLat.lng > sw.lng &&
+                    marker.lngLat.lng < ne.lng;
+                if (visible) {
+                    const point3d = geodeticToEnu(marker.lngLat.lng, marker.lngLat.lat, reference.alt + alt, reference.lng, reference.lat, reference.alt);
+                    markerScene.add(marker, point3d);
+                }
+                else if (!visible && exists) {
+                    markerScene.remove(marker.id);
+                }
+            }
+        }));
+        subs.push(this._navigator.stateService.reference$.pipe(skip(1), withLatestFrom(groundAltitude$))
+            .subscribe(([reference, alt]) => {
+            const markerScene = this._markerScene;
+            for (const marker of markerScene.getAll()) {
+                const point3d = geodeticToEnu(marker.lngLat.lng, marker.lngLat.lat, reference.alt + alt, reference.lng, reference.lat, reference.alt);
+                markerScene.update(marker.id, point3d);
+            }
+        }));
+        subs.push(groundAltitude$.pipe(skip(1), withLatestFrom(this._navigator.stateService.reference$, currentLngLat$))
+            .subscribe(([alt, reference, lngLat]) => {
+            const markerScene = this._markerScene;
+            const position = geodeticToEnu(lngLat.lng, lngLat.lat, reference.alt + alt, reference.lng, reference.lat, reference.alt);
+            for (const marker of markerScene.getAll()) {
+                const point3d = geodeticToEnu(marker.lngLat.lng, marker.lngLat.lat, reference.alt + alt, reference.lng, reference.lat, reference.alt);
+                const distanceX = point3d[0] - position[0];
+                const distanceY = point3d[1] - position[1];
+                const groundDistance = Math
+                    .sqrt(distanceX * distanceX + distanceY * distanceY);
+                if (groundDistance > 50) {
+                    continue;
+                }
+                markerScene.lerpAltitude(marker.id, alt, Math.min(1, Math.max(0, 1.2 - 1.2 * groundDistance / 50)));
+            }
+        }));
+        subs.push(this._navigator.stateService.currentState$
+            .pipe(map((frame) => {
+            const scene = this._markerScene;
+            return {
+                name: this._name,
+                renderer: {
+                    frameId: frame.id,
+                    needsRender: scene.needsRender,
+                    render: scene.render.bind(scene),
+                    pass: RenderPass$1.Opaque,
+                },
+            };
+        }))
+            .subscribe(this._container.glRenderer.render$));
+        const hoveredMarkerId$ = combineLatest(this._container.renderService.renderCamera$, this._container.mouseService.mouseMove$)
+            .pipe(map(([render, event]) => {
+            const element = this._container.container;
+            const [canvasX, canvasY] = this._viewportCoords.canvasPosition(event, element);
+            const viewport = this._viewportCoords
+                .canvasToViewport(canvasX, canvasY, element);
+            const markerId = this._markerScene.intersectObjects(viewport, render.perspective);
+            return markerId;
+        }), publishReplay(1), refCount());
+        const draggingStarted$ = this._container.mouseService
+            .filtered$(this._name, this._container.mouseService.mouseDragStart$).pipe(map(() => {
+            return true;
+        }));
+        const draggingStopped$ = this._container.mouseService
+            .filtered$(this._name, this._container.mouseService.mouseDragEnd$).pipe(map(() => {
+            return false;
+        }));
+        const filteredDragging$ = merge(draggingStarted$, draggingStopped$)
+            .pipe(startWith(false));
+        subs.push(merge(draggingStarted$.pipe(withLatestFrom(hoveredMarkerId$)), combineLatest(draggingStopped$, of(null))).pipe(startWith([false, null]), pairwise())
+            .subscribe(([previous, current]) => {
+            const dragging = current[0];
+            const type = dragging ?
+                "markerdragstart" :
+                "markerdragend";
+            const id = dragging ? current[1] : previous[1];
+            const marker = this._markerScene.get(id);
+            const event = {
+                marker,
+                target: this,
+                type,
+            };
+            this.fire(type, event);
+        }));
+        const mouseDown$ = merge(this._container.mouseService.mouseDown$.pipe(map(() => { return true; })), this._container.mouseService.documentMouseUp$.pipe(map(() => { return false; }))).pipe(startWith(false));
+        subs.push(combineLatest(this._container.mouseService.active$, hoveredMarkerId$.pipe(distinctUntilChanged()), mouseDown$, filteredDragging$)
+            .pipe(map(([active, markerId, mouseDown, filteredDragging]) => {
+            return (!active && markerId != null && mouseDown) ||
+                filteredDragging;
+        }), distinctUntilChanged())
+            .subscribe((claim) => {
+            if (claim) {
+                this._container.mouseService.claimMouse(this._name, 1);
+                this._container.mouseService.claimWheel(this._name, 1);
+            }
+            else {
+                this._container.mouseService.unclaimMouse(this._name);
+                this._container.mouseService.unclaimWheel(this._name);
+            }
+        }));
+        const offset$ = this._container.mouseService
+            .filtered$(this._name, this._container.mouseService.mouseDragStart$).pipe(withLatestFrom(hoveredMarkerId$, this._container.renderService.renderCamera$), map(([e, id, r]) => {
+            const marker = this._markerScene.get(id);
+            const element = this._container.container;
+            const [groundCanvasX, groundCanvasY] = this._viewportCoords
+                .projectToCanvas(marker.geometry.position
+                .toArray(), element, r.perspective);
+            const [canvasX, canvasY] = this._viewportCoords
+                .canvasPosition(e, element);
+            const offset = [canvasX - groundCanvasX, canvasY - groundCanvasY];
+            return [marker, offset, r];
+        }), publishReplay(1), refCount());
+        subs.push(this._container.mouseService
+            .filtered$(this._name, this._container.mouseService.mouseDrag$)
+            .pipe(withLatestFrom(offset$, this._navigator.stateService.reference$, clampedConfiguration$))
+            .subscribe(([event, [marker, offset, render], reference, configuration]) => {
+            if (!this._markerScene.has(marker.id)) {
+                return;
+            }
+            const element = this._container.container;
+            const [canvasX, canvasY] = this._viewportCoords
+                .canvasPosition(event, element);
+            const groundX = canvasX - offset[0];
+            const groundY = canvasY - offset[1];
+            const [viewportX, viewportY] = this._viewportCoords
+                .canvasToViewport(groundX, groundY, element);
+            const direction = new Vector3(viewportX, viewportY, 1)
+                .unproject(render.perspective)
+                .sub(render.perspective.position)
+                .normalize();
+            const distance = Math.min(this._relativeGroundAltitude / direction.z, configuration.visibleBBoxSize / 2 - 0.1);
+            if (distance < 0) {
+                return;
+            }
+            const intersection = direction
+                .clone()
+                .multiplyScalar(distance)
+                .add(render.perspective.position);
+            intersection.z =
+                render.perspective.position.z
+                    + this._relativeGroundAltitude;
+            const [lng, lat] = enuToGeodetic(intersection.x, intersection.y, intersection.z, reference.lng, reference.lat, reference.alt);
+            this._markerScene
+                .update(marker.id, intersection.toArray(), { lat, lng });
+            this._markerSet.update(marker);
+            const type = "markerposition";
+            const markerEvent = {
+                marker,
+                target: this,
+                type,
+            };
+            this.fire(type, markerEvent);
+        }));
+    }
+    _deactivate() {
+        this._subscriptions.unsubscribe();
+        this._markerScene.clear();
+    }
+    _getDefaultConfiguration() {
+        return { visibleBBoxSize: 100 };
+    }
+}
+MarkerComponent.componentName = "marker";
+
+function sign$1(n) {
+    return n > 0 ? 1 : n < 0 ? -1 : 0;
+}
+function colinearPointOnSegment(p, s) {
+    return p.x <= Math.max(s.p1.x, s.p2.x) &&
+        p.x >= Math.min(s.p1.x, s.p2.x) &&
+        p.y >= Math.max(s.p1.y, s.p2.y) &&
+        p.y >= Math.min(s.p1.y, s.p2.y);
+}
+function parallel(s1, s2) {
+    const ux = s1.p2.x - s1.p1.x;
+    const uy = s1.p2.y - s1.p1.y;
+    const vx = s2.p2.x - s2.p1.x;
+    const vy = s2.p2.y - s2.p1.y;
+    const cross = ux * vy - uy * vx;
+    const u2 = ux * ux + uy * uy;
+    const v2 = vx * vx + vy * vy;
+    const epsilon2 = 1e-10;
+    return cross * cross < epsilon2 * u2 * v2;
+}
+function tripletOrientation(p1, p2, p3) {
+    const orientation = (p2.y - p1.y) * (p3.x - p2.x) -
+        (p3.y - p2.y) * (p2.x - p1.x);
+    return sign$1(orientation);
+}
+function segmentsIntersect(s1, s2) {
+    if (parallel(s1, s2)) {
+        return false;
+    }
+    const o1 = tripletOrientation(s1.p1, s1.p2, s2.p1);
+    const o2 = tripletOrientation(s1.p1, s1.p2, s2.p2);
+    const o3 = tripletOrientation(s2.p1, s2.p2, s1.p1);
+    const o4 = tripletOrientation(s2.p1, s2.p2, s1.p2);
+    if (o1 !== o2 && o3 !== o4) {
+        return true;
+    }
+    if (o1 === 0 && colinearPointOnSegment(s2.p1, s1)) {
+        return true;
+    }
+    if (o2 === 0 && colinearPointOnSegment(s2.p2, s1)) {
+        return true;
+    }
+    if (o3 === 0 && colinearPointOnSegment(s1.p1, s2)) {
+        return true;
+    }
+    if (o4 === 0 && colinearPointOnSegment(s1.p2, s2)) {
+        return true;
+    }
+    return false;
+}
+function segmentIntersection(s1, s2) {
+    if (parallel(s1, s2)) {
+        return undefined;
+    }
+    const x1 = s1.p1.x;
+    const x2 = s1.p2.x;
+    const y1 = s1.p1.y;
+    const y2 = s1.p2.y;
+    const x3 = s2.p1.x;
+    const x4 = s2.p2.x;
+    const y3 = s2.p1.y;
+    const y4 = s2.p2.y;
+    const den = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
+    const xNum = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4);
+    const yNum = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4);
+    return { x: xNum / den, y: yNum / den };
+}
+
+function basicBoundaryPoints(pointsPerSide) {
+    let points = [];
+    let os = [[0, 0], [1, 0], [1, 1], [0, 1]];
+    let ds = [[1, 0], [0, 1], [-1, 0], [0, -1]];
+    for (let side = 0; side < 4; ++side) {
+        let o = os[side];
+        let d = ds[side];
+        for (let i = 0; i < pointsPerSide; ++i) {
+            points.push([o[0] + d[0] * i / pointsPerSide,
+                o[1] + d[1] * i / pointsPerSide]);
+        }
+    }
+    return points;
+}
+function insideViewport(x, y) {
+    return x >= -1 && x <= 1 && y >= -1 && y <= 1;
+}
+function insideBasic(x, y) {
+    return x >= 0 && x <= 1 && y >= 0 && y <= 1;
+}
+function viewportDistances(transform, perspective, viewportCoords) {
+    const boundaryPointsBasic = basicBoundaryPoints(100);
+    const boundaryPointsViewport = boundaryPointsBasic
+        .map((basic) => {
+        return viewportCoords.basicToViewportSafe(basic[0], basic[1], transform, perspective);
+    });
+    const visibleBoundaryPoints = [];
+    const viewportSides = [
+        { x: -1, y: 1 },
+        { x: 1, y: 1 },
+        { x: 1, y: -1 },
+        { x: -1, y: -1 }
+    ];
+    const intersections = [false, false, false, false];
+    for (let i = 0; i < boundaryPointsViewport.length; i++) {
+        const p1 = boundaryPointsViewport[i];
+        const p2 = boundaryPointsViewport[(i + 1) % boundaryPointsViewport.length];
+        if (p1 === null) {
+            continue;
+        }
+        if (p2 === null) {
+            if (insideViewport(p1[0], p1[1])) {
+                visibleBoundaryPoints.push(p1);
+            }
+            continue;
+        }
+        const [x1, y1] = p1;
+        const [x2, y2] = p2;
+        if (insideViewport(x1, y1)) {
+            if (insideViewport(x2, y2)) {
+                visibleBoundaryPoints.push(p1);
+            }
+            else {
+                for (let side = 0; side < 4; side++) {
+                    const s1 = { p1: { x: x1, y: y1 }, p2: { x: x2, y: y2 } };
+                    const s2 = { p1: viewportSides[side], p2: viewportSides[(side + 1) % 4] };
+                    const intersecting = segmentsIntersect(s1, s2);
+                    if (intersecting) {
+                        const intersection = segmentIntersection(s1, s2);
+                        visibleBoundaryPoints.push(p1, [intersection.x, intersection.y]);
+                        intersections[side] = true;
+                    }
+                }
+            }
+        }
+    }
+    const [topLeftBasicX, topLeftBasicY] = viewportCoords.viewportToBasic(-1, 1, transform, perspective);
+    const [topRightBasicX, topRightBasicY] = viewportCoords.viewportToBasic(1, 1, transform, perspective);
+    const [bottomRightBasicX, bottomRightBasicY] = viewportCoords.viewportToBasic(1, -1, transform, perspective);
+    const [bottomLeftBasicX, bottomLeftBasicY] = viewportCoords.viewportToBasic(-1, -1, transform, perspective);
+    if (insideBasic(topLeftBasicX, topLeftBasicY)) {
+        intersections[3] = intersections[0] = true;
+    }
+    if (insideBasic(topRightBasicX, topRightBasicY)) {
+        intersections[0] = intersections[1] = true;
+    }
+    if (insideBasic(bottomRightBasicX, bottomRightBasicY)) {
+        intersections[1] = intersections[2] = true;
+    }
+    if (insideBasic(bottomLeftBasicX, bottomLeftBasicY)) {
+        intersections[2] = intersections[3] = true;
+    }
+    const maximums = [-1, -1, 1, 1];
+    for (let visibleBoundaryPoint of visibleBoundaryPoints) {
+        const x = visibleBoundaryPoint[0];
+        const y = visibleBoundaryPoint[1];
+        if (x > maximums[1]) {
+            maximums[1] = x;
+        }
+        if (x < maximums[3]) {
+            maximums[3] = x;
+        }
+        if (y > maximums[0]) {
+            maximums[0] = y;
+        }
+        if (y < maximums[2]) {
+            maximums[2] = y;
+        }
+    }
+    const boundary = [1, 1, -1, -1];
+    const distances = [];
+    for (let side = 0; side < 4; side++) {
+        if (intersections[side]) {
+            distances.push(0);
+            continue;
+        }
+        distances.push(Math.abs(boundary[side] - maximums[side]));
+    }
+    return distances;
+}
+
+/**
+ * The `BounceHandler` ensures that the viewer bounces back to the image
+ * when drag panning outside of the image edge.
+ */
+class BounceHandler extends HandlerBase {
+    constructor(component, container, navigator, viewportCoords, spatial) {
+        super(component, container, navigator);
+        this._spatial = spatial;
+        this._viewportCoords = viewportCoords;
+    }
+    _enable() {
+        const inTransition$ = this._navigator.stateService.currentState$.pipe(map((frame) => {
+            return frame.state.alpha < 1;
+        }), distinctUntilChanged());
+        this._bounceSubscription = combineLatest(inTransition$, this._navigator.stateService.inTranslation$, this._container.mouseService.active$, this._container.touchService.active$).pipe(map((noForce) => {
+            return noForce[0] || noForce[1] || noForce[2] || noForce[3];
+        }), distinctUntilChanged(), switchMap((noForce) => {
+            return noForce ?
+                empty() :
+                combineLatest(this._container.renderService.renderCamera$, this._navigator.stateService.currentTransform$.pipe(first()));
+        }), withLatestFrom(this._navigator.panService.panImages$))
+            .subscribe(([[render, transform], nts]) => {
+            if (!transform.hasValidScale && render.camera.focal < 0.1) {
+                return;
+            }
+            if (render.perspective.aspect === 0 || render.perspective.aspect === Number.POSITIVE_INFINITY) {
+                return;
+            }
+            const distances = viewportDistances(transform, render.perspective, this._viewportCoords);
+            const basic = this._viewportCoords.viewportToBasic(0, 0, transform, render.perspective);
+            if ((basic[0] < 0 || basic[0] > 1) && nts.length > 0) {
+                distances[0] = distances[2] = 0;
+            }
+            for (const [, t] of nts) {
+                const d = viewportDistances(t, render.perspective, this._viewportCoords);
+                for (let i = 1; i < distances.length; i += 2) {
+                    if (d[i] < distances[i]) {
+                        distances[i] = d[i];
+                    }
+                }
+            }
+            if (Math.max(...distances) < 0.01) {
+                return;
+            }
+            const horizontalDistance = distances[1] - distances[3];
+            const verticalDistance = distances[0] - distances[2];
+            const currentDirection = this._viewportCoords
+                .unprojectFromViewport(0, 0, render.perspective)
+                .sub(render.perspective.position);
+            const directionPhi = this._viewportCoords
+                .unprojectFromViewport(horizontalDistance, 0, render.perspective)
+                .sub(render.perspective.position);
+            const directionTheta = this._viewportCoords
+                .unprojectFromViewport(0, verticalDistance, render.perspective)
+                .sub(render.perspective.position);
+            let phi = (horizontalDistance > 0 ? 1 : -1) * directionPhi.angleTo(currentDirection);
+            let theta = (verticalDistance > 0 ? 1 : -1) * directionTheta.angleTo(currentDirection);
+            const threshold = Math.PI / 60;
+            const coeff = 1e-1;
+            phi = this._spatial.clamp(coeff * phi, -threshold, threshold);
+            theta = this._spatial.clamp(coeff * theta, -threshold, threshold);
+            this._navigator.stateService.rotateUnbounded({ phi: phi, theta: theta });
+        });
+    }
+    _disable() {
+        this._bounceSubscription.unsubscribe();
+    }
+    _getConfiguration() {
+        return {};
+    }
+}
+
+class MouseOperator {
+    static filteredPairwiseMouseDrag$(name, mouseService) {
+        return this._filteredPairwiseMouseDrag$(name, mouseService, mouseService.mouseDragStart$, mouseService.mouseDrag$, mouseService.mouseDragEnd$);
+    }
+    static filteredPairwiseMouseRightDrag$(name, mouseService) {
+        return this._filteredPairwiseMouseDrag$(name, mouseService, mouseService.mouseRightDragStart$, mouseService.mouseRightDrag$, mouseService.mouseRightDragEnd$);
+    }
+    static _filteredPairwiseMouseDrag$(name, mouseService, mouseDragStart$, mouseDrag$, mouseDragEnd$) {
+        return mouseService
+            .filtered$(name, mouseDragStart$).pipe(switchMap((mouseDragStart) => {
+            const dragging$ = concat(of(mouseDragStart), mouseService
+                .filtered$(name, mouseDrag$));
+            const dragEnd$ = mouseService
+                .filtered$(name, mouseDragEnd$).pipe(map(() => {
+                return null;
+            }));
+            return merge(dragging$, dragEnd$).pipe(takeWhile((e) => {
+                return !!e;
+            }), startWith(null));
+        }), pairwise(), filter((pair) => {
+            return pair[0] != null && pair[1] != null;
+        }));
+    }
+}
+
+/**
+ * The `DragPanHandler` allows the user to pan the viewer image by clicking and dragging the cursor.
+ *
+ * @example
+ * ```js
+ * var pointerComponent = viewer.getComponent("pointer");
+ *
+ * pointerComponent.dragPan.disable();
+ * pointerComponent.dragPan.enable();
+ *
+ * var isEnabled = pointerComponent.dragPan.isEnabled;
+ * ```
+ */
+class DragPanHandler extends HandlerBase {
+    /** @ignore */
+    constructor(component, container, navigator, viewportCoords, spatial) {
+        super(component, container, navigator);
+        this._spatial = spatial;
+        this._viewportCoords = viewportCoords;
+    }
+    _enable() {
+        let draggingStarted$ = this._container.mouseService
+            .filtered$(this._component.name, this._container.mouseService.mouseDragStart$).pipe(map(() => {
+            return true;
+        }), share());
+        let draggingStopped$ = this._container.mouseService
+            .filtered$(this._component.name, this._container.mouseService.mouseDragEnd$).pipe(map(() => {
+            return false;
+        }), share());
+        this._activeMouseSubscription = merge(draggingStarted$, draggingStopped$)
+            .subscribe(this._container.mouseService.activate$);
+        const documentMouseMove$ = merge(draggingStarted$, draggingStopped$).pipe(switchMap((dragging) => {
+            return dragging ?
+                this._container.mouseService.documentMouseMove$ :
+                empty();
+        }));
+        this._preventDefaultSubscription = merge(documentMouseMove$, this._container.touchService.touchMove$)
+            .subscribe((event) => {
+            event.preventDefault(); // prevent selection of content outside the viewer
+        });
+        let touchMovingStarted$ = this._container.touchService.singleTouchDragStart$.pipe(map(() => {
+            return true;
+        }));
+        let touchMovingStopped$ = this._container.touchService.singleTouchDragEnd$.pipe(map(() => {
+            return false;
+        }));
+        this._activeTouchSubscription = merge(touchMovingStarted$, touchMovingStopped$)
+            .subscribe(this._container.touchService.activate$);
+        const rotation$ = this._navigator.stateService.currentState$.pipe(map((frame) => {
+            return isSpherical(frame.state.currentImage.cameraType) ||
+                frame.state.imagesAhead < 1;
+        }), distinctUntilChanged(), switchMap((enable) => {
+            if (!enable) {
+                return empty();
+            }
+            const mouseDrag$ = MouseOperator.filteredPairwiseMouseDrag$(this._component.name, this._container.mouseService);
+            const singleTouchDrag$ = merge(this._container.touchService.singleTouchDragStart$, this._container.touchService.singleTouchDrag$, this._container.touchService.singleTouchDragEnd$.pipe(map(() => { return null; }))).pipe(map((event) => {
+                return event != null && event.touches.length > 0 ?
+                    event.touches[0] : null;
+            }), pairwise(), filter((pair) => {
+                return pair[0] != null && pair[1] != null;
+            }));
+            return merge(mouseDrag$, singleTouchDrag$);
+        }), withLatestFrom(this._container.renderService.renderCamera$, this._navigator.stateService.currentTransform$, this._navigator.panService.panImages$), map(([events, render, transform, nts]) => {
+            let previousEvent = events[0];
+            let event = events[1];
+            let movementX = event.clientX - previousEvent.clientX;
+            let movementY = event.clientY - previousEvent.clientY;
+            let element = this._container.container;
+            let [canvasX, canvasY] = this._viewportCoords.canvasPosition(event, element);
+            let currentDirection = this._viewportCoords.unprojectFromCanvas(canvasX, canvasY, element, render.perspective)
+                .sub(render.perspective.position);
+            let directionX = this._viewportCoords.unprojectFromCanvas(canvasX - movementX, canvasY, element, render.perspective)
+                .sub(render.perspective.position);
+            let directionY = this._viewportCoords.unprojectFromCanvas(canvasX, canvasY - movementY, element, render.perspective)
+                .sub(render.perspective.position);
+            let phi = (movementX > 0 ? 1 : -1) * directionX.angleTo(currentDirection);
+            let theta = (movementY > 0 ? -1 : 1) * directionY.angleTo(currentDirection);
+            const distances = viewportDistances(transform, render.perspective, this._viewportCoords);
+            for (const [, t] of nts) {
+                const d = viewportDistances(t, render.perspective, this._viewportCoords);
+                for (let i = 0; i < distances.length; i++) {
+                    if (d[i] < distances[i]) {
+                        distances[i] = d[i];
+                    }
+                }
+            }
+            if (distances[0] > 0 && theta < 0) {
+                theta /= Math.max(1, 2e2 * distances[0]);
+            }
+            if (distances[2] > 0 && theta > 0) {
+                theta /= Math.max(1, 2e2 * distances[2]);
+            }
+            if (distances[1] > 0 && phi < 0) {
+                phi /= Math.max(1, 2e2 * distances[1]);
+            }
+            if (distances[3] > 0 && phi > 0) {
+                phi /= Math.max(1, 2e2 * distances[3]);
+            }
+            return { phi: phi, theta: theta };
+        }), share());
+        this._rotateWithoutInertiaSubscription = rotation$
+            .subscribe((rotation) => {
+            this._navigator.stateService.rotateWithoutInertia(rotation);
+        });
+        this._rotateSubscription = rotation$.pipe(scan((rotationBuffer, rotation) => {
+            this._drainBuffer(rotationBuffer);
+            rotationBuffer.push([Date.now(), rotation]);
+            return rotationBuffer;
+        }, []), sample(merge(this._container.mouseService.filtered$(this._component.name, this._container.mouseService.mouseDragEnd$), this._container.touchService.singleTouchDragEnd$)), map((rotationBuffer) => {
+            const drainedBuffer = this._drainBuffer(rotationBuffer.slice());
+            const rotation = { phi: 0, theta: 0 };
+            for (const bufferedRotation of drainedBuffer) {
+                rotation.phi += bufferedRotation[1].phi;
+                rotation.theta += bufferedRotation[1].theta;
+            }
+            const count = drainedBuffer.length;
+            if (count > 0) {
+                rotation.phi /= count;
+                rotation.theta /= count;
+            }
+            const threshold = Math.PI / 18;
+            rotation.phi = this._spatial.clamp(rotation.phi, -threshold, threshold);
+            rotation.theta = this._spatial.clamp(rotation.theta, -threshold, threshold);
+            return rotation;
+        }))
+            .subscribe((rotation) => {
+            this._navigator.stateService.rotate(rotation);
+        });
+    }
+    _disable() {
+        this._activeMouseSubscription.unsubscribe();
+        this._activeTouchSubscription.unsubscribe();
+        this._preventDefaultSubscription.unsubscribe();
+        this._rotateSubscription.unsubscribe();
+        this._rotateWithoutInertiaSubscription.unsubscribe();
+        this._activeMouseSubscription = null;
+        this._activeTouchSubscription = null;
+        this._preventDefaultSubscription = null;
+        this._rotateSubscription = null;
+    }
+    _getConfiguration(enable) {
+        return { dragPan: enable };
+    }
+    _drainBuffer(buffer) {
+        const cutoff = 50;
+        const now = Date.now();
+        while (buffer.length > 0 && now - buffer[0][0] > cutoff) {
+            buffer.shift();
+        }
+        return buffer;
+    }
+}
+
+class EarthControlHandler extends HandlerBase {
+    /** @ignore */
+    constructor(component, container, navigator, viewportCoords, spatial) {
+        super(component, container, navigator);
+        this._spatial = spatial;
+        this._viewportCoords = viewportCoords;
+        this._subscriptions = new SubscriptionHolder();
+    }
+    _enable() {
+        const earth$ = this._navigator.stateService.state$.pipe(map((state) => {
+            return state === State.Earth;
+        }), publishReplay(1), refCount());
+        const subs = this._subscriptions;
+        subs.push(earth$.pipe(switchMap((earth) => {
+            return earth ?
+                this._container.mouseService.mouseWheel$ :
+                empty();
+        }))
+            .subscribe((event) => {
+            event.preventDefault();
+        }));
+        subs.push(earth$.pipe(switchMap((earth) => {
+            if (!earth) {
+                return empty();
+            }
+            return MouseOperator.filteredPairwiseMouseDrag$(this._component.name, this._container.mouseService).pipe(filter(([e1, e2]) => {
+                return !(e1.ctrlKey && e2.ctrlKey);
+            }));
+        }), withLatestFrom(this._container.renderService.renderCamera$, this._navigator.stateService.currentTransform$), map(([[previous, current], render, transform]) => {
+            const planeNormal = [0, 0, 1];
+            const planePoint = [0, 0, -2];
+            const currentIntersection = this._planeIntersection(current, planeNormal, planePoint, render.perspective, this._container.container);
+            const previousIntersection = this._planeIntersection(previous, planeNormal, planePoint, render.perspective, this._container.container);
+            if (!currentIntersection || !previousIntersection) {
+                return null;
+            }
+            const direction = new Vector3()
+                .subVectors(currentIntersection, previousIntersection)
+                .multiplyScalar(-1)
+                .toArray();
+            return direction;
+        }), filter((direction) => {
+            return !!direction;
+        }))
+            .subscribe((direction) => {
+            this._navigator.stateService.truck(direction);
+        }));
+        subs.push(earth$.pipe(switchMap((earth) => {
+            if (!earth) {
+                return empty();
+            }
+            return MouseOperator.filteredPairwiseMouseDrag$(this._component.name, this._container.mouseService).pipe(filter(([e1, e2]) => {
+                return e1.ctrlKey && e2.ctrlKey;
+            }));
+        }), map(([previous, current]) => {
+            return this._mousePairToRotation(previous, current);
+        }))
+            .subscribe((rotation) => {
+            this._navigator.stateService.orbit(rotation);
+        }));
+        subs.push(earth$.pipe(switchMap((earth) => {
+            if (!earth) {
+                return empty();
+            }
+            return MouseOperator.filteredPairwiseMouseRightDrag$(this._component.name, this._container.mouseService).pipe(filter(([e1, e2]) => {
+                return !e1.ctrlKey && !e2.ctrlKey;
+            }));
+        }), map(([previous, current]) => {
+            return this._mousePairToRotation(previous, current);
+        }))
+            .subscribe((rotation) => {
+            this._navigator.stateService.orbit(rotation);
+        }));
+        subs.push(earth$.pipe(switchMap((earth) => {
+            if (!earth) {
+                return empty();
+            }
+            return this._container.mouseService
+                .filteredWheel$(this._component.name, this._container.mouseService.mouseWheel$);
+        }), map((event) => {
+            let delta = event.deltaY;
+            if (event.deltaMode === 1) {
+                delta = 40 * delta;
+            }
+            else if (event.deltaMode === 2) {
+                delta = 800 * delta;
+            }
+            const canvasSize = this._viewportCoords.containerToCanvas(this._container.container);
+            return -delta / canvasSize[1];
+        }))
+            .subscribe((delta) => {
+            this._navigator.stateService.dolly(delta);
+        }));
+    }
+    _disable() {
+        this._subscriptions.unsubscribe();
+    }
+    _getConfiguration() {
+        return {};
+    }
+    _eventToViewport(event, element) {
+        const previousCanvas = this._viewportCoords.canvasPosition(event, element);
+        return this._viewportCoords.canvasToViewport(previousCanvas[0], previousCanvas[1], element);
+    }
+    _mousePairToRotation(previous, current) {
+        const [currentX, currentY] = this._eventToViewport(current, this._container.container);
+        const [previousX, previousY] = this._eventToViewport(previous, this._container.container);
+        const phi = (previousX - currentX) * Math.PI;
+        const theta = (currentY - previousY) * Math.PI / 2;
+        return { phi: phi, theta: theta };
+    }
+    _planeIntersection(event, planeNormal, planePoint, camera, element) {
+        const [canvasX, canvasY] = this._viewportCoords.canvasPosition(event, element);
+        const direction = this._viewportCoords
+            .unprojectFromCanvas(canvasX, canvasY, element, camera)
+            .sub(camera.position)
+            .normalize();
+        if (Math.abs(this._spatial.angleToPlane(direction.toArray(), planeNormal)) < Math.PI / 90) {
+            return null;
+        }
+        const l0 = camera.position.clone();
+        const n = new Vector3().fromArray(planeNormal);
+        const p0 = new Vector3().fromArray(planePoint);
+        const d = new Vector3().subVectors(p0, l0).dot(n) / direction.clone().dot(n);
+        const intersection = new Vector3().addVectors(l0, direction.multiplyScalar(d));
+        if (this._viewportCoords.worldToCamera(intersection.toArray(), camera)[2] > 0) {
+            return null;
+        }
+        return intersection;
+    }
+}
+
+/**
+ * The `ScrollZoomHandler` allows the user to zoom the viewer image by scrolling.
+ *
+ * @example
+ * ```js
+ * var pointerComponent = viewer.getComponent("pointer");
+ *
+ * pointerComponent.scrollZoom.disable();
+ * pointerComponent.scrollZoom.enable();
+ *
+ * var isEnabled = pointerComponent.scrollZoom.isEnabled;
+ * ```
+ */
+class ScrollZoomHandler extends HandlerBase {
+    /** @ignore */
+    constructor(component, container, navigator, viewportCoords) {
+        super(component, container, navigator);
+        this._viewportCoords = viewportCoords;
+    }
+    _enable() {
+        this._container.mouseService.claimWheel(this._component.name, 0);
+        this._preventDefaultSubscription = this._container.mouseService.mouseWheel$
+            .subscribe((event) => {
+            event.preventDefault();
+        });
+        this._zoomSubscription = this._container.mouseService
+            .filteredWheel$(this._component.name, this._container.mouseService.mouseWheel$).pipe(withLatestFrom(this._navigator.stateService.currentState$, (w, f) => {
+            return [w, f];
+        }), filter((args) => {
+            let state = args[1].state;
+            return isSpherical(state.currentImage.cameraType) ||
+                state.imagesAhead < 1;
+        }), map((args) => {
+            return args[0];
+        }), withLatestFrom(this._container.renderService.renderCamera$, this._navigator.stateService.currentTransform$, (w, r, t) => {
+            return [w, r, t];
+        }))
+            .subscribe((args) => {
+            let event = args[0];
+            let render = args[1];
+            let transform = args[2];
+            let element = this._container.container;
+            let [canvasX, canvasY] = this._viewportCoords.canvasPosition(event, element);
+            let unprojected = this._viewportCoords.unprojectFromCanvas(canvasX, canvasY, element, render.perspective);
+            let reference = transform.projectBasic(unprojected.toArray());
+            let deltaY = event.deltaY;
+            if (event.deltaMode === 1) {
+                deltaY = 40 * deltaY;
+            }
+            else if (event.deltaMode === 2) {
+                deltaY = 800 * deltaY;
+            }
+            const canvasSize = this._viewportCoords.containerToCanvas(element);
+            let zoom = -3 * deltaY / canvasSize[1];
+            this._navigator.stateService.zoomIn(zoom, reference);
+        });
+    }
+    _disable() {
+        this._container.mouseService.unclaimWheel(this._component.name);
+        this._preventDefaultSubscription.unsubscribe();
+        this._zoomSubscription.unsubscribe();
+        this._preventDefaultSubscription = null;
+        this._zoomSubscription = null;
+    }
+    _getConfiguration(enable) {
+        return { scrollZoom: enable };
+    }
+}
+
+/**
+ * The `TouchZoomHandler` allows the user to zoom the viewer image by pinching on a touchscreen.
+ *
+ * @example
+ * ```js
+ * var pointerComponent = viewer.getComponent("pointer");
+ *
+ * pointerComponent.touchZoom.disable();
+ * pointerComponent.touchZoom.enable();
+ *
+ * var isEnabled = pointerComponent.touchZoom.isEnabled;
+ * ```
+ */
+class TouchZoomHandler extends HandlerBase {
+    /** @ignore */
+    constructor(component, container, navigator, viewportCoords) {
+        super(component, container, navigator);
+        this._viewportCoords = viewportCoords;
+    }
+    _enable() {
+        this._preventDefaultSubscription = this._container.touchService.pinch$
+            .subscribe((pinch) => {
+            pinch.originalEvent.preventDefault();
+        });
+        let pinchStarted$ = this._container.touchService.pinchStart$.pipe(map((event) => {
+            return true;
+        }));
+        let pinchStopped$ = this._container.touchService.pinchEnd$.pipe(map((event) => {
+            return false;
+        }));
+        this._activeSubscription = merge(pinchStarted$, pinchStopped$)
+            .subscribe(this._container.touchService.activate$);
+        this._zoomSubscription = this._container.touchService.pinch$.pipe(withLatestFrom(this._navigator.stateService.currentState$), filter((args) => {
+            let state = args[1].state;
+            return isSpherical(state.currentImage.cameraType) ||
+                state.imagesAhead < 1;
+        }), map((args) => {
+            return args[0];
+        }), withLatestFrom(this._container.renderService.renderCamera$, this._navigator.stateService.currentTransform$))
+            .subscribe(([pinch, render, transform]) => {
+            let element = this._container.container;
+            let [canvasX, canvasY] = this._viewportCoords.canvasPosition(pinch, element);
+            let unprojected = this._viewportCoords.unprojectFromCanvas(canvasX, canvasY, element, render.perspective);
+            let reference = transform.projectBasic(unprojected.toArray());
+            const [canvasWidth, canvasHeight] = this._viewportCoords.containerToCanvas(element);
+            let zoom = 3 * pinch.distanceChange / Math.min(canvasWidth, canvasHeight);
+            this._navigator.stateService.zoomIn(zoom, reference);
+        });
+    }
+    _disable() {
+        this._activeSubscription.unsubscribe();
+        this._preventDefaultSubscription.unsubscribe();
+        this._zoomSubscription.unsubscribe();
+        this._preventDefaultSubscription = null;
+        this._zoomSubscription = null;
+    }
+    _getConfiguration(enable) {
+        return { touchZoom: enable };
+    }
+}
+
+/**
+ * @class PointerComponent
+ *
+ * @classdesc Component handling mouse, pen, and touch events for camera movement.
+ *
+ * To retrive and use the mouse component
+ *
+ * @example
+ * ```js
+ * var viewer = new Viewer({ ... });
+ *
+ * var pointerComponent = viewer.getComponent("pointer");
+ * ```
+ */
+class PointerComponent extends Component {
+    /** @ignore */
+    constructor(name, container, navigator) {
+        super(name, container, navigator);
+        const spatial = new Spatial();
+        const viewportCoords = new ViewportCoords();
+        this._bounceHandler =
+            new BounceHandler(this, container, navigator, viewportCoords, spatial);
+        this._dragPanHandler =
+            new DragPanHandler(this, container, navigator, viewportCoords, spatial);
+        this._earthControlHandler =
+            new EarthControlHandler(this, container, navigator, viewportCoords, spatial);
+        this._scrollZoomHandler =
+            new ScrollZoomHandler(this, container, navigator, viewportCoords);
+        this._touchZoomHandler =
+            new TouchZoomHandler(this, container, navigator, viewportCoords);
+    }
+    /**
+     * Get drag pan.
+     *
+     * @returns {DragPanHandler} The drag pan handler.
+     */
+    get dragPan() {
+        return this._dragPanHandler;
+    }
+    /**
+     * Get earth control.
+     *
+     * @returns {EarthControlHandler} The earth control handler.
+     */
+    get earthControl() {
+        return this._earthControlHandler;
+    }
+    /**
+     * Get scroll zoom.
+     *
+     * @returns {ScrollZoomHandler} The scroll zoom handler.
+     */
+    get scrollZoom() {
+        return this._scrollZoomHandler;
+    }
+    /**
+     * Get touch zoom.
+     *
+     * @returns {TouchZoomHandler} The touch zoom handler.
+     */
+    get touchZoom() {
+        return this._touchZoomHandler;
+    }
+    _activate() {
+        this._bounceHandler.enable();
+        this._subscriptions.push(this._configuration$
+            .subscribe((configuration) => {
+            if (configuration.dragPan) {
+                this._dragPanHandler.enable();
+            }
+            else {
+                this._dragPanHandler.disable();
+            }
+            if (configuration.earthControl) {
+                this._earthControlHandler.enable();
+            }
+            else {
+                this._earthControlHandler.disable();
+            }
+            if (configuration.scrollZoom) {
+                this._scrollZoomHandler.enable();
+            }
+            else {
+                this._scrollZoomHandler.disable();
+            }
+            if (configuration.touchZoom) {
+                this._touchZoomHandler.enable();
+            }
+            else {
+                this._touchZoomHandler.disable();
+            }
+        }));
+        this._container.mouseService.claimMouse(this._name, 0);
+    }
+    _deactivate() {
+        this._container.mouseService.unclaimMouse(this._name);
+        this._subscriptions.unsubscribe();
+        this._bounceHandler.disable();
+        this._dragPanHandler.disable();
+        this._earthControlHandler.disable();
+        this._scrollZoomHandler.disable();
+        this._touchZoomHandler.disable();
+    }
+    _getDefaultConfiguration() {
+        return {
+            dragPan: true,
+            earthControl: true,
+            scrollZoom: true,
+            touchZoom: true,
+        };
+    }
+}
+/** @inheritdoc */
+PointerComponent.componentName = "pointer";
+
+class DOM {
+    constructor(doc) {
+        this._document = !!doc ? doc : document;
+    }
+    get document() {
+        return this._document;
+    }
+    createElement(tagName, className, container) {
+        const element = this._document.createElement(tagName);
+        if (!!className) {
+            element.className = className;
+        }
+        if (!!container) {
+            container.appendChild(element);
+        }
+        return element;
+    }
+}
+
+/**
+ * @class PopupComponent
+ *
+ * @classdesc Component for showing HTML popup objects.
+ *
+ * The `add` method is used for adding new popups. Popups are removed by reference.
+ *
+ * It is not possible to update popups in the set by updating any properties
+ * directly on the popup object. Popups need to be replaced by
+ * removing them and creating new ones with relevant changed properties and
+ * adding those instead.
+ *
+ * Popups are only relevant to a single image because they are based on
+ * 2D basic image coordinates. Popups related to a certain image should
+ * be removed when the viewer is moved to another image.
+ *
+ * To retrive and use the popup component
+ *
+ * @example
+ * ```js
+ * var viewer = new Viewer({ component: { popup: true }, ... });
+ *
+ * var popupComponent = viewer.getComponent("popup");
+ * ```
+ */
+class PopupComponent extends Component {
+    /** @ignore */
+    constructor(name, container, navigator, dom) {
+        super(name, container, navigator);
+        this._dom = !!dom ? dom : new DOM();
+        this._popups = [];
+        this._added$ = new Subject();
+        this._popups$ = new Subject();
+    }
+    /**
+     * Add popups to the popups set.
+     *
+     * @description Adding a new popup never replaces an old one
+     * because they are stored by reference. Adding an already
+     * existing popup has no effect.
+     *
+     * @param {Array<Popup>} popups - Popups to add.
+     *
+     * @example
+     * ```js
+     * popupComponent.add([popup1, popup2]);
+     * ```
+     */
+    add(popups) {
+        for (const popup of popups) {
+            if (this._popups.indexOf(popup) !== -1) {
+                continue;
+            }
+            this._popups.push(popup);
+            if (this._activated) {
+                popup.setParentContainer(this._popupContainer);
+            }
+        }
+        this._added$.next(popups);
+        this._popups$.next(this._popups);
+    }
+    /**
+     * Returns an array of all popups.
+     *
+     * @example
+     * ```js
+     * var popups = popupComponent.getAll();
+     * ```
+     */
+    getAll() {
+        return this._popups.slice();
+    }
+    /**
+     * Remove popups based on reference from the popup set.
+     *
+     * @param {Array<Popup>} popups - Popups to remove.
+     *
+     * @example
+     * ```js
+     * popupComponent.remove([popup1, popup2]);
+     * ```
+     */
+    remove(popups) {
+        for (const popup of popups) {
+            this._remove(popup);
+        }
+        this._popups$.next(this._popups);
+    }
+    /**
+     * Remove all popups from the popup set.
+     *
+     * @example
+     * ```js
+     * popupComponent.removeAll();
+     * ```
+     */
+    removeAll() {
+        for (const popup of this._popups.slice()) {
+            this._remove(popup);
+        }
+        this._popups$.next(this._popups);
+    }
+    _activate() {
+        this._popupContainer = this._dom.createElement("div", "mapillary-popup-container", this._container.container);
+        for (const popup of this._popups) {
+            popup.setParentContainer(this._popupContainer);
+        }
+        const subs = this._subscriptions;
+        subs.push(combineLatest(this._container.renderService.renderCamera$, this._container.renderService.size$, this._navigator.stateService.currentTransform$)
+            .subscribe(([renderCamera, size, transform]) => {
+            for (const popup of this._popups) {
+                popup.update(renderCamera, size, transform);
+            }
+        }));
+        const changed$ = this._popups$.pipe(startWith(this._popups), switchMap((popups) => {
+            return from(popups).pipe(mergeMap((popup) => {
+                return popup.changed$;
+            }));
+        }), map((popup) => {
+            return [popup];
+        }));
+        subs.push(merge(this._added$, changed$).pipe(withLatestFrom(this._container.renderService.renderCamera$, this._container.renderService.size$, this._navigator.stateService.currentTransform$))
+            .subscribe(([popups, renderCamera, size, transform]) => {
+            for (const popup of popups) {
+                popup.update(renderCamera, size, transform);
+            }
+        }));
+    }
+    _deactivate() {
+        this._subscriptions.unsubscribe();
+        for (const popup of this._popups) {
+            popup.remove();
+        }
+        this._container.container.removeChild(this._popupContainer);
+        delete this._popupContainer;
+    }
+    _getDefaultConfiguration() {
+        return {};
+    }
+    _remove(popup) {
+        const index = this._popups.indexOf(popup);
+        if (index === -1) {
+            return;
+        }
+        const removed = this._popups.splice(index, 1)[0];
+        if (this._activated) {
+            removed.remove();
+        }
+    }
+}
+PopupComponent.componentName = "popup";
+
+/**
+ * Enumeration for graph modes.
+ * @enum {number}
+ * @readonly
+ * @description Modes for the retrieval and caching performed
+ * by the graph service on the graph.
+ */
+var GraphMode;
+(function (GraphMode) {
+    /**
+     * Caching is performed on sequences only and sequence edges are
+     * calculated. Spatial tiles
+     * are not retrieved and spatial edges are not calculated when
+     * caching nodes. Complete sequences are being cached for requested
+     * nodes within the graph.
+     */
+    GraphMode[GraphMode["Sequence"] = 0] = "Sequence";
+    /**
+     * Caching is performed with emphasis on spatial data. Sequence edges
+     * as well as spatial edges are cached. Sequence data
+     * is still requested but complete sequences are not being cached
+     * for requested nodes.
+     *
+     * This is the initial mode of the graph service.
+     */
+    GraphMode[GraphMode["Spatial"] = 1] = "Spatial";
+})(GraphMode || (GraphMode = {}));
+
+var SequenceMode;
+(function (SequenceMode) {
+    SequenceMode[SequenceMode["Default"] = 0] = "Default";
+    SequenceMode[SequenceMode["Playback"] = 1] = "Playback";
+    SequenceMode[SequenceMode["Timeline"] = 2] = "Timeline";
+})(SequenceMode || (SequenceMode = {}));
+
+class SequenceDOMRenderer {
+    constructor(container) {
+        this._container = container;
+        this._minThresholdWidth = 320;
+        this._maxThresholdWidth = 1480;
+        this._minThresholdHeight = 240;
+        this._maxThresholdHeight = 820;
+        this._stepperDefaultWidth = 108;
+        this._controlsDefaultWidth = 88;
+        this._defaultHeight = 30;
+        this._expandControls = false;
+        this._mode = SequenceMode.Default;
+        this._speed = 0.5;
+        this._changingSpeed = false;
+        this._index = null;
+        this._changingPosition = false;
+        this._mouseEnterDirection$ = new Subject();
+        this._mouseLeaveDirection$ = new Subject();
+        this._notifyChanged$ = new Subject();
+        this._notifyChangingPositionChanged$ = new Subject();
+        this._notifySpeedChanged$ = new Subject();
+        this._notifyIndexChanged$ = new Subject();
+    }
+    get changed$() {
+        return this._notifyChanged$;
+    }
+    get changingPositionChanged$() {
+        return this._notifyChangingPositionChanged$;
+    }
+    get speed$() {
+        return this._notifySpeedChanged$;
+    }
+    get index$() {
+        return this._notifyIndexChanged$;
+    }
+    get mouseEnterDirection$() {
+        return this._mouseEnterDirection$;
+    }
+    get mouseLeaveDirection$() {
+        return this._mouseLeaveDirection$;
+    }
+    activate() {
+        if (!!this._changingSubscription) {
+            return;
+        }
+        this._changingSubscription = merge(this._container.mouseService.documentMouseUp$, this._container.touchService.touchEnd$.pipe(filter((touchEvent) => {
+            return touchEvent.touches.length === 0;
+        })))
+            .subscribe(() => {
+            if (this._changingSpeed) {
+                this._changingSpeed = false;
+            }
+            if (this._changingPosition) {
+                this._setChangingPosition(false);
+            }
+        });
+    }
+    deactivate() {
+        if (!this._changingSubscription) {
+            return;
+        }
+        this._changingSpeed = false;
+        this._changingPosition = false;
+        this._expandControls = false;
+        this._mode = SequenceMode.Default;
+        this._changingSubscription.unsubscribe();
+        this._changingSubscription = null;
+    }
+    render(edgeStatus, configuration, containerWidth, speed, index, max, playEnabled, component, navigator) {
+        if (configuration.visible === false) {
+            return virtualDom.h("div.mapillary-sequence-container", {}, []);
+        }
+        const stepper = this._createStepper(edgeStatus, configuration, playEnabled, containerWidth, component, navigator);
+        const controls = this._createSequenceControls(containerWidth);
+        const playback = this._createPlaybackControls(containerWidth, speed, component, configuration);
+        const timeline = this._createTimelineControls(containerWidth, index, max);
+        return virtualDom.h("div.mapillary-sequence-container", [stepper, controls, playback, timeline]);
+    }
+    getContainerWidth(size, configuration) {
+        let minWidth = configuration.minWidth;
+        let maxWidth = configuration.maxWidth;
+        if (maxWidth < minWidth) {
+            maxWidth = minWidth;
+        }
+        let relativeWidth = (size.width - this._minThresholdWidth) / (this._maxThresholdWidth - this._minThresholdWidth);
+        let relativeHeight = (size.height - this._minThresholdHeight) / (this._maxThresholdHeight - this._minThresholdHeight);
+        let coeff = Math.max(0, Math.min(1, Math.min(relativeWidth, relativeHeight)));
+        return minWidth + coeff * (maxWidth - minWidth);
+    }
+    _createPositionInput(index, max) {
+        this._index = index;
+        const onPosition = (e) => {
+            this._index = Number(e.target.value);
+            this._notifyIndexChanged$.next(this._index);
+        };
+        const boundingRect = this._container.domContainer.getBoundingClientRect();
+        const width = Math.max(276, Math.min(410, 5 + 0.8 * boundingRect.width)) - 65;
+        const onStart = (e) => {
+            e.stopPropagation();
+            this._setChangingPosition(true);
+        };
+        const onMove = (e) => {
+            if (this._changingPosition === true) {
+                e.stopPropagation();
+            }
+        };
+        const onKeyDown = (e) => {
+            if (e.key === "ArrowDown" || e.key === "ArrowLeft" ||
+                e.key === "ArrowRight" || e.key === "ArrowUp") {
+                e.preventDefault();
+            }
+        };
+        const positionInputProperties = {
+            max: max != null ? max : 1,
+            min: 0,
+            onchange: onPosition,
+            oninput: onPosition,
+            onkeydown: onKeyDown,
+            onpointerdown: onStart,
+            onpointermove: onMove,
+            ontouchmove: onMove,
+            ontouchstart: onStart,
+            style: {
+                width: `${width}px`,
+            },
+            type: "range",
+            value: index != null ? index : 0,
+        };
+        const disabled = index == null || max == null || max <= 1;
+        if (disabled) {
+            positionInputProperties.disabled = "true";
+        }
+        const positionInput = virtualDom.h("input.mapillary-sequence-position", positionInputProperties, []);
+        const positionContainerClass = disabled ? ".mapillary-sequence-position-container-inactive" : ".mapillary-sequence-position-container";
+        return virtualDom.h("div" + positionContainerClass, [positionInput]);
+    }
+    _createSpeedInput(speed) {
+        this._speed = speed;
+        const onSpeed = (e) => {
+            this._speed = Number(e.target.value) / 1000;
+            this._notifySpeedChanged$.next(this._speed);
+        };
+        const boundingRect = this._container.domContainer.getBoundingClientRect();
+        const width = Math.max(276, Math.min(410, 5 + 0.8 * boundingRect.width)) - 160;
+        const onStart = (e) => {
+            this._changingSpeed = true;
+            e.stopPropagation();
+        };
+        const onMove = (e) => {
+            if (this._changingSpeed === true) {
+                e.stopPropagation();
+            }
+        };
+        const onKeyDown = (e) => {
+            if (e.key === "ArrowDown" || e.key === "ArrowLeft" ||
+                e.key === "ArrowRight" || e.key === "ArrowUp") {
+                e.preventDefault();
+            }
+        };
+        const speedInput = virtualDom.h("input.mapillary-sequence-speed", {
+            max: 1000,
+            min: 0,
+            onchange: onSpeed,
+            oninput: onSpeed,
+            onkeydown: onKeyDown,
+            onpointerdown: onStart,
+            onpointermove: onMove,
+            ontouchmove: onMove,
+            ontouchstart: onStart,
+            style: {
+                width: `${width}px`,
+            },
+            type: "range",
+            value: 1000 * speed,
+        }, []);
+        return virtualDom.h("div.mapillary-sequence-speed-container", [speedInput]);
+    }
+    _createPlaybackControls(containerWidth, speed, component, configuration) {
+        if (this._mode !== SequenceMode.Playback) {
+            return virtualDom.h("div.mapillary-sequence-playback", []);
+        }
+        const switchIcon = virtualDom.h("div.mapillary-sequence-switch-icon.mapillary-sequence-icon-visible", []);
+        const direction = configuration.direction === NavigationDirection.Next ?
+            NavigationDirection.Prev : NavigationDirection.Next;
+        const playing = configuration.playing;
+        const switchButtonProperties = {
+            onclick: () => {
+                if (!playing) {
+                    component.configure({ direction });
+                }
+            },
+        };
+        const switchButtonClassName = configuration.playing ? ".mapillary-sequence-switch-button-inactive" : ".mapillary-sequence-switch-button";
+        const switchButton = virtualDom.h("div" + switchButtonClassName, switchButtonProperties, [switchIcon]);
+        const slowIcon = virtualDom.h("div.mapillary-sequence-slow-icon.mapillary-sequence-icon-visible", []);
+        const slowContainer = virtualDom.h("div.mapillary-sequence-slow-container", [slowIcon]);
+        const fastIcon = virtualDom.h("div.mapillary-sequence-fast-icon.mapillary-sequence-icon-visible", []);
+        const fastContainer = virtualDom.h("div.mapillary-sequence-fast-container", [fastIcon]);
+        const closeIcon = virtualDom.h("div.mapillary-sequence-close-icon.mapillary-sequence-icon-visible", []);
+        const closeButtonProperties = {
+            onclick: () => {
+                this._mode = SequenceMode.Default;
+                this._notifyChanged$.next(this);
+            },
+        };
+        const closeButton = virtualDom.h("div.mapillary-sequence-close-button", closeButtonProperties, [closeIcon]);
+        const speedInput = this._createSpeedInput(speed);
+        const playbackChildren = [switchButton, slowContainer, speedInput, fastContainer, closeButton];
+        const top = Math.round(containerWidth / this._stepperDefaultWidth * this._defaultHeight + 10);
+        const playbackProperties = { style: { top: `${top}px` } };
+        return virtualDom.h("div.mapillary-sequence-playback", playbackProperties, playbackChildren);
+    }
+    _createPlayingButton(nextId, prevId, playEnabled, configuration, component) {
+        let canPlay = (configuration.direction === NavigationDirection.Next && nextId != null) ||
+            (configuration.direction === NavigationDirection.Prev && prevId != null);
+        canPlay = canPlay && playEnabled;
+        let onclick = configuration.playing ?
+            () => { component.stop(); } :
+            canPlay ? () => { component.play(); } : null;
+        let buttonProperties = { onclick: onclick };
+        let iconProperties = {};
+        if (configuration.direction === NavigationDirection.Prev) {
+            iconProperties.style = {
+                transform: "rotate(180deg) translate(50%, 50%)",
+            };
+        }
+        let icon = virtualDom.h("div.mapillary-sequence-icon", iconProperties, []);
+        let buttonClass = configuration.playing ?
+            "mapillary-sequence-stop" :
+            canPlay ?
+                "mapillary-sequence-play" :
+                "mapillary-sequence-play-inactive";
+        return virtualDom.h("div." + buttonClass, buttonProperties, [icon]);
+    }
+    _createSequenceControls(containerWidth) {
+        const borderRadius = Math.round(8 / this._stepperDefaultWidth * containerWidth);
+        const expanderProperties = {
+            onclick: () => {
+                this._expandControls = !this._expandControls;
+                this._mode = SequenceMode.Default;
+                this._notifyChanged$.next(this);
+            },
+            style: {
+                "border-bottom-right-radius": `${borderRadius}px`,
+                "border-top-right-radius": `${borderRadius}px`,
+            },
+        };
+        const expanderBar = virtualDom.h("div.mapillary-sequence-expander-bar", []);
+        const expander = virtualDom.h("div.mapillary-sequence-expander-button", expanderProperties, [expanderBar]);
+        const fastIconClassName = this._mode === SequenceMode.Playback ?
+            ".mapillary-sequence-fast-icon-gray.mapillary-sequence-icon-visible" : ".mapillary-sequence-fast-icon";
+        const fastIcon = virtualDom.h("div" + fastIconClassName, []);
+        const playbackProperties = {
+            onclick: () => {
+                this._mode = this._mode === SequenceMode.Playback ?
+                    SequenceMode.Default :
+                    SequenceMode.Playback;
+                this._notifyChanged$.next(this);
+            },
+        };
+        const playback = virtualDom.h("div.mapillary-sequence-playback-button", playbackProperties, [fastIcon]);
+        const timelineIconClassName = this._mode === SequenceMode.Timeline ?
+            ".mapillary-sequence-timeline-icon-gray.mapillary-sequence-icon-visible" : ".mapillary-sequence-timeline-icon";
+        const timelineIcon = virtualDom.h("div" + timelineIconClassName, []);
+        const timelineProperties = {
+            onclick: () => {
+                this._mode = this._mode === SequenceMode.Timeline ?
+                    SequenceMode.Default :
+                    SequenceMode.Timeline;
+                this._notifyChanged$.next(this);
+            },
+        };
+        const timeline = virtualDom.h("div.mapillary-sequence-timeline-button", timelineProperties, [timelineIcon]);
+        const properties = {
+            style: {
+                height: (this._defaultHeight / this._stepperDefaultWidth * containerWidth) + "px",
+                transform: `translate(${containerWidth / 2 + 2}px, 0)`,
+                width: (this._controlsDefaultWidth / this._stepperDefaultWidth * containerWidth) + "px",
+            },
+        };
+        const className = ".mapillary-sequence-controls" +
+            (this._expandControls ? ".mapillary-sequence-controls-expanded" : "");
+        return virtualDom.h("div" + className, properties, [playback, timeline, expander]);
+    }
+    _createSequenceArrows(nextId, prevId, containerWidth, configuration, navigator) {
+        let nextProperties = {
+            onclick: nextId != null ?
+                () => {
+                    navigator.moveDir$(NavigationDirection.Next)
+                        .subscribe(undefined, (error) => {
+                        if (!(error instanceof CancelMapillaryError)) {
+                            console.error(error);
+                        }
+                    });
+                } :
+                null,
+            onpointerenter: () => { this._mouseEnterDirection$.next(NavigationDirection.Next); },
+            onpointerleave: () => { this._mouseLeaveDirection$.next(NavigationDirection.Next); },
+        };
+        const borderRadius = Math.round(8 / this._stepperDefaultWidth * containerWidth);
+        let prevProperties = {
+            onclick: prevId != null ?
+                () => {
+                    navigator.moveDir$(NavigationDirection.Prev)
+                        .subscribe(undefined, (error) => {
+                        if (!(error instanceof CancelMapillaryError)) {
+                            console.error(error);
+                        }
+                    });
+                } :
+                null,
+            onpointerenter: () => { this._mouseEnterDirection$.next(NavigationDirection.Prev); },
+            onpointerleave: () => { this._mouseLeaveDirection$.next(NavigationDirection.Prev); },
+            style: {
+                "border-bottom-left-radius": `${borderRadius}px`,
+                "border-top-left-radius": `${borderRadius}px`,
+            },
+        };
+        let nextClass = this._getStepClassName(NavigationDirection.Next, nextId, configuration.highlightId);
+        let prevClass = this._getStepClassName(NavigationDirection.Prev, prevId, configuration.highlightId);
+        let nextIcon = virtualDom.h("div.mapillary-sequence-icon", []);
+        let prevIcon = virtualDom.h("div.mapillary-sequence-icon", []);
+        return [
+            virtualDom.h("div." + prevClass, prevProperties, [prevIcon]),
+            virtualDom.h("div." + nextClass, nextProperties, [nextIcon]),
+        ];
+    }
+    _createStepper(edgeStatus, configuration, playEnabled, containerWidth, component, navigator) {
+        let nextId = null;
+        let prevId = null;
+        for (let edge of edgeStatus.edges) {
+            if (edge.data.direction === NavigationDirection.Next) {
+                nextId = edge.target;
+            }
+            if (edge.data.direction === NavigationDirection.Prev) {
+                prevId = edge.target;
+            }
+        }
+        const playingButton = this._createPlayingButton(nextId, prevId, playEnabled, configuration, component);
+        const buttons = this._createSequenceArrows(nextId, prevId, containerWidth, configuration, navigator);
+        buttons.splice(1, 0, playingButton);
+        const containerProperties = {
+            oncontextmenu: (event) => { event.preventDefault(); },
+            style: {
+                height: (this._defaultHeight / this._stepperDefaultWidth * containerWidth) + "px",
+                width: containerWidth + "px",
+            },
+        };
+        return virtualDom.h("div.mapillary-sequence-stepper", containerProperties, buttons);
+    }
+    _createTimelineControls(containerWidth, index, max) {
+        if (this._mode !== SequenceMode.Timeline) {
+            return virtualDom.h("div.mapillary-sequence-timeline", []);
+        }
+        const positionInput = this._createPositionInput(index, max);
+        const closeIcon = virtualDom.h("div.mapillary-sequence-close-icon.mapillary-sequence-icon-visible", []);
+        const closeButtonProperties = {
+            onclick: () => {
+                this._mode = SequenceMode.Default;
+                this._notifyChanged$.next(this);
+            },
+        };
+        const closeButton = virtualDom.h("div.mapillary-sequence-close-button", closeButtonProperties, [closeIcon]);
+        const top = Math.round(containerWidth / this._stepperDefaultWidth * this._defaultHeight + 10);
+        const playbackProperties = { style: { top: `${top}px` } };
+        return virtualDom.h("div.mapillary-sequence-timeline", playbackProperties, [positionInput, closeButton]);
+    }
+    _getStepClassName(direction, imageId, highlightId) {
+        let className = direction === NavigationDirection.Next ?
+            "mapillary-sequence-step-next" :
+            "mapillary-sequence-step-prev";
+        if (imageId == null) {
+            className += "-inactive";
+        }
+        else {
+            if (highlightId === imageId) {
+                className += "-highlight";
+            }
+        }
+        return className;
+    }
+    _setChangingPosition(value) {
+        this._changingPosition = value;
+        this._notifyChangingPositionChanged$.next(value);
+    }
+}
+
+/**
+ * @class SequenceComponent
+ * @classdesc Component showing navigation arrows for sequence directions
+ * as well as playing button. Exposes an API to start and stop play.
+ */
+class SequenceComponent extends Component {
+    constructor(name, container, navigator, renderer, scheduler) {
+        super(name, container, navigator);
+        this._sequenceDOMRenderer = !!renderer ? renderer : new SequenceDOMRenderer(container);
+        this._scheduler = scheduler;
+        this._containerWidth$ = new Subject();
+        this._hoveredIdSubject$ = new Subject();
+        this._hoveredId$ = this._hoveredIdSubject$.pipe(share());
+        this._navigator.playService.playing$.pipe(skip(1), withLatestFrom(this._configuration$))
+            .subscribe(([playing, configuration]) => {
+            const type = "playing";
+            const event = {
+                playing,
+                target: this,
+                type,
+            };
+            this.fire(type, event);
+            if (playing === configuration.playing) {
+                return;
+            }
+            if (playing) {
+                this.play();
+            }
+            else {
+                this.stop();
+            }
+        });
+        this._navigator.playService.direction$.pipe(skip(1), withLatestFrom(this._configuration$))
+            .subscribe(([direction, configuration]) => {
+            if (direction !== configuration.direction) {
+                this.configure({ direction });
+            }
+        });
+    }
+    fire(type, event) {
+        super.fire(type, event);
+    }
+    off(type, handler) {
+        super.off(type, handler);
+    }
+    on(type, handler) {
+        super.on(type, handler);
+    }
+    /**
+     * Start playing.
+     *
+     * @fires playing
+     */
+    play() { this.configure({ playing: true }); }
+    /**
+     * Stop playing.
+     *
+     * @fires playing
+     */
+    stop() { this.configure({ playing: false }); }
+    _activate() {
+        this._sequenceDOMRenderer.activate();
+        const edgeStatus$ = this._navigator.stateService.currentImage$.pipe(switchMap((image) => {
+            return image.sequenceEdges$;
+        }), publishReplay(1), refCount());
+        const sequence$ = this._navigator.stateService.currentImage$.pipe(distinctUntilChanged(undefined, (image) => {
+            return image.sequenceId;
+        }), switchMap((image) => {
+            return concat(of(null), this._navigator.graphService.cacheSequence$(image.sequenceId).pipe(retry(3), catchError((e) => {
+                console.error("Failed to cache sequence", e);
+                return of(null);
+            })));
+        }), startWith(null), publishReplay(1), refCount());
+        const subs = this._subscriptions;
+        subs.push(sequence$.subscribe());
+        const rendererId$ = this._sequenceDOMRenderer.index$.pipe(withLatestFrom(sequence$), map(([index, sequence]) => {
+            return sequence != null ? sequence.imageIds[index] : null;
+        }), filter((id) => {
+            return !!id;
+        }), distinctUntilChanged(), publish(), refCount());
+        subs.push(merge(rendererId$.pipe(debounceTime(100, this._scheduler)), rendererId$.pipe(auditTime(400, this._scheduler))).pipe(distinctUntilChanged(), switchMap((id) => {
+            return this._navigator.moveTo$(id).pipe(catchError(() => {
+                return empty();
+            }));
+        }))
+            .subscribe());
+        subs.push(this._sequenceDOMRenderer.changingPositionChanged$.pipe(filter((changing) => {
+            return changing;
+        }))
+            .subscribe(() => {
+            this._navigator.graphService.setGraphMode(GraphMode.Sequence);
+        }));
+        subs.push(this._sequenceDOMRenderer.changingPositionChanged$.pipe(filter((changing) => {
+            return !changing;
+        }))
+            .subscribe(() => {
+            this._navigator.graphService.setGraphMode(GraphMode.Spatial);
+        }));
+        this._navigator.graphService.graphMode$.pipe(switchMap((mode) => {
+            return mode === GraphMode.Spatial ?
+                this._navigator.stateService.currentImage$.pipe(take(2)) :
+                empty();
+        }), filter((image) => {
+            return !image.spatialEdges.cached;
+        }), switchMap((image) => {
+            return this._navigator.graphService.cacheImage$(image.id).pipe(catchError(() => {
+                return empty();
+            }));
+        }))
+            .subscribe();
+        subs.push(this._sequenceDOMRenderer.changingPositionChanged$.pipe(filter((changing) => {
+            return changing;
+        }))
+            .subscribe(() => {
+            this._navigator.playService.stop();
+        }));
+        subs.push(combineLatest(this._navigator.graphService.graphMode$, this._sequenceDOMRenderer.changingPositionChanged$.pipe(startWith(false), distinctUntilChanged())).pipe(withLatestFrom(this._navigator.stateService.currentImage$), switchMap(([[mode, changing], image]) => {
+            return changing && mode === GraphMode.Sequence ?
+                this._navigator.graphService.cacheSequenceImages$(image.sequenceId, image.id).pipe(retry(3), catchError((error) => {
+                    console.error("Failed to cache sequence images.", error);
+                    return empty();
+                })) :
+                empty();
+        }))
+            .subscribe());
+        const position$ = sequence$.pipe(switchMap((sequence) => {
+            if (!sequence) {
+                return of({ index: null, max: null });
+            }
+            let firstCurrentId = true;
+            return this._sequenceDOMRenderer.changingPositionChanged$.pipe(startWith(false), distinctUntilChanged(), switchMap((changingPosition) => {
+                const skipCount = !changingPosition &&
+                    firstCurrentId ?
+                    0 : 1;
+                firstCurrentId = false;
+                return changingPosition ?
+                    rendererId$ :
+                    this._navigator.stateService.currentImage$.pipe(map((image) => {
+                        return image.id;
+                    }), distinctUntilChanged(), skip(skipCount));
+            }), map((imageId) => {
+                const index = sequence.imageIds.indexOf(imageId);
+                if (index === -1) {
+                    return { index: null, max: null };
+                }
+                return { index: index, max: sequence.imageIds.length - 1 };
+            }));
+        }));
+        const earth$ = this._navigator.stateService.state$.pipe(map((state) => {
+            return state === State.Earth;
+        }), distinctUntilChanged());
+        subs.push(combineLatest(edgeStatus$, this._configuration$, this._containerWidth$, this._sequenceDOMRenderer.changed$.pipe(startWith(this._sequenceDOMRenderer)), this._navigator.playService.speed$, position$, earth$).pipe(map(([edgeStatus, configuration, containerWidth, , speed, position, earth]) => {
+            const vNode = this._sequenceDOMRenderer
+                .render(edgeStatus, configuration, containerWidth, speed, position.index, position.max, !earth, this, this._navigator);
+            return { name: this._name, vNode: vNode };
+        }))
+            .subscribe(this._container.domRenderer.render$));
+        subs.push(this._sequenceDOMRenderer.speed$
+            .subscribe((speed) => {
+            this._navigator.playService.setSpeed(speed);
+        }));
+        subs.push(this._configuration$.pipe(map((configuration) => {
+            return configuration.direction;
+        }), distinctUntilChanged())
+            .subscribe((direction) => {
+            this._navigator.playService.setDirection(direction);
+        }));
+        subs.push(combineLatest(this._container.renderService.size$, this._configuration$.pipe(distinctUntilChanged((value1, value2) => {
+            return value1[0] === value2[0] && value1[1] === value2[1];
+        }, (configuration) => {
+            return [configuration.minWidth, configuration.maxWidth];
+        }))).pipe(map(([size, configuration]) => {
+            return this._sequenceDOMRenderer.getContainerWidth(size, configuration);
+        }))
+            .subscribe(this._containerWidth$));
+        subs.push(this._configuration$.pipe(map((configuration) => {
+            return configuration.playing;
+        }), distinctUntilChanged())
+            .subscribe((playing) => {
+            if (playing) {
+                this._navigator.playService.play();
+            }
+            else {
+                this._navigator.playService.stop();
+            }
+        }));
+        subs.push(this._sequenceDOMRenderer.mouseEnterDirection$.pipe(switchMap((direction) => {
+            const edgeTo$ = edgeStatus$.pipe(map((edgeStatus) => {
+                for (let edge of edgeStatus.edges) {
+                    if (edge.data.direction === direction) {
+                        return edge.target;
+                    }
+                }
+                return null;
+            }), takeUntil(this._sequenceDOMRenderer.mouseLeaveDirection$));
+            return concat(edgeTo$, of(null));
+        }), distinctUntilChanged())
+            .subscribe(this._hoveredIdSubject$));
+        subs.push(this._hoveredId$
+            .subscribe((id) => {
+            const type = "hover";
+            const event = {
+                id,
+                target: this,
+                type,
+            };
+            this.fire(type, event);
+        }));
+    }
+    _deactivate() {
+        this._subscriptions.unsubscribe();
+        this._sequenceDOMRenderer.deactivate();
+    }
+    _getDefaultConfiguration() {
+        return {
+            direction: NavigationDirection.Next,
+            maxWidth: 108,
+            minWidth: 70,
+            playing: false,
+            visible: true,
+        };
+    }
+}
+/** @inheritdoc */
+SequenceComponent.componentName = "sequence";
+
+/**
+ * Enumeration for slider mode.
+ *
+ * @enum {number}
+ * @readonly
+ *
+ * @description Modes for specifying how transitions
+ * between images are performed in slider mode. Only
+ * applicable when the slider component determines
+ * that transitions with motion is possilble. When it
+ * is not, the stationary mode will be applied.
+ */
+var SliderConfigurationMode;
+(function (SliderConfigurationMode) {
+    /**
+     * Transitions with motion.
+     *
+     * @description The slider component moves the
+     * camera between the image origins.
+     *
+     * In this mode it is not possible to zoom or pan.
+     *
+     * The slider component falls back to stationary
+     * mode when it determines that the pair of images
+     * does not have a strong enough relation.
+     */
+    SliderConfigurationMode[SliderConfigurationMode["Motion"] = 0] = "Motion";
+    /**
+     * Stationary transitions.
+     *
+     * @description The camera is stationary.
+     *
+     * In this mode it is possible to zoom and pan.
+     */
+    SliderConfigurationMode[SliderConfigurationMode["Stationary"] = 1] = "Stationary";
+})(SliderConfigurationMode || (SliderConfigurationMode = {}));
+
+class SliderGLRenderer {
+    constructor() {
+        this._factory = new MeshFactory();
+        this._scene = new MeshScene();
+        this._spatial = new Spatial();
+        this._currentKey = null;
+        this._previousKey = null;
+        this._disabled = false;
+        this._curtain = 1;
+        this._frameId = 0;
+        this._needsRender = false;
+        this._mode = null;
+        this._currentProviderDisposers = {};
+        this._previousProviderDisposers = {};
+    }
+    get disabled() {
+        return this._disabled;
+    }
+    get frameId() {
+        return this._frameId;
+    }
+    get needsRender() {
+        return this._needsRender;
+    }
+    setTextureProvider(key, provider) {
+        this._setTextureProvider(key, this._currentKey, provider, this._currentProviderDisposers, this._updateTexture.bind(this));
+    }
+    setTextureProviderPrev(key, provider) {
+        this._setTextureProvider(key, this._previousKey, provider, this._previousProviderDisposers, this._updateTexturePrev.bind(this));
+    }
+    update(frame, mode) {
+        this._updateFrameId(frame.id);
+        this._updateImagePlanes(frame.state, mode);
+    }
+    updateCurtain(curtain) {
+        if (this._curtain === curtain) {
+            return;
+        }
+        this._curtain = curtain;
+        this._updateCurtain();
+        this._needsRender = true;
+    }
+    updateTexture(imageElement, image) {
+        const planes = image.id === this._currentKey ?
+            this._scene.planes :
+            image.id === this._previousKey ?
+                this._scene.planesOld :
+                {};
+        if (Object.keys(planes).length === 0) {
+            return;
+        }
+        this._needsRender = true;
+        for (const key in planes) {
+            if (!planes.hasOwnProperty(key)) {
+                continue;
+            }
+            const plane = planes[key];
+            let material = plane.material;
+            let texture = material.uniforms.projectorTex.value;
+            texture.image = imageElement;
+            texture.needsUpdate = true;
+        }
+    }
+    updateTextureImage(imageElement, image) {
+        if (this._currentKey !== image.id) {
+            return;
+        }
+        this._needsRender = true;
+        const planes = this._scene.planes;
+        for (const key in planes) {
+            if (!planes.hasOwnProperty(key)) {
+                continue;
+            }
+            const plane = planes[key];
+            let material = plane.material;
+            let texture = material.uniforms.projectorTex.value;
+            texture.image = imageElement;
+            texture.needsUpdate = true;
+        }
+    }
+    render(perspectiveCamera, renderer) {
+        if (!this.disabled) {
+            renderer.render(this._scene.sceneOld, perspectiveCamera);
+        }
+        renderer.render(this._scene.scene, perspectiveCamera);
+        this._needsRender = false;
+    }
+    dispose() {
+        this._scene.clear();
+        for (const key in this._currentProviderDisposers) {
+            if (!this._currentProviderDisposers.hasOwnProperty(key)) {
+                continue;
+            }
+            this._currentProviderDisposers[key]();
+        }
+        for (const key in this._previousProviderDisposers) {
+            if (!this._previousProviderDisposers.hasOwnProperty(key)) {
+                continue;
+            }
+            this._previousProviderDisposers[key]();
+        }
+        this._currentProviderDisposers = {};
+        this._previousProviderDisposers = {};
+    }
+    _getBasicCorners(currentAspect, previousAspect) {
+        let offsetX;
+        let offsetY;
+        if (currentAspect > previousAspect) {
+            offsetX = 0.5;
+            offsetY = 0.5 * currentAspect / previousAspect;
+        }
+        else {
+            offsetX = 0.5 * previousAspect / currentAspect;
+            offsetY = 0.5;
+        }
+        return [[0.5 - offsetX, 0.5 - offsetY], [0.5 + offsetX, 0.5 + offsetY]];
+    }
+    _setDisabled(state) {
+        this._disabled = state.currentImage == null ||
+            state.previousImage == null ||
+            (isSpherical(state.currentImage.cameraType) &&
+                !isSpherical(state.previousImage.cameraType));
+    }
+    _setTextureProvider(key, originalKey, provider, providerDisposers, updateTexture) {
+        if (key !== originalKey) {
+            return;
+        }
+        let createdSubscription = provider.textureCreated$
+            .subscribe(updateTexture);
+        let updatedSubscription = provider.textureUpdated$
+            .subscribe((updated) => {
+            this._needsRender = true;
+        });
+        let dispose = () => {
+            createdSubscription.unsubscribe();
+            updatedSubscription.unsubscribe();
+            provider.dispose();
+        };
+        if (key in providerDisposers) {
+            let disposeProvider = providerDisposers[key];
+            disposeProvider();
+            delete providerDisposers[key];
+        }
+        providerDisposers[key] = dispose;
+    }
+    _updateCurtain() {
+        const planes = this._scene.planes;
+        for (const key in planes) {
+            if (!planes.hasOwnProperty(key)) {
+                continue;
+            }
+            const plane = planes[key];
+            let shaderMaterial = plane.material;
+            if (!!shaderMaterial.uniforms.curtain) {
+                shaderMaterial.uniforms.curtain.value = this._curtain;
+            }
+        }
+    }
+    _updateFrameId(frameId) {
+        this._frameId = frameId;
+    }
+    _updateImagePlanes(state, mode) {
+        const currentChanged = state.currentImage != null && this._currentKey !== state.currentImage.id;
+        const previousChanged = state.previousImage != null && this._previousKey !== state.previousImage.id;
+        const modeChanged = this._mode !== mode;
+        if (!(currentChanged || previousChanged || modeChanged)) {
+            return;
+        }
+        this._setDisabled(state);
+        this._needsRender = true;
+        this._mode = mode;
+        const motionless = state.motionless ||
+            mode === SliderConfigurationMode.Stationary ||
+            isSpherical(state.currentImage.cameraType);
+        if (this.disabled || previousChanged) {
+            if (this._previousKey in this._previousProviderDisposers) {
+                this._previousProviderDisposers[this._previousKey]();
+                delete this._previousProviderDisposers[this._previousKey];
+            }
+        }
+        if (this.disabled) {
+            this._scene.setImagePlanesOld({});
+        }
+        else {
+            if (previousChanged || modeChanged) {
+                const previousNode = state.previousImage;
+                this._previousKey = previousNode.id;
+                const elements = state.currentTransform.rt.elements;
+                let translation = [elements[12], elements[13], elements[14]];
+                const currentAspect = state.currentTransform.basicAspect;
+                const previousAspect = state.previousTransform.basicAspect;
+                const textureScale = currentAspect > previousAspect ?
+                    [1, previousAspect / currentAspect] :
+                    [currentAspect / previousAspect, 1];
+                let rotation = state.currentImage.rotation;
+                let width = state.currentImage.width;
+                let height = state.currentImage.height;
+                if (isSpherical(previousNode.cameraType)) {
+                    rotation = state.previousImage.rotation;
+                    translation = this._spatial
+                        .rotate(this._spatial
+                        .opticalCenter(state.currentImage.rotation, translation)
+                        .toArray(), rotation)
+                        .multiplyScalar(-1)
+                        .toArray();
+                    width = state.previousImage.width;
+                    height = state.previousImage.height;
+                }
+                const transform = new Transform(state.currentImage.exifOrientation, width, height, state.currentImage.scale, rotation, translation, previousNode.image, textureScale, state.currentImage.cameraParameters, state.currentImage.cameraType);
+                let mesh = undefined;
+                if (isSpherical(previousNode.cameraType)) {
+                    mesh = this._factory.createMesh(previousNode, motionless ||
+                        isSpherical(state.currentImage.cameraType) ?
+                        transform : state.previousTransform);
+                }
+                else {
+                    if (motionless) {
+                        const [[basicX0, basicY0], [basicX1, basicY1]] = this._getBasicCorners(currentAspect, previousAspect);
+                        mesh = this._factory.createFlatMesh(state.previousImage, transform, basicX0, basicX1, basicY0, basicY1);
+                    }
+                    else {
+                        mesh = this._factory.createMesh(state.previousImage, state.previousTransform);
+                    }
+                }
+                const previousPlanes = {};
+                previousPlanes[previousNode.id] = mesh;
+                this._scene.setImagePlanesOld(previousPlanes);
+            }
+        }
+        if (currentChanged || modeChanged) {
+            if (this._currentKey in this._currentProviderDisposers) {
+                this._currentProviderDisposers[this._currentKey]();
+                delete this._currentProviderDisposers[this._currentKey];
+            }
+            this._currentKey = state.currentImage.id;
+            const planes = {};
+            if (isSpherical(state.currentImage.cameraType)) {
+                planes[state.currentImage.id] =
+                    this._factory.createCurtainMesh(state.currentImage, state.currentTransform);
+            }
+            else {
+                if (motionless) {
+                    planes[state.currentImage.id] = this._factory.createDistortedCurtainMesh(state.currentImage, state.currentTransform);
+                }
+                else {
+                    planes[state.currentImage.id] = this._factory.createCurtainMesh(state.currentImage, state.currentTransform);
+                }
+            }
+            this._scene.setImagePlanes(planes);
+            this._updateCurtain();
+        }
+    }
+    _updateTexture(texture) {
+        this._needsRender = true;
+        const planes = this._scene.planes;
+        for (const key in planes) {
+            if (!planes.hasOwnProperty(key)) {
+                continue;
+            }
+            const plane = planes[key];
+            let material = plane.material;
+            let oldTexture = material.uniforms.projectorTex.value;
+            material.uniforms.projectorTex.value = null;
+            oldTexture.dispose();
+            material.uniforms.projectorTex.value = texture;
+        }
+    }
+    _updateTexturePrev(texture) {
+        this._needsRender = true;
+        const planes = this._scene.planesOld;
+        for (const key in planes) {
+            if (!planes.hasOwnProperty(key)) {
+                continue;
+            }
+            const plane = planes[key];
+            let material = plane.material;
+            let oldTexture = material.uniforms.projectorTex.value;
+            material.uniforms.projectorTex.value = null;
+            oldTexture.dispose();
+            material.uniforms.projectorTex.value = texture;
+        }
+    }
+}
+
+class SliderDOMRenderer {
+    constructor(container) {
+        this._container = container;
+        this._interacting = false;
+        this._notifyModeChanged$ = new Subject();
+        this._notifyPositionChanged$ = new Subject();
+        this._stopInteractionSubscription = null;
+    }
+    get mode$() {
+        return this._notifyModeChanged$;
+    }
+    get position$() {
+        return this._notifyPositionChanged$;
+    }
+    activate() {
+        if (!!this._stopInteractionSubscription) {
+            return;
+        }
+        this._stopInteractionSubscription = merge(this._container.mouseService.documentMouseUp$, this._container.touchService.touchEnd$.pipe(filter((touchEvent) => {
+            return touchEvent.touches.length === 0;
+        })))
+            .subscribe((event) => {
+            if (this._interacting) {
+                this._interacting = false;
+            }
+        });
+    }
+    deactivate() {
+        if (!this._stopInteractionSubscription) {
+            return;
+        }
+        this._interacting = false;
+        this._stopInteractionSubscription.unsubscribe();
+        this._stopInteractionSubscription = null;
+    }
+    render(position, mode, motionless, spherical, visible) {
+        const children = [];
+        if (visible) {
+            children.push(virtualDom.h("div.mapillary-slider-border", []));
+            const modeVisible = !(motionless || spherical);
+            if (modeVisible) {
+                children.push(this._createModeButton(mode));
+                children.push(this._createModeButton2d(mode));
+            }
+            children.push(this._createPositionInput(position, modeVisible));
+        }
+        const boundingRect = this._container.domContainer.getBoundingClientRect();
+        const width = Math.max(215, Math.min(400, boundingRect.width - 100));
+        return virtualDom.h("div.mapillary-slider-container", { style: { width: `${width}px` } }, children);
+    }
+    _createModeButton(mode) {
+        const properties = {
+            onclick: () => {
+                if (mode === SliderConfigurationMode.Motion) {
+                    return;
+                }
+                this._notifyModeChanged$.next(SliderConfigurationMode.Motion);
+            },
+        };
+        const className = mode === SliderConfigurationMode.Stationary ?
+            "mapillary-slider-mode-button-inactive" :
+            "mapillary-slider-mode-button";
+        return virtualDom.h("div." + className, properties, [virtualDom.h("div.mapillary-slider-mode-icon", [])]);
+    }
+    _createModeButton2d(mode) {
+        const properties = {
+            onclick: () => {
+                if (mode === SliderConfigurationMode.Stationary) {
+                    return;
+                }
+                this._notifyModeChanged$.next(SliderConfigurationMode.Stationary);
+            },
+        };
+        const className = mode === SliderConfigurationMode.Motion ?
+            "mapillary-slider-mode-button-2d-inactive" :
+            "mapillary-slider-mode-button-2d";
+        return virtualDom.h("div." + className, properties, [virtualDom.h("div.mapillary-slider-mode-icon-2d", [])]);
+    }
+    _createPositionInput(position, modeVisible) {
+        const onChange = (e) => {
+            this._notifyPositionChanged$.next(Number(e.target.value) / 1000);
+        };
+        const onStart = (e) => {
+            this._interacting = true;
+            e.stopPropagation();
+        };
+        const onMove = (e) => {
+            if (this._interacting) {
+                e.stopPropagation();
+            }
+        };
+        const onKeyDown = (e) => {
+            if (e.key === "ArrowDown" || e.key === "ArrowLeft" ||
+                e.key === "ArrowRight" || e.key === "ArrowUp") {
+                e.preventDefault();
+            }
+        };
+        const boundingRect = this._container.domContainer.getBoundingClientRect();
+        const width = Math.max(215, Math.min(400, boundingRect.width - 105)) - 84 + (modeVisible ? 0 : 52);
+        const positionInput = virtualDom.h("input.mapillary-slider-position", {
+            max: 1000,
+            min: 0,
+            onchange: onChange,
+            oninput: onChange,
+            onkeydown: onKeyDown,
+            onpointerdown: onStart,
+            onpointermove: onMove,
+            ontouchmove: onMove,
+            ontouchstart: onStart,
+            style: {
+                width: `${width}px`,
+            },
+            type: "range",
+            value: 1000 * position,
+        }, []);
+        return virtualDom.h("div.mapillary-slider-position-container", [positionInput]);
+    }
+}
+
+/**
+ * @class SliderComponent
+ *
+ * @classdesc Component for comparing pairs of images. Renders
+ * a slider for adjusting the curtain of the first image.
+ *
+ * Deactivate the sequence, direction and image plane
+ * components when activating the slider component to avoid
+ * interfering UI elements.
+ *
+ * To retrive and use the slider component
+ *
+ * @example
+ * ```js
+ * var viewer = new Viewer({ ... });
+ *
+ * viewer.deactivateComponent("image");
+ * viewer.deactivateComponent("direction");
+ * viewer.deactivateComponent("sequence");
+ *
+ * viewer.activateComponent("slider");
+ *
+ * var sliderComponent = viewer.getComponent("slider");
+ * ```
+ */
+class SliderComponent extends Component {
+    /** @ignore */
+    constructor(name, container, navigator, viewportCoords) {
+        super(name, container, navigator);
+        this._viewportCoords = !!viewportCoords ? viewportCoords : new ViewportCoords();
+        this._domRenderer = new SliderDOMRenderer(container);
+        this._imageTileLoader = new TileLoader(navigator.api);
+        this._roiCalculator = new RegionOfInterestCalculator();
+        this._spatial = new Spatial();
+        this._glRendererOperation$ = new Subject();
+        this._glRendererCreator$ = new Subject();
+        this._glRendererDisposer$ = new Subject();
+        this._glRenderer$ = this._glRendererOperation$.pipe(scan((glRenderer, operation) => {
+            return operation(glRenderer);
+        }, null), filter((glRenderer) => {
+            return glRenderer != null;
+        }), distinctUntilChanged(undefined, (glRenderer) => {
+            return glRenderer.frameId;
+        }));
+        this._glRendererCreator$.pipe(map(() => {
+            return (glRenderer) => {
+                if (glRenderer != null) {
+                    throw new Error("Multiple slider states can not be created at the same time");
+                }
+                return new SliderGLRenderer();
+            };
+        }))
+            .subscribe(this._glRendererOperation$);
+        this._glRendererDisposer$.pipe(map(() => {
+            return (glRenderer) => {
+                glRenderer.dispose();
+                return null;
+            };
+        }))
+            .subscribe(this._glRendererOperation$);
+    }
+    _activate() {
+        const subs = this._subscriptions;
+        subs.push(this._domRenderer.mode$
+            .subscribe((mode) => {
+            this.configure({ mode });
+        }));
+        subs.push(this._glRenderer$.pipe(map((glRenderer) => {
+            let renderHash = {
+                name: this._name,
+                renderer: {
+                    frameId: glRenderer.frameId,
+                    needsRender: glRenderer.needsRender,
+                    render: glRenderer.render.bind(glRenderer),
+                    pass: RenderPass$1.Background,
+                },
+            };
+            return renderHash;
+        }))
+            .subscribe(this._container.glRenderer.render$));
+        const position$ = concat(this.configuration$.pipe(map((configuration) => {
+            return configuration.initialPosition != null ?
+                configuration.initialPosition : 1;
+        }), first()), this._domRenderer.position$);
+        const mode$ = this.configuration$.pipe(map((configuration) => {
+            return configuration.mode;
+        }), distinctUntilChanged());
+        const motionless$ = this._navigator.stateService.currentState$.pipe(map((frame) => {
+            return frame.state.motionless;
+        }), distinctUntilChanged());
+        const spherical$ = this._navigator.stateService.currentState$.pipe(map((frame) => {
+            return isSpherical(frame.state.currentImage.cameraType);
+        }), distinctUntilChanged());
+        const sliderVisible$ = combineLatest(this._configuration$.pipe(map((configuration) => {
+            return configuration.sliderVisible;
+        })), this._navigator.stateService.currentState$.pipe(map((frame) => {
+            return !(frame.state.currentImage == null ||
+                frame.state.previousImage == null ||
+                (isSpherical(frame.state.currentImage.cameraType) &&
+                    !isSpherical(frame.state.previousImage.cameraType)));
+        }), distinctUntilChanged())).pipe(map(([sliderVisible, enabledState]) => {
+            return sliderVisible && enabledState;
+        }), distinctUntilChanged());
+        this._waitSubscription = combineLatest(mode$, motionless$, spherical$, sliderVisible$).pipe(withLatestFrom(this._navigator.stateService.state$))
+            .subscribe(([[mode, motionless, spherical, sliderVisible], state]) => {
+            const interactive = sliderVisible &&
+                (motionless ||
+                    mode === SliderConfigurationMode.Stationary ||
+                    spherical);
+            if (interactive && state !== State.WaitingInteractively) {
+                this._navigator.stateService.waitInteractively();
+            }
+            else if (!interactive && state !== State.Waiting) {
+                this._navigator.stateService.wait();
+            }
+        });
+        subs.push(combineLatest(position$, mode$, motionless$, spherical$, sliderVisible$)
+            .subscribe(([position, mode, motionless, spherical]) => {
+            if (motionless || mode === SliderConfigurationMode.Stationary || spherical) {
+                this._navigator.stateService.moveTo(1);
+            }
+            else {
+                this._navigator.stateService.moveTo(position);
+            }
+        }));
+        subs.push(combineLatest(position$, mode$, motionless$, spherical$, sliderVisible$, this._container.renderService.size$).pipe(map(([position, mode, motionless, spherical, sliderVisible]) => {
+            return {
+                name: this._name,
+                vNode: this._domRenderer.render(position, mode, motionless, spherical, sliderVisible),
+            };
+        }))
+            .subscribe(this._container.domRenderer.render$));
+        this._glRendererCreator$.next(null);
+        subs.push(combineLatest(position$, spherical$, sliderVisible$, this._container.renderService.renderCamera$, this._navigator.stateService.currentTransform$).pipe(map(([position, spherical, visible, render, transform]) => {
+            if (!spherical) {
+                return visible ? position : 1;
+            }
+            const basicMin = this._viewportCoords.viewportToBasic(-1.15, 0, transform, render.perspective);
+            const basicMax = this._viewportCoords.viewportToBasic(1.15, 0, transform, render.perspective);
+            const shiftedMax = basicMax[0] < basicMin[0] ? basicMax[0] + 1 : basicMax[0];
+            const basicPosition = basicMin[0] + position * (shiftedMax - basicMin[0]);
+            return basicPosition > 1 ? basicPosition - 1 : basicPosition;
+        }), map((position) => {
+            return (glRenderer) => {
+                glRenderer.updateCurtain(position);
+                return glRenderer;
+            };
+        }))
+            .subscribe(this._glRendererOperation$));
+        subs.push(combineLatest(this._navigator.stateService.currentState$, mode$).pipe(map(([frame, mode]) => {
+            return (glRenderer) => {
+                glRenderer.update(frame, mode);
+                return glRenderer;
+            };
+        }))
+            .subscribe(this._glRendererOperation$));
+        subs.push(this._configuration$.pipe(filter((configuration) => {
+            return configuration.ids != null;
+        }), switchMap((configuration) => {
+            return zip(zip(this._catchCacheImage$(configuration.ids.background), this._catchCacheImage$(configuration.ids.foreground)).pipe(map((images) => {
+                return { background: images[0], foreground: images[1] };
+            })), this._navigator.stateService.currentState$.pipe(first())).pipe(map((nf) => {
+                return { images: nf[0], state: nf[1].state };
+            }));
+        }))
+            .subscribe((co) => {
+            if (co.state.currentImage != null &&
+                co.state.previousImage != null &&
+                co.state.currentImage.id === co.images.foreground.id &&
+                co.state.previousImage.id === co.images.background.id) {
+                return;
+            }
+            if (co.state.currentImage.id === co.images.background.id) {
+                this._navigator.stateService.setImages([co.images.foreground]);
+                return;
+            }
+            if (co.state.currentImage.id === co.images.foreground.id &&
+                co.state.trajectory.length === 1) {
+                this._navigator.stateService.prependImages([co.images.background]);
+                return;
+            }
+            this._navigator.stateService.setImages([co.images.background]);
+            this._navigator.stateService.setImages([co.images.foreground]);
+        }, (e) => {
+            console.error(e);
+        }));
+        const textureProvider$ = this._container.configurationService.imageTiling$.pipe(switchMap((active) => {
+            return active ?
+                this._navigator.stateService.currentState$ :
+                new Subject();
+        }), distinctUntilChanged(undefined, (frame) => {
+            return frame.state.currentImage.id;
+        }), withLatestFrom(this._container.glRenderer.webGLRenderer$, this._container.renderService.size$), map(([frame, renderer, size]) => {
+            const state = frame.state;
+            Math.max(size.width, size.height);
+            const currentImage = state.currentImage;
+            const currentTransform = state.currentTransform;
+            return new TextureProvider(currentImage.id, currentTransform.basicWidth, currentTransform.basicHeight, currentImage.image, this._imageTileLoader, new TileStore(), renderer);
+        }), publishReplay(1), refCount());
+        subs.push(textureProvider$.subscribe(() => { }));
+        subs.push(textureProvider$.pipe(map((provider) => {
+            return (renderer) => {
+                renderer.setTextureProvider(provider.id, provider);
+                return renderer;
+            };
+        }))
+            .subscribe(this._glRendererOperation$));
+        subs.push(textureProvider$.pipe(pairwise())
+            .subscribe((pair) => {
+            let previous = pair[0];
+            previous.abort();
+        }));
+        const roiTrigger$ = this._container.configurationService.imageTiling$.pipe(switchMap((active) => {
+            return active ?
+                combineLatest(this._container.renderService.renderCameraFrame$, this._container.renderService.size$.pipe(debounceTime(250))) :
+                new Subject();
+        }), map(([camera, size]) => {
+            return [
+                camera.camera.position.clone(),
+                camera.camera.lookat.clone(),
+                camera.zoom.valueOf(),
+                size.height.valueOf(),
+                size.width.valueOf()
+            ];
+        }), pairwise(), skipWhile((pls) => {
+            return pls[1][2] - pls[0][2] < 0 || pls[1][2] === 0;
+        }), map((pls) => {
+            let samePosition = pls[0][0].equals(pls[1][0]);
+            let sameLookat = pls[0][1].equals(pls[1][1]);
+            let sameZoom = pls[0][2] === pls[1][2];
+            let sameHeight = pls[0][3] === pls[1][3];
+            let sameWidth = pls[0][4] === pls[1][4];
+            return samePosition && sameLookat && sameZoom && sameHeight && sameWidth;
+        }), distinctUntilChanged(), filter((stalled) => {
+            return stalled;
+        }), switchMap(() => {
+            return this._container.renderService.renderCameraFrame$.pipe(first());
+        }), withLatestFrom(this._container.renderService.size$, this._navigator.stateService.currentTransform$));
+        subs.push(textureProvider$.pipe(switchMap((provider) => {
+            return roiTrigger$.pipe(map(([camera, size, transform]) => {
+                return [
+                    this._roiCalculator.computeRegionOfInterest(camera, size, transform),
+                    provider,
+                ];
+            }));
+        }), filter((args) => {
+            return !args[1].disposed;
+        }))
+            .subscribe((args) => {
+            let roi = args[0];
+            let provider = args[1];
+            provider.setRegionOfInterest(roi);
+        }));
+        const hasTexture$ = textureProvider$.pipe(switchMap((provider) => {
+            return provider.hasTexture$;
+        }), startWith(false), publishReplay(1), refCount());
+        subs.push(hasTexture$.subscribe(() => { }));
+        const textureProviderPrev$ = this._container.configurationService.imageTiling$.pipe(switchMap((active) => {
+            return active ?
+                this._navigator.stateService.currentState$ :
+                new Subject();
+        }), filter((frame) => {
+            return !!frame.state.previousImage;
+        }), distinctUntilChanged(undefined, (frame) => {
+            return frame.state.previousImage.id;
+        }), withLatestFrom(this._container.glRenderer.webGLRenderer$, this._container.renderService.size$), map(([frame, renderer, size]) => {
+            const state = frame.state;
+            const previousImage = state.previousImage;
+            const previousTransform = state.previousTransform;
+            return new TextureProvider(previousImage.id, previousTransform.basicWidth, previousTransform.basicHeight, previousImage.image, this._imageTileLoader, new TileStore(), renderer);
+        }), publishReplay(1), refCount());
+        subs.push(textureProviderPrev$.subscribe(() => { }));
+        subs.push(textureProviderPrev$.pipe(map((provider) => {
+            return (renderer) => {
+                renderer.setTextureProviderPrev(provider.id, provider);
+                return renderer;
+            };
+        }))
+            .subscribe(this._glRendererOperation$));
+        subs.push(textureProviderPrev$.pipe(pairwise())
+            .subscribe((pair) => {
+            let previous = pair[0];
+            previous.abort();
+        }));
+        const roiTriggerPrev$ = this._container.configurationService.imageTiling$.pipe(switchMap((active) => {
+            return active ?
+                combineLatest(this._container.renderService.renderCameraFrame$, this._container.renderService.size$.pipe(debounceTime(250))) :
+                new Subject();
+        }), map(([camera, size]) => {
+            return [
+                camera.camera.position.clone(),
+                camera.camera.lookat.clone(),
+                camera.zoom.valueOf(),
+                size.height.valueOf(),
+                size.width.valueOf()
+            ];
+        }), pairwise(), skipWhile((pls) => {
+            return pls[1][2] - pls[0][2] < 0 || pls[1][2] === 0;
+        }), map((pls) => {
+            let samePosition = pls[0][0].equals(pls[1][0]);
+            let sameLookat = pls[0][1].equals(pls[1][1]);
+            let sameZoom = pls[0][2] === pls[1][2];
+            let sameHeight = pls[0][3] === pls[1][3];
+            let sameWidth = pls[0][4] === pls[1][4];
+            return samePosition && sameLookat && sameZoom && sameHeight && sameWidth;
+        }), distinctUntilChanged(), filter((stalled) => {
+            return stalled;
+        }), switchMap(() => {
+            return this._container.renderService.renderCameraFrame$.pipe(first());
+        }), withLatestFrom(this._container.renderService.size$, this._navigator.stateService.currentTransform$));
+        subs.push(textureProviderPrev$.pipe(switchMap((provider) => {
+            return roiTriggerPrev$.pipe(map(([camera, size, transform]) => {
+                return [
+                    this._roiCalculator.computeRegionOfInterest(camera, size, transform),
+                    provider,
+                ];
+            }));
+        }), filter((args) => {
+            return !args[1].disposed;
+        }), withLatestFrom(this._navigator.stateService.currentState$))
+            .subscribe(([[roi, provider], frame]) => {
+            let shiftedRoi = null;
+            if (isSpherical(frame.state.previousImage.cameraType)) {
+                if (isSpherical(frame.state.currentImage.cameraType)) {
+                    const currentViewingDirection = this._spatial.viewingDirection(frame.state.currentImage.rotation);
+                    const previousViewingDirection = this._spatial.viewingDirection(frame.state.previousImage.rotation);
+                    const directionDiff = this._spatial.angleBetweenVector2(currentViewingDirection.x, currentViewingDirection.y, previousViewingDirection.x, previousViewingDirection.y);
+                    const shift = directionDiff / (2 * Math.PI);
+                    const bbox = {
+                        maxX: this._spatial.wrap(roi.bbox.maxX + shift, 0, 1),
+                        maxY: roi.bbox.maxY,
+                        minX: this._spatial.wrap(roi.bbox.minX + shift, 0, 1),
+                        minY: roi.bbox.minY,
+                    };
+                    shiftedRoi = {
+                        bbox: bbox,
+                        pixelHeight: roi.pixelHeight,
+                        pixelWidth: roi.pixelWidth,
+                    };
+                }
+                else {
+                    const currentViewingDirection = this._spatial.viewingDirection(frame.state.currentImage.rotation);
+                    const previousViewingDirection = this._spatial.viewingDirection(frame.state.previousImage.rotation);
+                    const directionDiff = this._spatial.angleBetweenVector2(currentViewingDirection.x, currentViewingDirection.y, previousViewingDirection.x, previousViewingDirection.y);
+                    const shiftX = directionDiff / (2 * Math.PI);
+                    const a1 = this._spatial.angleToPlane(currentViewingDirection.toArray(), [0, 0, 1]);
+                    const a2 = this._spatial.angleToPlane(previousViewingDirection.toArray(), [0, 0, 1]);
+                    const shiftY = (a2 - a1) / (2 * Math.PI);
+                    const currentTransform = frame.state.currentTransform;
+                    const size = Math.max(currentTransform.basicWidth, currentTransform.basicHeight);
+                    const hFov = size > 0 ?
+                        2 * Math.atan(0.5 * currentTransform.basicWidth / (size * currentTransform.focal)) :
+                        Math.PI / 3;
+                    const vFov = size > 0 ?
+                        2 * Math.atan(0.5 * currentTransform.basicHeight / (size * currentTransform.focal)) :
+                        Math.PI / 3;
+                    const spanningWidth = hFov / (2 * Math.PI);
+                    const spanningHeight = vFov / Math.PI;
+                    const basicWidth = (roi.bbox.maxX - roi.bbox.minX) * spanningWidth;
+                    const basicHeight = (roi.bbox.maxY - roi.bbox.minY) * spanningHeight;
+                    const pixelWidth = roi.pixelWidth * spanningWidth;
+                    const pixelHeight = roi.pixelHeight * spanningHeight;
+                    const zoomShiftX = (roi.bbox.minX + roi.bbox.maxX) / 2 - 0.5;
+                    const zoomShiftY = (roi.bbox.minY + roi.bbox.maxY) / 2 - 0.5;
+                    const minX = 0.5 + shiftX + spanningWidth * zoomShiftX - basicWidth / 2;
+                    const maxX = 0.5 + shiftX + spanningWidth * zoomShiftX + basicWidth / 2;
+                    const minY = 0.5 + shiftY + spanningHeight * zoomShiftY - basicHeight / 2;
+                    const maxY = 0.5 + shiftY + spanningHeight * zoomShiftY + basicHeight / 2;
+                    const bbox = {
+                        maxX: this._spatial.wrap(maxX, 0, 1),
+                        maxY: maxY,
+                        minX: this._spatial.wrap(minX, 0, 1),
+                        minY: minY,
+                    };
+                    shiftedRoi = {
+                        bbox: bbox,
+                        pixelHeight: pixelHeight,
+                        pixelWidth: pixelWidth,
+                    };
+                }
+            }
+            else {
+                const currentBasicAspect = frame.state.currentTransform.basicAspect;
+                const previousBasicAspect = frame.state.previousTransform.basicAspect;
+                const [[cornerMinX, cornerMinY], [cornerMaxX, cornerMaxY]] = this._getBasicCorners(currentBasicAspect, previousBasicAspect);
+                const basicWidth = cornerMaxX - cornerMinX;
+                const basicHeight = cornerMaxY - cornerMinY;
+                const pixelWidth = roi.pixelWidth / basicWidth;
+                const pixelHeight = roi.pixelHeight / basicHeight;
+                const minX = (basicWidth - 1) / (2 * basicWidth) + roi.bbox.minX / basicWidth;
+                const maxX = (basicWidth - 1) / (2 * basicWidth) + roi.bbox.maxX / basicWidth;
+                const minY = (basicHeight - 1) / (2 * basicHeight) + roi.bbox.minY / basicHeight;
+                const maxY = (basicHeight - 1) / (2 * basicHeight) + roi.bbox.maxY / basicHeight;
+                const bbox = {
+                    maxX: maxX,
+                    maxY: maxY,
+                    minX: minX,
+                    minY: minY,
+                };
+                this._clipBoundingBox(bbox);
+                shiftedRoi = {
+                    bbox: bbox,
+                    pixelHeight: pixelHeight,
+                    pixelWidth: pixelWidth,
+                };
+            }
+            provider.setRegionOfInterest(shiftedRoi);
+        }));
+        const hasTexturePrev$ = textureProviderPrev$.pipe(switchMap((provider) => {
+            return provider.hasTexture$;
+        }), startWith(false), publishReplay(1), refCount());
+        subs.push(hasTexturePrev$.subscribe(() => { }));
+    }
+    _deactivate() {
+        this._waitSubscription.unsubscribe();
+        this._navigator.stateService.state$.pipe(first())
+            .subscribe((state) => {
+            if (state !== State.Traversing) {
+                this._navigator.stateService.traverse();
+            }
+        });
+        this._glRendererDisposer$.next(null);
+        this._domRenderer.deactivate();
+        this._subscriptions.unsubscribe();
+        this.configure({ ids: null });
+    }
+    _getDefaultConfiguration() {
+        return {
+            initialPosition: 1,
+            mode: SliderConfigurationMode.Motion,
+            sliderVisible: true,
+        };
+    }
+    _catchCacheImage$(imageId) {
+        return this._navigator.graphService.cacheImage$(imageId).pipe(catchError((error) => {
+            console.error(`Failed to cache slider image (${imageId})`, error);
+            return empty();
+        }));
+    }
+    _getBasicCorners(currentAspect, previousAspect) {
+        let offsetX;
+        let offsetY;
+        if (currentAspect > previousAspect) {
+            offsetX = 0.5;
+            offsetY = 0.5 * currentAspect / previousAspect;
+        }
+        else {
+            offsetX = 0.5 * previousAspect / currentAspect;
+            offsetY = 0.5;
+        }
+        return [[0.5 - offsetX, 0.5 - offsetY], [0.5 + offsetX, 0.5 + offsetY]];
+    }
+    _clipBoundingBox(bbox) {
+        bbox.minX = Math.max(0, Math.min(1, bbox.minX));
+        bbox.maxX = Math.max(0, Math.min(1, bbox.maxX));
+        bbox.minY = Math.max(0, Math.min(1, bbox.minY));
+        bbox.maxY = Math.max(0, Math.min(1, bbox.maxY));
+    }
+}
+SliderComponent.componentName = "slider";
+
+class PlayService {
+    constructor(graphService, stateService) {
+        this._subscriptions = new SubscriptionHolder();
+        this._graphService = graphService;
+        this._stateService = stateService;
+        const subs = this._subscriptions;
+        this._directionSubject$ = new Subject();
+        this._direction$ = this._directionSubject$.pipe(startWith(NavigationDirection.Next), publishReplay(1), refCount());
+        subs.push(this._direction$.subscribe());
+        this._playing = false;
+        this._playingSubject$ = new Subject();
+        this._playing$ = this._playingSubject$.pipe(startWith(this._playing), publishReplay(1), refCount());
+        subs.push(this._playing$.subscribe());
+        this._speed = 0.5;
+        this._speedSubject$ = new Subject();
+        this._speed$ = this._speedSubject$.pipe(startWith(this._speed), publishReplay(1), refCount());
+        subs.push(this._speed$.subscribe());
+        this._imagesAhead = this._mapImagesAhead(this._mapSpeed(this._speed));
+        this._bridging$ = null;
+    }
+    get playing() {
+        return this._playing;
+    }
+    get direction$() {
+        return this._direction$;
+    }
+    get playing$() {
+        return this._playing$;
+    }
+    get speed$() {
+        return this._speed$;
+    }
+    play() {
+        if (this._playing) {
+            return;
+        }
+        this._stateService.cutImages();
+        const stateSpeed = this._setSpeed(this._speed);
+        this._stateService.setSpeed(stateSpeed);
+        this._graphModeSubscription = this._speed$.pipe(map((speed) => {
+            return speed > PlayService.sequenceSpeed ? GraphMode.Sequence : GraphMode.Spatial;
+        }), distinctUntilChanged())
+            .subscribe((mode) => {
+            this._graphService.setGraphMode(mode);
+        });
+        this._cacheSubscription = combineLatest(this._stateService.currentImage$.pipe(map((image) => {
+            return [image.sequenceId, image.id];
+        }), distinctUntilChanged(undefined, ([sequenceId]) => {
+            return sequenceId;
+        })), this._graphService.graphMode$, this._direction$).pipe(switchMap(([[sequenceId, imageId], mode, direction]) => {
+            if (direction !== NavigationDirection.Next && direction !== NavigationDirection.Prev) {
+                return of([undefined, direction]);
+            }
+            const sequence$ = (mode === GraphMode.Sequence ?
+                this._graphService.cacheSequenceImages$(sequenceId, imageId) :
+                this._graphService.cacheSequence$(sequenceId)).pipe(retry(3), catchError((error) => {
+                console.error(error);
+                return of(undefined);
+            }));
+            return combineLatest(sequence$, of(direction));
+        }), switchMap(([sequence, direction]) => {
+            if (sequence === undefined) {
+                return empty();
+            }
+            const imageIds = sequence.imageIds.slice();
+            if (direction === NavigationDirection.Prev) {
+                imageIds.reverse();
+            }
+            return this._stateService.currentState$.pipe(map((frame) => {
+                return [frame.state.trajectory[frame.state.trajectory.length - 1].id, frame.state.imagesAhead];
+            }), scan(([lastRequestKey, previousRequestKeys], [lastTrajectoryKey, imagesAhead]) => {
+                if (lastRequestKey === undefined) {
+                    lastRequestKey = lastTrajectoryKey;
+                }
+                const lastIndex = imageIds.length - 1;
+                if (imagesAhead >= this._imagesAhead || imageIds[lastIndex] === lastRequestKey) {
+                    return [lastRequestKey, []];
+                }
+                const current = imageIds.indexOf(lastTrajectoryKey);
+                const start = imageIds.indexOf(lastRequestKey) + 1;
+                const end = Math.min(lastIndex, current + this._imagesAhead - imagesAhead) + 1;
+                if (end <= start) {
+                    return [lastRequestKey, []];
+                }
+                return [imageIds[end - 1], imageIds.slice(start, end)];
+            }, [undefined, []]), mergeMap(([lastRequestKey, newRequestKeys]) => {
+                return from(newRequestKeys);
+            }));
+        }), mergeMap((key) => {
+            return this._graphService.cacheImage$(key).pipe(catchError(() => {
+                return empty();
+            }));
+        }, 6))
+            .subscribe();
+        this._playingSubscription = this._stateService.currentState$.pipe(filter((frame) => {
+            return frame.state.imagesAhead < this._imagesAhead;
+        }), distinctUntilChanged(undefined, (frame) => {
+            return frame.state.lastImage.id;
+        }), map((frame) => {
+            const lastImage = frame.state.lastImage;
+            const trajectory = frame.state.trajectory;
+            let increasingTime = undefined;
+            for (let i = trajectory.length - 2; i >= 0; i--) {
+                const image = trajectory[i];
+                if (image.sequenceId !== lastImage.sequenceId) {
+                    break;
+                }
+                if (image.capturedAt !== lastImage.capturedAt) {
+                    increasingTime = image.capturedAt < lastImage.capturedAt;
+                    break;
+                }
+            }
+            return [frame.state.lastImage, increasingTime];
+        }), withLatestFrom(this._direction$), switchMap(([[image, increasingTime], direction]) => {
+            return zip(([NavigationDirection.Next, NavigationDirection.Prev].indexOf(direction) > -1 ?
+                image.sequenceEdges$ :
+                image.spatialEdges$).pipe(first((status) => {
+                return status.cached;
+            }), timeout(15000)), of(direction)).pipe(map(([s, d]) => {
+                for (let edge of s.edges) {
+                    if (edge.data.direction === d) {
+                        return edge.target;
+                    }
+                }
+                return null;
+            }), switchMap((key) => {
+                return key != null ?
+                    this._graphService.cacheImage$(key) :
+                    empty();
+            }));
+        }))
+            .subscribe((image) => {
+            this._stateService.appendImagess([image]);
+        }, (error) => {
+            console.error(error);
+            this.stop();
+        });
+        this._clearSubscription = this._stateService.currentImage$.pipe(bufferCount(1, 10))
+            .subscribe((images) => {
+            this._stateService.clearPriorImages();
+        });
+        this._setPlaying(true);
+        const currentLastImages$ = this._stateService.currentState$.pipe(map((frame) => {
+            return frame.state;
+        }), distinctUntilChanged(([kc1, kl1], [kc2, kl2]) => {
+            return kc1 === kc2 && kl1 === kl2;
+        }, (state) => {
+            return [state.currentImage.id, state.lastImage.id];
+        }), filter((state) => {
+            return state.currentImage.id === state.lastImage.id &&
+                state.currentIndex === state.trajectory.length - 1;
+        }), map((state) => {
+            return state.currentImage;
+        }));
+        this._stopSubscription = combineLatest(currentLastImages$, this._direction$).pipe(switchMap(([image, direction]) => {
+            const edgeStatus$ = ([NavigationDirection.Next, NavigationDirection.Prev].indexOf(direction) > -1 ?
+                image.sequenceEdges$ :
+                image.spatialEdges$).pipe(first((status) => {
+                return status.cached;
+            }), timeout(15000), catchError((error) => {
+                console.error(error);
+                return of({ cached: false, edges: [] });
+            }));
+            return combineLatest(of(direction), edgeStatus$).pipe(map(([d, es]) => {
+                for (const edge of es.edges) {
+                    if (edge.data.direction === d) {
+                        return true;
+                    }
+                }
+                return false;
+            }));
+        }), mergeMap((hasEdge) => {
+            if (hasEdge || !this._bridging$) {
+                return of(hasEdge);
+            }
+            return this._bridging$.pipe(map((image) => {
+                return image != null;
+            }), catchError((error) => {
+                console.error(error);
+                return of(false);
+            }));
+        }), first((hasEdge) => {
+            return !hasEdge;
+        }))
+            .subscribe(undefined, undefined, () => { this.stop(); });
+        if (this._stopSubscription.closed) {
+            this._stopSubscription = null;
+        }
+        this._earthSubscription = this._stateService.state$
+            .pipe(map((state) => {
+            return state === State.Earth;
+        }), distinctUntilChanged(), first((earth) => {
+            return earth;
+        }))
+            .subscribe(undefined, undefined, () => { this.stop(); });
+        if (this._earthSubscription.closed) {
+            this._earthSubscription = null;
+        }
+    }
+    dispose() {
+        this.stop();
+        this._subscriptions.unsubscribe();
+    }
+    setDirection(direction) {
+        this._directionSubject$.next(direction);
+    }
+    setSpeed(speed) {
+        speed = Math.max(0, Math.min(1, speed));
+        if (speed === this._speed) {
+            return;
+        }
+        const stateSpeed = this._setSpeed(speed);
+        if (this._playing) {
+            this._stateService.setSpeed(stateSpeed);
+        }
+        this._speedSubject$.next(this._speed);
+    }
+    stop() {
+        if (!this._playing) {
+            return;
+        }
+        if (!!this._stopSubscription) {
+            if (!this._stopSubscription.closed) {
+                this._stopSubscription.unsubscribe();
+            }
+            this._stopSubscription = null;
+        }
+        if (!!this._earthSubscription) {
+            if (!this._earthSubscription.closed) {
+                this._earthSubscription.unsubscribe();
+            }
+            this._earthSubscription = null;
+        }
+        this._graphModeSubscription.unsubscribe();
+        this._graphModeSubscription = null;
+        this._cacheSubscription.unsubscribe();
+        this._cacheSubscription = null;
+        this._playingSubscription.unsubscribe();
+        this._playingSubscription = null;
+        this._clearSubscription.unsubscribe();
+        this._clearSubscription = null;
+        this._stateService.setSpeed(1);
+        this._stateService.cutImages();
+        this._graphService.setGraphMode(GraphMode.Spatial);
+        this._setPlaying(false);
+    }
+    _mapSpeed(speed) {
+        const x = 2 * speed - 1;
+        return Math.pow(10, x) - 0.2 * x;
+    }
+    _mapImagesAhead(stateSpeed) {
+        return Math.round(Math.max(10, Math.min(50, 8 + 6 * stateSpeed)));
+    }
+    _setPlaying(playing) {
+        this._playing = playing;
+        this._playingSubject$.next(playing);
+    }
+    _setSpeed(speed) {
+        this._speed = speed;
+        const stateSpeed = this._mapSpeed(this._speed);
+        this._imagesAhead = this._mapImagesAhead(stateSpeed);
+        return stateSpeed;
+    }
+}
+PlayService.sequenceSpeed = 0.54;
+
+var CameraVisualizationMode;
+(function (CameraVisualizationMode) {
+    /**
+     * Cameras are hidden.
+     */
+    CameraVisualizationMode[CameraVisualizationMode["Hidden"] = 0] = "Hidden";
+    /**
+     * Cameras are shown, all with the same color.
+     */
+    CameraVisualizationMode[CameraVisualizationMode["Homogeneous"] = 1] = "Homogeneous";
+    /**
+     * Cameras are shown with colors based on the
+     * their clusters.
+     */
+    CameraVisualizationMode[CameraVisualizationMode["Cluster"] = 2] = "Cluster";
+    /**
+     * Cameras are shown with colors based on the
+     * their connected components.
+     */
+    CameraVisualizationMode[CameraVisualizationMode["ConnectedComponent"] = 3] = "ConnectedComponent";
+    /**
+     * Cameras are shown, with colors based on the
+     * their sequence.
+     */
+    CameraVisualizationMode[CameraVisualizationMode["Sequence"] = 4] = "Sequence";
+})(CameraVisualizationMode || (CameraVisualizationMode = {}));
+
+var OriginalPositionMode;
+(function (OriginalPositionMode) {
+    /**
+     * Original positions are hidden.
+     */
+    OriginalPositionMode[OriginalPositionMode["Hidden"] = 0] = "Hidden";
+    /**
+     * Visualize original positions with altitude change.
+     */
+    OriginalPositionMode[OriginalPositionMode["Altitude"] = 1] = "Altitude";
+    /**
+     * Visualize original positions without altitude change,
+     * i.e. as flat lines from the camera origin.
+     */
+    OriginalPositionMode[OriginalPositionMode["Flat"] = 2] = "Flat";
+})(OriginalPositionMode || (OriginalPositionMode = {}));
+
+class ClusterPoints extends Points {
+    constructor(parameters) {
+        super();
+        this._originalSize = parameters.originalSize;
+        const cluster = parameters.cluster;
+        const scale = parameters.scale;
+        const translation = parameters.translation;
+        this._makeAttributes(cluster);
+        this.material.size = scale * this._originalSize;
+        this.material.vertexColors = true;
+        this.material.needsUpdate = true;
+        this.matrixAutoUpdate = false;
+        this.position.fromArray(translation);
+        this.updateMatrix();
+        this.updateMatrixWorld(false);
+    }
+    dispose() {
+        this.geometry.dispose();
+        this.material.dispose();
+    }
+    resize(scale) {
+        this.material.size = scale * this._originalSize;
+        this.material.needsUpdate = true;
+    }
+    _makeAttributes(cluster) {
+        const positions = [];
+        const colors = [];
+        const points = cluster.points;
+        for (const pointId in points) {
+            if (!points.hasOwnProperty(pointId)) {
+                continue;
+            }
+            const point = points[pointId];
+            positions.push(...point.coordinates);
+            const color = point.color;
+            colors.push(color[0]);
+            colors.push(color[1]);
+            colors.push(color[2]);
+        }
+        const geometry = this.geometry;
+        geometry.setAttribute("position", new BufferAttribute(new Float32Array(positions), 3));
+        geometry.setAttribute("color", new BufferAttribute(new Float32Array(colors), 3));
+    }
+}
+
+class CellLine extends Line {
+    constructor(vertices) {
+        super();
+        this._makeAttributes(vertices);
+        this.matrixAutoUpdate = false;
+        this.updateMatrix();
+        this.updateMatrixWorld(false);
+    }
+    dispose() {
+        this.geometry.dispose();
+        this.material.dispose();
+    }
+    _makeAttributes(vertices) {
+        const closedPolygon = vertices.slice();
+        closedPolygon.push(vertices[0]);
+        let index = 0;
+        const positions = new Float32Array(3 * (vertices.length + 1));
+        for (const vertex of closedPolygon) {
+            positions[index++] = vertex[0];
+            positions[index++] = vertex[1];
+            positions[index++] = vertex[2];
+        }
+        this.geometry.setAttribute("position", new BufferAttribute(positions, 3));
+    }
+}
+
+// Level 0: 1 x 1 x 1 meter cubes
+const OCTREE_ROOT_LEVEL = 14; // 16384 meters
+const OCTREE_LEAF_LEVEL = 6; // 64 meters
+function isLeafLevel(level, leafLevel) {
+    return level === leafLevel;
+}
+function levelToSize(level) {
+    return Math.pow(2, level);
+}
+function levelToRootBoundingBox(level) {
+    const size = levelToSize(level);
+    const half = size / 2;
+    const min = [-half, -half, -half];
+    const max = [half, half, half];
+    return { min, max };
+}
+
+class SpatialOctreeNode {
+    constructor(level, leafLevel, boundingBox, parent) {
+        this.level = level;
+        this.leafLevel = leafLevel;
+        this.boundingBox = boundingBox;
+        this.parent = parent;
+        this.children = [];
+        this.items = [];
+        if (parent) {
+            parent.children.push(this);
+        }
+    }
+    get isEmpty() {
+        return !(this.children.length || this.items.length);
+    }
+    add(object) {
+        const self = this;
+        if (!self.boundingBox.containsPoint(object.position)) {
+            throw new Error(`Item not contained in node`);
+        }
+        if (isLeafLevel(self.level, self.leafLevel)) {
+            self.items.push(object);
+            return this;
+        }
+        for (const child of self.children) {
+            if (child.boundingBox.containsPoint(object.position)) {
+                return child.add(object);
+            }
+        }
+        for (const boundingBox of self._generateBoundingBoxes()) {
+            if (boundingBox.containsPoint(object.position)) {
+                const child = new SpatialOctreeNode(self.level - 1, self.leafLevel, boundingBox, self);
+                return child.add(object);
+            }
+        }
+        throw new Error(`Item not contained in children`);
+    }
+    intersect(ray, target, nodes) {
+        if (!ray.intersectBox(this.boundingBox, target)) {
+            return;
+        }
+        if (isLeafLevel(this.level, this.leafLevel)) {
+            nodes.push(this);
+            return;
+        }
+        for (const child of this.children) {
+            child.intersect(ray, target, nodes);
+        }
+    }
+    remove(object) {
+        const index = this.items.indexOf(object);
+        if (index < 0) {
+            throw new Error(`Item does not exist ${object.uuid}`);
+        }
+        this.items.splice(index, 1);
+    }
+    traverse() {
+        const self = this;
+        if (!self.isEmpty) {
+            return;
+        }
+        const parent = self.parent;
+        if (!parent) {
+            return;
+        }
+        const index = parent.children.indexOf(self);
+        if (index < 0) {
+            throw new Error(`Corrupt octree`);
+        }
+        parent.children.splice(index, 1);
+        this.parent = null;
+        parent.traverse();
+    }
+    _generateBoundingBoxes() {
+        const self = this;
+        const min = self.boundingBox.min;
+        const max = self.boundingBox.max;
+        const size = (max.x - min.x) / 2;
+        const mins = [
+            [min.x, min.y + size, min.z + size],
+            [min.x + size, min.y + size, min.z + size],
+            [min.x, min.y, min.z + size],
+            [min.x + size, min.y, min.z + size],
+            [min.x, min.y + size, min.z],
+            [min.x + size, min.y + size, min.z],
+            [min.x, min.y, min.z],
+            [min.x + size, min.y, min.z],
+        ];
+        const boundingBoxes = [];
+        for (const [minX, minY, minZ] of mins) {
+            boundingBoxes.push(new Box3(new Vector3(minX, minY, minZ), new Vector3(minX + size, minY + size, minZ + size)));
+        }
+        return boundingBoxes;
+    }
+}
+
+class SpatialOctree {
+    constructor(rootLevel, leafLevel) {
+        this.rootLevel = rootLevel;
+        this.leafLevel = leafLevel;
+        if (leafLevel > rootLevel) {
+            throw new Error();
+        }
+        this._index = new Map();
+        this._root = this._makeRoot();
+    }
+    get root() {
+        return this._root;
+    }
+    add(object) {
+        if (!this.root.boundingBox.containsPoint(object.position)) {
+            console.warn(`Object outside bounding box ${object.uuid}`);
+            return;
+        }
+        const leaf = this._root.add(object);
+        this._index.set(object.uuid, leaf);
+    }
+    has(object) {
+        return this._index.has(object.uuid);
+    }
+    intersect(ray) {
+        const leaves = [];
+        const target = new Vector3();
+        this._root.intersect(ray, target, leaves);
+        return leaves
+            .map(leaf => leaf.items)
+            .reduce((acc, items) => {
+            acc.push(...items);
+            return acc;
+        }, []);
+    }
+    reset() {
+        this._root = this._makeRoot();
+        this._index.clear();
+    }
+    remove(object) {
+        if (!this.has(object)) {
+            throw new Error(`Frame does not exist ${object.uuid}`);
+        }
+        const leaf = this._index.get(object.uuid);
+        leaf.remove(object);
+        leaf.traverse();
+        this._index.delete(object.uuid);
+    }
+    _makeRoot() {
+        const level = this.rootLevel;
+        const bbox = levelToRootBoundingBox(level);
+        const box = new Box3(new Vector3().fromArray(bbox.min), new Vector3().fromArray(bbox.max));
+        return new SpatialOctreeNode(level, this.leafLevel, box);
+    }
+}
+
+class SpatialIntersection {
+    constructor(octree, raycaster) {
+        this._objects = [];
+        this._objectImageMap = new Map();
+        this._octree = octree !== null && octree !== void 0 ? octree : new SpatialOctree(OCTREE_ROOT_LEVEL, OCTREE_LEAF_LEVEL);
+        this._raycaster = raycaster !== null && raycaster !== void 0 ? raycaster : new Raycaster();
+        this._interactiveLayer = 1;
+        this._raycaster = !!raycaster ?
+            raycaster :
+            new Raycaster(undefined, undefined, 1, 10000);
+        this._lineThreshold = 0.2;
+        this._largeLineThreshold = 0.4;
+        this._raycaster.params.Line.threshold = this._lineThreshold;
+        this._raycaster.layers.set(this._interactiveLayer);
+    }
+    get interactiveLayer() { return this._interactiveLayer; }
+    get octree() { return this._octree; }
+    get raycaster() { return this._raycaster; }
+    add(object, imageId) {
+        const uuid = object.uuid;
+        this._objectImageMap.set(uuid, imageId);
+        this._objects.push(object);
+        this._octree.add(object);
+    }
+    intersectObjects(viewport, camera) {
+        this._raycaster.setFromCamera(new Vector2().fromArray(viewport), camera);
+        const objects = this._octree.intersect(this.raycaster.ray);
+        const intersects = this._raycaster.intersectObjects(objects);
+        const onMap = this._objectImageMap;
+        for (const intersect of intersects) {
+            const uuid = intersect.object.uuid;
+            if (!onMap.has(uuid)) {
+                continue;
+            }
+            return onMap.get(uuid);
+        }
+        return null;
+    }
+    remove(object) {
+        const objects = this._objects;
+        const index = objects.indexOf(object);
+        if (index !== -1) {
+            const deleted = objects.splice(index, 1);
+            for (const d of deleted) {
+                this._objectImageMap.delete(d.uuid);
+            }
+            this._octree.remove(object);
+        }
+        else {
+            console.warn(`Object does not exist`);
+        }
+    }
+    resetIntersectionThreshold(useLarge) {
+        this._raycaster.params.Line.threshold = useLarge ?
+            this._largeLineThreshold :
+            this._lineThreshold;
+    }
+}
+
+class PositionLine extends Line {
+    constructor(parameters) {
+        super(parameters.geometry, parameters.material);
+        const mode = parameters.mode;
+        const originalOrigin = parameters.originalOrigin;
+        const transform = parameters.transform;
+        const origin = transform.unprojectBasic([0, 0], 0);
+        this._relativeAltitude = originalOrigin[2] - origin[2];
+        this._makeAttributes(origin, originalOrigin, mode);
+        this.matrixAutoUpdate = false;
+        this.position.fromArray(origin);
+        this.updateMatrix();
+        this.updateMatrixWorld(false);
+    }
+    dispose() {
+        this.geometry.dispose();
+        this.material.dispose();
+    }
+    setMode(mode) {
+        const positionAttribute = this.geometry.attributes.position;
+        const positions = positionAttribute.array;
+        positions[5] = this._modeToAltitude(mode);
+        positionAttribute.needsUpdate = true;
+        this.geometry.computeBoundingSphere();
+    }
+    _makeAttributes(origin, originalOrigin, mode) {
+        const positions = new Float32Array(6);
+        positions[0] = 0;
+        positions[1] = 0;
+        positions[2] = 0;
+        positions[3] = originalOrigin[0] - origin[0];
+        positions[4] = originalOrigin[1] - origin[1];
+        positions[5] = this._modeToAltitude(mode);
+        const attribute = new BufferAttribute(positions, 3);
+        this.geometry.setAttribute("position", attribute);
+        attribute.needsUpdate = true;
+        this.geometry.computeBoundingSphere();
+    }
+    _modeToAltitude(mode) {
+        return mode === OriginalPositionMode.Altitude ?
+            this._relativeAltitude : 0;
+    }
+}
+
+class CameraFrameBase extends LineSegments {
+    constructor(parameters) {
+        super(parameters.geometry, parameters.material);
+        const color = parameters.color;
+        const size = parameters.size;
+        const scale = parameters.scale;
+        const transform = parameters.transform;
+        const origin = transform.unprojectBasic([0, 0], 0);
+        const positions = this._makePositions(size, transform, origin);
+        this._makeAttributes(positions, color);
+        this.geometry.computeBoundingSphere();
+        this.geometry.computeBoundingBox();
+        this.matrixAutoUpdate = false;
+        this.position.fromArray(origin);
+        this.scale.set(scale, scale, scale);
+        this.updateMatrix();
+        this.updateMatrixWorld(false);
+    }
+    dispose() {
+        this.geometry.dispose();
+        this.material.dispose();
+    }
+    setColor(color) {
+        this._updateColorAttribute(color);
+        return this;
+    }
+    resize(scale) {
+        this.scale.set(scale, scale, scale);
+        this.updateMatrix();
+        this.updateMatrixWorld(false);
+        return this;
+    }
+    _makeAttributes(positions, color) {
+        const geometry = this.geometry;
+        const positionAttribute = new BufferAttribute(new Float32Array(positions), 3);
+        geometry.setAttribute("position", positionAttribute);
+        positionAttribute.needsUpdate = true;
+        const colorAttribute = new BufferAttribute(new Float32Array(positions.length), 3);
+        geometry.setAttribute("color", colorAttribute);
+        this._updateColorAttribute(color);
+    }
+    _updateColorAttribute(color) {
+        const [r, g, b] = new Color(color).toArray();
+        const colorAttribute = this.geometry.attributes.color;
+        const colors = colorAttribute.array;
+        const length = colors.length;
+        let index = 0;
+        for (let i = 0; i < length; i++) {
+            colors[index++] = r;
+            colors[index++] = g;
+            colors[index++] = b;
+        }
+        colorAttribute.needsUpdate = true;
+    }
+}
+
+class SphericalCameraFrame extends CameraFrameBase {
+    _makePositions(size, transform, origin) {
+        const vs = 10;
+        const positions = [];
+        positions.push(...this._makeAxis(size, transform, origin));
+        positions.push(...this._makeLat(0.5, vs, size, transform, origin));
+        for (const lat of [0, 0.25, 0.5, 0.75]) {
+            positions
+                .push(...this._makeLng(lat, vs, size, transform, origin));
+        }
+        return positions;
+    }
+    _makeAxis(size, transform, origin) {
+        const south = transform.unprojectBasic([0.5, 1], 0.8 * size);
+        const north = transform.unprojectBasic([0.5, 0], 1.2 * size);
+        return [
+            south[0] - origin[0],
+            south[1] - origin[1],
+            south[2] - origin[2],
+            north[0] - origin[0],
+            north[1] - origin[1],
+            north[2] - origin[2],
+        ];
+    }
+    _makeLat(basicY, numVertices, size, transform, origin) {
+        const dist = 0.8 * size;
+        const [originX, originY, originZ] = origin;
+        const positions = [];
+        const first = transform.unprojectBasic([0, basicY], dist);
+        first[0] -= originX;
+        first[1] -= originY;
+        first[2] -= originZ;
+        positions.push(...first);
+        for (let i = 1; i <= numVertices; i++) {
+            const position = transform.unprojectBasic([i / numVertices, basicY], dist);
+            position[0] -= originX;
+            position[1] -= originY;
+            position[2] -= originZ;
+            positions.push(...position, ...position);
+        }
+        positions.push(...first);
+        return positions;
+    }
+    _makeLng(basicX, numVertices, size, transform, origin) {
+        const dist = 0.8 * size;
+        const [originX, originY, originZ] = origin;
+        const positions = [];
+        const first = transform.unprojectBasic([basicX, 0], dist);
+        first[0] -= originX;
+        first[1] -= originY;
+        first[2] -= originZ;
+        positions.push(...first);
+        for (let i = 0; i <= numVertices; i++) {
+            const position = transform.unprojectBasic([basicX, i / numVertices], dist);
+            position[0] -= originX;
+            position[1] -= originY;
+            position[2] -= originZ;
+            positions.push(...position, ...position);
+        }
+        positions.push(...first);
+        return positions;
+    }
+}
+
+class PerspectiveCameraFrame extends CameraFrameBase {
+    _makePositions(size, transform, origin) {
+        const samples = 8;
+        const positions = [];
+        positions.push(...this._makeDiags(size, transform, origin));
+        positions.push(...this._makeFrame(size, samples, transform, origin));
+        return positions;
+    }
+    _makeDiags(size, transform, origin) {
+        const depth = size;
+        const [originX, originY, originZ] = origin;
+        const cameraCenter = [0, 0, 0];
+        const positions = [];
+        for (const vertex2d of [[0, 0], [1, 0], [1, 1], [0, 1]]) {
+            const corner = transform.unprojectBasic(vertex2d, depth, true);
+            corner[0] -= originX;
+            corner[1] -= originY;
+            corner[2] -= originZ;
+            positions.push(...cameraCenter, ...corner);
+        }
+        return positions;
+    }
+    _makeFrame(size, samples, transform, origin) {
+        const vertices2d = [];
+        vertices2d.push(...this._subsample([0, 1], [0, 0], samples));
+        vertices2d.push(...this._subsample([0, 0], [1, 0], samples));
+        vertices2d.push(...this._subsample([1, 0], [1, 1], samples));
+        const depth = size;
+        const [originX, originY, originZ] = origin;
+        const positions = [];
+        for (const vertex2d of vertices2d) {
+            const position = transform.unprojectBasic(vertex2d, depth, true);
+            position[0] -= originX;
+            position[1] -= originY;
+            position[2] -= originZ;
+            positions.push(...position);
+        }
+        return positions;
+    }
+    _interpolate(a, b, alpha) {
+        return a + alpha * (b - a);
+    }
+    _subsample(p1, p2, subsamples) {
+        if (subsamples < 1) {
+            return [p1, p2];
+        }
+        const samples = [];
+        samples.push(p1);
+        for (let i = 0; i <= subsamples; i++) {
+            const p = [];
+            for (let j = 0; j < 3; j++) {
+                p.push(this._interpolate(p1[j], p2[j], i / (subsamples + 1)));
+            }
+            samples.push(p);
+            samples.push(p);
+        }
+        samples.push(p2);
+        return samples;
+    }
+}
+
+class SpatialCell {
+    constructor(id, _scene, _intersection) {
+        this.id = id;
+        this._scene = _scene;
+        this._intersection = _intersection;
+        this.cameras = new Object3D();
+        this.keys = [];
+        this._positionLines = {};
+        this._positions = new Object3D();
+        this._cameraFrames = {};
+        this._clusters = new Map();
+        this._connectedComponents = new Map();
+        this._sequences = new Map();
+        this._props = {};
+        this.clusterVisibles = {};
+        this._frameMaterial = new LineBasicMaterial({
+            fog: false,
+            vertexColors: true,
+        });
+        this._positionMaterial = new LineBasicMaterial({
+            fog: false,
+            color: 0xff0000,
+        });
+        this._scene.add(this.cameras, this._positions);
+    }
+    addImage(props) {
+        const image = props.image;
+        const id = image.id;
+        if (this.hasImage(id)) {
+            throw new Error(`Image exists ${id}`);
+        }
+        const ccId = props.idMap.ccId;
+        if (!(this._connectedComponents.has(ccId))) {
+            this._connectedComponents.set(ccId, []);
+        }
+        const cId = props.idMap.clusterId;
+        if (!this._clusters.has(cId)) {
+            this._clusters.set(cId, []);
+        }
+        const sId = props.idMap.sequenceId;
+        if (!this._sequences.has(sId)) {
+            this._sequences.set(sId, []);
+        }
+        this._props[id] = {
+            image: image,
+            ids: { ccId, clusterId: cId, sequenceId: sId },
+        };
+        this.keys.push(id);
+    }
+    applyCameraColor(imageId, color) {
+        this._cameraFrames[imageId].setColor(color);
+    }
+    applyCameraSize(size) {
+        for (const camera of this.cameras.children) {
+            camera.resize(size);
+        }
+    }
+    applyFilter(filter) {
+        var _a;
+        const clusterVisibles = this.clusterVisibles;
+        for (const clusterId in clusterVisibles) {
+            if (!clusterVisibles.hasOwnProperty(clusterId)) {
+                continue;
+            }
+            clusterVisibles[clusterId] = false;
+        }
+        const cameraFrames = this._cameraFrames;
+        const positionLines = this._positionLines;
+        const interactiveLayer = this._intersection.interactiveLayer;
+        for (const props of Object.values(this._props)) {
+            const image = props.image;
+            const visible = filter(image);
+            const key = image.id;
+            positionLines[key].visible = visible;
+            const camera = cameraFrames[key];
+            this._setCameraVisibility(camera, visible, interactiveLayer);
+            clusterVisibles[_a = props.ids.clusterId] || (clusterVisibles[_a] = visible);
+        }
+    }
+    applyPositionMode(mode) {
+        this._positions.visible =
+            mode !== OriginalPositionMode.Hidden;
+        for (const position of this._positions.children) {
+            position.setMode(mode);
+        }
+    }
+    dispose() {
+        this._disposeCameras();
+        this._disposePositions();
+        this._scene = null;
+        this._intersection = null;
+    }
+    getCamerasByMode(mode) {
+        if (mode === CameraVisualizationMode.Cluster) {
+            return this._clusters;
+        }
+        else if (mode === CameraVisualizationMode.ConnectedComponent) {
+            return this._connectedComponents;
+        }
+        else if (mode === CameraVisualizationMode.Sequence) {
+            return this._sequences;
+        }
+        const cvm = CameraVisualizationMode;
+        const defaultId = cvm[cvm.Homogeneous];
+        const cameras = new Map();
+        cameras.set(defaultId, this.cameras.children);
+        return cameras;
+    }
+    getColorId(imageId, mode) {
+        const props = this._props[imageId];
+        const cvm = CameraVisualizationMode;
+        switch (mode) {
+            case cvm.Cluster:
+                return props.ids.clusterId;
+            case cvm.ConnectedComponent:
+                return props.ids.ccId;
+            case cvm.Sequence:
+                return props.ids.sequenceId;
+            default:
+                return cvm[cvm.Homogeneous];
+        }
+    }
+    hasImage(key) {
+        return this.keys.indexOf(key) !== -1;
+    }
+    visualize(props) {
+        var _a, _b;
+        const id = props.id;
+        const visible = props.visible;
+        const transform = props.transform;
+        const cameraParameters = {
+            color: props.color,
+            material: this._frameMaterial,
+            scale: props.scale,
+            size: props.maxSize,
+            transform,
+        };
+        const camera = isSpherical(transform.cameraType) ?
+            new SphericalCameraFrame(cameraParameters) :
+            new PerspectiveCameraFrame(cameraParameters);
+        const interactiveLayer = this._intersection.interactiveLayer;
+        this._setCameraVisibility(camera, visible, interactiveLayer);
+        this.cameras.add(camera);
+        this._cameraFrames[id] = camera;
+        const intersection = this._intersection;
+        intersection.add(camera, id);
+        const ids = this._props[id].ids;
+        (_a = this.clusterVisibles)[_b = ids.clusterId] || (_a[_b] = visible);
+        this._connectedComponents.get(ids.ccId).push(camera);
+        this._clusters.get(ids.clusterId).push(camera);
+        this._sequences.get(ids.sequenceId).push(camera);
+        const positionParameters = {
+            material: this._positionMaterial,
+            mode: props.positionMode,
+            originalOrigin: props.originalPosition,
+            transform,
+        };
+        const position = new PositionLine(positionParameters);
+        position.visible = visible;
+        this._positions.add(position);
+        this._positionLines[id] = position;
+    }
+    _disposeCameras() {
+        const intersection = this._intersection;
+        const cameras = this.cameras;
+        for (const camera of cameras.children.slice()) {
+            camera.dispose();
+            intersection.remove(camera);
+            cameras.remove(camera);
+        }
+        this._scene.remove(this.cameras);
+    }
+    _disposePositions() {
+        const positions = this._positions;
+        for (const position of positions.children.slice()) {
+            position.dispose();
+            positions.remove(position);
+        }
+        this._scene.remove(this._positions);
+    }
+    _setCameraVisibility(camera, visible, layer) {
+        camera.visible = visible;
+        if (visible) {
+            camera.layers.enable(layer);
+        }
+        else {
+            camera.layers.disable(layer);
+        }
+    }
+}
+
+class SpatialAssets {
+    constructor() {
+        this._colors = new Map();
+        const cvm = CameraVisualizationMode;
+        this._colors.set(cvm[cvm.Homogeneous], "#FFFFFF");
+    }
+    getColor(id) {
+        const colors = this._colors;
+        if (!colors.has(id)) {
+            colors.set(id, this._randomColor());
+        }
+        return colors.get(id);
+    }
+    _randomColor() {
+        return `hsl(${Math.floor(360 * Math.random())}, 100%, 50%)`;
+    }
+}
+
+function isModeVisible(mode) {
+    return mode !== CameraVisualizationMode.Hidden;
+}
+function isOverviewState(state) {
+    return state === State.Custom || state === State.Earth;
+}
+
+const NO_CLUSTER_ID = "NO_CLUSTER_ID";
+const NO_MERGE_ID = "NO_MERGE_ID";
+const NO_SEQUENCE_ID = "NO_SEQUENCE_ID";
+class SpatialScene {
+    constructor(configuration, scene) {
+        this._rayNearScale = 1.1;
+        this._originalPointSize = 2;
+        this._originalCameraSize = 2;
+        this._imageCellMap = new Map();
+        this._scene = !!scene ? scene : new Scene();
+        this._scene.autoUpdate = false;
+        this._intersection = new SpatialIntersection();
+        this._assets = new SpatialAssets();
+        this._needsRender = false;
+        this._images = {};
+        this._cells = {};
+        this._cellClusters = {};
+        this._clusters = {};
+        this._cameraVisualizationMode =
+            !!configuration.cameraVisualizationMode ?
+                configuration.cameraVisualizationMode :
+                CameraVisualizationMode.Homogeneous;
+        this._cameraSize = configuration.cameraSize;
+        this._pointSize = configuration.pointSize;
+        this._pointsVisible = configuration.pointsVisible;
+        this._positionMode = configuration.originalPositionMode;
+        this._cellsVisible = configuration.cellsVisible;
+        this._hoveredId = null;
+        this._selectedId = null;
+        this._colors = { hover: "#FF0000", select: "#FF8000" };
+        this._filter = () => true;
+    }
+    get needsRender() { return this._needsRender; }
+    get intersection() {
+        return this._intersection;
+    }
+    addCluster(reconstruction, translation, cellId) {
+        if (this.hasCluster(reconstruction.id, cellId)) {
+            return;
+        }
+        const clusterId = reconstruction.id;
+        if (!(clusterId in this._clusters)) {
+            this._clusters[clusterId] = {
+                points: new Object3D(),
+                cellIds: [],
+            };
+            const visible = this._getClusterVisible(clusterId);
+            this._clusters[clusterId].points.visible = visible;
+            this._clusters[clusterId].points.add(new ClusterPoints({
+                cluster: reconstruction,
+                originalSize: this._originalPointSize,
+                scale: this._pointSize,
+                translation,
+            }));
+            this._scene.add(this._clusters[clusterId].points);
+        }
+        if (this._clusters[clusterId].cellIds.indexOf(cellId) === -1) {
+            this._clusters[clusterId].cellIds.push(cellId);
+        }
+        if (!(cellId in this._cellClusters)) {
+            this._cellClusters[cellId] = { keys: [] };
+        }
+        if (this._cellClusters[cellId].keys.indexOf(clusterId) === -1) {
+            this._cellClusters[cellId].keys.push(clusterId);
+        }
+        this._needsRender = true;
+    }
+    addImage(image, transform, originalPosition, cellId) {
+        var _a, _b, _c;
+        const imageId = image.id;
+        const idMap = {
+            clusterId: (_a = image.clusterId) !== null && _a !== void 0 ? _a : NO_CLUSTER_ID,
+            sequenceId: (_b = image.sequenceId) !== null && _b !== void 0 ? _b : NO_SEQUENCE_ID,
+            ccId: (_c = image.mergeId) !== null && _c !== void 0 ? _c : NO_MERGE_ID,
+        };
+        if (!(cellId in this._images)) {
+            const created = new SpatialCell(cellId, this._scene, this._intersection);
+            created.cameras.visible =
+                isModeVisible(this._cameraVisualizationMode);
+            created.applyPositionMode(this._positionMode);
+            this._images[cellId] = created;
+        }
+        const cell = this._images[cellId];
+        if (cell.hasImage(imageId)) {
+            return;
+        }
+        cell.addImage({ idMap, image: image });
+        const colorId = cell.getColorId(imageId, this._cameraVisualizationMode);
+        const color = this._assets.getColor(colorId);
+        const visible = this._filter(image);
+        cell.visualize({
+            id: imageId,
+            color,
+            positionMode: this._positionMode,
+            scale: this._cameraSize,
+            transform,
+            visible,
+            maxSize: this._originalCameraSize,
+            originalPosition
+        });
+        this._imageCellMap.set(imageId, cellId);
+        if (imageId === this._selectedId) {
+            this._highlight(imageId, this._colors.select, this._cameraVisualizationMode);
+        }
+        if (idMap.clusterId in this._clusters) {
+            const clusterVisible = this._getClusterVisible(idMap.clusterId);
+            this._clusters[idMap.clusterId].points.visible = clusterVisible;
+        }
+        this._needsRender = true;
+    }
+    addCell(vertices, cellId) {
+        if (this.hasCell(cellId)) {
+            return;
+        }
+        const cell = new CellLine(vertices);
+        this._cells[cellId] = new Object3D();
+        this._cells[cellId].visible = this._cellsVisible;
+        this._cells[cellId].add(cell);
+        this._scene.add(this._cells[cellId]);
+        this._needsRender = true;
+    }
+    deactivate() {
+        this._filter = () => true;
+        this._selectedId = null;
+        this._hoveredId = null;
+        this.uncache();
+    }
+    hasCluster(clusterId, cellId) {
+        return clusterId in this._clusters &&
+            this._clusters[clusterId].cellIds.indexOf(cellId) !== -1;
+    }
+    hasCell(cellId) {
+        return cellId in this._cells;
+    }
+    hasImage(imageId, cellId) {
+        return cellId in this._images &&
+            this._images[cellId].hasImage(imageId);
+    }
+    setCameraSize(cameraSize) {
+        if (Math.abs(cameraSize - this._cameraSize) < 1e-3) {
+            return;
+        }
+        const imageCells = this._images;
+        for (const cellId of Object.keys(imageCells)) {
+            imageCells[cellId].applyCameraSize(cameraSize);
+        }
+        this._intersection.raycaster.near = this._getNear(cameraSize);
+        this._cameraSize = cameraSize;
+        this._needsRender = true;
+    }
+    setFilter(filter) {
+        this._filter = filter;
+        const clusterVisibles = {};
+        for (const imageCell of Object.values(this._images)) {
+            imageCell.applyFilter(filter);
+            const imageCV = imageCell.clusterVisibles;
+            for (const clusterId in imageCV) {
+                if (!imageCV.hasOwnProperty(clusterId)) {
+                    continue;
+                }
+                if (!(clusterId in clusterVisibles)) {
+                    clusterVisibles[clusterId] = false;
+                }
+                clusterVisibles[clusterId] || (clusterVisibles[clusterId] = imageCV[clusterId]);
+            }
+        }
+        const pointsVisible = this._pointsVisible;
+        for (const clusterId in clusterVisibles) {
+            if (!clusterVisibles.hasOwnProperty(clusterId)) {
+                continue;
+            }
+            clusterVisibles[clusterId] && (clusterVisibles[clusterId] = pointsVisible);
+            const visible = clusterVisibles[clusterId];
+            if (clusterId in this._clusters) {
+                this._clusters[clusterId].points.visible = visible;
+            }
+        }
+        this._needsRender = true;
+    }
+    setHoveredImage(imageId) {
+        if (imageId != null && !this._imageCellMap.has(imageId)) {
+            throw new MapillaryError(`Image does not exist: ${imageId}`);
+        }
+        if (this._hoveredId === imageId) {
+            return;
+        }
+        this._needsRender = true;
+        if (this._hoveredId != null) {
+            if (this._hoveredId === this._selectedId) {
+                this._highlight(this._hoveredId, this._colors.select, this._cameraVisualizationMode);
+            }
+            else {
+                this._resetCameraColor(this._hoveredId);
+            }
+        }
+        this._highlight(imageId, this._colors.hover, this._cameraVisualizationMode);
+        this._hoveredId = imageId;
+    }
+    setNavigationState(isOverview) {
+        this._intersection.resetIntersectionThreshold(isOverview);
+    }
+    setPointSize(pointSize) {
+        if (Math.abs(pointSize - this._pointSize) < 1e-3) {
+            return;
+        }
+        const clusters = this._clusters;
+        for (const key in clusters) {
+            if (!clusters.hasOwnProperty(key)) {
+                continue;
+            }
+            for (const points of clusters[key].points.children) {
+                points.resize(pointSize);
+            }
+        }
+        this._pointSize = pointSize;
+        this._needsRender = true;
+    }
+    setPointVisibility(visible) {
+        if (visible === this._pointsVisible) {
+            return;
+        }
+        for (const clusterId in this._clusters) {
+            if (!this._clusters.hasOwnProperty(clusterId)) {
+                continue;
+            }
+            this._clusters[clusterId].points.visible = visible;
+        }
+        this._pointsVisible = visible;
+        this._needsRender = true;
+    }
+    setPositionMode(mode) {
+        if (mode === this._positionMode) {
+            return;
+        }
+        for (const cell of Object.values(this._images)) {
+            cell.applyPositionMode(mode);
+        }
+        this._positionMode = mode;
+        this._needsRender = true;
+    }
+    setSelectedImage(id) {
+        if (this._selectedId === id) {
+            return;
+        }
+        this._needsRender = true;
+        if (this._selectedId != null) {
+            this._resetCameraColor(this._selectedId);
+        }
+        this._highlight(id, this._colors.select, this._cameraVisualizationMode);
+        this._selectedId = id;
+    }
+    setCellVisibility(visible) {
+        if (visible === this._cellsVisible) {
+            return;
+        }
+        for (const cellId in this._cells) {
+            if (!this._cells.hasOwnProperty(cellId)) {
+                continue;
+            }
+            this._cells[cellId].visible = visible;
+        }
+        this._cellsVisible = visible;
+        this._needsRender = true;
+    }
+    setCameraVisualizationMode(mode) {
+        if (mode === this._cameraVisualizationMode) {
+            return;
+        }
+        const visible = isModeVisible(mode);
+        const assets = this._assets;
+        for (const cell of Object.values(this._images)) {
+            cell.cameras.visible = visible;
+            const cameraMap = cell.getCamerasByMode(mode);
+            cameraMap.forEach((cameras, colorId) => {
+                const color = assets.getColor(colorId);
+                for (const camera of cameras) {
+                    camera.setColor(color);
+                }
+            });
+        }
+        this._highlight(this._hoveredId, this._colors.hover, mode);
+        this._highlight(this._selectedId, this._colors.select, mode);
+        this._cameraVisualizationMode = mode;
+        this._needsRender = true;
+    }
+    render(camera, renderer) {
+        renderer.render(this._scene, camera);
+        this._needsRender = false;
+    }
+    uncache(keepCellIds) {
+        for (const cellId of Object.keys(this._cellClusters)) {
+            if (!!keepCellIds && keepCellIds.indexOf(cellId) !== -1) {
+                continue;
+            }
+            this._disposeReconstruction(cellId);
+        }
+        for (const cellId of Object.keys(this._images)) {
+            if (!!keepCellIds && keepCellIds.indexOf(cellId) !== -1) {
+                continue;
+            }
+            const nceMap = this._imageCellMap;
+            const keys = this._images[cellId].keys;
+            for (const key of keys) {
+                nceMap.delete(key);
+            }
+            this._images[cellId].dispose();
+            delete this._images[cellId];
+        }
+        for (const cellId of Object.keys(this._cells)) {
+            if (!!keepCellIds && keepCellIds.indexOf(cellId) !== -1) {
+                continue;
+            }
+            this._disposeCell(cellId);
+        }
+        this._needsRender = true;
+    }
+    _getClusterVisible(clusterId) {
+        if (!this._pointsVisible) {
+            return false;
+        }
+        let visible = false;
+        for (const imageCell of Object.values(this._images)) {
+            const imageCV = imageCell.clusterVisibles;
+            if (!(clusterId in imageCV)) {
+                continue;
+            }
+            visible || (visible = imageCV[clusterId]);
+        }
+        return visible;
+    }
+    _disposePoints(cellId) {
+        for (const clusterId of this._cellClusters[cellId].keys) {
+            if (!(clusterId in this._clusters)) {
+                continue;
+            }
+            const index = this._clusters[clusterId].cellIds.indexOf(cellId);
+            if (index === -1) {
+                continue;
+            }
+            this._clusters[clusterId].cellIds.splice(index, 1);
+            if (this._clusters[clusterId].cellIds.length > 0) {
+                continue;
+            }
+            for (const points of this._clusters[clusterId].points.children.slice()) {
+                points.dispose();
+            }
+            this._scene.remove(this._clusters[clusterId].points);
+            delete this._clusters[clusterId];
+        }
+    }
+    _disposeReconstruction(cellId) {
+        this._disposePoints(cellId);
+        delete this._cellClusters[cellId];
+    }
+    _disposeCell(cellId) {
+        const cell = this._cells[cellId];
+        for (const line of cell.children.slice()) {
+            line.dispose();
+            cell.remove(line);
+        }
+        this._scene.remove(cell);
+        delete this._cells[cellId];
+    }
+    _getNear(cameraSize) {
+        const near = this._rayNearScale *
+            this._originalCameraSize *
+            cameraSize;
+        return Math.max(1, near);
+    }
+    _resetCameraColor(imageId) {
+        const nceMap = this._imageCellMap;
+        if (imageId == null || !nceMap.has(imageId)) {
+            return;
+        }
+        const cellId = nceMap.get(imageId);
+        const cell = this._images[cellId];
+        const colorId = cell.getColorId(imageId, this._cameraVisualizationMode);
+        const color = this._assets.getColor(colorId);
+        cell.applyCameraColor(imageId, color);
+    }
+    _highlight(imageId, color, mode) {
+        const nceMap = this._imageCellMap;
+        if (imageId == null || !nceMap.has(imageId)) {
+            return;
+        }
+        const cellId = nceMap.get(imageId);
+        color = mode === CameraVisualizationMode.Homogeneous ?
+            color : "#FFFFFF";
+        this._images[cellId].applyCameraColor(imageId, color);
+    }
+}
+
+class SpatialCache {
+    constructor(graphService, provider) {
+        this._graphService = graphService;
+        this._data = provider;
+        this._cells = {};
+        this._cacheRequests = {};
+        this._clusters = {};
+        this._clusterCells = {};
+        this._cellClusters = {};
+        this._cachingCells$ = {};
+        this._cachingClusters$ = {};
+    }
+    cacheClusters$(cellId) {
+        if (!this.hasCell(cellId)) {
+            throw new Error("Cannot cache reconstructions of a non-existing cell.");
+        }
+        if (this.hasClusters(cellId)) {
+            throw new Error("Cannot cache reconstructions that already exists.");
+        }
+        if (this.isCachingClusters(cellId)) {
+            return this._cachingClusters$[cellId];
+        }
+        const duplicatedClusters = this.getCell(cellId)
+            .filter((n) => {
+            return !!n.clusterId && !!n.clusterUrl;
+        })
+            .map((n) => {
+            return { key: n.clusterId, url: n.clusterUrl };
+        });
+        const clusters = Array
+            .from(new Map(duplicatedClusters.map((cd) => {
+            return [cd.key, cd];
+        }))
+            .values());
+        this._cellClusters[cellId] = clusters;
+        this._cacheRequests[cellId] = [];
+        let aborter;
+        const abort = new Promise((_, reject) => {
+            aborter = reject;
+        });
+        this._cacheRequests[cellId].push(aborter);
+        this._cachingClusters$[cellId] =
+            this._cacheClusters$(clusters, cellId, abort).pipe(finalize(() => {
+                if (cellId in this._cachingClusters$) {
+                    delete this._cachingClusters$[cellId];
+                }
+                if (cellId in this._cacheRequests) {
+                    delete this._cacheRequests[cellId];
+                }
+            }), publish(), refCount());
+        return this._cachingClusters$[cellId];
+    }
+    cacheCell$(cellId) {
+        if (this.hasCell(cellId)) {
+            throw new Error("Cannot cache cell that already exists.");
+        }
+        if (this.isCachingCell(cellId)) {
+            return this._cachingCells$[cellId];
+        }
+        this._cachingCells$[cellId] = this._graphService.cacheCell$(cellId).pipe(catchError((error) => {
+            console.error(error);
+            return empty();
+        }), filter(() => {
+            return !(cellId in this._cells);
+        }), tap((images) => {
+            this._cells[cellId] = [];
+            this._cells[cellId].push(...images);
+            delete this._cachingCells$[cellId];
+        }), finalize(() => {
+            if (cellId in this._cachingCells$) {
+                delete this._cachingCells$[cellId];
+            }
+        }), publish(), refCount());
+        return this._cachingCells$[cellId];
+    }
+    isCachingClusters(cellId) {
+        return cellId in this._cachingClusters$;
+    }
+    isCachingCell(cellId) {
+        return cellId in this._cachingCells$;
+    }
+    hasClusters(cellId) {
+        if (cellId in this._cachingClusters$ ||
+            !(cellId in this._cellClusters)) {
+            return false;
+        }
+        for (const cd of this._cellClusters[cellId]) {
+            if (!(cd.key in this._clusters)) {
+                return false;
+            }
+        }
+        return true;
+    }
+    hasCell(cellId) {
+        return !(cellId in this._cachingCells$) && cellId in this._cells;
+    }
+    getClusters(cellId) {
+        return cellId in this._cellClusters ?
+            this._cellClusters[cellId]
+                .map((cd) => {
+                return this._clusters[cd.key];
+            })
+                .filter((reconstruction) => {
+                return !!reconstruction;
+            }) :
+            [];
+    }
+    getCell(cellId) {
+        return cellId in this._cells ? this._cells[cellId] : [];
+    }
+    uncache(keepCellIds) {
+        for (let cellId of Object.keys(this._cacheRequests)) {
+            if (!!keepCellIds && keepCellIds.indexOf(cellId) !== -1) {
+                continue;
+            }
+            for (const aborter of this._cacheRequests[cellId]) {
+                aborter();
+            }
+            delete this._cacheRequests[cellId];
+        }
+        for (let cellId of Object.keys(this._cellClusters)) {
+            if (!!keepCellIds && keepCellIds.indexOf(cellId) !== -1) {
+                continue;
+            }
+            for (const cd of this._cellClusters[cellId]) {
+                if (!(cd.key in this._clusterCells)) {
+                    continue;
+                }
+                const index = this._clusterCells[cd.key].indexOf(cellId);
+                if (index === -1) {
+                    continue;
+                }
+                this._clusterCells[cd.key].splice(index, 1);
+                if (this._clusterCells[cd.key].length > 0) {
+                    continue;
+                }
+                delete this._clusterCells[cd.key];
+                delete this._clusters[cd.key];
+            }
+            delete this._cellClusters[cellId];
+        }
+        for (let cellId of Object.keys(this._cells)) {
+            if (!!keepCellIds && keepCellIds.indexOf(cellId) !== -1) {
+                continue;
+            }
+            delete this._cells[cellId];
+        }
+    }
+    updateCell$(cellId) {
+        if (!this.hasCell(cellId)) {
+            throw new Error("Cannot update cell that does not exists.");
+        }
+        return this._graphService.cacheCell$(cellId).pipe(catchError((error) => {
+            console.error(error);
+            return empty();
+        }), filter(() => {
+            return cellId in this._cells;
+        }), tap((images) => {
+            this._cells[cellId] = [];
+            this._cells[cellId].push(...images);
+        }), publish(), refCount());
+    }
+    updateClusters$(cellId) {
+        if (!this.hasCell(cellId)) {
+            throw new Error("Cannot update reconstructions of a non-existing cell.");
+        }
+        if (!this.hasClusters(cellId)) {
+            throw new Error("Cannot update reconstructions for cell that is not cached.");
+        }
+        const duplicatedClusters = this.getCell(cellId)
+            .filter((n) => {
+            return !!n.clusterId && !!n.clusterUrl;
+        })
+            .map((n) => {
+            return { key: n.clusterId, url: n.clusterUrl };
+        });
+        const clusters = Array
+            .from(new Map(duplicatedClusters.map((cd) => {
+            return [cd.key, cd];
+        }))
+            .values())
+            .filter(cd => {
+            return !(cd.key in this._clusters);
+        });
+        this._cellClusters[cellId].push(...clusters);
+        return this._cacheClusters$(clusters, cellId, null);
+    }
+    _cacheClusters$(clusters, cellId, cancellation) {
+        return from(clusters).pipe(mergeMap((cd) => {
+            if (this._hasCluster(cd.key)) {
+                return of(this._getCluster(cd.key));
+            }
+            return this._getCluster$(cd.url, cd.key, cancellation)
+                .pipe(catchError((error) => {
+                if (error instanceof CancelMapillaryError) {
+                    return empty();
+                }
+                console.error(error);
+                return empty();
+            }));
+        }, 6), filter(() => {
+            return cellId in this._cellClusters;
+        }), tap((reconstruction) => {
+            if (!this._hasCluster(reconstruction.id)) {
+                this._clusters[reconstruction.id] = reconstruction;
+            }
+            if (!(reconstruction.id in this._clusterCells)) {
+                this._clusterCells[reconstruction.id] = [];
+            }
+            if (this._clusterCells[reconstruction.id].indexOf(cellId) === -1) {
+                this._clusterCells[reconstruction.id].push(cellId);
+            }
+        }));
+    }
+    _getCluster(id) {
+        return this._clusters[id];
+    }
+    _getCluster$(url, clusterId, abort) {
+        return Observable.create((subscriber) => {
+            this._data.getCluster(url, abort)
+                .then((reconstruction) => {
+                reconstruction.id = clusterId;
+                subscriber.next(reconstruction);
+                subscriber.complete();
+            }, (error) => {
+                subscriber.error(error);
+            });
+        });
+    }
+    _hasCluster(id) {
+        return id in this._clusters;
+    }
+}
+
+function connectedComponent(cellId, depth, geometry) {
+    const cells = new Set();
+    cells.add(cellId);
+    connectedComponentRecursive(cells, [cellId], 0, depth, geometry);
+    return Array.from(cells);
+}
+function connectedComponentRecursive(cells, current, currentDepth, maxDepth, geometry) {
+    if (currentDepth >= maxDepth) {
+        return;
+    }
+    const adjacent = [];
+    for (const cellId of current) {
+        const aCells = geometry.getAdjacent(cellId);
+        adjacent.push(...aCells);
+    }
+    const newCells = [];
+    for (const a of adjacent) {
+        if (cells.has(a)) {
+            continue;
+        }
+        cells.add(a);
+        newCells.push(a);
+    }
+    connectedComponentRecursive(cells, newCells, currentDepth + 1, maxDepth, geometry);
+}
+
+class SpatialComponent extends Component {
+    /** @ignore */
+    constructor(name, container, navigator) {
+        super(name, container, navigator);
+        this._cache = new SpatialCache(navigator.graphService, navigator.api.data);
+        this._scene = new SpatialScene(this._getDefaultConfiguration());
+        this._viewportCoords = new ViewportCoords();
+        this._spatial = new Spatial();
+    }
+    /**
+     * Returns the image id of the camera frame closest to the current
+     * render camera position at the specified point.
+     *
+     * @description Notice that the pixelPoint argument requires x, y
+     * coordinates from pixel space.
+     *
+     * With this function, you can use the coordinates provided by mouse
+     * events to get information out of the spatial component.
+     *
+     * If no camera frame exist at the pixel
+     * point, `null` will be returned.
+     *
+     * @param {Array<number>} pixelPoint - Pixel coordinates on
+     * the viewer element.
+     * @returns {string} Image id of the camera frame closest to
+     * the camera. If no camera frame is intersected at the
+     * pixel point, `null` will be returned.
+     *
+     * @example
+     * ```js
+     * spatialComponent.getFrameIdAt([100, 125])
+     *     .then((imageId) => { console.log(imageId); });
+     * ```
+     */
+    getFrameIdAt(pixelPoint) {
+        return new Promise((resolve, reject) => {
+            this._container.renderService.renderCamera$.pipe(first(), map((render) => {
+                const viewport = this._viewportCoords
+                    .canvasToViewport(pixelPoint[0], pixelPoint[1], this._container.container);
+                const id = this._scene.intersection
+                    .intersectObjects(viewport, render.perspective);
+                return id;
+            }))
+                .subscribe((id) => {
+                resolve(id);
+            }, (error) => {
+                reject(error);
+            });
+        });
+    }
+    _activate() {
+        this._navigator.cacheService.configure({ cellDepth: 3 });
+        const subs = this._subscriptions;
+        subs.push(this._navigator.stateService.reference$
+            .subscribe(() => {
+            this._scene.uncache();
+        }));
+        subs.push(this._navigator.graphService.filter$
+            .subscribe(imageFilter => { this._scene.setFilter(imageFilter); }));
+        const bearing$ = this._container.renderService.bearing$.pipe(map((bearing) => {
+            const interval = 6;
+            const discrete = interval * Math.floor(bearing / interval);
+            return discrete;
+        }), distinctUntilChanged(), publishReplay(1), refCount());
+        const cellId$ = this._navigator.stateService.currentImage$
+            .pipe(map((image) => {
+            return this._navigator.api.data.geometry
+                .lngLatToCellId(image.originalLngLat);
+        }), distinctUntilChanged(), publishReplay(1), refCount());
+        const cellGridDepth$ = this._configuration$
+            .pipe(map((c) => {
+            return this._spatial.clamp(c.cellGridDepth, 1, 3);
+        }), distinctUntilChanged(), publishReplay(1), refCount());
+        const sequencePlay$ = combineLatest(this._navigator.playService.playing$, this._navigator.playService.speed$).pipe(map(([playing, speed]) => {
+            return playing && speed > PlayService.sequenceSpeed;
+        }), distinctUntilChanged(), publishReplay(1), refCount());
+        const isOverview$ = this._navigator.stateService.state$.pipe(map((state) => {
+            return isOverviewState(state);
+        }), distinctUntilChanged(), publishReplay(1), refCount());
+        subs.push(isOverview$.subscribe((isOverview) => {
+            this._scene.setNavigationState(isOverview);
+        }));
+        const cell$ = combineLatest(isOverview$, sequencePlay$, bearing$, cellGridDepth$, this._navigator.stateService.currentImage$)
+            .pipe(distinctUntilChanged(([o1, s1, b1, d1, i1], [o2, s2, b2, d2, i2]) => {
+            if (o1 !== o2) {
+                return false;
+            }
+            const isd = i1.id === i2.id && s1 === s2 && d1 === d2;
+            if (o1) {
+                return isd;
+            }
+            return isd && b1 === b2;
+        }), concatMap(([isOverview, sequencePlay, bearing, depth, image]) => {
+            if (isOverview) {
+                const geometry = this._navigator.api.data.geometry;
+                const cellId = geometry
+                    .lngLatToCellId(image.originalLngLat);
+                const cells = sequencePlay ?
+                    [cellId] :
+                    connectedComponent(cellId, depth, geometry);
+                return of(cells);
+            }
+            const fov = sequencePlay ? 30 : 90;
+            return of(this._cellsInFov(image, bearing, fov));
+        }), switchMap((cellIds) => {
+            return from(cellIds).pipe(mergeMap((cellId) => {
+                const t$ = this._cache.hasCell(cellId) ?
+                    of(this._cache.getCell(cellId)) :
+                    this._cache.cacheCell$(cellId);
+                return t$.pipe(map((images) => ({ id: cellId, images })));
+            }, 6));
+        }));
+        subs.push(cell$.pipe(withLatestFrom(this._navigator.stateService.reference$))
+            .subscribe(([cell, reference]) => {
+            if (this._scene.hasCell(cell.id)) {
+                return;
+            }
+            this._scene.addCell(this._cellToTopocentric(cell.id, reference), cell.id);
+        }));
+        subs.push(cell$.pipe(withLatestFrom(this._navigator.stateService.reference$))
+            .subscribe(([cell, reference]) => {
+            this._addSceneImages(cell, reference);
+        }));
+        subs.push(cell$.pipe(concatMap((cell) => {
+            const cellId = cell.id;
+            let reconstructions$;
+            if (this._cache.hasClusters(cellId)) {
+                reconstructions$ = from(this._cache.getClusters(cellId));
+            }
+            else if (this._cache.isCachingClusters(cellId)) {
+                reconstructions$ = this._cache.cacheClusters$(cellId).pipe(last(null, {}), switchMap(() => {
+                    return from(this._cache.getClusters(cellId));
+                }));
+            }
+            else if (this._cache.hasCell(cellId)) {
+                reconstructions$ = this._cache.cacheClusters$(cellId);
+            }
+            else {
+                reconstructions$ = empty();
+            }
+            return combineLatest(of(cellId), reconstructions$);
+        }), withLatestFrom(this._navigator.stateService.reference$))
+            .subscribe(([[cellId, reconstruction], reference]) => {
+            if (this._scene
+                .hasCluster(reconstruction.id, cellId)) {
+                return;
+            }
+            this._scene.addCluster(reconstruction, this._computeTranslation(reconstruction, reference), cellId);
+        }));
+        subs.push(this._configuration$.pipe(map((c) => {
+            c.cameraSize = this._spatial.clamp(c.cameraSize, 0.01, 1);
+            c.pointSize = this._spatial.clamp(c.pointSize, 0.01, 1);
+            return {
+                cameraSize: c.cameraSize,
+                cameraVisualizationMode: c.cameraVisualizationMode,
+                cellsVisible: c.cellsVisible,
+                originalPositionMode: c.originalPositionMode,
+                pointSize: c.pointSize,
+                pointsVisible: c.pointsVisible,
+            };
+        }), distinctUntilChanged((c1, c2) => {
+            return c1.cameraSize === c2.cameraSize &&
+                c1.cameraVisualizationMode === c2.cameraVisualizationMode &&
+                c1.cellsVisible === c2.cellsVisible &&
+                c1.originalPositionMode === c2.originalPositionMode &&
+                c1.pointSize === c2.pointSize &&
+                c1.pointsVisible === c2.pointsVisible;
+        }))
+            .subscribe((c) => {
+            this._scene.setCameraSize(c.cameraSize);
+            this._scene.setPointSize(c.pointSize);
+            this._scene.setPointVisibility(c.pointsVisible);
+            this._scene.setCellVisibility(c.cellsVisible);
+            const cvm = c.cameraVisualizationMode;
+            this._scene.setCameraVisualizationMode(cvm);
+            const opm = c.originalPositionMode;
+            this._scene.setPositionMode(opm);
+        }));
+        subs.push(combineLatest(cellId$, cellGridDepth$)
+            .subscribe(([cellId, depth]) => {
+            const keepCells = connectedComponent(cellId, depth, this._navigator.api.data.geometry);
+            this._scene.uncache(keepCells);
+            this._cache.uncache(keepCells);
+        }));
+        subs.push(this._navigator.playService.playing$.pipe(switchMap((playing) => {
+            return playing ?
+                empty() :
+                this._container.mouseService.dblClick$;
+        }), withLatestFrom(this._container.renderService.renderCamera$), switchMap(([event, render]) => {
+            const element = this._container.container;
+            const [canvasX, canvasY] = this._viewportCoords
+                .canvasPosition(event, element);
+            const viewport = this._viewportCoords.canvasToViewport(canvasX, canvasY, element);
+            const id = this._scene.intersection
+                .intersectObjects(viewport, render.perspective);
+            return !!id ?
+                this._navigator.moveTo$(id).pipe(catchError(() => {
+                    return empty();
+                })) :
+                empty();
+        }))
+            .subscribe());
+        const intersectChange$ = combineLatest(this._configuration$, this._navigator.stateService.state$).pipe(map(([c, state]) => {
+            c.cameraSize = this._spatial.clamp(c.cameraSize, 0.01, 1);
+            return {
+                size: c.cameraSize,
+                visible: isModeVisible(c.cameraVisualizationMode),
+                state,
+            };
+        }), distinctUntilChanged((c1, c2) => {
+            return c1.size === c2.size &&
+                c1.visible === c2.visible &&
+                c1.state === c2.state;
+        }));
+        const mouseMove$ = this._container.mouseService.mouseMove$.pipe(publishReplay(1), refCount());
+        subs.push(mouseMove$.subscribe());
+        const mouseHover$ = merge(this._container.mouseService.mouseEnter$, this._container.mouseService.mouseLeave$, this._container.mouseService.windowBlur$);
+        subs.push(combineLatest(this._navigator.playService.playing$, mouseHover$, isOverview$, this._navigator.graphService.filter$)
+            .pipe(switchMap(([playing, mouseHover]) => {
+            return !playing && mouseHover.type === "pointerenter" ?
+                combineLatest(concat(mouseMove$.pipe(take(1)), this._container.mouseService.mouseMove$), this._container.renderService.renderCamera$, intersectChange$) :
+                combineLatest(of(mouseHover), of(null), of(null));
+        }))
+            .subscribe(([event, render]) => {
+            if (event.type !== "pointermove") {
+                this._scene.setHoveredImage(null);
+                return;
+            }
+            const element = this._container.container;
+            const [canvasX, canvasY] = this._viewportCoords.canvasPosition(event, element);
+            const viewport = this._viewportCoords.canvasToViewport(canvasX, canvasY, element);
+            const key = this._scene.intersection
+                .intersectObjects(viewport, render.perspective);
+            this._scene.setHoveredImage(key);
+        }));
+        subs.push(this._navigator.stateService.currentId$
+            .subscribe((id) => {
+            this._scene.setSelectedImage(id);
+        }));
+        subs.push(this._navigator.stateService.currentState$
+            .pipe(map((frame) => {
+            const scene = this._scene;
+            return {
+                name: this._name,
+                renderer: {
+                    frameId: frame.id,
+                    needsRender: scene.needsRender,
+                    render: scene.render.bind(scene),
+                    pass: RenderPass$1.Opaque,
+                },
+            };
+        }))
+            .subscribe(this._container.glRenderer.render$));
+        const updatedCell$ = this._navigator.graphService.dataAdded$
+            .pipe(filter((cellId) => {
+            return this._cache.hasCell(cellId);
+        }), mergeMap((cellId) => {
+            return this._cache.updateCell$(cellId).pipe(map((images) => ({ id: cellId, images })), withLatestFrom(this._navigator.stateService.reference$));
+        }), publish(), refCount());
+        subs.push(updatedCell$
+            .subscribe(([cell, reference]) => {
+            this._addSceneImages(cell, reference);
+        }));
+        subs.push(updatedCell$
+            .pipe(concatMap(([cell]) => {
+            const cellId = cell.id;
+            const cache = this._cache;
+            let reconstructions$;
+            if (cache.hasClusters(cellId)) {
+                reconstructions$ =
+                    cache.updateClusters$(cellId);
+            }
+            else if (cache.isCachingClusters(cellId)) {
+                reconstructions$ = this._cache.cacheClusters$(cellId).pipe(last(null, {}), switchMap(() => {
+                    return from(cache.updateClusters$(cellId));
+                }));
+            }
+            else {
+                reconstructions$ = empty();
+            }
+            return combineLatest(of(cellId), reconstructions$);
+        }), withLatestFrom(this._navigator.stateService.reference$))
+            .subscribe(([[cellId, reconstruction], reference]) => {
+            if (this._scene.hasCluster(reconstruction.id, cellId)) {
+                return;
+            }
+            this._scene.addCluster(reconstruction, this._computeTranslation(reconstruction, reference), cellId);
+        }));
+    }
+    _deactivate() {
+        this._subscriptions.unsubscribe();
+        this._cache.uncache();
+        this._scene.deactivate();
+        this._navigator.cacheService.configure();
+    }
+    _getDefaultConfiguration() {
+        return {
+            cameraSize: 0.1,
+            cameraVisualizationMode: CameraVisualizationMode.Homogeneous,
+            cellGridDepth: 1,
+            originalPositionMode: OriginalPositionMode.Hidden,
+            pointSize: 0.1,
+            pointsVisible: true,
+            cellsVisible: false,
+        };
+    }
+    _addSceneImages(cell, reference) {
+        const cellId = cell.id;
+        const images = cell.images;
+        for (const image of images) {
+            if (this._scene.hasImage(image.id, cellId)) {
+                continue;
+            }
+            this._scene.addImage(image, this._createTransform(image, reference), this._computeOriginalPosition(image, reference), cellId);
+        }
+    }
+    _cellsInFov(image, bearing, fov) {
+        const spatial = this._spatial;
+        const geometry = this._navigator.api.data.geometry;
+        const cell = geometry.lngLatToCellId(image.originalLngLat);
+        const cells = [cell];
+        const threshold = fov / 2;
+        const adjacent = geometry.getAdjacent(cell);
+        for (const a of adjacent) {
+            const vertices = geometry.getVertices(a);
+            for (const vertex of vertices) {
+                const [x, y] = geodeticToEnu(vertex.lng, vertex.lat, 0, image.lngLat.lng, image.lngLat.lat, 0);
+                const azimuthal = Math.atan2(y, x);
+                const vertexBearing = spatial.radToDeg(spatial.azimuthalToBearing(azimuthal));
+                if (Math.abs(vertexBearing - bearing) < threshold) {
+                    cells.push(a);
+                }
+            }
+        }
+        return cells;
+    }
+    _computeOriginalPosition(image, reference) {
+        return geodeticToEnu(image.originalLngLat.lng, image.originalLngLat.lat, image.originalAltitude != null ? image.originalAltitude : image.computedAltitude, reference.lng, reference.lat, reference.alt);
+    }
+    _cellToTopocentric(cellId, reference) {
+        const vertices = this._navigator.api.data.geometry
+            .getVertices(cellId)
+            .map((vertex) => {
+            return geodeticToEnu(vertex.lng, vertex.lat, -2, reference.lng, reference.lat, reference.alt);
+        });
+        return vertices;
+    }
+    _computeTranslation(reconstruction, reference) {
+        return geodeticToEnu(reconstruction.reference.lng, reconstruction.reference.lat, reconstruction.reference.alt, reference.lng, reference.lat, reference.alt);
+    }
+    _createTransform(image, reference) {
+        const translation = computeTranslation({ alt: image.computedAltitude, lat: image.lngLat.lat, lng: image.lngLat.lng }, image.rotation, reference);
+        const transform = new Transform(image.exifOrientation, image.width, image.height, image.scale, image.rotation, translation, undefined, undefined, image.cameraParameters, image.cameraType);
+        return transform;
+    }
+}
+SpatialComponent.componentName = "spatial";
+
+/**
+ * @class Geometry
+ * @abstract
+ * @classdesc Represents a geometry.
+ */
+class Geometry {
+    /**
+     * Create a geometry.
+     *
+     * @constructor
+     * @ignore
+     */
+    constructor() {
+        this._notifyChanged$ = new Subject();
+    }
+    /**
+     * Get changed observable.
+     *
+     * @description Emits the geometry itself every time the geometry
+     * has changed.
+     *
+     * @returns {Observable<Geometry>} Observable emitting the geometry instance.
+     * @ignore
+     */
+    get changed$() {
+        return this._notifyChanged$;
+    }
+}
+
+class GeometryTagError extends MapillaryError {
+    constructor(message) {
+        super(message != null ? message : "The provided geometry value is incorrect");
+        Object.setPrototypeOf(this, GeometryTagError.prototype);
+        this.name = "GeometryTagError";
+    }
+}
+
+/**
+ * @class PointsGeometry
+ *
+ * @classdesc Represents a point set in the 2D basic image coordinate system.
+ *
+ * @example
+ * ```js
+ * var points = [[0.5, 0.3], [0.7, 0.3], [0.6, 0.5]];
+ * var pointsGeometry = new PointsGeometry(points);
+ * ```
+ */
+class PointsGeometry extends Geometry {
+    /**
+     * Create a points geometry.
+     *
+     * @constructor
+     * @param {Array<Array<number>>} points - Array of 2D points on the basic coordinate
+     * system. The number of points must be greater than or equal to two.
+     *
+     * @throws {GeometryTagError} Point coordinates must be valid basic coordinates.
+     */
+    constructor(points) {
+        super();
+        const pointsLength = points.length;
+        if (pointsLength < 2) {
+            throw new GeometryTagError("A points geometry must have two or more positions.");
+        }
+        this._points = [];
+        for (const point of points) {
+            if (point[0] < 0 || point[0] > 1 ||
+                point[1] < 0 || point[1] > 1) {
+                throw new GeometryTagError("Basic coordinates of points must be on the interval [0, 1].");
+            }
+            this._points.push(point.slice());
+        }
+    }
+    /**
+     * Get points property.
+     * @returns {Array<Array<number>>} Array of 2d points.
+     */
+    get points() {
+        return this._points;
+    }
+    /**
+     * Add a point to the point set.
+     *
+     * @param {Array<number>} point - Point to add.
+     * @ignore
+     */
+    addPoint2d(point) {
+        const clamped = [
+            Math.max(0, Math.min(1, point[0])),
+            Math.max(0, Math.min(1, point[1])),
+        ];
+        this._points.push(clamped);
+        this._notifyChanged$.next(this);
+    }
+    /**
+     * Get the coordinates of a point from the point set representation of the geometry.
+     *
+     * @param {number} index - Point index.
+     * @returns {Array<number>} Array representing the 2D basic coordinates of the point.
+     * @ignore
+     */
+    getPoint2d(index) {
+        return this._points[index].slice();
+    }
+    /**
+     * Remove a point from the point set.
+     *
+     * @param {number} index - The index of the point to remove.
+     * @ignore
+     */
+    removePoint2d(index) {
+        if (index < 0 ||
+            index >= this._points.length ||
+            this._points.length < 3) {
+            throw new GeometryTagError("Index for removed point must be valid.");
+        }
+        this._points.splice(index, 1);
+        this._notifyChanged$.next(this);
+    }
+    /** @ignore */
+    setVertex2d(index, value, transform) {
+        this.setPoint2d(index, value, transform);
+    }
+    /** @ignore */
+    setPoint2d(index, value, transform) {
+        const changed = [
+            Math.max(0, Math.min(1, value[0])),
+            Math.max(0, Math.min(1, value[1])),
+        ];
+        this._points[index] = changed;
+        this._notifyChanged$.next(this);
+    }
+    /** @ignore */
+    getPoints3d(transform) {
+        return this._getPoints3d(this._points, transform);
+    }
+    /** @ignore */
+    getPoint3d(index, transform) {
+        return transform.unprojectBasic(this._points[index], 200);
+    }
+    /** @ignore */
+    getPoints2d() {
+        return this._points.slice();
+    }
+    /** @ignore */
+    getCentroid2d(transform) {
+        if (!transform) {
+            throw new GeometryTagError("Get centroid must be called with a transform for points geometries.");
+        }
+        const [minX, minY, maxX, maxY] = this.getRect2d(transform);
+        const centroidX = minX < maxX ?
+            (minX + maxX) / 2 :
+            ((minX + maxX + 1) / 2) % 1;
+        const centroidY = (minY + maxY) / 2;
+        return [centroidX, centroidY];
+    }
+    /** @ignore */
+    getCentroid3d(transform) {
+        let centroid2d = this.getCentroid2d();
+        return transform.unprojectBasic(centroid2d, 200);
+    }
+    /** @ignore */
+    getRect2d(transform) {
+        let minX = 1;
+        let maxX = 0;
+        let minY = 1;
+        let maxY = 0;
+        const points = this._points;
+        for (const point of points) {
+            if (point[0] < minX) {
+                minX = point[0];
+            }
+            if (point[0] > maxX) {
+                maxX = point[0];
+            }
+            if (point[1] < minY) {
+                minY = point[1];
+            }
+            if (point[1] > maxY) {
+                maxY = point[1];
+            }
+        }
+        if (isSpherical(transform.cameraType)) {
+            const indices = [];
+            for (let i = 0; i < points.length; i++) {
+                indices[i] = i;
+            }
+            indices.sort((a, b) => {
+                return points[a][0] < points[b][0] ?
+                    -1 :
+                    points[a][0] > points[b][0] ?
+                        1 :
+                        a < b ? -1 : 1;
+            });
+            let maxDistanceX = points[indices[0]][0] + 1 - points[indices[indices.length - 1]][0];
+            let leftMostIndex = 0;
+            for (let i = 0; i < indices.length - 1; i++) {
+                const index1 = indices[i];
+                const index2 = indices[i + 1];
+                const distanceX = points[index2][0] - points[index1][0];
+                if (distanceX > maxDistanceX) {
+                    maxDistanceX = distanceX;
+                    leftMostIndex = i + 1;
+                }
+            }
+            if (leftMostIndex > 0) {
+                minX = points[indices[leftMostIndex]][0];
+                maxX = points[indices[leftMostIndex - 1]][0];
+            }
+        }
+        return [minX, minY, maxX, maxY];
+    }
+    /** @ignore */
+    setCentroid2d(value, transform) {
+        throw new Error("Not implemented");
+    }
+    _getPoints3d(points2d, transform) {
+        return points2d
+            .map((point) => {
+            return transform.unprojectBasic(point, 200);
+        });
+    }
+}
+
+class CreateTag {
+    constructor(geometry, transform, viewportCoords) {
+        this._geometry = geometry;
+        this._transform = transform;
+        this._viewportCoords = !!viewportCoords ? viewportCoords : new ViewportCoords();
+        this._aborted$ = new Subject();
+        this._created$ = new Subject();
+        this._glObjectsChanged$ = new Subject();
+        this._geometryChangedSubscription = this._geometry.changed$
+            .subscribe(() => {
+            this._onGeometryChanged();
+            this._glObjectsChanged$.next(this);
+        });
+    }
+    get geometry() {
+        return this._geometry;
+    }
+    get glObjects() {
+        return this._glObjects;
+    }
+    get aborted$() {
+        return this._aborted$;
+    }
+    get created$() {
+        return this._created$;
+    }
+    get glObjectsChanged$() {
+        return this._glObjectsChanged$;
+    }
+    get geometryChanged$() {
+        return this._geometry.changed$.pipe(map(() => {
+            return this;
+        }));
+    }
+    dispose() {
+        this._geometryChangedSubscription.unsubscribe();
+    }
+    _canvasToTransform(canvas) {
+        const canvasX = Math.round(canvas[0]);
+        const canvasY = Math.round(canvas[1]);
+        const transform = `translate(-50%,-50%) translate(${canvasX}px,${canvasY}px)`;
+        return transform;
+    }
+    _colorToBackground(color) {
+        return "#" + ("000000" + color.toString(16)).substr(-6);
+    }
+    _createOutine(polygon3d, color) {
+        const positions = this._getLinePositions(polygon3d);
+        const geometry = new BufferGeometry();
+        geometry.setAttribute("position", new BufferAttribute(positions, 3));
+        const material = new LineBasicMaterial({
+            color: color,
+            linewidth: 1,
+        });
+        return new Line(geometry, material);
+    }
+    _disposeLine(line) {
+        if (line == null) {
+            return;
+        }
+        line.geometry.dispose();
+        line.material.dispose();
+    }
+    _getLinePositions(polygon3d) {
+        const length = polygon3d.length;
+        const positions = new Float32Array(length * 3);
+        for (let i = 0; i < length; ++i) {
+            const index = 3 * i;
+            const position = polygon3d[i];
+            positions[index] = position[0];
+            positions[index + 1] = position[1];
+            positions[index + 2] = position[2];
+        }
+        return positions;
+    }
+}
+
+var earcut_1 = earcut;
+var _default$2 = earcut;
+
+function earcut(data, holeIndices, dim) {
+
+    dim = dim || 2;
+
+    var hasHoles = holeIndices && holeIndices.length,
+        outerLen = hasHoles ? holeIndices[0] * dim : data.length,
+        outerNode = linkedList(data, 0, outerLen, dim, true),
+        triangles = [];
+
+    if (!outerNode || outerNode.next === outerNode.prev) return triangles;
+
+    var minX, minY, maxX, maxY, x, y, invSize;
+
+    if (hasHoles) outerNode = eliminateHoles(data, holeIndices, outerNode, dim);
+
+    // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox
+    if (data.length > 80 * dim) {
+        minX = maxX = data[0];
+        minY = maxY = data[1];
+
+        for (var i = dim; i < outerLen; i += dim) {
+            x = data[i];
+            y = data[i + 1];
+            if (x < minX) minX = x;
+            if (y < minY) minY = y;
+            if (x > maxX) maxX = x;
+            if (y > maxY) maxY = y;
+        }
+
+        // minX, minY and invSize are later used to transform coords into integers for z-order calculation
+        invSize = Math.max(maxX - minX, maxY - minY);
+        invSize = invSize !== 0 ? 1 / invSize : 0;
+    }
+
+    earcutLinked(outerNode, triangles, dim, minX, minY, invSize);
+
+    return triangles;
+}
+
+// create a circular doubly linked list from polygon points in the specified winding order
+function linkedList(data, start, end, dim, clockwise) {
+    var i, last;
+
+    if (clockwise === (signedArea$1(data, start, end, dim) > 0)) {
+        for (i = start; i < end; i += dim) last = insertNode(i, data[i], data[i + 1], last);
+    } else {
+        for (i = end - dim; i >= start; i -= dim) last = insertNode(i, data[i], data[i + 1], last);
+    }
+
+    if (last && equals$1(last, last.next)) {
+        removeNode(last);
+        last = last.next;
+    }
+
+    return last;
+}
+
+// eliminate colinear or duplicate points
+function filterPoints(start, end) {
+    if (!start) return start;
+    if (!end) end = start;
+
+    var p = start,
+        again;
+    do {
+        again = false;
+
+        if (!p.steiner && (equals$1(p, p.next) || area(p.prev, p, p.next) === 0)) {
+            removeNode(p);
+            p = end = p.prev;
+            if (p === p.next) break;
+            again = true;
+
+        } else {
+            p = p.next;
+        }
+    } while (again || p !== end);
+
+    return end;
+}
+
+// main ear slicing loop which triangulates a polygon (given as a linked list)
+function earcutLinked(ear, triangles, dim, minX, minY, invSize, pass) {
+    if (!ear) return;
+
+    // interlink polygon nodes in z-order
+    if (!pass && invSize) indexCurve(ear, minX, minY, invSize);
+
+    var stop = ear,
+        prev, next;
+
+    // iterate through ears, slicing them one by one
+    while (ear.prev !== ear.next) {
+        prev = ear.prev;
+        next = ear.next;
+
+        if (invSize ? isEarHashed(ear, minX, minY, invSize) : isEar(ear)) {
+            // cut off the triangle
+            triangles.push(prev.i / dim);
+            triangles.push(ear.i / dim);
+            triangles.push(next.i / dim);
+
+            removeNode(ear);
+
+            // skipping the next vertex leads to less sliver triangles
+            ear = next.next;
+            stop = next.next;
+
+            continue;
+        }
+
+        ear = next;
+
+        // if we looped through the whole remaining polygon and can't find any more ears
+        if (ear === stop) {
+            // try filtering points and slicing again
+            if (!pass) {
+                earcutLinked(filterPoints(ear), triangles, dim, minX, minY, invSize, 1);
+
+            // if this didn't work, try curing all small self-intersections locally
+            } else if (pass === 1) {
+                ear = cureLocalIntersections(filterPoints(ear), triangles, dim);
+                earcutLinked(ear, triangles, dim, minX, minY, invSize, 2);
+
+            // as a last resort, try splitting the remaining polygon into two
+            } else if (pass === 2) {
+                splitEarcut(ear, triangles, dim, minX, minY, invSize);
+            }
+
+            break;
+        }
+    }
+}
+
+// check whether a polygon node forms a valid ear with adjacent nodes
+function isEar(ear) {
+    var a = ear.prev,
+        b = ear,
+        c = ear.next;
+
+    if (area(a, b, c) >= 0) return false; // reflex, can't be an ear
+
+    // now make sure we don't have other points inside the potential ear
+    var p = ear.next.next;
+
+    while (p !== ear.prev) {
+        if (pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) &&
+            area(p.prev, p, p.next) >= 0) return false;
+        p = p.next;
+    }
+
+    return true;
+}
+
+function isEarHashed(ear, minX, minY, invSize) {
+    var a = ear.prev,
+        b = ear,
+        c = ear.next;
+
+    if (area(a, b, c) >= 0) return false; // reflex, can't be an ear
+
+    // triangle bbox; min & max are calculated like this for speed
+    var minTX = a.x < b.x ? (a.x < c.x ? a.x : c.x) : (b.x < c.x ? b.x : c.x),
+        minTY = a.y < b.y ? (a.y < c.y ? a.y : c.y) : (b.y < c.y ? b.y : c.y),
+        maxTX = a.x > b.x ? (a.x > c.x ? a.x : c.x) : (b.x > c.x ? b.x : c.x),
+        maxTY = a.y > b.y ? (a.y > c.y ? a.y : c.y) : (b.y > c.y ? b.y : c.y);
+
+    // z-order range for the current triangle bbox;
+    var minZ = zOrder(minTX, minTY, minX, minY, invSize),
+        maxZ = zOrder(maxTX, maxTY, minX, minY, invSize);
+
+    var p = ear.prevZ,
+        n = ear.nextZ;
+
+    // look for points inside the triangle in both directions
+    while (p && p.z >= minZ && n && n.z <= maxZ) {
+        if (p !== ear.prev && p !== ear.next &&
+            pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) &&
+            area(p.prev, p, p.next) >= 0) return false;
+        p = p.prevZ;
+
+        if (n !== ear.prev && n !== ear.next &&
+            pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, n.x, n.y) &&
+            area(n.prev, n, n.next) >= 0) return false;
+        n = n.nextZ;
+    }
+
+    // look for remaining points in decreasing z-order
+    while (p && p.z >= minZ) {
+        if (p !== ear.prev && p !== ear.next &&
+            pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) &&
+            area(p.prev, p, p.next) >= 0) return false;
+        p = p.prevZ;
+    }
+
+    // look for remaining points in increasing z-order
+    while (n && n.z <= maxZ) {
+        if (n !== ear.prev && n !== ear.next &&
+            pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, n.x, n.y) &&
+            area(n.prev, n, n.next) >= 0) return false;
+        n = n.nextZ;
+    }
+
+    return true;
+}
+
+// go through all polygon nodes and cure small local self-intersections
+function cureLocalIntersections(start, triangles, dim) {
+    var p = start;
+    do {
+        var a = p.prev,
+            b = p.next.next;
+
+        if (!equals$1(a, b) && intersects(a, p, p.next, b) && locallyInside(a, b) && locallyInside(b, a)) {
+
+            triangles.push(a.i / dim);
+            triangles.push(p.i / dim);
+            triangles.push(b.i / dim);
+
+            // remove two nodes involved
+            removeNode(p);
+            removeNode(p.next);
+
+            p = start = b;
+        }
+        p = p.next;
+    } while (p !== start);
+
+    return filterPoints(p);
+}
+
+// try splitting polygon into two and triangulate them independently
+function splitEarcut(start, triangles, dim, minX, minY, invSize) {
+    // look for a valid diagonal that divides the polygon into two
+    var a = start;
+    do {
+        var b = a.next.next;
+        while (b !== a.prev) {
+            if (a.i !== b.i && isValidDiagonal(a, b)) {
+                // split the polygon in two by the diagonal
+                var c = splitPolygon(a, b);
+
+                // filter colinear points around the cuts
+                a = filterPoints(a, a.next);
+                c = filterPoints(c, c.next);
+
+                // run earcut on each half
+                earcutLinked(a, triangles, dim, minX, minY, invSize);
+                earcutLinked(c, triangles, dim, minX, minY, invSize);
+                return;
+            }
+            b = b.next;
+        }
+        a = a.next;
+    } while (a !== start);
+}
+
+// link every hole into the outer loop, producing a single-ring polygon without holes
+function eliminateHoles(data, holeIndices, outerNode, dim) {
+    var queue = [],
+        i, len, start, end, list;
+
+    for (i = 0, len = holeIndices.length; i < len; i++) {
+        start = holeIndices[i] * dim;
+        end = i < len - 1 ? holeIndices[i + 1] * dim : data.length;
+        list = linkedList(data, start, end, dim, false);
+        if (list === list.next) list.steiner = true;
+        queue.push(getLeftmost(list));
+    }
+
+    queue.sort(compareX);
+
+    // process holes from left to right
+    for (i = 0; i < queue.length; i++) {
+        eliminateHole(queue[i], outerNode);
+        outerNode = filterPoints(outerNode, outerNode.next);
+    }
+
+    return outerNode;
+}
+
+function compareX(a, b) {
+    return a.x - b.x;
+}
+
+// find a bridge between vertices that connects hole with an outer ring and and link it
+function eliminateHole(hole, outerNode) {
+    outerNode = findHoleBridge(hole, outerNode);
+    if (outerNode) {
+        var b = splitPolygon(outerNode, hole);
+
+        // filter collinear points around the cuts
+        filterPoints(outerNode, outerNode.next);
+        filterPoints(b, b.next);
+    }
+}
+
+// David Eberly's algorithm for finding a bridge between hole and outer polygon
+function findHoleBridge(hole, outerNode) {
+    var p = outerNode,
+        hx = hole.x,
+        hy = hole.y,
+        qx = -Infinity,
+        m;
+
+    // find a segment intersected by a ray from the hole's leftmost point to the left;
+    // segment's endpoint with lesser x will be potential connection point
+    do {
+        if (hy <= p.y && hy >= p.next.y && p.next.y !== p.y) {
+            var x = p.x + (hy - p.y) * (p.next.x - p.x) / (p.next.y - p.y);
+            if (x <= hx && x > qx) {
+                qx = x;
+                if (x === hx) {
+                    if (hy === p.y) return p;
+                    if (hy === p.next.y) return p.next;
+                }
+                m = p.x < p.next.x ? p : p.next;
+            }
+        }
+        p = p.next;
+    } while (p !== outerNode);
+
+    if (!m) return null;
+
+    if (hx === qx) return m; // hole touches outer segment; pick leftmost endpoint
+
+    // look for points inside the triangle of hole point, segment intersection and endpoint;
+    // if there are no points found, we have a valid connection;
+    // otherwise choose the point of the minimum angle with the ray as connection point
+
+    var stop = m,
+        mx = m.x,
+        my = m.y,
+        tanMin = Infinity,
+        tan;
+
+    p = m;
+
+    do {
+        if (hx >= p.x && p.x >= mx && hx !== p.x &&
+                pointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y)) {
+
+            tan = Math.abs(hy - p.y) / (hx - p.x); // tangential
+
+            if (locallyInside(p, hole) &&
+                (tan < tanMin || (tan === tanMin && (p.x > m.x || (p.x === m.x && sectorContainsSector(m, p)))))) {
+                m = p;
+                tanMin = tan;
+            }
+        }
+
+        p = p.next;
+    } while (p !== stop);
+
+    return m;
+}
+
+// whether sector in vertex m contains sector in vertex p in the same coordinates
+function sectorContainsSector(m, p) {
+    return area(m.prev, m, p.prev) < 0 && area(p.next, m, m.next) < 0;
+}
+
+// interlink polygon nodes in z-order
+function indexCurve(start, minX, minY, invSize) {
+    var p = start;
+    do {
+        if (p.z === null) p.z = zOrder(p.x, p.y, minX, minY, invSize);
+        p.prevZ = p.prev;
+        p.nextZ = p.next;
+        p = p.next;
+    } while (p !== start);
+
+    p.prevZ.nextZ = null;
+    p.prevZ = null;
+
+    sortLinked(p);
+}
+
+// Simon Tatham's linked list merge sort algorithm
+// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html
+function sortLinked(list) {
+    var i, p, q, e, tail, numMerges, pSize, qSize,
+        inSize = 1;
+
+    do {
+        p = list;
+        list = null;
+        tail = null;
+        numMerges = 0;
+
+        while (p) {
+            numMerges++;
+            q = p;
+            pSize = 0;
+            for (i = 0; i < inSize; i++) {
+                pSize++;
+                q = q.nextZ;
+                if (!q) break;
+            }
+            qSize = inSize;
+
+            while (pSize > 0 || (qSize > 0 && q)) {
+
+                if (pSize !== 0 && (qSize === 0 || !q || p.z <= q.z)) {
+                    e = p;
+                    p = p.nextZ;
+                    pSize--;
+                } else {
+                    e = q;
+                    q = q.nextZ;
+                    qSize--;
+                }
+
+                if (tail) tail.nextZ = e;
+                else list = e;
+
+                e.prevZ = tail;
+                tail = e;
+            }
+
+            p = q;
+        }
+
+        tail.nextZ = null;
+        inSize *= 2;
+
+    } while (numMerges > 1);
+
+    return list;
+}
+
+// z-order of a point given coords and inverse of the longer side of data bbox
+function zOrder(x, y, minX, minY, invSize) {
+    // coords are transformed into non-negative 15-bit integer range
+    x = 32767 * (x - minX) * invSize;
+    y = 32767 * (y - minY) * invSize;
+
+    x = (x | (x << 8)) & 0x00FF00FF;
+    x = (x | (x << 4)) & 0x0F0F0F0F;
+    x = (x | (x << 2)) & 0x33333333;
+    x = (x | (x << 1)) & 0x55555555;
+
+    y = (y | (y << 8)) & 0x00FF00FF;
+    y = (y | (y << 4)) & 0x0F0F0F0F;
+    y = (y | (y << 2)) & 0x33333333;
+    y = (y | (y << 1)) & 0x55555555;
+
+    return x | (y << 1);
+}
+
+// find the leftmost node of a polygon ring
+function getLeftmost(start) {
+    var p = start,
+        leftmost = start;
+    do {
+        if (p.x < leftmost.x || (p.x === leftmost.x && p.y < leftmost.y)) leftmost = p;
+        p = p.next;
+    } while (p !== start);
+
+    return leftmost;
+}
+
+// check if a point lies within a convex triangle
+function pointInTriangle(ax, ay, bx, by, cx, cy, px, py) {
+    return (cx - px) * (ay - py) - (ax - px) * (cy - py) >= 0 &&
+           (ax - px) * (by - py) - (bx - px) * (ay - py) >= 0 &&
+           (bx - px) * (cy - py) - (cx - px) * (by - py) >= 0;
+}
+
+// check if a diagonal between two polygon nodes is valid (lies in polygon interior)
+function isValidDiagonal(a, b) {
+    return a.next.i !== b.i && a.prev.i !== b.i && !intersectsPolygon(a, b) && // dones't intersect other edges
+           (locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b) && // locally visible
+            (area(a.prev, a, b.prev) || area(a, b.prev, b)) || // does not create opposite-facing sectors
+            equals$1(a, b) && area(a.prev, a, a.next) > 0 && area(b.prev, b, b.next) > 0); // special zero-length case
+}
+
+// signed area of a triangle
+function area(p, q, r) {
+    return (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y);
+}
+
+// check if two points are equal
+function equals$1(p1, p2) {
+    return p1.x === p2.x && p1.y === p2.y;
+}
+
+// check if two segments intersect
+function intersects(p1, q1, p2, q2) {
+    var o1 = sign(area(p1, q1, p2));
+    var o2 = sign(area(p1, q1, q2));
+    var o3 = sign(area(p2, q2, p1));
+    var o4 = sign(area(p2, q2, q1));
+
+    if (o1 !== o2 && o3 !== o4) return true; // general case
+
+    if (o1 === 0 && onSegment(p1, p2, q1)) return true; // p1, q1 and p2 are collinear and p2 lies on p1q1
+    if (o2 === 0 && onSegment(p1, q2, q1)) return true; // p1, q1 and q2 are collinear and q2 lies on p1q1
+    if (o3 === 0 && onSegment(p2, p1, q2)) return true; // p2, q2 and p1 are collinear and p1 lies on p2q2
+    if (o4 === 0 && onSegment(p2, q1, q2)) return true; // p2, q2 and q1 are collinear and q1 lies on p2q2
+
+    return false;
+}
+
+// for collinear points p, q, r, check if point q lies on segment pr
+function onSegment(p, q, r) {
+    return q.x <= Math.max(p.x, r.x) && q.x >= Math.min(p.x, r.x) && q.y <= Math.max(p.y, r.y) && q.y >= Math.min(p.y, r.y);
+}
+
+function sign(num) {
+    return num > 0 ? 1 : num < 0 ? -1 : 0;
+}
+
+// check if a polygon diagonal intersects any polygon segments
+function intersectsPolygon(a, b) {
+    var p = a;
+    do {
+        if (p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i &&
+                intersects(p, p.next, a, b)) return true;
+        p = p.next;
+    } while (p !== a);
+
+    return false;
+}
+
+// check if a polygon diagonal is locally inside the polygon
+function locallyInside(a, b) {
+    return area(a.prev, a, a.next) < 0 ?
+        area(a, b, a.next) >= 0 && area(a, a.prev, b) >= 0 :
+        area(a, b, a.prev) < 0 || area(a, a.next, b) < 0;
+}
+
+// check if the middle point of a polygon diagonal is inside the polygon
+function middleInside(a, b) {
+    var p = a,
+        inside = false,
+        px = (a.x + b.x) / 2,
+        py = (a.y + b.y) / 2;
+    do {
+        if (((p.y > py) !== (p.next.y > py)) && p.next.y !== p.y &&
+                (px < (p.next.x - p.x) * (py - p.y) / (p.next.y - p.y) + p.x))
+            inside = !inside;
+        p = p.next;
+    } while (p !== a);
+
+    return inside;
+}
+
+// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two;
+// if one belongs to the outer ring and another to a hole, it merges it into a single ring
+function splitPolygon(a, b) {
+    var a2 = new Node(a.i, a.x, a.y),
+        b2 = new Node(b.i, b.x, b.y),
+        an = a.next,
+        bp = b.prev;
+
+    a.next = b;
+    b.prev = a;
+
+    a2.next = an;
+    an.prev = a2;
+
+    b2.next = a2;
+    a2.prev = b2;
+
+    bp.next = b2;
+    b2.prev = bp;
+
+    return b2;
+}
+
+// create a node and optionally link it with previous one (in a circular doubly linked list)
+function insertNode(i, x, y, last) {
+    var p = new Node(i, x, y);
+
+    if (!last) {
+        p.prev = p;
+        p.next = p;
+
+    } else {
+        p.next = last.next;
+        p.prev = last;
+        last.next.prev = p;
+        last.next = p;
+    }
+    return p;
+}
+
+function removeNode(p) {
+    p.next.prev = p.prev;
+    p.prev.next = p.next;
+
+    if (p.prevZ) p.prevZ.nextZ = p.nextZ;
+    if (p.nextZ) p.nextZ.prevZ = p.prevZ;
+}
+
+function Node(i, x, y) {
+    // vertex index in coordinates array
+    this.i = i;
+
+    // vertex coordinates
+    this.x = x;
+    this.y = y;
+
+    // previous and next vertex nodes in a polygon ring
+    this.prev = null;
+    this.next = null;
+
+    // z-order curve value
+    this.z = null;
+
+    // previous and next nodes in z-order
+    this.prevZ = null;
+    this.nextZ = null;
+
+    // indicates whether this is a steiner point
+    this.steiner = false;
+}
+
+// return a percentage difference between the polygon area and its triangulation area;
+// used to verify correctness of triangulation
+earcut.deviation = function (data, holeIndices, dim, triangles) {
+    var hasHoles = holeIndices && holeIndices.length;
+    var outerLen = hasHoles ? holeIndices[0] * dim : data.length;
+
+    var polygonArea = Math.abs(signedArea$1(data, 0, outerLen, dim));
+    if (hasHoles) {
+        for (var i = 0, len = holeIndices.length; i < len; i++) {
+            var start = holeIndices[i] * dim;
+            var end = i < len - 1 ? holeIndices[i + 1] * dim : data.length;
+            polygonArea -= Math.abs(signedArea$1(data, start, end, dim));
+        }
+    }
+
+    var trianglesArea = 0;
+    for (i = 0; i < triangles.length; i += 3) {
+        var a = triangles[i] * dim;
+        var b = triangles[i + 1] * dim;
+        var c = triangles[i + 2] * dim;
+        trianglesArea += Math.abs(
+            (data[a] - data[c]) * (data[b + 1] - data[a + 1]) -
+            (data[a] - data[b]) * (data[c + 1] - data[a + 1]));
+    }
+
+    return polygonArea === 0 && trianglesArea === 0 ? 0 :
+        Math.abs((trianglesArea - polygonArea) / polygonArea);
+};
+
+function signedArea$1(data, start, end, dim) {
+    var sum = 0;
+    for (var i = start, j = end - dim; i < end; i += dim) {
+        sum += (data[j] - data[i]) * (data[i + 1] + data[j + 1]);
+        j = i;
+    }
+    return sum;
+}
+
+// turn a polygon in a multi-dimensional array form (e.g. as in GeoJSON) into a form Earcut accepts
+earcut.flatten = function (data) {
+    var dim = data[0][0].length,
+        result = {vertices: [], holes: [], dimensions: dim},
+        holeIndex = 0;
+
+    for (var i = 0; i < data.length; i++) {
+        for (var j = 0; j < data[i].length; j++) {
+            for (var d = 0; d < dim; d++) result.vertices.push(data[i][j][d]);
+        }
+        if (i > 0) {
+            holeIndex += data[i - 1].length;
+            result.holes.push(holeIndex);
+        }
+    }
+    return result;
+};
+earcut_1.default = _default$2;
+
+class TinyQueue$1 {
+    constructor(data = [], compare = defaultCompare$1) {
+        this.data = data;
+        this.length = this.data.length;
+        this.compare = compare;
+
+        if (this.length > 0) {
+            for (let i = (this.length >> 1) - 1; i >= 0; i--) this._down(i);
+        }
+    }
+
+    push(item) {
+        this.data.push(item);
+        this.length++;
+        this._up(this.length - 1);
+    }
+
+    pop() {
+        if (this.length === 0) return undefined;
+
+        const top = this.data[0];
+        const bottom = this.data.pop();
+        this.length--;
+
+        if (this.length > 0) {
+            this.data[0] = bottom;
+            this._down(0);
+        }
+
+        return top;
+    }
+
+    peek() {
+        return this.data[0];
+    }
+
+    _up(pos) {
+        const {data, compare} = this;
+        const item = data[pos];
+
+        while (pos > 0) {
+            const parent = (pos - 1) >> 1;
+            const current = data[parent];
+            if (compare(item, current) >= 0) break;
+            data[pos] = current;
+            pos = parent;
+        }
+
+        data[pos] = item;
+    }
+
+    _down(pos) {
+        const {data, compare} = this;
+        const halfLength = this.length >> 1;
+        const item = data[pos];
+
+        while (pos < halfLength) {
+            let left = (pos << 1) + 1;
+            let best = data[left];
+            const right = left + 1;
+
+            if (right < this.length && compare(data[right], best) < 0) {
+                left = right;
+                best = data[right];
+            }
+            if (compare(best, item) >= 0) break;
+
+            data[pos] = best;
+            pos = left;
+        }
+
+        data[pos] = item;
+    }
+}
+
+function defaultCompare$1(a, b) {
+    return a < b ? -1 : a > b ? 1 : 0;
+}
+
+var tinyqueue$1 = /*#__PURE__*/Object.freeze({
+    __proto__: null,
+    'default': TinyQueue$1
+});
+
+var require$$0 = /*@__PURE__*/getAugmentedNamespace(tinyqueue$1);
+
+var Queue = require$$0;
+
+if (Queue.default) Queue = Queue.default; // temporary webpack fix
+
+var polylabel_1 = polylabel;
+var _default$1 = polylabel;
+
+function polylabel(polygon, precision, debug) {
+    precision = precision || 1.0;
+
+    // find the bounding box of the outer ring
+    var minX, minY, maxX, maxY;
+    for (var i = 0; i < polygon[0].length; i++) {
+        var p = polygon[0][i];
+        if (!i || p[0] < minX) minX = p[0];
+        if (!i || p[1] < minY) minY = p[1];
+        if (!i || p[0] > maxX) maxX = p[0];
+        if (!i || p[1] > maxY) maxY = p[1];
+    }
+
+    var width = maxX - minX;
+    var height = maxY - minY;
+    var cellSize = Math.min(width, height);
+    var h = cellSize / 2;
+
+    if (cellSize === 0) {
+        var degeneratePoleOfInaccessibility = [minX, minY];
+        degeneratePoleOfInaccessibility.distance = 0;
+        return degeneratePoleOfInaccessibility;
+    }
+
+    // a priority queue of cells in order of their "potential" (max distance to polygon)
+    var cellQueue = new Queue(undefined, compareMax);
+
+    // cover polygon with initial cells
+    for (var x = minX; x < maxX; x += cellSize) {
+        for (var y = minY; y < maxY; y += cellSize) {
+            cellQueue.push(new Cell(x + h, y + h, h, polygon));
+        }
+    }
+
+    // take centroid as the first best guess
+    var bestCell = getCentroidCell(polygon);
+
+    // special case for rectangular polygons
+    var bboxCell = new Cell(minX + width / 2, minY + height / 2, 0, polygon);
+    if (bboxCell.d > bestCell.d) bestCell = bboxCell;
+
+    var numProbes = cellQueue.length;
+
+    while (cellQueue.length) {
+        // pick the most promising cell from the queue
+        var cell = cellQueue.pop();
+
+        // update the best cell if we found a better one
+        if (cell.d > bestCell.d) {
+            bestCell = cell;
+            if (debug) console.log('found best %d after %d probes', Math.round(1e4 * cell.d) / 1e4, numProbes);
+        }
+
+        // do not drill down further if there's no chance of a better solution
+        if (cell.max - bestCell.d <= precision) continue;
+
+        // split the cell into four cells
+        h = cell.h / 2;
+        cellQueue.push(new Cell(cell.x - h, cell.y - h, h, polygon));
+        cellQueue.push(new Cell(cell.x + h, cell.y - h, h, polygon));
+        cellQueue.push(new Cell(cell.x - h, cell.y + h, h, polygon));
+        cellQueue.push(new Cell(cell.x + h, cell.y + h, h, polygon));
+        numProbes += 4;
+    }
+
+    if (debug) {
+        console.log('num probes: ' + numProbes);
+        console.log('best distance: ' + bestCell.d);
+    }
+
+    var poleOfInaccessibility = [bestCell.x, bestCell.y];
+    poleOfInaccessibility.distance = bestCell.d;
+    return poleOfInaccessibility;
+}
+
+function compareMax(a, b) {
+    return b.max - a.max;
+}
+
+function Cell(x, y, h, polygon) {
+    this.x = x; // cell center x
+    this.y = y; // cell center y
+    this.h = h; // half the cell size
+    this.d = pointToPolygonDist(x, y, polygon); // distance from cell center to polygon
+    this.max = this.d + this.h * Math.SQRT2; // max distance to polygon within a cell
+}
+
+// signed distance from point to polygon outline (negative if point is outside)
+function pointToPolygonDist(x, y, polygon) {
+    var inside = false;
+    var minDistSq = Infinity;
+
+    for (var k = 0; k < polygon.length; k++) {
+        var ring = polygon[k];
+
+        for (var i = 0, len = ring.length, j = len - 1; i < len; j = i++) {
+            var a = ring[i];
+            var b = ring[j];
+
+            if ((a[1] > y !== b[1] > y) &&
+                (x < (b[0] - a[0]) * (y - a[1]) / (b[1] - a[1]) + a[0])) inside = !inside;
+
+            minDistSq = Math.min(minDistSq, getSegDistSq(x, y, a, b));
+        }
+    }
+
+    return minDistSq === 0 ? 0 : (inside ? 1 : -1) * Math.sqrt(minDistSq);
+}
+
+// get polygon centroid
+function getCentroidCell(polygon) {
+    var area = 0;
+    var x = 0;
+    var y = 0;
+    var points = polygon[0];
+
+    for (var i = 0, len = points.length, j = len - 1; i < len; j = i++) {
+        var a = points[i];
+        var b = points[j];
+        var f = a[0] * b[1] - b[0] * a[1];
+        x += (a[0] + b[0]) * f;
+        y += (a[1] + b[1]) * f;
+        area += f * 3;
+    }
+    if (area === 0) return new Cell(points[0][0], points[0][1], 0, polygon);
+    return new Cell(x / area, y / area, 0, polygon);
+}
+
+// get squared distance from a point to a segment
+function getSegDistSq(px, py, a, b) {
+
+    var x = a[0];
+    var y = a[1];
+    var dx = b[0] - x;
+    var dy = b[1] - y;
+
+    if (dx !== 0 || dy !== 0) {
+
+        var t = ((px - x) * dx + (py - y) * dy) / (dx * dx + dy * dy);
+
+        if (t > 1) {
+            x = b[0];
+            y = b[1];
+
+        } else if (t > 0) {
+            x += dx * t;
+            y += dy * t;
+        }
+    }
+
+    dx = px - x;
+    dy = py - y;
+
+    return dx * dx + dy * dy;
+}
+polylabel_1.default = _default$1;
+
+function DEFAULT_COMPARE (a, b) { return a > b ? 1 : a < b ? -1 : 0; }
+
+class SplayTree {
+
+  constructor(compare = DEFAULT_COMPARE, noDuplicates = false) {
+    this._compare = compare;
+    this._root = null;
+    this._size = 0;
+    this._noDuplicates = !!noDuplicates;
+  }
+
+
+  rotateLeft(x) {
+    var y = x.right;
+    if (y) {
+      x.right = y.left;
+      if (y.left) y.left.parent = x;
+      y.parent = x.parent;
+    }
+
+    if (!x.parent)                this._root = y;
+    else if (x === x.parent.left) x.parent.left = y;
+    else                          x.parent.right = y;
+    if (y) y.left = x;
+    x.parent = y;
+  }
+
+
+  rotateRight(x) {
+    var y = x.left;
+    if (y) {
+      x.left = y.right;
+      if (y.right) y.right.parent = x;
+      y.parent = x.parent;
+    }
+
+    if (!x.parent)               this._root = y;
+    else if(x === x.parent.left) x.parent.left = y;
+    else                         x.parent.right = y;
+    if (y) y.right = x;
+    x.parent = y;
+  }
+
+
+  _splay(x) {
+    while (x.parent) {
+      var p = x.parent;
+      if (!p.parent) {
+        if (p.left === x) this.rotateRight(p);
+        else              this.rotateLeft(p);
+      } else if (p.left === x && p.parent.left === p) {
+        this.rotateRight(p.parent);
+        this.rotateRight(p);
+      } else if (p.right === x && p.parent.right === p) {
+        this.rotateLeft(p.parent);
+        this.rotateLeft(p);
+      } else if (p.left === x && p.parent.right === p) {
+        this.rotateRight(p);
+        this.rotateLeft(p);
+      } else {
+        this.rotateLeft(p);
+        this.rotateRight(p);
+      }
+    }
+  }
+
+
+  splay(x) {
+    var p, gp, ggp, l, r;
+
+    while (x.parent) {
+      p = x.parent;
+      gp = p.parent;
+
+      if (gp && gp.parent) {
+        ggp = gp.parent;
+        if (ggp.left === gp) ggp.left  = x;
+        else                 ggp.right = x;
+        x.parent = ggp;
+      } else {
+        x.parent = null;
+        this._root = x;
+      }
+
+      l = x.left; r = x.right;
+
+      if (x === p.left) { // left
+        if (gp) {
+          if (gp.left === p) {
+            /* zig-zig */
+            if (p.right) {
+              gp.left = p.right;
+              gp.left.parent = gp;
+            } else gp.left = null;
+
+            p.right   = gp;
+            gp.parent = p;
+          } else {
+            /* zig-zag */
+            if (l) {
+              gp.right = l;
+              l.parent = gp;
+            } else gp.right = null;
+
+            x.left    = gp;
+            gp.parent = x;
+          }
+        }
+        if (r) {
+          p.left = r;
+          r.parent = p;
+        } else p.left = null;
+
+        x.right  = p;
+        p.parent = x;
+      } else { // right
+        if (gp) {
+          if (gp.right === p) {
+            /* zig-zig */
+            if (p.left) {
+              gp.right = p.left;
+              gp.right.parent = gp;
+            } else gp.right = null;
+
+            p.left = gp;
+            gp.parent = p;
+          } else {
+            /* zig-zag */
+            if (r) {
+              gp.left = r;
+              r.parent = gp;
+            } else gp.left = null;
+
+            x.right   = gp;
+            gp.parent = x;
+          }
+        }
+        if (l) {
+          p.right = l;
+          l.parent = p;
+        } else p.right = null;
+
+        x.left   = p;
+        p.parent = x;
+      }
+    }
+  }
+
+
+  replace(u, v) {
+    if (!u.parent) this._root = v;
+    else if (u === u.parent.left) u.parent.left = v;
+    else u.parent.right = v;
+    if (v) v.parent = u.parent;
+  }
+
+
+  minNode(u = this._root) {
+    if (u) while (u.left) u = u.left;
+    return u;
+  }
+
+
+  maxNode(u = this._root) {
+    if (u) while (u.right) u = u.right;
+    return u;
+  }
+
+
+  insert(key, data) {
+    var z = this._root;
+    var p = null;
+    var comp = this._compare;
+    var cmp;
+
+    if (this._noDuplicates) {
+      while (z) {
+        p = z;
+        cmp = comp(z.key, key);
+        if (cmp === 0) return;
+        else if (comp(z.key, key) < 0) z = z.right;
+        else z = z.left;
+      }
+    } else {
+      while (z) {
+        p = z;
+        if (comp(z.key, key) < 0) z = z.right;
+        else z = z.left;
+      }
+    }
+
+    z = { key, data, left: null, right: null, parent: p };
+
+    if (!p)                          this._root = z;
+    else if (comp(p.key, z.key) < 0) p.right = z;
+    else                             p.left  = z;
+
+    this.splay(z);
+    this._size++;
+    return z;
+  }
+
+
+  find (key) {
+    var z    = this._root;
+    var comp = this._compare;
+    while (z) {
+      var cmp = comp(z.key, key);
+      if      (cmp < 0) z = z.right;
+      else if (cmp > 0) z = z.left;
+      else              return z;
+    }
+    return null;
+  }
+
+  /**
+   * Whether the tree contains a node with the given key
+   * @param  {Key} key
+   * @return {boolean} true/false
+   */
+  contains (key) {
+    var node       = this._root;
+    var comparator = this._compare;
+    while (node)  {
+      var cmp = comparator(key, node.key);
+      if      (cmp === 0) return true;
+      else if (cmp < 0)   node = node.left;
+      else                node = node.right;
+    }
+
+    return false;
+  }
+
+
+  remove (key) {
+    var z = this.find(key);
+
+    if (!z) return false;
+
+    this.splay(z);
+
+    if (!z.left) this.replace(z, z.right);
+    else if (!z.right) this.replace(z, z.left);
+    else {
+      var y = this.minNode(z.right);
+      if (y.parent !== z) {
+        this.replace(y, y.right);
+        y.right = z.right;
+        y.right.parent = y;
+      }
+      this.replace(z, y);
+      y.left = z.left;
+      y.left.parent = y;
+    }
+
+    this._size--;
+    return true;
+  }
+
+
+  removeNode(z) {
+    if (!z) return false;
+
+    this.splay(z);
+
+    if (!z.left) this.replace(z, z.right);
+    else if (!z.right) this.replace(z, z.left);
+    else {
+      var y = this.minNode(z.right);
+      if (y.parent !== z) {
+        this.replace(y, y.right);
+        y.right = z.right;
+        y.right.parent = y;
+      }
+      this.replace(z, y);
+      y.left = z.left;
+      y.left.parent = y;
+    }
+
+    this._size--;
+    return true;
+  }
+
+
+  erase (key) {
+    var z = this.find(key);
+    if (!z) return;
+
+    this.splay(z);
+
+    var s = z.left;
+    var t = z.right;
+
+    var sMax = null;
+    if (s) {
+      s.parent = null;
+      sMax = this.maxNode(s);
+      this.splay(sMax);
+      this._root = sMax;
+    }
+    if (t) {
+      if (s) sMax.right = t;
+      else   this._root = t;
+      t.parent = sMax;
+    }
+
+    this._size--;
+  }
+
+  /**
+   * Removes and returns the node with smallest key
+   * @return {?Node}
+   */
+  pop () {
+    var node = this._root, returnValue = null;
+    if (node) {
+      while (node.left) node = node.left;
+      returnValue = { key: node.key, data: node.data };
+      this.remove(node.key);
+    }
+    return returnValue;
+  }
+
+
+  /* eslint-disable class-methods-use-this */
+
+  /**
+   * Successor node
+   * @param  {Node} node
+   * @return {?Node}
+   */
+  next (node) {
+    var successor = node;
+    if (successor) {
+      if (successor.right) {
+        successor = successor.right;
+        while (successor && successor.left) successor = successor.left;
+      } else {
+        successor = node.parent;
+        while (successor && successor.right === node) {
+          node = successor; successor = successor.parent;
+        }
+      }
+    }
+    return successor;
+  }
+
+
+  /**
+   * Predecessor node
+   * @param  {Node} node
+   * @return {?Node}
+   */
+  prev (node) {
+    var predecessor = node;
+    if (predecessor) {
+      if (predecessor.left) {
+        predecessor = predecessor.left;
+        while (predecessor && predecessor.right) predecessor = predecessor.right;
+      } else {
+        predecessor = node.parent;
+        while (predecessor && predecessor.left === node) {
+          node = predecessor;
+          predecessor = predecessor.parent;
+        }
+      }
+    }
+    return predecessor;
+  }
+  /* eslint-enable class-methods-use-this */
+
+
+  /**
+   * @param  {forEachCallback} callback
+   * @return {SplayTree}
+   */
+  forEach(callback) {
+    var current = this._root;
+    var s = [], done = false, i = 0;
+
+    while (!done) {
+      // Reach the left most Node of the current Node
+      if (current) {
+        // Place pointer to a tree node on the stack
+        // before traversing the node's left subtree
+        s.push(current);
+        current = current.left;
+      } else {
+        // BackTrack from the empty subtree and visit the Node
+        // at the top of the stack; however, if the stack is
+        // empty you are done
+        if (s.length > 0) {
+          current = s.pop();
+          callback(current, i++);
+
+          // We have visited the node and its left
+          // subtree. Now, it's right subtree's turn
+          current = current.right;
+        } else done = true;
+      }
+    }
+    return this;
+  }
+
+
+  /**
+   * Walk key range from `low` to `high`. Stops if `fn` returns a value.
+   * @param  {Key}      low
+   * @param  {Key}      high
+   * @param  {Function} fn
+   * @param  {*?}       ctx
+   * @return {SplayTree}
+   */
+  range(low, high, fn, ctx) {
+    const Q = [];
+    const compare = this._compare;
+    let node = this._root, cmp;
+
+    while (Q.length !== 0 || node) {
+      if (node) {
+        Q.push(node);
+        node = node.left;
+      } else {
+        node = Q.pop();
+        cmp = compare(node.key, high);
+        if (cmp > 0) {
+          break;
+        } else if (compare(node.key, low) >= 0) {
+          if (fn.call(ctx, node)) return this; // stop if smth is returned
+        }
+        node = node.right;
+      }
+    }
+    return this;
+  }
+
+  /**
+   * Returns all keys in order
+   * @return {Array<Key>}
+   */
+  keys () {
+    var current = this._root;
+    var s = [], r = [], done = false;
+
+    while (!done) {
+      if (current) {
+        s.push(current);
+        current = current.left;
+      } else {
+        if (s.length > 0) {
+          current = s.pop();
+          r.push(current.key);
+          current = current.right;
+        } else done = true;
+      }
+    }
+    return r;
+  }
+
+
+  /**
+   * Returns `data` fields of all nodes in order.
+   * @return {Array<Value>}
+   */
+  values () {
+    var current = this._root;
+    var s = [], r = [], done = false;
+
+    while (!done) {
+      if (current) {
+        s.push(current);
+        current = current.left;
+      } else {
+        if (s.length > 0) {
+          current = s.pop();
+          r.push(current.data);
+          current = current.right;
+        } else done = true;
+      }
+    }
+    return r;
+  }
+
+
+  /**
+   * Returns node at given index
+   * @param  {number} index
+   * @return {?Node}
+   */
+  at (index) {
+    // removed after a consideration, more misleading than useful
+    // index = index % this.size;
+    // if (index < 0) index = this.size - index;
+
+    var current = this._root;
+    var s = [], done = false, i = 0;
+
+    while (!done) {
+      if (current) {
+        s.push(current);
+        current = current.left;
+      } else {
+        if (s.length > 0) {
+          current = s.pop();
+          if (i === index) return current;
+          i++;
+          current = current.right;
+        } else done = true;
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Bulk-load items. Both array have to be same size
+   * @param  {Array<Key>}    keys
+   * @param  {Array<Value>}  [values]
+   * @param  {Boolean}       [presort=false] Pre-sort keys and values, using
+   *                                         tree's comparator. Sorting is done
+   *                                         in-place
+   * @return {AVLTree}
+   */
+  load(keys = [], values = [], presort = false) {
+    if (this._size !== 0) throw new Error('bulk-load: tree is not empty');
+    const size = keys.length;
+    if (presort) sort(keys, values, 0, size - 1, this._compare);
+    this._root = loadRecursive(null, keys, values, 0, size);
+    this._size = size;
+    return this;
+  }
+
+
+  min() {
+    var node = this.minNode(this._root);
+    if (node) return node.key;
+    else      return null;
+  }
+
+
+  max() {
+    var node = this.maxNode(this._root);
+    if (node) return node.key;
+    else      return null;
+  }
+
+  isEmpty() { return this._root === null; }
+  get size() { return this._size; }
+
+
+  /**
+   * Create a tree and load it with items
+   * @param  {Array<Key>}          keys
+   * @param  {Array<Value>?}        [values]
+
+   * @param  {Function?}            [comparator]
+   * @param  {Boolean?}             [presort=false] Pre-sort keys and values, using
+   *                                               tree's comparator. Sorting is done
+   *                                               in-place
+   * @param  {Boolean?}             [noDuplicates=false]   Allow duplicates
+   * @return {SplayTree}
+   */
+  static createTree(keys, values, comparator, presort, noDuplicates) {
+    return new SplayTree(comparator, noDuplicates).load(keys, values, presort);
+  }
+}
+
+
+function loadRecursive (parent, keys, values, start, end) {
+  const size = end - start;
+  if (size > 0) {
+    const middle = start + Math.floor(size / 2);
+    const key    = keys[middle];
+    const data   = values[middle];
+    const node   = { key, data, parent };
+    node.left    = loadRecursive(node, keys, values, start, middle);
+    node.right   = loadRecursive(node, keys, values, middle + 1, end);
+    return node;
+  }
+  return null;
+}
+
+
+function sort(keys, values, left, right, compare) {
+  if (left >= right) return;
+
+  const pivot = keys[(left + right) >> 1];
+  let i = left - 1;
+  let j = right + 1;
+
+  while (true) {
+    do i++; while (compare(keys[i], pivot) < 0);
+    do j--; while (compare(keys[j], pivot) > 0);
+    if (i >= j) break;
+
+    let tmp = keys[i];
+    keys[i] = keys[j];
+    keys[j] = tmp;
+
+    tmp = values[i];
+    values[i] = values[j];
+    values[j] = tmp;
+  }
+
+  sort(keys, values,  left,     j, compare);
+  sort(keys, values, j + 1, right, compare);
+}
+
+const NORMAL               = 0;
+const NON_CONTRIBUTING     = 1;
+const SAME_TRANSITION      = 2;
+const DIFFERENT_TRANSITION = 3;
+
+const INTERSECTION = 0;
+const UNION        = 1;
+const DIFFERENCE   = 2;
+const XOR          = 3;
+
+/**
+ * @param  {SweepEvent} event
+ * @param  {SweepEvent} prev
+ * @param  {Operation} operation
+ */
+function computeFields (event, prev, operation) {
+  // compute inOut and otherInOut fields
+  if (prev === null) {
+    event.inOut      = false;
+    event.otherInOut = true;
+
+  // previous line segment in sweepline belongs to the same polygon
+  } else {
+    if (event.isSubject === prev.isSubject) {
+      event.inOut      = !prev.inOut;
+      event.otherInOut = prev.otherInOut;
+
+    // previous line segment in sweepline belongs to the clipping polygon
+    } else {
+      event.inOut      = !prev.otherInOut;
+      event.otherInOut = prev.isVertical() ? !prev.inOut : prev.inOut;
+    }
+
+    // compute prevInResult field
+    if (prev) {
+      event.prevInResult = (!inResult(prev, operation) || prev.isVertical())
+        ? prev.prevInResult : prev;
+    }
+  }
+
+  // check if the line segment belongs to the Boolean operation
+  let isInResult = inResult(event, operation);
+  if (isInResult) {
+    event.resultTransition = determineResultTransition(event, operation);
+  } else {
+    event.resultTransition = 0;
+  }
+}
+
+
+/* eslint-disable indent */
+function inResult(event, operation) {
+  switch (event.type) {
+    case NORMAL:
+      switch (operation) {
+        case INTERSECTION:
+          return !event.otherInOut;
+        case UNION:
+          return event.otherInOut;
+        case DIFFERENCE:
+          // return (event.isSubject && !event.otherInOut) ||
+          //         (!event.isSubject && event.otherInOut);
+          return (event.isSubject && event.otherInOut) ||
+                  (!event.isSubject && !event.otherInOut);
+        case XOR:
+          return true;
+      }
+      break;
+    case SAME_TRANSITION:
+      return operation === INTERSECTION || operation === UNION;
+    case DIFFERENT_TRANSITION:
+      return operation === DIFFERENCE;
+    case NON_CONTRIBUTING:
+      return false;
+  }
+  return false;
+}
+/* eslint-enable indent */
+
+
+function determineResultTransition(event, operation) {
+  let thisIn = !event.inOut;
+  let thatIn = !event.otherInOut;
+
+  let isIn;
+  switch (operation) {
+    case INTERSECTION:
+      isIn = thisIn && thatIn; break;
+    case UNION:
+      isIn = thisIn || thatIn; break;
+    case XOR:
+      isIn = thisIn ^ thatIn; break;
+    case DIFFERENCE:
+      if (event.isSubject) {
+        isIn = thisIn && !thatIn;
+      } else {
+        isIn = thatIn && !thisIn;
+      }
+      break;
+  }
+  return isIn ? +1 : -1;
+}
+
+class SweepEvent {
+
+
+  /**
+   * Sweepline event
+   *
+   * @class {SweepEvent}
+   * @param {Array.<Number>}  point
+   * @param {Boolean}         left
+   * @param {SweepEvent=}     otherEvent
+   * @param {Boolean}         isSubject
+   * @param {Number}          edgeType
+   */
+  constructor (point, left, otherEvent, isSubject, edgeType) {
+
+    /**
+     * Is left endpoint?
+     * @type {Boolean}
+     */
+    this.left = left;
+
+    /**
+     * @type {Array.<Number>}
+     */
+    this.point = point;
+
+    /**
+     * Other edge reference
+     * @type {SweepEvent}
+     */
+    this.otherEvent = otherEvent;
+
+    /**
+     * Belongs to source or clipping polygon
+     * @type {Boolean}
+     */
+    this.isSubject = isSubject;
+
+    /**
+     * Edge contribution type
+     * @type {Number}
+     */
+    this.type = edgeType || NORMAL;
+
+
+    /**
+     * In-out transition for the sweepline crossing polygon
+     * @type {Boolean}
+     */
+    this.inOut = false;
+
+
+    /**
+     * @type {Boolean}
+     */
+    this.otherInOut = false;
+
+    /**
+     * Previous event in result?
+     * @type {SweepEvent}
+     */
+    this.prevInResult = null;
+
+    /**
+     * Type of result transition (0 = not in result, +1 = out-in, -1, in-out)
+     * @type {Number}
+     */
+    this.resultTransition = 0;
+
+    // connection step
+
+    /**
+     * @type {Number}
+     */
+    this.otherPos = -1;
+
+    /**
+     * @type {Number}
+     */
+    this.outputContourId = -1;
+
+    this.isExteriorRing = true;   // TODO: Looks unused, remove?
+  }
+
+
+  /**
+   * @param  {Array.<Number>}  p
+   * @return {Boolean}
+   */
+  isBelow (p) {
+    const p0 = this.point, p1 = this.otherEvent.point;
+    return this.left
+      ? (p0[0] - p[0]) * (p1[1] - p[1]) - (p1[0] - p[0]) * (p0[1] - p[1]) > 0
+      // signedArea(this.point, this.otherEvent.point, p) > 0 :
+      : (p1[0] - p[0]) * (p0[1] - p[1]) - (p0[0] - p[0]) * (p1[1] - p[1]) > 0;
+      //signedArea(this.otherEvent.point, this.point, p) > 0;
+  }
+
+
+  /**
+   * @param  {Array.<Number>}  p
+   * @return {Boolean}
+   */
+  isAbove (p) {
+    return !this.isBelow(p);
+  }
+
+
+  /**
+   * @return {Boolean}
+   */
+  isVertical () {
+    return this.point[0] === this.otherEvent.point[0];
+  }
+
+
+  /**
+   * Does event belong to result?
+   * @return {Boolean}
+   */
+  get inResult() {
+    return this.resultTransition !== 0;
+  }
+
+
+  clone () {
+    const copy = new SweepEvent(
+      this.point, this.left, this.otherEvent, this.isSubject, this.type);
+
+    copy.contourId        = this.contourId;
+    copy.resultTransition = this.resultTransition;
+    copy.prevInResult     = this.prevInResult;
+    copy.isExteriorRing   = this.isExteriorRing;
+    copy.inOut            = this.inOut;
+    copy.otherInOut       = this.otherInOut;
+
+    return copy;
+  }
+}
+
+function equals(p1, p2) {
+  if (p1[0] === p2[0]) {
+    if (p1[1] === p2[1]) {
+      return true;
+    } else {
+      return false;
+    }
+  }
+  return false;
+}
+
+// const EPSILON = 1e-9;
+// const abs = Math.abs;
+// TODO https://github.com/w8r/martinez/issues/6#issuecomment-262847164
+// Precision problem.
+//
+// module.exports = function equals(p1, p2) {
+//   return abs(p1[0] - p2[0]) <= EPSILON && abs(p1[1] - p2[1]) <= EPSILON;
+// };
+
+const epsilon = 1.1102230246251565e-16;
+const splitter = 134217729;
+const resulterrbound = (3 + 8 * epsilon) * epsilon;
+
+// fast_expansion_sum_zeroelim routine from oritinal code
+function sum(elen, e, flen, f, h) {
+    let Q, Qnew, hh, bvirt;
+    let enow = e[0];
+    let fnow = f[0];
+    let eindex = 0;
+    let findex = 0;
+    if ((fnow > enow) === (fnow > -enow)) {
+        Q = enow;
+        enow = e[++eindex];
+    } else {
+        Q = fnow;
+        fnow = f[++findex];
+    }
+    let hindex = 0;
+    if (eindex < elen && findex < flen) {
+        if ((fnow > enow) === (fnow > -enow)) {
+            Qnew = enow + Q;
+            hh = Q - (Qnew - enow);
+            enow = e[++eindex];
+        } else {
+            Qnew = fnow + Q;
+            hh = Q - (Qnew - fnow);
+            fnow = f[++findex];
+        }
+        Q = Qnew;
+        if (hh !== 0) {
+            h[hindex++] = hh;
+        }
+        while (eindex < elen && findex < flen) {
+            if ((fnow > enow) === (fnow > -enow)) {
+                Qnew = Q + enow;
+                bvirt = Qnew - Q;
+                hh = Q - (Qnew - bvirt) + (enow - bvirt);
+                enow = e[++eindex];
+            } else {
+                Qnew = Q + fnow;
+                bvirt = Qnew - Q;
+                hh = Q - (Qnew - bvirt) + (fnow - bvirt);
+                fnow = f[++findex];
+            }
+            Q = Qnew;
+            if (hh !== 0) {
+                h[hindex++] = hh;
+            }
+        }
+    }
+    while (eindex < elen) {
+        Qnew = Q + enow;
+        bvirt = Qnew - Q;
+        hh = Q - (Qnew - bvirt) + (enow - bvirt);
+        enow = e[++eindex];
+        Q = Qnew;
+        if (hh !== 0) {
+            h[hindex++] = hh;
+        }
+    }
+    while (findex < flen) {
+        Qnew = Q + fnow;
+        bvirt = Qnew - Q;
+        hh = Q - (Qnew - bvirt) + (fnow - bvirt);
+        fnow = f[++findex];
+        Q = Qnew;
+        if (hh !== 0) {
+            h[hindex++] = hh;
+        }
+    }
+    if (Q !== 0 || hindex === 0) {
+        h[hindex++] = Q;
+    }
+    return hindex;
+}
+
+function estimate(elen, e) {
+    let Q = e[0];
+    for (let i = 1; i < elen; i++) Q += e[i];
+    return Q;
+}
+
+function vec(n) {
+    return new Float64Array(n);
+}
+
+const ccwerrboundA = (3 + 16 * epsilon) * epsilon;
+const ccwerrboundB = (2 + 12 * epsilon) * epsilon;
+const ccwerrboundC = (9 + 64 * epsilon) * epsilon * epsilon;
+
+const B = vec(4);
+const C1 = vec(8);
+const C2 = vec(12);
+const D = vec(16);
+const u = vec(4);
+
+function orient2dadapt(ax, ay, bx, by, cx, cy, detsum) {
+    let acxtail, acytail, bcxtail, bcytail;
+    let bvirt, c, ahi, alo, bhi, blo, _i, _j, _0, s1, s0, t1, t0, u3;
+
+    const acx = ax - cx;
+    const bcx = bx - cx;
+    const acy = ay - cy;
+    const bcy = by - cy;
+
+    s1 = acx * bcy;
+    c = splitter * acx;
+    ahi = c - (c - acx);
+    alo = acx - ahi;
+    c = splitter * bcy;
+    bhi = c - (c - bcy);
+    blo = bcy - bhi;
+    s0 = alo * blo - (s1 - ahi * bhi - alo * bhi - ahi * blo);
+    t1 = acy * bcx;
+    c = splitter * acy;
+    ahi = c - (c - acy);
+    alo = acy - ahi;
+    c = splitter * bcx;
+    bhi = c - (c - bcx);
+    blo = bcx - bhi;
+    t0 = alo * blo - (t1 - ahi * bhi - alo * bhi - ahi * blo);
+    _i = s0 - t0;
+    bvirt = s0 - _i;
+    B[0] = s0 - (_i + bvirt) + (bvirt - t0);
+    _j = s1 + _i;
+    bvirt = _j - s1;
+    _0 = s1 - (_j - bvirt) + (_i - bvirt);
+    _i = _0 - t1;
+    bvirt = _0 - _i;
+    B[1] = _0 - (_i + bvirt) + (bvirt - t1);
+    u3 = _j + _i;
+    bvirt = u3 - _j;
+    B[2] = _j - (u3 - bvirt) + (_i - bvirt);
+    B[3] = u3;
+
+    let det = estimate(4, B);
+    let errbound = ccwerrboundB * detsum;
+    if (det >= errbound || -det >= errbound) {
+        return det;
+    }
+
+    bvirt = ax - acx;
+    acxtail = ax - (acx + bvirt) + (bvirt - cx);
+    bvirt = bx - bcx;
+    bcxtail = bx - (bcx + bvirt) + (bvirt - cx);
+    bvirt = ay - acy;
+    acytail = ay - (acy + bvirt) + (bvirt - cy);
+    bvirt = by - bcy;
+    bcytail = by - (bcy + bvirt) + (bvirt - cy);
+
+    if (acxtail === 0 && acytail === 0 && bcxtail === 0 && bcytail === 0) {
+        return det;
+    }
+
+    errbound = ccwerrboundC * detsum + resulterrbound * Math.abs(det);
+    det += (acx * bcytail + bcy * acxtail) - (acy * bcxtail + bcx * acytail);
+    if (det >= errbound || -det >= errbound) return det;
+
+    s1 = acxtail * bcy;
+    c = splitter * acxtail;
+    ahi = c - (c - acxtail);
+    alo = acxtail - ahi;
+    c = splitter * bcy;
+    bhi = c - (c - bcy);
+    blo = bcy - bhi;
+    s0 = alo * blo - (s1 - ahi * bhi - alo * bhi - ahi * blo);
+    t1 = acytail * bcx;
+    c = splitter * acytail;
+    ahi = c - (c - acytail);
+    alo = acytail - ahi;
+    c = splitter * bcx;
+    bhi = c - (c - bcx);
+    blo = bcx - bhi;
+    t0 = alo * blo - (t1 - ahi * bhi - alo * bhi - ahi * blo);
+    _i = s0 - t0;
+    bvirt = s0 - _i;
+    u[0] = s0 - (_i + bvirt) + (bvirt - t0);
+    _j = s1 + _i;
+    bvirt = _j - s1;
+    _0 = s1 - (_j - bvirt) + (_i - bvirt);
+    _i = _0 - t1;
+    bvirt = _0 - _i;
+    u[1] = _0 - (_i + bvirt) + (bvirt - t1);
+    u3 = _j + _i;
+    bvirt = u3 - _j;
+    u[2] = _j - (u3 - bvirt) + (_i - bvirt);
+    u[3] = u3;
+    const C1len = sum(4, B, 4, u, C1);
+
+    s1 = acx * bcytail;
+    c = splitter * acx;
+    ahi = c - (c - acx);
+    alo = acx - ahi;
+    c = splitter * bcytail;
+    bhi = c - (c - bcytail);
+    blo = bcytail - bhi;
+    s0 = alo * blo - (s1 - ahi * bhi - alo * bhi - ahi * blo);
+    t1 = acy * bcxtail;
+    c = splitter * acy;
+    ahi = c - (c - acy);
+    alo = acy - ahi;
+    c = splitter * bcxtail;
+    bhi = c - (c - bcxtail);
+    blo = bcxtail - bhi;
+    t0 = alo * blo - (t1 - ahi * bhi - alo * bhi - ahi * blo);
+    _i = s0 - t0;
+    bvirt = s0 - _i;
+    u[0] = s0 - (_i + bvirt) + (bvirt - t0);
+    _j = s1 + _i;
+    bvirt = _j - s1;
+    _0 = s1 - (_j - bvirt) + (_i - bvirt);
+    _i = _0 - t1;
+    bvirt = _0 - _i;
+    u[1] = _0 - (_i + bvirt) + (bvirt - t1);
+    u3 = _j + _i;
+    bvirt = u3 - _j;
+    u[2] = _j - (u3 - bvirt) + (_i - bvirt);
+    u[3] = u3;
+    const C2len = sum(C1len, C1, 4, u, C2);
+
+    s1 = acxtail * bcytail;
+    c = splitter * acxtail;
+    ahi = c - (c - acxtail);
+    alo = acxtail - ahi;
+    c = splitter * bcytail;
+    bhi = c - (c - bcytail);
+    blo = bcytail - bhi;
+    s0 = alo * blo - (s1 - ahi * bhi - alo * bhi - ahi * blo);
+    t1 = acytail * bcxtail;
+    c = splitter * acytail;
+    ahi = c - (c - acytail);
+    alo = acytail - ahi;
+    c = splitter * bcxtail;
+    bhi = c - (c - bcxtail);
+    blo = bcxtail - bhi;
+    t0 = alo * blo - (t1 - ahi * bhi - alo * bhi - ahi * blo);
+    _i = s0 - t0;
+    bvirt = s0 - _i;
+    u[0] = s0 - (_i + bvirt) + (bvirt - t0);
+    _j = s1 + _i;
+    bvirt = _j - s1;
+    _0 = s1 - (_j - bvirt) + (_i - bvirt);
+    _i = _0 - t1;
+    bvirt = _0 - _i;
+    u[1] = _0 - (_i + bvirt) + (bvirt - t1);
+    u3 = _j + _i;
+    bvirt = u3 - _j;
+    u[2] = _j - (u3 - bvirt) + (_i - bvirt);
+    u[3] = u3;
+    const Dlen = sum(C2len, C2, 4, u, D);
+
+    return D[Dlen - 1];
+}
+
+function orient2d(ax, ay, bx, by, cx, cy) {
+    const detleft = (ay - cy) * (bx - cx);
+    const detright = (ax - cx) * (by - cy);
+    const det = detleft - detright;
+
+    if (detleft === 0 || detright === 0 || (detleft > 0) !== (detright > 0)) return det;
+
+    const detsum = Math.abs(detleft + detright);
+    if (Math.abs(det) >= ccwerrboundA * detsum) return det;
+
+    return -orient2dadapt(ax, ay, bx, by, cx, cy, detsum);
+}
+
+/**
+ * Signed area of the triangle (p0, p1, p2)
+ * @param  {Array.<Number>} p0
+ * @param  {Array.<Number>} p1
+ * @param  {Array.<Number>} p2
+ * @return {Number}
+ */
+function signedArea(p0, p1, p2) {
+  const res = orient2d(p0[0], p0[1], p1[0], p1[1], p2[0], p2[1]);
+  if (res > 0) return -1;
+  if (res < 0) return 1;
+  return 0;
+}
+
+/**
+ * @param  {SweepEvent} e1
+ * @param  {SweepEvent} e2
+ * @return {Number}
+ */
+function compareEvents(e1, e2) {
+  const p1 = e1.point;
+  const p2 = e2.point;
+
+  // Different x-coordinate
+  if (p1[0] > p2[0]) return 1;
+  if (p1[0] < p2[0]) return -1;
+
+  // Different points, but same x-coordinate
+  // Event with lower y-coordinate is processed first
+  if (p1[1] !== p2[1]) return p1[1] > p2[1] ? 1 : -1;
+
+  return specialCases(e1, e2, p1);
+}
+
+
+/* eslint-disable no-unused-vars */
+function specialCases(e1, e2, p1, p2) {
+  // Same coordinates, but one is a left endpoint and the other is
+  // a right endpoint. The right endpoint is processed first
+  if (e1.left !== e2.left)
+    return e1.left ? 1 : -1;
+
+  // const p2 = e1.otherEvent.point, p3 = e2.otherEvent.point;
+  // const sa = (p1[0] - p3[0]) * (p2[1] - p3[1]) - (p2[0] - p3[0]) * (p1[1] - p3[1])
+  // Same coordinates, both events
+  // are left endpoints or right endpoints.
+  // not collinear
+  if (signedArea(p1, e1.otherEvent.point, e2.otherEvent.point) !== 0) {
+    // the event associate to the bottom segment is processed first
+    return (!e1.isBelow(e2.otherEvent.point)) ? 1 : -1;
+  }
+
+  return (!e1.isSubject && e2.isSubject) ? 1 : -1;
+}
+/* eslint-enable no-unused-vars */
+
+/**
+ * @param  {SweepEvent} se
+ * @param  {Array.<Number>} p
+ * @param  {Queue} queue
+ * @return {Queue}
+ */
+function divideSegment(se, p, queue)  {
+  const r = new SweepEvent(p, false, se,            se.isSubject);
+  const l = new SweepEvent(p, true,  se.otherEvent, se.isSubject);
+
+  /* eslint-disable no-console */
+  if (equals(se.point, se.otherEvent.point)) {
+    console.warn('what is that, a collapsed segment?', se);
+  }
+  /* eslint-enable no-console */
+
+  r.contourId = l.contourId = se.contourId;
+
+  // avoid a rounding error. The left event would be processed after the right event
+  if (compareEvents(l, se.otherEvent) > 0) {
+    se.otherEvent.left = true;
+    l.left = false;
+  }
+
+  // avoid a rounding error. The left event would be processed after the right event
+  // if (compareEvents(se, r) > 0) {}
+
+  se.otherEvent.otherEvent = l;
+  se.otherEvent = r;
+
+  queue.push(l);
+  queue.push(r);
+
+  return queue;
+}
+
+//const EPS = 1e-9;
+
+/**
+ * Finds the magnitude of the cross product of two vectors (if we pretend
+ * they're in three dimensions)
+ *
+ * @param {Object} a First vector
+ * @param {Object} b Second vector
+ * @private
+ * @returns {Number} The magnitude of the cross product
+ */
+function crossProduct(a, b) {
+  return (a[0] * b[1]) - (a[1] * b[0]);
+}
+
+/**
+ * Finds the dot product of two vectors.
+ *
+ * @param {Object} a First vector
+ * @param {Object} b Second vector
+ * @private
+ * @returns {Number} The dot product
+ */
+function dotProduct(a, b) {
+  return (a[0] * b[0]) + (a[1] * b[1]);
+}
+
+/**
+ * Finds the intersection (if any) between two line segments a and b, given the
+ * line segments' end points a1, a2 and b1, b2.
+ *
+ * This algorithm is based on Schneider and Eberly.
+ * http://www.cimec.org.ar/~ncalvo/Schneider_Eberly.pdf
+ * Page 244.
+ *
+ * @param {Array.<Number>} a1 point of first line
+ * @param {Array.<Number>} a2 point of first line
+ * @param {Array.<Number>} b1 point of second line
+ * @param {Array.<Number>} b2 point of second line
+ * @param {Boolean=}       noEndpointTouch whether to skip single touchpoints
+ *                                         (meaning connected segments) as
+ *                                         intersections
+ * @returns {Array.<Array.<Number>>|Null} If the lines intersect, the point of
+ * intersection. If they overlap, the two end points of the overlapping segment.
+ * Otherwise, null.
+ */
+function intersection$1 (a1, a2, b1, b2, noEndpointTouch) {
+  // The algorithm expects our lines in the form P + sd, where P is a point,
+  // s is on the interval [0, 1], and d is a vector.
+  // We are passed two points. P can be the first point of each pair. The
+  // vector, then, could be thought of as the distance (in x and y components)
+  // from the first point to the second point.
+  // So first, let's make our vectors:
+  const va = [a2[0] - a1[0], a2[1] - a1[1]];
+  const vb = [b2[0] - b1[0], b2[1] - b1[1]];
+  // We also define a function to convert back to regular point form:
+
+  /* eslint-disable arrow-body-style */
+
+  function toPoint(p, s, d) {
+    return [
+      p[0] + s * d[0],
+      p[1] + s * d[1]
+    ];
+  }
+
+  /* eslint-enable arrow-body-style */
+
+  // The rest is pretty much a straight port of the algorithm.
+  const e = [b1[0] - a1[0], b1[1] - a1[1]];
+  let kross    = crossProduct(va, vb);
+  let sqrKross = kross * kross;
+  const sqrLenA  = dotProduct(va, va);
+  //const sqrLenB  = dotProduct(vb, vb);
+
+  // Check for line intersection. This works because of the properties of the
+  // cross product -- specifically, two vectors are parallel if and only if the
+  // cross product is the 0 vector. The full calculation involves relative error
+  // to account for possible very small line segments. See Schneider & Eberly
+  // for details.
+  if (sqrKross > 0/* EPS * sqrLenB * sqLenA */) {
+    // If they're not parallel, then (because these are line segments) they
+    // still might not actually intersect. This code checks that the
+    // intersection point of the lines is actually on both line segments.
+    const s = crossProduct(e, vb) / kross;
+    if (s < 0 || s > 1) {
+      // not on line segment a
+      return null;
+    }
+    const t = crossProduct(e, va) / kross;
+    if (t < 0 || t > 1) {
+      // not on line segment b
+      return null;
+    }
+    if (s === 0 || s === 1) {
+      // on an endpoint of line segment a
+      return noEndpointTouch ? null : [toPoint(a1, s, va)];
+    }
+    if (t === 0 || t === 1) {
+      // on an endpoint of line segment b
+      return noEndpointTouch ? null : [toPoint(b1, t, vb)];
+    }
+    return [toPoint(a1, s, va)];
+  }
+
+  // If we've reached this point, then the lines are either parallel or the
+  // same, but the segments could overlap partially or fully, or not at all.
+  // So we need to find the overlap, if any. To do that, we can use e, which is
+  // the (vector) difference between the two initial points. If this is parallel
+  // with the line itself, then the two lines are the same line, and there will
+  // be overlap.
+  //const sqrLenE = dotProduct(e, e);
+  kross = crossProduct(e, va);
+  sqrKross = kross * kross;
+
+  if (sqrKross > 0 /* EPS * sqLenB * sqLenE */) {
+  // Lines are just parallel, not the same. No overlap.
+    return null;
+  }
+
+  const sa = dotProduct(va, e) / sqrLenA;
+  const sb = sa + dotProduct(va, vb) / sqrLenA;
+  const smin = Math.min(sa, sb);
+  const smax = Math.max(sa, sb);
+
+  // this is, essentially, the FindIntersection acting on floats from
+  // Schneider & Eberly, just inlined into this function.
+  if (smin <= 1 && smax >= 0) {
+
+    // overlap on an end point
+    if (smin === 1) {
+      return noEndpointTouch ? null : [toPoint(a1, smin > 0 ? smin : 0, va)];
+    }
+
+    if (smax === 0) {
+      return noEndpointTouch ? null : [toPoint(a1, smax < 1 ? smax : 1, va)];
+    }
+
+    if (noEndpointTouch && smin === 0 && smax === 1) return null;
+
+    // There's overlap on a segment -- two points of intersection. Return both.
+    return [
+      toPoint(a1, smin > 0 ? smin : 0, va),
+      toPoint(a1, smax < 1 ? smax : 1, va)
+    ];
+  }
+
+  return null;
+}
+
+/**
+ * @param  {SweepEvent} se1
+ * @param  {SweepEvent} se2
+ * @param  {Queue}      queue
+ * @return {Number}
+ */
+function possibleIntersection (se1, se2, queue) {
+  // that disallows self-intersecting polygons,
+  // did cost us half a day, so I'll leave it
+  // out of respect
+  // if (se1.isSubject === se2.isSubject) return;
+  const inter = intersection$1(
+    se1.point, se1.otherEvent.point,
+    se2.point, se2.otherEvent.point
+  );
+
+  const nintersections = inter ? inter.length : 0;
+  if (nintersections === 0) return 0; // no intersection
+
+  // the line segments intersect at an endpoint of both line segments
+  if ((nintersections === 1) &&
+      (equals(se1.point, se2.point) ||
+       equals(se1.otherEvent.point, se2.otherEvent.point))) {
+    return 0;
+  }
+
+  if (nintersections === 2 && se1.isSubject === se2.isSubject) {
+    // if(se1.contourId === se2.contourId){
+    // console.warn('Edges of the same polygon overlap',
+    //   se1.point, se1.otherEvent.point, se2.point, se2.otherEvent.point);
+    // }
+    //throw new Error('Edges of the same polygon overlap');
+    return 0;
+  }
+
+  // The line segments associated to se1 and se2 intersect
+  if (nintersections === 1) {
+
+    // if the intersection point is not an endpoint of se1
+    if (!equals(se1.point, inter[0]) && !equals(se1.otherEvent.point, inter[0])) {
+      divideSegment(se1, inter[0], queue);
+    }
+
+    // if the intersection point is not an endpoint of se2
+    if (!equals(se2.point, inter[0]) && !equals(se2.otherEvent.point, inter[0])) {
+      divideSegment(se2, inter[0], queue);
+    }
+    return 1;
+  }
+
+  // The line segments associated to se1 and se2 overlap
+  const events        = [];
+  let leftCoincide  = false;
+  let rightCoincide = false;
+
+  if (equals(se1.point, se2.point)) {
+    leftCoincide = true; // linked
+  } else if (compareEvents(se1, se2) === 1) {
+    events.push(se2, se1);
+  } else {
+    events.push(se1, se2);
+  }
+
+  if (equals(se1.otherEvent.point, se2.otherEvent.point)) {
+    rightCoincide = true;
+  } else if (compareEvents(se1.otherEvent, se2.otherEvent) === 1) {
+    events.push(se2.otherEvent, se1.otherEvent);
+  } else {
+    events.push(se1.otherEvent, se2.otherEvent);
+  }
+
+  if ((leftCoincide && rightCoincide) || leftCoincide) {
+    // both line segments are equal or share the left endpoint
+    se2.type = NON_CONTRIBUTING;
+    se1.type = (se2.inOut === se1.inOut)
+      ? SAME_TRANSITION : DIFFERENT_TRANSITION;
+
+    if (leftCoincide && !rightCoincide) {
+      // honestly no idea, but changing events selection from [2, 1]
+      // to [0, 1] fixes the overlapping self-intersecting polygons issue
+      divideSegment(events[1].otherEvent, events[0].point, queue);
+    }
+    return 2;
+  }
+
+  // the line segments share the right endpoint
+  if (rightCoincide) {
+    divideSegment(events[0], events[1].point, queue);
+    return 3;
+  }
+
+  // no line segment includes totally the other one
+  if (events[0] !== events[3].otherEvent) {
+    divideSegment(events[0], events[1].point, queue);
+    divideSegment(events[1], events[2].point, queue);
+    return 3;
+  }
+
+  // one line segment includes the other one
+  divideSegment(events[0], events[1].point, queue);
+  divideSegment(events[3].otherEvent, events[2].point, queue);
+
+  return 3;
+}
+
+/**
+ * @param  {SweepEvent} le1
+ * @param  {SweepEvent} le2
+ * @return {Number}
+ */
+function compareSegments(le1, le2) {
+  if (le1 === le2) return 0;
+
+  // Segments are not collinear
+  if (signedArea(le1.point, le1.otherEvent.point, le2.point) !== 0 ||
+    signedArea(le1.point, le1.otherEvent.point, le2.otherEvent.point) !== 0) {
+
+    // If they share their left endpoint use the right endpoint to sort
+    if (equals(le1.point, le2.point)) return le1.isBelow(le2.otherEvent.point) ? -1 : 1;
+
+    // Different left endpoint: use the left endpoint to sort
+    if (le1.point[0] === le2.point[0]) return le1.point[1] < le2.point[1] ? -1 : 1;
+
+    // has the line segment associated to e1 been inserted
+    // into S after the line segment associated to e2 ?
+    if (compareEvents(le1, le2) === 1) return le2.isAbove(le1.point) ? -1 : 1;
+
+    // The line segment associated to e2 has been inserted
+    // into S after the line segment associated to e1
+    return le1.isBelow(le2.point) ? -1 : 1;
+  }
+
+  if (le1.isSubject === le2.isSubject) { // same polygon
+    let p1 = le1.point, p2 = le2.point;
+    if (p1[0] === p2[0] && p1[1] === p2[1]/*equals(le1.point, le2.point)*/) {
+      p1 = le1.otherEvent.point; p2 = le2.otherEvent.point;
+      if (p1[0] === p2[0] && p1[1] === p2[1]) return 0;
+      else return le1.contourId > le2.contourId ? 1 : -1;
+    }
+  } else { // Segments are collinear, but belong to separate polygons
+    return le1.isSubject ? -1 : 1;
+  }
+
+  return compareEvents(le1, le2) === 1 ? 1 : -1;
+}
+
+function subdivide(eventQueue, subject, clipping, sbbox, cbbox, operation) {
+  const sweepLine = new SplayTree(compareSegments);
+  const sortedEvents = [];
+
+  const rightbound = Math.min(sbbox[2], cbbox[2]);
+
+  let prev, next, begin;
+
+  while (eventQueue.length !== 0) {
+    let event = eventQueue.pop();
+    sortedEvents.push(event);
+
+    // optimization by bboxes for intersection and difference goes here
+    if ((operation === INTERSECTION && event.point[0] > rightbound) ||
+        (operation === DIFFERENCE   && event.point[0] > sbbox[2])) {
+      break;
+    }
+
+    if (event.left) {
+      next  = prev = sweepLine.insert(event);
+      begin = sweepLine.minNode();
+
+      if (prev !== begin) prev = sweepLine.prev(prev);
+      else                prev = null;
+
+      next = sweepLine.next(next);
+
+      const prevEvent = prev ? prev.key : null;
+      let prevprevEvent;
+      computeFields(event, prevEvent, operation);
+      if (next) {
+        if (possibleIntersection(event, next.key, eventQueue) === 2) {
+          computeFields(event, prevEvent, operation);
+          computeFields(event, next.key, operation);
+        }
+      }
+
+      if (prev) {
+        if (possibleIntersection(prev.key, event, eventQueue) === 2) {
+          let prevprev = prev;
+          if (prevprev !== begin) prevprev = sweepLine.prev(prevprev);
+          else                    prevprev = null;
+
+          prevprevEvent = prevprev ? prevprev.key : null;
+          computeFields(prevEvent, prevprevEvent, operation);
+          computeFields(event,     prevEvent,     operation);
+        }
+      }
+    } else {
+      event = event.otherEvent;
+      next = prev = sweepLine.find(event);
+
+      if (prev && next) {
+
+        if (prev !== begin) prev = sweepLine.prev(prev);
+        else                prev = null;
+
+        next = sweepLine.next(next);
+        sweepLine.remove(event);
+
+        if (next && prev) {
+          possibleIntersection(prev.key, next.key, eventQueue);
+        }
+      }
+    }
+  }
+  return sortedEvents;
+}
+
+class Contour {
+
+  /**
+   * Contour
+   *
+   * @class {Contour}
+   */
+  constructor() {
+    this.points = [];
+    this.holeIds = [];
+    this.holeOf = null;
+    this.depth = null;
+  }
+
+  isExterior() {
+    return this.holeOf == null;
+  }
+
+}
+
+/**
+ * @param  {Array.<SweepEvent>} sortedEvents
+ * @return {Array.<SweepEvent>}
+ */
+function orderEvents(sortedEvents) {
+  let event, i, len, tmp;
+  const resultEvents = [];
+  for (i = 0, len = sortedEvents.length; i < len; i++) {
+    event = sortedEvents[i];
+    if ((event.left && event.inResult) ||
+      (!event.left && event.otherEvent.inResult)) {
+      resultEvents.push(event);
+    }
+  }
+  // Due to overlapping edges the resultEvents array can be not wholly sorted
+  let sorted = false;
+  while (!sorted) {
+    sorted = true;
+    for (i = 0, len = resultEvents.length; i < len; i++) {
+      if ((i + 1) < len &&
+        compareEvents(resultEvents[i], resultEvents[i + 1]) === 1) {
+        tmp = resultEvents[i];
+        resultEvents[i] = resultEvents[i + 1];
+        resultEvents[i + 1] = tmp;
+        sorted = false;
+      }
+    }
+  }
+
+
+  for (i = 0, len = resultEvents.length; i < len; i++) {
+    event = resultEvents[i];
+    event.otherPos = i;
+  }
+
+  // imagine, the right event is found in the beginning of the queue,
+  // when his left counterpart is not marked yet
+  for (i = 0, len = resultEvents.length; i < len; i++) {
+    event = resultEvents[i];
+    if (!event.left) {
+      tmp = event.otherPos;
+      event.otherPos = event.otherEvent.otherPos;
+      event.otherEvent.otherPos = tmp;
+    }
+  }
+
+  return resultEvents;
+}
+
+
+/**
+ * @param  {Number} pos
+ * @param  {Array.<SweepEvent>} resultEvents
+ * @param  {Object>}    processed
+ * @return {Number}
+ */
+function nextPos(pos, resultEvents, processed, origPos) {
+  let newPos = pos + 1,
+      p = resultEvents[pos].point,
+      p1;
+  const length = resultEvents.length;
+
+  if (newPos < length)
+    p1 = resultEvents[newPos].point;
+
+  while (newPos < length && p1[0] === p[0] && p1[1] === p[1]) {
+    if (!processed[newPos]) {
+      return newPos;
+    } else   {
+      newPos++;
+    }
+    p1 = resultEvents[newPos].point;
+  }
+
+  newPos = pos - 1;
+
+  while (processed[newPos] && newPos > origPos) {
+    newPos--;
+  }
+
+  return newPos;
+}
+
+
+function initializeContourFromContext(event, contours, contourId) {
+  const contour = new Contour();
+  if (event.prevInResult != null) {
+    const prevInResult = event.prevInResult;
+    // Note that it is valid to query the "previous in result" for its output contour id,
+    // because we must have already processed it (i.e., assigned an output contour id)
+    // in an earlier iteration, otherwise it wouldn't be possible that it is "previous in
+    // result".
+    const lowerContourId = prevInResult.outputContourId;
+    const lowerResultTransition = prevInResult.resultTransition;
+    if (lowerResultTransition > 0) {
+      // We are inside. Now we have to check if the thing below us is another hole or
+      // an exterior contour.
+      const lowerContour = contours[lowerContourId];
+      if (lowerContour.holeOf != null) {
+        // The lower contour is a hole => Connect the new contour as a hole to its parent,
+        // and use same depth.
+        const parentContourId = lowerContour.holeOf;
+        contours[parentContourId].holeIds.push(contourId);
+        contour.holeOf = parentContourId;
+        contour.depth = contours[lowerContourId].depth;
+      } else {
+        // The lower contour is an exterior contour => Connect the new contour as a hole,
+        // and increment depth.
+        contours[lowerContourId].holeIds.push(contourId);
+        contour.holeOf = lowerContourId;
+        contour.depth = contours[lowerContourId].depth + 1;
+      }
+    } else {
+      // We are outside => this contour is an exterior contour of same depth.
+      contour.holeOf = null;
+      contour.depth = contours[lowerContourId].depth;
+    }
+  } else {
+    // There is no lower/previous contour => this contour is an exterior contour of depth 0.
+    contour.holeOf = null;
+    contour.depth = 0;
+  }
+  return contour;
+}
+
+/**
+ * @param  {Array.<SweepEvent>} sortedEvents
+ * @return {Array.<*>} polygons
+ */
+function connectEdges(sortedEvents) {
+  let i, len;
+  const resultEvents = orderEvents(sortedEvents);
+
+  // "false"-filled array
+  const processed = {};
+  const contours = [];
+
+  for (i = 0, len = resultEvents.length; i < len; i++) {
+
+    if (processed[i]) {
+      continue;
+    }
+
+    const contourId = contours.length;
+    const contour = initializeContourFromContext(resultEvents[i], contours, contourId);
+
+    // Helper function that combines marking an event as processed with assigning its output contour ID
+    const markAsProcessed = (pos) => {
+      processed[pos] = true;
+      resultEvents[pos].outputContourId = contourId;
+    };
+
+    let pos = i;
+    let origPos = i;
+
+    const initial = resultEvents[i].point;
+    contour.points.push(initial);
+
+    /* eslint no-constant-condition: "off" */
+    while (true) {
+      markAsProcessed(pos);
+
+      pos = resultEvents[pos].otherPos;
+
+      markAsProcessed(pos);
+      contour.points.push(resultEvents[pos].point);
+
+      pos = nextPos(pos, resultEvents, processed, origPos);
+
+      if (pos == origPos) {
+        break;
+      }
+    }
+
+    contours.push(contour);
+  }
+
+  return contours;
+}
+
+var tinyqueue = TinyQueue;
+var _default = TinyQueue;
+
+function TinyQueue(data, compare) {
+    if (!(this instanceof TinyQueue)) return new TinyQueue(data, compare);
+
+    this.data = data || [];
+    this.length = this.data.length;
+    this.compare = compare || defaultCompare;
+
+    if (this.length > 0) {
+        for (var i = (this.length >> 1) - 1; i >= 0; i--) this._down(i);
+    }
+}
+
+function defaultCompare(a, b) {
+    return a < b ? -1 : a > b ? 1 : 0;
+}
+
+TinyQueue.prototype = {
+
+    push: function (item) {
+        this.data.push(item);
+        this.length++;
+        this._up(this.length - 1);
+    },
+
+    pop: function () {
+        if (this.length === 0) return undefined;
+
+        var top = this.data[0];
+        this.length--;
+
+        if (this.length > 0) {
+            this.data[0] = this.data[this.length];
+            this._down(0);
+        }
+        this.data.pop();
+
+        return top;
+    },
+
+    peek: function () {
+        return this.data[0];
+    },
+
+    _up: function (pos) {
+        var data = this.data;
+        var compare = this.compare;
+        var item = data[pos];
+
+        while (pos > 0) {
+            var parent = (pos - 1) >> 1;
+            var current = data[parent];
+            if (compare(item, current) >= 0) break;
+            data[pos] = current;
+            pos = parent;
+        }
+
+        data[pos] = item;
+    },
+
+    _down: function (pos) {
+        var data = this.data;
+        var compare = this.compare;
+        var halfLength = this.length >> 1;
+        var item = data[pos];
+
+        while (pos < halfLength) {
+            var left = (pos << 1) + 1;
+            var right = left + 1;
+            var best = data[left];
+
+            if (right < this.length && compare(data[right], best) < 0) {
+                left = right;
+                best = data[right];
+            }
+            if (compare(best, item) >= 0) break;
+
+            data[pos] = best;
+            pos = left;
+        }
+
+        data[pos] = item;
+    }
+};
+tinyqueue.default = _default;
+
+const max = Math.max;
+const min = Math.min;
+
+let contourId = 0;
+
+
+function processPolygon(contourOrHole, isSubject, depth, Q, bbox, isExteriorRing) {
+  let i, len, s1, s2, e1, e2;
+  for (i = 0, len = contourOrHole.length - 1; i < len; i++) {
+    s1 = contourOrHole[i];
+    s2 = contourOrHole[i + 1];
+    e1 = new SweepEvent(s1, false, undefined, isSubject);
+    e2 = new SweepEvent(s2, false, e1,        isSubject);
+    e1.otherEvent = e2;
+
+    if (s1[0] === s2[0] && s1[1] === s2[1]) {
+      continue; // skip collapsed edges, or it breaks
+    }
+
+    e1.contourId = e2.contourId = depth;
+    if (!isExteriorRing) {
+      e1.isExteriorRing = false;
+      e2.isExteriorRing = false;
+    }
+    if (compareEvents(e1, e2) > 0) {
+      e2.left = true;
+    } else {
+      e1.left = true;
+    }
+
+    const x = s1[0], y = s1[1];
+    bbox[0] = min(bbox[0], x);
+    bbox[1] = min(bbox[1], y);
+    bbox[2] = max(bbox[2], x);
+    bbox[3] = max(bbox[3], y);
+
+    // Pushing it so the queue is sorted from left to right,
+    // with object on the left having the highest priority.
+    Q.push(e1);
+    Q.push(e2);
+  }
+}
+
+
+function fillQueue(subject, clipping, sbbox, cbbox, operation) {
+  const eventQueue = new tinyqueue(null, compareEvents);
+  let polygonSet, isExteriorRing, i, ii, j, jj; //, k, kk;
+
+  for (i = 0, ii = subject.length; i < ii; i++) {
+    polygonSet = subject[i];
+    for (j = 0, jj = polygonSet.length; j < jj; j++) {
+      isExteriorRing = j === 0;
+      if (isExteriorRing) contourId++;
+      processPolygon(polygonSet[j], true, contourId, eventQueue, sbbox, isExteriorRing);
+    }
+  }
+
+  for (i = 0, ii = clipping.length; i < ii; i++) {
+    polygonSet = clipping[i];
+    for (j = 0, jj = polygonSet.length; j < jj; j++) {
+      isExteriorRing = j === 0;
+      if (operation === DIFFERENCE) isExteriorRing = false;
+      if (isExteriorRing) contourId++;
+      processPolygon(polygonSet[j], false, contourId, eventQueue, cbbox, isExteriorRing);
+    }
+  }
+
+  return eventQueue;
+}
+
+const EMPTY = [];
+
+
+function trivialOperation(subject, clipping, operation) {
+  let result = null;
+  if (subject.length * clipping.length === 0) {
+    if        (operation === INTERSECTION) {
+      result = EMPTY;
+    } else if (operation === DIFFERENCE) {
+      result = subject;
+    } else if (operation === UNION ||
+               operation === XOR) {
+      result = (subject.length === 0) ? clipping : subject;
+    }
+  }
+  return result;
+}
+
+
+function compareBBoxes(subject, clipping, sbbox, cbbox, operation) {
+  let result = null;
+  if (sbbox[0] > cbbox[2] ||
+      cbbox[0] > sbbox[2] ||
+      sbbox[1] > cbbox[3] ||
+      cbbox[1] > sbbox[3]) {
+    if        (operation === INTERSECTION) {
+      result = EMPTY;
+    } else if (operation === DIFFERENCE) {
+      result = subject;
+    } else if (operation === UNION ||
+               operation === XOR) {
+      result = subject.concat(clipping);
+    }
+  }
+  return result;
+}
+
+
+function boolean(subject, clipping, operation) {
+  if (typeof subject[0][0][0] === 'number') {
+    subject = [subject];
+  }
+  if (typeof clipping[0][0][0] === 'number') {
+    clipping = [clipping];
+  }
+  let trivial = trivialOperation(subject, clipping, operation);
+  if (trivial) {
+    return trivial === EMPTY ? null : trivial;
+  }
+  const sbbox = [Infinity, Infinity, -Infinity, -Infinity];
+  const cbbox = [Infinity, Infinity, -Infinity, -Infinity];
+
+  // console.time('fill queue');
+  const eventQueue = fillQueue(subject, clipping, sbbox, cbbox, operation);
+  //console.timeEnd('fill queue');
+
+  trivial = compareBBoxes(subject, clipping, sbbox, cbbox, operation);
+  if (trivial) {
+    return trivial === EMPTY ? null : trivial;
+  }
+  // console.time('subdivide edges');
+  const sortedEvents = subdivide(eventQueue, subject, clipping, sbbox, cbbox, operation);
+  //console.timeEnd('subdivide edges');
+
+  // console.time('connect vertices');
+  const contours = connectEdges(sortedEvents);
+  //console.timeEnd('connect vertices');
+
+  // Convert contours to polygons
+  const polygons = [];
+  for (let i = 0; i < contours.length; i++) {
+    let contour = contours[i];
+    if (contour.isExterior()) {
+      // The exterior ring goes first
+      let rings = [contour.points];
+      // Followed by holes if any
+      for (let j = 0; j < contour.holeIds.length; j++) {
+        let holeId = contour.holeIds[j];
+        rings.push(contours[holeId].points);
+      }
+      polygons.push(rings);
+    }
+  }
+
+  return polygons;
+}
+
+function intersection (subject, clipping) {
+  return boolean(subject, clipping, INTERSECTION);
+}
+
+/**
+ * @class VertexGeometry
+ * @abstract
+ * @classdesc Represents a vertex geometry.
+ */
+class VertexGeometry extends Geometry {
+    /**
+     * Create a vertex geometry.
+     *
+     * @constructor
+     * @ignore
+     */
+    constructor() {
+        super();
+        this._subsampleThreshold = 0.005;
+    }
+    /**
+     * Finds the polygon pole of inaccessibility, the most distant internal
+     * point from the polygon outline.
+     *
+     * @param {Array<Array<number>>} points2d - 2d points of outline to triangulate.
+     * @returns {Array<number>} Point of inaccessibility.
+     * @ignore
+     */
+    _getPoleOfInaccessibility2d(points2d) {
+        let pole2d = polylabel_1([points2d], 3e-2);
+        return pole2d;
+    }
+    _project(points2d, transform) {
+        const camera = this._createCamera(transform.upVector().toArray(), transform.unprojectSfM([0, 0], 0), transform.unprojectSfM([0, 0], 10));
+        return this._deunproject(points2d, transform, camera);
+    }
+    _subsample(points2d, threshold = this._subsampleThreshold) {
+        const subsampled = [];
+        const length = points2d.length;
+        for (let index = 0; index < length; index++) {
+            const p1 = points2d[index];
+            const p2 = points2d[(index + 1) % length];
+            subsampled.push(p1);
+            const dist = Math.sqrt(Math.pow((p2[0] - p1[0]), 2) + Math.pow((p2[1] - p1[1]), 2));
+            const subsamples = Math.floor(dist / threshold);
+            const coeff = 1 / (subsamples + 1);
+            for (let i = 1; i <= subsamples; i++) {
+                const alpha = i * coeff;
+                const subsample = [
+                    (1 - alpha) * p1[0] + alpha * p2[0],
+                    (1 - alpha) * p1[1] + alpha * p2[1],
+                ];
+                subsampled.push(subsample);
+            }
+        }
+        return subsampled;
+    }
+    /**
+     * Triangulates a 2d polygon and returns the triangle
+     * representation as a flattened array of 3d points.
+     *
+     * @param {Array<Array<number>>} points2d - 2d points of outline to triangulate.
+     * @param {Array<Array<number>>} points3d - 3d points of outline corresponding to the 2d points.
+     * @param {Array<Array<Array<number>>>} [holes2d] - 2d points of holes to triangulate.
+     * @param {Array<Array<Array<number>>>} [holes3d] - 3d points of holes corresponding to the 2d points.
+     * @returns {Array<number>} Flattened array of 3d points ordered based on the triangles.
+     * @ignore
+     */
+    _triangulate(points2d, points3d, holes2d, holes3d) {
+        let data = [points2d.slice(0, -1)];
+        for (let hole2d of holes2d != null ? holes2d : []) {
+            data.push(hole2d.slice(0, -1));
+        }
+        let points = points3d.slice(0, -1);
+        for (let hole3d of holes3d != null ? holes3d : []) {
+            points = points.concat(hole3d.slice(0, -1));
+        }
+        let flattened = earcut_1.flatten(data);
+        let indices = earcut_1(flattened.vertices, flattened.holes, flattened.dimensions);
+        let triangles = [];
+        for (let i = 0; i < indices.length; ++i) {
+            let point = points[indices[i]];
+            triangles.push(point[0]);
+            triangles.push(point[1]);
+            triangles.push(point[2]);
+        }
+        return triangles;
+    }
+    _triangulateSpherical(points2d, holes2d, transform) {
+        const triangles = [];
+        const epsilon = 1e-9;
+        const subareasX = 3;
+        const subareasY = 3;
+        for (let x = 0; x < subareasX; x++) {
+            for (let y = 0; y < subareasY; y++) {
+                const epsilonX0 = x === 0 ? -epsilon : epsilon;
+                const epsilonY0 = y === 0 ? -epsilon : epsilon;
+                const x0 = x / subareasX + epsilonX0;
+                const y0 = y / subareasY + epsilonY0;
+                const x1 = (x + 1) / subareasX + epsilon;
+                const y1 = (y + 1) / subareasY + epsilon;
+                const bbox2d = [
+                    [x0, y0],
+                    [x0, y1],
+                    [x1, y1],
+                    [x1, y0],
+                    [x0, y0],
+                ];
+                const lookat2d = [
+                    (2 * x + 1) / (2 * subareasX),
+                    (2 * y + 1) / (2 * subareasY),
+                ];
+                triangles.push(...this._triangulateSubarea(points2d, holes2d, bbox2d, lookat2d, transform));
+            }
+        }
+        return triangles;
+    }
+    _unproject(points2d, transform, distance = 200) {
+        return points2d
+            .map((point) => {
+            return transform.unprojectBasic(point, distance);
+        });
+    }
+    _createCamera(upVector, position, lookAt) {
+        const camera = new Camera$1();
+        camera.up.copy(new Vector3().fromArray(upVector));
+        camera.position.copy(new Vector3().fromArray(position));
+        camera.lookAt(new Vector3().fromArray(lookAt));
+        camera.updateMatrix();
+        camera.updateMatrixWorld(true);
+        return camera;
+    }
+    _deunproject(points2d, transform, camera) {
+        return points2d
+            .map((point2d) => {
+            const pointWorld = transform.unprojectBasic(point2d, 10000);
+            const pointCamera = new Vector3(pointWorld[0], pointWorld[1], pointWorld[2])
+                .applyMatrix4(camera.matrixWorldInverse);
+            return [pointCamera.x / pointCamera.z, pointCamera.y / pointCamera.z];
+        });
+    }
+    _triangulateSubarea(points2d, holes2d, bbox2d, lookat2d, transform) {
+        const intersections = intersection([points2d, ...holes2d], [bbox2d]);
+        if (!intersections) {
+            return [];
+        }
+        const triangles = [];
+        const threshold = this._subsampleThreshold;
+        const camera = this._createCamera(transform.upVector().toArray(), transform.unprojectSfM([0, 0], 0), transform.unprojectBasic(lookat2d, 10));
+        for (const intersection of intersections) {
+            const subsampledPolygon2d = this._subsample(intersection[0], threshold);
+            const polygon2d = this._deunproject(subsampledPolygon2d, transform, camera);
+            const polygon3d = this._unproject(subsampledPolygon2d, transform);
+            const polygonHoles2d = [];
+            const polygonHoles3d = [];
+            for (let i = 1; i < intersection.length; i++) {
+                let subsampledHole2d = this._subsample(intersection[i], threshold);
+                const hole2d = this._deunproject(subsampledHole2d, transform, camera);
+                const hole3d = this._unproject(subsampledHole2d, transform);
+                polygonHoles2d.push(hole2d);
+                polygonHoles3d.push(hole3d);
+            }
+            triangles.push(...this._triangulate(polygon2d, polygon3d, polygonHoles2d, polygonHoles3d));
+        }
+        return triangles;
+    }
+}
+
+/**
+ * @class RectGeometry
+ *
+ * @classdesc Represents a rectangle geometry in the 2D basic image coordinate system.
+ *
+ * @example
+ * ```js
+ * var basicRect = [0.5, 0.3, 0.7, 0.4];
+ * var rectGeometry = new RectGeometry(basicRect);
+ * ```
+ */
+class RectGeometry extends VertexGeometry {
+    /**
+     * Create a rectangle geometry.
+     *
+     * @constructor
+     * @param {Array<number>} rect - An array representing the top-left and bottom-right
+     * corners of the rectangle in basic coordinates. Ordered according to [x0, y0, x1, y1].
+     *
+     * @throws {GeometryTagError} Rectangle coordinates must be valid basic coordinates.
+     */
+    constructor(rect) {
+        super();
+        if (rect.length !== 4) {
+            throw new GeometryTagError("Rectangle needs to have four values.");
+        }
+        if (rect[1] > rect[3]) {
+            throw new GeometryTagError("Basic Y coordinates values can not be inverted.");
+        }
+        for (let coord of rect) {
+            if (coord < 0 || coord > 1) {
+                throw new GeometryTagError("Basic coordinates must be on the interval [0, 1].");
+            }
+        }
+        this._anchorIndex = undefined;
+        this._rect = rect.slice(0, 4);
+        this._inverted = this._rect[0] > this._rect[2];
+    }
+    /**
+     * Get anchor index property.
+     *
+     * @returns {number} Index representing the current anchor property if
+     * achoring indexing has been initialized. If anchor indexing has not been
+     * initialized or has been terminated undefined will be returned.
+     * @ignore
+     */
+    get anchorIndex() {
+        return this._anchorIndex;
+    }
+    /**
+     * Get inverted property.
+     *
+     * @returns {boolean} Boolean determining whether the rect geometry is
+     * inverted. For spherical the rect geometrye may be inverted.
+     * @ignore
+     */
+    get inverted() {
+        return this._inverted;
+    }
+    /**
+     * Get rect property.
+     *
+     * @returns {Array<number>} Array representing the top-left and bottom-right
+     * corners of the rectangle in basic coordinates.
+     */
+    get rect() {
+        return this._rect;
+    }
+    /**
+     * Initialize anchor indexing to enable setting opposite vertex.
+     *
+     * @param {number} [index] - The index of the vertex to use as anchor.
+     *
+     * @throws {GeometryTagError} If anchor indexing has already been initialized.
+     * @throws {GeometryTagError} If index is not valid (0 to 3).
+     * @ignore
+     */
+    initializeAnchorIndexing(index) {
+        if (this._anchorIndex !== undefined) {
+            throw new GeometryTagError("Anchor indexing is already initialized.");
+        }
+        if (index < 0 || index > 3) {
+            throw new GeometryTagError(`Invalid anchor index: ${index}.`);
+        }
+        this._anchorIndex = index === undefined ? 0 : index;
+    }
+    /**
+     * Terminate anchor indexing to disable setting pposite vertex.
+     * @ignore
+     */
+    terminateAnchorIndexing() {
+        this._anchorIndex = undefined;
+    }
+    /**
+     * Set the value of the vertex opposite to the anchor in the polygon
+     * representation of the rectangle.
+     *
+     * @description Setting the opposite vertex may change the anchor index.
+     *
+     * @param {Array<number>} opposite - The new value of the vertex opposite to the anchor.
+     * @param {Transform} transform - The transform of the image related to the rectangle.
+     *
+     * @throws {GeometryTagError} When anchor indexing has not been initialized.
+     * @ignore
+     */
+    setOppositeVertex2d(opposite, transform) {
+        if (this._anchorIndex === undefined) {
+            throw new GeometryTagError("Anchor indexing needs to be initialized.");
+        }
+        const changed = [
+            Math.max(0, Math.min(1, opposite[0])),
+            Math.max(0, Math.min(1, opposite[1])),
+        ];
+        const original = this._rect.slice();
+        const anchor = this._anchorIndex === 0 ? [original[0], original[3]] :
+            this._anchorIndex === 1 ? [original[0], original[1]] :
+                this._anchorIndex === 2 ? [original[2], original[1]] :
+                    [original[2], original[3]];
+        if (isSpherical(transform.cameraType)) {
+            const deltaX = this._anchorIndex < 2 ?
+                changed[0] - original[2] :
+                changed[0] - original[0];
+            if (!this._inverted && this._anchorIndex < 2 && changed[0] < 0.25 && original[2] > 0.75 && deltaX < -0.5) {
+                // right side passes boundary rightward
+                this._inverted = true;
+                this._anchorIndex = anchor[1] > changed[1] ? 0 : 1;
+            }
+            else if (!this._inverted && this._anchorIndex >= 2 && changed[0] < 0.25 && original[2] > 0.75 && deltaX < -0.5) {
+                // left side passes right side and boundary rightward
+                this._inverted = true;
+                this._anchorIndex = anchor[1] > changed[1] ? 0 : 1;
+            }
+            else if (this._inverted && this._anchorIndex >= 2 && changed[0] < 0.25 && original[0] > 0.75 && deltaX < -0.5) {
+                this._inverted = false;
+                if (anchor[0] > changed[0]) {
+                    // left side passes boundary rightward
+                    this._anchorIndex = anchor[1] > changed[1] ? 3 : 2;
+                }
+                else {
+                    // left side passes right side and boundary rightward
+                    this._anchorIndex = anchor[1] > changed[1] ? 0 : 1;
+                }
+            }
+            else if (!this._inverted && this._anchorIndex >= 2 && changed[0] > 0.75 && original[0] < 0.25 && deltaX > 0.5) {
+                // left side passes boundary leftward
+                this._inverted = true;
+                this._anchorIndex = anchor[1] > changed[1] ? 3 : 2;
+            }
+            else if (!this._inverted && this._anchorIndex < 2 && changed[0] > 0.75 && original[0] < 0.25 && deltaX > 0.5) {
+                // right side passes left side and boundary leftward
+                this._inverted = true;
+                this._anchorIndex = anchor[1] > changed[1] ? 3 : 2;
+            }
+            else if (this._inverted && this._anchorIndex < 2 && changed[0] > 0.75 && original[2] < 0.25 && deltaX > 0.5) {
+                this._inverted = false;
+                if (anchor[0] > changed[0]) {
+                    // right side passes boundary leftward
+                    this._anchorIndex = anchor[1] > changed[1] ? 3 : 2;
+                }
+                else {
+                    // right side passes left side and boundary leftward
+                    this._anchorIndex = anchor[1] > changed[1] ? 0 : 1;
+                }
+            }
+            else if (this._inverted && this._anchorIndex < 2 && changed[0] > original[0]) {
+                // inverted and right side passes left side completing a loop
+                this._inverted = false;
+                this._anchorIndex = anchor[1] > changed[1] ? 0 : 1;
+            }
+            else if (this._inverted && this._anchorIndex >= 2 && changed[0] < original[2]) {
+                // inverted and left side passes right side completing a loop
+                this._inverted = false;
+                this._anchorIndex = anchor[1] > changed[1] ? 3 : 2;
+            }
+            else if (this._inverted) {
+                // if still inverted only top and bottom can switch
+                if (this._anchorIndex < 2) {
+                    this._anchorIndex = anchor[1] > changed[1] ? 0 : 1;
+                }
+                else {
+                    this._anchorIndex = anchor[1] > changed[1] ? 3 : 2;
+                }
+            }
+            else {
+                // if still not inverted treat as non spherical
+                if (anchor[0] <= changed[0] && anchor[1] > changed[1]) {
+                    this._anchorIndex = 0;
+                }
+                else if (anchor[0] <= changed[0] && anchor[1] <= changed[1]) {
+                    this._anchorIndex = 1;
+                }
+                else if (anchor[0] > changed[0] && anchor[1] <= changed[1]) {
+                    this._anchorIndex = 2;
+                }
+                else {
+                    this._anchorIndex = 3;
+                }
+            }
+            const rect = [];
+            if (this._anchorIndex === 0) {
+                rect[0] = anchor[0];
+                rect[1] = changed[1];
+                rect[2] = changed[0];
+                rect[3] = anchor[1];
+            }
+            else if (this._anchorIndex === 1) {
+                rect[0] = anchor[0];
+                rect[1] = anchor[1];
+                rect[2] = changed[0];
+                rect[3] = changed[1];
+            }
+            else if (this._anchorIndex === 2) {
+                rect[0] = changed[0];
+                rect[1] = anchor[1];
+                rect[2] = anchor[0];
+                rect[3] = changed[1];
+            }
+            else {
+                rect[0] = changed[0];
+                rect[1] = changed[1];
+                rect[2] = anchor[0];
+                rect[3] = anchor[1];
+            }
+            if (!this._inverted && rect[0] > rect[2] ||
+                this._inverted && rect[0] < rect[2]) {
+                rect[0] = original[0];
+                rect[2] = original[2];
+            }
+            if (rect[1] > rect[3]) {
+                rect[1] = original[1];
+                rect[3] = original[3];
+            }
+            this._rect[0] = rect[0];
+            this._rect[1] = rect[1];
+            this._rect[2] = rect[2];
+            this._rect[3] = rect[3];
+        }
+        else {
+            if (anchor[0] <= changed[0] && anchor[1] > changed[1]) {
+                this._anchorIndex = 0;
+            }
+            else if (anchor[0] <= changed[0] && anchor[1] <= changed[1]) {
+                this._anchorIndex = 1;
+            }
+            else if (anchor[0] > changed[0] && anchor[1] <= changed[1]) {
+                this._anchorIndex = 2;
+            }
+            else {
+                this._anchorIndex = 3;
+            }
+            const rect = [];
+            if (this._anchorIndex === 0) {
+                rect[0] = anchor[0];
+                rect[1] = changed[1];
+                rect[2] = changed[0];
+                rect[3] = anchor[1];
+            }
+            else if (this._anchorIndex === 1) {
+                rect[0] = anchor[0];
+                rect[1] = anchor[1];
+                rect[2] = changed[0];
+                rect[3] = changed[1];
+            }
+            else if (this._anchorIndex === 2) {
+                rect[0] = changed[0];
+                rect[1] = anchor[1];
+                rect[2] = anchor[0];
+                rect[3] = changed[1];
+            }
+            else {
+                rect[0] = changed[0];
+                rect[1] = changed[1];
+                rect[2] = anchor[0];
+                rect[3] = anchor[1];
+            }
+            if (rect[0] > rect[2]) {
+                rect[0] = original[0];
+                rect[2] = original[2];
+            }
+            if (rect[1] > rect[3]) {
+                rect[1] = original[1];
+                rect[3] = original[3];
+            }
+            this._rect[0] = rect[0];
+            this._rect[1] = rect[1];
+            this._rect[2] = rect[2];
+            this._rect[3] = rect[3];
+        }
+        this._notifyChanged$.next(this);
+    }
+    /**
+     * Set the value of a vertex in the polygon representation of the rectangle.
+     *
+     * @description The polygon is defined to have the first vertex at the
+     * bottom-left corner with the rest of the vertices following in clockwise order.
+     *
+     * @param {number} index - The index of the vertex to be set.
+     * @param {Array<number>} value - The new value of the vertex.
+     * @param {Transform} transform - The transform of the image related to the rectangle.
+     * @ignore
+     */
+    setVertex2d(index, value, transform) {
+        let original = this._rect.slice();
+        let changed = [
+            Math.max(0, Math.min(1, value[0])),
+            Math.max(0, Math.min(1, value[1])),
+        ];
+        let rect = [];
+        if (index === 0) {
+            rect[0] = changed[0];
+            rect[1] = original[1];
+            rect[2] = original[2];
+            rect[3] = changed[1];
+        }
+        else if (index === 1) {
+            rect[0] = changed[0];
+            rect[1] = changed[1];
+            rect[2] = original[2];
+            rect[3] = original[3];
+        }
+        else if (index === 2) {
+            rect[0] = original[0];
+            rect[1] = changed[1];
+            rect[2] = changed[0];
+            rect[3] = original[3];
+        }
+        else if (index === 3) {
+            rect[0] = original[0];
+            rect[1] = original[1];
+            rect[2] = changed[0];
+            rect[3] = changed[1];
+        }
+        if (isSpherical(transform.cameraType)) {
+            let passingBoundaryLeftward = index < 2 && changed[0] > 0.75 && original[0] < 0.25 ||
+                index >= 2 && this._inverted && changed[0] > 0.75 && original[2] < 0.25;
+            let passingBoundaryRightward = index < 2 && this._inverted && changed[0] < 0.25 && original[0] > 0.75 ||
+                index >= 2 && changed[0] < 0.25 && original[2] > 0.75;
+            if (passingBoundaryLeftward || passingBoundaryRightward) {
+                this._inverted = !this._inverted;
+            }
+            else {
+                if (rect[0] - original[0] < -0.25) {
+                    rect[0] = original[0];
+                }
+                if (rect[2] - original[2] > 0.25) {
+                    rect[2] = original[2];
+                }
+            }
+            if (!this._inverted && rect[0] > rect[2] ||
+                this._inverted && rect[0] < rect[2]) {
+                rect[0] = original[0];
+                rect[2] = original[2];
+            }
+        }
+        else {
+            if (rect[0] > rect[2]) {
+                rect[0] = original[0];
+                rect[2] = original[2];
+            }
+        }
+        if (rect[1] > rect[3]) {
+            rect[1] = original[1];
+            rect[3] = original[3];
+        }
+        this._rect[0] = rect[0];
+        this._rect[1] = rect[1];
+        this._rect[2] = rect[2];
+        this._rect[3] = rect[3];
+        this._notifyChanged$.next(this);
+    }
+    /** @ignore */
+    setCentroid2d(value, transform) {
+        let original = this._rect.slice();
+        let x0 = original[0];
+        let x1 = this._inverted ? original[2] + 1 : original[2];
+        let y0 = original[1];
+        let y1 = original[3];
+        let centerX = x0 + (x1 - x0) / 2;
+        let centerY = y0 + (y1 - y0) / 2;
+        let translationX = 0;
+        if (isSpherical(transform.cameraType)) {
+            translationX = this._inverted ? value[0] + 1 - centerX : value[0] - centerX;
+        }
+        else {
+            let minTranslationX = -x0;
+            let maxTranslationX = 1 - x1;
+            translationX = Math.max(minTranslationX, Math.min(maxTranslationX, value[0] - centerX));
+        }
+        let minTranslationY = -y0;
+        let maxTranslationY = 1 - y1;
+        let translationY = Math.max(minTranslationY, Math.min(maxTranslationY, value[1] - centerY));
+        this._rect[0] = original[0] + translationX;
+        this._rect[1] = original[1] + translationY;
+        this._rect[2] = original[2] + translationX;
+        this._rect[3] = original[3] + translationY;
+        if (this._rect[0] < 0) {
+            this._rect[0] += 1;
+            this._inverted = !this._inverted;
+        }
+        else if (this._rect[0] > 1) {
+            this._rect[0] -= 1;
+            this._inverted = !this._inverted;
+        }
+        if (this._rect[2] < 0) {
+            this._rect[2] += 1;
+            this._inverted = !this._inverted;
+        }
+        else if (this._rect[2] > 1) {
+            this._rect[2] -= 1;
+            this._inverted = !this._inverted;
+        }
+        this._notifyChanged$.next(this);
+    }
+    /**
+     * Get the 3D coordinates for the vertices of the rectangle with
+     * interpolated points along the lines.
+     *
+     * @param {Transform} transform - The transform of the image related to
+     * the rectangle.
+     * @returns {Array<Array<number>>} Polygon array of 3D world coordinates
+     * representing the rectangle.
+     * @ignore
+     */
+    getPoints3d(transform) {
+        return this._getPoints2d()
+            .map((point) => {
+            return transform.unprojectBasic(point, 200);
+        });
+    }
+    /**
+     * Get the coordinates of a vertex from the polygon representation of the geometry.
+     *
+     * @description The first vertex represents the bottom-left corner with the rest of
+     * the vertices following in clockwise order. The method shifts the right side
+     * coordinates of the rectangle by one unit to ensure that the vertices are ordered
+     * clockwise.
+     *
+     * @param {number} index - Vertex index.
+     * @returns {Array<number>} Array representing the 2D basic coordinates of the vertex.
+     * @ignore
+     */
+    getVertex2d(index) {
+        return this._rectToVertices2d(this._rect)[index];
+    }
+    /**
+     * Get the coordinates of a vertex from the polygon representation of the geometry.
+     *
+     * @description The first vertex represents the bottom-left corner with the rest of
+     * the vertices following in clockwise order. The coordinates will not be shifted
+     * so they may not appear in clockwise order when layed out on the plane.
+     *
+     * @param {number} index - Vertex index.
+     * @returns {Array<number>} Array representing the 2D basic coordinates of the vertex.
+     * @ignore
+     */
+    getNonAdjustedVertex2d(index) {
+        return this._rectToNonAdjustedVertices2d(this._rect)[index];
+    }
+    /**
+     * Get a vertex from the polygon representation of the 3D coordinates for the
+     * vertices of the geometry.
+     *
+     * @description The first vertex represents the bottom-left corner with the rest of
+     * the vertices following in clockwise order.
+     *
+     * @param {number} index - Vertex index.
+     * @param {Transform} transform - The transform of the image related to the geometry.
+     * @returns {Array<Array<number>>} Polygon array of 3D world coordinates representing
+     * the vertices of the geometry.
+     * @ignore
+     */
+    getVertex3d(index, transform) {
+        return transform.unprojectBasic(this._rectToVertices2d(this._rect)[index], 200);
+    }
+    /**
+     * Get a polygon representation of the 2D basic coordinates for the vertices of the rectangle.
+     *
+     * @description The first vertex represents the bottom-left corner with the rest of
+     * the vertices following in clockwise order.
+     *
+     * @returns {Array<Array<number>>} Polygon array of 2D basic coordinates representing
+     * the rectangle vertices.
+     * @ignore
+     */
+    getVertices2d() {
+        return this._rectToVertices2d(this._rect);
+    }
+    /**
+     * Get a polygon representation of the 3D coordinates for the vertices of the rectangle.
+     *
+     * @description The first vertex represents the bottom-left corner with the rest of
+     * the vertices following in clockwise order.
+     *
+     * @param {Transform} transform - The transform of the image related to the rectangle.
+     * @returns {Array<Array<number>>} Polygon array of 3D world coordinates representing
+     * the rectangle vertices.
+     * @ignore
+     */
+    getVertices3d(transform) {
+        return this._rectToVertices2d(this._rect)
+            .map((vertex) => {
+            return transform.unprojectBasic(vertex, 200);
+        });
+    }
+    /** @ignore */
+    getCentroid2d() {
+        const rect = this._rect;
+        const x0 = rect[0];
+        const x1 = this._inverted ? rect[2] + 1 : rect[2];
+        const y0 = rect[1];
+        const y1 = rect[3];
+        const centroidX = (x0 + x1) / 2;
+        const centroidY = (y0 + y1) / 2;
+        return [centroidX, centroidY];
+    }
+    /** @ignore */
+    getCentroid3d(transform) {
+        const centroid2d = this.getCentroid2d();
+        return transform.unprojectBasic(centroid2d, 200);
+    }
+    /**
+     * @ignore
+     */
+    getPoleOfInaccessibility2d() {
+        return this._getPoleOfInaccessibility2d(this._rectToVertices2d(this._rect));
+    }
+    /** @ignore */
+    getPoleOfInaccessibility3d(transform) {
+        let pole2d = this._getPoleOfInaccessibility2d(this._rectToVertices2d(this._rect));
+        return transform.unprojectBasic(pole2d, 200);
+    }
+    /** @ignore */
+    getTriangles3d(transform) {
+        return isSpherical(transform.cameraType) ?
+            [] :
+            this._triangulate(this._project(this._getPoints2d(), transform), this.getPoints3d(transform));
+    }
+    /**
+     * Check if a particular bottom-right value is valid according to the current
+     * rectangle coordinates.
+     *
+     * @param {Array<number>} bottomRight - The bottom-right coordinates to validate
+     * @returns {boolean} Value indicating whether the provided bottom-right coordinates
+     * are valid.
+     * @ignore
+     */
+    validate(bottomRight) {
+        let rect = this._rect;
+        if (!this._inverted && bottomRight[0] < rect[0] ||
+            bottomRight[0] - rect[2] > 0.25 ||
+            bottomRight[1] < rect[1]) {
+            return false;
+        }
+        return true;
+    }
+    /**
+     * Get the 2D coordinates for the vertices of the rectangle with
+     * interpolated points along the lines.
+     *
+     * @returns {Array<Array<number>>} Polygon array of 2D basic coordinates
+     * representing the rectangle.
+     */
+    _getPoints2d() {
+        let vertices2d = this._rectToVertices2d(this._rect);
+        let sides = vertices2d.length - 1;
+        let sections = 10;
+        let points2d = [];
+        for (let i = 0; i < sides; ++i) {
+            let startX = vertices2d[i][0];
+            let startY = vertices2d[i][1];
+            let endX = vertices2d[i + 1][0];
+            let endY = vertices2d[i + 1][1];
+            let intervalX = (endX - startX) / (sections - 1);
+            let intervalY = (endY - startY) / (sections - 1);
+            for (let j = 0; j < sections; ++j) {
+                let point = [
+                    startX + j * intervalX,
+                    startY + j * intervalY,
+                ];
+                points2d.push(point);
+            }
+        }
+        return points2d;
+    }
+    /**
+     * Convert the top-left, bottom-right representation of a rectangle to a polygon
+     * representation of the vertices starting at the bottom-left corner going
+     * clockwise.
+     *
+     * @description The method shifts the right side coordinates of the rectangle
+     * by one unit to ensure that the vertices are ordered clockwise.
+     *
+     * @param {Array<number>} rect - Top-left, bottom-right representation of a
+     * rectangle.
+     * @returns {Array<Array<number>>} Polygon representation of the vertices of the
+     * rectangle.
+     */
+    _rectToVertices2d(rect) {
+        return [
+            [rect[0], rect[3]],
+            [rect[0], rect[1]],
+            [this._inverted ? rect[2] + 1 : rect[2], rect[1]],
+            [this._inverted ? rect[2] + 1 : rect[2], rect[3]],
+            [rect[0], rect[3]],
+        ];
+    }
+    /**
+     * Convert the top-left, bottom-right representation of a rectangle to a polygon
+     * representation of the vertices starting at the bottom-left corner going
+     * clockwise.
+     *
+     * @description The first vertex represents the bottom-left corner with the rest of
+     * the vertices following in clockwise order. The coordinates will not be shifted
+     * to ensure that the vertices are ordered clockwise when layed out on the plane.
+     *
+     * @param {Array<number>} rect - Top-left, bottom-right representation of a
+     * rectangle.
+     * @returns {Array<Array<number>>} Polygon representation of the vertices of the
+     * rectangle.
+     */
+    _rectToNonAdjustedVertices2d(rect) {
+        return [
+            [rect[0], rect[3]],
+            [rect[0], rect[1]],
+            [rect[2], rect[1]],
+            [rect[2], rect[3]],
+            [rect[0], rect[3]],
+        ];
+    }
+}
+
+class ExtremePointCreateTag extends CreateTag {
+    constructor(geometry, options, transform, viewportCoords) {
+        super(geometry, transform, viewportCoords);
+        this._options = {
+            color: options.color == null ? 0xFFFFFF : options.color,
+            indicateCompleter: options.indicateCompleter == null ? true : options.indicateCompleter,
+        };
+        this._rectGeometry = new RectGeometry(this._geometry.getRect2d(transform));
+        this._createGlObjects();
+    }
+    create() {
+        if (this._geometry.points.length < 3) {
+            return;
+        }
+        this._geometry.removePoint2d(this._geometry.points.length - 1);
+        this._created$.next(this);
+    }
+    dispose() {
+        super.dispose();
+        this._disposeObjects();
+    }
+    getDOMObjects(camera, size) {
+        const container = {
+            offsetHeight: size.height, offsetWidth: size.width,
+        };
+        const vNodes = [];
+        const points2d = this._geometry.getPoints2d();
+        const length = points2d.length;
+        for (let index = 0; index < length - 1; index++) {
+            const nonModifiedIndex = index;
+            const [pointX, pointY] = points2d[index];
+            const pointCanvas = this._viewportCoords.basicToCanvasSafe(pointX, pointY, container, this._transform, camera);
+            if (!pointCanvas) {
+                continue;
+            }
+            const abort = (e) => {
+                e.stopPropagation();
+                this._aborted$.next(this);
+            };
+            const remove = (e) => {
+                e.stopPropagation();
+                this._geometry.removePoint2d(nonModifiedIndex);
+            };
+            const transform = this._canvasToTransform(pointCanvas);
+            const completerProperties = {
+                onclick: index === 0 && length < 3 ? abort : remove,
+                style: { transform: transform },
+            };
+            vNodes.push(virtualDom.h("div.mapillary-tag-interactor", completerProperties, []));
+            const background = this._colorToBackground(this._options.color);
+            const pointProperties = {
+                style: {
+                    background: background,
+                    transform: transform,
+                },
+            };
+            vNodes.push(virtualDom.h("div.mapillary-tag-vertex", pointProperties, []));
+        }
+        if (length > 2 && this._options.indicateCompleter === true) {
+            const [centroidX, centroidY] = this._geometry.getCentroid2d(this._transform);
+            const centroidCanvas = this._viewportCoords.basicToCanvasSafe(centroidX, centroidY, container, this._transform, camera);
+            if (!!centroidCanvas) {
+                const complete = (e) => {
+                    e.stopPropagation();
+                    this._geometry.removePoint2d(this._geometry.points.length - 1);
+                    this._created$.next(this);
+                };
+                const transform = this._canvasToTransform(centroidCanvas);
+                const completerProperties = {
+                    onclick: complete,
+                    style: { transform: transform },
+                };
+                vNodes.push(virtualDom.h("div.mapillary-tag-completer.mapillary-tag-larger", completerProperties, []));
+                const pointProperties = {
+                    style: {
+                        background: this._colorToBackground(this._options.color),
+                        transform: transform,
+                    },
+                };
+                vNodes.push(virtualDom.h("div.mapillary-tag-vertex.mapillary-tag-larger", pointProperties, []));
+                const dotProperties = {
+                    style: {
+                        transform: transform,
+                    },
+                };
+                vNodes.push(virtualDom.h("div.mapillary-tag-dot", dotProperties, []));
+            }
+        }
+        return vNodes;
+    }
+    _onGeometryChanged() {
+        this._disposeObjects();
+        this._rectGeometry = new RectGeometry(this._geometry.getRect2d(this._transform));
+        this._createGlObjects();
+    }
+    _createGlObjects() {
+        this._glObjects = [];
+        const polygon3d = this._rectGeometry.getPoints3d(this._transform);
+        this._outline = this._createOutine(polygon3d, this._options.color);
+        this._glObjects.push(this._outline);
+    }
+    _disposeObjects() {
+        this._disposeLine(this._outline);
+        this._outline = null;
+        this._glObjects = null;
+    }
+}
+
+/**
+ * @class PolygonGeometry
+ *
+ * @classdesc Represents a polygon geometry in the 2D basic image coordinate system.
+ * All polygons and holes provided to the constructor needs to be closed.
+ *
+ * @example
+ * ```js
+ * var basicPolygon = [[0.5, 0.3], [0.7, 0.3], [0.6, 0.5], [0.5, 0.3]];
+ * var polygonGeometry = new PolygonGeometry(basicPolygon);
+ * ```
+ */
+class PolygonGeometry extends VertexGeometry {
+    /**
+     * Create a polygon geometry.
+     *
+     * @constructor
+     * @param {Array<Array<number>>} polygon - Array of polygon vertices. Must be closed.
+     * @param {Array<Array<Array<number>>>} [holes] - Array of arrays of hole vertices.
+     * Each array of holes vertices must be closed.
+     *
+     * @throws {GeometryTagError} Polygon coordinates must be valid basic coordinates.
+     */
+    constructor(polygon, holes) {
+        super();
+        let polygonLength = polygon.length;
+        if (polygonLength < 3) {
+            throw new GeometryTagError("A polygon must have three or more positions.");
+        }
+        if (polygon[0][0] !== polygon[polygonLength - 1][0] ||
+            polygon[0][1] !== polygon[polygonLength - 1][1]) {
+            throw new GeometryTagError("First and last positions must be equivalent.");
+        }
+        this._polygon = [];
+        for (let vertex of polygon) {
+            if (vertex[0] < 0 || vertex[0] > 1 ||
+                vertex[1] < 0 || vertex[1] > 1) {
+                throw new GeometryTagError("Basic coordinates of polygon must be on the interval [0, 1].");
+            }
+            this._polygon.push(vertex.slice());
+        }
+        this._holes = [];
+        if (holes == null) {
+            return;
+        }
+        for (let i = 0; i < holes.length; i++) {
+            let hole = holes[i];
+            let holeLength = hole.length;
+            if (holeLength < 3) {
+                throw new GeometryTagError("A polygon hole must have three or more positions.");
+            }
+            if (hole[0][0] !== hole[holeLength - 1][0] ||
+                hole[0][1] !== hole[holeLength - 1][1]) {
+                throw new GeometryTagError("First and last positions of hole must be equivalent.");
+            }
+            this._holes.push([]);
+            for (let vertex of hole) {
+                if (vertex[0] < 0 || vertex[0] > 1 ||
+                    vertex[1] < 0 || vertex[1] > 1) {
+                    throw new GeometryTagError("Basic coordinates of hole must be on the interval [0, 1].");
+                }
+                this._holes[i].push(vertex.slice());
+            }
+        }
+    }
+    /**
+     * Get polygon property.
+     * @returns {Array<Array<number>>} Closed 2d polygon.
+     */
+    get polygon() {
+        return this._polygon;
+    }
+    /**
+     * Get holes property.
+     * @returns {Array<Array<Array<number>>>} Holes of 2d polygon.
+     */
+    get holes() {
+        return this._holes;
+    }
+    /**
+     * Add a vertex to the polygon by appending it after the last vertex.
+     *
+     * @param {Array<number>} vertex - Vertex to add.
+     * @ignore
+     */
+    addVertex2d(vertex) {
+        let clamped = [
+            Math.max(0, Math.min(1, vertex[0])),
+            Math.max(0, Math.min(1, vertex[1])),
+        ];
+        this._polygon.splice(this._polygon.length - 1, 0, clamped);
+        this._notifyChanged$.next(this);
+    }
+    /**
+     * Get the coordinates of a vertex from the polygon representation of the geometry.
+     *
+     * @param {number} index - Vertex index.
+     * @returns {Array<number>} Array representing the 2D basic coordinates of the vertex.
+     * @ignore
+     */
+    getVertex2d(index) {
+        return this._polygon[index].slice();
+    }
+    /**
+     * Remove a vertex from the polygon.
+     *
+     * @param {number} index - The index of the vertex to remove.
+     * @ignore
+     */
+    removeVertex2d(index) {
+        if (index < 0 ||
+            index >= this._polygon.length ||
+            this._polygon.length < 4) {
+            throw new GeometryTagError("Index for removed vertex must be valid.");
+        }
+        if (index > 0 && index < this._polygon.length - 1) {
+            this._polygon.splice(index, 1);
+        }
+        else {
+            this._polygon.splice(0, 1);
+            this._polygon.pop();
+            let closing = this._polygon[0].slice();
+            this._polygon.push(closing);
+        }
+        this._notifyChanged$.next(this);
+    }
+    /** @ignore */
+    setVertex2d(index, value, transform) {
+        let changed = [
+            Math.max(0, Math.min(1, value[0])),
+            Math.max(0, Math.min(1, value[1])),
+        ];
+        if (index === 0 || index === this._polygon.length - 1) {
+            this._polygon[0] = changed.slice();
+            this._polygon[this._polygon.length - 1] = changed.slice();
+        }
+        else {
+            this._polygon[index] = changed.slice();
+        }
+        this._notifyChanged$.next(this);
+    }
+    /** @ignore */
+    setCentroid2d(value, transform) {
+        let xs = this._polygon.map((point) => { return point[0]; });
+        let ys = this._polygon.map((point) => { return point[1]; });
+        let minX = Math.min.apply(Math, xs);
+        let maxX = Math.max.apply(Math, xs);
+        let minY = Math.min.apply(Math, ys);
+        let maxY = Math.max.apply(Math, ys);
+        let centroid = this.getCentroid2d();
+        let minTranslationX = -minX;
+        let maxTranslationX = 1 - maxX;
+        let minTranslationY = -minY;
+        let maxTranslationY = 1 - maxY;
+        let translationX = Math.max(minTranslationX, Math.min(maxTranslationX, value[0] - centroid[0]));
+        let translationY = Math.max(minTranslationY, Math.min(maxTranslationY, value[1] - centroid[1]));
+        for (let point of this._polygon) {
+            point[0] += translationX;
+            point[1] += translationY;
+        }
+        this._notifyChanged$.next(this);
+    }
+    /** @ignore */
+    getPoints3d(transform) {
+        return this._getPoints3d(this._subsample(this._polygon), transform);
+    }
+    /** @ignore */
+    getVertex3d(index, transform) {
+        return transform.unprojectBasic(this._polygon[index], 200);
+    }
+    /** @ignore */
+    getVertices2d() {
+        return this._polygon.slice();
+    }
+    /** @ignore */
+    getVertices3d(transform) {
+        return this._getPoints3d(this._polygon, transform);
+    }
+    /**
+     * Get a polygon representation of the 3D coordinates for the vertices of each hole
+     * of the geometry. Line segments between vertices will possibly be subsampled
+     * resulting in a larger number of points than the total number of vertices.
+     *
+     * @param {Transform} transform - The transform of the image related to the geometry.
+     * @returns {Array<Array<Array<number>>>} Array of hole polygons in 3D world coordinates
+     * representing the vertices of each hole of the geometry.
+     * @ignore
+     */
+    getHolePoints3d(transform) {
+        return this._holes
+            .map((hole2d) => {
+            return this._getPoints3d(this._subsample(hole2d), transform);
+        });
+    }
+    /**
+     * Get a polygon representation of the 3D coordinates for the vertices of each hole
+     * of the geometry.
+     *
+     * @param {Transform} transform - The transform of the image related to the geometry.
+     * @returns {Array<Array<Array<number>>>} Array of hole polygons in 3D world coordinates
+     * representing the vertices of each hole of the geometry.
+     * @ignore
+     */
+    getHoleVertices3d(transform) {
+        return this._holes
+            .map((hole2d) => {
+            return this._getPoints3d(hole2d, transform);
+        });
+    }
+    /** @ignore */
+    getCentroid2d() {
+        let polygon = this._polygon;
+        let area = 0;
+        let centroidX = 0;
+        let centroidY = 0;
+        for (let i = 0; i < polygon.length - 1; i++) {
+            let xi = polygon[i][0];
+            let yi = polygon[i][1];
+            let xi1 = polygon[i + 1][0];
+            let yi1 = polygon[i + 1][1];
+            let a = xi * yi1 - xi1 * yi;
+            area += a;
+            centroidX += (xi + xi1) * a;
+            centroidY += (yi + yi1) * a;
+        }
+        area /= 2;
+        centroidX /= 6 * area;
+        centroidY /= 6 * area;
+        return [centroidX, centroidY];
+    }
+    /** @ignore */
+    getCentroid3d(transform) {
+        let centroid2d = this.getCentroid2d();
+        return transform.unprojectBasic(centroid2d, 200);
+    }
+    /** @ignore */
+    get3dDomainTriangles3d(transform) {
+        return this._triangulate(this._project(this._polygon, transform), this.getVertices3d(transform), this._holes
+            .map((hole2d) => {
+            return this._project(hole2d, transform);
+        }), this.getHoleVertices3d(transform));
+    }
+    /** @ignore */
+    getTriangles3d(transform) {
+        if (isSpherical(transform.cameraType)) {
+            return this._triangulateSpherical(this._polygon.slice(), this.holes.slice(), transform);
+        }
+        const points2d = this._project(this._subsample(this._polygon), transform);
+        const points3d = this.getPoints3d(transform);
+        const holes2d = this._holes
+            .map((hole) => {
+            return this._project(this._subsample(hole), transform);
+        });
+        const holes3d = this.getHolePoints3d(transform);
+        return this._triangulate(points2d, points3d, holes2d, holes3d);
+    }
+    /** @ignore */
+    getPoleOfInaccessibility2d() {
+        return this._getPoleOfInaccessibility2d(this._polygon.slice());
+    }
+    /** @ignore */
+    getPoleOfInaccessibility3d(transform) {
+        let pole2d = this._getPoleOfInaccessibility2d(this._polygon.slice());
+        return transform.unprojectBasic(pole2d, 200);
+    }
+    _getPoints3d(points2d, transform) {
+        return points2d
+            .map((point) => {
+            return transform.unprojectBasic(point, 200);
+        });
+    }
+}
+
+class OutlineCreateTag extends CreateTag {
+    constructor(geometry, options, transform, viewportCoords) {
+        super(geometry, transform, viewportCoords);
+        this._options = { color: options.color == null ? 0xFFFFFF : options.color };
+        this._createGlObjects();
+    }
+    create() {
+        if (this._geometry instanceof RectGeometry) {
+            this._created$.next(this);
+        }
+        else if (this._geometry instanceof PolygonGeometry) {
+            const polygonGeometry = this._geometry;
+            polygonGeometry.removeVertex2d(polygonGeometry.polygon.length - 2);
+            this._created$.next(this);
+        }
+    }
+    dispose() {
+        super.dispose();
+        this._disposeLine(this._outline);
+        this._disposeObjects();
+    }
+    getDOMObjects(camera, size) {
+        const vNodes = [];
+        const container = {
+            offsetHeight: size.height, offsetWidth: size.width,
+        };
+        const abort = (e) => {
+            e.stopPropagation();
+            this._aborted$.next(this);
+        };
+        if (this._geometry instanceof RectGeometry) {
+            const anchorIndex = this._geometry.anchorIndex;
+            const vertexIndex = anchorIndex === undefined ? 1 : anchorIndex;
+            const [basicX, basicY] = this._geometry.getVertex2d(vertexIndex);
+            const canvasPoint = this._viewportCoords.basicToCanvasSafe(basicX, basicY, container, this._transform, camera);
+            if (canvasPoint != null) {
+                const background = this._colorToBackground(this._options.color);
+                const transform = this._canvasToTransform(canvasPoint);
+                const pointProperties = {
+                    style: { background: background, transform: transform },
+                };
+                const completerProperties = {
+                    onclick: abort,
+                    style: { transform: transform },
+                };
+                vNodes.push(virtualDom.h("div.mapillary-tag-interactor", completerProperties, []));
+                vNodes.push(virtualDom.h("div.mapillary-tag-vertex", pointProperties, []));
+            }
+        }
+        else if (this._geometry instanceof PolygonGeometry) {
+            const polygonGeometry = this._geometry;
+            const [firstVertexBasicX, firstVertexBasicY] = polygonGeometry.getVertex2d(0);
+            const firstVertexCanvas = this._viewportCoords.basicToCanvasSafe(firstVertexBasicX, firstVertexBasicY, container, this._transform, camera);
+            if (firstVertexCanvas != null) {
+                const firstOnclick = polygonGeometry.polygon.length > 4 ?
+                    (e) => {
+                        e.stopPropagation();
+                        polygonGeometry.removeVertex2d(polygonGeometry.polygon.length - 2);
+                        this._created$.next(this);
+                    } :
+                    abort;
+                const transform = this._canvasToTransform(firstVertexCanvas);
+                const completerProperties = {
+                    onclick: firstOnclick,
+                    style: { transform: transform },
+                };
+                const firstClass = polygonGeometry.polygon.length > 4 ?
+                    "mapillary-tag-completer" :
+                    "mapillary-tag-interactor";
+                vNodes.push(virtualDom.h("div." + firstClass, completerProperties, []));
+            }
+            if (polygonGeometry.polygon.length > 3) {
+                const [lastVertexBasicX, lastVertexBasicY] = polygonGeometry.getVertex2d(polygonGeometry.polygon.length - 3);
+                const lastVertexCanvas = this._viewportCoords.basicToCanvasSafe(lastVertexBasicX, lastVertexBasicY, container, this._transform, camera);
+                if (lastVertexCanvas != null) {
+                    const remove = (e) => {
+                        e.stopPropagation();
+                        polygonGeometry.removeVertex2d(polygonGeometry.polygon.length - 3);
+                    };
+                    const transform = this._canvasToTransform(lastVertexCanvas);
+                    const completerProperties = {
+                        onclick: remove,
+                        style: { transform: transform },
+                    };
+                    vNodes.push(virtualDom.h("div.mapillary-tag-interactor", completerProperties, []));
+                }
+            }
+            const verticesBasic = polygonGeometry.polygon.slice();
+            verticesBasic.splice(-2, 2);
+            for (const vertexBasic of verticesBasic) {
+                const vertexCanvas = this._viewportCoords.basicToCanvasSafe(vertexBasic[0], vertexBasic[1], container, this._transform, camera);
+                if (vertexCanvas != null) {
+                    const background = this._colorToBackground(this._options.color);
+                    const transform = this._canvasToTransform(vertexCanvas);
+                    const pointProperties = {
+                        style: {
+                            background: background,
+                            transform: transform,
+                        },
+                    };
+                    vNodes.push(virtualDom.h("div.mapillary-tag-vertex", pointProperties, []));
+                }
+            }
+        }
+        return vNodes;
+    }
+    addPoint(point) {
+        if (this._geometry instanceof RectGeometry) {
+            const rectGeometry = this._geometry;
+            if (!rectGeometry.validate(point)) {
+                return;
+            }
+            this._created$.next(this);
+        }
+        else if (this._geometry instanceof PolygonGeometry) {
+            const polygonGeometry = this._geometry;
+            polygonGeometry.addVertex2d(point);
+        }
+    }
+    _onGeometryChanged() {
+        this._disposeLine(this._outline);
+        this._disposeObjects();
+        this._createGlObjects();
+    }
+    _disposeObjects() {
+        this._outline = null;
+        this._glObjects = [];
+    }
+    _createGlObjects() {
+        const polygon3d = this._geometry instanceof RectGeometry ?
+            this._geometry.getPoints3d(this._transform) :
+            this._geometry.getVertices3d(this._transform);
+        this._outline = this._createOutine(polygon3d, this._options.color);
+        this._glObjects = [this._outline];
+    }
+}
+
+class TagCreator {
+    constructor(component, navigator) {
+        this._component = component;
+        this._navigator = navigator;
+        this._tagOperation$ = new Subject();
+        this._createPoints$ = new Subject();
+        this._createPolygon$ = new Subject();
+        this._createRect$ = new Subject();
+        this._delete$ = new Subject();
+        this._tag$ = this._tagOperation$.pipe(scan((tag, operation) => {
+            return operation(tag);
+        }, null), share());
+        this._replayedTag$ = this._tag$.pipe(publishReplay(1), refCount());
+        this._replayedTag$.subscribe();
+        this._createPoints$.pipe(withLatestFrom(this._component.configuration$, this._navigator.stateService.currentTransform$), map(([coord, conf, transform]) => {
+            return () => {
+                const geometry = new PointsGeometry([
+                    [coord[0], coord[1]],
+                    [coord[0], coord[1]],
+                ]);
+                return new ExtremePointCreateTag(geometry, {
+                    color: conf.createColor,
+                    indicateCompleter: conf.indicatePointsCompleter,
+                }, transform);
+            };
+        }))
+            .subscribe(this._tagOperation$);
+        this._createRect$.pipe(withLatestFrom(this._component.configuration$, this._navigator.stateService.currentTransform$), map(([coord, conf, transform]) => {
+            return () => {
+                const geometry = new RectGeometry([
+                    coord[0],
+                    coord[1],
+                    coord[0],
+                    coord[1],
+                ]);
+                return new OutlineCreateTag(geometry, { color: conf.createColor }, transform);
+            };
+        }))
+            .subscribe(this._tagOperation$);
+        this._createPolygon$.pipe(withLatestFrom(this._component.configuration$, this._navigator.stateService.currentTransform$), map(([coord, conf, transform]) => {
+            return () => {
+                const geometry = new PolygonGeometry([
+                    [coord[0], coord[1]],
+                    [coord[0], coord[1]],
+                    [coord[0], coord[1]],
+                ]);
+                return new OutlineCreateTag(geometry, { color: conf.createColor }, transform);
+            };
+        }))
+            .subscribe(this._tagOperation$);
+        this._delete$.pipe(map(() => {
+            return () => {
+                return null;
+            };
+        }))
+            .subscribe(this._tagOperation$);
+    }
+    get createRect$() {
+        return this._createRect$;
+    }
+    get createPolygon$() {
+        return this._createPolygon$;
+    }
+    get createPoints$() {
+        return this._createPoints$;
+    }
+    get delete$() {
+        return this._delete$;
+    }
+    get tag$() {
+        return this._tag$;
+    }
+    get replayedTag$() {
+        return this._replayedTag$;
+    }
+}
+
+class TagDOMRenderer {
+    render(tags, createTag, atlas, camera, size) {
+        let vNodes = [];
+        for (const tag of tags) {
+            vNodes = vNodes.concat(tag.getDOMObjects(atlas, camera, size));
+        }
+        if (createTag != null) {
+            vNodes = vNodes.concat(createTag.getDOMObjects(camera, size));
+        }
+        return virtualDom.h("div.mapillary-tag-container", {}, vNodes);
+    }
+    clear() {
+        return virtualDom.h("div", {}, []);
+    }
+}
+
+class TagScene {
+    constructor(scene, raycaster) {
+        this._createTag = null;
+        this._needsRender = false;
+        this._raycaster = !!raycaster ? raycaster : new Raycaster();
+        this._scene = !!scene ? scene : new Scene();
+        this._objectTags = {};
+        this._retrievableObjects = [];
+        this._tags = {};
+    }
+    get needsRender() {
+        return this._needsRender;
+    }
+    add(tags) {
+        for (let tag of tags) {
+            if (tag.tag.id in this._tags) {
+                this._remove(tag.tag.id);
+            }
+            this._add(tag);
+        }
+        this._needsRender = true;
+    }
+    addCreateTag(tag) {
+        for (const object of tag.glObjects) {
+            this._scene.add(object);
+        }
+        this._createTag = { tag: tag, objects: tag.glObjects };
+        this._needsRender = true;
+    }
+    clear() {
+        for (const id of Object.keys(this._tags)) {
+            this._remove(id);
+        }
+        this._needsRender = false;
+    }
+    get(id) {
+        return this.has(id) ? this._tags[id].tag : undefined;
+    }
+    has(id) {
+        return id in this._tags;
+    }
+    hasCreateTag() {
+        return this._createTag != null;
+    }
+    intersectObjects([viewportX, viewportY], camera) {
+        this._raycaster.setFromCamera(new Vector2(viewportX, viewportY), camera);
+        const intersects = this._raycaster.intersectObjects(this._retrievableObjects);
+        const intersectedIds = [];
+        for (const intersect of intersects) {
+            if (intersect.object.uuid in this._objectTags) {
+                intersectedIds.push(this._objectTags[intersect.object.uuid]);
+            }
+        }
+        return intersectedIds;
+    }
+    remove(ids) {
+        for (const id of ids) {
+            this._remove(id);
+        }
+        this._needsRender = true;
+    }
+    removeAll() {
+        for (const id of Object.keys(this._tags)) {
+            this._remove(id);
+        }
+        this._needsRender = true;
+    }
+    removeCreateTag() {
+        if (this._createTag == null) {
+            return;
+        }
+        for (const object of this._createTag.objects) {
+            this._scene.remove(object);
+        }
+        this._createTag.tag.dispose();
+        this._createTag = null;
+        this._needsRender = true;
+    }
+    render(perspectiveCamera, renderer) {
+        renderer.render(this._scene, perspectiveCamera);
+        this._needsRender = false;
+    }
+    update() {
+        this._needsRender = true;
+    }
+    updateCreateTagObjects(tag) {
+        if (this._createTag.tag !== tag) {
+            throw new Error("Create tags do not have the same reference.");
+        }
+        for (let object of this._createTag.objects) {
+            this._scene.remove(object);
+        }
+        for (const object of tag.glObjects) {
+            this._scene.add(object);
+        }
+        this._createTag.objects = tag.glObjects;
+        this._needsRender = true;
+    }
+    updateObjects(tag) {
+        const id = tag.tag.id;
+        if (this._tags[id].tag !== tag) {
+            throw new Error("Tags do not have the same reference.");
+        }
+        const tagObjects = this._tags[id];
+        this._removeObjects(tagObjects);
+        delete this._tags[id];
+        this._add(tag);
+        this._needsRender = true;
+    }
+    _add(tag) {
+        const id = tag.tag.id;
+        const tagObjects = { tag: tag, objects: [], retrievableObjects: [] };
+        this._tags[id] = tagObjects;
+        for (const object of tag.getGLObjects()) {
+            tagObjects.objects.push(object);
+            this._scene.add(object);
+        }
+        for (const retrievableObject of tag.getRetrievableObjects()) {
+            tagObjects.retrievableObjects.push(retrievableObject);
+            this._retrievableObjects.push(retrievableObject);
+            this._objectTags[retrievableObject.uuid] = tag.tag.id;
+        }
+    }
+    _remove(id) {
+        const tagObjects = this._tags[id];
+        this._removeObjects(tagObjects);
+        tagObjects.tag.dispose();
+        delete this._tags[id];
+    }
+    _removeObjects(tagObjects) {
+        for (const object of tagObjects.objects) {
+            this._scene.remove(object);
+        }
+        for (const retrievableObject of tagObjects.retrievableObjects) {
+            const index = this._retrievableObjects.indexOf(retrievableObject);
+            if (index !== -1) {
+                this._retrievableObjects.splice(index, 1);
+            }
+        }
+    }
+}
+
+/**
+ * Enumeration for tag modes
+ * @enum {number}
+ * @readonly
+ * @description Modes for the interaction in the tag component.
+ */
+var TagMode;
+(function (TagMode) {
+    /**
+     * Disables creating tags.
+     */
+    TagMode[TagMode["Default"] = 0] = "Default";
+    /**
+     * Create a point geometry through a click.
+     */
+    TagMode[TagMode["CreatePoint"] = 1] = "CreatePoint";
+    /**
+     * Create a points geometry through clicks.
+     */
+    TagMode[TagMode["CreatePoints"] = 2] = "CreatePoints";
+    /**
+     * Create a polygon geometry through clicks.
+     */
+    TagMode[TagMode["CreatePolygon"] = 3] = "CreatePolygon";
+    /**
+     * Create a rect geometry through clicks.
+     */
+    TagMode[TagMode["CreateRect"] = 4] = "CreateRect";
+    /**
+     * Create a rect geometry through drag.
+     *
+     * @description Claims the mouse which results in mouse handlers like
+     * drag pan and scroll zoom becoming inactive.
+     */
+    TagMode[TagMode["CreateRectDrag"] = 5] = "CreateRectDrag";
+})(TagMode || (TagMode = {}));
+
+var TagOperation;
+(function (TagOperation) {
+    TagOperation[TagOperation["None"] = 0] = "None";
+    TagOperation[TagOperation["Centroid"] = 1] = "Centroid";
+    TagOperation[TagOperation["Vertex"] = 2] = "Vertex";
+})(TagOperation || (TagOperation = {}));
+
+class RenderTag {
+    constructor(tag, transform, viewportCoords) {
+        this._tag = tag;
+        this._transform = transform;
+        this._viewportCoords = !!viewportCoords ? viewportCoords : new ViewportCoords();
+        this._glObjectsChanged$ = new Subject();
+        this._interact$ = new Subject();
+    }
+    get glObjectsChanged$() {
+        return this._glObjectsChanged$;
+    }
+    get interact$() {
+        return this._interact$;
+    }
+    get tag() {
+        return this._tag;
+    }
+}
+
+class OutlineRenderTagBase extends RenderTag {
+    constructor(tag, transform) {
+        super(tag, transform);
+        this._geometryChangedSubscription = this._tag.geometry.changed$
+            .subscribe(() => {
+            this._onGeometryChanged();
+        });
+        this._changedSubscription = this._tag.changed$
+            .subscribe(() => {
+            const glObjectsChanged = this._onTagChanged();
+            if (glObjectsChanged) {
+                this._glObjectsChanged$.next(this);
+            }
+        });
+    }
+    dispose() {
+        this._changedSubscription.unsubscribe();
+        this._geometryChangedSubscription.unsubscribe();
+    }
+    _colorToCss(color) {
+        return "#" + ("000000" + color.toString(16)).substr(-6);
+    }
+    _createFill() {
+        let triangles = this._getTriangles();
+        let positions = new Float32Array(triangles);
+        let geometry = new BufferGeometry();
+        geometry.setAttribute("position", new BufferAttribute(positions, 3));
+        geometry.computeBoundingSphere();
+        let material = new MeshBasicMaterial({ side: DoubleSide, transparent: true });
+        this._updateFillMaterial(material);
+        return new Mesh(geometry, material);
+    }
+    _createLine(points3d) {
+        let positions = this._getLinePositions(points3d);
+        let geometry = new BufferGeometry();
+        geometry.setAttribute("position", new BufferAttribute(positions, 3));
+        geometry.computeBoundingSphere();
+        let material = new LineBasicMaterial();
+        this._updateLineBasicMaterial(material);
+        const line = new Line(geometry, material);
+        line.renderOrder = 1;
+        return line;
+    }
+    _createOutline() {
+        return this._createLine(this._getPoints3d());
+    }
+    _disposeFill() {
+        if (this._fill == null) {
+            return;
+        }
+        this._fill.geometry.dispose();
+        this._fill.material.dispose();
+        this._fill = null;
+    }
+    _disposeOutline() {
+        if (this._outline == null) {
+            return;
+        }
+        this._outline.geometry.dispose();
+        this._outline.material.dispose();
+        this._outline = null;
+    }
+    _getLinePositions(points3d) {
+        let length = points3d.length;
+        let positions = new Float32Array(length * 3);
+        for (let i = 0; i < length; ++i) {
+            let index = 3 * i;
+            let position = points3d[i];
+            positions[index + 0] = position[0];
+            positions[index + 1] = position[1];
+            positions[index + 2] = position[2];
+        }
+        return positions;
+    }
+    _interact(operation, cursor, vertexIndex) {
+        return (e) => {
+            let offsetX = e.offsetX - e.target.offsetWidth / 2;
+            let offsetY = e.offsetY - e.target.offsetHeight / 2;
+            this._interact$.next({
+                cursor: cursor,
+                offsetX: offsetX,
+                offsetY: offsetY,
+                operation: operation,
+                tag: this._tag,
+                vertexIndex: vertexIndex,
+            });
+        };
+    }
+    _updateFillGeometry() {
+        let triangles = this._getTriangles();
+        let positions = new Float32Array(triangles);
+        let geometry = this._fill.geometry;
+        let attribute = geometry.getAttribute("position");
+        if (attribute.array.length === positions.length) {
+            attribute.set(positions);
+            attribute.needsUpdate = true;
+        }
+        else {
+            geometry.deleteAttribute("position");
+            geometry.setAttribute("position", new BufferAttribute(positions, 3));
+        }
+        geometry.computeBoundingSphere();
+    }
+    _updateLine(line, points3d) {
+        let positions = this._getLinePositions(points3d);
+        let geometry = line.geometry;
+        let attribute = geometry.getAttribute("position");
+        attribute.set(positions);
+        attribute.needsUpdate = true;
+        geometry.computeBoundingSphere();
+    }
+    _updateOulineGeometry() {
+        this._updateLine(this._outline, this._getPoints3d());
+    }
+}
+
+/**
+ * @class OutlineRenderTag
+ * @classdesc Tag visualizing the properties of an OutlineTag.
+ */
+class ExtremePointRenderTag extends OutlineRenderTagBase {
+    constructor(tag, transform) {
+        super(tag, transform);
+        this._rectGeometry = new RectGeometry(this._tag.geometry.getRect2d(transform));
+        this._fill = !isSpherical(transform.cameraType) ?
+            this._createFill() : null;
+        this._outline = this._tag.lineWidth >= 1 ?
+            this._createOutline() :
+            null;
+    }
+    dispose() {
+        super.dispose();
+        this._disposeFill();
+        this._disposeOutline();
+    }
+    getDOMObjects(atlas, camera, size) {
+        const vNodes = [];
+        const container = {
+            offsetHeight: size.height, offsetWidth: size.width,
+        };
+        if (!this._tag.editable) {
+            return vNodes;
+        }
+        const lineColor = this._colorToCss(this._tag.lineColor);
+        const points2d = this._tag.geometry.getPoints2d();
+        for (let i = 0; i < points2d.length; i++) {
+            const [vertexBasicX, vertexBasicY] = points2d[i];
+            const vertexCanvas = this._viewportCoords.basicToCanvasSafe(vertexBasicX, vertexBasicY, container, this._transform, camera);
+            if (vertexCanvas == null) {
+                continue;
+            }
+            const cursor = "crosshair";
+            const interact = this._interact(TagOperation.Vertex, cursor, i);
+            const vertexCanvasX = Math.round(vertexCanvas[0]);
+            const vertexCanvasY = Math.round(vertexCanvas[1]);
+            const transform = `translate(-50%, -50%) translate(${vertexCanvasX}px,${vertexCanvasY}px)`;
+            const properties = {
+                onpointerdown: interact,
+                style: { background: lineColor, transform: transform, cursor: cursor },
+            };
+            vNodes.push(virtualDom.h("div.mapillary-tag-resizer", properties, []));
+            if (!this._tag.indicateVertices) {
+                continue;
+            }
+            const pointProperties = {
+                style: { background: lineColor, transform: transform },
+            };
+            vNodes.push(virtualDom.h("div.mapillary-tag-vertex", pointProperties, []));
+        }
+        return vNodes;
+    }
+    getGLObjects() {
+        const glObjects = [];
+        if (this._fill != null) {
+            glObjects.push(this._fill);
+        }
+        if (this._outline != null) {
+            glObjects.push(this._outline);
+        }
+        return glObjects;
+    }
+    getRetrievableObjects() {
+        return this._fill != null ? [this._fill] : [];
+    }
+    _onGeometryChanged() {
+        this._rectGeometry = new RectGeometry(this._tag.geometry.getRect2d(this._transform));
+        if (this._fill != null) {
+            this._updateFillGeometry();
+        }
+        if (this._outline != null) {
+            this._updateOulineGeometry();
+        }
+    }
+    _onTagChanged() {
+        let glObjectsChanged = false;
+        if (this._fill != null) {
+            this._updateFillMaterial(this._fill.material);
+        }
+        if (this._outline == null) {
+            if (this._tag.lineWidth >= 1) {
+                this._outline = this._createOutline();
+                glObjectsChanged = true;
+            }
+        }
+        else {
+            this._updateOutlineMaterial();
+        }
+        return glObjectsChanged;
+    }
+    _getPoints3d() {
+        return this._rectGeometry.getPoints3d(this._transform);
+    }
+    _getTriangles() {
+        return this._rectGeometry.getTriangles3d(this._transform);
+    }
+    _updateFillMaterial(material) {
+        material.color = new Color(this._tag.fillColor);
+        material.opacity = this._tag.fillOpacity;
+        material.needsUpdate = true;
+    }
+    _updateLineBasicMaterial(material) {
+        material.color = new Color(this._tag.lineColor);
+        material.linewidth = Math.max(this._tag.lineWidth, 1);
+        material.visible = this._tag.lineWidth >= 1 && this._tag.lineOpacity > 0;
+        material.opacity = this._tag.lineOpacity;
+        material.transparent = this._tag.lineOpacity < 1;
+        material.needsUpdate = true;
+    }
+    _updateOutlineMaterial() {
+        let material = this._outline.material;
+        this._updateLineBasicMaterial(material);
+    }
+}
+
+/**
+ * @class Tag
+ * @abstract
+ * @classdesc Abstract class representing the basic functionality of for a tag.
+ */
+class Tag extends EventEmitter {
+    /**
+     * Create a tag.
+     *
+     * @constructor
+     * @param {string} id
+     * @param {Geometry} geometry
+     */
+    constructor(id, geometry) {
+        super();
+        this._id = id;
+        this._geometry = geometry;
+        this._notifyChanged$ = new Subject();
+        this._notifyChanged$
+            .subscribe((t) => {
+            const type = "tag";
+            const event = {
+                target: this,
+                type,
+            };
+            this.fire(type, event);
+        });
+        this._geometry.changed$
+            .subscribe((g) => {
+            const type = "geometry";
+            const event = {
+                target: this,
+                type,
+            };
+            this.fire(type, event);
+        });
+    }
+    /**
+     * Get id property.
+     * @returns {string}
+     */
+    get id() {
+        return this._id;
+    }
+    /**
+     * Get geometry property.
+     * @returns {Geometry} The geometry of the tag.
+     */
+    get geometry() {
+        return this._geometry;
+    }
+    /**
+     * Get changed observable.
+     * @returns {Observable<Tag>}
+     * @ignore
+     */
+    get changed$() {
+        return this._notifyChanged$;
+    }
+    /**
+     * Get geometry changed observable.
+     * @returns {Observable<Tag>}
+     * @ignore
+     */
+    get geometryChanged$() {
+        return this._geometry.changed$.pipe(map(() => {
+            return this;
+        }), share());
+    }
+    fire(type, event) {
+        super.fire(type, event);
+    }
+    off(type, handler) {
+        super.off(type, handler);
+    }
+    on(type, handler) {
+        super.on(type, handler);
+    }
+}
+
+/**
+ * @class ExtremePointTag
+ *
+ * @classdesc Tag holding properties for visualizing a extreme points
+ * and their outline.
+ *
+ * @example
+ * ```js
+ * var geometry = new PointsGeometry([[0.3, 0.3], [0.5, 0.4]]);
+ * var tag = new ExtremePointTag(
+ *     "id-1",
+ *     geometry
+ *     { editable: true, lineColor: 0xff0000 });
+ *
+ * tagComponent.add([tag]);
+ * ```
+ */
+class ExtremePointTag extends Tag {
+    /**
+     * Create an extreme point tag.
+     *
+     * @override
+     * @constructor
+     * @param {string} id - Unique identifier of the tag.
+     * @param {PointsGeometry} geometry - Geometry defining points of tag.
+     * @param {ExtremePointTagOptions} options - Options defining the visual appearance and
+     * behavior of the extreme point tag.
+     */
+    constructor(id, geometry, options) {
+        super(id, geometry);
+        options = !!options ? options : {};
+        this._editable = options.editable == null ? false : options.editable;
+        this._fillColor = options.fillColor == null ? 0xFFFFFF : options.fillColor;
+        this._fillOpacity = options.fillOpacity == null ? 0.0 : options.fillOpacity;
+        this._indicateVertices = options.indicateVertices == null ? true : options.indicateVertices;
+        this._lineColor = options.lineColor == null ? 0xFFFFFF : options.lineColor;
+        this._lineOpacity = options.lineOpacity == null ? 1 : options.lineOpacity;
+        this._lineWidth = options.lineWidth == null ? 1 : options.lineWidth;
+    }
+    /**
+     * Get editable property.
+     * @returns {boolean} Value indicating if tag is editable.
+     */
+    get editable() {
+        return this._editable;
+    }
+    /**
+     * Set editable property.
+     * @param {boolean}
+     *
+     * @fires changed
+     */
+    set editable(value) {
+        this._editable = value;
+        this._notifyChanged$.next(this);
+    }
+    /**
+     * Get fill color property.
+     * @returns {number}
+     */
+    get fillColor() {
+        return this._fillColor;
+    }
+    /**
+     * Set fill color property.
+     * @param {number}
+     *
+     * @fires changed
+     */
+    set fillColor(value) {
+        this._fillColor = value;
+        this._notifyChanged$.next(this);
+    }
+    /**
+     * Get fill opacity property.
+     * @returns {number}
+     */
+    get fillOpacity() {
+        return this._fillOpacity;
+    }
+    /**
+     * Set fill opacity property.
+     * @param {number}
+     *
+     * @fires changed
+     */
+    set fillOpacity(value) {
+        this._fillOpacity = value;
+        this._notifyChanged$.next(this);
+    }
+    /** @inheritdoc */
+    get geometry() {
+        return this._geometry;
+    }
+    /**
+     * Get indicate vertices property.
+     * @returns {boolean} Value indicating if vertices should be indicated
+     * when tag is editable.
+     */
+    get indicateVertices() {
+        return this._indicateVertices;
+    }
+    /**
+     * Set indicate vertices property.
+     * @param {boolean}
+     *
+     * @fires changed
+     */
+    set indicateVertices(value) {
+        this._indicateVertices = value;
+        this._notifyChanged$.next(this);
+    }
+    /**
+     * Get line color property.
+     * @returns {number}
+     */
+    get lineColor() {
+        return this._lineColor;
+    }
+    /**
+     * Set line color property.
+     * @param {number}
+     *
+     * @fires changed
+     */
+    set lineColor(value) {
+        this._lineColor = value;
+        this._notifyChanged$.next(this);
+    }
+    /**
+     * Get line opacity property.
+     * @returns {number}
+     */
+    get lineOpacity() {
+        return this._lineOpacity;
+    }
+    /**
+     * Set line opacity property.
+     * @param {number}
+     *
+     * @fires changed
+     */
+    set lineOpacity(value) {
+        this._lineOpacity = value;
+        this._notifyChanged$.next(this);
+    }
+    /**
+     * Get line width property.
+     * @returns {number}
+     */
+    get lineWidth() {
+        return this._lineWidth;
+    }
+    /**
+     * Set line width property.
+     * @param {number}
+     *
+     * @fires changed
+     */
+    set lineWidth(value) {
+        this._lineWidth = value;
+        this._notifyChanged$.next(this);
+    }
+    /**
+     * Set options for tag.
+     *
+     * @description Sets all the option properties provided and keeps
+     * the rest of the values as is.
+     *
+     * @param {ExtremePointTagOptions} options - Extreme point tag options
+     *
+     * @fires changed
+     */
+    setOptions(options) {
+        this._editable = options.editable == null ? this._editable : options.editable;
+        this._indicateVertices = options.indicateVertices == null ? this._indicateVertices : options.indicateVertices;
+        this._lineColor = options.lineColor == null ? this._lineColor : options.lineColor;
+        this._lineWidth = options.lineWidth == null ? this._lineWidth : options.lineWidth;
+        this._fillColor = options.fillColor == null ? this._fillColor : options.fillColor;
+        this._fillOpacity = options.fillOpacity == null ? this._fillOpacity : options.fillOpacity;
+        this._notifyChanged$.next(this);
+    }
+}
+
+/**
+ * Enumeration for tag domains.
+ * @enum {number}
+ * @readonly
+ * @description Defines where lines between two vertices are treated
+ * as straight.
+ *
+ * Only applicable for polygons. For rectangles lines between
+ * vertices are always treated as straight in the distorted 2D
+ * projection and bended in the undistorted 3D space.
+ */
+var TagDomain;
+(function (TagDomain) {
+    /**
+     * Treats lines between two vertices as straight in the
+     * distorted 2D projection, i.e. on the image. If the image
+     * is distorted this will result in bended lines when rendered
+     * in the undistorted 3D space.
+     */
+    TagDomain[TagDomain["TwoDimensional"] = 0] = "TwoDimensional";
+    /**
+     * Treats lines as straight in the undistorted 3D space. If the
+     * image is distorted this will result in bended lines when rendered
+     * on the distorted 2D projection of the image.
+     */
+    TagDomain[TagDomain["ThreeDimensional"] = 1] = "ThreeDimensional";
+})(TagDomain || (TagDomain = {}));
+
+/**
+ * @class OutlineRenderTag
+ * @classdesc Tag visualizing the properties of an OutlineTag.
+ */
+class OutlineRenderTag extends OutlineRenderTagBase {
+    constructor(tag, transform) {
+        super(tag, transform);
+        this._fill = !isSpherical(transform.cameraType) ?
+            this._createFill() :
+            tag.domain === TagDomain.TwoDimensional &&
+                tag.geometry instanceof PolygonGeometry ?
+                this._createFill() :
+                null;
+        this._holes = this._tag.lineWidth >= 1 ?
+            this._createHoles() :
+            [];
+        this._outline = this._tag.lineWidth >= 1 ?
+            this._createOutline() :
+            null;
+    }
+    dispose() {
+        super.dispose();
+        this._disposeFill();
+        this._disposeHoles();
+        this._disposeOutline();
+    }
+    getDOMObjects(atlas, camera, size) {
+        const vNodes = [];
+        const isRect = this._tag.geometry instanceof RectGeometry;
+        const isPerspective = !isSpherical(this._transform.cameraType);
+        const container = {
+            offsetHeight: size.height, offsetWidth: size.width,
+        };
+        if (this._tag.icon != null && (isRect || isPerspective)) {
+            const [iconBasicX, iconBasicY] = this._tag.geometry instanceof RectGeometry ?
+                this._tag.geometry.getVertex2d(this._tag.iconIndex) :
+                this._tag.geometry.getPoleOfInaccessibility2d();
+            const iconCanvas = this._viewportCoords.basicToCanvasSafe(iconBasicX, iconBasicY, container, this._transform, camera);
+            if (iconCanvas != null) {
+                const interact = () => {
+                    this._interact$.next({ offsetX: 0, offsetY: 0, operation: TagOperation.None, tag: this._tag });
+                };
+                if (atlas.loaded) {
+                    const sprite = atlas.getDOMSprite(this._tag.icon, this._tag.iconFloat);
+                    const iconCanvasX = Math.round(iconCanvas[0]);
+                    const iconCanvasY = Math.round(iconCanvas[1]);
+                    const transform = `translate(${iconCanvasX}px,${iconCanvasY}px)`;
+                    const click = (e) => {
+                        e.stopPropagation();
+                        this._tag.click$.next(this._tag);
+                    };
+                    const properties = {
+                        onclick: click,
+                        onpointerdown: interact,
+                        style: { transform: transform },
+                    };
+                    vNodes.push(virtualDom.h("div.mapillary-tag-symbol", properties, [sprite]));
+                }
+            }
+        }
+        else if (this._tag.text != null && (isRect || isPerspective)) {
+            const [textBasicX, textBasicY] = this._tag.geometry instanceof RectGeometry ?
+                this._tag.geometry.getVertex2d(3) :
+                this._tag.geometry.getPoleOfInaccessibility2d();
+            const textCanvas = this._viewportCoords.basicToCanvasSafe(textBasicX, textBasicY, container, this._transform, camera);
+            if (textCanvas != null) {
+                const textCanvasX = Math.round(textCanvas[0]);
+                const textCanvasY = Math.round(textCanvas[1]);
+                const transform = this._tag.geometry instanceof RectGeometry ?
+                    `translate(${textCanvasX}px,${textCanvasY}px)` :
+                    `translate(-50%, -50%) translate(${textCanvasX}px,${textCanvasY}px)`;
+                const interact = () => {
+                    this._interact$.next({ offsetX: 0, offsetY: 0, operation: TagOperation.None, tag: this._tag });
+                };
+                const properties = {
+                    onpointerdown: interact,
+                    style: {
+                        color: this._colorToCss(this._tag.textColor),
+                        transform: transform,
+                    },
+                    textContent: this._tag.text,
+                };
+                vNodes.push(virtualDom.h("span.mapillary-tag-symbol", properties, []));
+            }
+        }
+        if (!this._tag.editable) {
+            return vNodes;
+        }
+        const lineColor = this._colorToCss(this._tag.lineColor);
+        if (this._tag.geometry instanceof RectGeometry) {
+            const [centroidBasicX, centroidBasicY] = this._tag.geometry.getCentroid2d();
+            const centroidCanvas = this._viewportCoords.basicToCanvasSafe(centroidBasicX, centroidBasicY, container, this._transform, camera);
+            if (centroidCanvas != null) {
+                const interact = this._interact(TagOperation.Centroid, "move");
+                const centroidCanvasX = Math.round(centroidCanvas[0]);
+                const centroidCanvasY = Math.round(centroidCanvas[1]);
+                const transform = `translate(-50%, -50%) translate(${centroidCanvasX}px,${centroidCanvasY}px)`;
+                const properties = {
+                    onpointerdown: interact,
+                    style: { background: lineColor, transform: transform },
+                };
+                vNodes.push(virtualDom.h("div.mapillary-tag-mover", properties, []));
+            }
+        }
+        const vertices2d = this._tag.geometry.getVertices2d();
+        for (let i = 0; i < vertices2d.length - 1; i++) {
+            if (isRect &&
+                ((this._tag.icon != null && i === this._tag.iconIndex) ||
+                    (this._tag.icon == null && this._tag.text != null && i === 3))) {
+                continue;
+            }
+            const [vertexBasicX, vertexBasicY] = vertices2d[i];
+            const vertexCanvas = this._viewportCoords.basicToCanvasSafe(vertexBasicX, vertexBasicY, container, this._transform, camera);
+            if (vertexCanvas == null) {
+                continue;
+            }
+            const cursor = isRect ?
+                i % 2 === 0 ? "nesw-resize" : "nwse-resize" :
+                "crosshair";
+            const interact = this._interact(TagOperation.Vertex, cursor, i);
+            const vertexCanvasX = Math.round(vertexCanvas[0]);
+            const vertexCanvasY = Math.round(vertexCanvas[1]);
+            const transform = `translate(-50%, -50%) translate(${vertexCanvasX}px,${vertexCanvasY}px)`;
+            const properties = {
+                onpointerdown: interact,
+                style: { background: lineColor, transform: transform, cursor: cursor },
+            };
+            vNodes.push(virtualDom.h("div.mapillary-tag-resizer", properties, []));
+            if (!this._tag.indicateVertices) {
+                continue;
+            }
+            const pointProperties = {
+                style: { background: lineColor, transform: transform },
+            };
+            vNodes.push(virtualDom.h("div.mapillary-tag-vertex", pointProperties, []));
+        }
+        return vNodes;
+    }
+    getGLObjects() {
+        const glObjects = [];
+        if (this._fill != null) {
+            glObjects.push(this._fill);
+        }
+        for (const hole of this._holes) {
+            glObjects.push(hole);
+        }
+        if (this._outline != null) {
+            glObjects.push(this._outline);
+        }
+        return glObjects;
+    }
+    getRetrievableObjects() {
+        return this._fill != null ? [this._fill] : [];
+    }
+    _onGeometryChanged() {
+        if (this._fill != null) {
+            this._updateFillGeometry();
+        }
+        if (this._holes.length > 0) {
+            this._updateHoleGeometries();
+        }
+        if (this._outline != null) {
+            this._updateOulineGeometry();
+        }
+    }
+    _onTagChanged() {
+        let glObjectsChanged = false;
+        if (this._fill != null) {
+            this._updateFillMaterial(this._fill.material);
+        }
+        if (this._outline == null) {
+            if (this._tag.lineWidth >= 1) {
+                this._holes = this._createHoles();
+                this._outline = this._createOutline();
+                glObjectsChanged = true;
+            }
+        }
+        else {
+            this._updateHoleMaterials();
+            this._updateOutlineMaterial();
+        }
+        return glObjectsChanged;
+    }
+    _getPoints3d() {
+        return this._in3dDomain() ?
+            this._tag.geometry.getVertices3d(this._transform) :
+            this._tag.geometry.getPoints3d(this._transform);
+    }
+    _getTriangles() {
+        return this._in3dDomain() ?
+            this._tag.geometry.get3dDomainTriangles3d(this._transform) :
+            this._tag.geometry.getTriangles3d(this._transform);
+    }
+    _updateFillMaterial(material) {
+        material.color = new Color(this._tag.fillColor);
+        material.opacity = this._tag.fillOpacity;
+        material.needsUpdate = true;
+    }
+    _updateLineBasicMaterial(material) {
+        material.color = new Color(this._tag.lineColor);
+        material.linewidth = Math.max(this._tag.lineWidth, 1);
+        material.visible = this._tag.lineWidth >= 1 && this._tag.lineOpacity > 0;
+        material.opacity = this._tag.lineOpacity;
+        material.transparent = this._tag.lineOpacity < 1;
+        material.needsUpdate = true;
+    }
+    _createHoles() {
+        let holes = [];
+        if (this._tag.geometry instanceof PolygonGeometry) {
+            let holes3d = this._getHoles3d();
+            for (let holePoints3d of holes3d) {
+                let hole = this._createLine(holePoints3d);
+                holes.push(hole);
+            }
+        }
+        return holes;
+    }
+    _disposeHoles() {
+        for (let hole of this._holes) {
+            hole.geometry.dispose();
+            hole.material.dispose();
+        }
+        this._holes = [];
+    }
+    _getHoles3d() {
+        const polygonGeometry = this._tag.geometry;
+        return this._in3dDomain() ?
+            polygonGeometry.getHoleVertices3d(this._transform) :
+            polygonGeometry.getHolePoints3d(this._transform);
+    }
+    _in3dDomain() {
+        return this._tag.geometry instanceof PolygonGeometry && this._tag.domain === TagDomain.ThreeDimensional;
+    }
+    _updateHoleGeometries() {
+        let holes3d = this._getHoles3d();
+        if (holes3d.length !== this._holes.length) {
+            throw new Error("Changing the number of holes is not supported.");
+        }
+        for (let i = 0; i < this._holes.length; i++) {
+            let holePoints3d = holes3d[i];
+            let hole = this._holes[i];
+            this._updateLine(hole, holePoints3d);
+        }
+    }
+    _updateHoleMaterials() {
+        for (const hole of this._holes) {
+            this._updateLineBasicMaterial(hole.material);
+        }
+    }
+    _updateOutlineMaterial() {
+        this._updateLineBasicMaterial(this._outline.material);
+    }
+}
+
+/**
+ * Enumeration for alignments
+ * @enum {number}
+ * @readonly
+ */
+var Alignment;
+(function (Alignment) {
+    /**
+     * Align to bottom
+     */
+    Alignment[Alignment["Bottom"] = 0] = "Bottom";
+    /**
+     * Align to bottom left
+     */
+    Alignment[Alignment["BottomLeft"] = 1] = "BottomLeft";
+    /**
+     * Align to bottom right
+     */
+    Alignment[Alignment["BottomRight"] = 2] = "BottomRight";
+    /**
+     * Align to center
+     */
+    Alignment[Alignment["Center"] = 3] = "Center";
+    /**
+     * Align to left
+     */
+    Alignment[Alignment["Left"] = 4] = "Left";
+    /**
+     * Align to right
+     */
+    Alignment[Alignment["Right"] = 5] = "Right";
+    /**
+     * Align to top
+     */
+    Alignment[Alignment["Top"] = 6] = "Top";
+    /**
+     * Align to top left
+     */
+    Alignment[Alignment["TopLeft"] = 7] = "TopLeft";
+    /**
+     * Align to top right
+     */
+    Alignment[Alignment["TopRight"] = 8] = "TopRight";
+})(Alignment || (Alignment = {}));
+
+/**
+ * @class OutlineTag
+ *
+ * @classdesc Tag holding properties for visualizing a geometry outline.
+ *
+ * @example
+ * ```js
+ * var geometry = new RectGeometry([0.3, 0.3, 0.5, 0.4]);
+ * var tag = new OutlineTag(
+ *     "id-1",
+ *     geometry
+ *     { editable: true, lineColor: 0xff0000 });
+ *
+ * tagComponent.add([tag]);
+ * ```
+ */
+class OutlineTag extends Tag {
+    /**
+     * Create an outline tag.
+     *
+     * @override
+     * @constructor
+     * @param {string} id - Unique identifier of the tag.
+     * @param {VertexGeometry} geometry - Geometry defining vertices of tag.
+     * @param {OutlineTagOptions} options - Options defining the visual appearance and
+     * behavior of the outline tag.
+     */
+    constructor(id, geometry, options) {
+        super(id, geometry);
+        options = !!options ? options : {};
+        const domain = options.domain != null && geometry instanceof PolygonGeometry ?
+            options.domain : TagDomain.TwoDimensional;
+        const twoDimensionalPolygon = this._twoDimensionalPolygon(domain, geometry);
+        this._domain = domain;
+        this._editable = options.editable == null || twoDimensionalPolygon ? false : options.editable;
+        this._fillColor = options.fillColor == null ? 0xFFFFFF : options.fillColor;
+        this._fillOpacity = options.fillOpacity == null ? 0.0 : options.fillOpacity;
+        this._icon = options.icon === undefined ? null : options.icon;
+        this._iconFloat = options.iconFloat == null ? Alignment.Center : options.iconFloat;
+        this._iconIndex = options.iconIndex == null ? 3 : options.iconIndex;
+        this._indicateVertices = options.indicateVertices == null ? true : options.indicateVertices;
+        this._lineColor = options.lineColor == null ? 0xFFFFFF : options.lineColor;
+        this._lineOpacity = options.lineOpacity == null ? 1 : options.lineOpacity;
+        this._lineWidth = options.lineWidth == null ? 1 : options.lineWidth;
+        this._text = options.text === undefined ? null : options.text;
+        this._textColor = options.textColor == null ? 0xFFFFFF : options.textColor;
+        this._click$ = new Subject();
+        this._click$
+            .subscribe(() => {
+            const type = "click";
+            const event = {
+                target: this,
+                type,
+            };
+            this.fire(type, event);
+        });
+    }
+    /**
+     * Click observable.
+     *
+     * @description An observable emitting the tag when the icon of the
+     * tag has been clicked.
+     *
+     * @returns {Observable<Tag>}
+     */
+    get click$() {
+        return this._click$;
+    }
+    /**
+     * Get domain property.
+     *
+     * @description Readonly property that can only be set in constructor.
+     *
+     * @returns Value indicating the domain of the tag.
+     */
+    get domain() {
+        return this._domain;
+    }
+    /**
+     * Get editable property.
+     * @returns {boolean} Value indicating if tag is editable.
+     */
+    get editable() {
+        return this._editable;
+    }
+    /**
+     * Set editable property.
+     * @param {boolean}
+     *
+     * @fires changed
+     */
+    set editable(value) {
+        if (this._twoDimensionalPolygon(this._domain, this._geometry)) {
+            return;
+        }
+        this._editable = value;
+        this._notifyChanged$.next(this);
+    }
+    /**
+     * Get fill color property.
+     * @returns {number}
+     */
+    get fillColor() {
+        return this._fillColor;
+    }
+    /**
+     * Set fill color property.
+     * @param {number}
+     *
+     * @fires changed
+     */
+    set fillColor(value) {
+        this._fillColor = value;
+        this._notifyChanged$.next(this);
+    }
+    /**
+     * Get fill opacity property.
+     * @returns {number}
+     */
+    get fillOpacity() {
+        return this._fillOpacity;
+    }
+    /**
+     * Set fill opacity property.
+     * @param {number}
+     *
+     * @fires changed
+     */
+    set fillOpacity(value) {
+        this._fillOpacity = value;
+        this._notifyChanged$.next(this);
+    }
+    /** @inheritdoc */
+    get geometry() {
+        return this._geometry;
+    }
+    /**
+     * Get icon property.
+     * @returns {string}
+     */
+    get icon() {
+        return this._icon;
+    }
+    /**
+     * Set icon property.
+     * @param {string}
+     *
+     * @fires changed
+     */
+    set icon(value) {
+        this._icon = value;
+        this._notifyChanged$.next(this);
+    }
+    /**
+     * Get icon float property.
+     * @returns {Alignment}
+     */
+    get iconFloat() {
+        return this._iconFloat;
+    }
+    /**
+     * Set icon float property.
+     * @param {Alignment}
+     *
+     * @fires changed
+     */
+    set iconFloat(value) {
+        this._iconFloat = value;
+        this._notifyChanged$.next(this);
+    }
+    /**
+     * Get icon index property.
+     * @returns {number}
+     */
+    get iconIndex() {
+        return this._iconIndex;
+    }
+    /**
+     * Set icon index property.
+     * @param {number}
+     *
+     * @fires changed
+     */
+    set iconIndex(value) {
+        this._iconIndex = value;
+        this._notifyChanged$.next(this);
+    }
+    /**
+     * Get indicate vertices property.
+     * @returns {boolean} Value indicating if vertices should be indicated
+     * when tag is editable.
+     */
+    get indicateVertices() {
+        return this._indicateVertices;
+    }
+    /**
+     * Set indicate vertices property.
+     * @param {boolean}
+     *
+     * @fires changed
+     */
+    set indicateVertices(value) {
+        this._indicateVertices = value;
+        this._notifyChanged$.next(this);
+    }
+    /**
+     * Get line color property.
+     * @returns {number}
+     */
+    get lineColor() {
+        return this._lineColor;
+    }
+    /**
+     * Set line color property.
+     * @param {number}
+     *
+     * @fires changed
+     */
+    set lineColor(value) {
+        this._lineColor = value;
+        this._notifyChanged$.next(this);
+    }
+    /**
+     * Get line opacity property.
+     * @returns {number}
+     */
+    get lineOpacity() {
+        return this._lineOpacity;
+    }
+    /**
+     * Set line opacity property.
+     * @param {number}
+     *
+     * @fires changed
+     */
+    set lineOpacity(value) {
+        this._lineOpacity = value;
+        this._notifyChanged$.next(this);
+    }
+    /**
+     * Get line width property.
+     * @returns {number}
+     */
+    get lineWidth() {
+        return this._lineWidth;
+    }
+    /**
+     * Set line width property.
+     * @param {number}
+     *
+     * @fires changed
+     */
+    set lineWidth(value) {
+        this._lineWidth = value;
+        this._notifyChanged$.next(this);
+    }
+    /**
+     * Get text property.
+     * @returns {string}
+     */
+    get text() {
+        return this._text;
+    }
+    /**
+     * Set text property.
+     * @param {string}
+     *
+     * @fires changed
+     */
+    set text(value) {
+        this._text = value;
+        this._notifyChanged$.next(this);
+    }
+    /**
+     * Get text color property.
+     * @returns {number}
+     */
+    get textColor() {
+        return this._textColor;
+    }
+    /**
+     * Set text color property.
+     * @param {number}
+     *
+     * @fires changed
+     */
+    set textColor(value) {
+        this._textColor = value;
+        this._notifyChanged$.next(this);
+    }
+    fire(type, event) {
+        super.fire(type, event);
+    }
+    off(type, handler) {
+        super.off(type, handler);
+    }
+    on(type, handler) {
+        super.on(type, handler);
+    }
+    /**
+     * Set options for tag.
+     *
+     * @description Sets all the option properties provided and keeps
+     * the rest of the values as is.
+     *
+     * @param {OutlineTagOptions} options - Outline tag options
+     *
+     * @fires changed
+     */
+    setOptions(options) {
+        const twoDimensionalPolygon = this._twoDimensionalPolygon(this._domain, this._geometry);
+        this._editable = twoDimensionalPolygon || options.editable == null ? this._editable : options.editable;
+        this._icon = options.icon === undefined ? this._icon : options.icon;
+        this._iconFloat = options.iconFloat == null ? this._iconFloat : options.iconFloat;
+        this._iconIndex = options.iconIndex == null ? this._iconIndex : options.iconIndex;
+        this._indicateVertices = options.indicateVertices == null ? this._indicateVertices : options.indicateVertices;
+        this._lineColor = options.lineColor == null ? this._lineColor : options.lineColor;
+        this._lineWidth = options.lineWidth == null ? this._lineWidth : options.lineWidth;
+        this._fillColor = options.fillColor == null ? this._fillColor : options.fillColor;
+        this._fillOpacity = options.fillOpacity == null ? this._fillOpacity : options.fillOpacity;
+        this._text = options.text === undefined ? this._text : options.text;
+        this._textColor = options.textColor == null ? this._textColor : options.textColor;
+        this._notifyChanged$.next(this);
+    }
+    _twoDimensionalPolygon(domain, geometry) {
+        return domain !== TagDomain.ThreeDimensional && geometry instanceof PolygonGeometry;
+    }
+}
+
+/**
+ * @class SpotRenderTag
+ * @classdesc Tag visualizing the properties of a SpotTag.
+ */
+class SpotRenderTag extends RenderTag {
+    dispose() { }
+    getDOMObjects(atlas, camera, size) {
+        const tag = this._tag;
+        const container = {
+            offsetHeight: size.height, offsetWidth: size.width,
+        };
+        const vNodes = [];
+        const [centroidBasicX, centroidBasicY] = tag.geometry.getCentroid2d();
+        const centroidCanvas = this._viewportCoords.basicToCanvasSafe(centroidBasicX, centroidBasicY, container, this._transform, camera);
+        if (centroidCanvas != null) {
+            const interactNone = (e) => {
+                this._interact$.next({ offsetX: 0, offsetY: 0, operation: TagOperation.None, tag: tag });
+            };
+            const canvasX = Math.round(centroidCanvas[0]);
+            const canvasY = Math.round(centroidCanvas[1]);
+            if (tag.icon != null) {
+                if (atlas.loaded) {
+                    const sprite = atlas.getDOMSprite(tag.icon, Alignment.Bottom);
+                    const iconTransform = `translate(${canvasX}px,${canvasY + 8}px)`;
+                    const properties = {
+                        onpointerdown: interactNone,
+                        style: {
+                            pointerEvents: "all",
+                            transform: iconTransform,
+                        },
+                    };
+                    vNodes.push(virtualDom.h("div", properties, [sprite]));
+                }
+            }
+            else if (tag.text != null) {
+                const textTransform = `translate(-50%,0%) translate(${canvasX}px,${canvasY + 8}px)`;
+                const properties = {
+                    onpointerdown: interactNone,
+                    style: {
+                        color: this._colorToCss(tag.textColor),
+                        transform: textTransform,
+                    },
+                    textContent: tag.text,
+                };
+                vNodes.push(virtualDom.h("span.mapillary-tag-symbol", properties, []));
+            }
+            const interact = this._interact(TagOperation.Centroid, tag, "move");
+            const background = this._colorToCss(tag.color);
+            const transform = `translate(-50%,-50%) translate(${canvasX}px,${canvasY}px)`;
+            if (tag.editable) {
+                let interactorProperties = {
+                    onpointerdown: interact,
+                    style: {
+                        background: background,
+                        transform: transform,
+                    },
+                };
+                vNodes.push(virtualDom.h("div.mapillary-tag-spot-interactor", interactorProperties, []));
+            }
+            const pointProperties = {
+                style: {
+                    background: background,
+                    transform: transform,
+                },
+            };
+            vNodes.push(virtualDom.h("div.mapillary-tag-vertex", pointProperties, []));
+        }
+        return vNodes;
+    }
+    getGLObjects() { return []; }
+    getRetrievableObjects() { return []; }
+    _colorToCss(color) {
+        return "#" + ("000000" + color.toString(16)).substr(-6);
+    }
+    _interact(operation, tag, cursor, vertexIndex) {
+        return (e) => {
+            const offsetX = e.offsetX - e.target.offsetWidth / 2;
+            const offsetY = e.offsetY - e.target.offsetHeight / 2;
+            this._interact$.next({
+                cursor: cursor,
+                offsetX: offsetX,
+                offsetY: offsetY,
+                operation: operation,
+                tag: tag,
+                vertexIndex: vertexIndex,
+            });
+        };
+    }
+}
+
+/**
+ * @class SpotTag
+ *
+ * @classdesc Tag holding properties for visualizing the centroid of a geometry.
+ *
+ * @example
+ * ```js
+ * var geometry = new PointGeometry([0.3, 0.3]);
+ * var tag = new SpotTag(
+ *     "id-1",
+ *     geometry
+ *     { editable: true, color: 0xff0000 });
+ *
+ * tagComponent.add([tag]);
+ * ```
+ */
+class SpotTag extends Tag {
+    /**
+     * Create a spot tag.
+     *
+     * @override
+     * @constructor
+     * @param {string} id
+     * @param {Geometry} geometry
+     * @param {IOutlineTagOptions} options - Options defining the visual appearance and
+     * behavior of the spot tag.
+     */
+    constructor(id, geometry, options) {
+        super(id, geometry);
+        options = !!options ? options : {};
+        this._color = options.color == null ? 0xFFFFFF : options.color;
+        this._editable = options.editable == null ? false : options.editable;
+        this._icon = options.icon === undefined ? null : options.icon;
+        this._text = options.text === undefined ? null : options.text;
+        this._textColor = options.textColor == null ? 0xFFFFFF : options.textColor;
+    }
+    /**
+     * Get color property.
+     * @returns {number} The color of the spot as a hexagonal number;
+     */
+    get color() {
+        return this._color;
+    }
+    /**
+     * Set color property.
+     * @param {number}
+     *
+     * @fires changed
+     */
+    set color(value) {
+        this._color = value;
+        this._notifyChanged$.next(this);
+    }
+    /**
+     * Get editable property.
+     * @returns {boolean} Value indicating if tag is editable.
+     */
+    get editable() {
+        return this._editable;
+    }
+    /**
+     * Set editable property.
+     * @param {boolean}
+     *
+     * @fires changed
+     */
+    set editable(value) {
+        this._editable = value;
+        this._notifyChanged$.next(this);
+    }
+    /**
+     * Get icon property.
+     * @returns {string}
+     */
+    get icon() {
+        return this._icon;
+    }
+    /**
+     * Set icon property.
+     * @param {string}
+     *
+     * @fires changed
+     */
+    set icon(value) {
+        this._icon = value;
+        this._notifyChanged$.next(this);
+    }
+    /**
+     * Get text property.
+     * @returns {string}
+     */
+    get text() {
+        return this._text;
+    }
+    /**
+     * Set text property.
+     * @param {string}
+     *
+     * @fires changed
+     */
+    set text(value) {
+        this._text = value;
+        this._notifyChanged$.next(this);
+    }
+    /**
+     * Get text color property.
+     * @returns {number}
+     */
+    get textColor() {
+        return this._textColor;
+    }
+    /**
+     * Set text color property.
+     * @param {number}
+     *
+     * @fires changed
+     */
+    set textColor(value) {
+        this._textColor = value;
+        this._notifyChanged$.next(this);
+    }
+    /**
+     * Set options for tag.
+     *
+     * @description Sets all the option properties provided and keps
+     * the rest of the values as is.
+     *
+     * @param {SpotTagOptions} options - Spot tag options
+     *
+     * @fires changed
+     */
+    setOptions(options) {
+        this._color = options.color == null ? this._color : options.color;
+        this._editable = options.editable == null ? this._editable : options.editable;
+        this._icon = options.icon === undefined ? this._icon : options.icon;
+        this._text = options.text === undefined ? this._text : options.text;
+        this._textColor = options.textColor == null ? this._textColor : options.textColor;
+        this._notifyChanged$.next(this);
+    }
+}
+
+class TagSet {
+    constructor() {
+        this._active = false;
+        this._hash = {};
+        this._hashDeactivated = {};
+        this._notifyChanged$ = new Subject();
+    }
+    get active() {
+        return this._active;
+    }
+    get changed$() {
+        return this._notifyChanged$;
+    }
+    activate(transform) {
+        if (this._active) {
+            return;
+        }
+        for (const id in this._hashDeactivated) {
+            if (!this._hashDeactivated.hasOwnProperty(id)) {
+                continue;
+            }
+            const tag = this._hashDeactivated[id];
+            this._add(tag, transform);
+        }
+        this._hashDeactivated = {};
+        this._active = true;
+        this._notifyChanged$.next(this);
+    }
+    deactivate() {
+        if (!this._active) {
+            return;
+        }
+        for (const id in this._hash) {
+            if (!this._hash.hasOwnProperty(id)) {
+                continue;
+            }
+            this._hashDeactivated[id] = this._hash[id].tag;
+        }
+        this._hash = {};
+        this._active = false;
+    }
+    add(tags, transform) {
+        this._assertActivationState(true);
+        for (const tag of tags) {
+            this._add(tag, transform);
+        }
+        this._notifyChanged$.next(this);
+    }
+    addDeactivated(tags) {
+        this._assertActivationState(false);
+        for (const tag of tags) {
+            if (!(tag instanceof OutlineTag ||
+                tag instanceof SpotTag ||
+                tag instanceof ExtremePointTag)) {
+                throw new Error("Tag type not supported");
+            }
+            this._hashDeactivated[tag.id] = tag;
+        }
+    }
+    get(id) {
+        return this.has(id) ? this._hash[id] : undefined;
+    }
+    getAll() {
+        const hash = this._hash;
+        return Object.keys(hash)
+            .map((id) => {
+            return hash[id];
+        });
+    }
+    getAllDeactivated() {
+        const hashDeactivated = this._hashDeactivated;
+        return Object.keys(hashDeactivated)
+            .map((id) => {
+            return hashDeactivated[id];
+        });
+    }
+    getDeactivated(id) {
+        return this.hasDeactivated(id) ? this._hashDeactivated[id] : undefined;
+    }
+    has(id) {
+        return id in this._hash;
+    }
+    hasDeactivated(id) {
+        return id in this._hashDeactivated;
+    }
+    remove(ids) {
+        this._assertActivationState(true);
+        const hash = this._hash;
+        for (const id of ids) {
+            if (!(id in hash)) {
+                continue;
+            }
+            delete hash[id];
+        }
+        this._notifyChanged$.next(this);
+    }
+    removeAll() {
+        this._assertActivationState(true);
+        this._hash = {};
+        this._notifyChanged$.next(this);
+    }
+    removeAllDeactivated() {
+        this._assertActivationState(false);
+        this._hashDeactivated = {};
+    }
+    removeDeactivated(ids) {
+        this._assertActivationState(false);
+        const hashDeactivated = this._hashDeactivated;
+        for (const id of ids) {
+            if (!(id in hashDeactivated)) {
+                continue;
+            }
+            delete hashDeactivated[id];
+        }
+    }
+    _add(tag, transform) {
+        if (tag instanceof OutlineTag) {
+            this._hash[tag.id] = new OutlineRenderTag(tag, transform);
+        }
+        else if (tag instanceof SpotTag) {
+            this._hash[tag.id] = new SpotRenderTag(tag, transform);
+        }
+        else if (tag instanceof ExtremePointTag) {
+            this._hash[tag.id] = new ExtremePointRenderTag(tag, transform);
+        }
+        else {
+            throw new Error("Tag type not supported");
+        }
+    }
+    _assertActivationState(should) {
+        if (should !== this._active) {
+            throw new Error("Tag set not in correct state for operation.");
+        }
+    }
+}
+
+/**
+ * @class PointGeometry
+ *
+ * @classdesc Represents a point geometry in the 2D basic image coordinate system.
+ *
+ * @example
+ * ```js
+ * var basicPoint = [0.5, 0.7];
+ * var pointGeometry = new PointGeometry(basicPoint);
+ * ```
+ */
+class PointGeometry extends Geometry {
+    /**
+     * Create a point geometry.
+     *
+     * @constructor
+     * @param {Array<number>} point - An array representing the basic coordinates of
+     * the point.
+     *
+     * @throws {GeometryTagError} Point coordinates must be valid basic coordinates.
+     */
+    constructor(point) {
+        super();
+        let x = point[0];
+        let y = point[1];
+        if (x < 0 || x > 1 || y < 0 || y > 1) {
+            throw new GeometryTagError("Basic coordinates must be on the interval [0, 1].");
+        }
+        this._point = point.slice();
+    }
+    /**
+     * Get point property.
+     * @returns {Array<number>} Array representing the basic coordinates of the point.
+     */
+    get point() {
+        return this._point;
+    }
+    /**
+     * Get the 2D basic coordinates for the centroid of the point, i.e. the 2D
+     * basic coordinates of the point itself.
+     *
+     * @returns {Array<number>} 2D basic coordinates representing the centroid.
+     * @ignore
+     */
+    getCentroid2d() {
+        return this._point.slice();
+    }
+    /**
+     * Get the 3D world coordinates for the centroid of the point, i.e. the 3D
+     * world coordinates of the point itself.
+     *
+     * @param {Transform} transform - The transform of the image related to the point.
+     * @returns {Array<number>} 3D world coordinates representing the centroid.
+     * @ignore
+     */
+    getCentroid3d(transform) {
+        return transform.unprojectBasic(this._point, 200);
+    }
+    /**
+     * Set the centroid of the point, i.e. the point coordinates.
+     *
+     * @param {Array<number>} value - The new value of the centroid.
+     * @param {Transform} transform - The transform of the image related to the point.
+     * @ignore
+     */
+    setCentroid2d(value, transform) {
+        let changed = [
+            Math.max(0, Math.min(1, value[0])),
+            Math.max(0, Math.min(1, value[1])),
+        ];
+        this._point[0] = changed[0];
+        this._point[1] = changed[1];
+        this._notifyChanged$.next(this);
+    }
+}
+
+class TagHandlerBase extends HandlerBase {
+    constructor(component, container, navigator, viewportCoords) {
+        super(component, container, navigator);
+        this._name = `${this._component.name}-${this._getNameExtension()}`;
+        this._viewportCoords = viewportCoords;
+    }
+    _getConfiguration(enable) {
+        return {};
+    }
+    _mouseEventToBasic(event, element, camera, transform, offsetX, offsetY) {
+        offsetX = offsetX != null ? offsetX : 0;
+        offsetY = offsetY != null ? offsetY : 0;
+        const [canvasX, canvasY] = this._viewportCoords.canvasPosition(event, element);
+        const basic = this._viewportCoords.canvasToBasic(canvasX - offsetX, canvasY - offsetY, element, transform, camera.perspective);
+        return basic;
+    }
+}
+
+class CreateHandlerBase extends TagHandlerBase {
+    constructor(component, container, navigator, viewportCoords, tagCreator) {
+        super(component, container, navigator, viewportCoords);
+        this._tagCreator = tagCreator;
+        this._geometryCreated$ = new Subject();
+    }
+    get geometryCreated$() {
+        return this._geometryCreated$;
+    }
+    _enable() {
+        this._enableCreate();
+        this._container.container.classList.add("component-tag-create");
+    }
+    _disable() {
+        this._container.container.classList.remove("component-tag-create");
+        this._disableCreate();
+    }
+    _validateBasic(basic) {
+        const x = basic[0];
+        const y = basic[1];
+        return 0 <= x && x <= 1 && 0 <= y && y <= 1;
+    }
+    _mouseEventToBasic$(mouseEvent$) {
+        return mouseEvent$.pipe(withLatestFrom(this._container.renderService.renderCamera$, this._navigator.stateService.currentTransform$), map(([event, camera, transform]) => {
+            return this._mouseEventToBasic(event, this._container.container, camera, transform);
+        }));
+    }
+}
+
+class CreatePointHandler extends CreateHandlerBase {
+    _enableCreate() {
+        this._container.mouseService.deferPixels(this._name, 4);
+        this._geometryCreatedSubscription = this._mouseEventToBasic$(this._container.mouseService.proximateClick$).pipe(filter(this._validateBasic), map((basic) => {
+            return new PointGeometry(basic);
+        }))
+            .subscribe(this._geometryCreated$);
+    }
+    _disableCreate() {
+        this._container.mouseService.undeferPixels(this._name);
+        this._geometryCreatedSubscription.unsubscribe();
+    }
+    _getNameExtension() {
+        return "create-point";
+    }
+}
+
+class CreateVertexHandler extends CreateHandlerBase {
+    _enableCreate() {
+        this._container.mouseService.deferPixels(this._name, 4);
+        const transformChanged$ = this._navigator.stateService.currentTransform$.pipe(map(() => { }), publishReplay(1), refCount());
+        this._deleteSubscription = transformChanged$.pipe(skip(1))
+            .subscribe(this._tagCreator.delete$);
+        const basicClick$ = this._mouseEventToBasic$(this._container.mouseService.proximateClick$).pipe(share());
+        this._createSubscription = transformChanged$.pipe(switchMap(() => {
+            return basicClick$.pipe(filter(this._validateBasic), take(1));
+        }))
+            .subscribe(this._create$);
+        this._setVertexSubscription = this._tagCreator.tag$.pipe(switchMap((tag) => {
+            return !!tag ?
+                combineLatest(of(tag), merge(this._container.mouseService.mouseMove$, this._container.mouseService.domMouseMove$), this._container.renderService.renderCamera$, this._navigator.stateService.currentTransform$) :
+                empty();
+        }))
+            .subscribe(([tag, event, camera, transform]) => {
+            const basicPoint = this._mouseEventToBasic(event, this._container.container, camera, transform);
+            this._setVertex2d(tag, basicPoint, transform);
+        });
+        this._addPointSubscription = this._tagCreator.tag$.pipe(switchMap((tag) => {
+            return !!tag ?
+                combineLatest(of(tag), basicClick$) :
+                empty();
+        }))
+            .subscribe(([tag, basicPoint]) => {
+            this._addPoint(tag, basicPoint);
+        });
+        this._geometryCreateSubscription = this._tagCreator.tag$.pipe(switchMap((tag) => {
+            return !!tag ?
+                tag.created$.pipe(map((t) => {
+                    return t.geometry;
+                })) :
+                empty();
+        }))
+            .subscribe(this._geometryCreated$);
+    }
+    _disableCreate() {
+        this._container.mouseService.undeferPixels(this._name);
+        this._tagCreator.delete$.next(null);
+        this._addPointSubscription.unsubscribe();
+        this._createSubscription.unsubscribe();
+        this._deleteSubscription.unsubscribe();
+        this._geometryCreateSubscription.unsubscribe();
+        this._setVertexSubscription.unsubscribe();
+    }
+}
+
+class CreatePointsHandler extends CreateVertexHandler {
+    get _create$() {
+        return this._tagCreator.createPoints$;
+    }
+    _addPoint(tag, basicPoint) {
+        tag.geometry.addPoint2d(basicPoint);
+    }
+    _getNameExtension() {
+        return "create-points";
+    }
+    _setVertex2d(tag, basicPoint, transform) {
+        tag.geometry.setPoint2d((tag.geometry).points.length - 1, basicPoint, transform);
+    }
+}
+
+class CreatePolygonHandler extends CreateVertexHandler {
+    get _create$() {
+        return this._tagCreator.createPolygon$;
+    }
+    _addPoint(tag, basicPoint) {
+        tag.addPoint(basicPoint);
+    }
+    _getNameExtension() {
+        return "create-polygon";
+    }
+    _setVertex2d(tag, basicPoint, transform) {
+        tag.geometry.setVertex2d(tag.geometry.polygon.length - 2, basicPoint, transform);
+    }
+}
+
+class CreateRectHandler extends CreateVertexHandler {
+    get _create$() {
+        return this._tagCreator.createRect$;
+    }
+    _addPoint(tag, basicPoint) {
+        const rectGeometry = tag.geometry;
+        if (!rectGeometry.validate(basicPoint)) {
+            basicPoint = rectGeometry.getNonAdjustedVertex2d(3);
+        }
+        tag.addPoint(basicPoint);
+    }
+    _enable() {
+        super._enable();
+        this._initializeAnchorIndexingSubscription = this._tagCreator.tag$.pipe(filter((tag) => {
+            return !!tag;
+        }))
+            .subscribe((tag) => {
+            tag.geometry.initializeAnchorIndexing();
+        });
+    }
+    _disable() {
+        super._disable();
+        this._initializeAnchorIndexingSubscription.unsubscribe();
+    }
+    _getNameExtension() {
+        return "create-rect";
+    }
+    _setVertex2d(tag, basicPoint, transform) {
+        tag.geometry.setOppositeVertex2d(basicPoint, transform);
+    }
+}
+
+class CreateRectDragHandler extends CreateHandlerBase {
+    _enableCreate() {
+        this._container.mouseService.claimMouse(this._name, 2);
+        this._deleteSubscription = this._navigator.stateService.currentTransform$.pipe(map((transform) => { return null; }), skip(1))
+            .subscribe(this._tagCreator.delete$);
+        this._createSubscription = this._mouseEventToBasic$(this._container.mouseService.filtered$(this._name, this._container.mouseService.mouseDragStart$)).pipe(filter(this._validateBasic))
+            .subscribe(this._tagCreator.createRect$);
+        this._initializeAnchorIndexingSubscription = this._tagCreator.tag$.pipe(filter((tag) => {
+            return !!tag;
+        }))
+            .subscribe((tag) => {
+            tag.geometry.initializeAnchorIndexing();
+        });
+        const basicMouse$ = combineLatest(merge(this._container.mouseService.filtered$(this._name, this._container.mouseService.mouseMove$), this._container.mouseService.filtered$(this._name, this._container.mouseService.domMouseMove$)), this._container.renderService.renderCamera$).pipe(withLatestFrom(this._navigator.stateService.currentTransform$), map(([[event, camera], transform]) => {
+            return this._mouseEventToBasic(event, this._container.container, camera, transform);
+        }));
+        this._setVertexSubscription = this._tagCreator.tag$.pipe(switchMap((tag) => {
+            return !!tag ?
+                combineLatest(of(tag), basicMouse$, this._navigator.stateService.currentTransform$) :
+                empty();
+        }))
+            .subscribe(([tag, basicPoint, transform]) => {
+            tag.geometry.setOppositeVertex2d(basicPoint, transform);
+        });
+        const basicMouseDragEnd$ = this._container.mouseService.mouseDragEnd$.pipe(withLatestFrom(this._mouseEventToBasic$(this._container.mouseService.filtered$(this._name, this._container.mouseService.mouseDrag$)).pipe(filter(this._validateBasic)), (event, basicPoint) => {
+            return basicPoint;
+        }), share());
+        this._addPointSubscription = this._tagCreator.tag$.pipe(switchMap((tag) => {
+            return !!tag ?
+                combineLatest(of(tag), basicMouseDragEnd$) :
+                empty();
+        }))
+            .subscribe(([tag, basicPoint]) => {
+            const rectGeometry = tag.geometry;
+            if (!rectGeometry.validate(basicPoint)) {
+                basicPoint = rectGeometry.getNonAdjustedVertex2d(3);
+            }
+            tag.addPoint(basicPoint);
+        });
+        this._geometryCreatedSubscription = this._tagCreator.tag$.pipe(switchMap((tag) => {
+            return !!tag ?
+                tag.created$.pipe(map((t) => {
+                    return t.geometry;
+                })) :
+                empty();
+        }))
+            .subscribe(this._geometryCreated$);
+    }
+    _disableCreate() {
+        this._container.mouseService.unclaimMouse(this._name);
+        this._tagCreator.delete$.next(null);
+        this._addPointSubscription.unsubscribe();
+        this._createSubscription.unsubscribe();
+        this._deleteSubscription.unsubscribe();
+        this._geometryCreatedSubscription.unsubscribe();
+        this._initializeAnchorIndexingSubscription.unsubscribe();
+        this._setVertexSubscription.unsubscribe();
+    }
+    _getNameExtension() {
+        return "create-rect-drag";
+    }
+}
+
+class EditVertexHandler extends TagHandlerBase {
+    constructor(component, container, navigator, viewportCoords, tagSet) {
+        super(component, container, navigator, viewportCoords);
+        this._tagSet = tagSet;
+    }
+    _enable() {
+        const interaction$ = this._tagSet.changed$.pipe(map((tagSet) => {
+            return tagSet.getAll();
+        }), switchMap((tags) => {
+            return from(tags).pipe(mergeMap((tag) => {
+                return tag.interact$;
+            }));
+        }), switchMap((interaction) => {
+            return concat(of(interaction), this._container.mouseService.documentMouseUp$.pipe(map(() => {
+                return { offsetX: 0, offsetY: 0, operation: TagOperation.None, tag: null };
+            }), first()));
+        }), share());
+        merge(this._container.mouseService.mouseMove$, this._container.mouseService.domMouseMove$).pipe(share());
+        this._claimMouseSubscription = interaction$.pipe(switchMap((interaction) => {
+            return !!interaction.tag ? this._container.mouseService.domMouseDragStart$ : empty();
+        }))
+            .subscribe(() => {
+            this._container.mouseService.claimMouse(this._name, 3);
+        });
+        this._cursorSubscription = interaction$.pipe(map((interaction) => {
+            return interaction.cursor;
+        }), distinctUntilChanged())
+            .subscribe((cursor) => {
+            const interactionCursors = ["crosshair", "move", "nesw-resize", "nwse-resize"];
+            for (const interactionCursor of interactionCursors) {
+                this._container.container.classList.remove(`component-tag-edit-${interactionCursor}`);
+            }
+            if (!!cursor) {
+                this._container.container.classList.add(`component-tag-edit-${cursor}`);
+            }
+        });
+        this._unclaimMouseSubscription = this._container.mouseService
+            .filtered$(this._name, this._container.mouseService.domMouseDragEnd$)
+            .subscribe((e) => {
+            this._container.mouseService.unclaimMouse(this._name);
+        });
+        this._preventDefaultSubscription = interaction$.pipe(switchMap((interaction) => {
+            return !!interaction.tag ?
+                this._container.mouseService.documentMouseMove$ :
+                empty();
+        }))
+            .subscribe((event) => {
+            event.preventDefault(); // prevent selection of content outside the viewer
+        });
+        this._updateGeometrySubscription = interaction$.pipe(switchMap((interaction) => {
+            if (interaction.operation === TagOperation.None || !interaction.tag) {
+                return empty();
+            }
+            const mouseDrag$ = this._container.mouseService
+                .filtered$(this._name, this._container.mouseService.domMouseDrag$).pipe(filter((event) => {
+                return this._viewportCoords.insideElement(event, this._container.container);
+            }));
+            return combineLatest(mouseDrag$, this._container.renderService.renderCamera$).pipe(withLatestFrom(of(interaction), this._navigator.stateService.currentTransform$, ([event, render], i, transform) => {
+                return [event, render, i, transform];
+            }));
+        }))
+            .subscribe(([mouseEvent, renderCamera, interaction, transform]) => {
+            const basic = this._mouseEventToBasic(mouseEvent, this._container.container, renderCamera, transform, interaction.offsetX, interaction.offsetY);
+            const geometry = interaction.tag.geometry;
+            if (interaction.operation === TagOperation.Centroid) {
+                geometry.setCentroid2d(basic, transform);
+            }
+            else if (interaction.operation === TagOperation.Vertex) {
+                geometry.setVertex2d(interaction.vertexIndex, basic, transform);
+            }
+        });
+    }
+    _disable() {
+        this._claimMouseSubscription.unsubscribe();
+        this._cursorSubscription.unsubscribe();
+        this._preventDefaultSubscription.unsubscribe();
+        this._unclaimMouseSubscription.unsubscribe();
+        this._updateGeometrySubscription.unsubscribe();
+    }
+    _getNameExtension() {
+        return "edit-vertex";
+    }
+}
+
+/**
+ * @class TagComponent
+ *
+ * @classdesc Component for showing and editing tags with different
+ * geometries composed from 2D basic image coordinates (see the
+ * {@link Viewer} class documentation for more information about coordinate
+ * systems).
+ *
+ * The `add` method is used for adding new tags or replacing
+ * tags already in the set. Tags are removed by id.
+ *
+ * If a tag already in the set has the same
+ * id as one of the tags added, the old tag will be removed and
+ * the added tag will take its place.
+ *
+ * The tag component mode can be set to either be non interactive or
+ * to be in creating mode of a certain geometry type.
+ *
+ * The tag properties can be updated at any time and the change will
+ * be visibile immediately.
+ *
+ * Tags are only relevant to a single image because they are based on
+ * 2D basic image coordinates. Tags related to a certain image should
+ * be removed when the viewer is moved to another image.
+ *
+ * To retrive and use the tag component
+ *
+ * @example
+ * ```js
+ * var viewer = new Viewer({ component: { tag: true } }, ...);
+ *
+ * var tagComponent = viewer.getComponent("tag");
+ * ```
+ */
+class TagComponent extends Component {
+    /** @ignore */
+    constructor(name, container, navigator) {
+        super(name, container, navigator);
+        this._tagDomRenderer = new TagDOMRenderer();
+        this._tagScene = new TagScene();
+        this._tagSet = new TagSet();
+        this._tagCreator = new TagCreator(this, navigator);
+        this._viewportCoords = new ViewportCoords();
+        this._createHandlers = {
+            "CreatePoint": new CreatePointHandler(this, container, navigator, this._viewportCoords, this._tagCreator),
+            "CreatePoints": new CreatePointsHandler(this, container, navigator, this._viewportCoords, this._tagCreator),
+            "CreatePolygon": new CreatePolygonHandler(this, container, navigator, this._viewportCoords, this._tagCreator),
+            "CreateRect": new CreateRectHandler(this, container, navigator, this._viewportCoords, this._tagCreator),
+            "CreateRectDrag": new CreateRectDragHandler(this, container, navigator, this._viewportCoords, this._tagCreator),
+            "Default": undefined,
+        };
+        this._editVertexHandler =
+            new EditVertexHandler(this, container, navigator, this._viewportCoords, this._tagSet);
+        this._renderTags$ = this._tagSet.changed$.pipe(map((tagSet) => {
+            const tags = tagSet.getAll();
+            // ensure that tags are always rendered in the same order
+            // to avoid hover tracking problems on first resize.
+            tags.sort((t1, t2) => {
+                const id1 = t1.tag.id;
+                const id2 = t2.tag.id;
+                if (id1 < id2) {
+                    return -1;
+                }
+                if (id1 > id2) {
+                    return 1;
+                }
+                return 0;
+            });
+            return tags;
+        }), share());
+        this._tagChanged$ = this._renderTags$.pipe(switchMap((tags) => {
+            return from(tags).pipe(mergeMap((tag) => {
+                return merge(tag.tag.changed$, tag.tag.geometryChanged$);
+            }));
+        }), share());
+        this._renderTagGLChanged$ = this._renderTags$.pipe(switchMap((tags) => {
+            return from(tags).pipe(mergeMap((tag) => {
+                return tag.glObjectsChanged$;
+            }));
+        }), share());
+        this._createGeometryChanged$ = this._tagCreator.tag$.pipe(switchMap((tag) => {
+            return tag != null ?
+                tag.geometryChanged$ :
+                empty();
+        }), share());
+        this._createGLObjectsChanged$ = this._tagCreator.tag$.pipe(switchMap((tag) => {
+            return tag != null ?
+                tag.glObjectsChanged$ :
+                empty();
+        }), share());
+        this._creatingConfiguration$ = this._configuration$.pipe(distinctUntilChanged((c1, c2) => {
+            return c1.mode === c2.mode;
+        }, (configuration) => {
+            return {
+                createColor: configuration.createColor,
+                mode: configuration.mode,
+            };
+        }), publishReplay(1), refCount());
+        this._creatingConfiguration$
+            .subscribe((configuration) => {
+            const type = "tagmode";
+            const event = {
+                mode: configuration.mode,
+                target: this,
+                type,
+            };
+            this.fire(type, event);
+        });
+    }
+    /**
+     * Add tags to the tag set or replace tags in the tag set.
+     *
+     * @description If a tag already in the set has the same
+     * id as one of the tags added, the old tag will be removed
+     * the added tag will take its place.
+     *
+     * @param {Array<Tag>} tags - Tags to add.
+     *
+     * @example
+     * ```js
+     * tagComponent.add([tag1, tag2]);
+     * ```
+     */
+    add(tags) {
+        if (this._activated) {
+            this._navigator.stateService.currentTransform$.pipe(first())
+                .subscribe((transform) => {
+                this._tagSet.add(tags, transform);
+                const renderTags = tags
+                    .map((tag) => {
+                    return this._tagSet.get(tag.id);
+                });
+                this._tagScene.add(renderTags);
+            });
+        }
+        else {
+            this._tagSet.addDeactivated(tags);
+        }
+    }
+    /**
+     * Calculate the smallest rectangle containing all the points
+     * in the points geometry.
+     *
+     * @description The result may be different depending on if the
+     * current image is an spherical or not. If the
+     * current image is an spherical the rectangle may
+     * wrap the horizontal border of the image.
+     *
+     * @returns {Promise<Array<number>>} Promise to the rectangle
+     * on the format specified for the {@link RectGeometry} in basic
+     * coordinates.
+     */
+    calculateRect(geometry) {
+        return new Promise((resolve, reject) => {
+            this._navigator.stateService.currentTransform$.pipe(first(), map((transform) => {
+                return geometry.getRect2d(transform);
+            }))
+                .subscribe((rect) => {
+                resolve(rect);
+            }, (error) => {
+                reject(error);
+            });
+        });
+    }
+    /**
+     * Force the creation of a geometry programatically using its
+     * current vertices.
+     *
+     * @description The method only has an effect when the tag
+     * mode is either of the following modes:
+     *
+     * {@link TagMode.CreatePoints}
+     * {@link TagMode.CreatePolygon}
+     * {@link TagMode.CreateRect}
+     * {@link TagMode.CreateRectDrag}
+     *
+     * In the case of points or polygon creation, only the created
+     * vertices are used, i.e. the mouse position is disregarded.
+     *
+     * In the case of rectangle creation the position of the mouse
+     * at the time of the method call is used as one of the vertices
+     * defining the rectangle.
+     *
+     * @fires geometrycreate
+     *
+     * @example
+     * ```js
+     * tagComponent.on("geometrycreate", function(geometry) {
+     *     console.log(geometry);
+     * });
+     *
+     * tagComponent.create();
+     * ```
+     */
+    create() {
+        this._tagCreator.replayedTag$.pipe(first(), filter((tag) => {
+            return !!tag;
+        }))
+            .subscribe((tag) => {
+            tag.create();
+        });
+    }
+    /**
+     * Change the current tag mode.
+     *
+     * @description Change the tag mode to one of the create modes for creating new geometries.
+     *
+     * @param {TagMode} mode - New tag mode.
+     *
+     * @fires tagmode
+     *
+     * @example
+     * ```js
+     * tagComponent.changeMode(TagMode.CreateRect);
+     * ```
+     */
+    changeMode(mode) {
+        this.configure({ mode: mode });
+    }
+    fire(type, event) {
+        super.fire(type, event);
+    }
+    /**
+     * Returns the tag in the tag set with the specified id, or
+     * undefined if the id matches no tag.
+     *
+     * @param {string} tagId - Id of the tag.
+     *
+     * @example
+     * ```js
+     * var tag = tagComponent.get("tagId");
+     * ```
+     */
+    get(tagId) {
+        if (this._activated) {
+            const renderTag = this._tagSet.get(tagId);
+            return renderTag !== undefined ? renderTag.tag : undefined;
+        }
+        else {
+            return this._tagSet.getDeactivated(tagId);
+        }
+    }
+    /**
+     * Returns an array of all tags.
+     *
+     * @example
+     * ```js
+     * var tags = tagComponent.getAll();
+     * ```
+     */
+    getAll() {
+        if (this.activated) {
+            return this._tagSet
+                .getAll()
+                .map((renderTag) => {
+                return renderTag.tag;
+            });
+        }
+        else {
+            return this._tagSet.getAllDeactivated();
+        }
+    }
+    /**
+     * Returns an array of tag ids for tags that contain the specified point.
+     *
+     * @description The pixel point must lie inside the polygon or rectangle
+     * of an added tag for the tag id to be returned. Tag ids for
+     * tags that do not have a fill will also be returned if the point is inside
+     * the geometry of the tag. Tags with point geometries can not be retrieved.
+     *
+     * No tag ids will be returned for polygons rendered in cropped spherical or
+     * rectangles rendered in spherical.
+     *
+     * Notice that the pixelPoint argument requires x, y coordinates from pixel space.
+     *
+     * With this function, you can use the coordinates provided by mouse
+     * events to get information out of the tag component.
+     *
+     * If no tag at exist the pixel point, an empty array will be returned.
+     *
+     * @param {Array<number>} pixelPoint - Pixel coordinates on the viewer element.
+     * @returns {Promise<Array<string>>} Promise to the ids of the tags that
+     * contain the specified pixel point.
+     *
+     * @example
+     * ```js
+     * tagComponent.getTagIdsAt([100, 100])
+     *     .then((tagIds) => { console.log(tagIds); });
+     * ```
+     */
+    getTagIdsAt(pixelPoint) {
+        return new Promise((resolve, reject) => {
+            this._container.renderService.renderCamera$.pipe(first(), map((render) => {
+                const viewport = this._viewportCoords
+                    .canvasToViewport(pixelPoint[0], pixelPoint[1], this._container.container);
+                const ids = this._tagScene.intersectObjects(viewport, render.perspective);
+                return ids;
+            }))
+                .subscribe((ids) => {
+                resolve(ids);
+            }, (error) => {
+                reject(error);
+            });
+        });
+    }
+    /**
+     * Check if a tag exist in the tag set.
+     *
+     * @param {string} tagId - Id of the tag.
+     *
+     * @example
+     * ```js
+     * var tagExists = tagComponent.has("tagId");
+     * ```
+     */
+    has(tagId) {
+        return this._activated ? this._tagSet.has(tagId) : this._tagSet.hasDeactivated(tagId);
+    }
+    off(type, handler) {
+        super.off(type, handler);
+    }
+    on(type, handler) {
+        super.on(type, handler);
+    }
+    /**
+     * Remove tags with the specified ids from the tag set.
+     *
+     * @param {Array<string>} tagIds - Ids for tags to remove.
+     *
+     * @example
+     * ```js
+     * tagComponent.remove(["id-1", "id-2"]);
+     * ```
+     */
+    remove(tagIds) {
+        if (this._activated) {
+            this._tagSet.remove(tagIds);
+            this._tagScene.remove(tagIds);
+        }
+        else {
+            this._tagSet.removeDeactivated(tagIds);
+        }
+    }
+    /**
+     * Remove all tags from the tag set.
+     *
+     * @example
+     * ```js
+     * tagComponent.removeAll();
+     * ```
+     */
+    removeAll() {
+        if (this._activated) {
+            this._tagSet.removeAll();
+            this._tagScene.removeAll();
+        }
+        else {
+            this._tagSet.removeAllDeactivated();
+        }
+    }
+    _activate() {
+        this._editVertexHandler.enable();
+        const handlerGeometryCreated$ = from(Object.keys(this._createHandlers)).pipe(map((key) => {
+            return this._createHandlers[key];
+        }), filter((handler) => {
+            return !!handler;
+        }), mergeMap((handler) => {
+            return handler.geometryCreated$;
+        }), share());
+        const subs = this._subscriptions;
+        subs.push(handlerGeometryCreated$
+            .subscribe((geometry) => {
+            const type = "geometrycreate";
+            const event = {
+                geometry,
+                target: this,
+                type,
+            };
+            this.fire(type, event);
+        }));
+        subs.push(this._tagCreator.tag$.pipe(skipWhile((tag) => {
+            return tag == null;
+        }), distinctUntilChanged())
+            .subscribe((tag) => {
+            const type = tag != null ?
+                "tagcreatestart" :
+                "tagcreateend";
+            const event = {
+                target: this,
+                type,
+            };
+            this.fire(type, event);
+        }));
+        subs.push(handlerGeometryCreated$
+            .subscribe(() => {
+            this.changeMode(TagMode.Default);
+        }));
+        subs.push(this._creatingConfiguration$
+            .subscribe((configuration) => {
+            this._disableCreateHandlers();
+            const mode = TagMode[configuration.mode];
+            const handler = this._createHandlers[mode];
+            if (!!handler) {
+                handler.enable();
+            }
+        }));
+        subs.push(this._renderTags$
+            .subscribe(() => {
+            const type = "tags";
+            const event = {
+                target: this,
+                type,
+            };
+            this.fire(type, event);
+        }));
+        subs.push(this._tagCreator.tag$.pipe(switchMap((tag) => {
+            return tag != null ?
+                tag.aborted$.pipe(map(() => { return null; })) :
+                empty();
+        }))
+            .subscribe(() => { this.changeMode(TagMode.Default); }));
+        subs.push(this._tagCreator.tag$
+            .subscribe((tag) => {
+            if (this._tagScene.hasCreateTag()) {
+                this._tagScene.removeCreateTag();
+            }
+            if (tag != null) {
+                this._tagScene.addCreateTag(tag);
+            }
+        }));
+        subs.push(this._createGLObjectsChanged$
+            .subscribe((tag) => {
+            this._tagScene.updateCreateTagObjects(tag);
+        }));
+        subs.push(this._renderTagGLChanged$
+            .subscribe((tag) => {
+            this._tagScene.updateObjects(tag);
+        }));
+        subs.push(this._tagChanged$
+            .subscribe(() => {
+            this._tagScene.update();
+        }));
+        subs.push(combineLatest(this._renderTags$.pipe(startWith([]), tap(() => {
+            this._container.domRenderer.render$.next({
+                name: this._name,
+                vNode: this._tagDomRenderer.clear(),
+            });
+        })), this._container.renderService.renderCamera$, this._container.spriteService.spriteAtlas$, this._container.renderService.size$, this._tagChanged$.pipe(startWith(null)), merge(this._tagCreator.tag$, this._createGeometryChanged$).pipe(startWith(null))).pipe(map(([renderTags, rc, atlas, size, , ct]) => {
+            return {
+                name: this._name,
+                vNode: this._tagDomRenderer.render(renderTags, ct, atlas, rc.perspective, size),
+            };
+        }))
+            .subscribe(this._container.domRenderer.render$));
+        subs.push(this._navigator.stateService.currentState$.pipe(map((frame) => {
+            const tagScene = this._tagScene;
+            return {
+                name: this._name,
+                renderer: {
+                    frameId: frame.id,
+                    needsRender: tagScene.needsRender,
+                    render: tagScene.render.bind(tagScene),
+                    pass: RenderPass$1.Opaque,
+                },
+            };
+        }))
+            .subscribe(this._container.glRenderer.render$));
+        this._navigator.stateService.currentTransform$.pipe(first())
+            .subscribe((transform) => {
+            this._tagSet.activate(transform);
+            this._tagScene.add(this._tagSet.getAll());
+        });
+    }
+    _deactivate() {
+        this._editVertexHandler.disable();
+        this._disableCreateHandlers();
+        this._tagScene.clear();
+        this._tagSet.deactivate();
+        this._tagCreator.delete$.next(null);
+        this._subscriptions.unsubscribe();
+        this._container.container.classList.remove("component-tag-create");
+    }
+    _getDefaultConfiguration() {
+        return {
+            createColor: 0xFFFFFF,
+            indicatePointsCompleter: true,
+            mode: TagMode.Default,
+        };
+    }
+    _disableCreateHandlers() {
+        const createHandlers = this._createHandlers;
+        for (const key in createHandlers) {
+            if (!createHandlers.hasOwnProperty(key)) {
+                continue;
+            }
+            const handler = createHandlers[key];
+            if (!!handler) {
+                handler.disable();
+            }
+        }
+    }
+}
+/** @inheritdoc */
+TagComponent.componentName = "tag";
+
+/**
+ * @class ZoomComponent
+ *
+ * @classdesc Component rendering UI elements used for zooming.
+ *
+ * @example
+ * ```js
+ * var viewer = new Viewer({ ... });
+ *
+ * var zoomComponent = viewer.getComponent("zoom");
+ * zoomComponent.configure({ size: ComponentSize.Small });
+ * ```
+ */
+class ZoomComponent extends Component {
+    constructor(name, container, navigator) {
+        super(name, container, navigator);
+        this._viewportCoords = new ViewportCoords();
+        this._zoomDelta$ = new Subject();
+    }
+    _activate() {
+        const subs = this._subscriptions;
+        subs.push(combineLatest(this._navigator.stateService.currentState$, this._navigator.stateService.state$, this._configuration$, this._container.renderService.size$).pipe(map(([frame, state, configuration, size]) => {
+            const zoom = frame.state.zoom;
+            const zoomInIcon = virtualDom.h("div.mapillary-zoom-in-icon", []);
+            const zoomInButton = zoom >= 3 || state === State.Waiting ?
+                virtualDom.h("div.mapillary-zoom-in-button-inactive", [zoomInIcon]) :
+                virtualDom.h("div.mapillary-zoom-in-button", { onclick: () => { this._zoomDelta$.next(1); } }, [zoomInIcon]);
+            const zoomOutIcon = virtualDom.h("div.mapillary-zoom-out-icon", []);
+            const zoomOutButton = zoom <= 0 || state === State.Waiting ?
+                virtualDom.h("div.mapillary-zoom-out-button-inactive", [zoomOutIcon]) :
+                virtualDom.h("div.mapillary-zoom-out-button", { onclick: () => { this._zoomDelta$.next(-1); } }, [zoomOutIcon]);
+            const compact = configuration.size === ComponentSize.Small ||
+                configuration.size === ComponentSize.Automatic && size.width < 640 ?
+                ".mapillary-zoom-compact" : "";
+            return {
+                name: this._name,
+                vNode: virtualDom.h("div.mapillary-zoom-container" + compact, { oncontextmenu: (event) => { event.preventDefault(); } }, [zoomInButton, zoomOutButton]),
+            };
+        }))
+            .subscribe(this._container.domRenderer.render$));
+        subs.push(this._zoomDelta$.pipe(withLatestFrom(this._container.renderService.renderCamera$, this._navigator.stateService.currentTransform$))
+            .subscribe(([zoomDelta, render, transform]) => {
+            const unprojected = this._viewportCoords.unprojectFromViewport(0, 0, render.perspective);
+            const reference = transform.projectBasic(unprojected.toArray());
+            this._navigator.stateService.zoomIn(zoomDelta, reference);
+        }));
+    }
+    _deactivate() {
+        this._subscriptions.unsubscribe();
+    }
+    _getDefaultConfiguration() {
+        return { size: ComponentSize.Automatic };
+    }
+}
+ZoomComponent.componentName = "zoom";
+
+class ImageFallbackComponent extends Component {
+    constructor(name, container, navigator, dom) {
+        super(name, container, navigator);
+        this._canvasId = `${container.id}-${this._name}`;
+        this._dom = !!dom ? dom : new DOM();
+    }
+    _activate() {
+        const canvasSize$ = this._container.domRenderer.element$.pipe(map(() => {
+            return this._dom.document.getElementById(this._canvasId);
+        }), filter((canvas) => {
+            return !!canvas;
+        }), map((canvas) => {
+            const adaptableDomRenderer = canvas.parentElement;
+            const width = adaptableDomRenderer.offsetWidth;
+            const height = adaptableDomRenderer.offsetHeight;
+            return [canvas, { height: height, width: width }];
+        }), distinctUntilChanged((s1, s2) => {
+            return s1.height === s2.height && s1.width === s2.width;
+        }, ([, size]) => {
+            return size;
+        }));
+        this._subscriptions.push(combineLatest(canvasSize$, this._navigator.stateService.currentImage$)
+            .subscribe(([[canvas, size], image]) => {
+            canvas.width = size.width;
+            canvas.height = size.height;
+            canvas
+                .getContext("2d")
+                .drawImage(image.image, 0, 0, size.width, size.height);
+        }));
+        this._container.domRenderer.renderAdaptive$.next({ name: this._name, vNode: virtualDom.h(`canvas#${this._canvasId}`, []) });
+    }
+    _deactivate() {
+        this._subscriptions.unsubscribe();
+    }
+    _getDefaultConfiguration() {
+        return {};
+    }
+}
+ImageFallbackComponent.componentName = "imagefallback";
+
+/**
+ * @class NavigationFallbackComponent
+ *
+ * @classdesc Fallback navigation component for environments without WebGL support.
+ *
+ * Replaces the functionality in the Direction and Sequence components.
+ */
+class NavigationFallbackComponent extends Component {
+    /** @ignore */
+    constructor(name, container, navigator) {
+        super(name, container, navigator);
+        this._seqNames = {};
+        this._seqNames[NavigationDirection[NavigationDirection.Prev]] = "-prev";
+        this._seqNames[NavigationDirection[NavigationDirection.Next]] = "-next";
+        this._spaTopNames = {};
+        this._spaTopNames[NavigationDirection[NavigationDirection.TurnLeft]] = "-turn-left";
+        this._spaTopNames[NavigationDirection[NavigationDirection.StepLeft]] = "-left";
+        this._spaTopNames[NavigationDirection[NavigationDirection.StepForward]] = "-forward";
+        this._spaTopNames[NavigationDirection[NavigationDirection.StepRight]] = "-right";
+        this._spaTopNames[NavigationDirection[NavigationDirection.TurnRight]] = "-turn-right";
+        this._spaBottomNames = {};
+        this._spaBottomNames[NavigationDirection[NavigationDirection.TurnU]] = "-turn-around";
+        this._spaBottomNames[NavigationDirection[NavigationDirection.StepBackward]] = "-backward";
+    }
+    _activate() {
+        this._subscriptions.push(combineLatest(this._navigator.stateService.currentImage$, this._configuration$).pipe(switchMap(([image, configuration]) => {
+            const sequenceEdges$ = configuration.sequence ?
+                image.sequenceEdges$.pipe(map((status) => {
+                    return status.edges
+                        .map((edge) => {
+                        return edge.data.direction;
+                    });
+                })) :
+                of([]);
+            const spatialEdges$ = !isSpherical(image.cameraType) &&
+                configuration.spatial ?
+                image.spatialEdges$.pipe(map((status) => {
+                    return status.edges
+                        .map((edge) => {
+                        return edge.data.direction;
+                    });
+                })) :
+                of([]);
+            return combineLatest(sequenceEdges$, spatialEdges$).pipe(map(([seq, spa]) => {
+                return seq.concat(spa);
+            }));
+        }), map((edgeDirections) => {
+            const seqs = this._createArrowRow(this._seqNames, edgeDirections);
+            const spaTops = this._createArrowRow(this._spaTopNames, edgeDirections);
+            const spaBottoms = this._createArrowRow(this._spaBottomNames, edgeDirections);
+            const seqContainer = virtualDom.h(`div.mapillary-navigation-sequence`, seqs);
+            const spaTopContainer = virtualDom.h(`div.NavigationSpatialTop`, spaTops);
+            const spaBottomContainer = virtualDom.h(`div.mapillary-navigation-spatial-bottom`, spaBottoms);
+            const spaContainer = virtualDom.h(`div.mapillary-navigation-spatial`, [spaTopContainer, spaBottomContainer]);
+            return { name: this._name, vNode: virtualDom.h(`div.NavigationContainer`, [seqContainer, spaContainer]) };
+        }))
+            .subscribe(this._container.domRenderer.render$));
+    }
+    _deactivate() {
+        this._subscriptions.unsubscribe();
+    }
+    _getDefaultConfiguration() {
+        return { sequence: true, spatial: true };
+    }
+    _createArrowRow(arrowNames, edgeDirections) {
+        const arrows = [];
+        for (const arrowName in arrowNames) {
+            if (!(arrowNames.hasOwnProperty(arrowName))) {
+                continue;
+            }
+            const direction = NavigationDirection[arrowName];
+            if (edgeDirections.indexOf(direction) !== -1) {
+                arrows.push(this._createVNode(direction, arrowNames[arrowName], "visible"));
+            }
+            else {
+                arrows.push(this._createVNode(direction, arrowNames[arrowName], "hidden"));
+            }
+        }
+        return arrows;
+    }
+    _createVNode(direction, name, visibility) {
+        return virtualDom.h(`span.mapillary-navigation-button.mapillary-navigation${name}`, {
+            onclick: () => {
+                this._navigator.moveDir$(direction)
+                    .subscribe(undefined, (error) => {
+                    if (!(error instanceof CancelMapillaryError)) {
+                        console.error(error);
+                    }
+                });
+            },
+            style: {
+                visibility: visibility,
+            },
+        }, []);
+    }
+}
+NavigationFallbackComponent.componentName = "navigationfallback";
+
+/*! pako 2.0.3 https://github.com/nodeca/pako @license (MIT AND Zlib) */
+// (C) 1995-2013 Jean-loup Gailly and Mark Adler
+// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not
+//   claim that you wrote the original software. If you use this software
+//   in a product, an acknowledgment in the product documentation would be
+//   appreciated but is not required.
+// 2. Altered source versions must be plainly marked as such, and must not be
+//   misrepresented as being the original software.
+// 3. This notice may not be removed or altered from any source distribution.
+
+/* eslint-disable space-unary-ops */
+
+/* Public constants ==========================================================*/
+/* ===========================================================================*/
+
+
+//const Z_FILTERED          = 1;
+//const Z_HUFFMAN_ONLY      = 2;
+//const Z_RLE               = 3;
+const Z_FIXED               = 4;
+//const Z_DEFAULT_STRATEGY  = 0;
+
+/* Possible values of the data_type field (though see inflate()) */
+const Z_BINARY              = 0;
+const Z_TEXT                = 1;
+//const Z_ASCII             = 1; // = Z_TEXT
+const Z_UNKNOWN             = 2;
+
+/*============================================================================*/
+
+
+function zero(buf) { let len = buf.length; while (--len >= 0) { buf[len] = 0; } }
+
+// From zutil.h
+
+const STORED_BLOCK = 0;
+const STATIC_TREES = 1;
+const DYN_TREES    = 2;
+/* The three kinds of block type */
+
+const MIN_MATCH    = 3;
+const MAX_MATCH    = 258;
+/* The minimum and maximum match lengths */
+
+// From deflate.h
+/* ===========================================================================
+ * Internal compression state.
+ */
+
+const LENGTH_CODES  = 29;
+/* number of length codes, not counting the special END_BLOCK code */
+
+const LITERALS      = 256;
+/* number of literal bytes 0..255 */
+
+const L_CODES       = LITERALS + 1 + LENGTH_CODES;
+/* number of Literal or Length codes, including the END_BLOCK code */
+
+const D_CODES       = 30;
+/* number of distance codes */
+
+const BL_CODES      = 19;
+/* number of codes used to transfer the bit lengths */
+
+const HEAP_SIZE     = 2 * L_CODES + 1;
+/* maximum heap size */
+
+const MAX_BITS      = 15;
+/* All codes must not exceed MAX_BITS bits */
+
+const Buf_size      = 16;
+/* size of bit buffer in bi_buf */
+
+
+/* ===========================================================================
+ * Constants
+ */
+
+const MAX_BL_BITS = 7;
+/* Bit length codes must not exceed MAX_BL_BITS bits */
+
+const END_BLOCK   = 256;
+/* end of block literal code */
+
+const REP_3_6     = 16;
+/* repeat previous bit length 3-6 times (2 bits of repeat count) */
+
+const REPZ_3_10   = 17;
+/* repeat a zero length 3-10 times  (3 bits of repeat count) */
+
+const REPZ_11_138 = 18;
+/* repeat a zero length 11-138 times  (7 bits of repeat count) */
+
+/* eslint-disable comma-spacing,array-bracket-spacing */
+const extra_lbits =   /* extra bits for each length code */
+  new Uint8Array([0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0]);
+
+const extra_dbits =   /* extra bits for each distance code */
+  new Uint8Array([0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13]);
+
+const extra_blbits =  /* extra bits for each bit length code */
+  new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7]);
+
+const bl_order =
+  new Uint8Array([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]);
+/* eslint-enable comma-spacing,array-bracket-spacing */
+
+/* The lengths of the bit length codes are sent in order of decreasing
+ * probability, to avoid transmitting the lengths for unused bit length codes.
+ */
+
+/* ===========================================================================
+ * Local data. These are initialized only once.
+ */
+
+// We pre-fill arrays with 0 to avoid uninitialized gaps
+
+const DIST_CODE_LEN = 512; /* see definition of array dist_code below */
+
+// !!!! Use flat array instead of structure, Freq = i*2, Len = i*2+1
+const static_ltree  = new Array((L_CODES + 2) * 2);
+zero(static_ltree);
+/* The static literal tree. Since the bit lengths are imposed, there is no
+ * need for the L_CODES extra codes used during heap construction. However
+ * The codes 286 and 287 are needed to build a canonical tree (see _tr_init
+ * below).
+ */
+
+const static_dtree  = new Array(D_CODES * 2);
+zero(static_dtree);
+/* The static distance tree. (Actually a trivial tree since all codes use
+ * 5 bits.)
+ */
+
+const _dist_code    = new Array(DIST_CODE_LEN);
+zero(_dist_code);
+/* Distance codes. The first 256 values correspond to the distances
+ * 3 .. 258, the last 256 values correspond to the top 8 bits of
+ * the 15 bit distances.
+ */
+
+const _length_code  = new Array(MAX_MATCH - MIN_MATCH + 1);
+zero(_length_code);
+/* length code for each normalized match length (0 == MIN_MATCH) */
+
+const base_length   = new Array(LENGTH_CODES);
+zero(base_length);
+/* First normalized length for each code (0 = MIN_MATCH) */
+
+const base_dist     = new Array(D_CODES);
+zero(base_dist);
+/* First normalized distance for each code (0 = distance of 1) */
+
+
+function StaticTreeDesc(static_tree, extra_bits, extra_base, elems, max_length) {
+
+  this.static_tree  = static_tree;  /* static tree or NULL */
+  this.extra_bits   = extra_bits;   /* extra bits for each code or NULL */
+  this.extra_base   = extra_base;   /* base index for extra_bits */
+  this.elems        = elems;        /* max number of elements in the tree */
+  this.max_length   = max_length;   /* max bit length for the codes */
+
+  // show if `static_tree` has data or dummy - needed for monomorphic objects
+  this.has_stree    = static_tree && static_tree.length;
+}
+
+
+let static_l_desc;
+let static_d_desc;
+let static_bl_desc;
+
+
+function TreeDesc(dyn_tree, stat_desc) {
+  this.dyn_tree = dyn_tree;     /* the dynamic tree */
+  this.max_code = 0;            /* largest code with non zero frequency */
+  this.stat_desc = stat_desc;   /* the corresponding static tree */
+}
+
+
+
+const d_code = (dist) => {
+
+  return dist < 256 ? _dist_code[dist] : _dist_code[256 + (dist >>> 7)];
+};
+
+
+/* ===========================================================================
+ * Output a short LSB first on the stream.
+ * IN assertion: there is enough room in pendingBuf.
+ */
+const put_short = (s, w) => {
+//    put_byte(s, (uch)((w) & 0xff));
+//    put_byte(s, (uch)((ush)(w) >> 8));
+  s.pending_buf[s.pending++] = (w) & 0xff;
+  s.pending_buf[s.pending++] = (w >>> 8) & 0xff;
+};
+
+
+/* ===========================================================================
+ * Send a value on a given number of bits.
+ * IN assertion: length <= 16 and value fits in length bits.
+ */
+const send_bits = (s, value, length) => {
+
+  if (s.bi_valid > (Buf_size - length)) {
+    s.bi_buf |= (value << s.bi_valid) & 0xffff;
+    put_short(s, s.bi_buf);
+    s.bi_buf = value >> (Buf_size - s.bi_valid);
+    s.bi_valid += length - Buf_size;
+  } else {
+    s.bi_buf |= (value << s.bi_valid) & 0xffff;
+    s.bi_valid += length;
+  }
+};
+
+
+const send_code = (s, c, tree) => {
+
+  send_bits(s, tree[c * 2]/*.Code*/, tree[c * 2 + 1]/*.Len*/);
+};
+
+
+/* ===========================================================================
+ * Reverse the first len bits of a code, using straightforward code (a faster
+ * method would use a table)
+ * IN assertion: 1 <= len <= 15
+ */
+const bi_reverse = (code, len) => {
+
+  let res = 0;
+  do {
+    res |= code & 1;
+    code >>>= 1;
+    res <<= 1;
+  } while (--len > 0);
+  return res >>> 1;
+};
+
+
+/* ===========================================================================
+ * Flush the bit buffer, keeping at most 7 bits in it.
+ */
+const bi_flush = (s) => {
+
+  if (s.bi_valid === 16) {
+    put_short(s, s.bi_buf);
+    s.bi_buf = 0;
+    s.bi_valid = 0;
+
+  } else if (s.bi_valid >= 8) {
+    s.pending_buf[s.pending++] = s.bi_buf & 0xff;
+    s.bi_buf >>= 8;
+    s.bi_valid -= 8;
+  }
+};
+
+
+/* ===========================================================================
+ * Compute the optimal bit lengths for a tree and update the total bit length
+ * for the current block.
+ * IN assertion: the fields freq and dad are set, heap[heap_max] and
+ *    above are the tree nodes sorted by increasing frequency.
+ * OUT assertions: the field len is set to the optimal bit length, the
+ *     array bl_count contains the frequencies for each bit length.
+ *     The length opt_len is updated; static_len is also updated if stree is
+ *     not null.
+ */
+const gen_bitlen = (s, desc) =>
+//    deflate_state *s;
+//    tree_desc *desc;    /* the tree descriptor */
+{
+  const tree            = desc.dyn_tree;
+  const max_code        = desc.max_code;
+  const stree           = desc.stat_desc.static_tree;
+  const has_stree       = desc.stat_desc.has_stree;
+  const extra           = desc.stat_desc.extra_bits;
+  const base            = desc.stat_desc.extra_base;
+  const max_length      = desc.stat_desc.max_length;
+  let h;              /* heap index */
+  let n, m;           /* iterate over the tree elements */
+  let bits;           /* bit length */
+  let xbits;          /* extra bits */
+  let f;              /* frequency */
+  let overflow = 0;   /* number of elements with bit length too large */
+
+  for (bits = 0; bits <= MAX_BITS; bits++) {
+    s.bl_count[bits] = 0;
+  }
+
+  /* In a first pass, compute the optimal bit lengths (which may
+   * overflow in the case of the bit length tree).
+   */
+  tree[s.heap[s.heap_max] * 2 + 1]/*.Len*/ = 0; /* root of the heap */
+
+  for (h = s.heap_max + 1; h < HEAP_SIZE; h++) {
+    n = s.heap[h];
+    bits = tree[tree[n * 2 + 1]/*.Dad*/ * 2 + 1]/*.Len*/ + 1;
+    if (bits > max_length) {
+      bits = max_length;
+      overflow++;
+    }
+    tree[n * 2 + 1]/*.Len*/ = bits;
+    /* We overwrite tree[n].Dad which is no longer needed */
+
+    if (n > max_code) { continue; } /* not a leaf node */
+
+    s.bl_count[bits]++;
+    xbits = 0;
+    if (n >= base) {
+      xbits = extra[n - base];
+    }
+    f = tree[n * 2]/*.Freq*/;
+    s.opt_len += f * (bits + xbits);
+    if (has_stree) {
+      s.static_len += f * (stree[n * 2 + 1]/*.Len*/ + xbits);
+    }
+  }
+  if (overflow === 0) { return; }
+
+  // Trace((stderr,"\nbit length overflow\n"));
+  /* This happens for example on obj2 and pic of the Calgary corpus */
+
+  /* Find the first bit length which could increase: */
+  do {
+    bits = max_length - 1;
+    while (s.bl_count[bits] === 0) { bits--; }
+    s.bl_count[bits]--;      /* move one leaf down the tree */
+    s.bl_count[bits + 1] += 2; /* move one overflow item as its brother */
+    s.bl_count[max_length]--;
+    /* The brother of the overflow item also moves one step up,
+     * but this does not affect bl_count[max_length]
+     */
+    overflow -= 2;
+  } while (overflow > 0);
+
+  /* Now recompute all bit lengths, scanning in increasing frequency.
+   * h is still equal to HEAP_SIZE. (It is simpler to reconstruct all
+   * lengths instead of fixing only the wrong ones. This idea is taken
+   * from 'ar' written by Haruhiko Okumura.)
+   */
+  for (bits = max_length; bits !== 0; bits--) {
+    n = s.bl_count[bits];
+    while (n !== 0) {
+      m = s.heap[--h];
+      if (m > max_code) { continue; }
+      if (tree[m * 2 + 1]/*.Len*/ !== bits) {
+        // Trace((stderr,"code %d bits %d->%d\n", m, tree[m].Len, bits));
+        s.opt_len += (bits - tree[m * 2 + 1]/*.Len*/) * tree[m * 2]/*.Freq*/;
+        tree[m * 2 + 1]/*.Len*/ = bits;
+      }
+      n--;
+    }
+  }
+};
+
+
+/* ===========================================================================
+ * Generate the codes for a given tree and bit counts (which need not be
+ * optimal).
+ * IN assertion: the array bl_count contains the bit length statistics for
+ * the given tree and the field len is set for all tree elements.
+ * OUT assertion: the field code is set for all tree elements of non
+ *     zero code length.
+ */
+const gen_codes = (tree, max_code, bl_count) =>
+//    ct_data *tree;             /* the tree to decorate */
+//    int max_code;              /* largest code with non zero frequency */
+//    ushf *bl_count;            /* number of codes at each bit length */
+{
+  const next_code = new Array(MAX_BITS + 1); /* next code value for each bit length */
+  let code = 0;              /* running code value */
+  let bits;                  /* bit index */
+  let n;                     /* code index */
+
+  /* The distribution counts are first used to generate the code values
+   * without bit reversal.
+   */
+  for (bits = 1; bits <= MAX_BITS; bits++) {
+    next_code[bits] = code = (code + bl_count[bits - 1]) << 1;
+  }
+  /* Check that the bit counts in bl_count are consistent. The last code
+   * must be all ones.
+   */
+  //Assert (code + bl_count[MAX_BITS]-1 == (1<<MAX_BITS)-1,
+  //        "inconsistent bit counts");
+  //Tracev((stderr,"\ngen_codes: max_code %d ", max_code));
+
+  for (n = 0;  n <= max_code; n++) {
+    let len = tree[n * 2 + 1]/*.Len*/;
+    if (len === 0) { continue; }
+    /* Now reverse the bits */
+    tree[n * 2]/*.Code*/ = bi_reverse(next_code[len]++, len);
+
+    //Tracecv(tree != static_ltree, (stderr,"\nn %3d %c l %2d c %4x (%x) ",
+    //     n, (isgraph(n) ? n : ' '), len, tree[n].Code, next_code[len]-1));
+  }
+};
+
+
+/* ===========================================================================
+ * Initialize the various 'constant' tables.
+ */
+const tr_static_init = () => {
+
+  let n;        /* iterates over tree elements */
+  let bits;     /* bit counter */
+  let length;   /* length value */
+  let code;     /* code value */
+  let dist;     /* distance index */
+  const bl_count = new Array(MAX_BITS + 1);
+  /* number of codes at each bit length for an optimal tree */
+
+  // do check in _tr_init()
+  //if (static_init_done) return;
+
+  /* For some embedded targets, global variables are not initialized: */
+/*#ifdef NO_INIT_GLOBAL_POINTERS
+  static_l_desc.static_tree = static_ltree;
+  static_l_desc.extra_bits = extra_lbits;
+  static_d_desc.static_tree = static_dtree;
+  static_d_desc.extra_bits = extra_dbits;
+  static_bl_desc.extra_bits = extra_blbits;
+#endif*/
+
+  /* Initialize the mapping length (0..255) -> length code (0..28) */
+  length = 0;
+  for (code = 0; code < LENGTH_CODES - 1; code++) {
+    base_length[code] = length;
+    for (n = 0; n < (1 << extra_lbits[code]); n++) {
+      _length_code[length++] = code;
+    }
+  }
+  //Assert (length == 256, "tr_static_init: length != 256");
+  /* Note that the length 255 (match length 258) can be represented
+   * in two different ways: code 284 + 5 bits or code 285, so we
+   * overwrite length_code[255] to use the best encoding:
+   */
+  _length_code[length - 1] = code;
+
+  /* Initialize the mapping dist (0..32K) -> dist code (0..29) */
+  dist = 0;
+  for (code = 0; code < 16; code++) {
+    base_dist[code] = dist;
+    for (n = 0; n < (1 << extra_dbits[code]); n++) {
+      _dist_code[dist++] = code;
+    }
+  }
+  //Assert (dist == 256, "tr_static_init: dist != 256");
+  dist >>= 7; /* from now on, all distances are divided by 128 */
+  for (; code < D_CODES; code++) {
+    base_dist[code] = dist << 7;
+    for (n = 0; n < (1 << (extra_dbits[code] - 7)); n++) {
+      _dist_code[256 + dist++] = code;
+    }
+  }
+  //Assert (dist == 256, "tr_static_init: 256+dist != 512");
+
+  /* Construct the codes of the static literal tree */
+  for (bits = 0; bits <= MAX_BITS; bits++) {
+    bl_count[bits] = 0;
+  }
+
+  n = 0;
+  while (n <= 143) {
+    static_ltree[n * 2 + 1]/*.Len*/ = 8;
+    n++;
+    bl_count[8]++;
+  }
+  while (n <= 255) {
+    static_ltree[n * 2 + 1]/*.Len*/ = 9;
+    n++;
+    bl_count[9]++;
+  }
+  while (n <= 279) {
+    static_ltree[n * 2 + 1]/*.Len*/ = 7;
+    n++;
+    bl_count[7]++;
+  }
+  while (n <= 287) {
+    static_ltree[n * 2 + 1]/*.Len*/ = 8;
+    n++;
+    bl_count[8]++;
+  }
+  /* Codes 286 and 287 do not exist, but we must include them in the
+   * tree construction to get a canonical Huffman tree (longest code
+   * all ones)
+   */
+  gen_codes(static_ltree, L_CODES + 1, bl_count);
+
+  /* The static distance tree is trivial: */
+  for (n = 0; n < D_CODES; n++) {
+    static_dtree[n * 2 + 1]/*.Len*/ = 5;
+    static_dtree[n * 2]/*.Code*/ = bi_reverse(n, 5);
+  }
+
+  // Now data ready and we can init static trees
+  static_l_desc = new StaticTreeDesc(static_ltree, extra_lbits, LITERALS + 1, L_CODES, MAX_BITS);
+  static_d_desc = new StaticTreeDesc(static_dtree, extra_dbits, 0,          D_CODES, MAX_BITS);
+  static_bl_desc = new StaticTreeDesc(new Array(0), extra_blbits, 0,         BL_CODES, MAX_BL_BITS);
+
+  //static_init_done = true;
+};
+
+
+/* ===========================================================================
+ * Initialize a new block.
+ */
+const init_block = (s) => {
+
+  let n; /* iterates over tree elements */
+
+  /* Initialize the trees. */
+  for (n = 0; n < L_CODES;  n++) { s.dyn_ltree[n * 2]/*.Freq*/ = 0; }
+  for (n = 0; n < D_CODES;  n++) { s.dyn_dtree[n * 2]/*.Freq*/ = 0; }
+  for (n = 0; n < BL_CODES; n++) { s.bl_tree[n * 2]/*.Freq*/ = 0; }
+
+  s.dyn_ltree[END_BLOCK * 2]/*.Freq*/ = 1;
+  s.opt_len = s.static_len = 0;
+  s.last_lit = s.matches = 0;
+};
+
+
+/* ===========================================================================
+ * Flush the bit buffer and align the output on a byte boundary
+ */
+const bi_windup = (s) =>
+{
+  if (s.bi_valid > 8) {
+    put_short(s, s.bi_buf);
+  } else if (s.bi_valid > 0) {
+    //put_byte(s, (Byte)s->bi_buf);
+    s.pending_buf[s.pending++] = s.bi_buf;
+  }
+  s.bi_buf = 0;
+  s.bi_valid = 0;
+};
+
+/* ===========================================================================
+ * Copy a stored block, storing first the length and its
+ * one's complement if requested.
+ */
+const copy_block = (s, buf, len, header) =>
+//DeflateState *s;
+//charf    *buf;    /* the input data */
+//unsigned len;     /* its length */
+//int      header;  /* true if block header must be written */
+{
+  bi_windup(s);        /* align on byte boundary */
+
+  if (header) {
+    put_short(s, len);
+    put_short(s, ~len);
+  }
+//  while (len--) {
+//    put_byte(s, *buf++);
+//  }
+  s.pending_buf.set(s.window.subarray(buf, buf + len), s.pending);
+  s.pending += len;
+};
+
+/* ===========================================================================
+ * Compares to subtrees, using the tree depth as tie breaker when
+ * the subtrees have equal frequency. This minimizes the worst case length.
+ */
+const smaller = (tree, n, m, depth) => {
+
+  const _n2 = n * 2;
+  const _m2 = m * 2;
+  return (tree[_n2]/*.Freq*/ < tree[_m2]/*.Freq*/ ||
+         (tree[_n2]/*.Freq*/ === tree[_m2]/*.Freq*/ && depth[n] <= depth[m]));
+};
+
+/* ===========================================================================
+ * Restore the heap property by moving down the tree starting at node k,
+ * exchanging a node with the smallest of its two sons if necessary, stopping
+ * when the heap property is re-established (each father smaller than its
+ * two sons).
+ */
+const pqdownheap = (s, tree, k) =>
+//    deflate_state *s;
+//    ct_data *tree;  /* the tree to restore */
+//    int k;               /* node to move down */
+{
+  const v = s.heap[k];
+  let j = k << 1;  /* left son of k */
+  while (j <= s.heap_len) {
+    /* Set j to the smallest of the two sons: */
+    if (j < s.heap_len &&
+      smaller(tree, s.heap[j + 1], s.heap[j], s.depth)) {
+      j++;
+    }
+    /* Exit if v is smaller than both sons */
+    if (smaller(tree, v, s.heap[j], s.depth)) { break; }
+
+    /* Exchange v with the smallest son */
+    s.heap[k] = s.heap[j];
+    k = j;
+
+    /* And continue down the tree, setting j to the left son of k */
+    j <<= 1;
+  }
+  s.heap[k] = v;
+};
+
+
+// inlined manually
+// const SMALLEST = 1;
+
+/* ===========================================================================
+ * Send the block data compressed using the given Huffman trees
+ */
+const compress_block = (s, ltree, dtree) =>
+//    deflate_state *s;
+//    const ct_data *ltree; /* literal tree */
+//    const ct_data *dtree; /* distance tree */
+{
+  let dist;           /* distance of matched string */
+  let lc;             /* match length or unmatched char (if dist == 0) */
+  let lx = 0;         /* running index in l_buf */
+  let code;           /* the code to send */
+  let extra;          /* number of extra bits to send */
+
+  if (s.last_lit !== 0) {
+    do {
+      dist = (s.pending_buf[s.d_buf + lx * 2] << 8) | (s.pending_buf[s.d_buf + lx * 2 + 1]);
+      lc = s.pending_buf[s.l_buf + lx];
+      lx++;
+
+      if (dist === 0) {
+        send_code(s, lc, ltree); /* send a literal byte */
+        //Tracecv(isgraph(lc), (stderr," '%c' ", lc));
+      } else {
+        /* Here, lc is the match length - MIN_MATCH */
+        code = _length_code[lc];
+        send_code(s, code + LITERALS + 1, ltree); /* send the length code */
+        extra = extra_lbits[code];
+        if (extra !== 0) {
+          lc -= base_length[code];
+          send_bits(s, lc, extra);       /* send the extra length bits */
+        }
+        dist--; /* dist is now the match distance - 1 */
+        code = d_code(dist);
+        //Assert (code < D_CODES, "bad d_code");
+
+        send_code(s, code, dtree);       /* send the distance code */
+        extra = extra_dbits[code];
+        if (extra !== 0) {
+          dist -= base_dist[code];
+          send_bits(s, dist, extra);   /* send the extra distance bits */
+        }
+      } /* literal or match pair ? */
+
+      /* Check that the overlay between pending_buf and d_buf+l_buf is ok: */
+      //Assert((uInt)(s->pending) < s->lit_bufsize + 2*lx,
+      //       "pendingBuf overflow");
+
+    } while (lx < s.last_lit);
+  }
+
+  send_code(s, END_BLOCK, ltree);
+};
+
+
+/* ===========================================================================
+ * Construct one Huffman tree and assigns the code bit strings and lengths.
+ * Update the total bit length for the current block.
+ * IN assertion: the field freq is set for all tree elements.
+ * OUT assertions: the fields len and code are set to the optimal bit length
+ *     and corresponding code. The length opt_len is updated; static_len is
+ *     also updated if stree is not null. The field max_code is set.
+ */
+const build_tree = (s, desc) =>
+//    deflate_state *s;
+//    tree_desc *desc; /* the tree descriptor */
+{
+  const tree     = desc.dyn_tree;
+  const stree    = desc.stat_desc.static_tree;
+  const has_stree = desc.stat_desc.has_stree;
+  const elems    = desc.stat_desc.elems;
+  let n, m;          /* iterate over heap elements */
+  let max_code = -1; /* largest code with non zero frequency */
+  let node;          /* new node being created */
+
+  /* Construct the initial heap, with least frequent element in
+   * heap[SMALLEST]. The sons of heap[n] are heap[2*n] and heap[2*n+1].
+   * heap[0] is not used.
+   */
+  s.heap_len = 0;
+  s.heap_max = HEAP_SIZE;
+
+  for (n = 0; n < elems; n++) {
+    if (tree[n * 2]/*.Freq*/ !== 0) {
+      s.heap[++s.heap_len] = max_code = n;
+      s.depth[n] = 0;
+
+    } else {
+      tree[n * 2 + 1]/*.Len*/ = 0;
+    }
+  }
+
+  /* The pkzip format requires that at least one distance code exists,
+   * and that at least one bit should be sent even if there is only one
+   * possible code. So to avoid special checks later on we force at least
+   * two codes of non zero frequency.
+   */
+  while (s.heap_len < 2) {
+    node = s.heap[++s.heap_len] = (max_code < 2 ? ++max_code : 0);
+    tree[node * 2]/*.Freq*/ = 1;
+    s.depth[node] = 0;
+    s.opt_len--;
+
+    if (has_stree) {
+      s.static_len -= stree[node * 2 + 1]/*.Len*/;
+    }
+    /* node is 0 or 1 so it does not have extra bits */
+  }
+  desc.max_code = max_code;
+
+  /* The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree,
+   * establish sub-heaps of increasing lengths:
+   */
+  for (n = (s.heap_len >> 1/*int /2*/); n >= 1; n--) { pqdownheap(s, tree, n); }
+
+  /* Construct the Huffman tree by repeatedly combining the least two
+   * frequent nodes.
+   */
+  node = elems;              /* next internal node of the tree */
+  do {
+    //pqremove(s, tree, n);  /* n = node of least frequency */
+    /*** pqremove ***/
+    n = s.heap[1/*SMALLEST*/];
+    s.heap[1/*SMALLEST*/] = s.heap[s.heap_len--];
+    pqdownheap(s, tree, 1/*SMALLEST*/);
+    /***/
+
+    m = s.heap[1/*SMALLEST*/]; /* m = node of next least frequency */
+
+    s.heap[--s.heap_max] = n; /* keep the nodes sorted by frequency */
+    s.heap[--s.heap_max] = m;
+
+    /* Create a new node father of n and m */
+    tree[node * 2]/*.Freq*/ = tree[n * 2]/*.Freq*/ + tree[m * 2]/*.Freq*/;
+    s.depth[node] = (s.depth[n] >= s.depth[m] ? s.depth[n] : s.depth[m]) + 1;
+    tree[n * 2 + 1]/*.Dad*/ = tree[m * 2 + 1]/*.Dad*/ = node;
+
+    /* and insert the new node in the heap */
+    s.heap[1/*SMALLEST*/] = node++;
+    pqdownheap(s, tree, 1/*SMALLEST*/);
+
+  } while (s.heap_len >= 2);
+
+  s.heap[--s.heap_max] = s.heap[1/*SMALLEST*/];
+
+  /* At this point, the fields freq and dad are set. We can now
+   * generate the bit lengths.
+   */
+  gen_bitlen(s, desc);
+
+  /* The field len is now set, we can generate the bit codes */
+  gen_codes(tree, max_code, s.bl_count);
+};
+
+
+/* ===========================================================================
+ * Scan a literal or distance tree to determine the frequencies of the codes
+ * in the bit length tree.
+ */
+const scan_tree = (s, tree, max_code) =>
+//    deflate_state *s;
+//    ct_data *tree;   /* the tree to be scanned */
+//    int max_code;    /* and its largest code of non zero frequency */
+{
+  let n;                     /* iterates over all tree elements */
+  let prevlen = -1;          /* last emitted length */
+  let curlen;                /* length of current code */
+
+  let nextlen = tree[0 * 2 + 1]/*.Len*/; /* length of next code */
+
+  let count = 0;             /* repeat count of the current code */
+  let max_count = 7;         /* max repeat count */
+  let min_count = 4;         /* min repeat count */
+
+  if (nextlen === 0) {
+    max_count = 138;
+    min_count = 3;
+  }
+  tree[(max_code + 1) * 2 + 1]/*.Len*/ = 0xffff; /* guard */
+
+  for (n = 0; n <= max_code; n++) {
+    curlen = nextlen;
+    nextlen = tree[(n + 1) * 2 + 1]/*.Len*/;
+
+    if (++count < max_count && curlen === nextlen) {
+      continue;
+
+    } else if (count < min_count) {
+      s.bl_tree[curlen * 2]/*.Freq*/ += count;
+
+    } else if (curlen !== 0) {
+
+      if (curlen !== prevlen) { s.bl_tree[curlen * 2]/*.Freq*/++; }
+      s.bl_tree[REP_3_6 * 2]/*.Freq*/++;
+
+    } else if (count <= 10) {
+      s.bl_tree[REPZ_3_10 * 2]/*.Freq*/++;
+
+    } else {
+      s.bl_tree[REPZ_11_138 * 2]/*.Freq*/++;
+    }
+
+    count = 0;
+    prevlen = curlen;
+
+    if (nextlen === 0) {
+      max_count = 138;
+      min_count = 3;
+
+    } else if (curlen === nextlen) {
+      max_count = 6;
+      min_count = 3;
+
+    } else {
+      max_count = 7;
+      min_count = 4;
+    }
+  }
+};
+
+
+/* ===========================================================================
+ * Send a literal or distance tree in compressed form, using the codes in
+ * bl_tree.
+ */
+const send_tree = (s, tree, max_code) =>
+//    deflate_state *s;
+//    ct_data *tree; /* the tree to be scanned */
+//    int max_code;       /* and its largest code of non zero frequency */
+{
+  let n;                     /* iterates over all tree elements */
+  let prevlen = -1;          /* last emitted length */
+  let curlen;                /* length of current code */
+
+  let nextlen = tree[0 * 2 + 1]/*.Len*/; /* length of next code */
+
+  let count = 0;             /* repeat count of the current code */
+  let max_count = 7;         /* max repeat count */
+  let min_count = 4;         /* min repeat count */
+
+  /* tree[max_code+1].Len = -1; */  /* guard already set */
+  if (nextlen === 0) {
+    max_count = 138;
+    min_count = 3;
+  }
+
+  for (n = 0; n <= max_code; n++) {
+    curlen = nextlen;
+    nextlen = tree[(n + 1) * 2 + 1]/*.Len*/;
+
+    if (++count < max_count && curlen === nextlen) {
+      continue;
+
+    } else if (count < min_count) {
+      do { send_code(s, curlen, s.bl_tree); } while (--count !== 0);
+
+    } else if (curlen !== 0) {
+      if (curlen !== prevlen) {
+        send_code(s, curlen, s.bl_tree);
+        count--;
+      }
+      //Assert(count >= 3 && count <= 6, " 3_6?");
+      send_code(s, REP_3_6, s.bl_tree);
+      send_bits(s, count - 3, 2);
+
+    } else if (count <= 10) {
+      send_code(s, REPZ_3_10, s.bl_tree);
+      send_bits(s, count - 3, 3);
+
+    } else {
+      send_code(s, REPZ_11_138, s.bl_tree);
+      send_bits(s, count - 11, 7);
+    }
+
+    count = 0;
+    prevlen = curlen;
+    if (nextlen === 0) {
+      max_count = 138;
+      min_count = 3;
+
+    } else if (curlen === nextlen) {
+      max_count = 6;
+      min_count = 3;
+
+    } else {
+      max_count = 7;
+      min_count = 4;
+    }
+  }
+};
+
+
+/* ===========================================================================
+ * Construct the Huffman tree for the bit lengths and return the index in
+ * bl_order of the last bit length code to send.
+ */
+const build_bl_tree = (s) => {
+
+  let max_blindex;  /* index of last bit length code of non zero freq */
+
+  /* Determine the bit length frequencies for literal and distance trees */
+  scan_tree(s, s.dyn_ltree, s.l_desc.max_code);
+  scan_tree(s, s.dyn_dtree, s.d_desc.max_code);
+
+  /* Build the bit length tree: */
+  build_tree(s, s.bl_desc);
+  /* opt_len now includes the length of the tree representations, except
+   * the lengths of the bit lengths codes and the 5+5+4 bits for the counts.
+   */
+
+  /* Determine the number of bit length codes to send. The pkzip format
+   * requires that at least 4 bit length codes be sent. (appnote.txt says
+   * 3 but the actual value used is 4.)
+   */
+  for (max_blindex = BL_CODES - 1; max_blindex >= 3; max_blindex--) {
+    if (s.bl_tree[bl_order[max_blindex] * 2 + 1]/*.Len*/ !== 0) {
+      break;
+    }
+  }
+  /* Update opt_len to include the bit length tree and counts */
+  s.opt_len += 3 * (max_blindex + 1) + 5 + 5 + 4;
+  //Tracev((stderr, "\ndyn trees: dyn %ld, stat %ld",
+  //        s->opt_len, s->static_len));
+
+  return max_blindex;
+};
+
+
+/* ===========================================================================
+ * Send the header for a block using dynamic Huffman trees: the counts, the
+ * lengths of the bit length codes, the literal tree and the distance tree.
+ * IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4.
+ */
+const send_all_trees = (s, lcodes, dcodes, blcodes) =>
+//    deflate_state *s;
+//    int lcodes, dcodes, blcodes; /* number of codes for each tree */
+{
+  let rank;                    /* index in bl_order */
+
+  //Assert (lcodes >= 257 && dcodes >= 1 && blcodes >= 4, "not enough codes");
+  //Assert (lcodes <= L_CODES && dcodes <= D_CODES && blcodes <= BL_CODES,
+  //        "too many codes");
+  //Tracev((stderr, "\nbl counts: "));
+  send_bits(s, lcodes - 257, 5); /* not +255 as stated in appnote.txt */
+  send_bits(s, dcodes - 1,   5);
+  send_bits(s, blcodes - 4,  4); /* not -3 as stated in appnote.txt */
+  for (rank = 0; rank < blcodes; rank++) {
+    //Tracev((stderr, "\nbl code %2d ", bl_order[rank]));
+    send_bits(s, s.bl_tree[bl_order[rank] * 2 + 1]/*.Len*/, 3);
+  }
+  //Tracev((stderr, "\nbl tree: sent %ld", s->bits_sent));
+
+  send_tree(s, s.dyn_ltree, lcodes - 1); /* literal tree */
+  //Tracev((stderr, "\nlit tree: sent %ld", s->bits_sent));
+
+  send_tree(s, s.dyn_dtree, dcodes - 1); /* distance tree */
+  //Tracev((stderr, "\ndist tree: sent %ld", s->bits_sent));
+};
+
+
+/* ===========================================================================
+ * Check if the data type is TEXT or BINARY, using the following algorithm:
+ * - TEXT if the two conditions below are satisfied:
+ *    a) There are no non-portable control characters belonging to the
+ *       "black list" (0..6, 14..25, 28..31).
+ *    b) There is at least one printable character belonging to the
+ *       "white list" (9 {TAB}, 10 {LF}, 13 {CR}, 32..255).
+ * - BINARY otherwise.
+ * - The following partially-portable control characters form a
+ *   "gray list" that is ignored in this detection algorithm:
+ *   (7 {BEL}, 8 {BS}, 11 {VT}, 12 {FF}, 26 {SUB}, 27 {ESC}).
+ * IN assertion: the fields Freq of dyn_ltree are set.
+ */
+const detect_data_type = (s) => {
+  /* black_mask is the bit mask of black-listed bytes
+   * set bits 0..6, 14..25, and 28..31
+   * 0xf3ffc07f = binary 11110011111111111100000001111111
+   */
+  let black_mask = 0xf3ffc07f;
+  let n;
+
+  /* Check for non-textual ("black-listed") bytes. */
+  for (n = 0; n <= 31; n++, black_mask >>>= 1) {
+    if ((black_mask & 1) && (s.dyn_ltree[n * 2]/*.Freq*/ !== 0)) {
+      return Z_BINARY;
+    }
+  }
+
+  /* Check for textual ("white-listed") bytes. */
+  if (s.dyn_ltree[9 * 2]/*.Freq*/ !== 0 || s.dyn_ltree[10 * 2]/*.Freq*/ !== 0 ||
+      s.dyn_ltree[13 * 2]/*.Freq*/ !== 0) {
+    return Z_TEXT;
+  }
+  for (n = 32; n < LITERALS; n++) {
+    if (s.dyn_ltree[n * 2]/*.Freq*/ !== 0) {
+      return Z_TEXT;
+    }
+  }
+
+  /* There are no "black-listed" or "white-listed" bytes:
+   * this stream either is empty or has tolerated ("gray-listed") bytes only.
+   */
+  return Z_BINARY;
+};
+
+
+let static_init_done = false;
+
+/* ===========================================================================
+ * Initialize the tree data structures for a new zlib stream.
+ */
+const _tr_init = (s) =>
+{
+
+  if (!static_init_done) {
+    tr_static_init();
+    static_init_done = true;
+  }
+
+  s.l_desc  = new TreeDesc(s.dyn_ltree, static_l_desc);
+  s.d_desc  = new TreeDesc(s.dyn_dtree, static_d_desc);
+  s.bl_desc = new TreeDesc(s.bl_tree, static_bl_desc);
+
+  s.bi_buf = 0;
+  s.bi_valid = 0;
+
+  /* Initialize the first block of the first file: */
+  init_block(s);
+};
+
+
+/* ===========================================================================
+ * Send a stored block
+ */
+const _tr_stored_block = (s, buf, stored_len, last) =>
+//DeflateState *s;
+//charf *buf;       /* input block */
+//ulg stored_len;   /* length of input block */
+//int last;         /* one if this is the last block for a file */
+{
+  send_bits(s, (STORED_BLOCK << 1) + (last ? 1 : 0), 3);    /* send block type */
+  copy_block(s, buf, stored_len, true); /* with header */
+};
+
+
+/* ===========================================================================
+ * Send one empty static block to give enough lookahead for inflate.
+ * This takes 10 bits, of which 7 may remain in the bit buffer.
+ */
+const _tr_align = (s) => {
+  send_bits(s, STATIC_TREES << 1, 3);
+  send_code(s, END_BLOCK, static_ltree);
+  bi_flush(s);
+};
+
+
+/* ===========================================================================
+ * Determine the best encoding for the current block: dynamic trees, static
+ * trees or store, and output the encoded block to the zip file.
+ */
+const _tr_flush_block = (s, buf, stored_len, last) =>
+//DeflateState *s;
+//charf *buf;       /* input block, or NULL if too old */
+//ulg stored_len;   /* length of input block */
+//int last;         /* one if this is the last block for a file */
+{
+  let opt_lenb, static_lenb;  /* opt_len and static_len in bytes */
+  let max_blindex = 0;        /* index of last bit length code of non zero freq */
+
+  /* Build the Huffman trees unless a stored block is forced */
+  if (s.level > 0) {
+
+    /* Check if the file is binary or text */
+    if (s.strm.data_type === Z_UNKNOWN) {
+      s.strm.data_type = detect_data_type(s);
+    }
+
+    /* Construct the literal and distance trees */
+    build_tree(s, s.l_desc);
+    // Tracev((stderr, "\nlit data: dyn %ld, stat %ld", s->opt_len,
+    //        s->static_len));
+
+    build_tree(s, s.d_desc);
+    // Tracev((stderr, "\ndist data: dyn %ld, stat %ld", s->opt_len,
+    //        s->static_len));
+    /* At this point, opt_len and static_len are the total bit lengths of
+     * the compressed block data, excluding the tree representations.
+     */
+
+    /* Build the bit length tree for the above two trees, and get the index
+     * in bl_order of the last bit length code to send.
+     */
+    max_blindex = build_bl_tree(s);
+
+    /* Determine the best encoding. Compute the block lengths in bytes. */
+    opt_lenb = (s.opt_len + 3 + 7) >>> 3;
+    static_lenb = (s.static_len + 3 + 7) >>> 3;
+
+    // Tracev((stderr, "\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u ",
+    //        opt_lenb, s->opt_len, static_lenb, s->static_len, stored_len,
+    //        s->last_lit));
+
+    if (static_lenb <= opt_lenb) { opt_lenb = static_lenb; }
+
+  } else {
+    // Assert(buf != (char*)0, "lost buf");
+    opt_lenb = static_lenb = stored_len + 5; /* force a stored block */
+  }
+
+  if ((stored_len + 4 <= opt_lenb) && (buf !== -1)) {
+    /* 4: two words for the lengths */
+
+    /* The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE.
+     * Otherwise we can't have processed more than WSIZE input bytes since
+     * the last block flush, because compression would have been
+     * successful. If LIT_BUFSIZE <= WSIZE, it is never too late to
+     * transform a block into a stored block.
+     */
+    _tr_stored_block(s, buf, stored_len, last);
+
+  } else if (s.strategy === Z_FIXED || static_lenb === opt_lenb) {
+
+    send_bits(s, (STATIC_TREES << 1) + (last ? 1 : 0), 3);
+    compress_block(s, static_ltree, static_dtree);
+
+  } else {
+    send_bits(s, (DYN_TREES << 1) + (last ? 1 : 0), 3);
+    send_all_trees(s, s.l_desc.max_code + 1, s.d_desc.max_code + 1, max_blindex + 1);
+    compress_block(s, s.dyn_ltree, s.dyn_dtree);
+  }
+  // Assert (s->compressed_len == s->bits_sent, "bad compressed size");
+  /* The above check is made mod 2^32, for files larger than 512 MB
+   * and uLong implemented on 32 bits.
+   */
+  init_block(s);
+
+  if (last) {
+    bi_windup(s);
+  }
+  // Tracev((stderr,"\ncomprlen %lu(%lu) ", s->compressed_len>>3,
+  //       s->compressed_len-7*last));
+};
+
+/* ===========================================================================
+ * Save the match info and tally the frequency counts. Return true if
+ * the current block must be flushed.
+ */
+const _tr_tally = (s, dist, lc) =>
+//    deflate_state *s;
+//    unsigned dist;  /* distance of matched string */
+//    unsigned lc;    /* match length-MIN_MATCH or unmatched char (if dist==0) */
+{
+  //let out_length, in_length, dcode;
+
+  s.pending_buf[s.d_buf + s.last_lit * 2]     = (dist >>> 8) & 0xff;
+  s.pending_buf[s.d_buf + s.last_lit * 2 + 1] = dist & 0xff;
+
+  s.pending_buf[s.l_buf + s.last_lit] = lc & 0xff;
+  s.last_lit++;
+
+  if (dist === 0) {
+    /* lc is the unmatched char */
+    s.dyn_ltree[lc * 2]/*.Freq*/++;
+  } else {
+    s.matches++;
+    /* Here, lc is the match length - MIN_MATCH */
+    dist--;             /* dist = match distance - 1 */
+    //Assert((ush)dist < (ush)MAX_DIST(s) &&
+    //       (ush)lc <= (ush)(MAX_MATCH-MIN_MATCH) &&
+    //       (ush)d_code(dist) < (ush)D_CODES,  "_tr_tally: bad match");
+
+    s.dyn_ltree[(_length_code[lc] + LITERALS + 1) * 2]/*.Freq*/++;
+    s.dyn_dtree[d_code(dist) * 2]/*.Freq*/++;
+  }
+
+// (!) This block is disabled in zlib defaults,
+// don't enable it for binary compatibility
+
+//#ifdef TRUNCATE_BLOCK
+//  /* Try to guess if it is profitable to stop the current block here */
+//  if ((s.last_lit & 0x1fff) === 0 && s.level > 2) {
+//    /* Compute an upper bound for the compressed length */
+//    out_length = s.last_lit*8;
+//    in_length = s.strstart - s.block_start;
+//
+//    for (dcode = 0; dcode < D_CODES; dcode++) {
+//      out_length += s.dyn_dtree[dcode*2]/*.Freq*/ * (5 + extra_dbits[dcode]);
+//    }
+//    out_length >>>= 3;
+//    //Tracev((stderr,"\nlast_lit %u, in %ld, out ~%ld(%ld%%) ",
+//    //       s->last_lit, in_length, out_length,
+//    //       100L - out_length*100L/in_length));
+//    if (s.matches < (s.last_lit>>1)/*int /2*/ && out_length < (in_length>>1)/*int /2*/) {
+//      return true;
+//    }
+//  }
+//#endif
+
+  return (s.last_lit === s.lit_bufsize - 1);
+  /* We avoid equality with lit_bufsize because of wraparound at 64K
+   * on 16 bit machines and because stored blocks are restricted to
+   * 64K-1 bytes.
+   */
+};
+
+var _tr_init_1  = _tr_init;
+var _tr_stored_block_1 = _tr_stored_block;
+var _tr_flush_block_1  = _tr_flush_block;
+var _tr_tally_1 = _tr_tally;
+var _tr_align_1 = _tr_align;
+
+var trees = {
+       _tr_init: _tr_init_1,
+       _tr_stored_block: _tr_stored_block_1,
+       _tr_flush_block: _tr_flush_block_1,
+       _tr_tally: _tr_tally_1,
+       _tr_align: _tr_align_1
+};
+
+// Note: adler32 takes 12% for level 0 and 2% for level 6.
+// It isn't worth it to make additional optimizations as in original.
+// Small size is preferable.
+
+// (C) 1995-2013 Jean-loup Gailly and Mark Adler
+// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not
+//   claim that you wrote the original software. If you use this software
+//   in a product, an acknowledgment in the product documentation would be
+//   appreciated but is not required.
+// 2. Altered source versions must be plainly marked as such, and must not be
+//   misrepresented as being the original software.
+// 3. This notice may not be removed or altered from any source distribution.
+
+const adler32 = (adler, buf, len, pos) => {
+  let s1 = (adler & 0xffff) |0,
+      s2 = ((adler >>> 16) & 0xffff) |0,
+      n = 0;
+
+  while (len !== 0) {
+    // Set limit ~ twice less than 5552, to keep
+    // s2 in 31-bits, because we force signed ints.
+    // in other case %= will fail.
+    n = len > 2000 ? 2000 : len;
+    len -= n;
+
+    do {
+      s1 = (s1 + buf[pos++]) |0;
+      s2 = (s2 + s1) |0;
+    } while (--n);
+
+    s1 %= 65521;
+    s2 %= 65521;
+  }
+
+  return (s1 | (s2 << 16)) |0;
+};
+
+
+var adler32_1 = adler32;
+
+// Note: we can't get significant speed boost here.
+// So write code to minimize size - no pregenerated tables
+// and array tools dependencies.
+
+// (C) 1995-2013 Jean-loup Gailly and Mark Adler
+// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not
+//   claim that you wrote the original software. If you use this software
+//   in a product, an acknowledgment in the product documentation would be
+//   appreciated but is not required.
+// 2. Altered source versions must be plainly marked as such, and must not be
+//   misrepresented as being the original software.
+// 3. This notice may not be removed or altered from any source distribution.
+
+// Use ordinary array, since untyped makes no boost here
+const makeTable = () => {
+  let c, table = [];
+
+  for (var n = 0; n < 256; n++) {
+    c = n;
+    for (var k = 0; k < 8; k++) {
+      c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1));
+    }
+    table[n] = c;
+  }
+
+  return table;
+};
+
+// Create table on load. Just 255 signed longs. Not a problem.
+const crcTable = new Uint32Array(makeTable());
+
+
+const crc32 = (crc, buf, len, pos) => {
+  const t = crcTable;
+  const end = pos + len;
+
+  crc ^= -1;
+
+  for (let i = pos; i < end; i++) {
+    crc = (crc >>> 8) ^ t[(crc ^ buf[i]) & 0xFF];
+  }
+
+  return (crc ^ (-1)); // >>> 0;
+};
+
+
+var crc32_1 = crc32;
+
+// (C) 1995-2013 Jean-loup Gailly and Mark Adler
+// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not
+//   claim that you wrote the original software. If you use this software
+//   in a product, an acknowledgment in the product documentation would be
+//   appreciated but is not required.
+// 2. Altered source versions must be plainly marked as such, and must not be
+//   misrepresented as being the original software.
+// 3. This notice may not be removed or altered from any source distribution.
+
+var messages = {
+  2:      'need dictionary',     /* Z_NEED_DICT       2  */
+  1:      'stream end',          /* Z_STREAM_END      1  */
+  0:      '',                    /* Z_OK              0  */
+  '-1':   'file error',          /* Z_ERRNO         (-1) */
+  '-2':   'stream error',        /* Z_STREAM_ERROR  (-2) */
+  '-3':   'data error',          /* Z_DATA_ERROR    (-3) */
+  '-4':   'insufficient memory', /* Z_MEM_ERROR     (-4) */
+  '-5':   'buffer error',        /* Z_BUF_ERROR     (-5) */
+  '-6':   'incompatible version' /* Z_VERSION_ERROR (-6) */
+};
+
+// (C) 1995-2013 Jean-loup Gailly and Mark Adler
+// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not
+//   claim that you wrote the original software. If you use this software
+//   in a product, an acknowledgment in the product documentation would be
+//   appreciated but is not required.
+// 2. Altered source versions must be plainly marked as such, and must not be
+//   misrepresented as being the original software.
+// 3. This notice may not be removed or altered from any source distribution.
+
+var constants = {
+
+  /* Allowed flush values; see deflate() and inflate() below for details */
+  Z_NO_FLUSH:         0,
+  Z_PARTIAL_FLUSH:    1,
+  Z_SYNC_FLUSH:       2,
+  Z_FULL_FLUSH:       3,
+  Z_FINISH:           4,
+  Z_BLOCK:            5,
+  Z_TREES:            6,
+
+  /* Return codes for the compression/decompression functions. Negative values
+  * are errors, positive values are used for special but normal events.
+  */
+  Z_OK:               0,
+  Z_STREAM_END:       1,
+  Z_NEED_DICT:        2,
+  Z_ERRNO:           -1,
+  Z_STREAM_ERROR:    -2,
+  Z_DATA_ERROR:      -3,
+  Z_MEM_ERROR:       -4,
+  Z_BUF_ERROR:       -5,
+  //Z_VERSION_ERROR: -6,
+
+  /* compression levels */
+  Z_NO_COMPRESSION:         0,
+  Z_BEST_SPEED:             1,
+  Z_BEST_COMPRESSION:       9,
+  Z_DEFAULT_COMPRESSION:   -1,
+
+
+  Z_FILTERED:               1,
+  Z_HUFFMAN_ONLY:           2,
+  Z_RLE:                    3,
+  Z_FIXED:                  4,
+  Z_DEFAULT_STRATEGY:       0,
+
+  /* Possible values of the data_type field (though see inflate()) */
+  Z_BINARY:                 0,
+  Z_TEXT:                   1,
+  //Z_ASCII:                1, // = Z_TEXT (deprecated)
+  Z_UNKNOWN:                2,
+
+  /* The deflate compression method */
+  Z_DEFLATED:               8
+  //Z_NULL:                 null // Use -1 or null inline, depending on var type
+};
+
+// (C) 1995-2013 Jean-loup Gailly and Mark Adler
+// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not
+//   claim that you wrote the original software. If you use this software
+//   in a product, an acknowledgment in the product documentation would be
+//   appreciated but is not required.
+// 2. Altered source versions must be plainly marked as such, and must not be
+//   misrepresented as being the original software.
+// 3. This notice may not be removed or altered from any source distribution.
+
+const { _tr_init: _tr_init$1, _tr_stored_block: _tr_stored_block$1, _tr_flush_block: _tr_flush_block$1, _tr_tally: _tr_tally$1, _tr_align: _tr_align$1 } = trees;
+
+
+
+
+/* Public constants ==========================================================*/
+/* ===========================================================================*/
+
+const {
+  Z_NO_FLUSH, Z_PARTIAL_FLUSH, Z_FULL_FLUSH, Z_FINISH, Z_BLOCK,
+  Z_OK, Z_STREAM_END, Z_STREAM_ERROR, Z_DATA_ERROR, Z_BUF_ERROR,
+  Z_DEFAULT_COMPRESSION,
+  Z_FILTERED, Z_HUFFMAN_ONLY, Z_RLE, Z_FIXED: Z_FIXED$1, Z_DEFAULT_STRATEGY,
+  Z_UNKNOWN: Z_UNKNOWN$1,
+  Z_DEFLATED
+} = constants;
+
+/*============================================================================*/
+
+
+const MAX_MEM_LEVEL = 9;
+/* Maximum value for memLevel in deflateInit2 */
+const MAX_WBITS = 15;
+/* 32K LZ77 window */
+const DEF_MEM_LEVEL = 8;
+
+
+const LENGTH_CODES$1  = 29;
+/* number of length codes, not counting the special END_BLOCK code */
+const LITERALS$1      = 256;
+/* number of literal bytes 0..255 */
+const L_CODES$1       = LITERALS$1 + 1 + LENGTH_CODES$1;
+/* number of Literal or Length codes, including the END_BLOCK code */
+const D_CODES$1       = 30;
+/* number of distance codes */
+const BL_CODES$1      = 19;
+/* number of codes used to transfer the bit lengths */
+const HEAP_SIZE$1     = 2 * L_CODES$1 + 1;
+/* maximum heap size */
+const MAX_BITS$1  = 15;
+/* All codes must not exceed MAX_BITS bits */
+
+const MIN_MATCH$1 = 3;
+const MAX_MATCH$1 = 258;
+const MIN_LOOKAHEAD = (MAX_MATCH$1 + MIN_MATCH$1 + 1);
+
+const PRESET_DICT = 0x20;
+
+const INIT_STATE = 42;
+const EXTRA_STATE = 69;
+const NAME_STATE = 73;
+const COMMENT_STATE = 91;
+const HCRC_STATE = 103;
+const BUSY_STATE = 113;
+const FINISH_STATE = 666;
+
+const BS_NEED_MORE      = 1; /* block not completed, need more input or more output */
+const BS_BLOCK_DONE     = 2; /* block flush performed */
+const BS_FINISH_STARTED = 3; /* finish started, need only more output at next deflate */
+const BS_FINISH_DONE    = 4; /* finish done, accept no more input or output */
+
+const OS_CODE = 0x03; // Unix :) . Don't detect, use this default.
+
+const err = (strm, errorCode) => {
+  strm.msg = messages[errorCode];
+  return errorCode;
+};
+
+const rank = (f) => {
+  return ((f) << 1) - ((f) > 4 ? 9 : 0);
+};
+
+const zero$1 = (buf) => {
+  let len = buf.length; while (--len >= 0) { buf[len] = 0; }
+};
+
+
+/* eslint-disable new-cap */
+let HASH_ZLIB = (s, prev, data) => ((prev << s.hash_shift) ^ data) & s.hash_mask;
+// This hash causes less collisions, https://github.com/nodeca/pako/issues/135
+// But breaks binary compatibility
+//let HASH_FAST = (s, prev, data) => ((prev << 8) + (prev >> 8) + (data << 4)) & s.hash_mask;
+let HASH = HASH_ZLIB;
+
+/* =========================================================================
+ * Flush as much pending output as possible. All deflate() output goes
+ * through this function so some applications may wish to modify it
+ * to avoid allocating a large strm->output buffer and copying into it.
+ * (See also read_buf()).
+ */
+const flush_pending = (strm) => {
+  const s = strm.state;
+
+  //_tr_flush_bits(s);
+  let len = s.pending;
+  if (len > strm.avail_out) {
+    len = strm.avail_out;
+  }
+  if (len === 0) { return; }
+
+  strm.output.set(s.pending_buf.subarray(s.pending_out, s.pending_out + len), strm.next_out);
+  strm.next_out += len;
+  s.pending_out += len;
+  strm.total_out += len;
+  strm.avail_out -= len;
+  s.pending -= len;
+  if (s.pending === 0) {
+    s.pending_out = 0;
+  }
+};
+
+
+const flush_block_only = (s, last) => {
+  _tr_flush_block$1(s, (s.block_start >= 0 ? s.block_start : -1), s.strstart - s.block_start, last);
+  s.block_start = s.strstart;
+  flush_pending(s.strm);
+};
+
+
+const put_byte = (s, b) => {
+  s.pending_buf[s.pending++] = b;
+};
+
+
+/* =========================================================================
+ * Put a short in the pending buffer. The 16-bit value is put in MSB order.
+ * IN assertion: the stream state is correct and there is enough room in
+ * pending_buf.
+ */
+const putShortMSB = (s, b) => {
+
+  //  put_byte(s, (Byte)(b >> 8));
+//  put_byte(s, (Byte)(b & 0xff));
+  s.pending_buf[s.pending++] = (b >>> 8) & 0xff;
+  s.pending_buf[s.pending++] = b & 0xff;
+};
+
+
+/* ===========================================================================
+ * Read a new buffer from the current input stream, update the adler32
+ * and total number of bytes read.  All deflate() input goes through
+ * this function so some applications may wish to modify it to avoid
+ * allocating a large strm->input buffer and copying from it.
+ * (See also flush_pending()).
+ */
+const read_buf = (strm, buf, start, size) => {
+
+  let len = strm.avail_in;
+
+  if (len > size) { len = size; }
+  if (len === 0) { return 0; }
+
+  strm.avail_in -= len;
+
+  // zmemcpy(buf, strm->next_in, len);
+  buf.set(strm.input.subarray(strm.next_in, strm.next_in + len), start);
+  if (strm.state.wrap === 1) {
+    strm.adler = adler32_1(strm.adler, buf, len, start);
+  }
+
+  else if (strm.state.wrap === 2) {
+    strm.adler = crc32_1(strm.adler, buf, len, start);
+  }
+
+  strm.next_in += len;
+  strm.total_in += len;
+
+  return len;
+};
+
+
+/* ===========================================================================
+ * Set match_start to the longest match starting at the given string and
+ * return its length. Matches shorter or equal to prev_length are discarded,
+ * in which case the result is equal to prev_length and match_start is
+ * garbage.
+ * IN assertions: cur_match is the head of the hash chain for the current
+ *   string (strstart) and its distance is <= MAX_DIST, and prev_length >= 1
+ * OUT assertion: the match length is not greater than s->lookahead.
+ */
+const longest_match = (s, cur_match) => {
+
+  let chain_length = s.max_chain_length;      /* max hash chain length */
+  let scan = s.strstart; /* current string */
+  let match;                       /* matched string */
+  let len;                           /* length of current match */
+  let best_len = s.prev_length;              /* best match length so far */
+  let nice_match = s.nice_match;             /* stop if match long enough */
+  const limit = (s.strstart > (s.w_size - MIN_LOOKAHEAD)) ?
+      s.strstart - (s.w_size - MIN_LOOKAHEAD) : 0/*NIL*/;
+
+  const _win = s.window; // shortcut
+
+  const wmask = s.w_mask;
+  const prev  = s.prev;
+
+  /* Stop when cur_match becomes <= limit. To simplify the code,
+   * we prevent matches with the string of window index 0.
+   */
+
+  const strend = s.strstart + MAX_MATCH$1;
+  let scan_end1  = _win[scan + best_len - 1];
+  let scan_end   = _win[scan + best_len];
+
+  /* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16.
+   * It is easy to get rid of this optimization if necessary.
+   */
+  // Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever");
+
+  /* Do not waste too much time if we already have a good match: */
+  if (s.prev_length >= s.good_match) {
+    chain_length >>= 2;
+  }
+  /* Do not look for matches beyond the end of the input. This is necessary
+   * to make deflate deterministic.
+   */
+  if (nice_match > s.lookahead) { nice_match = s.lookahead; }
+
+  // Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead");
+
+  do {
+    // Assert(cur_match < s->strstart, "no future");
+    match = cur_match;
+
+    /* Skip to next match if the match length cannot increase
+     * or if the match length is less than 2.  Note that the checks below
+     * for insufficient lookahead only occur occasionally for performance
+     * reasons.  Therefore uninitialized memory will be accessed, and
+     * conditional jumps will be made that depend on those values.
+     * However the length of the match is limited to the lookahead, so
+     * the output of deflate is not affected by the uninitialized values.
+     */
+
+    if (_win[match + best_len]     !== scan_end  ||
+        _win[match + best_len - 1] !== scan_end1 ||
+        _win[match]                !== _win[scan] ||
+        _win[++match]              !== _win[scan + 1]) {
+      continue;
+    }
+
+    /* The check at best_len-1 can be removed because it will be made
+     * again later. (This heuristic is not always a win.)
+     * It is not necessary to compare scan[2] and match[2] since they
+     * are always equal when the other bytes match, given that
+     * the hash keys are equal and that HASH_BITS >= 8.
+     */
+    scan += 2;
+    match++;
+    // Assert(*scan == *match, "match[2]?");
+
+    /* We check for insufficient lookahead only every 8th comparison;
+     * the 256th check will be made at strstart+258.
+     */
+    do {
+      /*jshint noempty:false*/
+    } while (_win[++scan] === _win[++match] && _win[++scan] === _win[++match] &&
+             _win[++scan] === _win[++match] && _win[++scan] === _win[++match] &&
+             _win[++scan] === _win[++match] && _win[++scan] === _win[++match] &&
+             _win[++scan] === _win[++match] && _win[++scan] === _win[++match] &&
+             scan < strend);
+
+    // Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan");
+
+    len = MAX_MATCH$1 - (strend - scan);
+    scan = strend - MAX_MATCH$1;
+
+    if (len > best_len) {
+      s.match_start = cur_match;
+      best_len = len;
+      if (len >= nice_match) {
+        break;
+      }
+      scan_end1  = _win[scan + best_len - 1];
+      scan_end   = _win[scan + best_len];
+    }
+  } while ((cur_match = prev[cur_match & wmask]) > limit && --chain_length !== 0);
+
+  if (best_len <= s.lookahead) {
+    return best_len;
+  }
+  return s.lookahead;
+};
+
+
+/* ===========================================================================
+ * Fill the window when the lookahead becomes insufficient.
+ * Updates strstart and lookahead.
+ *
+ * IN assertion: lookahead < MIN_LOOKAHEAD
+ * OUT assertions: strstart <= window_size-MIN_LOOKAHEAD
+ *    At least one byte has been read, or avail_in == 0; reads are
+ *    performed for at least two bytes (required for the zip translate_eol
+ *    option -- not supported here).
+ */
+const fill_window = (s) => {
+
+  const _w_size = s.w_size;
+  let p, n, m, more, str;
+
+  //Assert(s->lookahead < MIN_LOOKAHEAD, "already enough lookahead");
+
+  do {
+    more = s.window_size - s.lookahead - s.strstart;
+
+    // JS ints have 32 bit, block below not needed
+    /* Deal with !@#$% 64K limit: */
+    //if (sizeof(int) <= 2) {
+    //    if (more == 0 && s->strstart == 0 && s->lookahead == 0) {
+    //        more = wsize;
+    //
+    //  } else if (more == (unsigned)(-1)) {
+    //        /* Very unlikely, but possible on 16 bit machine if
+    //         * strstart == 0 && lookahead == 1 (input done a byte at time)
+    //         */
+    //        more--;
+    //    }
+    //}
+
+
+    /* If the window is almost full and there is insufficient lookahead,
+     * move the upper half to the lower one to make room in the upper half.
+     */
+    if (s.strstart >= _w_size + (_w_size - MIN_LOOKAHEAD)) {
+
+      s.window.set(s.window.subarray(_w_size, _w_size + _w_size), 0);
+      s.match_start -= _w_size;
+      s.strstart -= _w_size;
+      /* we now have strstart >= MAX_DIST */
+      s.block_start -= _w_size;
+
+      /* Slide the hash table (could be avoided with 32 bit values
+       at the expense of memory usage). We slide even when level == 0
+       to keep the hash table consistent if we switch back to level > 0
+       later. (Using level 0 permanently is not an optimal usage of
+       zlib, so we don't care about this pathological case.)
+       */
+
+      n = s.hash_size;
+      p = n;
+
+      do {
+        m = s.head[--p];
+        s.head[p] = (m >= _w_size ? m - _w_size : 0);
+      } while (--n);
+
+      n = _w_size;
+      p = n;
+
+      do {
+        m = s.prev[--p];
+        s.prev[p] = (m >= _w_size ? m - _w_size : 0);
+        /* If n is not on any hash chain, prev[n] is garbage but
+         * its value will never be used.
+         */
+      } while (--n);
+
+      more += _w_size;
+    }
+    if (s.strm.avail_in === 0) {
+      break;
+    }
+
+    /* If there was no sliding:
+     *    strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 &&
+     *    more == window_size - lookahead - strstart
+     * => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1)
+     * => more >= window_size - 2*WSIZE + 2
+     * In the BIG_MEM or MMAP case (not yet supported),
+     *   window_size == input_size + MIN_LOOKAHEAD  &&
+     *   strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD.
+     * Otherwise, window_size == 2*WSIZE so more >= 2.
+     * If there was sliding, more >= WSIZE. So in all cases, more >= 2.
+     */
+    //Assert(more >= 2, "more < 2");
+    n = read_buf(s.strm, s.window, s.strstart + s.lookahead, more);
+    s.lookahead += n;
+
+    /* Initialize the hash value now that we have some input: */
+    if (s.lookahead + s.insert >= MIN_MATCH$1) {
+      str = s.strstart - s.insert;
+      s.ins_h = s.window[str];
+
+      /* UPDATE_HASH(s, s->ins_h, s->window[str + 1]); */
+      s.ins_h = HASH(s, s.ins_h, s.window[str + 1]);
+//#if MIN_MATCH != 3
+//        Call update_hash() MIN_MATCH-3 more times
+//#endif
+      while (s.insert) {
+        /* UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); */
+        s.ins_h = HASH(s, s.ins_h, s.window[str + MIN_MATCH$1 - 1]);
+
+        s.prev[str & s.w_mask] = s.head[s.ins_h];
+        s.head[s.ins_h] = str;
+        str++;
+        s.insert--;
+        if (s.lookahead + s.insert < MIN_MATCH$1) {
+          break;
+        }
+      }
+    }
+    /* If the whole input has less than MIN_MATCH bytes, ins_h is garbage,
+     * but this is not important since only literal bytes will be emitted.
+     */
+
+  } while (s.lookahead < MIN_LOOKAHEAD && s.strm.avail_in !== 0);
+
+  /* If the WIN_INIT bytes after the end of the current data have never been
+   * written, then zero those bytes in order to avoid memory check reports of
+   * the use of uninitialized (or uninitialised as Julian writes) bytes by
+   * the longest match routines.  Update the high water mark for the next
+   * time through here.  WIN_INIT is set to MAX_MATCH since the longest match
+   * routines allow scanning to strstart + MAX_MATCH, ignoring lookahead.
+   */
+//  if (s.high_water < s.window_size) {
+//    const curr = s.strstart + s.lookahead;
+//    let init = 0;
+//
+//    if (s.high_water < curr) {
+//      /* Previous high water mark below current data -- zero WIN_INIT
+//       * bytes or up to end of window, whichever is less.
+//       */
+//      init = s.window_size - curr;
+//      if (init > WIN_INIT)
+//        init = WIN_INIT;
+//      zmemzero(s->window + curr, (unsigned)init);
+//      s->high_water = curr + init;
+//    }
+//    else if (s->high_water < (ulg)curr + WIN_INIT) {
+//      /* High water mark at or above current data, but below current data
+//       * plus WIN_INIT -- zero out to current data plus WIN_INIT, or up
+//       * to end of window, whichever is less.
+//       */
+//      init = (ulg)curr + WIN_INIT - s->high_water;
+//      if (init > s->window_size - s->high_water)
+//        init = s->window_size - s->high_water;
+//      zmemzero(s->window + s->high_water, (unsigned)init);
+//      s->high_water += init;
+//    }
+//  }
+//
+//  Assert((ulg)s->strstart <= s->window_size - MIN_LOOKAHEAD,
+//    "not enough room for search");
+};
+
+/* ===========================================================================
+ * Copy without compression as much as possible from the input stream, return
+ * the current block state.
+ * This function does not insert new strings in the dictionary since
+ * uncompressible data is probably not useful. This function is used
+ * only for the level=0 compression option.
+ * NOTE: this function should be optimized to avoid extra copying from
+ * window to pending_buf.
+ */
+const deflate_stored = (s, flush) => {
+
+  /* Stored blocks are limited to 0xffff bytes, pending_buf is limited
+   * to pending_buf_size, and each stored block has a 5 byte header:
+   */
+  let max_block_size = 0xffff;
+
+  if (max_block_size > s.pending_buf_size - 5) {
+    max_block_size = s.pending_buf_size - 5;
+  }
+
+  /* Copy as much as possible from input to output: */
+  for (;;) {
+    /* Fill the window as much as possible: */
+    if (s.lookahead <= 1) {
+
+      //Assert(s->strstart < s->w_size+MAX_DIST(s) ||
+      //  s->block_start >= (long)s->w_size, "slide too late");
+//      if (!(s.strstart < s.w_size + (s.w_size - MIN_LOOKAHEAD) ||
+//        s.block_start >= s.w_size)) {
+//        throw  new Error("slide too late");
+//      }
+
+      fill_window(s);
+      if (s.lookahead === 0 && flush === Z_NO_FLUSH) {
+        return BS_NEED_MORE;
+      }
+
+      if (s.lookahead === 0) {
+        break;
+      }
+      /* flush the current block */
+    }
+    //Assert(s->block_start >= 0L, "block gone");
+//    if (s.block_start < 0) throw new Error("block gone");
+
+    s.strstart += s.lookahead;
+    s.lookahead = 0;
+
+    /* Emit a stored block if pending_buf will be full: */
+    const max_start = s.block_start + max_block_size;
+
+    if (s.strstart === 0 || s.strstart >= max_start) {
+      /* strstart == 0 is possible when wraparound on 16-bit machine */
+      s.lookahead = s.strstart - max_start;
+      s.strstart = max_start;
+      /*** FLUSH_BLOCK(s, 0); ***/
+      flush_block_only(s, false);
+      if (s.strm.avail_out === 0) {
+        return BS_NEED_MORE;
+      }
+      /***/
+
+
+    }
+    /* Flush if we may have to slide, otherwise block_start may become
+     * negative and the data will be gone:
+     */
+    if (s.strstart - s.block_start >= (s.w_size - MIN_LOOKAHEAD)) {
+      /*** FLUSH_BLOCK(s, 0); ***/
+      flush_block_only(s, false);
+      if (s.strm.avail_out === 0) {
+        return BS_NEED_MORE;
+      }
+      /***/
+    }
+  }
+
+  s.insert = 0;
+
+  if (flush === Z_FINISH) {
+    /*** FLUSH_BLOCK(s, 1); ***/
+    flush_block_only(s, true);
+    if (s.strm.avail_out === 0) {
+      return BS_FINISH_STARTED;
+    }
+    /***/
+    return BS_FINISH_DONE;
+  }
+
+  if (s.strstart > s.block_start) {
+    /*** FLUSH_BLOCK(s, 0); ***/
+    flush_block_only(s, false);
+    if (s.strm.avail_out === 0) {
+      return BS_NEED_MORE;
+    }
+    /***/
+  }
+
+  return BS_NEED_MORE;
+};
+
+/* ===========================================================================
+ * Compress as much as possible from the input stream, return the current
+ * block state.
+ * This function does not perform lazy evaluation of matches and inserts
+ * new strings in the dictionary only for unmatched strings or for short
+ * matches. It is used only for the fast compression options.
+ */
+const deflate_fast = (s, flush) => {
+
+  let hash_head;        /* head of the hash chain */
+  let bflush;           /* set if current block must be flushed */
+
+  for (;;) {
+    /* Make sure that we always have enough lookahead, except
+     * at the end of the input file. We need MAX_MATCH bytes
+     * for the next match, plus MIN_MATCH bytes to insert the
+     * string following the next match.
+     */
+    if (s.lookahead < MIN_LOOKAHEAD) {
+      fill_window(s);
+      if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH) {
+        return BS_NEED_MORE;
+      }
+      if (s.lookahead === 0) {
+        break; /* flush the current block */
+      }
+    }
+
+    /* Insert the string window[strstart .. strstart+2] in the
+     * dictionary, and set hash_head to the head of the hash chain:
+     */
+    hash_head = 0/*NIL*/;
+    if (s.lookahead >= MIN_MATCH$1) {
+      /*** INSERT_STRING(s, s.strstart, hash_head); ***/
+      s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + MIN_MATCH$1 - 1]);
+      hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];
+      s.head[s.ins_h] = s.strstart;
+      /***/
+    }
+
+    /* Find the longest match, discarding those <= prev_length.
+     * At this point we have always match_length < MIN_MATCH
+     */
+    if (hash_head !== 0/*NIL*/ && ((s.strstart - hash_head) <= (s.w_size - MIN_LOOKAHEAD))) {
+      /* To simplify the code, we prevent matches with the string
+       * of window index 0 (in particular we have to avoid a match
+       * of the string with itself at the start of the input file).
+       */
+      s.match_length = longest_match(s, hash_head);
+      /* longest_match() sets match_start */
+    }
+    if (s.match_length >= MIN_MATCH$1) {
+      // check_match(s, s.strstart, s.match_start, s.match_length); // for debug only
+
+      /*** _tr_tally_dist(s, s.strstart - s.match_start,
+                     s.match_length - MIN_MATCH, bflush); ***/
+      bflush = _tr_tally$1(s, s.strstart - s.match_start, s.match_length - MIN_MATCH$1);
+
+      s.lookahead -= s.match_length;
+
+      /* Insert new strings in the hash table only if the match length
+       * is not too large. This saves time but degrades compression.
+       */
+      if (s.match_length <= s.max_lazy_match/*max_insert_length*/ && s.lookahead >= MIN_MATCH$1) {
+        s.match_length--; /* string at strstart already in table */
+        do {
+          s.strstart++;
+          /*** INSERT_STRING(s, s.strstart, hash_head); ***/
+          s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + MIN_MATCH$1 - 1]);
+          hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];
+          s.head[s.ins_h] = s.strstart;
+          /***/
+          /* strstart never exceeds WSIZE-MAX_MATCH, so there are
+           * always MIN_MATCH bytes ahead.
+           */
+        } while (--s.match_length !== 0);
+        s.strstart++;
+      } else
+      {
+        s.strstart += s.match_length;
+        s.match_length = 0;
+        s.ins_h = s.window[s.strstart];
+        /* UPDATE_HASH(s, s.ins_h, s.window[s.strstart+1]); */
+        s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + 1]);
+
+//#if MIN_MATCH != 3
+//                Call UPDATE_HASH() MIN_MATCH-3 more times
+//#endif
+        /* If lookahead < MIN_MATCH, ins_h is garbage, but it does not
+         * matter since it will be recomputed at next deflate call.
+         */
+      }
+    } else {
+      /* No match, output a literal byte */
+      //Tracevv((stderr,"%c", s.window[s.strstart]));
+      /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/
+      bflush = _tr_tally$1(s, 0, s.window[s.strstart]);
+
+      s.lookahead--;
+      s.strstart++;
+    }
+    if (bflush) {
+      /*** FLUSH_BLOCK(s, 0); ***/
+      flush_block_only(s, false);
+      if (s.strm.avail_out === 0) {
+        return BS_NEED_MORE;
+      }
+      /***/
+    }
+  }
+  s.insert = ((s.strstart < (MIN_MATCH$1 - 1)) ? s.strstart : MIN_MATCH$1 - 1);
+  if (flush === Z_FINISH) {
+    /*** FLUSH_BLOCK(s, 1); ***/
+    flush_block_only(s, true);
+    if (s.strm.avail_out === 0) {
+      return BS_FINISH_STARTED;
+    }
+    /***/
+    return BS_FINISH_DONE;
+  }
+  if (s.last_lit) {
+    /*** FLUSH_BLOCK(s, 0); ***/
+    flush_block_only(s, false);
+    if (s.strm.avail_out === 0) {
+      return BS_NEED_MORE;
+    }
+    /***/
+  }
+  return BS_BLOCK_DONE;
+};
+
+/* ===========================================================================
+ * Same as above, but achieves better compression. We use a lazy
+ * evaluation for matches: a match is finally adopted only if there is
+ * no better match at the next window position.
+ */
+const deflate_slow = (s, flush) => {
+
+  let hash_head;          /* head of hash chain */
+  let bflush;              /* set if current block must be flushed */
+
+  let max_insert;
+
+  /* Process the input block. */
+  for (;;) {
+    /* Make sure that we always have enough lookahead, except
+     * at the end of the input file. We need MAX_MATCH bytes
+     * for the next match, plus MIN_MATCH bytes to insert the
+     * string following the next match.
+     */
+    if (s.lookahead < MIN_LOOKAHEAD) {
+      fill_window(s);
+      if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH) {
+        return BS_NEED_MORE;
+      }
+      if (s.lookahead === 0) { break; } /* flush the current block */
+    }
+
+    /* Insert the string window[strstart .. strstart+2] in the
+     * dictionary, and set hash_head to the head of the hash chain:
+     */
+    hash_head = 0/*NIL*/;
+    if (s.lookahead >= MIN_MATCH$1) {
+      /*** INSERT_STRING(s, s.strstart, hash_head); ***/
+      s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + MIN_MATCH$1 - 1]);
+      hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];
+      s.head[s.ins_h] = s.strstart;
+      /***/
+    }
+
+    /* Find the longest match, discarding those <= prev_length.
+     */
+    s.prev_length = s.match_length;
+    s.prev_match = s.match_start;
+    s.match_length = MIN_MATCH$1 - 1;
+
+    if (hash_head !== 0/*NIL*/ && s.prev_length < s.max_lazy_match &&
+        s.strstart - hash_head <= (s.w_size - MIN_LOOKAHEAD)/*MAX_DIST(s)*/) {
+      /* To simplify the code, we prevent matches with the string
+       * of window index 0 (in particular we have to avoid a match
+       * of the string with itself at the start of the input file).
+       */
+      s.match_length = longest_match(s, hash_head);
+      /* longest_match() sets match_start */
+
+      if (s.match_length <= 5 &&
+         (s.strategy === Z_FILTERED || (s.match_length === MIN_MATCH$1 && s.strstart - s.match_start > 4096/*TOO_FAR*/))) {
+
+        /* If prev_match is also MIN_MATCH, match_start is garbage
+         * but we will ignore the current match anyway.
+         */
+        s.match_length = MIN_MATCH$1 - 1;
+      }
+    }
+    /* If there was a match at the previous step and the current
+     * match is not better, output the previous match:
+     */
+    if (s.prev_length >= MIN_MATCH$1 && s.match_length <= s.prev_length) {
+      max_insert = s.strstart + s.lookahead - MIN_MATCH$1;
+      /* Do not insert strings in hash table beyond this. */
+
+      //check_match(s, s.strstart-1, s.prev_match, s.prev_length);
+
+      /***_tr_tally_dist(s, s.strstart - 1 - s.prev_match,
+                     s.prev_length - MIN_MATCH, bflush);***/
+      bflush = _tr_tally$1(s, s.strstart - 1 - s.prev_match, s.prev_length - MIN_MATCH$1);
+      /* Insert in hash table all strings up to the end of the match.
+       * strstart-1 and strstart are already inserted. If there is not
+       * enough lookahead, the last two strings are not inserted in
+       * the hash table.
+       */
+      s.lookahead -= s.prev_length - 1;
+      s.prev_length -= 2;
+      do {
+        if (++s.strstart <= max_insert) {
+          /*** INSERT_STRING(s, s.strstart, hash_head); ***/
+          s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + MIN_MATCH$1 - 1]);
+          hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];
+          s.head[s.ins_h] = s.strstart;
+          /***/
+        }
+      } while (--s.prev_length !== 0);
+      s.match_available = 0;
+      s.match_length = MIN_MATCH$1 - 1;
+      s.strstart++;
+
+      if (bflush) {
+        /*** FLUSH_BLOCK(s, 0); ***/
+        flush_block_only(s, false);
+        if (s.strm.avail_out === 0) {
+          return BS_NEED_MORE;
+        }
+        /***/
+      }
+
+    } else if (s.match_available) {
+      /* If there was no match at the previous position, output a
+       * single literal. If there was a match but the current match
+       * is longer, truncate the previous match to a single literal.
+       */
+      //Tracevv((stderr,"%c", s->window[s->strstart-1]));
+      /*** _tr_tally_lit(s, s.window[s.strstart-1], bflush); ***/
+      bflush = _tr_tally$1(s, 0, s.window[s.strstart - 1]);
+
+      if (bflush) {
+        /*** FLUSH_BLOCK_ONLY(s, 0) ***/
+        flush_block_only(s, false);
+        /***/
+      }
+      s.strstart++;
+      s.lookahead--;
+      if (s.strm.avail_out === 0) {
+        return BS_NEED_MORE;
+      }
+    } else {
+      /* There is no previous match to compare with, wait for
+       * the next step to decide.
+       */
+      s.match_available = 1;
+      s.strstart++;
+      s.lookahead--;
+    }
+  }
+  //Assert (flush != Z_NO_FLUSH, "no flush?");
+  if (s.match_available) {
+    //Tracevv((stderr,"%c", s->window[s->strstart-1]));
+    /*** _tr_tally_lit(s, s.window[s.strstart-1], bflush); ***/
+    bflush = _tr_tally$1(s, 0, s.window[s.strstart - 1]);
+
+    s.match_available = 0;
+  }
+  s.insert = s.strstart < MIN_MATCH$1 - 1 ? s.strstart : MIN_MATCH$1 - 1;
+  if (flush === Z_FINISH) {
+    /*** FLUSH_BLOCK(s, 1); ***/
+    flush_block_only(s, true);
+    if (s.strm.avail_out === 0) {
+      return BS_FINISH_STARTED;
+    }
+    /***/
+    return BS_FINISH_DONE;
+  }
+  if (s.last_lit) {
+    /*** FLUSH_BLOCK(s, 0); ***/
+    flush_block_only(s, false);
+    if (s.strm.avail_out === 0) {
+      return BS_NEED_MORE;
+    }
+    /***/
+  }
+
+  return BS_BLOCK_DONE;
+};
+
+
+/* ===========================================================================
+ * For Z_RLE, simply look for runs of bytes, generate matches only of distance
+ * one.  Do not maintain a hash table.  (It will be regenerated if this run of
+ * deflate switches away from Z_RLE.)
+ */
+const deflate_rle = (s, flush) => {
+
+  let bflush;            /* set if current block must be flushed */
+  let prev;              /* byte at distance one to match */
+  let scan, strend;      /* scan goes up to strend for length of run */
+
+  const _win = s.window;
+
+  for (;;) {
+    /* Make sure that we always have enough lookahead, except
+     * at the end of the input file. We need MAX_MATCH bytes
+     * for the longest run, plus one for the unrolled loop.
+     */
+    if (s.lookahead <= MAX_MATCH$1) {
+      fill_window(s);
+      if (s.lookahead <= MAX_MATCH$1 && flush === Z_NO_FLUSH) {
+        return BS_NEED_MORE;
+      }
+      if (s.lookahead === 0) { break; } /* flush the current block */
+    }
+
+    /* See how many times the previous byte repeats */
+    s.match_length = 0;
+    if (s.lookahead >= MIN_MATCH$1 && s.strstart > 0) {
+      scan = s.strstart - 1;
+      prev = _win[scan];
+      if (prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan]) {
+        strend = s.strstart + MAX_MATCH$1;
+        do {
+          /*jshint noempty:false*/
+        } while (prev === _win[++scan] && prev === _win[++scan] &&
+                 prev === _win[++scan] && prev === _win[++scan] &&
+                 prev === _win[++scan] && prev === _win[++scan] &&
+                 prev === _win[++scan] && prev === _win[++scan] &&
+                 scan < strend);
+        s.match_length = MAX_MATCH$1 - (strend - scan);
+        if (s.match_length > s.lookahead) {
+          s.match_length = s.lookahead;
+        }
+      }
+      //Assert(scan <= s->window+(uInt)(s->window_size-1), "wild scan");
+    }
+
+    /* Emit match if have run of MIN_MATCH or longer, else emit literal */
+    if (s.match_length >= MIN_MATCH$1) {
+      //check_match(s, s.strstart, s.strstart - 1, s.match_length);
+
+      /*** _tr_tally_dist(s, 1, s.match_length - MIN_MATCH, bflush); ***/
+      bflush = _tr_tally$1(s, 1, s.match_length - MIN_MATCH$1);
+
+      s.lookahead -= s.match_length;
+      s.strstart += s.match_length;
+      s.match_length = 0;
+    } else {
+      /* No match, output a literal byte */
+      //Tracevv((stderr,"%c", s->window[s->strstart]));
+      /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/
+      bflush = _tr_tally$1(s, 0, s.window[s.strstart]);
+
+      s.lookahead--;
+      s.strstart++;
+    }
+    if (bflush) {
+      /*** FLUSH_BLOCK(s, 0); ***/
+      flush_block_only(s, false);
+      if (s.strm.avail_out === 0) {
+        return BS_NEED_MORE;
+      }
+      /***/
+    }
+  }
+  s.insert = 0;
+  if (flush === Z_FINISH) {
+    /*** FLUSH_BLOCK(s, 1); ***/
+    flush_block_only(s, true);
+    if (s.strm.avail_out === 0) {
+      return BS_FINISH_STARTED;
+    }
+    /***/
+    return BS_FINISH_DONE;
+  }
+  if (s.last_lit) {
+    /*** FLUSH_BLOCK(s, 0); ***/
+    flush_block_only(s, false);
+    if (s.strm.avail_out === 0) {
+      return BS_NEED_MORE;
+    }
+    /***/
+  }
+  return BS_BLOCK_DONE;
+};
+
+/* ===========================================================================
+ * For Z_HUFFMAN_ONLY, do not look for matches.  Do not maintain a hash table.
+ * (It will be regenerated if this run of deflate switches away from Huffman.)
+ */
+const deflate_huff = (s, flush) => {
+
+  let bflush;             /* set if current block must be flushed */
+
+  for (;;) {
+    /* Make sure that we have a literal to write. */
+    if (s.lookahead === 0) {
+      fill_window(s);
+      if (s.lookahead === 0) {
+        if (flush === Z_NO_FLUSH) {
+          return BS_NEED_MORE;
+        }
+        break;      /* flush the current block */
+      }
+    }
+
+    /* Output a literal byte */
+    s.match_length = 0;
+    //Tracevv((stderr,"%c", s->window[s->strstart]));
+    /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/
+    bflush = _tr_tally$1(s, 0, s.window[s.strstart]);
+    s.lookahead--;
+    s.strstart++;
+    if (bflush) {
+      /*** FLUSH_BLOCK(s, 0); ***/
+      flush_block_only(s, false);
+      if (s.strm.avail_out === 0) {
+        return BS_NEED_MORE;
+      }
+      /***/
+    }
+  }
+  s.insert = 0;
+  if (flush === Z_FINISH) {
+    /*** FLUSH_BLOCK(s, 1); ***/
+    flush_block_only(s, true);
+    if (s.strm.avail_out === 0) {
+      return BS_FINISH_STARTED;
+    }
+    /***/
+    return BS_FINISH_DONE;
+  }
+  if (s.last_lit) {
+    /*** FLUSH_BLOCK(s, 0); ***/
+    flush_block_only(s, false);
+    if (s.strm.avail_out === 0) {
+      return BS_NEED_MORE;
+    }
+    /***/
+  }
+  return BS_BLOCK_DONE;
+};
+
+/* Values for max_lazy_match, good_match and max_chain_length, depending on
+ * the desired pack level (0..9). The values given below have been tuned to
+ * exclude worst case performance for pathological files. Better values may be
+ * found for specific files.
+ */
+function Config(good_length, max_lazy, nice_length, max_chain, func) {
+
+  this.good_length = good_length;
+  this.max_lazy = max_lazy;
+  this.nice_length = nice_length;
+  this.max_chain = max_chain;
+  this.func = func;
+}
+
+const configuration_table = [
+  /*      good lazy nice chain */
+  new Config(0, 0, 0, 0, deflate_stored),          /* 0 store only */
+  new Config(4, 4, 8, 4, deflate_fast),            /* 1 max speed, no lazy matches */
+  new Config(4, 5, 16, 8, deflate_fast),           /* 2 */
+  new Config(4, 6, 32, 32, deflate_fast),          /* 3 */
+
+  new Config(4, 4, 16, 16, deflate_slow),          /* 4 lazy matches */
+  new Config(8, 16, 32, 32, deflate_slow),         /* 5 */
+  new Config(8, 16, 128, 128, deflate_slow),       /* 6 */
+  new Config(8, 32, 128, 256, deflate_slow),       /* 7 */
+  new Config(32, 128, 258, 1024, deflate_slow),    /* 8 */
+  new Config(32, 258, 258, 4096, deflate_slow)     /* 9 max compression */
+];
+
+
+/* ===========================================================================
+ * Initialize the "longest match" routines for a new zlib stream
+ */
+const lm_init = (s) => {
+
+  s.window_size = 2 * s.w_size;
+
+  /*** CLEAR_HASH(s); ***/
+  zero$1(s.head); // Fill with NIL (= 0);
+
+  /* Set the default configuration parameters:
+   */
+  s.max_lazy_match = configuration_table[s.level].max_lazy;
+  s.good_match = configuration_table[s.level].good_length;
+  s.nice_match = configuration_table[s.level].nice_length;
+  s.max_chain_length = configuration_table[s.level].max_chain;
+
+  s.strstart = 0;
+  s.block_start = 0;
+  s.lookahead = 0;
+  s.insert = 0;
+  s.match_length = s.prev_length = MIN_MATCH$1 - 1;
+  s.match_available = 0;
+  s.ins_h = 0;
+};
+
+
+function DeflateState() {
+  this.strm = null;            /* pointer back to this zlib stream */
+  this.status = 0;            /* as the name implies */
+  this.pending_buf = null;      /* output still pending */
+  this.pending_buf_size = 0;  /* size of pending_buf */
+  this.pending_out = 0;       /* next pending byte to output to the stream */
+  this.pending = 0;           /* nb of bytes in the pending buffer */
+  this.wrap = 0;              /* bit 0 true for zlib, bit 1 true for gzip */
+  this.gzhead = null;         /* gzip header information to write */
+  this.gzindex = 0;           /* where in extra, name, or comment */
+  this.method = Z_DEFLATED; /* can only be DEFLATED */
+  this.last_flush = -1;   /* value of flush param for previous deflate call */
+
+  this.w_size = 0;  /* LZ77 window size (32K by default) */
+  this.w_bits = 0;  /* log2(w_size)  (8..16) */
+  this.w_mask = 0;  /* w_size - 1 */
+
+  this.window = null;
+  /* Sliding window. Input bytes are read into the second half of the window,
+   * and move to the first half later to keep a dictionary of at least wSize
+   * bytes. With this organization, matches are limited to a distance of
+   * wSize-MAX_MATCH bytes, but this ensures that IO is always
+   * performed with a length multiple of the block size.
+   */
+
+  this.window_size = 0;
+  /* Actual size of window: 2*wSize, except when the user input buffer
+   * is directly used as sliding window.
+   */
+
+  this.prev = null;
+  /* Link to older string with same hash index. To limit the size of this
+   * array to 64K, this link is maintained only for the last 32K strings.
+   * An index in this array is thus a window index modulo 32K.
+   */
+
+  this.head = null;   /* Heads of the hash chains or NIL. */
+
+  this.ins_h = 0;       /* hash index of string to be inserted */
+  this.hash_size = 0;   /* number of elements in hash table */
+  this.hash_bits = 0;   /* log2(hash_size) */
+  this.hash_mask = 0;   /* hash_size-1 */
+
+  this.hash_shift = 0;
+  /* Number of bits by which ins_h must be shifted at each input
+   * step. It must be such that after MIN_MATCH steps, the oldest
+   * byte no longer takes part in the hash key, that is:
+   *   hash_shift * MIN_MATCH >= hash_bits
+   */
+
+  this.block_start = 0;
+  /* Window position at the beginning of the current output block. Gets
+   * negative when the window is moved backwards.
+   */
+
+  this.match_length = 0;      /* length of best match */
+  this.prev_match = 0;        /* previous match */
+  this.match_available = 0;   /* set if previous match exists */
+  this.strstart = 0;          /* start of string to insert */
+  this.match_start = 0;       /* start of matching string */
+  this.lookahead = 0;         /* number of valid bytes ahead in window */
+
+  this.prev_length = 0;
+  /* Length of the best match at previous step. Matches not greater than this
+   * are discarded. This is used in the lazy match evaluation.
+   */
+
+  this.max_chain_length = 0;
+  /* To speed up deflation, hash chains are never searched beyond this
+   * length.  A higher limit improves compression ratio but degrades the
+   * speed.
+   */
+
+  this.max_lazy_match = 0;
+  /* Attempt to find a better match only when the current match is strictly
+   * smaller than this value. This mechanism is used only for compression
+   * levels >= 4.
+   */
+  // That's alias to max_lazy_match, don't use directly
+  //this.max_insert_length = 0;
+  /* Insert new strings in the hash table only if the match length is not
+   * greater than this length. This saves time but degrades compression.
+   * max_insert_length is used only for compression levels <= 3.
+   */
+
+  this.level = 0;     /* compression level (1..9) */
+  this.strategy = 0;  /* favor or force Huffman coding*/
+
+  this.good_match = 0;
+  /* Use a faster search when the previous match is longer than this */
+
+  this.nice_match = 0; /* Stop searching when current match exceeds this */
+
+              /* used by trees.c: */
+
+  /* Didn't use ct_data typedef below to suppress compiler warning */
+
+  // struct ct_data_s dyn_ltree[HEAP_SIZE];   /* literal and length tree */
+  // struct ct_data_s dyn_dtree[2*D_CODES+1]; /* distance tree */
+  // struct ct_data_s bl_tree[2*BL_CODES+1];  /* Huffman tree for bit lengths */
+
+  // Use flat array of DOUBLE size, with interleaved fata,
+  // because JS does not support effective
+  this.dyn_ltree  = new Uint16Array(HEAP_SIZE$1 * 2);
+  this.dyn_dtree  = new Uint16Array((2 * D_CODES$1 + 1) * 2);
+  this.bl_tree    = new Uint16Array((2 * BL_CODES$1 + 1) * 2);
+  zero$1(this.dyn_ltree);
+  zero$1(this.dyn_dtree);
+  zero$1(this.bl_tree);
+
+  this.l_desc   = null;         /* desc. for literal tree */
+  this.d_desc   = null;         /* desc. for distance tree */
+  this.bl_desc  = null;         /* desc. for bit length tree */
+
+  //ush bl_count[MAX_BITS+1];
+  this.bl_count = new Uint16Array(MAX_BITS$1 + 1);
+  /* number of codes at each bit length for an optimal tree */
+
+  //int heap[2*L_CODES+1];      /* heap used to build the Huffman trees */
+  this.heap = new Uint16Array(2 * L_CODES$1 + 1);  /* heap used to build the Huffman trees */
+  zero$1(this.heap);
+
+  this.heap_len = 0;               /* number of elements in the heap */
+  this.heap_max = 0;               /* element of largest frequency */
+  /* The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used.
+   * The same heap array is used to build all trees.
+   */
+
+  this.depth = new Uint16Array(2 * L_CODES$1 + 1); //uch depth[2*L_CODES+1];
+  zero$1(this.depth);
+  /* Depth of each subtree used as tie breaker for trees of equal frequency
+   */
+
+  this.l_buf = 0;          /* buffer index for literals or lengths */
+
+  this.lit_bufsize = 0;
+  /* Size of match buffer for literals/lengths.  There are 4 reasons for
+   * limiting lit_bufsize to 64K:
+   *   - frequencies can be kept in 16 bit counters
+   *   - if compression is not successful for the first block, all input
+   *     data is still in the window so we can still emit a stored block even
+   *     when input comes from standard input.  (This can also be done for
+   *     all blocks if lit_bufsize is not greater than 32K.)
+   *   - if compression is not successful for a file smaller than 64K, we can
+   *     even emit a stored file instead of a stored block (saving 5 bytes).
+   *     This is applicable only for zip (not gzip or zlib).
+   *   - creating new Huffman trees less frequently may not provide fast
+   *     adaptation to changes in the input data statistics. (Take for
+   *     example a binary file with poorly compressible code followed by
+   *     a highly compressible string table.) Smaller buffer sizes give
+   *     fast adaptation but have of course the overhead of transmitting
+   *     trees more frequently.
+   *   - I can't count above 4
+   */
+
+  this.last_lit = 0;      /* running index in l_buf */
+
+  this.d_buf = 0;
+  /* Buffer index for distances. To simplify the code, d_buf and l_buf have
+   * the same number of elements. To use different lengths, an extra flag
+   * array would be necessary.
+   */
+
+  this.opt_len = 0;       /* bit length of current block with optimal trees */
+  this.static_len = 0;    /* bit length of current block with static trees */
+  this.matches = 0;       /* number of string matches in current block */
+  this.insert = 0;        /* bytes at end of window left to insert */
+
+
+  this.bi_buf = 0;
+  /* Output buffer. bits are inserted starting at the bottom (least
+   * significant bits).
+   */
+  this.bi_valid = 0;
+  /* Number of valid bits in bi_buf.  All bits above the last valid bit
+   * are always zero.
+   */
+
+  // Used for window memory init. We safely ignore it for JS. That makes
+  // sense only for pointers and memory check tools.
+  //this.high_water = 0;
+  /* High water mark offset in window for initialized bytes -- bytes above
+   * this are set to zero in order to avoid memory check warnings when
+   * longest match routines access bytes past the input.  This is then
+   * updated to the new high water mark.
+   */
+}
+
+
+const deflateResetKeep = (strm) => {
+
+  if (!strm || !strm.state) {
+    return err(strm, Z_STREAM_ERROR);
+  }
+
+  strm.total_in = strm.total_out = 0;
+  strm.data_type = Z_UNKNOWN$1;
+
+  const s = strm.state;
+  s.pending = 0;
+  s.pending_out = 0;
+
+  if (s.wrap < 0) {
+    s.wrap = -s.wrap;
+    /* was made negative by deflate(..., Z_FINISH); */
+  }
+  s.status = (s.wrap ? INIT_STATE : BUSY_STATE);
+  strm.adler = (s.wrap === 2) ?
+    0  // crc32(0, Z_NULL, 0)
+  :
+    1; // adler32(0, Z_NULL, 0)
+  s.last_flush = Z_NO_FLUSH;
+  _tr_init$1(s);
+  return Z_OK;
+};
+
+
+const deflateReset = (strm) => {
+
+  const ret = deflateResetKeep(strm);
+  if (ret === Z_OK) {
+    lm_init(strm.state);
+  }
+  return ret;
+};
+
+
+const deflateSetHeader = (strm, head) => {
+
+  if (!strm || !strm.state) { return Z_STREAM_ERROR; }
+  if (strm.state.wrap !== 2) { return Z_STREAM_ERROR; }
+  strm.state.gzhead = head;
+  return Z_OK;
+};
+
+
+const deflateInit2 = (strm, level, method, windowBits, memLevel, strategy) => {
+
+  if (!strm) { // === Z_NULL
+    return Z_STREAM_ERROR;
+  }
+  let wrap = 1;
+
+  if (level === Z_DEFAULT_COMPRESSION) {
+    level = 6;
+  }
+
+  if (windowBits < 0) { /* suppress zlib wrapper */
+    wrap = 0;
+    windowBits = -windowBits;
+  }
+
+  else if (windowBits > 15) {
+    wrap = 2;           /* write gzip wrapper instead */
+    windowBits -= 16;
+  }
+
+
+  if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || method !== Z_DEFLATED ||
+    windowBits < 8 || windowBits > 15 || level < 0 || level > 9 ||
+    strategy < 0 || strategy > Z_FIXED$1) {
+    return err(strm, Z_STREAM_ERROR);
+  }
+
+
+  if (windowBits === 8) {
+    windowBits = 9;
+  }
+  /* until 256-byte window bug fixed */
+
+  const s = new DeflateState();
+
+  strm.state = s;
+  s.strm = strm;
+
+  s.wrap = wrap;
+  s.gzhead = null;
+  s.w_bits = windowBits;
+  s.w_size = 1 << s.w_bits;
+  s.w_mask = s.w_size - 1;
+
+  s.hash_bits = memLevel + 7;
+  s.hash_size = 1 << s.hash_bits;
+  s.hash_mask = s.hash_size - 1;
+  s.hash_shift = ~~((s.hash_bits + MIN_MATCH$1 - 1) / MIN_MATCH$1);
+
+  s.window = new Uint8Array(s.w_size * 2);
+  s.head = new Uint16Array(s.hash_size);
+  s.prev = new Uint16Array(s.w_size);
+
+  // Don't need mem init magic for JS.
+  //s.high_water = 0;  /* nothing written to s->window yet */
+
+  s.lit_bufsize = 1 << (memLevel + 6); /* 16K elements by default */
+
+  s.pending_buf_size = s.lit_bufsize * 4;
+
+  //overlay = (ushf *) ZALLOC(strm, s->lit_bufsize, sizeof(ush)+2);
+  //s->pending_buf = (uchf *) overlay;
+  s.pending_buf = new Uint8Array(s.pending_buf_size);
+
+  // It is offset from `s.pending_buf` (size is `s.lit_bufsize * 2`)
+  //s->d_buf = overlay + s->lit_bufsize/sizeof(ush);
+  s.d_buf = 1 * s.lit_bufsize;
+
+  //s->l_buf = s->pending_buf + (1+sizeof(ush))*s->lit_bufsize;
+  s.l_buf = (1 + 2) * s.lit_bufsize;
+
+  s.level = level;
+  s.strategy = strategy;
+  s.method = method;
+
+  return deflateReset(strm);
+};
+
+const deflateInit = (strm, level) => {
+
+  return deflateInit2(strm, level, Z_DEFLATED, MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY);
+};
+
+
+const deflate = (strm, flush) => {
+
+  let beg, val; // for gzip header write only
+
+  if (!strm || !strm.state ||
+    flush > Z_BLOCK || flush < 0) {
+    return strm ? err(strm, Z_STREAM_ERROR) : Z_STREAM_ERROR;
+  }
+
+  const s = strm.state;
+
+  if (!strm.output ||
+      (!strm.input && strm.avail_in !== 0) ||
+      (s.status === FINISH_STATE && flush !== Z_FINISH)) {
+    return err(strm, (strm.avail_out === 0) ? Z_BUF_ERROR : Z_STREAM_ERROR);
+  }
+
+  s.strm = strm; /* just in case */
+  const old_flush = s.last_flush;
+  s.last_flush = flush;
+
+  /* Write the header */
+  if (s.status === INIT_STATE) {
+
+    if (s.wrap === 2) { // GZIP header
+      strm.adler = 0;  //crc32(0L, Z_NULL, 0);
+      put_byte(s, 31);
+      put_byte(s, 139);
+      put_byte(s, 8);
+      if (!s.gzhead) { // s->gzhead == Z_NULL
+        put_byte(s, 0);
+        put_byte(s, 0);
+        put_byte(s, 0);
+        put_byte(s, 0);
+        put_byte(s, 0);
+        put_byte(s, s.level === 9 ? 2 :
+                    (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2 ?
+                     4 : 0));
+        put_byte(s, OS_CODE);
+        s.status = BUSY_STATE;
+      }
+      else {
+        put_byte(s, (s.gzhead.text ? 1 : 0) +
+                    (s.gzhead.hcrc ? 2 : 0) +
+                    (!s.gzhead.extra ? 0 : 4) +
+                    (!s.gzhead.name ? 0 : 8) +
+                    (!s.gzhead.comment ? 0 : 16)
+        );
+        put_byte(s, s.gzhead.time & 0xff);
+        put_byte(s, (s.gzhead.time >> 8) & 0xff);
+        put_byte(s, (s.gzhead.time >> 16) & 0xff);
+        put_byte(s, (s.gzhead.time >> 24) & 0xff);
+        put_byte(s, s.level === 9 ? 2 :
+                    (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2 ?
+                     4 : 0));
+        put_byte(s, s.gzhead.os & 0xff);
+        if (s.gzhead.extra && s.gzhead.extra.length) {
+          put_byte(s, s.gzhead.extra.length & 0xff);
+          put_byte(s, (s.gzhead.extra.length >> 8) & 0xff);
+        }
+        if (s.gzhead.hcrc) {
+          strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending, 0);
+        }
+        s.gzindex = 0;
+        s.status = EXTRA_STATE;
+      }
+    }
+    else // DEFLATE header
+    {
+      let header = (Z_DEFLATED + ((s.w_bits - 8) << 4)) << 8;
+      let level_flags = -1;
+
+      if (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2) {
+        level_flags = 0;
+      } else if (s.level < 6) {
+        level_flags = 1;
+      } else if (s.level === 6) {
+        level_flags = 2;
+      } else {
+        level_flags = 3;
+      }
+      header |= (level_flags << 6);
+      if (s.strstart !== 0) { header |= PRESET_DICT; }
+      header += 31 - (header % 31);
+
+      s.status = BUSY_STATE;
+      putShortMSB(s, header);
+
+      /* Save the adler32 of the preset dictionary: */
+      if (s.strstart !== 0) {
+        putShortMSB(s, strm.adler >>> 16);
+        putShortMSB(s, strm.adler & 0xffff);
+      }
+      strm.adler = 1; // adler32(0L, Z_NULL, 0);
+    }
+  }
+
+//#ifdef GZIP
+  if (s.status === EXTRA_STATE) {
+    if (s.gzhead.extra/* != Z_NULL*/) {
+      beg = s.pending;  /* start of bytes to update crc */
+
+      while (s.gzindex < (s.gzhead.extra.length & 0xffff)) {
+        if (s.pending === s.pending_buf_size) {
+          if (s.gzhead.hcrc && s.pending > beg) {
+            strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg);
+          }
+          flush_pending(strm);
+          beg = s.pending;
+          if (s.pending === s.pending_buf_size) {
+            break;
+          }
+        }
+        put_byte(s, s.gzhead.extra[s.gzindex] & 0xff);
+        s.gzindex++;
+      }
+      if (s.gzhead.hcrc && s.pending > beg) {
+        strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg);
+      }
+      if (s.gzindex === s.gzhead.extra.length) {
+        s.gzindex = 0;
+        s.status = NAME_STATE;
+      }
+    }
+    else {
+      s.status = NAME_STATE;
+    }
+  }
+  if (s.status === NAME_STATE) {
+    if (s.gzhead.name/* != Z_NULL*/) {
+      beg = s.pending;  /* start of bytes to update crc */
+      //int val;
+
+      do {
+        if (s.pending === s.pending_buf_size) {
+          if (s.gzhead.hcrc && s.pending > beg) {
+            strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg);
+          }
+          flush_pending(strm);
+          beg = s.pending;
+          if (s.pending === s.pending_buf_size) {
+            val = 1;
+            break;
+          }
+        }
+        // JS specific: little magic to add zero terminator to end of string
+        if (s.gzindex < s.gzhead.name.length) {
+          val = s.gzhead.name.charCodeAt(s.gzindex++) & 0xff;
+        } else {
+          val = 0;
+        }
+        put_byte(s, val);
+      } while (val !== 0);
+
+      if (s.gzhead.hcrc && s.pending > beg) {
+        strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg);
+      }
+      if (val === 0) {
+        s.gzindex = 0;
+        s.status = COMMENT_STATE;
+      }
+    }
+    else {
+      s.status = COMMENT_STATE;
+    }
+  }
+  if (s.status === COMMENT_STATE) {
+    if (s.gzhead.comment/* != Z_NULL*/) {
+      beg = s.pending;  /* start of bytes to update crc */
+      //int val;
+
+      do {
+        if (s.pending === s.pending_buf_size) {
+          if (s.gzhead.hcrc && s.pending > beg) {
+            strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg);
+          }
+          flush_pending(strm);
+          beg = s.pending;
+          if (s.pending === s.pending_buf_size) {
+            val = 1;
+            break;
+          }
+        }
+        // JS specific: little magic to add zero terminator to end of string
+        if (s.gzindex < s.gzhead.comment.length) {
+          val = s.gzhead.comment.charCodeAt(s.gzindex++) & 0xff;
+        } else {
+          val = 0;
+        }
+        put_byte(s, val);
+      } while (val !== 0);
+
+      if (s.gzhead.hcrc && s.pending > beg) {
+        strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg);
+      }
+      if (val === 0) {
+        s.status = HCRC_STATE;
+      }
+    }
+    else {
+      s.status = HCRC_STATE;
+    }
+  }
+  if (s.status === HCRC_STATE) {
+    if (s.gzhead.hcrc) {
+      if (s.pending + 2 > s.pending_buf_size) {
+        flush_pending(strm);
+      }
+      if (s.pending + 2 <= s.pending_buf_size) {
+        put_byte(s, strm.adler & 0xff);
+        put_byte(s, (strm.adler >> 8) & 0xff);
+        strm.adler = 0; //crc32(0L, Z_NULL, 0);
+        s.status = BUSY_STATE;
+      }
+    }
+    else {
+      s.status = BUSY_STATE;
+    }
+  }
+//#endif
+
+  /* Flush as much pending output as possible */
+  if (s.pending !== 0) {
+    flush_pending(strm);
+    if (strm.avail_out === 0) {
+      /* Since avail_out is 0, deflate will be called again with
+       * more output space, but possibly with both pending and
+       * avail_in equal to zero. There won't be anything to do,
+       * but this is not an error situation so make sure we
+       * return OK instead of BUF_ERROR at next call of deflate:
+       */
+      s.last_flush = -1;
+      return Z_OK;
+    }
+
+    /* Make sure there is something to do and avoid duplicate consecutive
+     * flushes. For repeated and useless calls with Z_FINISH, we keep
+     * returning Z_STREAM_END instead of Z_BUF_ERROR.
+     */
+  } else if (strm.avail_in === 0 && rank(flush) <= rank(old_flush) &&
+    flush !== Z_FINISH) {
+    return err(strm, Z_BUF_ERROR);
+  }
+
+  /* User must not provide more input after the first FINISH: */
+  if (s.status === FINISH_STATE && strm.avail_in !== 0) {
+    return err(strm, Z_BUF_ERROR);
+  }
+
+  /* Start a new block or continue the current one.
+   */
+  if (strm.avail_in !== 0 || s.lookahead !== 0 ||
+    (flush !== Z_NO_FLUSH && s.status !== FINISH_STATE)) {
+    let bstate = (s.strategy === Z_HUFFMAN_ONLY) ? deflate_huff(s, flush) :
+      (s.strategy === Z_RLE ? deflate_rle(s, flush) :
+        configuration_table[s.level].func(s, flush));
+
+    if (bstate === BS_FINISH_STARTED || bstate === BS_FINISH_DONE) {
+      s.status = FINISH_STATE;
+    }
+    if (bstate === BS_NEED_MORE || bstate === BS_FINISH_STARTED) {
+      if (strm.avail_out === 0) {
+        s.last_flush = -1;
+        /* avoid BUF_ERROR next call, see above */
+      }
+      return Z_OK;
+      /* If flush != Z_NO_FLUSH && avail_out == 0, the next call
+       * of deflate should use the same flush parameter to make sure
+       * that the flush is complete. So we don't have to output an
+       * empty block here, this will be done at next call. This also
+       * ensures that for a very small output buffer, we emit at most
+       * one empty block.
+       */
+    }
+    if (bstate === BS_BLOCK_DONE) {
+      if (flush === Z_PARTIAL_FLUSH) {
+        _tr_align$1(s);
+      }
+      else if (flush !== Z_BLOCK) { /* FULL_FLUSH or SYNC_FLUSH */
+
+        _tr_stored_block$1(s, 0, 0, false);
+        /* For a full flush, this empty block will be recognized
+         * as a special marker by inflate_sync().
+         */
+        if (flush === Z_FULL_FLUSH) {
+          /*** CLEAR_HASH(s); ***/             /* forget history */
+          zero$1(s.head); // Fill with NIL (= 0);
+
+          if (s.lookahead === 0) {
+            s.strstart = 0;
+            s.block_start = 0;
+            s.insert = 0;
+          }
+        }
+      }
+      flush_pending(strm);
+      if (strm.avail_out === 0) {
+        s.last_flush = -1; /* avoid BUF_ERROR at next call, see above */
+        return Z_OK;
+      }
+    }
+  }
+  //Assert(strm->avail_out > 0, "bug2");
+  //if (strm.avail_out <= 0) { throw new Error("bug2");}
+
+  if (flush !== Z_FINISH) { return Z_OK; }
+  if (s.wrap <= 0) { return Z_STREAM_END; }
+
+  /* Write the trailer */
+  if (s.wrap === 2) {
+    put_byte(s, strm.adler & 0xff);
+    put_byte(s, (strm.adler >> 8) & 0xff);
+    put_byte(s, (strm.adler >> 16) & 0xff);
+    put_byte(s, (strm.adler >> 24) & 0xff);
+    put_byte(s, strm.total_in & 0xff);
+    put_byte(s, (strm.total_in >> 8) & 0xff);
+    put_byte(s, (strm.total_in >> 16) & 0xff);
+    put_byte(s, (strm.total_in >> 24) & 0xff);
+  }
+  else
+  {
+    putShortMSB(s, strm.adler >>> 16);
+    putShortMSB(s, strm.adler & 0xffff);
+  }
+
+  flush_pending(strm);
+  /* If avail_out is zero, the application will call deflate again
+   * to flush the rest.
+   */
+  if (s.wrap > 0) { s.wrap = -s.wrap; }
+  /* write the trailer only once! */
+  return s.pending !== 0 ? Z_OK : Z_STREAM_END;
+};
+
+
+const deflateEnd = (strm) => {
+
+  if (!strm/*== Z_NULL*/ || !strm.state/*== Z_NULL*/) {
+    return Z_STREAM_ERROR;
+  }
+
+  const status = strm.state.status;
+  if (status !== INIT_STATE &&
+    status !== EXTRA_STATE &&
+    status !== NAME_STATE &&
+    status !== COMMENT_STATE &&
+    status !== HCRC_STATE &&
+    status !== BUSY_STATE &&
+    status !== FINISH_STATE
+  ) {
+    return err(strm, Z_STREAM_ERROR);
+  }
+
+  strm.state = null;
+
+  return status === BUSY_STATE ? err(strm, Z_DATA_ERROR) : Z_OK;
+};
+
+
+/* =========================================================================
+ * Initializes the compression dictionary from the given byte
+ * sequence without producing any compressed output.
+ */
+const deflateSetDictionary = (strm, dictionary) => {
+
+  let dictLength = dictionary.length;
+
+  if (!strm/*== Z_NULL*/ || !strm.state/*== Z_NULL*/) {
+    return Z_STREAM_ERROR;
+  }
+
+  const s = strm.state;
+  const wrap = s.wrap;
+
+  if (wrap === 2 || (wrap === 1 && s.status !== INIT_STATE) || s.lookahead) {
+    return Z_STREAM_ERROR;
+  }
+
+  /* when using zlib wrappers, compute Adler-32 for provided dictionary */
+  if (wrap === 1) {
+    /* adler32(strm->adler, dictionary, dictLength); */
+    strm.adler = adler32_1(strm.adler, dictionary, dictLength, 0);
+  }
+
+  s.wrap = 0;   /* avoid computing Adler-32 in read_buf */
+
+  /* if dictionary would fill window, just replace the history */
+  if (dictLength >= s.w_size) {
+    if (wrap === 0) {            /* already empty otherwise */
+      /*** CLEAR_HASH(s); ***/
+      zero$1(s.head); // Fill with NIL (= 0);
+      s.strstart = 0;
+      s.block_start = 0;
+      s.insert = 0;
+    }
+    /* use the tail */
+    // dictionary = dictionary.slice(dictLength - s.w_size);
+    let tmpDict = new Uint8Array(s.w_size);
+    tmpDict.set(dictionary.subarray(dictLength - s.w_size, dictLength), 0);
+    dictionary = tmpDict;
+    dictLength = s.w_size;
+  }
+  /* insert dictionary into window and hash */
+  const avail = strm.avail_in;
+  const next = strm.next_in;
+  const input = strm.input;
+  strm.avail_in = dictLength;
+  strm.next_in = 0;
+  strm.input = dictionary;
+  fill_window(s);
+  while (s.lookahead >= MIN_MATCH$1) {
+    let str = s.strstart;
+    let n = s.lookahead - (MIN_MATCH$1 - 1);
+    do {
+      /* UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); */
+      s.ins_h = HASH(s, s.ins_h, s.window[str + MIN_MATCH$1 - 1]);
+
+      s.prev[str & s.w_mask] = s.head[s.ins_h];
+
+      s.head[s.ins_h] = str;
+      str++;
+    } while (--n);
+    s.strstart = str;
+    s.lookahead = MIN_MATCH$1 - 1;
+    fill_window(s);
+  }
+  s.strstart += s.lookahead;
+  s.block_start = s.strstart;
+  s.insert = s.lookahead;
+  s.lookahead = 0;
+  s.match_length = s.prev_length = MIN_MATCH$1 - 1;
+  s.match_available = 0;
+  strm.next_in = next;
+  strm.input = input;
+  strm.avail_in = avail;
+  s.wrap = wrap;
+  return Z_OK;
+};
+
+
+var deflateInit_1 = deflateInit;
+var deflateInit2_1 = deflateInit2;
+var deflateReset_1 = deflateReset;
+var deflateResetKeep_1 = deflateResetKeep;
+var deflateSetHeader_1 = deflateSetHeader;
+var deflate_2 = deflate;
+var deflateEnd_1 = deflateEnd;
+var deflateSetDictionary_1 = deflateSetDictionary;
+var deflateInfo = 'pako deflate (from Nodeca project)';
+
+/* Not implemented
+module.exports.deflateBound = deflateBound;
+module.exports.deflateCopy = deflateCopy;
+module.exports.deflateParams = deflateParams;
+module.exports.deflatePending = deflatePending;
+module.exports.deflatePrime = deflatePrime;
+module.exports.deflateTune = deflateTune;
+*/
+
+var deflate_1 = {
+       deflateInit: deflateInit_1,
+       deflateInit2: deflateInit2_1,
+       deflateReset: deflateReset_1,
+       deflateResetKeep: deflateResetKeep_1,
+       deflateSetHeader: deflateSetHeader_1,
+       deflate: deflate_2,
+       deflateEnd: deflateEnd_1,
+       deflateSetDictionary: deflateSetDictionary_1,
+       deflateInfo: deflateInfo
+};
+
+const _has = (obj, key) => {
+  return Object.prototype.hasOwnProperty.call(obj, key);
+};
+
+var assign = function (obj /*from1, from2, from3, ...*/) {
+  const sources = Array.prototype.slice.call(arguments, 1);
+  while (sources.length) {
+    const source = sources.shift();
+    if (!source) { continue; }
+
+    if (typeof source !== 'object') {
+      throw new TypeError(source + 'must be non-object');
+    }
+
+    for (const p in source) {
+      if (_has(source, p)) {
+        obj[p] = source[p];
+      }
+    }
+  }
+
+  return obj;
+};
+
+
+// Join array of chunks to single array.
+var flattenChunks = (chunks) => {
+  // calculate data length
+  let len = 0;
+
+  for (let i = 0, l = chunks.length; i < l; i++) {
+    len += chunks[i].length;
+  }
+
+  // join chunks
+  const result = new Uint8Array(len);
+
+  for (let i = 0, pos = 0, l = chunks.length; i < l; i++) {
+    let chunk = chunks[i];
+    result.set(chunk, pos);
+    pos += chunk.length;
+  }
+
+  return result;
+};
+
+var common = {
+       assign: assign,
+       flattenChunks: flattenChunks
+};
+
+// String encode/decode helpers
+
+
+// Quick check if we can use fast array to bin string conversion
+//
+// - apply(Array) can fail on Android 2.2
+// - apply(Uint8Array) can fail on iOS 5.1 Safari
+//
+let STR_APPLY_UIA_OK = true;
+
+try { String.fromCharCode.apply(null, new Uint8Array(1)); } catch (__) { STR_APPLY_UIA_OK = false; }
+
+
+// Table with utf8 lengths (calculated by first byte of sequence)
+// Note, that 5 & 6-byte values and some 4-byte values can not be represented in JS,
+// because max possible codepoint is 0x10ffff
+const _utf8len = new Uint8Array(256);
+for (let q = 0; q < 256; q++) {
+  _utf8len[q] = (q >= 252 ? 6 : q >= 248 ? 5 : q >= 240 ? 4 : q >= 224 ? 3 : q >= 192 ? 2 : 1);
+}
+_utf8len[254] = _utf8len[254] = 1; // Invalid sequence start
+
+
+// convert string to array (typed, when possible)
+var string2buf = (str) => {
+  let buf, c, c2, m_pos, i, str_len = str.length, buf_len = 0;
+
+  // count binary size
+  for (m_pos = 0; m_pos < str_len; m_pos++) {
+    c = str.charCodeAt(m_pos);
+    if ((c & 0xfc00) === 0xd800 && (m_pos + 1 < str_len)) {
+      c2 = str.charCodeAt(m_pos + 1);
+      if ((c2 & 0xfc00) === 0xdc00) {
+        c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00);
+        m_pos++;
+      }
+    }
+    buf_len += c < 0x80 ? 1 : c < 0x800 ? 2 : c < 0x10000 ? 3 : 4;
+  }
+
+  // allocate buffer
+  buf = new Uint8Array(buf_len);
+
+  // convert
+  for (i = 0, m_pos = 0; i < buf_len; m_pos++) {
+    c = str.charCodeAt(m_pos);
+    if ((c & 0xfc00) === 0xd800 && (m_pos + 1 < str_len)) {
+      c2 = str.charCodeAt(m_pos + 1);
+      if ((c2 & 0xfc00) === 0xdc00) {
+        c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00);
+        m_pos++;
+      }
+    }
+    if (c < 0x80) {
+      /* one byte */
+      buf[i++] = c;
+    } else if (c < 0x800) {
+      /* two bytes */
+      buf[i++] = 0xC0 | (c >>> 6);
+      buf[i++] = 0x80 | (c & 0x3f);
+    } else if (c < 0x10000) {
+      /* three bytes */
+      buf[i++] = 0xE0 | (c >>> 12);
+      buf[i++] = 0x80 | (c >>> 6 & 0x3f);
+      buf[i++] = 0x80 | (c & 0x3f);
+    } else {
+      /* four bytes */
+      buf[i++] = 0xf0 | (c >>> 18);
+      buf[i++] = 0x80 | (c >>> 12 & 0x3f);
+      buf[i++] = 0x80 | (c >>> 6 & 0x3f);
+      buf[i++] = 0x80 | (c & 0x3f);
+    }
+  }
+
+  return buf;
+};
+
+// Helper
+const buf2binstring = (buf, len) => {
+  // On Chrome, the arguments in a function call that are allowed is `65534`.
+  // If the length of the buffer is smaller than that, we can use this optimization,
+  // otherwise we will take a slower path.
+  if (len < 65534) {
+    if (buf.subarray && STR_APPLY_UIA_OK) {
+      return String.fromCharCode.apply(null, buf.length === len ? buf : buf.subarray(0, len));
+    }
+  }
+
+  let result = '';
+  for (let i = 0; i < len; i++) {
+    result += String.fromCharCode(buf[i]);
+  }
+  return result;
+};
+
+
+// convert array to string
+var buf2string = (buf, max) => {
+  let i, out;
+  const len = max || buf.length;
+
+  // Reserve max possible length (2 words per char)
+  // NB: by unknown reasons, Array is significantly faster for
+  //     String.fromCharCode.apply than Uint16Array.
+  const utf16buf = new Array(len * 2);
+
+  for (out = 0, i = 0; i < len;) {
+    let c = buf[i++];
+    // quick process ascii
+    if (c < 0x80) { utf16buf[out++] = c; continue; }
+
+    let c_len = _utf8len[c];
+    // skip 5 & 6 byte codes
+    if (c_len > 4) { utf16buf[out++] = 0xfffd; i += c_len - 1; continue; }
+
+    // apply mask on first byte
+    c &= c_len === 2 ? 0x1f : c_len === 3 ? 0x0f : 0x07;
+    // join the rest
+    while (c_len > 1 && i < len) {
+      c = (c << 6) | (buf[i++] & 0x3f);
+      c_len--;
+    }
+
+    // terminated by end of string?
+    if (c_len > 1) { utf16buf[out++] = 0xfffd; continue; }
+
+    if (c < 0x10000) {
+      utf16buf[out++] = c;
+    } else {
+      c -= 0x10000;
+      utf16buf[out++] = 0xd800 | ((c >> 10) & 0x3ff);
+      utf16buf[out++] = 0xdc00 | (c & 0x3ff);
+    }
+  }
+
+  return buf2binstring(utf16buf, out);
+};
+
+
+// Calculate max possible position in utf8 buffer,
+// that will not break sequence. If that's not possible
+// - (very small limits) return max size as is.
+//
+// buf[] - utf8 bytes array
+// max   - length limit (mandatory);
+var utf8border = (buf, max) => {
+
+  max = max || buf.length;
+  if (max > buf.length) { max = buf.length; }
+
+  // go back from last position, until start of sequence found
+  let pos = max - 1;
+  while (pos >= 0 && (buf[pos] & 0xC0) === 0x80) { pos--; }
+
+  // Very small and broken sequence,
+  // return max, because we should return something anyway.
+  if (pos < 0) { return max; }
+
+  // If we came to start of buffer - that means buffer is too small,
+  // return max too.
+  if (pos === 0) { return max; }
+
+  return (pos + _utf8len[buf[pos]] > max) ? pos : max;
+};
+
+var strings = {
+       string2buf: string2buf,
+       buf2string: buf2string,
+       utf8border: utf8border
+};
+
+// (C) 1995-2013 Jean-loup Gailly and Mark Adler
+// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not
+//   claim that you wrote the original software. If you use this software
+//   in a product, an acknowledgment in the product documentation would be
+//   appreciated but is not required.
+// 2. Altered source versions must be plainly marked as such, and must not be
+//   misrepresented as being the original software.
+// 3. This notice may not be removed or altered from any source distribution.
+
+function ZStream() {
+  /* next input byte */
+  this.input = null; // JS specific, because we have no pointers
+  this.next_in = 0;
+  /* number of bytes available at input */
+  this.avail_in = 0;
+  /* total number of input bytes read so far */
+  this.total_in = 0;
+  /* next output byte should be put there */
+  this.output = null; // JS specific, because we have no pointers
+  this.next_out = 0;
+  /* remaining free space at output */
+  this.avail_out = 0;
+  /* total number of bytes output so far */
+  this.total_out = 0;
+  /* last error message, NULL if no error */
+  this.msg = ''/*Z_NULL*/;
+  /* not visible by applications */
+  this.state = null;
+  /* best guess about the data type: binary or text */
+  this.data_type = 2/*Z_UNKNOWN*/;
+  /* adler32 value of the uncompressed data */
+  this.adler = 0;
+}
+
+var zstream = ZStream;
+
+const toString = Object.prototype.toString;
+
+/* Public constants ==========================================================*/
+/* ===========================================================================*/
+
+const {
+  Z_NO_FLUSH: Z_NO_FLUSH$1, Z_SYNC_FLUSH, Z_FULL_FLUSH: Z_FULL_FLUSH$1, Z_FINISH: Z_FINISH$1,
+  Z_OK: Z_OK$1, Z_STREAM_END: Z_STREAM_END$1,
+  Z_DEFAULT_COMPRESSION: Z_DEFAULT_COMPRESSION$1,
+  Z_DEFAULT_STRATEGY: Z_DEFAULT_STRATEGY$1,
+  Z_DEFLATED: Z_DEFLATED$1
+} = constants;
+
+/* ===========================================================================*/
+
+
+/**
+ * class Deflate
+ *
+ * Generic JS-style wrapper for zlib calls. If you don't need
+ * streaming behaviour - use more simple functions: [[deflate]],
+ * [[deflateRaw]] and [[gzip]].
+ **/
+
+/* internal
+ * Deflate.chunks -> Array
+ *
+ * Chunks of output data, if [[Deflate#onData]] not overridden.
+ **/
+
+/**
+ * Deflate.result -> Uint8Array
+ *
+ * Compressed result, generated by default [[Deflate#onData]]
+ * and [[Deflate#onEnd]] handlers. Filled after you push last chunk
+ * (call [[Deflate#push]] with `Z_FINISH` / `true` param).
+ **/
+
+/**
+ * Deflate.err -> Number
+ *
+ * Error code after deflate finished. 0 (Z_OK) on success.
+ * You will not need it in real life, because deflate errors
+ * are possible only on wrong options or bad `onData` / `onEnd`
+ * custom handlers.
+ **/
+
+/**
+ * Deflate.msg -> String
+ *
+ * Error message, if [[Deflate.err]] != 0
+ **/
+
+
+/**
+ * new Deflate(options)
+ * - options (Object): zlib deflate options.
+ *
+ * Creates new deflator instance with specified params. Throws exception
+ * on bad params. Supported options:
+ *
+ * - `level`
+ * - `windowBits`
+ * - `memLevel`
+ * - `strategy`
+ * - `dictionary`
+ *
+ * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced)
+ * for more information on these.
+ *
+ * Additional options, for internal needs:
+ *
+ * - `chunkSize` - size of generated data chunks (16K by default)
+ * - `raw` (Boolean) - do raw deflate
+ * - `gzip` (Boolean) - create gzip wrapper
+ * - `header` (Object) - custom header for gzip
+ *   - `text` (Boolean) - true if compressed data believed to be text
+ *   - `time` (Number) - modification time, unix timestamp
+ *   - `os` (Number) - operation system code
+ *   - `extra` (Array) - array of bytes with extra data (max 65536)
+ *   - `name` (String) - file name (binary string)
+ *   - `comment` (String) - comment (binary string)
+ *   - `hcrc` (Boolean) - true if header crc should be added
+ *
+ * ##### Example:
+ *
+ * ```javascript
+ * const pako = require('pako')
+ *   , chunk1 = new Uint8Array([1,2,3,4,5,6,7,8,9])
+ *   , chunk2 = new Uint8Array([10,11,12,13,14,15,16,17,18,19]);
+ *
+ * const deflate = new pako.Deflate({ level: 3});
+ *
+ * deflate.push(chunk1, false);
+ * deflate.push(chunk2, true);  // true -> last chunk
+ *
+ * if (deflate.err) { throw new Error(deflate.err); }
+ *
+ * console.log(deflate.result);
+ * ```
+ **/
+function Deflate(options) {
+  this.options = common.assign({
+    level: Z_DEFAULT_COMPRESSION$1,
+    method: Z_DEFLATED$1,
+    chunkSize: 16384,
+    windowBits: 15,
+    memLevel: 8,
+    strategy: Z_DEFAULT_STRATEGY$1
+  }, options || {});
+
+  let opt = this.options;
+
+  if (opt.raw && (opt.windowBits > 0)) {
+    opt.windowBits = -opt.windowBits;
+  }
+
+  else if (opt.gzip && (opt.windowBits > 0) && (opt.windowBits < 16)) {
+    opt.windowBits += 16;
+  }
+
+  this.err    = 0;      // error code, if happens (0 = Z_OK)
+  this.msg    = '';     // error message
+  this.ended  = false;  // used to avoid multiple onEnd() calls
+  this.chunks = [];     // chunks of compressed data
+
+  this.strm = new zstream();
+  this.strm.avail_out = 0;
+
+  let status = deflate_1.deflateInit2(
+    this.strm,
+    opt.level,
+    opt.method,
+    opt.windowBits,
+    opt.memLevel,
+    opt.strategy
+  );
+
+  if (status !== Z_OK$1) {
+    throw new Error(messages[status]);
+  }
+
+  if (opt.header) {
+    deflate_1.deflateSetHeader(this.strm, opt.header);
+  }
+
+  if (opt.dictionary) {
+    let dict;
+    // Convert data if needed
+    if (typeof opt.dictionary === 'string') {
+      // If we need to compress text, change encoding to utf8.
+      dict = strings.string2buf(opt.dictionary);
+    } else if (toString.call(opt.dictionary) === '[object ArrayBuffer]') {
+      dict = new Uint8Array(opt.dictionary);
+    } else {
+      dict = opt.dictionary;
+    }
+
+    status = deflate_1.deflateSetDictionary(this.strm, dict);
+
+    if (status !== Z_OK$1) {
+      throw new Error(messages[status]);
+    }
+
+    this._dict_set = true;
+  }
+}
+
+/**
+ * Deflate#push(data[, flush_mode]) -> Boolean
+ * - data (Uint8Array|ArrayBuffer|String): input data. Strings will be
+ *   converted to utf8 byte sequence.
+ * - flush_mode (Number|Boolean): 0..6 for corresponding Z_NO_FLUSH..Z_TREE modes.
+ *   See constants. Skipped or `false` means Z_NO_FLUSH, `true` means Z_FINISH.
+ *
+ * Sends input data to deflate pipe, generating [[Deflate#onData]] calls with
+ * new compressed chunks. Returns `true` on success. The last data block must
+ * have `flush_mode` Z_FINISH (or `true`). That will flush internal pending
+ * buffers and call [[Deflate#onEnd]].
+ *
+ * On fail call [[Deflate#onEnd]] with error code and return false.
+ *
+ * ##### Example
+ *
+ * ```javascript
+ * push(chunk, false); // push one of data chunks
+ * ...
+ * push(chunk, true);  // push last chunk
+ * ```
+ **/
+Deflate.prototype.push = function (data, flush_mode) {
+  const strm = this.strm;
+  const chunkSize = this.options.chunkSize;
+  let status, _flush_mode;
+
+  if (this.ended) { return false; }
+
+  if (flush_mode === ~~flush_mode) _flush_mode = flush_mode;
+  else _flush_mode = flush_mode === true ? Z_FINISH$1 : Z_NO_FLUSH$1;
+
+  // Convert data if needed
+  if (typeof data === 'string') {
+    // If we need to compress text, change encoding to utf8.
+    strm.input = strings.string2buf(data);
+  } else if (toString.call(data) === '[object ArrayBuffer]') {
+    strm.input = new Uint8Array(data);
+  } else {
+    strm.input = data;
+  }
+
+  strm.next_in = 0;
+  strm.avail_in = strm.input.length;
+
+  for (;;) {
+    if (strm.avail_out === 0) {
+      strm.output = new Uint8Array(chunkSize);
+      strm.next_out = 0;
+      strm.avail_out = chunkSize;
+    }
+
+    // Make sure avail_out > 6 to avoid repeating markers
+    if ((_flush_mode === Z_SYNC_FLUSH || _flush_mode === Z_FULL_FLUSH$1) && strm.avail_out <= 6) {
+      this.onData(strm.output.subarray(0, strm.next_out));
+      strm.avail_out = 0;
+      continue;
+    }
+
+    status = deflate_1.deflate(strm, _flush_mode);
+
+    // Ended => flush and finish
+    if (status === Z_STREAM_END$1) {
+      if (strm.next_out > 0) {
+        this.onData(strm.output.subarray(0, strm.next_out));
+      }
+      status = deflate_1.deflateEnd(this.strm);
+      this.onEnd(status);
+      this.ended = true;
+      return status === Z_OK$1;
+    }
+
+    // Flush if out buffer full
+    if (strm.avail_out === 0) {
+      this.onData(strm.output);
+      continue;
+    }
+
+    // Flush if requested and has data
+    if (_flush_mode > 0 && strm.next_out > 0) {
+      this.onData(strm.output.subarray(0, strm.next_out));
+      strm.avail_out = 0;
+      continue;
+    }
+
+    if (strm.avail_in === 0) break;
+  }
+
+  return true;
+};
+
+
+/**
+ * Deflate#onData(chunk) -> Void
+ * - chunk (Uint8Array): output data.
+ *
+ * By default, stores data blocks in `chunks[]` property and glue
+ * those in `onEnd`. Override this handler, if you need another behaviour.
+ **/
+Deflate.prototype.onData = function (chunk) {
+  this.chunks.push(chunk);
+};
+
+
+/**
+ * Deflate#onEnd(status) -> Void
+ * - status (Number): deflate status. 0 (Z_OK) on success,
+ *   other if not.
+ *
+ * Called once after you tell deflate that the input stream is
+ * complete (Z_FINISH). By default - join collected chunks,
+ * free memory and fill `results` / `err` properties.
+ **/
+Deflate.prototype.onEnd = function (status) {
+  // On success - join
+  if (status === Z_OK$1) {
+    this.result = common.flattenChunks(this.chunks);
+  }
+  this.chunks = [];
+  this.err = status;
+  this.msg = this.strm.msg;
+};
+
+// (C) 1995-2013 Jean-loup Gailly and Mark Adler
+// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not
+//   claim that you wrote the original software. If you use this software
+//   in a product, an acknowledgment in the product documentation would be
+//   appreciated but is not required.
+// 2. Altered source versions must be plainly marked as such, and must not be
+//   misrepresented as being the original software.
+// 3. This notice may not be removed or altered from any source distribution.
+
+// See state defs from inflate.js
+const BAD = 30;       /* got a data error -- remain here until reset */
+const TYPE = 12;      /* i: waiting for type bits, including last-flag bit */
+
+/*
+   Decode literal, length, and distance codes and write out the resulting
+   literal and match bytes until either not enough input or output is
+   available, an end-of-block is encountered, or a data error is encountered.
+   When large enough input and output buffers are supplied to inflate(), for
+   example, a 16K input buffer and a 64K output buffer, more than 95% of the
+   inflate execution time is spent in this routine.
+
+   Entry assumptions:
+
+        state.mode === LEN
+        strm.avail_in >= 6
+        strm.avail_out >= 258
+        start >= strm.avail_out
+        state.bits < 8
+
+   On return, state.mode is one of:
+
+        LEN -- ran out of enough output space or enough available input
+        TYPE -- reached end of block code, inflate() to interpret next block
+        BAD -- error in block data
+
+   Notes:
+
+    - The maximum input bits used by a length/distance pair is 15 bits for the
+      length code, 5 bits for the length extra, 15 bits for the distance code,
+      and 13 bits for the distance extra.  This totals 48 bits, or six bytes.
+      Therefore if strm.avail_in >= 6, then there is enough input to avoid
+      checking for available input while decoding.
+
+    - The maximum bytes that a single length/distance pair can output is 258
+      bytes, which is the maximum length that can be coded.  inflate_fast()
+      requires strm.avail_out >= 258 for each loop to avoid checking for
+      output space.
+ */
+var inffast = function inflate_fast(strm, start) {
+  let _in;                    /* local strm.input */
+  let last;                   /* have enough input while in < last */
+  let _out;                   /* local strm.output */
+  let beg;                    /* inflate()'s initial strm.output */
+  let end;                    /* while out < end, enough space available */
+//#ifdef INFLATE_STRICT
+  let dmax;                   /* maximum distance from zlib header */
+//#endif
+  let wsize;                  /* window size or zero if not using window */
+  let whave;                  /* valid bytes in the window */
+  let wnext;                  /* window write index */
+  // Use `s_window` instead `window`, avoid conflict with instrumentation tools
+  let s_window;               /* allocated sliding window, if wsize != 0 */
+  let hold;                   /* local strm.hold */
+  let bits;                   /* local strm.bits */
+  let lcode;                  /* local strm.lencode */
+  let dcode;                  /* local strm.distcode */
+  let lmask;                  /* mask for first level of length codes */
+  let dmask;                  /* mask for first level of distance codes */
+  let here;                   /* retrieved table entry */
+  let op;                     /* code bits, operation, extra bits, or */
+                              /*  window position, window bytes to copy */
+  let len;                    /* match length, unused bytes */
+  let dist;                   /* match distance */
+  let from;                   /* where to copy match from */
+  let from_source;
+
+
+  let input, output; // JS specific, because we have no pointers
+
+  /* copy state to local variables */
+  const state = strm.state;
+  //here = state.here;
+  _in = strm.next_in;
+  input = strm.input;
+  last = _in + (strm.avail_in - 5);
+  _out = strm.next_out;
+  output = strm.output;
+  beg = _out - (start - strm.avail_out);
+  end = _out + (strm.avail_out - 257);
+//#ifdef INFLATE_STRICT
+  dmax = state.dmax;
+//#endif
+  wsize = state.wsize;
+  whave = state.whave;
+  wnext = state.wnext;
+  s_window = state.window;
+  hold = state.hold;
+  bits = state.bits;
+  lcode = state.lencode;
+  dcode = state.distcode;
+  lmask = (1 << state.lenbits) - 1;
+  dmask = (1 << state.distbits) - 1;
+
+
+  /* decode literals and length/distances until end-of-block or not enough
+     input data or output space */
+
+  top:
+  do {
+    if (bits < 15) {
+      hold += input[_in++] << bits;
+      bits += 8;
+      hold += input[_in++] << bits;
+      bits += 8;
+    }
+
+    here = lcode[hold & lmask];
+
+    dolen:
+    for (;;) { // Goto emulation
+      op = here >>> 24/*here.bits*/;
+      hold >>>= op;
+      bits -= op;
+      op = (here >>> 16) & 0xff/*here.op*/;
+      if (op === 0) {                          /* literal */
+        //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ?
+        //        "inflate:         literal '%c'\n" :
+        //        "inflate:         literal 0x%02x\n", here.val));
+        output[_out++] = here & 0xffff/*here.val*/;
+      }
+      else if (op & 16) {                     /* length base */
+        len = here & 0xffff/*here.val*/;
+        op &= 15;                           /* number of extra bits */
+        if (op) {
+          if (bits < op) {
+            hold += input[_in++] << bits;
+            bits += 8;
+          }
+          len += hold & ((1 << op) - 1);
+          hold >>>= op;
+          bits -= op;
+        }
+        //Tracevv((stderr, "inflate:         length %u\n", len));
+        if (bits < 15) {
+          hold += input[_in++] << bits;
+          bits += 8;
+          hold += input[_in++] << bits;
+          bits += 8;
+        }
+        here = dcode[hold & dmask];
+
+        dodist:
+        for (;;) { // goto emulation
+          op = here >>> 24/*here.bits*/;
+          hold >>>= op;
+          bits -= op;
+          op = (here >>> 16) & 0xff/*here.op*/;
+
+          if (op & 16) {                      /* distance base */
+            dist = here & 0xffff/*here.val*/;
+            op &= 15;                       /* number of extra bits */
+            if (bits < op) {
+              hold += input[_in++] << bits;
+              bits += 8;
+              if (bits < op) {
+                hold += input[_in++] << bits;
+                bits += 8;
+              }
+            }
+            dist += hold & ((1 << op) - 1);
+//#ifdef INFLATE_STRICT
+            if (dist > dmax) {
+              strm.msg = 'invalid distance too far back';
+              state.mode = BAD;
+              break top;
+            }
+//#endif
+            hold >>>= op;
+            bits -= op;
+            //Tracevv((stderr, "inflate:         distance %u\n", dist));
+            op = _out - beg;                /* max distance in output */
+            if (dist > op) {                /* see if copy from window */
+              op = dist - op;               /* distance back in window */
+              if (op > whave) {
+                if (state.sane) {
+                  strm.msg = 'invalid distance too far back';
+                  state.mode = BAD;
+                  break top;
+                }
+
+// (!) This block is disabled in zlib defaults,
+// don't enable it for binary compatibility
+//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR
+//                if (len <= op - whave) {
+//                  do {
+//                    output[_out++] = 0;
+//                  } while (--len);
+//                  continue top;
+//                }
+//                len -= op - whave;
+//                do {
+//                  output[_out++] = 0;
+//                } while (--op > whave);
+//                if (op === 0) {
+//                  from = _out - dist;
+//                  do {
+//                    output[_out++] = output[from++];
+//                  } while (--len);
+//                  continue top;
+//                }
+//#endif
+              }
+              from = 0; // window index
+              from_source = s_window;
+              if (wnext === 0) {           /* very common case */
+                from += wsize - op;
+                if (op < len) {         /* some from window */
+                  len -= op;
+                  do {
+                    output[_out++] = s_window[from++];
+                  } while (--op);
+                  from = _out - dist;  /* rest from output */
+                  from_source = output;
+                }
+              }
+              else if (wnext < op) {      /* wrap around window */
+                from += wsize + wnext - op;
+                op -= wnext;
+                if (op < len) {         /* some from end of window */
+                  len -= op;
+                  do {
+                    output[_out++] = s_window[from++];
+                  } while (--op);
+                  from = 0;
+                  if (wnext < len) {  /* some from start of window */
+                    op = wnext;
+                    len -= op;
+                    do {
+                      output[_out++] = s_window[from++];
+                    } while (--op);
+                    from = _out - dist;      /* rest from output */
+                    from_source = output;
+                  }
+                }
+              }
+              else {                      /* contiguous in window */
+                from += wnext - op;
+                if (op < len) {         /* some from window */
+                  len -= op;
+                  do {
+                    output[_out++] = s_window[from++];
+                  } while (--op);
+                  from = _out - dist;  /* rest from output */
+                  from_source = output;
+                }
+              }
+              while (len > 2) {
+                output[_out++] = from_source[from++];
+                output[_out++] = from_source[from++];
+                output[_out++] = from_source[from++];
+                len -= 3;
+              }
+              if (len) {
+                output[_out++] = from_source[from++];
+                if (len > 1) {
+                  output[_out++] = from_source[from++];
+                }
+              }
+            }
+            else {
+              from = _out - dist;          /* copy direct from output */
+              do {                        /* minimum length is three */
+                output[_out++] = output[from++];
+                output[_out++] = output[from++];
+                output[_out++] = output[from++];
+                len -= 3;
+              } while (len > 2);
+              if (len) {
+                output[_out++] = output[from++];
+                if (len > 1) {
+                  output[_out++] = output[from++];
+                }
+              }
+            }
+          }
+          else if ((op & 64) === 0) {          /* 2nd level distance code */
+            here = dcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))];
+            continue dodist;
+          }
+          else {
+            strm.msg = 'invalid distance code';
+            state.mode = BAD;
+            break top;
+          }
+
+          break; // need to emulate goto via "continue"
+        }
+      }
+      else if ((op & 64) === 0) {              /* 2nd level length code */
+        here = lcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))];
+        continue dolen;
+      }
+      else if (op & 32) {                     /* end-of-block */
+        //Tracevv((stderr, "inflate:         end of block\n"));
+        state.mode = TYPE;
+        break top;
+      }
+      else {
+        strm.msg = 'invalid literal/length code';
+        state.mode = BAD;
+        break top;
+      }
+
+      break; // need to emulate goto via "continue"
+    }
+  } while (_in < last && _out < end);
+
+  /* return unused bytes (on entry, bits < 8, so in won't go too far back) */
+  len = bits >> 3;
+  _in -= len;
+  bits -= len << 3;
+  hold &= (1 << bits) - 1;
+
+  /* update state and return */
+  strm.next_in = _in;
+  strm.next_out = _out;
+  strm.avail_in = (_in < last ? 5 + (last - _in) : 5 - (_in - last));
+  strm.avail_out = (_out < end ? 257 + (end - _out) : 257 - (_out - end));
+  state.hold = hold;
+  state.bits = bits;
+  return;
+};
+
+// (C) 1995-2013 Jean-loup Gailly and Mark Adler
+// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not
+//   claim that you wrote the original software. If you use this software
+//   in a product, an acknowledgment in the product documentation would be
+//   appreciated but is not required.
+// 2. Altered source versions must be plainly marked as such, and must not be
+//   misrepresented as being the original software.
+// 3. This notice may not be removed or altered from any source distribution.
+
+const MAXBITS = 15;
+const ENOUGH_LENS = 852;
+const ENOUGH_DISTS = 592;
+//const ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS);
+
+const CODES = 0;
+const LENS = 1;
+const DISTS = 2;
+
+const lbase = new Uint16Array([ /* Length codes 257..285 base */
+  3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
+  35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0
+]);
+
+const lext = new Uint8Array([ /* Length codes 257..285 extra */
+  16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18,
+  19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 72, 78
+]);
+
+const dbase = new Uint16Array([ /* Distance codes 0..29 base */
+  1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
+  257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,
+  8193, 12289, 16385, 24577, 0, 0
+]);
+
+const dext = new Uint8Array([ /* Distance codes 0..29 extra */
+  16, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22,
+  23, 23, 24, 24, 25, 25, 26, 26, 27, 27,
+  28, 28, 29, 29, 64, 64
+]);
+
+const inflate_table = (type, lens, lens_index, codes, table, table_index, work, opts) =>
+{
+  const bits = opts.bits;
+      //here = opts.here; /* table entry for duplication */
+
+  let len = 0;               /* a code's length in bits */
+  let sym = 0;               /* index of code symbols */
+  let min = 0, max = 0;          /* minimum and maximum code lengths */
+  let root = 0;              /* number of index bits for root table */
+  let curr = 0;              /* number of index bits for current table */
+  let drop = 0;              /* code bits to drop for sub-table */
+  let left = 0;                   /* number of prefix codes available */
+  let used = 0;              /* code entries in table used */
+  let huff = 0;              /* Huffman code */
+  let incr;              /* for incrementing code, index */
+  let fill;              /* index for replicating entries */
+  let low;               /* low bits for current root entry */
+  let mask;              /* mask for low root bits */
+  let next;             /* next available space in table */
+  let base = null;     /* base value table to use */
+  let base_index = 0;
+//  let shoextra;    /* extra bits table to use */
+  let end;                    /* use base and extra for symbol > end */
+  const count = new Uint16Array(MAXBITS + 1); //[MAXBITS+1];    /* number of codes of each length */
+  const offs = new Uint16Array(MAXBITS + 1); //[MAXBITS+1];     /* offsets in table for each length */
+  let extra = null;
+  let extra_index = 0;
+
+  let here_bits, here_op, here_val;
+
+  /*
+   Process a set of code lengths to create a canonical Huffman code.  The
+   code lengths are lens[0..codes-1].  Each length corresponds to the
+   symbols 0..codes-1.  The Huffman code is generated by first sorting the
+   symbols by length from short to long, and retaining the symbol order
+   for codes with equal lengths.  Then the code starts with all zero bits
+   for the first code of the shortest length, and the codes are integer
+   increments for the same length, and zeros are appended as the length
+   increases.  For the deflate format, these bits are stored backwards
+   from their more natural integer increment ordering, and so when the
+   decoding tables are built in the large loop below, the integer codes
+   are incremented backwards.
+
+   This routine assumes, but does not check, that all of the entries in
+   lens[] are in the range 0..MAXBITS.  The caller must assure this.
+   1..MAXBITS is interpreted as that code length.  zero means that that
+   symbol does not occur in this code.
+
+   The codes are sorted by computing a count of codes for each length,
+   creating from that a table of starting indices for each length in the
+   sorted table, and then entering the symbols in order in the sorted
+   table.  The sorted table is work[], with that space being provided by
+   the caller.
+
+   The length counts are used for other purposes as well, i.e. finding
+   the minimum and maximum length codes, determining if there are any
+   codes at all, checking for a valid set of lengths, and looking ahead
+   at length counts to determine sub-table sizes when building the
+   decoding tables.
+   */
+
+  /* accumulate lengths for codes (assumes lens[] all in 0..MAXBITS) */
+  for (len = 0; len <= MAXBITS; len++) {
+    count[len] = 0;
+  }
+  for (sym = 0; sym < codes; sym++) {
+    count[lens[lens_index + sym]]++;
+  }
+
+  /* bound code lengths, force root to be within code lengths */
+  root = bits;
+  for (max = MAXBITS; max >= 1; max--) {
+    if (count[max] !== 0) { break; }
+  }
+  if (root > max) {
+    root = max;
+  }
+  if (max === 0) {                     /* no symbols to code at all */
+    //table.op[opts.table_index] = 64;  //here.op = (var char)64;    /* invalid code marker */
+    //table.bits[opts.table_index] = 1;   //here.bits = (var char)1;
+    //table.val[opts.table_index++] = 0;   //here.val = (var short)0;
+    table[table_index++] = (1 << 24) | (64 << 16) | 0;
+
+
+    //table.op[opts.table_index] = 64;
+    //table.bits[opts.table_index] = 1;
+    //table.val[opts.table_index++] = 0;
+    table[table_index++] = (1 << 24) | (64 << 16) | 0;
+
+    opts.bits = 1;
+    return 0;     /* no symbols, but wait for decoding to report error */
+  }
+  for (min = 1; min < max; min++) {
+    if (count[min] !== 0) { break; }
+  }
+  if (root < min) {
+    root = min;
+  }
+
+  /* check for an over-subscribed or incomplete set of lengths */
+  left = 1;
+  for (len = 1; len <= MAXBITS; len++) {
+    left <<= 1;
+    left -= count[len];
+    if (left < 0) {
+      return -1;
+    }        /* over-subscribed */
+  }
+  if (left > 0 && (type === CODES || max !== 1)) {
+    return -1;                      /* incomplete set */
+  }
+
+  /* generate offsets into symbol table for each length for sorting */
+  offs[1] = 0;
+  for (len = 1; len < MAXBITS; len++) {
+    offs[len + 1] = offs[len] + count[len];
+  }
+
+  /* sort symbols by length, by symbol order within each length */
+  for (sym = 0; sym < codes; sym++) {
+    if (lens[lens_index + sym] !== 0) {
+      work[offs[lens[lens_index + sym]]++] = sym;
+    }
+  }
+
+  /*
+   Create and fill in decoding tables.  In this loop, the table being
+   filled is at next and has curr index bits.  The code being used is huff
+   with length len.  That code is converted to an index by dropping drop
+   bits off of the bottom.  For codes where len is less than drop + curr,
+   those top drop + curr - len bits are incremented through all values to
+   fill the table with replicated entries.
+
+   root is the number of index bits for the root table.  When len exceeds
+   root, sub-tables are created pointed to by the root entry with an index
+   of the low root bits of huff.  This is saved in low to check for when a
+   new sub-table should be started.  drop is zero when the root table is
+   being filled, and drop is root when sub-tables are being filled.
+
+   When a new sub-table is needed, it is necessary to look ahead in the
+   code lengths to determine what size sub-table is needed.  The length
+   counts are used for this, and so count[] is decremented as codes are
+   entered in the tables.
+
+   used keeps track of how many table entries have been allocated from the
+   provided *table space.  It is checked for LENS and DIST tables against
+   the constants ENOUGH_LENS and ENOUGH_DISTS to guard against changes in
+   the initial root table size constants.  See the comments in inftrees.h
+   for more information.
+
+   sym increments through all symbols, and the loop terminates when
+   all codes of length max, i.e. all codes, have been processed.  This
+   routine permits incomplete codes, so another loop after this one fills
+   in the rest of the decoding tables with invalid code markers.
+   */
+
+  /* set up for code type */
+  // poor man optimization - use if-else instead of switch,
+  // to avoid deopts in old v8
+  if (type === CODES) {
+    base = extra = work;    /* dummy value--not used */
+    end = 19;
+
+  } else if (type === LENS) {
+    base = lbase;
+    base_index -= 257;
+    extra = lext;
+    extra_index -= 257;
+    end = 256;
+
+  } else {                    /* DISTS */
+    base = dbase;
+    extra = dext;
+    end = -1;
+  }
+
+  /* initialize opts for loop */
+  huff = 0;                   /* starting code */
+  sym = 0;                    /* starting code symbol */
+  len = min;                  /* starting code length */
+  next = table_index;              /* current table to fill in */
+  curr = root;                /* current table index bits */
+  drop = 0;                   /* current bits to drop from code for index */
+  low = -1;                   /* trigger new sub-table when len > root */
+  used = 1 << root;          /* use root table entries */
+  mask = used - 1;            /* mask for comparing low */
+
+  /* check available table space */
+  if ((type === LENS && used > ENOUGH_LENS) ||
+    (type === DISTS && used > ENOUGH_DISTS)) {
+    return 1;
+  }
+
+  /* process all codes and make table entries */
+  for (;;) {
+    /* create table entry */
+    here_bits = len - drop;
+    if (work[sym] < end) {
+      here_op = 0;
+      here_val = work[sym];
+    }
+    else if (work[sym] > end) {
+      here_op = extra[extra_index + work[sym]];
+      here_val = base[base_index + work[sym]];
+    }
+    else {
+      here_op = 32 + 64;         /* end of block */
+      here_val = 0;
+    }
+
+    /* replicate for those indices with low len bits equal to huff */
+    incr = 1 << (len - drop);
+    fill = 1 << curr;
+    min = fill;                 /* save offset to next table */
+    do {
+      fill -= incr;
+      table[next + (huff >> drop) + fill] = (here_bits << 24) | (here_op << 16) | here_val |0;
+    } while (fill !== 0);
+
+    /* backwards increment the len-bit code huff */
+    incr = 1 << (len - 1);
+    while (huff & incr) {
+      incr >>= 1;
+    }
+    if (incr !== 0) {
+      huff &= incr - 1;
+      huff += incr;
+    } else {
+      huff = 0;
+    }
+
+    /* go to next symbol, update count, len */
+    sym++;
+    if (--count[len] === 0) {
+      if (len === max) { break; }
+      len = lens[lens_index + work[sym]];
+    }
+
+    /* create new sub-table if needed */
+    if (len > root && (huff & mask) !== low) {
+      /* if first time, transition to sub-tables */
+      if (drop === 0) {
+        drop = root;
+      }
+
+      /* increment past last table */
+      next += min;            /* here min is 1 << curr */
+
+      /* determine length of next table */
+      curr = len - drop;
+      left = 1 << curr;
+      while (curr + drop < max) {
+        left -= count[curr + drop];
+        if (left <= 0) { break; }
+        curr++;
+        left <<= 1;
+      }
+
+      /* check for enough space */
+      used += 1 << curr;
+      if ((type === LENS && used > ENOUGH_LENS) ||
+        (type === DISTS && used > ENOUGH_DISTS)) {
+        return 1;
+      }
+
+      /* point entry in root table to sub-table */
+      low = huff & mask;
+      /*table.op[low] = curr;
+      table.bits[low] = root;
+      table.val[low] = next - opts.table_index;*/
+      table[low] = (root << 24) | (curr << 16) | (next - table_index) |0;
+    }
+  }
+
+  /* fill in remaining table entry if code is incomplete (guaranteed to have
+   at most one remaining entry, since if the code is incomplete, the
+   maximum code length that was allowed to get this far is one bit) */
+  if (huff !== 0) {
+    //table.op[next + huff] = 64;            /* invalid code marker */
+    //table.bits[next + huff] = len - drop;
+    //table.val[next + huff] = 0;
+    table[next + huff] = ((len - drop) << 24) | (64 << 16) |0;
+  }
+
+  /* set return parameters */
+  //opts.table_index += used;
+  opts.bits = root;
+  return 0;
+};
+
+
+var inftrees = inflate_table;
+
+// (C) 1995-2013 Jean-loup Gailly and Mark Adler
+// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not
+//   claim that you wrote the original software. If you use this software
+//   in a product, an acknowledgment in the product documentation would be
+//   appreciated but is not required.
+// 2. Altered source versions must be plainly marked as such, and must not be
+//   misrepresented as being the original software.
+// 3. This notice may not be removed or altered from any source distribution.
+
+
+
+
+
+
+const CODES$1 = 0;
+const LENS$1 = 1;
+const DISTS$1 = 2;
+
+/* Public constants ==========================================================*/
+/* ===========================================================================*/
+
+const {
+  Z_FINISH: Z_FINISH$2, Z_BLOCK: Z_BLOCK$1, Z_TREES,
+  Z_OK: Z_OK$2, Z_STREAM_END: Z_STREAM_END$2, Z_NEED_DICT, Z_STREAM_ERROR: Z_STREAM_ERROR$1, Z_DATA_ERROR: Z_DATA_ERROR$1, Z_MEM_ERROR, Z_BUF_ERROR: Z_BUF_ERROR$1,
+  Z_DEFLATED: Z_DEFLATED$2
+} = constants;
+
+
+/* STATES ====================================================================*/
+/* ===========================================================================*/
+
+
+const    HEAD = 1;       /* i: waiting for magic header */
+const    FLAGS = 2;      /* i: waiting for method and flags (gzip) */
+const    TIME = 3;       /* i: waiting for modification time (gzip) */
+const    OS = 4;         /* i: waiting for extra flags and operating system (gzip) */
+const    EXLEN = 5;      /* i: waiting for extra length (gzip) */
+const    EXTRA = 6;      /* i: waiting for extra bytes (gzip) */
+const    NAME = 7;       /* i: waiting for end of file name (gzip) */
+const    COMMENT = 8;    /* i: waiting for end of comment (gzip) */
+const    HCRC = 9;       /* i: waiting for header crc (gzip) */
+const    DICTID = 10;    /* i: waiting for dictionary check value */
+const    DICT = 11;      /* waiting for inflateSetDictionary() call */
+const        TYPE$1 = 12;      /* i: waiting for type bits, including last-flag bit */
+const        TYPEDO = 13;    /* i: same, but skip check to exit inflate on new block */
+const        STORED = 14;    /* i: waiting for stored size (length and complement) */
+const        COPY_ = 15;     /* i/o: same as COPY below, but only first time in */
+const        COPY = 16;      /* i/o: waiting for input or output to copy stored block */
+const        TABLE = 17;     /* i: waiting for dynamic block table lengths */
+const        LENLENS = 18;   /* i: waiting for code length code lengths */
+const        CODELENS = 19;  /* i: waiting for length/lit and distance code lengths */
+const            LEN_ = 20;      /* i: same as LEN below, but only first time in */
+const            LEN = 21;       /* i: waiting for length/lit/eob code */
+const            LENEXT = 22;    /* i: waiting for length extra bits */
+const            DIST = 23;      /* i: waiting for distance code */
+const            DISTEXT = 24;   /* i: waiting for distance extra bits */
+const            MATCH = 25;     /* o: waiting for output space to copy string */
+const            LIT = 26;       /* o: waiting for output space to write literal */
+const    CHECK = 27;     /* i: waiting for 32-bit check value */
+const    LENGTH = 28;    /* i: waiting for 32-bit length (gzip) */
+const    DONE = 29;      /* finished check, done -- remain here until reset */
+const    BAD$1 = 30;       /* got a data error -- remain here until reset */
+const    MEM = 31;       /* got an inflate() memory error -- remain here until reset */
+const    SYNC = 32;      /* looking for synchronization bytes to restart inflate() */
+
+/* ===========================================================================*/
+
+
+
+const ENOUGH_LENS$1 = 852;
+const ENOUGH_DISTS$1 = 592;
+//const ENOUGH =  (ENOUGH_LENS+ENOUGH_DISTS);
+
+const MAX_WBITS$1 = 15;
+/* 32K LZ77 window */
+const DEF_WBITS = MAX_WBITS$1;
+
+
+const zswap32 = (q) => {
+
+  return  (((q >>> 24) & 0xff) +
+          ((q >>> 8) & 0xff00) +
+          ((q & 0xff00) << 8) +
+          ((q & 0xff) << 24));
+};
+
+
+function InflateState() {
+  this.mode = 0;             /* current inflate mode */
+  this.last = false;          /* true if processing last block */
+  this.wrap = 0;              /* bit 0 true for zlib, bit 1 true for gzip */
+  this.havedict = false;      /* true if dictionary provided */
+  this.flags = 0;             /* gzip header method and flags (0 if zlib) */
+  this.dmax = 0;              /* zlib header max distance (INFLATE_STRICT) */
+  this.check = 0;             /* protected copy of check value */
+  this.total = 0;             /* protected copy of output count */
+  // TODO: may be {}
+  this.head = null;           /* where to save gzip header information */
+
+  /* sliding window */
+  this.wbits = 0;             /* log base 2 of requested window size */
+  this.wsize = 0;             /* window size or zero if not using window */
+  this.whave = 0;             /* valid bytes in the window */
+  this.wnext = 0;             /* window write index */
+  this.window = null;         /* allocated sliding window, if needed */
+
+  /* bit accumulator */
+  this.hold = 0;              /* input bit accumulator */
+  this.bits = 0;              /* number of bits in "in" */
+
+  /* for string and stored block copying */
+  this.length = 0;            /* literal or length of data to copy */
+  this.offset = 0;            /* distance back to copy string from */
+
+  /* for table and code decoding */
+  this.extra = 0;             /* extra bits needed */
+
+  /* fixed and dynamic code tables */
+  this.lencode = null;          /* starting table for length/literal codes */
+  this.distcode = null;         /* starting table for distance codes */
+  this.lenbits = 0;           /* index bits for lencode */
+  this.distbits = 0;          /* index bits for distcode */
+
+  /* dynamic table building */
+  this.ncode = 0;             /* number of code length code lengths */
+  this.nlen = 0;              /* number of length code lengths */
+  this.ndist = 0;             /* number of distance code lengths */
+  this.have = 0;              /* number of code lengths in lens[] */
+  this.next = null;              /* next available space in codes[] */
+
+  this.lens = new Uint16Array(320); /* temporary storage for code lengths */
+  this.work = new Uint16Array(288); /* work area for code table building */
+
+  /*
+   because we don't have pointers in js, we use lencode and distcode directly
+   as buffers so we don't need codes
+  */
+  //this.codes = new Int32Array(ENOUGH);       /* space for code tables */
+  this.lendyn = null;              /* dynamic table for length/literal codes (JS specific) */
+  this.distdyn = null;             /* dynamic table for distance codes (JS specific) */
+  this.sane = 0;                   /* if false, allow invalid distance too far */
+  this.back = 0;                   /* bits back of last unprocessed length/lit */
+  this.was = 0;                    /* initial length of match */
+}
+
+
+const inflateResetKeep = (strm) => {
+
+  if (!strm || !strm.state) { return Z_STREAM_ERROR$1; }
+  const state = strm.state;
+  strm.total_in = strm.total_out = state.total = 0;
+  strm.msg = ''; /*Z_NULL*/
+  if (state.wrap) {       /* to support ill-conceived Java test suite */
+    strm.adler = state.wrap & 1;
+  }
+  state.mode = HEAD;
+  state.last = 0;
+  state.havedict = 0;
+  state.dmax = 32768;
+  state.head = null/*Z_NULL*/;
+  state.hold = 0;
+  state.bits = 0;
+  //state.lencode = state.distcode = state.next = state.codes;
+  state.lencode = state.lendyn = new Int32Array(ENOUGH_LENS$1);
+  state.distcode = state.distdyn = new Int32Array(ENOUGH_DISTS$1);
+
+  state.sane = 1;
+  state.back = -1;
+  //Tracev((stderr, "inflate: reset\n"));
+  return Z_OK$2;
+};
+
+
+const inflateReset = (strm) => {
+
+  if (!strm || !strm.state) { return Z_STREAM_ERROR$1; }
+  const state = strm.state;
+  state.wsize = 0;
+  state.whave = 0;
+  state.wnext = 0;
+  return inflateResetKeep(strm);
+
+};
+
+
+const inflateReset2 = (strm, windowBits) => {
+  let wrap;
+
+  /* get the state */
+  if (!strm || !strm.state) { return Z_STREAM_ERROR$1; }
+  const state = strm.state;
+
+  /* extract wrap request from windowBits parameter */
+  if (windowBits < 0) {
+    wrap = 0;
+    windowBits = -windowBits;
+  }
+  else {
+    wrap = (windowBits >> 4) + 1;
+    if (windowBits < 48) {
+      windowBits &= 15;
+    }
+  }
+
+  /* set number of window bits, free window if different */
+  if (windowBits && (windowBits < 8 || windowBits > 15)) {
+    return Z_STREAM_ERROR$1;
+  }
+  if (state.window !== null && state.wbits !== windowBits) {
+    state.window = null;
+  }
+
+  /* update state and reset the rest of it */
+  state.wrap = wrap;
+  state.wbits = windowBits;
+  return inflateReset(strm);
+};
+
+
+const inflateInit2 = (strm, windowBits) => {
+
+  if (!strm) { return Z_STREAM_ERROR$1; }
+  //strm.msg = Z_NULL;                 /* in case we return an error */
+
+  const state = new InflateState();
+
+  //if (state === Z_NULL) return Z_MEM_ERROR;
+  //Tracev((stderr, "inflate: allocated\n"));
+  strm.state = state;
+  state.window = null/*Z_NULL*/;
+  const ret = inflateReset2(strm, windowBits);
+  if (ret !== Z_OK$2) {
+    strm.state = null/*Z_NULL*/;
+  }
+  return ret;
+};
+
+
+const inflateInit = (strm) => {
+
+  return inflateInit2(strm, DEF_WBITS);
+};
+
+
+/*
+ Return state with length and distance decoding tables and index sizes set to
+ fixed code decoding.  Normally this returns fixed tables from inffixed.h.
+ If BUILDFIXED is defined, then instead this routine builds the tables the
+ first time it's called, and returns those tables the first time and
+ thereafter.  This reduces the size of the code by about 2K bytes, in
+ exchange for a little execution time.  However, BUILDFIXED should not be
+ used for threaded applications, since the rewriting of the tables and virgin
+ may not be thread-safe.
+ */
+let virgin = true;
+
+let lenfix, distfix; // We have no pointers in JS, so keep tables separate
+
+
+const fixedtables = (state) => {
+
+  /* build fixed huffman tables if first call (may not be thread safe) */
+  if (virgin) {
+    lenfix = new Int32Array(512);
+    distfix = new Int32Array(32);
+
+    /* literal/length table */
+    let sym = 0;
+    while (sym < 144) { state.lens[sym++] = 8; }
+    while (sym < 256) { state.lens[sym++] = 9; }
+    while (sym < 280) { state.lens[sym++] = 7; }
+    while (sym < 288) { state.lens[sym++] = 8; }
+
+    inftrees(LENS$1,  state.lens, 0, 288, lenfix,   0, state.work, { bits: 9 });
+
+    /* distance table */
+    sym = 0;
+    while (sym < 32) { state.lens[sym++] = 5; }
+
+    inftrees(DISTS$1, state.lens, 0, 32,   distfix, 0, state.work, { bits: 5 });
+
+    /* do this just once */
+    virgin = false;
+  }
+
+  state.lencode = lenfix;
+  state.lenbits = 9;
+  state.distcode = distfix;
+  state.distbits = 5;
+};
+
+
+/*
+ Update the window with the last wsize (normally 32K) bytes written before
+ returning.  If window does not exist yet, create it.  This is only called
+ when a window is already in use, or when output has been written during this
+ inflate call, but the end of the deflate stream has not been reached yet.
+ It is also called to create a window for dictionary data when a dictionary
+ is loaded.
+
+ Providing output buffers larger than 32K to inflate() should provide a speed
+ advantage, since only the last 32K of output is copied to the sliding window
+ upon return from inflate(), and since all distances after the first 32K of
+ output will fall in the output data, making match copies simpler and faster.
+ The advantage may be dependent on the size of the processor's data caches.
+ */
+const updatewindow = (strm, src, end, copy) => {
+
+  let dist;
+  const state = strm.state;
+
+  /* if it hasn't been done already, allocate space for the window */
+  if (state.window === null) {
+    state.wsize = 1 << state.wbits;
+    state.wnext = 0;
+    state.whave = 0;
+
+    state.window = new Uint8Array(state.wsize);
+  }
+
+  /* copy state->wsize or less output bytes into the circular window */
+  if (copy >= state.wsize) {
+    state.window.set(src.subarray(end - state.wsize, end), 0);
+    state.wnext = 0;
+    state.whave = state.wsize;
+  }
+  else {
+    dist = state.wsize - state.wnext;
+    if (dist > copy) {
+      dist = copy;
+    }
+    //zmemcpy(state->window + state->wnext, end - copy, dist);
+    state.window.set(src.subarray(end - copy, end - copy + dist), state.wnext);
+    copy -= dist;
+    if (copy) {
+      //zmemcpy(state->window, end - copy, copy);
+      state.window.set(src.subarray(end - copy, end), 0);
+      state.wnext = copy;
+      state.whave = state.wsize;
+    }
+    else {
+      state.wnext += dist;
+      if (state.wnext === state.wsize) { state.wnext = 0; }
+      if (state.whave < state.wsize) { state.whave += dist; }
+    }
+  }
+  return 0;
+};
+
+
+const inflate = (strm, flush) => {
+
+  let state;
+  let input, output;          // input/output buffers
+  let next;                   /* next input INDEX */
+  let put;                    /* next output INDEX */
+  let have, left;             /* available input and output */
+  let hold;                   /* bit buffer */
+  let bits;                   /* bits in bit buffer */
+  let _in, _out;              /* save starting available input and output */
+  let copy;                   /* number of stored or match bytes to copy */
+  let from;                   /* where to copy match bytes from */
+  let from_source;
+  let here = 0;               /* current decoding table entry */
+  let here_bits, here_op, here_val; // paked "here" denormalized (JS specific)
+  //let last;                   /* parent table entry */
+  let last_bits, last_op, last_val; // paked "last" denormalized (JS specific)
+  let len;                    /* length to copy for repeats, bits to drop */
+  let ret;                    /* return code */
+  const hbuf = new Uint8Array(4);    /* buffer for gzip header crc calculation */
+  let opts;
+
+  let n; // temporary variable for NEED_BITS
+
+  const order = /* permutation of code lengths */
+    new Uint8Array([ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ]);
+
+
+  if (!strm || !strm.state || !strm.output ||
+      (!strm.input && strm.avail_in !== 0)) {
+    return Z_STREAM_ERROR$1;
+  }
+
+  state = strm.state;
+  if (state.mode === TYPE$1) { state.mode = TYPEDO; }    /* skip check */
+
+
+  //--- LOAD() ---
+  put = strm.next_out;
+  output = strm.output;
+  left = strm.avail_out;
+  next = strm.next_in;
+  input = strm.input;
+  have = strm.avail_in;
+  hold = state.hold;
+  bits = state.bits;
+  //---
+
+  _in = have;
+  _out = left;
+  ret = Z_OK$2;
+
+  inf_leave: // goto emulation
+  for (;;) {
+    switch (state.mode) {
+      case HEAD:
+        if (state.wrap === 0) {
+          state.mode = TYPEDO;
+          break;
+        }
+        //=== NEEDBITS(16);
+        while (bits < 16) {
+          if (have === 0) { break inf_leave; }
+          have--;
+          hold += input[next++] << bits;
+          bits += 8;
+        }
+        //===//
+        if ((state.wrap & 2) && hold === 0x8b1f) {  /* gzip header */
+          state.check = 0/*crc32(0L, Z_NULL, 0)*/;
+          //=== CRC2(state.check, hold);
+          hbuf[0] = hold & 0xff;
+          hbuf[1] = (hold >>> 8) & 0xff;
+          state.check = crc32_1(state.check, hbuf, 2, 0);
+          //===//
+
+          //=== INITBITS();
+          hold = 0;
+          bits = 0;
+          //===//
+          state.mode = FLAGS;
+          break;
+        }
+        state.flags = 0;           /* expect zlib header */
+        if (state.head) {
+          state.head.done = false;
+        }
+        if (!(state.wrap & 1) ||   /* check if zlib header allowed */
+          (((hold & 0xff)/*BITS(8)*/ << 8) + (hold >> 8)) % 31) {
+          strm.msg = 'incorrect header check';
+          state.mode = BAD$1;
+          break;
+        }
+        if ((hold & 0x0f)/*BITS(4)*/ !== Z_DEFLATED$2) {
+          strm.msg = 'unknown compression method';
+          state.mode = BAD$1;
+          break;
+        }
+        //--- DROPBITS(4) ---//
+        hold >>>= 4;
+        bits -= 4;
+        //---//
+        len = (hold & 0x0f)/*BITS(4)*/ + 8;
+        if (state.wbits === 0) {
+          state.wbits = len;
+        }
+        else if (len > state.wbits) {
+          strm.msg = 'invalid window size';
+          state.mode = BAD$1;
+          break;
+        }
+
+        // !!! pako patch. Force use `options.windowBits` if passed.
+        // Required to always use max window size by default.
+        state.dmax = 1 << state.wbits;
+        //state.dmax = 1 << len;
+
+        //Tracev((stderr, "inflate:   zlib header ok\n"));
+        strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/;
+        state.mode = hold & 0x200 ? DICTID : TYPE$1;
+        //=== INITBITS();
+        hold = 0;
+        bits = 0;
+        //===//
+        break;
+      case FLAGS:
+        //=== NEEDBITS(16); */
+        while (bits < 16) {
+          if (have === 0) { break inf_leave; }
+          have--;
+          hold += input[next++] << bits;
+          bits += 8;
+        }
+        //===//
+        state.flags = hold;
+        if ((state.flags & 0xff) !== Z_DEFLATED$2) {
+          strm.msg = 'unknown compression method';
+          state.mode = BAD$1;
+          break;
+        }
+        if (state.flags & 0xe000) {
+          strm.msg = 'unknown header flags set';
+          state.mode = BAD$1;
+          break;
+        }
+        if (state.head) {
+          state.head.text = ((hold >> 8) & 1);
+        }
+        if (state.flags & 0x0200) {
+          //=== CRC2(state.check, hold);
+          hbuf[0] = hold & 0xff;
+          hbuf[1] = (hold >>> 8) & 0xff;
+          state.check = crc32_1(state.check, hbuf, 2, 0);
+          //===//
+        }
+        //=== INITBITS();
+        hold = 0;
+        bits = 0;
+        //===//
+        state.mode = TIME;
+        /* falls through */
+      case TIME:
+        //=== NEEDBITS(32); */
+        while (bits < 32) {
+          if (have === 0) { break inf_leave; }
+          have--;
+          hold += input[next++] << bits;
+          bits += 8;
+        }
+        //===//
+        if (state.head) {
+          state.head.time = hold;
+        }
+        if (state.flags & 0x0200) {
+          //=== CRC4(state.check, hold)
+          hbuf[0] = hold & 0xff;
+          hbuf[1] = (hold >>> 8) & 0xff;
+          hbuf[2] = (hold >>> 16) & 0xff;
+          hbuf[3] = (hold >>> 24) & 0xff;
+          state.check = crc32_1(state.check, hbuf, 4, 0);
+          //===
+        }
+        //=== INITBITS();
+        hold = 0;
+        bits = 0;
+        //===//
+        state.mode = OS;
+        /* falls through */
+      case OS:
+        //=== NEEDBITS(16); */
+        while (bits < 16) {
+          if (have === 0) { break inf_leave; }
+          have--;
+          hold += input[next++] << bits;
+          bits += 8;
+        }
+        //===//
+        if (state.head) {
+          state.head.xflags = (hold & 0xff);
+          state.head.os = (hold >> 8);
+        }
+        if (state.flags & 0x0200) {
+          //=== CRC2(state.check, hold);
+          hbuf[0] = hold & 0xff;
+          hbuf[1] = (hold >>> 8) & 0xff;
+          state.check = crc32_1(state.check, hbuf, 2, 0);
+          //===//
+        }
+        //=== INITBITS();
+        hold = 0;
+        bits = 0;
+        //===//
+        state.mode = EXLEN;
+        /* falls through */
+      case EXLEN:
+        if (state.flags & 0x0400) {
+          //=== NEEDBITS(16); */
+          while (bits < 16) {
+            if (have === 0) { break inf_leave; }
+            have--;
+            hold += input[next++] << bits;
+            bits += 8;
+          }
+          //===//
+          state.length = hold;
+          if (state.head) {
+            state.head.extra_len = hold;
+          }
+          if (state.flags & 0x0200) {
+            //=== CRC2(state.check, hold);
+            hbuf[0] = hold & 0xff;
+            hbuf[1] = (hold >>> 8) & 0xff;
+            state.check = crc32_1(state.check, hbuf, 2, 0);
+            //===//
+          }
+          //=== INITBITS();
+          hold = 0;
+          bits = 0;
+          //===//
+        }
+        else if (state.head) {
+          state.head.extra = null/*Z_NULL*/;
+        }
+        state.mode = EXTRA;
+        /* falls through */
+      case EXTRA:
+        if (state.flags & 0x0400) {
+          copy = state.length;
+          if (copy > have) { copy = have; }
+          if (copy) {
+            if (state.head) {
+              len = state.head.extra_len - state.length;
+              if (!state.head.extra) {
+                // Use untyped array for more convenient processing later
+                state.head.extra = new Uint8Array(state.head.extra_len);
+              }
+              state.head.extra.set(
+                input.subarray(
+                  next,
+                  // extra field is limited to 65536 bytes
+                  // - no need for additional size check
+                  next + copy
+                ),
+                /*len + copy > state.head.extra_max - len ? state.head.extra_max : copy,*/
+                len
+              );
+              //zmemcpy(state.head.extra + len, next,
+              //        len + copy > state.head.extra_max ?
+              //        state.head.extra_max - len : copy);
+            }
+            if (state.flags & 0x0200) {
+              state.check = crc32_1(state.check, input, copy, next);
+            }
+            have -= copy;
+            next += copy;
+            state.length -= copy;
+          }
+          if (state.length) { break inf_leave; }
+        }
+        state.length = 0;
+        state.mode = NAME;
+        /* falls through */
+      case NAME:
+        if (state.flags & 0x0800) {
+          if (have === 0) { break inf_leave; }
+          copy = 0;
+          do {
+            // TODO: 2 or 1 bytes?
+            len = input[next + copy++];
+            /* use constant limit because in js we should not preallocate memory */
+            if (state.head && len &&
+                (state.length < 65536 /*state.head.name_max*/)) {
+              state.head.name += String.fromCharCode(len);
+            }
+          } while (len && copy < have);
+
+          if (state.flags & 0x0200) {
+            state.check = crc32_1(state.check, input, copy, next);
+          }
+          have -= copy;
+          next += copy;
+          if (len) { break inf_leave; }
+        }
+        else if (state.head) {
+          state.head.name = null;
+        }
+        state.length = 0;
+        state.mode = COMMENT;
+        /* falls through */
+      case COMMENT:
+        if (state.flags & 0x1000) {
+          if (have === 0) { break inf_leave; }
+          copy = 0;
+          do {
+            len = input[next + copy++];
+            /* use constant limit because in js we should not preallocate memory */
+            if (state.head && len &&
+                (state.length < 65536 /*state.head.comm_max*/)) {
+              state.head.comment += String.fromCharCode(len);
+            }
+          } while (len && copy < have);
+          if (state.flags & 0x0200) {
+            state.check = crc32_1(state.check, input, copy, next);
+          }
+          have -= copy;
+          next += copy;
+          if (len) { break inf_leave; }
+        }
+        else if (state.head) {
+          state.head.comment = null;
+        }
+        state.mode = HCRC;
+        /* falls through */
+      case HCRC:
+        if (state.flags & 0x0200) {
+          //=== NEEDBITS(16); */
+          while (bits < 16) {
+            if (have === 0) { break inf_leave; }
+            have--;
+            hold += input[next++] << bits;
+            bits += 8;
+          }
+          //===//
+          if (hold !== (state.check & 0xffff)) {
+            strm.msg = 'header crc mismatch';
+            state.mode = BAD$1;
+            break;
+          }
+          //=== INITBITS();
+          hold = 0;
+          bits = 0;
+          //===//
+        }
+        if (state.head) {
+          state.head.hcrc = ((state.flags >> 9) & 1);
+          state.head.done = true;
+        }
+        strm.adler = state.check = 0;
+        state.mode = TYPE$1;
+        break;
+      case DICTID:
+        //=== NEEDBITS(32); */
+        while (bits < 32) {
+          if (have === 0) { break inf_leave; }
+          have--;
+          hold += input[next++] << bits;
+          bits += 8;
+        }
+        //===//
+        strm.adler = state.check = zswap32(hold);
+        //=== INITBITS();
+        hold = 0;
+        bits = 0;
+        //===//
+        state.mode = DICT;
+        /* falls through */
+      case DICT:
+        if (state.havedict === 0) {
+          //--- RESTORE() ---
+          strm.next_out = put;
+          strm.avail_out = left;
+          strm.next_in = next;
+          strm.avail_in = have;
+          state.hold = hold;
+          state.bits = bits;
+          //---
+          return Z_NEED_DICT;
+        }
+        strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/;
+        state.mode = TYPE$1;
+        /* falls through */
+      case TYPE$1:
+        if (flush === Z_BLOCK$1 || flush === Z_TREES) { break inf_leave; }
+        /* falls through */
+      case TYPEDO:
+        if (state.last) {
+          //--- BYTEBITS() ---//
+          hold >>>= bits & 7;
+          bits -= bits & 7;
+          //---//
+          state.mode = CHECK;
+          break;
+        }
+        //=== NEEDBITS(3); */
+        while (bits < 3) {
+          if (have === 0) { break inf_leave; }
+          have--;
+          hold += input[next++] << bits;
+          bits += 8;
+        }
+        //===//
+        state.last = (hold & 0x01)/*BITS(1)*/;
+        //--- DROPBITS(1) ---//
+        hold >>>= 1;
+        bits -= 1;
+        //---//
+
+        switch ((hold & 0x03)/*BITS(2)*/) {
+          case 0:                             /* stored block */
+            //Tracev((stderr, "inflate:     stored block%s\n",
+            //        state.last ? " (last)" : ""));
+            state.mode = STORED;
+            break;
+          case 1:                             /* fixed block */
+            fixedtables(state);
+            //Tracev((stderr, "inflate:     fixed codes block%s\n",
+            //        state.last ? " (last)" : ""));
+            state.mode = LEN_;             /* decode codes */
+            if (flush === Z_TREES) {
+              //--- DROPBITS(2) ---//
+              hold >>>= 2;
+              bits -= 2;
+              //---//
+              break inf_leave;
+            }
+            break;
+          case 2:                             /* dynamic block */
+            //Tracev((stderr, "inflate:     dynamic codes block%s\n",
+            //        state.last ? " (last)" : ""));
+            state.mode = TABLE;
+            break;
+          case 3:
+            strm.msg = 'invalid block type';
+            state.mode = BAD$1;
+        }
+        //--- DROPBITS(2) ---//
+        hold >>>= 2;
+        bits -= 2;
+        //---//
+        break;
+      case STORED:
+        //--- BYTEBITS() ---// /* go to byte boundary */
+        hold >>>= bits & 7;
+        bits -= bits & 7;
+        //---//
+        //=== NEEDBITS(32); */
+        while (bits < 32) {
+          if (have === 0) { break inf_leave; }
+          have--;
+          hold += input[next++] << bits;
+          bits += 8;
+        }
+        //===//
+        if ((hold & 0xffff) !== ((hold >>> 16) ^ 0xffff)) {
+          strm.msg = 'invalid stored block lengths';
+          state.mode = BAD$1;
+          break;
+        }
+        state.length = hold & 0xffff;
+        //Tracev((stderr, "inflate:       stored length %u\n",
+        //        state.length));
+        //=== INITBITS();
+        hold = 0;
+        bits = 0;
+        //===//
+        state.mode = COPY_;
+        if (flush === Z_TREES) { break inf_leave; }
+        /* falls through */
+      case COPY_:
+        state.mode = COPY;
+        /* falls through */
+      case COPY:
+        copy = state.length;
+        if (copy) {
+          if (copy > have) { copy = have; }
+          if (copy > left) { copy = left; }
+          if (copy === 0) { break inf_leave; }
+          //--- zmemcpy(put, next, copy); ---
+          output.set(input.subarray(next, next + copy), put);
+          //---//
+          have -= copy;
+          next += copy;
+          left -= copy;
+          put += copy;
+          state.length -= copy;
+          break;
+        }
+        //Tracev((stderr, "inflate:       stored end\n"));
+        state.mode = TYPE$1;
+        break;
+      case TABLE:
+        //=== NEEDBITS(14); */
+        while (bits < 14) {
+          if (have === 0) { break inf_leave; }
+          have--;
+          hold += input[next++] << bits;
+          bits += 8;
+        }
+        //===//
+        state.nlen = (hold & 0x1f)/*BITS(5)*/ + 257;
+        //--- DROPBITS(5) ---//
+        hold >>>= 5;
+        bits -= 5;
+        //---//
+        state.ndist = (hold & 0x1f)/*BITS(5)*/ + 1;
+        //--- DROPBITS(5) ---//
+        hold >>>= 5;
+        bits -= 5;
+        //---//
+        state.ncode = (hold & 0x0f)/*BITS(4)*/ + 4;
+        //--- DROPBITS(4) ---//
+        hold >>>= 4;
+        bits -= 4;
+        //---//
+//#ifndef PKZIP_BUG_WORKAROUND
+        if (state.nlen > 286 || state.ndist > 30) {
+          strm.msg = 'too many length or distance symbols';
+          state.mode = BAD$1;
+          break;
+        }
+//#endif
+        //Tracev((stderr, "inflate:       table sizes ok\n"));
+        state.have = 0;
+        state.mode = LENLENS;
+        /* falls through */
+      case LENLENS:
+        while (state.have < state.ncode) {
+          //=== NEEDBITS(3);
+          while (bits < 3) {
+            if (have === 0) { break inf_leave; }
+            have--;
+            hold += input[next++] << bits;
+            bits += 8;
+          }
+          //===//
+          state.lens[order[state.have++]] = (hold & 0x07);//BITS(3);
+          //--- DROPBITS(3) ---//
+          hold >>>= 3;
+          bits -= 3;
+          //---//
+        }
+        while (state.have < 19) {
+          state.lens[order[state.have++]] = 0;
+        }
+        // We have separate tables & no pointers. 2 commented lines below not needed.
+        //state.next = state.codes;
+        //state.lencode = state.next;
+        // Switch to use dynamic table
+        state.lencode = state.lendyn;
+        state.lenbits = 7;
+
+        opts = { bits: state.lenbits };
+        ret = inftrees(CODES$1, state.lens, 0, 19, state.lencode, 0, state.work, opts);
+        state.lenbits = opts.bits;
+
+        if (ret) {
+          strm.msg = 'invalid code lengths set';
+          state.mode = BAD$1;
+          break;
+        }
+        //Tracev((stderr, "inflate:       code lengths ok\n"));
+        state.have = 0;
+        state.mode = CODELENS;
+        /* falls through */
+      case CODELENS:
+        while (state.have < state.nlen + state.ndist) {
+          for (;;) {
+            here = state.lencode[hold & ((1 << state.lenbits) - 1)];/*BITS(state.lenbits)*/
+            here_bits = here >>> 24;
+            here_op = (here >>> 16) & 0xff;
+            here_val = here & 0xffff;
+
+            if ((here_bits) <= bits) { break; }
+            //--- PULLBYTE() ---//
+            if (have === 0) { break inf_leave; }
+            have--;
+            hold += input[next++] << bits;
+            bits += 8;
+            //---//
+          }
+          if (here_val < 16) {
+            //--- DROPBITS(here.bits) ---//
+            hold >>>= here_bits;
+            bits -= here_bits;
+            //---//
+            state.lens[state.have++] = here_val;
+          }
+          else {
+            if (here_val === 16) {
+              //=== NEEDBITS(here.bits + 2);
+              n = here_bits + 2;
+              while (bits < n) {
+                if (have === 0) { break inf_leave; }
+                have--;
+                hold += input[next++] << bits;
+                bits += 8;
+              }
+              //===//
+              //--- DROPBITS(here.bits) ---//
+              hold >>>= here_bits;
+              bits -= here_bits;
+              //---//
+              if (state.have === 0) {
+                strm.msg = 'invalid bit length repeat';
+                state.mode = BAD$1;
+                break;
+              }
+              len = state.lens[state.have - 1];
+              copy = 3 + (hold & 0x03);//BITS(2);
+              //--- DROPBITS(2) ---//
+              hold >>>= 2;
+              bits -= 2;
+              //---//
+            }
+            else if (here_val === 17) {
+              //=== NEEDBITS(here.bits + 3);
+              n = here_bits + 3;
+              while (bits < n) {
+                if (have === 0) { break inf_leave; }
+                have--;
+                hold += input[next++] << bits;
+                bits += 8;
+              }
+              //===//
+              //--- DROPBITS(here.bits) ---//
+              hold >>>= here_bits;
+              bits -= here_bits;
+              //---//
+              len = 0;
+              copy = 3 + (hold & 0x07);//BITS(3);
+              //--- DROPBITS(3) ---//
+              hold >>>= 3;
+              bits -= 3;
+              //---//
+            }
+            else {
+              //=== NEEDBITS(here.bits + 7);
+              n = here_bits + 7;
+              while (bits < n) {
+                if (have === 0) { break inf_leave; }
+                have--;
+                hold += input[next++] << bits;
+                bits += 8;
+              }
+              //===//
+              //--- DROPBITS(here.bits) ---//
+              hold >>>= here_bits;
+              bits -= here_bits;
+              //---//
+              len = 0;
+              copy = 11 + (hold & 0x7f);//BITS(7);
+              //--- DROPBITS(7) ---//
+              hold >>>= 7;
+              bits -= 7;
+              //---//
+            }
+            if (state.have + copy > state.nlen + state.ndist) {
+              strm.msg = 'invalid bit length repeat';
+              state.mode = BAD$1;
+              break;
+            }
+            while (copy--) {
+              state.lens[state.have++] = len;
+            }
+          }
+        }
+
+        /* handle error breaks in while */
+        if (state.mode === BAD$1) { break; }
+
+        /* check for end-of-block code (better have one) */
+        if (state.lens[256] === 0) {
+          strm.msg = 'invalid code -- missing end-of-block';
+          state.mode = BAD$1;
+          break;
+        }
+
+        /* build code tables -- note: do not change the lenbits or distbits
+           values here (9 and 6) without reading the comments in inftrees.h
+           concerning the ENOUGH constants, which depend on those values */
+        state.lenbits = 9;
+
+        opts = { bits: state.lenbits };
+        ret = inftrees(LENS$1, state.lens, 0, state.nlen, state.lencode, 0, state.work, opts);
+        // We have separate tables & no pointers. 2 commented lines below not needed.
+        // state.next_index = opts.table_index;
+        state.lenbits = opts.bits;
+        // state.lencode = state.next;
+
+        if (ret) {
+          strm.msg = 'invalid literal/lengths set';
+          state.mode = BAD$1;
+          break;
+        }
+
+        state.distbits = 6;
+        //state.distcode.copy(state.codes);
+        // Switch to use dynamic table
+        state.distcode = state.distdyn;
+        opts = { bits: state.distbits };
+        ret = inftrees(DISTS$1, state.lens, state.nlen, state.ndist, state.distcode, 0, state.work, opts);
+        // We have separate tables & no pointers. 2 commented lines below not needed.
+        // state.next_index = opts.table_index;
+        state.distbits = opts.bits;
+        // state.distcode = state.next;
+
+        if (ret) {
+          strm.msg = 'invalid distances set';
+          state.mode = BAD$1;
+          break;
+        }
+        //Tracev((stderr, 'inflate:       codes ok\n'));
+        state.mode = LEN_;
+        if (flush === Z_TREES) { break inf_leave; }
+        /* falls through */
+      case LEN_:
+        state.mode = LEN;
+        /* falls through */
+      case LEN:
+        if (have >= 6 && left >= 258) {
+          //--- RESTORE() ---
+          strm.next_out = put;
+          strm.avail_out = left;
+          strm.next_in = next;
+          strm.avail_in = have;
+          state.hold = hold;
+          state.bits = bits;
+          //---
+          inffast(strm, _out);
+          //--- LOAD() ---
+          put = strm.next_out;
+          output = strm.output;
+          left = strm.avail_out;
+          next = strm.next_in;
+          input = strm.input;
+          have = strm.avail_in;
+          hold = state.hold;
+          bits = state.bits;
+          //---
+
+          if (state.mode === TYPE$1) {
+            state.back = -1;
+          }
+          break;
+        }
+        state.back = 0;
+        for (;;) {
+          here = state.lencode[hold & ((1 << state.lenbits) - 1)];  /*BITS(state.lenbits)*/
+          here_bits = here >>> 24;
+          here_op = (here >>> 16) & 0xff;
+          here_val = here & 0xffff;
+
+          if (here_bits <= bits) { break; }
+          //--- PULLBYTE() ---//
+          if (have === 0) { break inf_leave; }
+          have--;
+          hold += input[next++] << bits;
+          bits += 8;
+          //---//
+        }
+        if (here_op && (here_op & 0xf0) === 0) {
+          last_bits = here_bits;
+          last_op = here_op;
+          last_val = here_val;
+          for (;;) {
+            here = state.lencode[last_val +
+                    ((hold & ((1 << (last_bits + last_op)) - 1))/*BITS(last.bits + last.op)*/ >> last_bits)];
+            here_bits = here >>> 24;
+            here_op = (here >>> 16) & 0xff;
+            here_val = here & 0xffff;
+
+            if ((last_bits + here_bits) <= bits) { break; }
+            //--- PULLBYTE() ---//
+            if (have === 0) { break inf_leave; }
+            have--;
+            hold += input[next++] << bits;
+            bits += 8;
+            //---//
+          }
+          //--- DROPBITS(last.bits) ---//
+          hold >>>= last_bits;
+          bits -= last_bits;
+          //---//
+          state.back += last_bits;
+        }
+        //--- DROPBITS(here.bits) ---//
+        hold >>>= here_bits;
+        bits -= here_bits;
+        //---//
+        state.back += here_bits;
+        state.length = here_val;
+        if (here_op === 0) {
+          //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ?
+          //        "inflate:         literal '%c'\n" :
+          //        "inflate:         literal 0x%02x\n", here.val));
+          state.mode = LIT;
+          break;
+        }
+        if (here_op & 32) {
+          //Tracevv((stderr, "inflate:         end of block\n"));
+          state.back = -1;
+          state.mode = TYPE$1;
+          break;
+        }
+        if (here_op & 64) {
+          strm.msg = 'invalid literal/length code';
+          state.mode = BAD$1;
+          break;
+        }
+        state.extra = here_op & 15;
+        state.mode = LENEXT;
+        /* falls through */
+      case LENEXT:
+        if (state.extra) {
+          //=== NEEDBITS(state.extra);
+          n = state.extra;
+          while (bits < n) {
+            if (have === 0) { break inf_leave; }
+            have--;
+            hold += input[next++] << bits;
+            bits += 8;
+          }
+          //===//
+          state.length += hold & ((1 << state.extra) - 1)/*BITS(state.extra)*/;
+          //--- DROPBITS(state.extra) ---//
+          hold >>>= state.extra;
+          bits -= state.extra;
+          //---//
+          state.back += state.extra;
+        }
+        //Tracevv((stderr, "inflate:         length %u\n", state.length));
+        state.was = state.length;
+        state.mode = DIST;
+        /* falls through */
+      case DIST:
+        for (;;) {
+          here = state.distcode[hold & ((1 << state.distbits) - 1)];/*BITS(state.distbits)*/
+          here_bits = here >>> 24;
+          here_op = (here >>> 16) & 0xff;
+          here_val = here & 0xffff;
+
+          if ((here_bits) <= bits) { break; }
+          //--- PULLBYTE() ---//
+          if (have === 0) { break inf_leave; }
+          have--;
+          hold += input[next++] << bits;
+          bits += 8;
+          //---//
+        }
+        if ((here_op & 0xf0) === 0) {
+          last_bits = here_bits;
+          last_op = here_op;
+          last_val = here_val;
+          for (;;) {
+            here = state.distcode[last_val +
+                    ((hold & ((1 << (last_bits + last_op)) - 1))/*BITS(last.bits + last.op)*/ >> last_bits)];
+            here_bits = here >>> 24;
+            here_op = (here >>> 16) & 0xff;
+            here_val = here & 0xffff;
+
+            if ((last_bits + here_bits) <= bits) { break; }
+            //--- PULLBYTE() ---//
+            if (have === 0) { break inf_leave; }
+            have--;
+            hold += input[next++] << bits;
+            bits += 8;
+            //---//
+          }
+          //--- DROPBITS(last.bits) ---//
+          hold >>>= last_bits;
+          bits -= last_bits;
+          //---//
+          state.back += last_bits;
+        }
+        //--- DROPBITS(here.bits) ---//
+        hold >>>= here_bits;
+        bits -= here_bits;
+        //---//
+        state.back += here_bits;
+        if (here_op & 64) {
+          strm.msg = 'invalid distance code';
+          state.mode = BAD$1;
+          break;
+        }
+        state.offset = here_val;
+        state.extra = (here_op) & 15;
+        state.mode = DISTEXT;
+        /* falls through */
+      case DISTEXT:
+        if (state.extra) {
+          //=== NEEDBITS(state.extra);
+          n = state.extra;
+          while (bits < n) {
+            if (have === 0) { break inf_leave; }
+            have--;
+            hold += input[next++] << bits;
+            bits += 8;
+          }
+          //===//
+          state.offset += hold & ((1 << state.extra) - 1)/*BITS(state.extra)*/;
+          //--- DROPBITS(state.extra) ---//
+          hold >>>= state.extra;
+          bits -= state.extra;
+          //---//
+          state.back += state.extra;
+        }
+//#ifdef INFLATE_STRICT
+        if (state.offset > state.dmax) {
+          strm.msg = 'invalid distance too far back';
+          state.mode = BAD$1;
+          break;
+        }
+//#endif
+        //Tracevv((stderr, "inflate:         distance %u\n", state.offset));
+        state.mode = MATCH;
+        /* falls through */
+      case MATCH:
+        if (left === 0) { break inf_leave; }
+        copy = _out - left;
+        if (state.offset > copy) {         /* copy from window */
+          copy = state.offset - copy;
+          if (copy > state.whave) {
+            if (state.sane) {
+              strm.msg = 'invalid distance too far back';
+              state.mode = BAD$1;
+              break;
+            }
+// (!) This block is disabled in zlib defaults,
+// don't enable it for binary compatibility
+//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR
+//          Trace((stderr, "inflate.c too far\n"));
+//          copy -= state.whave;
+//          if (copy > state.length) { copy = state.length; }
+//          if (copy > left) { copy = left; }
+//          left -= copy;
+//          state.length -= copy;
+//          do {
+//            output[put++] = 0;
+//          } while (--copy);
+//          if (state.length === 0) { state.mode = LEN; }
+//          break;
+//#endif
+          }
+          if (copy > state.wnext) {
+            copy -= state.wnext;
+            from = state.wsize - copy;
+          }
+          else {
+            from = state.wnext - copy;
+          }
+          if (copy > state.length) { copy = state.length; }
+          from_source = state.window;
+        }
+        else {                              /* copy from output */
+          from_source = output;
+          from = put - state.offset;
+          copy = state.length;
+        }
+        if (copy > left) { copy = left; }
+        left -= copy;
+        state.length -= copy;
+        do {
+          output[put++] = from_source[from++];
+        } while (--copy);
+        if (state.length === 0) { state.mode = LEN; }
+        break;
+      case LIT:
+        if (left === 0) { break inf_leave; }
+        output[put++] = state.length;
+        left--;
+        state.mode = LEN;
+        break;
+      case CHECK:
+        if (state.wrap) {
+          //=== NEEDBITS(32);
+          while (bits < 32) {
+            if (have === 0) { break inf_leave; }
+            have--;
+            // Use '|' instead of '+' to make sure that result is signed
+            hold |= input[next++] << bits;
+            bits += 8;
+          }
+          //===//
+          _out -= left;
+          strm.total_out += _out;
+          state.total += _out;
+          if (_out) {
+            strm.adler = state.check =
+                /*UPDATE(state.check, put - _out, _out);*/
+                (state.flags ? crc32_1(state.check, output, _out, put - _out) : adler32_1(state.check, output, _out, put - _out));
+
+          }
+          _out = left;
+          // NB: crc32 stored as signed 32-bit int, zswap32 returns signed too
+          if ((state.flags ? hold : zswap32(hold)) !== state.check) {
+            strm.msg = 'incorrect data check';
+            state.mode = BAD$1;
+            break;
+          }
+          //=== INITBITS();
+          hold = 0;
+          bits = 0;
+          //===//
+          //Tracev((stderr, "inflate:   check matches trailer\n"));
+        }
+        state.mode = LENGTH;
+        /* falls through */
+      case LENGTH:
+        if (state.wrap && state.flags) {
+          //=== NEEDBITS(32);
+          while (bits < 32) {
+            if (have === 0) { break inf_leave; }
+            have--;
+            hold += input[next++] << bits;
+            bits += 8;
+          }
+          //===//
+          if (hold !== (state.total & 0xffffffff)) {
+            strm.msg = 'incorrect length check';
+            state.mode = BAD$1;
+            break;
+          }
+          //=== INITBITS();
+          hold = 0;
+          bits = 0;
+          //===//
+          //Tracev((stderr, "inflate:   length matches trailer\n"));
+        }
+        state.mode = DONE;
+        /* falls through */
+      case DONE:
+        ret = Z_STREAM_END$2;
+        break inf_leave;
+      case BAD$1:
+        ret = Z_DATA_ERROR$1;
+        break inf_leave;
+      case MEM:
+        return Z_MEM_ERROR;
+      case SYNC:
+        /* falls through */
+      default:
+        return Z_STREAM_ERROR$1;
+    }
+  }
+
+  // inf_leave <- here is real place for "goto inf_leave", emulated via "break inf_leave"
+
+  /*
+     Return from inflate(), updating the total counts and the check value.
+     If there was no progress during the inflate() call, return a buffer
+     error.  Call updatewindow() to create and/or update the window state.
+     Note: a memory error from inflate() is non-recoverable.
+   */
+
+  //--- RESTORE() ---
+  strm.next_out = put;
+  strm.avail_out = left;
+  strm.next_in = next;
+  strm.avail_in = have;
+  state.hold = hold;
+  state.bits = bits;
+  //---
+
+  if (state.wsize || (_out !== strm.avail_out && state.mode < BAD$1 &&
+                      (state.mode < CHECK || flush !== Z_FINISH$2))) {
+    if (updatewindow(strm, strm.output, strm.next_out, _out - strm.avail_out)) ;
+  }
+  _in -= strm.avail_in;
+  _out -= strm.avail_out;
+  strm.total_in += _in;
+  strm.total_out += _out;
+  state.total += _out;
+  if (state.wrap && _out) {
+    strm.adler = state.check = /*UPDATE(state.check, strm.next_out - _out, _out);*/
+      (state.flags ? crc32_1(state.check, output, _out, strm.next_out - _out) : adler32_1(state.check, output, _out, strm.next_out - _out));
+  }
+  strm.data_type = state.bits + (state.last ? 64 : 0) +
+                    (state.mode === TYPE$1 ? 128 : 0) +
+                    (state.mode === LEN_ || state.mode === COPY_ ? 256 : 0);
+  if (((_in === 0 && _out === 0) || flush === Z_FINISH$2) && ret === Z_OK$2) {
+    ret = Z_BUF_ERROR$1;
+  }
+  return ret;
+};
+
+
+const inflateEnd = (strm) => {
+
+  if (!strm || !strm.state /*|| strm->zfree == (free_func)0*/) {
+    return Z_STREAM_ERROR$1;
+  }
+
+  let state = strm.state;
+  if (state.window) {
+    state.window = null;
+  }
+  strm.state = null;
+  return Z_OK$2;
+};
+
+
+const inflateGetHeader = (strm, head) => {
+
+  /* check state */
+  if (!strm || !strm.state) { return Z_STREAM_ERROR$1; }
+  const state = strm.state;
+  if ((state.wrap & 2) === 0) { return Z_STREAM_ERROR$1; }
+
+  /* save header structure */
+  state.head = head;
+  head.done = false;
+  return Z_OK$2;
+};
+
+
+const inflateSetDictionary = (strm, dictionary) => {
+  const dictLength = dictionary.length;
+
+  let state;
+  let dictid;
+  let ret;
+
+  /* check state */
+  if (!strm /* == Z_NULL */ || !strm.state /* == Z_NULL */) { return Z_STREAM_ERROR$1; }
+  state = strm.state;
+
+  if (state.wrap !== 0 && state.mode !== DICT) {
+    return Z_STREAM_ERROR$1;
+  }
+
+  /* check for correct dictionary identifier */
+  if (state.mode === DICT) {
+    dictid = 1; /* adler32(0, null, 0)*/
+    /* dictid = adler32(dictid, dictionary, dictLength); */
+    dictid = adler32_1(dictid, dictionary, dictLength, 0);
+    if (dictid !== state.check) {
+      return Z_DATA_ERROR$1;
+    }
+  }
+  /* copy dictionary to window using updatewindow(), which will amend the
+   existing dictionary if appropriate */
+  ret = updatewindow(strm, dictionary, dictLength, dictLength);
+  if (ret) {
+    state.mode = MEM;
+    return Z_MEM_ERROR;
+  }
+  state.havedict = 1;
+  // Tracev((stderr, "inflate:   dictionary set\n"));
+  return Z_OK$2;
+};
+
+
+var inflateReset_1 = inflateReset;
+var inflateReset2_1 = inflateReset2;
+var inflateResetKeep_1 = inflateResetKeep;
+var inflateInit_1 = inflateInit;
+var inflateInit2_1 = inflateInit2;
+var inflate_2 = inflate;
+var inflateEnd_1 = inflateEnd;
+var inflateGetHeader_1 = inflateGetHeader;
+var inflateSetDictionary_1 = inflateSetDictionary;
+var inflateInfo = 'pako inflate (from Nodeca project)';
+
+/* Not implemented
+module.exports.inflateCopy = inflateCopy;
+module.exports.inflateGetDictionary = inflateGetDictionary;
+module.exports.inflateMark = inflateMark;
+module.exports.inflatePrime = inflatePrime;
+module.exports.inflateSync = inflateSync;
+module.exports.inflateSyncPoint = inflateSyncPoint;
+module.exports.inflateUndermine = inflateUndermine;
+*/
+
+var inflate_1 = {
+       inflateReset: inflateReset_1,
+       inflateReset2: inflateReset2_1,
+       inflateResetKeep: inflateResetKeep_1,
+       inflateInit: inflateInit_1,
+       inflateInit2: inflateInit2_1,
+       inflate: inflate_2,
+       inflateEnd: inflateEnd_1,
+       inflateGetHeader: inflateGetHeader_1,
+       inflateSetDictionary: inflateSetDictionary_1,
+       inflateInfo: inflateInfo
+};
+
+// (C) 1995-2013 Jean-loup Gailly and Mark Adler
+// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not
+//   claim that you wrote the original software. If you use this software
+//   in a product, an acknowledgment in the product documentation would be
+//   appreciated but is not required.
+// 2. Altered source versions must be plainly marked as such, and must not be
+//   misrepresented as being the original software.
+// 3. This notice may not be removed or altered from any source distribution.
+
+function GZheader() {
+  /* true if compressed data believed to be text */
+  this.text       = 0;
+  /* modification time */
+  this.time       = 0;
+  /* extra flags (not used when writing a gzip file) */
+  this.xflags     = 0;
+  /* operating system */
+  this.os         = 0;
+  /* pointer to extra field or Z_NULL if none */
+  this.extra      = null;
+  /* extra field length (valid if extra != Z_NULL) */
+  this.extra_len  = 0; // Actually, we don't need it in JS,
+                       // but leave for few code modifications
+
+  //
+  // Setup limits is not necessary because in js we should not preallocate memory
+  // for inflate use constant limit in 65536 bytes
+  //
+
+  /* space at extra (only when reading header) */
+  // this.extra_max  = 0;
+  /* pointer to zero-terminated file name or Z_NULL */
+  this.name       = '';
+  /* space at name (only when reading header) */
+  // this.name_max   = 0;
+  /* pointer to zero-terminated comment or Z_NULL */
+  this.comment    = '';
+  /* space at comment (only when reading header) */
+  // this.comm_max   = 0;
+  /* true if there was or will be a header crc */
+  this.hcrc       = 0;
+  /* true when done reading gzip header (not used when writing a gzip file) */
+  this.done       = false;
+}
+
+var gzheader = GZheader;
+
+const toString$1 = Object.prototype.toString;
+
+/* Public constants ==========================================================*/
+/* ===========================================================================*/
+
+const {
+  Z_NO_FLUSH: Z_NO_FLUSH$2, Z_FINISH: Z_FINISH$3,
+  Z_OK: Z_OK$3, Z_STREAM_END: Z_STREAM_END$3, Z_NEED_DICT: Z_NEED_DICT$1, Z_STREAM_ERROR: Z_STREAM_ERROR$2, Z_DATA_ERROR: Z_DATA_ERROR$2, Z_MEM_ERROR: Z_MEM_ERROR$1
+} = constants;
+
+/* ===========================================================================*/
+
+
+/**
+ * class Inflate
+ *
+ * Generic JS-style wrapper for zlib calls. If you don't need
+ * streaming behaviour - use more simple functions: [[inflate]]
+ * and [[inflateRaw]].
+ **/
+
+/* internal
+ * inflate.chunks -> Array
+ *
+ * Chunks of output data, if [[Inflate#onData]] not overridden.
+ **/
+
+/**
+ * Inflate.result -> Uint8Array|String
+ *
+ * Uncompressed result, generated by default [[Inflate#onData]]
+ * and [[Inflate#onEnd]] handlers. Filled after you push last chunk
+ * (call [[Inflate#push]] with `Z_FINISH` / `true` param).
+ **/
+
+/**
+ * Inflate.err -> Number
+ *
+ * Error code after inflate finished. 0 (Z_OK) on success.
+ * Should be checked if broken data possible.
+ **/
+
+/**
+ * Inflate.msg -> String
+ *
+ * Error message, if [[Inflate.err]] != 0
+ **/
+
+
+/**
+ * new Inflate(options)
+ * - options (Object): zlib inflate options.
+ *
+ * Creates new inflator instance with specified params. Throws exception
+ * on bad params. Supported options:
+ *
+ * - `windowBits`
+ * - `dictionary`
+ *
+ * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced)
+ * for more information on these.
+ *
+ * Additional options, for internal needs:
+ *
+ * - `chunkSize` - size of generated data chunks (16K by default)
+ * - `raw` (Boolean) - do raw inflate
+ * - `to` (String) - if equal to 'string', then result will be converted
+ *   from utf8 to utf16 (javascript) string. When string output requested,
+ *   chunk length can differ from `chunkSize`, depending on content.
+ *
+ * By default, when no options set, autodetect deflate/gzip data format via
+ * wrapper header.
+ *
+ * ##### Example:
+ *
+ * ```javascript
+ * const pako = require('pako')
+ * const chunk1 = new Uint8Array([1,2,3,4,5,6,7,8,9])
+ * const chunk2 = new Uint8Array([10,11,12,13,14,15,16,17,18,19]);
+ *
+ * const inflate = new pako.Inflate({ level: 3});
+ *
+ * inflate.push(chunk1, false);
+ * inflate.push(chunk2, true);  // true -> last chunk
+ *
+ * if (inflate.err) { throw new Error(inflate.err); }
+ *
+ * console.log(inflate.result);
+ * ```
+ **/
+function Inflate(options) {
+  this.options = common.assign({
+    chunkSize: 1024 * 64,
+    windowBits: 15,
+    to: ''
+  }, options || {});
+
+  const opt = this.options;
+
+  // Force window size for `raw` data, if not set directly,
+  // because we have no header for autodetect.
+  if (opt.raw && (opt.windowBits >= 0) && (opt.windowBits < 16)) {
+    opt.windowBits = -opt.windowBits;
+    if (opt.windowBits === 0) { opt.windowBits = -15; }
+  }
+
+  // If `windowBits` not defined (and mode not raw) - set autodetect flag for gzip/deflate
+  if ((opt.windowBits >= 0) && (opt.windowBits < 16) &&
+      !(options && options.windowBits)) {
+    opt.windowBits += 32;
+  }
+
+  // Gzip header has no info about windows size, we can do autodetect only
+  // for deflate. So, if window size not set, force it to max when gzip possible
+  if ((opt.windowBits > 15) && (opt.windowBits < 48)) {
+    // bit 3 (16) -> gzipped data
+    // bit 4 (32) -> autodetect gzip/deflate
+    if ((opt.windowBits & 15) === 0) {
+      opt.windowBits |= 15;
+    }
+  }
+
+  this.err    = 0;      // error code, if happens (0 = Z_OK)
+  this.msg    = '';     // error message
+  this.ended  = false;  // used to avoid multiple onEnd() calls
+  this.chunks = [];     // chunks of compressed data
+
+  this.strm   = new zstream();
+  this.strm.avail_out = 0;
+
+  let status  = inflate_1.inflateInit2(
+    this.strm,
+    opt.windowBits
+  );
+
+  if (status !== Z_OK$3) {
+    throw new Error(messages[status]);
+  }
+
+  this.header = new gzheader();
+
+  inflate_1.inflateGetHeader(this.strm, this.header);
+
+  // Setup dictionary
+  if (opt.dictionary) {
+    // Convert data if needed
+    if (typeof opt.dictionary === 'string') {
+      opt.dictionary = strings.string2buf(opt.dictionary);
+    } else if (toString$1.call(opt.dictionary) === '[object ArrayBuffer]') {
+      opt.dictionary = new Uint8Array(opt.dictionary);
+    }
+    if (opt.raw) { //In raw mode we need to set the dictionary early
+      status = inflate_1.inflateSetDictionary(this.strm, opt.dictionary);
+      if (status !== Z_OK$3) {
+        throw new Error(messages[status]);
+      }
+    }
+  }
+}
+
+/**
+ * Inflate#push(data[, flush_mode]) -> Boolean
+ * - data (Uint8Array|ArrayBuffer): input data
+ * - flush_mode (Number|Boolean): 0..6 for corresponding Z_NO_FLUSH..Z_TREE
+ *   flush modes. See constants. Skipped or `false` means Z_NO_FLUSH,
+ *   `true` means Z_FINISH.
+ *
+ * Sends input data to inflate pipe, generating [[Inflate#onData]] calls with
+ * new output chunks. Returns `true` on success. If end of stream detected,
+ * [[Inflate#onEnd]] will be called.
+ *
+ * `flush_mode` is not needed for normal operation, because end of stream
+ * detected automatically. You may try to use it for advanced things, but
+ * this functionality was not tested.
+ *
+ * On fail call [[Inflate#onEnd]] with error code and return false.
+ *
+ * ##### Example
+ *
+ * ```javascript
+ * push(chunk, false); // push one of data chunks
+ * ...
+ * push(chunk, true);  // push last chunk
+ * ```
+ **/
+Inflate.prototype.push = function (data, flush_mode) {
+  const strm = this.strm;
+  const chunkSize = this.options.chunkSize;
+  const dictionary = this.options.dictionary;
+  let status, _flush_mode, last_avail_out;
+
+  if (this.ended) return false;
+
+  if (flush_mode === ~~flush_mode) _flush_mode = flush_mode;
+  else _flush_mode = flush_mode === true ? Z_FINISH$3 : Z_NO_FLUSH$2;
+
+  // Convert data if needed
+  if (toString$1.call(data) === '[object ArrayBuffer]') {
+    strm.input = new Uint8Array(data);
+  } else {
+    strm.input = data;
+  }
+
+  strm.next_in = 0;
+  strm.avail_in = strm.input.length;
+
+  for (;;) {
+    if (strm.avail_out === 0) {
+      strm.output = new Uint8Array(chunkSize);
+      strm.next_out = 0;
+      strm.avail_out = chunkSize;
+    }
+
+    status = inflate_1.inflate(strm, _flush_mode);
+
+    if (status === Z_NEED_DICT$1 && dictionary) {
+      status = inflate_1.inflateSetDictionary(strm, dictionary);
+
+      if (status === Z_OK$3) {
+        status = inflate_1.inflate(strm, _flush_mode);
+      } else if (status === Z_DATA_ERROR$2) {
+        // Replace code with more verbose
+        status = Z_NEED_DICT$1;
+      }
+    }
+
+    // Skip snyc markers if more data follows and not raw mode
+    while (strm.avail_in > 0 &&
+           status === Z_STREAM_END$3 &&
+           strm.state.wrap > 0 &&
+           data[strm.next_in] !== 0)
+    {
+      inflate_1.inflateReset(strm);
+      status = inflate_1.inflate(strm, _flush_mode);
+    }
+
+    switch (status) {
+      case Z_STREAM_ERROR$2:
+      case Z_DATA_ERROR$2:
+      case Z_NEED_DICT$1:
+      case Z_MEM_ERROR$1:
+        this.onEnd(status);
+        this.ended = true;
+        return false;
+    }
+
+    // Remember real `avail_out` value, because we may patch out buffer content
+    // to align utf8 strings boundaries.
+    last_avail_out = strm.avail_out;
+
+    if (strm.next_out) {
+      if (strm.avail_out === 0 || status === Z_STREAM_END$3) {
+
+        if (this.options.to === 'string') {
+
+          let next_out_utf8 = strings.utf8border(strm.output, strm.next_out);
+
+          let tail = strm.next_out - next_out_utf8;
+          let utf8str = strings.buf2string(strm.output, next_out_utf8);
+
+          // move tail & realign counters
+          strm.next_out = tail;
+          strm.avail_out = chunkSize - tail;
+          if (tail) strm.output.set(strm.output.subarray(next_out_utf8, next_out_utf8 + tail), 0);
+
+          this.onData(utf8str);
+
+        } else {
+          this.onData(strm.output.length === strm.next_out ? strm.output : strm.output.subarray(0, strm.next_out));
+        }
+      }
+    }
+
+    // Must repeat iteration if out buffer is full
+    if (status === Z_OK$3 && last_avail_out === 0) continue;
+
+    // Finalize if end of stream reached.
+    if (status === Z_STREAM_END$3) {
+      status = inflate_1.inflateEnd(this.strm);
+      this.onEnd(status);
+      this.ended = true;
+      return true;
+    }
+
+    if (strm.avail_in === 0) break;
+  }
+
+  return true;
+};
+
+
+/**
+ * Inflate#onData(chunk) -> Void
+ * - chunk (Uint8Array|String): output data. When string output requested,
+ *   each chunk will be string.
+ *
+ * By default, stores data blocks in `chunks[]` property and glue
+ * those in `onEnd`. Override this handler, if you need another behaviour.
+ **/
+Inflate.prototype.onData = function (chunk) {
+  this.chunks.push(chunk);
+};
+
+
+/**
+ * Inflate#onEnd(status) -> Void
+ * - status (Number): inflate status. 0 (Z_OK) on success,
+ *   other if not.
+ *
+ * Called either after you tell inflate that the input stream is
+ * complete (Z_FINISH). By default - join collected chunks,
+ * free memory and fill `results` / `err` properties.
+ **/
+Inflate.prototype.onEnd = function (status) {
+  // On success - join
+  if (status === Z_OK$3) {
+    if (this.options.to === 'string') {
+      this.result = this.chunks.join('');
+    } else {
+      this.result = common.flattenChunks(this.chunks);
+    }
+  }
+  this.chunks = [];
+  this.err = status;
+  this.msg = this.strm.msg;
+};
+
+
+/**
+ * inflate(data[, options]) -> Uint8Array|String
+ * - data (Uint8Array): input data to decompress.
+ * - options (Object): zlib inflate options.
+ *
+ * Decompress `data` with inflate/ungzip and `options`. Autodetect
+ * format via wrapper header by default. That's why we don't provide
+ * separate `ungzip` method.
+ *
+ * Supported options are:
+ *
+ * - windowBits
+ *
+ * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced)
+ * for more information.
+ *
+ * Sugar (options):
+ *
+ * - `raw` (Boolean) - say that we work with raw stream, if you don't wish to specify
+ *   negative windowBits implicitly.
+ * - `to` (String) - if equal to 'string', then result will be converted
+ *   from utf8 to utf16 (javascript) string. When string output requested,
+ *   chunk length can differ from `chunkSize`, depending on content.
+ *
+ *
+ * ##### Example:
+ *
+ * ```javascript
+ * const pako = require('pako');
+ * const input = pako.deflate(new Uint8Array([1,2,3,4,5,6,7,8,9]));
+ * let output;
+ *
+ * try {
+ *   output = pako.inflate(input);
+ * } catch (err)
+ *   console.log(err);
+ * }
+ * ```
+ **/
+function inflate$1(input, options) {
+  const inflator = new Inflate(options);
+
+  inflator.push(input);
+
+  // That will never happens, if you don't cheat with options :)
+  if (inflator.err) throw inflator.msg || messages[inflator.err];
+
+  return inflator.result;
+}
+
+
+/**
+ * inflateRaw(data[, options]) -> Uint8Array|String
+ * - data (Uint8Array): input data to decompress.
+ * - options (Object): zlib inflate options.
+ *
+ * The same as [[inflate]], but creates raw data, without wrapper
+ * (header and adler32 crc).
+ **/
+function inflateRaw(input, options) {
+  options = options || {};
+  options.raw = true;
+  return inflate$1(input, options);
+}
+
+
+/**
+ * ungzip(data[, options]) -> Uint8Array|String
+ * - data (Uint8Array): input data to decompress.
+ * - options (Object): zlib inflate options.
+ *
+ * Just shortcut to [[inflate]], because it autodetects format
+ * by header.content. Done for convenience.
+ **/
+
+
+var Inflate_1 = Inflate;
+var inflate_2$1 = inflate$1;
+var inflateRaw_1 = inflateRaw;
+var ungzip = inflate$1;
+var constants$2 = constants;
+
+var inflate_1$1 = {
+       Inflate: Inflate_1,
+       inflate: inflate_2$1,
+       inflateRaw: inflateRaw_1,
+       ungzip: ungzip,
+       constants: constants$2
+};
+
+const { Inflate: Inflate$1, inflate: inflate$2, inflateRaw: inflateRaw$1, ungzip: ungzip$1 } = inflate_1$1;
+var inflate_1$2 = inflate$2;
+
+/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */
+var 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)
+};
+
+var 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;
+};
+
+var ieee754 = {
+       read: read,
+       write: write
+};
+
+var pbf = Pbf;
+
+
+
+function Pbf(buf) {
+    this.buf = ArrayBuffer.isView && ArrayBuffer.isView(buf) ? buf : new Uint8Array(buf || 0);
+    this.pos = 0;
+    this.type = 0;
+    this.length = this.buf.length;
+}
+
+Pbf.Varint  = 0; // varint: int32, int64, uint32, uint64, sint32, sint64, bool, enum
+Pbf.Fixed64 = 1; // 64-bit: double, fixed64, sfixed64
+Pbf.Bytes   = 2; // length-delimited: string, bytes, embedded messages, packed repeated fields
+Pbf.Fixed32 = 5; // 32-bit: float, fixed32, sfixed32
+
+var SHIFT_LEFT_32 = (1 << 16) * (1 << 16),
+    SHIFT_RIGHT_32 = 1 / SHIFT_LEFT_32;
+
+// Threshold chosen based on both benchmarking and knowledge about browser string
+// data structures (which currently switch structure types at 12 bytes or more)
+var TEXT_DECODER_MIN_LENGTH = 12;
+var utf8TextDecoder = typeof TextDecoder === 'undefined' ? null : new TextDecoder('utf8');
+
+Pbf.prototype = {
+
+    destroy: function() {
+        this.buf = null;
+    },
+
+    // === READING =================================================================
+
+    readFields: function(readField, result, end) {
+        end = end || this.length;
+
+        while (this.pos < end) {
+            var val = this.readVarint(),
+                tag = val >> 3,
+                startPos = this.pos;
+
+            this.type = val & 0x7;
+            readField(tag, result, this);
+
+            if (this.pos === startPos) this.skip(val);
+        }
+        return result;
+    },
+
+    readMessage: function(readField, result) {
+        return this.readFields(readField, result, this.readVarint() + this.pos);
+    },
+
+    readFixed32: function() {
+        var val = readUInt32(this.buf, this.pos);
+        this.pos += 4;
+        return val;
+    },
+
+    readSFixed32: function() {
+        var val = readInt32(this.buf, this.pos);
+        this.pos += 4;
+        return val;
+    },
+
+    // 64-bit int handling is based on github.com/dpw/node-buffer-more-ints (MIT-licensed)
+
+    readFixed64: function() {
+        var val = readUInt32(this.buf, this.pos) + readUInt32(this.buf, this.pos + 4) * SHIFT_LEFT_32;
+        this.pos += 8;
+        return val;
+    },
+
+    readSFixed64: function() {
+        var val = readUInt32(this.buf, this.pos) + readInt32(this.buf, this.pos + 4) * SHIFT_LEFT_32;
+        this.pos += 8;
+        return val;
+    },
+
+    readFloat: function() {
+        var val = ieee754.read(this.buf, this.pos, true, 23, 4);
+        this.pos += 4;
+        return val;
+    },
+
+    readDouble: function() {
+        var val = ieee754.read(this.buf, this.pos, true, 52, 8);
+        this.pos += 8;
+        return val;
+    },
+
+    readVarint: function(isSigned) {
+        var buf = this.buf,
+            val, b;
+
+        b = buf[this.pos++]; val  =  b & 0x7f;        if (b < 0x80) return val;
+        b = buf[this.pos++]; val |= (b & 0x7f) << 7;  if (b < 0x80) return val;
+        b = buf[this.pos++]; val |= (b & 0x7f) << 14; if (b < 0x80) return val;
+        b = buf[this.pos++]; val |= (b & 0x7f) << 21; if (b < 0x80) return val;
+        b = buf[this.pos];   val |= (b & 0x0f) << 28;
+
+        return readVarintRemainder(val, isSigned, this);
+    },
+
+    readVarint64: function() { // for compatibility with v2.0.1
+        return this.readVarint(true);
+    },
+
+    readSVarint: function() {
+        var num = this.readVarint();
+        return num % 2 === 1 ? (num + 1) / -2 : num / 2; // zigzag encoding
+    },
+
+    readBoolean: function() {
+        return Boolean(this.readVarint());
+    },
+
+    readString: function() {
+        var end = this.readVarint() + this.pos;
+        var pos = this.pos;
+        this.pos = end;
+
+        if (end - pos >= TEXT_DECODER_MIN_LENGTH && utf8TextDecoder) {
+            // longer strings are fast with the built-in browser TextDecoder API
+            return readUtf8TextDecoder(this.buf, pos, end);
+        }
+        // short strings are fast with our custom implementation
+        return readUtf8(this.buf, pos, end);
+    },
+
+    readBytes: function() {
+        var end = this.readVarint() + this.pos,
+            buffer = this.buf.subarray(this.pos, end);
+        this.pos = end;
+        return buffer;
+    },
+
+    // verbose for performance reasons; doesn't affect gzipped size
+
+    readPackedVarint: function(arr, isSigned) {
+        if (this.type !== Pbf.Bytes) return arr.push(this.readVarint(isSigned));
+        var end = readPackedEnd(this);
+        arr = arr || [];
+        while (this.pos < end) arr.push(this.readVarint(isSigned));
+        return arr;
+    },
+    readPackedSVarint: function(arr) {
+        if (this.type !== Pbf.Bytes) return arr.push(this.readSVarint());
+        var end = readPackedEnd(this);
+        arr = arr || [];
+        while (this.pos < end) arr.push(this.readSVarint());
+        return arr;
+    },
+    readPackedBoolean: function(arr) {
+        if (this.type !== Pbf.Bytes) return arr.push(this.readBoolean());
+        var end = readPackedEnd(this);
+        arr = arr || [];
+        while (this.pos < end) arr.push(this.readBoolean());
+        return arr;
+    },
+    readPackedFloat: function(arr) {
+        if (this.type !== Pbf.Bytes) return arr.push(this.readFloat());
+        var end = readPackedEnd(this);
+        arr = arr || [];
+        while (this.pos < end) arr.push(this.readFloat());
+        return arr;
+    },
+    readPackedDouble: function(arr) {
+        if (this.type !== Pbf.Bytes) return arr.push(this.readDouble());
+        var end = readPackedEnd(this);
+        arr = arr || [];
+        while (this.pos < end) arr.push(this.readDouble());
+        return arr;
+    },
+    readPackedFixed32: function(arr) {
+        if (this.type !== Pbf.Bytes) return arr.push(this.readFixed32());
+        var end = readPackedEnd(this);
+        arr = arr || [];
+        while (this.pos < end) arr.push(this.readFixed32());
+        return arr;
+    },
+    readPackedSFixed32: function(arr) {
+        if (this.type !== Pbf.Bytes) return arr.push(this.readSFixed32());
+        var end = readPackedEnd(this);
+        arr = arr || [];
+        while (this.pos < end) arr.push(this.readSFixed32());
+        return arr;
+    },
+    readPackedFixed64: function(arr) {
+        if (this.type !== Pbf.Bytes) return arr.push(this.readFixed64());
+        var end = readPackedEnd(this);
+        arr = arr || [];
+        while (this.pos < end) arr.push(this.readFixed64());
+        return arr;
+    },
+    readPackedSFixed64: function(arr) {
+        if (this.type !== Pbf.Bytes) return arr.push(this.readSFixed64());
+        var end = readPackedEnd(this);
+        arr = arr || [];
+        while (this.pos < end) arr.push(this.readSFixed64());
+        return arr;
+    },
+
+    skip: function(val) {
+        var type = val & 0x7;
+        if (type === Pbf.Varint) while (this.buf[this.pos++] > 0x7f) {}
+        else if (type === Pbf.Bytes) this.pos = this.readVarint() + this.pos;
+        else if (type === Pbf.Fixed32) this.pos += 4;
+        else if (type === Pbf.Fixed64) this.pos += 8;
+        else throw new Error('Unimplemented type: ' + type);
+    },
+
+    // === WRITING =================================================================
+
+    writeTag: function(tag, type) {
+        this.writeVarint((tag << 3) | type);
+    },
+
+    realloc: function(min) {
+        var length = this.length || 16;
+
+        while (length < this.pos + min) length *= 2;
+
+        if (length !== this.length) {
+            var buf = new Uint8Array(length);
+            buf.set(this.buf);
+            this.buf = buf;
+            this.length = length;
+        }
+    },
+
+    finish: function() {
+        this.length = this.pos;
+        this.pos = 0;
+        return this.buf.subarray(0, this.length);
+    },
+
+    writeFixed32: function(val) {
+        this.realloc(4);
+        writeInt32(this.buf, val, this.pos);
+        this.pos += 4;
+    },
+
+    writeSFixed32: function(val) {
+        this.realloc(4);
+        writeInt32(this.buf, val, this.pos);
+        this.pos += 4;
+    },
+
+    writeFixed64: function(val) {
+        this.realloc(8);
+        writeInt32(this.buf, val & -1, this.pos);
+        writeInt32(this.buf, Math.floor(val * SHIFT_RIGHT_32), this.pos + 4);
+        this.pos += 8;
+    },
+
+    writeSFixed64: function(val) {
+        this.realloc(8);
+        writeInt32(this.buf, val & -1, this.pos);
+        writeInt32(this.buf, Math.floor(val * SHIFT_RIGHT_32), this.pos + 4);
+        this.pos += 8;
+    },
+
+    writeVarint: function(val) {
+        val = +val || 0;
+
+        if (val > 0xfffffff || val < 0) {
+            writeBigVarint(val, this);
+            return;
+        }
+
+        this.realloc(4);
+
+        this.buf[this.pos++] =           val & 0x7f  | (val > 0x7f ? 0x80 : 0); if (val <= 0x7f) return;
+        this.buf[this.pos++] = ((val >>>= 7) & 0x7f) | (val > 0x7f ? 0x80 : 0); if (val <= 0x7f) return;
+        this.buf[this.pos++] = ((val >>>= 7) & 0x7f) | (val > 0x7f ? 0x80 : 0); if (val <= 0x7f) return;
+        this.buf[this.pos++] =   (val >>> 7) & 0x7f;
+    },
+
+    writeSVarint: function(val) {
+        this.writeVarint(val < 0 ? -val * 2 - 1 : val * 2);
+    },
+
+    writeBoolean: function(val) {
+        this.writeVarint(Boolean(val));
+    },
+
+    writeString: function(str) {
+        str = String(str);
+        this.realloc(str.length * 4);
+
+        this.pos++; // reserve 1 byte for short string length
+
+        var startPos = this.pos;
+        // write the string directly to the buffer and see how much was written
+        this.pos = writeUtf8(this.buf, str, this.pos);
+        var len = this.pos - startPos;
+
+        if (len >= 0x80) makeRoomForExtraLength(startPos, len, this);
+
+        // finally, write the message length in the reserved place and restore the position
+        this.pos = startPos - 1;
+        this.writeVarint(len);
+        this.pos += len;
+    },
+
+    writeFloat: function(val) {
+        this.realloc(4);
+        ieee754.write(this.buf, val, this.pos, true, 23, 4);
+        this.pos += 4;
+    },
+
+    writeDouble: function(val) {
+        this.realloc(8);
+        ieee754.write(this.buf, val, this.pos, true, 52, 8);
+        this.pos += 8;
+    },
+
+    writeBytes: function(buffer) {
+        var len = buffer.length;
+        this.writeVarint(len);
+        this.realloc(len);
+        for (var i = 0; i < len; i++) this.buf[this.pos++] = buffer[i];
+    },
+
+    writeRawMessage: function(fn, obj) {
+        this.pos++; // reserve 1 byte for short message length
+
+        // write the message directly to the buffer and see how much was written
+        var startPos = this.pos;
+        fn(obj, this);
+        var len = this.pos - startPos;
+
+        if (len >= 0x80) makeRoomForExtraLength(startPos, len, this);
+
+        // finally, write the message length in the reserved place and restore the position
+        this.pos = startPos - 1;
+        this.writeVarint(len);
+        this.pos += len;
+    },
+
+    writeMessage: function(tag, fn, obj) {
+        this.writeTag(tag, Pbf.Bytes);
+        this.writeRawMessage(fn, obj);
+    },
+
+    writePackedVarint:   function(tag, arr) { if (arr.length) this.writeMessage(tag, writePackedVarint, arr);   },
+    writePackedSVarint:  function(tag, arr) { if (arr.length) this.writeMessage(tag, writePackedSVarint, arr);  },
+    writePackedBoolean:  function(tag, arr) { if (arr.length) this.writeMessage(tag, writePackedBoolean, arr);  },
+    writePackedFloat:    function(tag, arr) { if (arr.length) this.writeMessage(tag, writePackedFloat, arr);    },
+    writePackedDouble:   function(tag, arr) { if (arr.length) this.writeMessage(tag, writePackedDouble, arr);   },
+    writePackedFixed32:  function(tag, arr) { if (arr.length) this.writeMessage(tag, writePackedFixed32, arr);  },
+    writePackedSFixed32: function(tag, arr) { if (arr.length) this.writeMessage(tag, writePackedSFixed32, arr); },
+    writePackedFixed64:  function(tag, arr) { if (arr.length) this.writeMessage(tag, writePackedFixed64, arr);  },
+    writePackedSFixed64: function(tag, arr) { if (arr.length) this.writeMessage(tag, writePackedSFixed64, arr); },
+
+    writeBytesField: function(tag, buffer) {
+        this.writeTag(tag, Pbf.Bytes);
+        this.writeBytes(buffer);
+    },
+    writeFixed32Field: function(tag, val) {
+        this.writeTag(tag, Pbf.Fixed32);
+        this.writeFixed32(val);
+    },
+    writeSFixed32Field: function(tag, val) {
+        this.writeTag(tag, Pbf.Fixed32);
+        this.writeSFixed32(val);
+    },
+    writeFixed64Field: function(tag, val) {
+        this.writeTag(tag, Pbf.Fixed64);
+        this.writeFixed64(val);
+    },
+    writeSFixed64Field: function(tag, val) {
+        this.writeTag(tag, Pbf.Fixed64);
+        this.writeSFixed64(val);
+    },
+    writeVarintField: function(tag, val) {
+        this.writeTag(tag, Pbf.Varint);
+        this.writeVarint(val);
+    },
+    writeSVarintField: function(tag, val) {
+        this.writeTag(tag, Pbf.Varint);
+        this.writeSVarint(val);
+    },
+    writeStringField: function(tag, str) {
+        this.writeTag(tag, Pbf.Bytes);
+        this.writeString(str);
+    },
+    writeFloatField: function(tag, val) {
+        this.writeTag(tag, Pbf.Fixed32);
+        this.writeFloat(val);
+    },
+    writeDoubleField: function(tag, val) {
+        this.writeTag(tag, Pbf.Fixed64);
+        this.writeDouble(val);
+    },
+    writeBooleanField: function(tag, val) {
+        this.writeVarintField(tag, Boolean(val));
+    }
+};
+
+function readVarintRemainder(l, s, p) {
+    var buf = p.buf,
+        h, b;
+
+    b = buf[p.pos++]; h  = (b & 0x70) >> 4;  if (b < 0x80) return toNum(l, h, s);
+    b = buf[p.pos++]; h |= (b & 0x7f) << 3;  if (b < 0x80) return toNum(l, h, s);
+    b = buf[p.pos++]; h |= (b & 0x7f) << 10; if (b < 0x80) return toNum(l, h, s);
+    b = buf[p.pos++]; h |= (b & 0x7f) << 17; if (b < 0x80) return toNum(l, h, s);
+    b = buf[p.pos++]; h |= (b & 0x7f) << 24; if (b < 0x80) return toNum(l, h, s);
+    b = buf[p.pos++]; h |= (b & 0x01) << 31; if (b < 0x80) return toNum(l, h, s);
+
+    throw new Error('Expected varint not more than 10 bytes');
+}
+
+function readPackedEnd(pbf) {
+    return pbf.type === Pbf.Bytes ?
+        pbf.readVarint() + pbf.pos : pbf.pos + 1;
+}
+
+function toNum(low, high, isSigned) {
+    if (isSigned) {
+        return high * 0x100000000 + (low >>> 0);
+    }
+
+    return ((high >>> 0) * 0x100000000) + (low >>> 0);
+}
+
+function writeBigVarint(val, pbf) {
+    var low, high;
+
+    if (val >= 0) {
+        low  = (val % 0x100000000) | 0;
+        high = (val / 0x100000000) | 0;
+    } else {
+        low  = ~(-val % 0x100000000);
+        high = ~(-val / 0x100000000);
+
+        if (low ^ 0xffffffff) {
+            low = (low + 1) | 0;
+        } else {
+            low = 0;
+            high = (high + 1) | 0;
+        }
+    }
+
+    if (val >= 0x10000000000000000 || val < -0x10000000000000000) {
+        throw new Error('Given varint doesn\'t fit into 10 bytes');
+    }
+
+    pbf.realloc(10);
+
+    writeBigVarintLow(low, high, pbf);
+    writeBigVarintHigh(high, pbf);
+}
+
+function writeBigVarintLow(low, high, pbf) {
+    pbf.buf[pbf.pos++] = low & 0x7f | 0x80; low >>>= 7;
+    pbf.buf[pbf.pos++] = low & 0x7f | 0x80; low >>>= 7;
+    pbf.buf[pbf.pos++] = low & 0x7f | 0x80; low >>>= 7;
+    pbf.buf[pbf.pos++] = low & 0x7f | 0x80; low >>>= 7;
+    pbf.buf[pbf.pos]   = low & 0x7f;
+}
+
+function writeBigVarintHigh(high, pbf) {
+    var lsb = (high & 0x07) << 4;
+
+    pbf.buf[pbf.pos++] |= lsb         | ((high >>>= 3) ? 0x80 : 0); if (!high) return;
+    pbf.buf[pbf.pos++]  = high & 0x7f | ((high >>>= 7) ? 0x80 : 0); if (!high) return;
+    pbf.buf[pbf.pos++]  = high & 0x7f | ((high >>>= 7) ? 0x80 : 0); if (!high) return;
+    pbf.buf[pbf.pos++]  = high & 0x7f | ((high >>>= 7) ? 0x80 : 0); if (!high) return;
+    pbf.buf[pbf.pos++]  = high & 0x7f | ((high >>>= 7) ? 0x80 : 0); if (!high) return;
+    pbf.buf[pbf.pos++]  = high & 0x7f;
+}
+
+function makeRoomForExtraLength(startPos, len, pbf) {
+    var extraLen =
+        len <= 0x3fff ? 1 :
+        len <= 0x1fffff ? 2 :
+        len <= 0xfffffff ? 3 : Math.floor(Math.log(len) / (Math.LN2 * 7));
+
+    // if 1 byte isn't enough for encoding message length, shift the data to the right
+    pbf.realloc(extraLen);
+    for (var i = pbf.pos - 1; i >= startPos; i--) pbf.buf[i + extraLen] = pbf.buf[i];
+}
+
+function writePackedVarint(arr, pbf)   { for (var i = 0; i < arr.length; i++) pbf.writeVarint(arr[i]);   }
+function writePackedSVarint(arr, pbf)  { for (var i = 0; i < arr.length; i++) pbf.writeSVarint(arr[i]);  }
+function writePackedFloat(arr, pbf)    { for (var i = 0; i < arr.length; i++) pbf.writeFloat(arr[i]);    }
+function writePackedDouble(arr, pbf)   { for (var i = 0; i < arr.length; i++) pbf.writeDouble(arr[i]);   }
+function writePackedBoolean(arr, pbf)  { for (var i = 0; i < arr.length; i++) pbf.writeBoolean(arr[i]);  }
+function writePackedFixed32(arr, pbf)  { for (var i = 0; i < arr.length; i++) pbf.writeFixed32(arr[i]);  }
+function writePackedSFixed32(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeSFixed32(arr[i]); }
+function writePackedFixed64(arr, pbf)  { for (var i = 0; i < arr.length; i++) pbf.writeFixed64(arr[i]);  }
+function writePackedSFixed64(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeSFixed64(arr[i]); }
+
+// Buffer code below from https://github.com/feross/buffer, MIT-licensed
+
+function readUInt32(buf, pos) {
+    return ((buf[pos]) |
+        (buf[pos + 1] << 8) |
+        (buf[pos + 2] << 16)) +
+        (buf[pos + 3] * 0x1000000);
+}
+
+function writeInt32(buf, val, pos) {
+    buf[pos] = val;
+    buf[pos + 1] = (val >>> 8);
+    buf[pos + 2] = (val >>> 16);
+    buf[pos + 3] = (val >>> 24);
+}
+
+function readInt32(buf, pos) {
+    return ((buf[pos]) |
+        (buf[pos + 1] << 8) |
+        (buf[pos + 2] << 16)) +
+        (buf[pos + 3] << 24);
+}
+
+function readUtf8(buf, pos, end) {
+    var str = '';
+    var i = pos;
+
+    while (i < end) {
+        var b0 = buf[i];
+        var c = null; // codepoint
+        var bytesPerSequence =
+            b0 > 0xEF ? 4 :
+            b0 > 0xDF ? 3 :
+            b0 > 0xBF ? 2 : 1;
+
+        if (i + bytesPerSequence > end) break;
+
+        var b1, b2, b3;
+
+        if (bytesPerSequence === 1) {
+            if (b0 < 0x80) {
+                c = b0;
+            }
+        } else if (bytesPerSequence === 2) {
+            b1 = buf[i + 1];
+            if ((b1 & 0xC0) === 0x80) {
+                c = (b0 & 0x1F) << 0x6 | (b1 & 0x3F);
+                if (c <= 0x7F) {
+                    c = null;
+                }
+            }
+        } else if (bytesPerSequence === 3) {
+            b1 = buf[i + 1];
+            b2 = buf[i + 2];
+            if ((b1 & 0xC0) === 0x80 && (b2 & 0xC0) === 0x80) {
+                c = (b0 & 0xF) << 0xC | (b1 & 0x3F) << 0x6 | (b2 & 0x3F);
+                if (c <= 0x7FF || (c >= 0xD800 && c <= 0xDFFF)) {
+                    c = null;
+                }
+            }
+        } else if (bytesPerSequence === 4) {
+            b1 = buf[i + 1];
+            b2 = buf[i + 2];
+            b3 = buf[i + 3];
+            if ((b1 & 0xC0) === 0x80 && (b2 & 0xC0) === 0x80 && (b3 & 0xC0) === 0x80) {
+                c = (b0 & 0xF) << 0x12 | (b1 & 0x3F) << 0xC | (b2 & 0x3F) << 0x6 | (b3 & 0x3F);
+                if (c <= 0xFFFF || c >= 0x110000) {
+                    c = null;
+                }
+            }
+        }
+
+        if (c === null) {
+            c = 0xFFFD;
+            bytesPerSequence = 1;
+
+        } else if (c > 0xFFFF) {
+            c -= 0x10000;
+            str += String.fromCharCode(c >>> 10 & 0x3FF | 0xD800);
+            c = 0xDC00 | c & 0x3FF;
+        }
+
+        str += String.fromCharCode(c);
+        i += bytesPerSequence;
+    }
+
+    return str;
+}
+
+function readUtf8TextDecoder(buf, pos, end) {
+    return utf8TextDecoder.decode(buf.subarray(pos, end));
+}
+
+function writeUtf8(buf, str, pos) {
+    for (var i = 0, c, lead; i < str.length; i++) {
+        c = str.charCodeAt(i); // code point
+
+        if (c > 0xD7FF && c < 0xE000) {
+            if (lead) {
+                if (c < 0xDC00) {
+                    buf[pos++] = 0xEF;
+                    buf[pos++] = 0xBF;
+                    buf[pos++] = 0xBD;
+                    lead = c;
+                    continue;
+                } else {
+                    c = lead - 0xD800 << 10 | c - 0xDC00 | 0x10000;
+                    lead = null;
+                }
+            } else {
+                if (c > 0xDBFF || (i + 1 === str.length)) {
+                    buf[pos++] = 0xEF;
+                    buf[pos++] = 0xBF;
+                    buf[pos++] = 0xBD;
+                } else {
+                    lead = c;
+                }
+                continue;
+            }
+        } else if (lead) {
+            buf[pos++] = 0xEF;
+            buf[pos++] = 0xBF;
+            buf[pos++] = 0xBD;
+            lead = null;
+        }
+
+        if (c < 0x80) {
+            buf[pos++] = c;
+        } else {
+            if (c < 0x800) {
+                buf[pos++] = c >> 0x6 | 0xC0;
+            } else {
+                if (c < 0x10000) {
+                    buf[pos++] = c >> 0xC | 0xE0;
+                } else {
+                    buf[pos++] = c >> 0x12 | 0xF0;
+                    buf[pos++] = c >> 0xC & 0x3F | 0x80;
+                }
+                buf[pos++] = c >> 0x6 & 0x3F | 0x80;
+            }
+            buf[pos++] = c & 0x3F | 0x80;
+        }
+    }
+    return pos;
+}
+
+/**
+ * Decompress and parse an array buffer containing zipped
+ * json data and return as a json object.
+ *
+ * @description Handles array buffers continaing zipped json
+ * data.
+ *
+ * @param {ArrayBuffer} buffer - Array buffer to decompress.
+ * @returns {Object} Parsed object.
+ */
+function decompress(buffer) {
+    const inflated = inflate_1$2(buffer, { to: "string" });
+    return JSON.parse(inflated);
+}
+/**
+ * Retrieves a resource as an array buffer and returns a promise
+ * to the buffer.
+ *
+ * @description Rejects the promise on request failure.
+ *
+ * @param {string} url - URL for resource to retrieve.
+ * @param {Promise} [abort] - Optional promise for aborting
+ * the request through rejection.
+ * @returns {Promise<ArrayBuffer>} Promise to the array buffer
+ * resource.
+ */
+function fetchArrayBuffer(url, abort) {
+    const method = "GET";
+    const responseType = "arraybuffer";
+    return xhrFetch(url, method, responseType, [], null, abort);
+}
+function xhrFetch(url, method, responseType, headers, body, abort) {
+    const xhr = new XMLHttpRequest();
+    const promise = new Promise((resolve, reject) => {
+        xhr.open(method, url, true);
+        for (const header of headers) {
+            xhr.setRequestHeader(header.name, header.value);
+        }
+        xhr.responseType = responseType;
+        xhr.timeout = 15000;
+        xhr.onload = () => {
+            var _a;
+            if (xhr.status !== 200) {
+                const error = (_a = xhr.response) !== null && _a !== void 0 ? _a : new MapillaryError(`Response status error: ${url}`);
+                reject(error);
+            }
+            if (!xhr.response) {
+                reject(new MapillaryError(`Response empty: ${url}`));
+            }
+            resolve(xhr.response);
+        };
+        xhr.onerror = () => {
+            reject(new MapillaryError(`Request error: ${url}`));
+        };
+        xhr.ontimeout = () => {
+            reject(new MapillaryError(`Request timeout: ${url}`));
+        };
+        xhr.onabort = () => {
+            reject(new MapillaryError(`Request aborted: ${url}`));
+        };
+        xhr.send(method === "POST" ? body : null);
+    });
+    if (!!abort) {
+        abort.catch(() => { xhr.abort(); });
+    }
+    return promise;
+}
+/**
+ * Read the fields of a protobuf array buffer into a mesh
+ * object.
+ *
+ * @param {ArrayBuffer} buffer - Protobuf array buffer
+ * to read from.
+ * @returns {MeshContract} Mesh object.
+ */
+function readMeshPbf(buffer) {
+    const pbf$1 = new pbf(buffer);
+    const mesh = { faces: [], vertices: [] };
+    return pbf$1.readFields(readMeshPbfField, mesh);
+}
+function readMeshPbfField(tag, mesh, pbf) {
+    if (tag === 1) {
+        mesh.vertices.push(pbf.readFloat());
+    }
+    else if (tag === 2) {
+        mesh.faces.push(pbf.readVarint());
+    }
+    else {
+        console.warn(`Unsupported pbf tag (${tag})`);
+    }
+}
+
+/**
+ * @class GeometryProviderBase
+ *
+ * @classdesc Base class to extend if implementing a geometry
+ * provider class.
+ *
+ * @example
+ * ```js
+ * class MyGeometryProvider extends GeometryProviderBase {
+ *      ...
+ * }
+ * ```
+ */
+class GeometryProviderBase {
+    /**
+     * Create a new geometry provider base instance.
+     */
+    constructor() { }
+    /**
+     * Convert a geodetic bounding box to the the minimum set
+     * of cell ids containing the bounding box.
+     *
+     * @description The bounding box needs
+     * to be sufficiently small to be contained in an area with the size
+     * of maximally four tiles. Up to nine adjacent tiles may be returned.
+     *
+     * @param {LngLat} sw - South west corner of bounding box.
+     * @param {LngLat} ne - North east corner of bounding box.
+     *
+     * @returns {Array<string>} Array of cell ids.
+     */
+    bboxToCellIds(sw, ne) {
+        throw new MapillaryError("Not implemented");
+    }
+    /**
+     * Get the cell ids of all adjacent cells.
+     *
+     * @description In the case of approximately rectangular cells
+     * this is typically the eight orthogonally and diagonally adjacent
+     * cells.
+     *
+     * @param {string} cellId - Id of cell.
+     * @returns {Array<string>} Array of cell ids. No specific
+     * order is guaranteed.
+     */
+    getAdjacent(cellId) {
+        throw new MapillaryError("Not implemented");
+    }
+    /**
+     * Get the vertices of a cell.
+     *
+     * @description The vertices form an unclosed
+     * clockwise polygon in the 2D longitude, latitude
+     * space. No assumption on the position of the first
+     * vertex relative to the others can be made.
+     *
+     * @param {string} cellId - Id of cell.
+     * @returns {Array<LngLat>} Unclosed clockwise polygon.
+     */
+    getVertices(cellId) {
+        throw new MapillaryError("Not implemented");
+    }
+    /**
+     * Convert geodetic coordinates to a cell id.
+     *
+     * @param {LngLat} lngLat - Longitude, latitude to convert.
+     * @returns {string} Cell id for the longitude, latitude.
+     */
+    lngLatToCellId(lngLat) {
+        throw new MapillaryError("Not implemented");
+    }
+    /** @ignore */
+    _approxBboxToCellIds(sw, ne) {
+        if (ne.lat <= sw.lat || ne.lng <= sw.lng) {
+            throw new MapillaryError("North east needs to be top right of south west");
+        }
+        const centerLat = (sw.lat + ne.lat) / 2;
+        const centerLng = (sw.lng + ne.lng) / 2;
+        const enu = geodeticToEnu(ne.lng, ne.lat, 0, centerLng, centerLat, 0);
+        const threshold = Math.max(enu[0], enu[1]);
+        return this._lngLatToCellIds({ lat: centerLat, lng: centerLng }, threshold);
+    }
+    /** @ignore */
+    _enuToGeodetic(point, reference) {
+        const [lng, lat] = enuToGeodetic(point[0], point[1], point[2], reference.lng, reference.lat, 0);
+        return { lat, lng };
+    }
+    /** @ignore */
+    _getLngLatBoundingBoxCorners(lngLat, threshold) {
+        return [
+            [-threshold, threshold, 0],
+            [threshold, threshold, 0],
+            [threshold, -threshold, 0],
+            [-threshold, -threshold, 0],
+        ].map((point) => {
+            return this._enuToGeodetic(point, lngLat);
+        });
+    }
+    /**
+     * Convert a geodetic square to cell ids.
+     *
+     * The square is specified as a longitude, latitude
+     * and a threshold from the position using Manhattan distance.
+     *
+     * @param {LngLat} lngLat - Longitude, latitude.
+     * @param {number} threshold - Threshold of the conversion in meters.
+     *
+     * @returns {Array<string>} Array of cell ids reachable within
+     * the threshold.
+     *
+     * @ignore
+     */
+    _lngLatToCellIds(lngLat, threshold) {
+        const cellId = this.lngLatToCellId(lngLat);
+        const bboxCorners = this._getLngLatBoundingBoxCorners(lngLat, threshold);
+        for (const corner of bboxCorners) {
+            const cid = this.lngLatToCellId(corner);
+            if (cid !== cellId) {
+                return [cellId, ...this.getAdjacent(cellId)];
+            }
+        }
+        return [cellId];
+    }
+}
+
+/**
+ * @class DataProviderBase
+ *
+ * @classdesc Base class to extend if implementing a data provider
+ * class.
+ *
+ * @fires datacreate
+ *
+ * @example
+ * ```js
+ * class MyDataProvider extends DataProviderBase {
+ *   constructor() {
+ *     super(new S2GeometryProvider());
+ *   }
+ *   ...
+ * }
+ * ```
+ */
+class DataProviderBase extends EventEmitter {
+    /**
+     * Create a new data provider base instance.
+     *
+     * @param {GeometryProviderBase} geometry - Geometry
+     * provider instance.
+     */
+    constructor(_geometry) {
+        super();
+        this._geometry = _geometry;
+        if (!(this._geometry instanceof GeometryProviderBase)) {
+            throw new MapillaryError("The data provider requires a geometry provider base instance.");
+        }
+    }
+    /**
+     * Get geometry property.
+     *
+     * @returns {GeometryProviderBase} Geometry provider instance.
+     */
+    get geometry() {
+        return this._geometry;
+    }
+    fire(type, event) {
+        super.fire(type, event);
+    }
+    /**
+     * Get core images in a geometry cell.
+     *
+     * @param {string} cellId - The id of the geometry cell.
+     * @returns {Promise<CoreImagesContract>} Promise to
+     * the core images of the requested geometry cell id.
+     * @throws Rejects the promise on errors.
+     */
+    getCoreImages(cellId) {
+        return Promise.reject(new MapillaryError("Not implemented"));
+    }
+    /**
+     * Get a cluster reconstruction.
+     *
+     * @param {string} url - URL for the cluster reconstruction
+     * to retrieve.
+     * @param {Promise} [abort] - Optional promise for aborting
+     * the request through rejection.
+     * @returns {Promise<ClusterContract>} Promise to the
+     * cluster reconstruction.
+     * @throws Rejects the promise on errors.
+     */
+    getCluster(url, abort) {
+        return Promise.reject(new MapillaryError("Not implemented"));
+    }
+    /**
+     * Get spatial images.
+     *
+     * @param {Array<string>} imageIds - The ids for the
+     * images to retrieve.
+     * @returns {Promise<SpatialImagesContract>} Promise to
+     * the spatial images of the requested image ids.
+     * @throws Rejects the promise on errors.
+     */
+    getSpatialImages(imageIds) {
+        return Promise.reject(new MapillaryError("Not implemented"));
+    }
+    /**
+     * Get complete images.
+     *
+     * @param {Array<string>} imageIds - The ids for the
+     * images to retrieve.
+     * @returns {Promise<ImagesContract>} Promise to the images of the
+     * requested image ids.
+     * @throws Rejects the promise on errors.
+     */
+    getImages(imageIds) {
+        return Promise.reject(new MapillaryError("Not implemented"));
+    }
+    /**
+     * Get an image as an array buffer.
+     *
+     * @param {string} url - URL for image to retrieve.
+     * @param {Promise<void>} [abort] - Optional promise for aborting
+     * the request through rejection.
+     * @returns {Promise<ArrayBuffer>} Promise to the array
+     * buffer containing the image.
+     * @throws Rejects the promise on errors.
+     */
+    getImageBuffer(url, abort) {
+        return Promise.reject(new MapillaryError("Not implemented"));
+    }
+    /**
+     * Get image tiles urls for a tile level.
+     *
+     * @param {ImageTilesRequestContract} tiles - Tiles to request
+     * @returns {Promise<ImageTilesContract>} Promise to the
+     * image tiles response contract
+     *
+     * @throws Rejects the promise on errors.
+     *
+     * @example
+     * ```js
+     * var tileRequest = { imageId: 'image-id', z: 12 };
+     * provider.getImageTiles(tileRequest)
+     *   .then((response) => console.log(response));
+     * ```
+     */
+    getImageTiles(tiles) {
+        return Promise.reject(new MapillaryError("Not implemented"));
+    }
+    /**
+     * Get a mesh.
+     *
+     * @param {string} url - URL for mesh to retrieve.
+     * @param {Promise<void>} [abort] - Optional promise for aborting
+     * the request through rejection.
+     * @returns {Promise<MeshContract>} Promise to the mesh.
+     * @throws Rejects the promise on errors.
+     */
+    getMesh(url, abort) {
+        return Promise.reject(new MapillaryError("Not implemented"));
+    }
+    /**
+     * Get sequence.
+     *
+     * @param {Array<string>} sequenceId - The id for the
+     * sequence to retrieve.
+     * @returns {Promise} Promise to the sequences of the
+     * requested image ids.
+     * @throws Rejects the promise on errors.
+     */
+    getSequence(sequenceId) {
+        return Promise.reject(new MapillaryError("Not implemented"));
+    }
+    off(type, handler) {
+        super.off(type, handler);
+    }
+    on(type, handler) {
+        super.on(type, handler);
+    }
+    /**
+     * Set an access token for authenticated API requests of
+     * protected resources.
+     *
+     * @param {string} [accessToken] accessToken - User access
+     * token or client access token.
+     */
+    setAccessToken(accessToken) {
+        throw new MapillaryError("Not implemented");
+    }
+}
+
+/*
+ Copyright 2013 Daniel Wirtz <dcode@dcode.io>
+ Copyright 2009 The Closure Library Authors. All Rights Reserved.
+
+ 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.
+ */
+
+var long = createCommonjsModule(function (module) {
+/**
+ * @license long.js (c) 2013 Daniel Wirtz <dcode@dcode.io>
+ * Released under the Apache License, Version 2.0
+ * see: https://github.com/dcodeIO/long.js for details
+ */
+(function(global, factory) {
+
+    /* AMD */ if (typeof commonjsRequire === 'function' && 'object' === "object" && module && module["exports"])
+        module["exports"] = factory();
+    /* Global */ else
+        (global["dcodeIO"] = global["dcodeIO"] || {})["Long"] = factory();
+
+})(commonjsGlobal, function() {
+
+    /**
+     * Constructs a 64 bit two's-complement integer, given its low and high 32 bit values as *signed* integers.
+     *  See the from* functions below for more convenient ways of constructing Longs.
+     * @exports Long
+     * @class A Long class for representing a 64 bit two's-complement integer value.
+     * @param {number} low The low (signed) 32 bits of the long
+     * @param {number} high The high (signed) 32 bits of the long
+     * @param {boolean=} unsigned Whether unsigned or not, defaults to `false` for signed
+     * @constructor
+     */
+    function Long(low, high, unsigned) {
+
+        /**
+         * The low 32 bits as a signed value.
+         * @type {number}
+         */
+        this.low = low | 0;
+
+        /**
+         * The high 32 bits as a signed value.
+         * @type {number}
+         */
+        this.high = high | 0;
+
+        /**
+         * Whether unsigned or not.
+         * @type {boolean}
+         */
+        this.unsigned = !!unsigned;
+    }
+
+    // The internal representation of a long is the two given signed, 32-bit values.
+    // We use 32-bit pieces because these are the size of integers on which
+    // Javascript performs bit-operations.  For operations like addition and
+    // multiplication, we split each number into 16 bit pieces, which can easily be
+    // multiplied within Javascript's floating-point representation without overflow
+    // or change in sign.
+    //
+    // In the algorithms below, we frequently reduce the negative case to the
+    // positive case by negating the input(s) and then post-processing the result.
+    // Note that we must ALWAYS check specially whether those values are MIN_VALUE
+    // (-2^63) because -MIN_VALUE == MIN_VALUE (since 2^63 cannot be represented as
+    // a positive number, it overflows back into a negative).  Not handling this
+    // case would often result in infinite recursion.
+    //
+    // Common constant values ZERO, ONE, NEG_ONE, etc. are defined below the from*
+    // methods on which they depend.
+
+    /**
+     * An indicator used to reliably determine if an object is a Long or not.
+     * @type {boolean}
+     * @const
+     * @private
+     */
+    Long.prototype.__isLong__;
+
+    Object.defineProperty(Long.prototype, "__isLong__", {
+        value: true,
+        enumerable: false,
+        configurable: false
+    });
+
+    /**
+     * @function
+     * @param {*} obj Object
+     * @returns {boolean}
+     * @inner
+     */
+    function isLong(obj) {
+        return (obj && obj["__isLong__"]) === true;
+    }
+
+    /**
+     * Tests if the specified object is a Long.
+     * @function
+     * @param {*} obj Object
+     * @returns {boolean}
+     */
+    Long.isLong = isLong;
+
+    /**
+     * A cache of the Long representations of small integer values.
+     * @type {!Object}
+     * @inner
+     */
+    var INT_CACHE = {};
+
+    /**
+     * A cache of the Long representations of small unsigned integer values.
+     * @type {!Object}
+     * @inner
+     */
+    var UINT_CACHE = {};
+
+    /**
+     * @param {number} value
+     * @param {boolean=} unsigned
+     * @returns {!Long}
+     * @inner
+     */
+    function fromInt(value, unsigned) {
+        var obj, cachedObj, cache;
+        if (unsigned) {
+            value >>>= 0;
+            if (cache = (0 <= value && value < 256)) {
+                cachedObj = UINT_CACHE[value];
+                if (cachedObj)
+                    return cachedObj;
+            }
+            obj = fromBits(value, (value | 0) < 0 ? -1 : 0, true);
+            if (cache)
+                UINT_CACHE[value] = obj;
+            return obj;
+        } else {
+            value |= 0;
+            if (cache = (-128 <= value && value < 128)) {
+                cachedObj = INT_CACHE[value];
+                if (cachedObj)
+                    return cachedObj;
+            }
+            obj = fromBits(value, value < 0 ? -1 : 0, false);
+            if (cache)
+                INT_CACHE[value] = obj;
+            return obj;
+        }
+    }
+
+    /**
+     * Returns a Long representing the given 32 bit integer value.
+     * @function
+     * @param {number} value The 32 bit integer in question
+     * @param {boolean=} unsigned Whether unsigned or not, defaults to `false` for signed
+     * @returns {!Long} The corresponding Long value
+     */
+    Long.fromInt = fromInt;
+
+    /**
+     * @param {number} value
+     * @param {boolean=} unsigned
+     * @returns {!Long}
+     * @inner
+     */
+    function fromNumber(value, unsigned) {
+        if (isNaN(value) || !isFinite(value))
+            return unsigned ? UZERO : ZERO;
+        if (unsigned) {
+            if (value < 0)
+                return UZERO;
+            if (value >= TWO_PWR_64_DBL)
+                return MAX_UNSIGNED_VALUE;
+        } else {
+            if (value <= -TWO_PWR_63_DBL)
+                return MIN_VALUE;
+            if (value + 1 >= TWO_PWR_63_DBL)
+                return MAX_VALUE;
+        }
+        if (value < 0)
+            return fromNumber(-value, unsigned).neg();
+        return fromBits((value % TWO_PWR_32_DBL) | 0, (value / TWO_PWR_32_DBL) | 0, unsigned);
+    }
+
+    /**
+     * Returns a Long representing the given value, provided that it is a finite number. Otherwise, zero is returned.
+     * @function
+     * @param {number} value The number in question
+     * @param {boolean=} unsigned Whether unsigned or not, defaults to `false` for signed
+     * @returns {!Long} The corresponding Long value
+     */
+    Long.fromNumber = fromNumber;
+
+    /**
+     * @param {number} lowBits
+     * @param {number} highBits
+     * @param {boolean=} unsigned
+     * @returns {!Long}
+     * @inner
+     */
+    function fromBits(lowBits, highBits, unsigned) {
+        return new Long(lowBits, highBits, unsigned);
+    }
+
+    /**
+     * Returns a Long representing the 64 bit integer that comes by concatenating the given low and high bits. Each is
+     *  assumed to use 32 bits.
+     * @function
+     * @param {number} lowBits The low 32 bits
+     * @param {number} highBits The high 32 bits
+     * @param {boolean=} unsigned Whether unsigned or not, defaults to `false` for signed
+     * @returns {!Long} The corresponding Long value
+     */
+    Long.fromBits = fromBits;
+
+    /**
+     * @function
+     * @param {number} base
+     * @param {number} exponent
+     * @returns {number}
+     * @inner
+     */
+    var pow_dbl = Math.pow; // Used 4 times (4*8 to 15+4)
+
+    /**
+     * @param {string} str
+     * @param {(boolean|number)=} unsigned
+     * @param {number=} radix
+     * @returns {!Long}
+     * @inner
+     */
+    function fromString(str, unsigned, radix) {
+        if (str.length === 0)
+            throw Error('empty string');
+        if (str === "NaN" || str === "Infinity" || str === "+Infinity" || str === "-Infinity")
+            return ZERO;
+        if (typeof unsigned === 'number') {
+            // For goog.math.long compatibility
+            radix = unsigned,
+            unsigned = false;
+        } else {
+            unsigned = !! unsigned;
+        }
+        radix = radix || 10;
+        if (radix < 2 || 36 < radix)
+            throw RangeError('radix');
+
+        var p;
+        if ((p = str.indexOf('-')) > 0)
+            throw Error('interior hyphen');
+        else if (p === 0) {
+            return fromString(str.substring(1), unsigned, radix).neg();
+        }
+
+        // Do several (8) digits each time through the loop, so as to
+        // minimize the calls to the very expensive emulated div.
+        var radixToPower = fromNumber(pow_dbl(radix, 8));
+
+        var result = ZERO;
+        for (var i = 0; i < str.length; i += 8) {
+            var size = Math.min(8, str.length - i),
+                value = parseInt(str.substring(i, i + size), radix);
+            if (size < 8) {
+                var power = fromNumber(pow_dbl(radix, size));
+                result = result.mul(power).add(fromNumber(value));
+            } else {
+                result = result.mul(radixToPower);
+                result = result.add(fromNumber(value));
+            }
+        }
+        result.unsigned = unsigned;
+        return result;
+    }
+
+    /**
+     * Returns a Long representation of the given string, written using the specified radix.
+     * @function
+     * @param {string} str The textual representation of the Long
+     * @param {(boolean|number)=} unsigned Whether unsigned or not, defaults to `false` for signed
+     * @param {number=} radix The radix in which the text is written (2-36), defaults to 10
+     * @returns {!Long} The corresponding Long value
+     */
+    Long.fromString = fromString;
+
+    /**
+     * @function
+     * @param {!Long|number|string|!{low: number, high: number, unsigned: boolean}} val
+     * @returns {!Long}
+     * @inner
+     */
+    function fromValue(val) {
+        if (val /* is compatible */ instanceof Long)
+            return val;
+        if (typeof val === 'number')
+            return fromNumber(val);
+        if (typeof val === 'string')
+            return fromString(val);
+        // Throws for non-objects, converts non-instanceof Long:
+        return fromBits(val.low, val.high, val.unsigned);
+    }
+
+    /**
+     * Converts the specified value to a Long.
+     * @function
+     * @param {!Long|number|string|!{low: number, high: number, unsigned: boolean}} val Value
+     * @returns {!Long}
+     */
+    Long.fromValue = fromValue;
+
+    // NOTE: the compiler should inline these constant values below and then remove these variables, so there should be
+    // no runtime penalty for these.
+
+    /**
+     * @type {number}
+     * @const
+     * @inner
+     */
+    var TWO_PWR_16_DBL = 1 << 16;
+
+    /**
+     * @type {number}
+     * @const
+     * @inner
+     */
+    var TWO_PWR_24_DBL = 1 << 24;
+
+    /**
+     * @type {number}
+     * @const
+     * @inner
+     */
+    var TWO_PWR_32_DBL = TWO_PWR_16_DBL * TWO_PWR_16_DBL;
+
+    /**
+     * @type {number}
+     * @const
+     * @inner
+     */
+    var TWO_PWR_64_DBL = TWO_PWR_32_DBL * TWO_PWR_32_DBL;
+
+    /**
+     * @type {number}
+     * @const
+     * @inner
+     */
+    var TWO_PWR_63_DBL = TWO_PWR_64_DBL / 2;
+
+    /**
+     * @type {!Long}
+     * @const
+     * @inner
+     */
+    var TWO_PWR_24 = fromInt(TWO_PWR_24_DBL);
+
+    /**
+     * @type {!Long}
+     * @inner
+     */
+    var ZERO = fromInt(0);
+
+    /**
+     * Signed zero.
+     * @type {!Long}
+     */
+    Long.ZERO = ZERO;
+
+    /**
+     * @type {!Long}
+     * @inner
+     */
+    var UZERO = fromInt(0, true);
+
+    /**
+     * Unsigned zero.
+     * @type {!Long}
+     */
+    Long.UZERO = UZERO;
+
+    /**
+     * @type {!Long}
+     * @inner
+     */
+    var ONE = fromInt(1);
+
+    /**
+     * Signed one.
+     * @type {!Long}
+     */
+    Long.ONE = ONE;
+
+    /**
+     * @type {!Long}
+     * @inner
+     */
+    var UONE = fromInt(1, true);
+
+    /**
+     * Unsigned one.
+     * @type {!Long}
+     */
+    Long.UONE = UONE;
+
+    /**
+     * @type {!Long}
+     * @inner
+     */
+    var NEG_ONE = fromInt(-1);
+
+    /**
+     * Signed negative one.
+     * @type {!Long}
+     */
+    Long.NEG_ONE = NEG_ONE;
+
+    /**
+     * @type {!Long}
+     * @inner
+     */
+    var MAX_VALUE = fromBits(0xFFFFFFFF|0, 0x7FFFFFFF|0, false);
+
+    /**
+     * Maximum signed value.
+     * @type {!Long}
+     */
+    Long.MAX_VALUE = MAX_VALUE;
+
+    /**
+     * @type {!Long}
+     * @inner
+     */
+    var MAX_UNSIGNED_VALUE = fromBits(0xFFFFFFFF|0, 0xFFFFFFFF|0, true);
+
+    /**
+     * Maximum unsigned value.
+     * @type {!Long}
+     */
+    Long.MAX_UNSIGNED_VALUE = MAX_UNSIGNED_VALUE;
+
+    /**
+     * @type {!Long}
+     * @inner
+     */
+    var MIN_VALUE = fromBits(0, 0x80000000|0, false);
+
+    /**
+     * Minimum signed value.
+     * @type {!Long}
+     */
+    Long.MIN_VALUE = MIN_VALUE;
+
+    /**
+     * @alias Long.prototype
+     * @inner
+     */
+    var LongPrototype = Long.prototype;
+
+    /**
+     * Converts the Long to a 32 bit integer, assuming it is a 32 bit integer.
+     * @returns {number}
+     */
+    LongPrototype.toInt = function toInt() {
+        return this.unsigned ? this.low >>> 0 : this.low;
+    };
+
+    /**
+     * Converts the Long to a the nearest floating-point representation of this value (double, 53 bit mantissa).
+     * @returns {number}
+     */
+    LongPrototype.toNumber = function toNumber() {
+        if (this.unsigned)
+            return ((this.high >>> 0) * TWO_PWR_32_DBL) + (this.low >>> 0);
+        return this.high * TWO_PWR_32_DBL + (this.low >>> 0);
+    };
+
+    /**
+     * Converts the Long to a string written in the specified radix.
+     * @param {number=} radix Radix (2-36), defaults to 10
+     * @returns {string}
+     * @override
+     * @throws {RangeError} If `radix` is out of range
+     */
+    LongPrototype.toString = function toString(radix) {
+        radix = radix || 10;
+        if (radix < 2 || 36 < radix)
+            throw RangeError('radix');
+        if (this.isZero())
+            return '0';
+        if (this.isNegative()) { // Unsigned Longs are never negative
+            if (this.eq(MIN_VALUE)) {
+                // We need to change the Long value before it can be negated, so we remove
+                // the bottom-most digit in this base and then recurse to do the rest.
+                var radixLong = fromNumber(radix),
+                    div = this.div(radixLong),
+                    rem1 = div.mul(radixLong).sub(this);
+                return div.toString(radix) + rem1.toInt().toString(radix);
+            } else
+                return '-' + this.neg().toString(radix);
+        }
+
+        // Do several (6) digits each time through the loop, so as to
+        // minimize the calls to the very expensive emulated div.
+        var radixToPower = fromNumber(pow_dbl(radix, 6), this.unsigned),
+            rem = this;
+        var result = '';
+        while (true) {
+            var remDiv = rem.div(radixToPower),
+                intval = rem.sub(remDiv.mul(radixToPower)).toInt() >>> 0,
+                digits = intval.toString(radix);
+            rem = remDiv;
+            if (rem.isZero())
+                return digits + result;
+            else {
+                while (digits.length < 6)
+                    digits = '0' + digits;
+                result = '' + digits + result;
+            }
+        }
+    };
+
+    /**
+     * Gets the high 32 bits as a signed integer.
+     * @returns {number} Signed high bits
+     */
+    LongPrototype.getHighBits = function getHighBits() {
+        return this.high;
+    };
+
+    /**
+     * Gets the high 32 bits as an unsigned integer.
+     * @returns {number} Unsigned high bits
+     */
+    LongPrototype.getHighBitsUnsigned = function getHighBitsUnsigned() {
+        return this.high >>> 0;
+    };
+
+    /**
+     * Gets the low 32 bits as a signed integer.
+     * @returns {number} Signed low bits
+     */
+    LongPrototype.getLowBits = function getLowBits() {
+        return this.low;
+    };
+
+    /**
+     * Gets the low 32 bits as an unsigned integer.
+     * @returns {number} Unsigned low bits
+     */
+    LongPrototype.getLowBitsUnsigned = function getLowBitsUnsigned() {
+        return this.low >>> 0;
+    };
+
+    /**
+     * Gets the number of bits needed to represent the absolute value of this Long.
+     * @returns {number}
+     */
+    LongPrototype.getNumBitsAbs = function getNumBitsAbs() {
+        if (this.isNegative()) // Unsigned Longs are never negative
+            return this.eq(MIN_VALUE) ? 64 : this.neg().getNumBitsAbs();
+        var val = this.high != 0 ? this.high : this.low;
+        for (var bit = 31; bit > 0; bit--)
+            if ((val & (1 << bit)) != 0)
+                break;
+        return this.high != 0 ? bit + 33 : bit + 1;
+    };
+
+    /**
+     * Tests if this Long's value equals zero.
+     * @returns {boolean}
+     */
+    LongPrototype.isZero = function isZero() {
+        return this.high === 0 && this.low === 0;
+    };
+
+    /**
+     * Tests if this Long's value is negative.
+     * @returns {boolean}
+     */
+    LongPrototype.isNegative = function isNegative() {
+        return !this.unsigned && this.high < 0;
+    };
+
+    /**
+     * Tests if this Long's value is positive.
+     * @returns {boolean}
+     */
+    LongPrototype.isPositive = function isPositive() {
+        return this.unsigned || this.high >= 0;
+    };
+
+    /**
+     * Tests if this Long's value is odd.
+     * @returns {boolean}
+     */
+    LongPrototype.isOdd = function isOdd() {
+        return (this.low & 1) === 1;
+    };
+
+    /**
+     * Tests if this Long's value is even.
+     * @returns {boolean}
+     */
+    LongPrototype.isEven = function isEven() {
+        return (this.low & 1) === 0;
+    };
+
+    /**
+     * Tests if this Long's value equals the specified's.
+     * @param {!Long|number|string} other Other value
+     * @returns {boolean}
+     */
+    LongPrototype.equals = function equals(other) {
+        if (!isLong(other))
+            other = fromValue(other);
+        if (this.unsigned !== other.unsigned && (this.high >>> 31) === 1 && (other.high >>> 31) === 1)
+            return false;
+        return this.high === other.high && this.low === other.low;
+    };
+
+    /**
+     * Tests if this Long's value equals the specified's. This is an alias of {@link Long#equals}.
+     * @function
+     * @param {!Long|number|string} other Other value
+     * @returns {boolean}
+     */
+    LongPrototype.eq = LongPrototype.equals;
+
+    /**
+     * Tests if this Long's value differs from the specified's.
+     * @param {!Long|number|string} other Other value
+     * @returns {boolean}
+     */
+    LongPrototype.notEquals = function notEquals(other) {
+        return !this.eq(/* validates */ other);
+    };
+
+    /**
+     * Tests if this Long's value differs from the specified's. This is an alias of {@link Long#notEquals}.
+     * @function
+     * @param {!Long|number|string} other Other value
+     * @returns {boolean}
+     */
+    LongPrototype.neq = LongPrototype.notEquals;
+
+    /**
+     * Tests if this Long's value is less than the specified's.
+     * @param {!Long|number|string} other Other value
+     * @returns {boolean}
+     */
+    LongPrototype.lessThan = function lessThan(other) {
+        return this.comp(/* validates */ other) < 0;
+    };
+
+    /**
+     * Tests if this Long's value is less than the specified's. This is an alias of {@link Long#lessThan}.
+     * @function
+     * @param {!Long|number|string} other Other value
+     * @returns {boolean}
+     */
+    LongPrototype.lt = LongPrototype.lessThan;
+
+    /**
+     * Tests if this Long's value is less than or equal the specified's.
+     * @param {!Long|number|string} other Other value
+     * @returns {boolean}
+     */
+    LongPrototype.lessThanOrEqual = function lessThanOrEqual(other) {
+        return this.comp(/* validates */ other) <= 0;
+    };
+
+    /**
+     * Tests if this Long's value is less than or equal the specified's. This is an alias of {@link Long#lessThanOrEqual}.
+     * @function
+     * @param {!Long|number|string} other Other value
+     * @returns {boolean}
+     */
+    LongPrototype.lte = LongPrototype.lessThanOrEqual;
+
+    /**
+     * Tests if this Long's value is greater than the specified's.
+     * @param {!Long|number|string} other Other value
+     * @returns {boolean}
+     */
+    LongPrototype.greaterThan = function greaterThan(other) {
+        return this.comp(/* validates */ other) > 0;
+    };
+
+    /**
+     * Tests if this Long's value is greater than the specified's. This is an alias of {@link Long#greaterThan}.
+     * @function
+     * @param {!Long|number|string} other Other value
+     * @returns {boolean}
+     */
+    LongPrototype.gt = LongPrototype.greaterThan;
+
+    /**
+     * Tests if this Long's value is greater than or equal the specified's.
+     * @param {!Long|number|string} other Other value
+     * @returns {boolean}
+     */
+    LongPrototype.greaterThanOrEqual = function greaterThanOrEqual(other) {
+        return this.comp(/* validates */ other) >= 0;
+    };
+
+    /**
+     * Tests if this Long's value is greater than or equal the specified's. This is an alias of {@link Long#greaterThanOrEqual}.
+     * @function
+     * @param {!Long|number|string} other Other value
+     * @returns {boolean}
+     */
+    LongPrototype.gte = LongPrototype.greaterThanOrEqual;
+
+    /**
+     * Compares this Long's value with the specified's.
+     * @param {!Long|number|string} other Other value
+     * @returns {number} 0 if they are the same, 1 if the this is greater and -1
+     *  if the given one is greater
+     */
+    LongPrototype.compare = function compare(other) {
+        if (!isLong(other))
+            other = fromValue(other);
+        if (this.eq(other))
+            return 0;
+        var thisNeg = this.isNegative(),
+            otherNeg = other.isNegative();
+        if (thisNeg && !otherNeg)
+            return -1;
+        if (!thisNeg && otherNeg)
+            return 1;
+        // At this point the sign bits are the same
+        if (!this.unsigned)
+            return this.sub(other).isNegative() ? -1 : 1;
+        // Both are positive if at least one is unsigned
+        return (other.high >>> 0) > (this.high >>> 0) || (other.high === this.high && (other.low >>> 0) > (this.low >>> 0)) ? -1 : 1;
+    };
+
+    /**
+     * Compares this Long's value with the specified's. This is an alias of {@link Long#compare}.
+     * @function
+     * @param {!Long|number|string} other Other value
+     * @returns {number} 0 if they are the same, 1 if the this is greater and -1
+     *  if the given one is greater
+     */
+    LongPrototype.comp = LongPrototype.compare;
+
+    /**
+     * Negates this Long's value.
+     * @returns {!Long} Negated Long
+     */
+    LongPrototype.negate = function negate() {
+        if (!this.unsigned && this.eq(MIN_VALUE))
+            return MIN_VALUE;
+        return this.not().add(ONE);
+    };
+
+    /**
+     * Negates this Long's value. This is an alias of {@link Long#negate}.
+     * @function
+     * @returns {!Long} Negated Long
+     */
+    LongPrototype.neg = LongPrototype.negate;
+
+    /**
+     * Returns the sum of this and the specified Long.
+     * @param {!Long|number|string} addend Addend
+     * @returns {!Long} Sum
+     */
+    LongPrototype.add = function add(addend) {
+        if (!isLong(addend))
+            addend = fromValue(addend);
+
+        // Divide each number into 4 chunks of 16 bits, and then sum the chunks.
+
+        var a48 = this.high >>> 16;
+        var a32 = this.high & 0xFFFF;
+        var a16 = this.low >>> 16;
+        var a00 = this.low & 0xFFFF;
+
+        var b48 = addend.high >>> 16;
+        var b32 = addend.high & 0xFFFF;
+        var b16 = addend.low >>> 16;
+        var b00 = addend.low & 0xFFFF;
+
+        var c48 = 0, c32 = 0, c16 = 0, c00 = 0;
+        c00 += a00 + b00;
+        c16 += c00 >>> 16;
+        c00 &= 0xFFFF;
+        c16 += a16 + b16;
+        c32 += c16 >>> 16;
+        c16 &= 0xFFFF;
+        c32 += a32 + b32;
+        c48 += c32 >>> 16;
+        c32 &= 0xFFFF;
+        c48 += a48 + b48;
+        c48 &= 0xFFFF;
+        return fromBits((c16 << 16) | c00, (c48 << 16) | c32, this.unsigned);
+    };
+
+    /**
+     * Returns the difference of this and the specified Long.
+     * @param {!Long|number|string} subtrahend Subtrahend
+     * @returns {!Long} Difference
+     */
+    LongPrototype.subtract = function subtract(subtrahend) {
+        if (!isLong(subtrahend))
+            subtrahend = fromValue(subtrahend);
+        return this.add(subtrahend.neg());
+    };
+
+    /**
+     * Returns the difference of this and the specified Long. This is an alias of {@link Long#subtract}.
+     * @function
+     * @param {!Long|number|string} subtrahend Subtrahend
+     * @returns {!Long} Difference
+     */
+    LongPrototype.sub = LongPrototype.subtract;
+
+    /**
+     * Returns the product of this and the specified Long.
+     * @param {!Long|number|string} multiplier Multiplier
+     * @returns {!Long} Product
+     */
+    LongPrototype.multiply = function multiply(multiplier) {
+        if (this.isZero())
+            return ZERO;
+        if (!isLong(multiplier))
+            multiplier = fromValue(multiplier);
+        if (multiplier.isZero())
+            return ZERO;
+        if (this.eq(MIN_VALUE))
+            return multiplier.isOdd() ? MIN_VALUE : ZERO;
+        if (multiplier.eq(MIN_VALUE))
+            return this.isOdd() ? MIN_VALUE : ZERO;
+
+        if (this.isNegative()) {
+            if (multiplier.isNegative())
+                return this.neg().mul(multiplier.neg());
+            else
+                return this.neg().mul(multiplier).neg();
+        } else if (multiplier.isNegative())
+            return this.mul(multiplier.neg()).neg();
+
+        // If both longs are small, use float multiplication
+        if (this.lt(TWO_PWR_24) && multiplier.lt(TWO_PWR_24))
+            return fromNumber(this.toNumber() * multiplier.toNumber(), this.unsigned);
+
+        // Divide each long into 4 chunks of 16 bits, and then add up 4x4 products.
+        // We can skip products that would overflow.
+
+        var a48 = this.high >>> 16;
+        var a32 = this.high & 0xFFFF;
+        var a16 = this.low >>> 16;
+        var a00 = this.low & 0xFFFF;
+
+        var b48 = multiplier.high >>> 16;
+        var b32 = multiplier.high & 0xFFFF;
+        var b16 = multiplier.low >>> 16;
+        var b00 = multiplier.low & 0xFFFF;
+
+        var c48 = 0, c32 = 0, c16 = 0, c00 = 0;
+        c00 += a00 * b00;
+        c16 += c00 >>> 16;
+        c00 &= 0xFFFF;
+        c16 += a16 * b00;
+        c32 += c16 >>> 16;
+        c16 &= 0xFFFF;
+        c16 += a00 * b16;
+        c32 += c16 >>> 16;
+        c16 &= 0xFFFF;
+        c32 += a32 * b00;
+        c48 += c32 >>> 16;
+        c32 &= 0xFFFF;
+        c32 += a16 * b16;
+        c48 += c32 >>> 16;
+        c32 &= 0xFFFF;
+        c32 += a00 * b32;
+        c48 += c32 >>> 16;
+        c32 &= 0xFFFF;
+        c48 += a48 * b00 + a32 * b16 + a16 * b32 + a00 * b48;
+        c48 &= 0xFFFF;
+        return fromBits((c16 << 16) | c00, (c48 << 16) | c32, this.unsigned);
+    };
+
+    /**
+     * Returns the product of this and the specified Long. This is an alias of {@link Long#multiply}.
+     * @function
+     * @param {!Long|number|string} multiplier Multiplier
+     * @returns {!Long} Product
+     */
+    LongPrototype.mul = LongPrototype.multiply;
+
+    /**
+     * Returns this Long divided by the specified. The result is signed if this Long is signed or
+     *  unsigned if this Long is unsigned.
+     * @param {!Long|number|string} divisor Divisor
+     * @returns {!Long} Quotient
+     */
+    LongPrototype.divide = function divide(divisor) {
+        if (!isLong(divisor))
+            divisor = fromValue(divisor);
+        if (divisor.isZero())
+            throw Error('division by zero');
+        if (this.isZero())
+            return this.unsigned ? UZERO : ZERO;
+        var approx, rem, res;
+        if (!this.unsigned) {
+            // This section is only relevant for signed longs and is derived from the
+            // closure library as a whole.
+            if (this.eq(MIN_VALUE)) {
+                if (divisor.eq(ONE) || divisor.eq(NEG_ONE))
+                    return MIN_VALUE;  // recall that -MIN_VALUE == MIN_VALUE
+                else if (divisor.eq(MIN_VALUE))
+                    return ONE;
+                else {
+                    // At this point, we have |other| >= 2, so |this/other| < |MIN_VALUE|.
+                    var halfThis = this.shr(1);
+                    approx = halfThis.div(divisor).shl(1);
+                    if (approx.eq(ZERO)) {
+                        return divisor.isNegative() ? ONE : NEG_ONE;
+                    } else {
+                        rem = this.sub(divisor.mul(approx));
+                        res = approx.add(rem.div(divisor));
+                        return res;
+                    }
+                }
+            } else if (divisor.eq(MIN_VALUE))
+                return this.unsigned ? UZERO : ZERO;
+            if (this.isNegative()) {
+                if (divisor.isNegative())
+                    return this.neg().div(divisor.neg());
+                return this.neg().div(divisor).neg();
+            } else if (divisor.isNegative())
+                return this.div(divisor.neg()).neg();
+            res = ZERO;
+        } else {
+            // The algorithm below has not been made for unsigned longs. It's therefore
+            // required to take special care of the MSB prior to running it.
+            if (!divisor.unsigned)
+                divisor = divisor.toUnsigned();
+            if (divisor.gt(this))
+                return UZERO;
+            if (divisor.gt(this.shru(1))) // 15 >>> 1 = 7 ; with divisor = 8 ; true
+                return UONE;
+            res = UZERO;
+        }
+
+        // Repeat the following until the remainder is less than other:  find a
+        // floating-point that approximates remainder / other *from below*, add this
+        // into the result, and subtract it from the remainder.  It is critical that
+        // the approximate value is less than or equal to the real value so that the
+        // remainder never becomes negative.
+        rem = this;
+        while (rem.gte(divisor)) {
+            // Approximate the result of division. This may be a little greater or
+            // smaller than the actual value.
+            approx = Math.max(1, Math.floor(rem.toNumber() / divisor.toNumber()));
+
+            // We will tweak the approximate result by changing it in the 48-th digit or
+            // the smallest non-fractional digit, whichever is larger.
+            var log2 = Math.ceil(Math.log(approx) / Math.LN2),
+                delta = (log2 <= 48) ? 1 : pow_dbl(2, log2 - 48),
+
+            // Decrease the approximation until it is smaller than the remainder.  Note
+            // that if it is too large, the product overflows and is negative.
+                approxRes = fromNumber(approx),
+                approxRem = approxRes.mul(divisor);
+            while (approxRem.isNegative() || approxRem.gt(rem)) {
+                approx -= delta;
+                approxRes = fromNumber(approx, this.unsigned);
+                approxRem = approxRes.mul(divisor);
+            }
+
+            // We know the answer can't be zero... and actually, zero would cause
+            // infinite recursion since we would make no progress.
+            if (approxRes.isZero())
+                approxRes = ONE;
+
+            res = res.add(approxRes);
+            rem = rem.sub(approxRem);
+        }
+        return res;
+    };
+
+    /**
+     * Returns this Long divided by the specified. This is an alias of {@link Long#divide}.
+     * @function
+     * @param {!Long|number|string} divisor Divisor
+     * @returns {!Long} Quotient
+     */
+    LongPrototype.div = LongPrototype.divide;
+
+    /**
+     * Returns this Long modulo the specified.
+     * @param {!Long|number|string} divisor Divisor
+     * @returns {!Long} Remainder
+     */
+    LongPrototype.modulo = function modulo(divisor) {
+        if (!isLong(divisor))
+            divisor = fromValue(divisor);
+        return this.sub(this.div(divisor).mul(divisor));
+    };
+
+    /**
+     * Returns this Long modulo the specified. This is an alias of {@link Long#modulo}.
+     * @function
+     * @param {!Long|number|string} divisor Divisor
+     * @returns {!Long} Remainder
+     */
+    LongPrototype.mod = LongPrototype.modulo;
+
+    /**
+     * Returns the bitwise NOT of this Long.
+     * @returns {!Long}
+     */
+    LongPrototype.not = function not() {
+        return fromBits(~this.low, ~this.high, this.unsigned);
+    };
+
+    /**
+     * Returns the bitwise AND of this Long and the specified.
+     * @param {!Long|number|string} other Other Long
+     * @returns {!Long}
+     */
+    LongPrototype.and = function and(other) {
+        if (!isLong(other))
+            other = fromValue(other);
+        return fromBits(this.low & other.low, this.high & other.high, this.unsigned);
+    };
+
+    /**
+     * Returns the bitwise OR of this Long and the specified.
+     * @param {!Long|number|string} other Other Long
+     * @returns {!Long}
+     */
+    LongPrototype.or = function or(other) {
+        if (!isLong(other))
+            other = fromValue(other);
+        return fromBits(this.low | other.low, this.high | other.high, this.unsigned);
+    };
+
+    /**
+     * Returns the bitwise XOR of this Long and the given one.
+     * @param {!Long|number|string} other Other Long
+     * @returns {!Long}
+     */
+    LongPrototype.xor = function xor(other) {
+        if (!isLong(other))
+            other = fromValue(other);
+        return fromBits(this.low ^ other.low, this.high ^ other.high, this.unsigned);
+    };
+
+    /**
+     * Returns this Long with bits shifted to the left by the given amount.
+     * @param {number|!Long} numBits Number of bits
+     * @returns {!Long} Shifted Long
+     */
+    LongPrototype.shiftLeft = function shiftLeft(numBits) {
+        if (isLong(numBits))
+            numBits = numBits.toInt();
+        if ((numBits &= 63) === 0)
+            return this;
+        else if (numBits < 32)
+            return fromBits(this.low << numBits, (this.high << numBits) | (this.low >>> (32 - numBits)), this.unsigned);
+        else
+            return fromBits(0, this.low << (numBits - 32), this.unsigned);
+    };
+
+    /**
+     * Returns this Long with bits shifted to the left by the given amount. This is an alias of {@link Long#shiftLeft}.
+     * @function
+     * @param {number|!Long} numBits Number of bits
+     * @returns {!Long} Shifted Long
+     */
+    LongPrototype.shl = LongPrototype.shiftLeft;
+
+    /**
+     * Returns this Long with bits arithmetically shifted to the right by the given amount.
+     * @param {number|!Long} numBits Number of bits
+     * @returns {!Long} Shifted Long
+     */
+    LongPrototype.shiftRight = function shiftRight(numBits) {
+        if (isLong(numBits))
+            numBits = numBits.toInt();
+        if ((numBits &= 63) === 0)
+            return this;
+        else if (numBits < 32)
+            return fromBits((this.low >>> numBits) | (this.high << (32 - numBits)), this.high >> numBits, this.unsigned);
+        else
+            return fromBits(this.high >> (numBits - 32), this.high >= 0 ? 0 : -1, this.unsigned);
+    };
+
+    /**
+     * Returns this Long with bits arithmetically shifted to the right by the given amount. This is an alias of {@link Long#shiftRight}.
+     * @function
+     * @param {number|!Long} numBits Number of bits
+     * @returns {!Long} Shifted Long
+     */
+    LongPrototype.shr = LongPrototype.shiftRight;
+
+    /**
+     * Returns this Long with bits logically shifted to the right by the given amount.
+     * @param {number|!Long} numBits Number of bits
+     * @returns {!Long} Shifted Long
+     */
+    LongPrototype.shiftRightUnsigned = function shiftRightUnsigned(numBits) {
+        if (isLong(numBits))
+            numBits = numBits.toInt();
+        numBits &= 63;
+        if (numBits === 0)
+            return this;
+        else {
+            var high = this.high;
+            if (numBits < 32) {
+                var low = this.low;
+                return fromBits((low >>> numBits) | (high << (32 - numBits)), high >>> numBits, this.unsigned);
+            } else if (numBits === 32)
+                return fromBits(high, 0, this.unsigned);
+            else
+                return fromBits(high >>> (numBits - 32), 0, this.unsigned);
+        }
+    };
+
+    /**
+     * Returns this Long with bits logically shifted to the right by the given amount. This is an alias of {@link Long#shiftRightUnsigned}.
+     * @function
+     * @param {number|!Long} numBits Number of bits
+     * @returns {!Long} Shifted Long
+     */
+    LongPrototype.shru = LongPrototype.shiftRightUnsigned;
+
+    /**
+     * Converts this Long to signed.
+     * @returns {!Long} Signed long
+     */
+    LongPrototype.toSigned = function toSigned() {
+        if (!this.unsigned)
+            return this;
+        return fromBits(this.low, this.high, false);
+    };
+
+    /**
+     * Converts this Long to unsigned.
+     * @returns {!Long} Unsigned long
+     */
+    LongPrototype.toUnsigned = function toUnsigned() {
+        if (this.unsigned)
+            return this;
+        return fromBits(this.low, this.high, true);
+    };
+
+    /**
+     * Converts this Long to its byte representation.
+     * @param {boolean=} le Whether little or big endian, defaults to big endian
+     * @returns {!Array.<number>} Byte representation
+     */
+    LongPrototype.toBytes = function(le) {
+        return le ? this.toBytesLE() : this.toBytesBE();
+    };
+
+    /**
+     * Converts this Long to its little endian byte representation.
+     * @returns {!Array.<number>} Little endian byte representation
+     */
+    LongPrototype.toBytesLE = function() {
+        var hi = this.high,
+            lo = this.low;
+        return [
+             lo         & 0xff,
+            (lo >>>  8) & 0xff,
+            (lo >>> 16) & 0xff,
+            (lo >>> 24) & 0xff,
+             hi         & 0xff,
+            (hi >>>  8) & 0xff,
+            (hi >>> 16) & 0xff,
+            (hi >>> 24) & 0xff
+        ];
+    };
+
+    /**
+     * Converts this Long to its big endian byte representation.
+     * @returns {!Array.<number>} Big endian byte representation
+     */
+    LongPrototype.toBytesBE = function() {
+        var hi = this.high,
+            lo = this.low;
+        return [
+            (hi >>> 24) & 0xff,
+            (hi >>> 16) & 0xff,
+            (hi >>>  8) & 0xff,
+             hi         & 0xff,
+            (lo >>> 24) & 0xff,
+            (lo >>> 16) & 0xff,
+            (lo >>>  8) & 0xff,
+             lo         & 0xff
+        ];
+    };
+
+    return Long;
+});
+});
+
+var s2geometry = createCommonjsModule(function (module) {
+/// S2 Geometry functions
+// the regional scoreboard is based on a level 6 S2 Cell
+// - https://docs.google.com/presentation/d/1Hl4KapfAENAOf4gv-pSngKwvS_jwNVHRPZTTDzXXn6Q/view?pli=1#slide=id.i22
+// at the time of writing there's no actual API for the intel map to retrieve scoreboard data,
+// but it's still useful to plot the score cells on the intel map
+
+
+// the S2 geometry is based on projecting the earth sphere onto a cube, with some scaling of face coordinates to
+// keep things close to approximate equal area for adjacent cells
+// to convert a lat,lng into a cell id:
+// - convert lat,lng to x,y,z
+// - convert x,y,z into face,u,v
+// - u,v scaled to s,t with quadratic formula
+// - s,t converted to integer i,j offsets
+// - i,j converted to a position along a Hubbert space-filling curve
+// - combine face,position to get the cell id
+
+//NOTE: compared to the google S2 geometry library, we vary from their code in the following ways
+// - cell IDs: they combine face and the hilbert curve position into a single 64 bit number. this gives efficient space
+//             and speed. javascript doesn't have appropriate data types, and speed is not cricical, so we use
+//             as [face,[bitpair,bitpair,...]] instead
+// - i,j: they always use 30 bits, adjusting as needed. we use 0 to (1<<level)-1 instead
+//        (so GetSizeIJ for a cell is always 1)
+
+(function (exports) {
+
+var S2 = exports.S2 = { L: {} };
+
+S2.L.LatLng = function (/*Number*/ rawLat, /*Number*/ rawLng, /*Boolean*/ noWrap) {
+  var lat = parseFloat(rawLat, 10);
+  var lng = parseFloat(rawLng, 10);
+
+  if (isNaN(lat) || isNaN(lng)) {
+    throw new Error('Invalid LatLng object: (' + rawLat + ', ' + rawLng + ')');
+  }
+
+  if (noWrap !== true) {
+    lat = Math.max(Math.min(lat, 90), -90);                 // clamp latitude into -90..90
+    lng = (lng + 180) % 360 + ((lng < -180 || lng === 180) ? 180 : -180);   // wrap longtitude into -180..180
+  }
+
+  return { lat: lat, lng: lng };
+};
+
+S2.L.LatLng.DEG_TO_RAD = Math.PI / 180;
+S2.L.LatLng.RAD_TO_DEG = 180 / Math.PI;
+
+/*
+S2.LatLngToXYZ = function(latLng) {
+  // http://stackoverflow.com/questions/8981943/lat-long-to-x-y-z-position-in-js-not-working
+  var lat = latLng.lat;
+  var lon = latLng.lng;
+  var DEG_TO_RAD = Math.PI / 180.0;
+
+  var phi = lat * DEG_TO_RAD;
+  var theta = lon * DEG_TO_RAD;
+
+  var cosLat = Math.cos(phi);
+  var sinLat = Math.sin(phi);
+  var cosLon = Math.cos(theta);
+  var sinLon = Math.sin(theta);
+  var rad = 500.0;
+
+  return [
+    rad * cosLat * cosLon
+  , rad * cosLat * sinLon
+  , rad * sinLat
+  ];
+};
+*/
+S2.LatLngToXYZ = function(latLng) {
+  var d2r = S2.L.LatLng.DEG_TO_RAD;
+
+  var phi = latLng.lat*d2r;
+  var theta = latLng.lng*d2r;
+
+  var cosphi = Math.cos(phi);
+
+  return [Math.cos(theta)*cosphi, Math.sin(theta)*cosphi, Math.sin(phi)];
+};
+
+S2.XYZToLatLng = function(xyz) {
+  var r2d = S2.L.LatLng.RAD_TO_DEG;
+
+  var lat = Math.atan2(xyz[2], Math.sqrt(xyz[0]*xyz[0]+xyz[1]*xyz[1]));
+  var lng = Math.atan2(xyz[1], xyz[0]);
+
+  return S2.L.LatLng(lat*r2d, lng*r2d);
+};
+
+var largestAbsComponent = function(xyz) {
+  var temp = [Math.abs(xyz[0]), Math.abs(xyz[1]), Math.abs(xyz[2])];
+
+  if (temp[0] > temp[1]) {
+    if (temp[0] > temp[2]) {
+      return 0;
+    } else {
+      return 2;
+    }
+  } else {
+    if (temp[1] > temp[2]) {
+      return 1;
+    } else {
+      return 2;
+    }
+  }
+
+};
+
+var faceXYZToUV = function(face,xyz) {
+  var u,v;
+
+  switch (face) {
+    case 0: u =  xyz[1]/xyz[0]; v =  xyz[2]/xyz[0]; break;
+    case 1: u = -xyz[0]/xyz[1]; v =  xyz[2]/xyz[1]; break;
+    case 2: u = -xyz[0]/xyz[2]; v = -xyz[1]/xyz[2]; break;
+    case 3: u =  xyz[2]/xyz[0]; v =  xyz[1]/xyz[0]; break;
+    case 4: u =  xyz[2]/xyz[1]; v = -xyz[0]/xyz[1]; break;
+    case 5: u = -xyz[1]/xyz[2]; v = -xyz[0]/xyz[2]; break;
+    default: throw {error: 'Invalid face'};
+  }
+
+  return [u,v];
+};
+
+
+
+
+S2.XYZToFaceUV = function(xyz) {
+  var face = largestAbsComponent(xyz);
+
+  if (xyz[face] < 0) {
+    face += 3;
+  }
+
+  var uv = faceXYZToUV (face,xyz);
+
+  return [face, uv];
+};
+
+S2.FaceUVToXYZ = function(face,uv) {
+  var u = uv[0];
+  var v = uv[1];
+
+  switch (face) {
+    case 0: return [ 1, u, v];
+    case 1: return [-u, 1, v];
+    case 2: return [-u,-v, 1];
+    case 3: return [-1,-v,-u];
+    case 4: return [ v,-1,-u];
+    case 5: return [ v, u,-1];
+    default: throw {error: 'Invalid face'};
+  }
+};
+
+var singleSTtoUV = function(st) {
+  if (st >= 0.5) {
+    return (1/3.0) * (4*st*st - 1);
+  } else {
+    return (1/3.0) * (1 - (4*(1-st)*(1-st)));
+  }
+};
+
+S2.STToUV = function(st) {
+  return [singleSTtoUV(st[0]), singleSTtoUV(st[1])];
+};
+
+
+var singleUVtoST = function(uv) {
+  if (uv >= 0) {
+    return 0.5 * Math.sqrt (1 + 3*uv);
+  } else {
+    return 1 - 0.5 * Math.sqrt (1 - 3*uv);
+  }
+};
+S2.UVToST = function(uv) {
+  return [singleUVtoST(uv[0]), singleUVtoST(uv[1])];
+};
+
+
+S2.STToIJ = function(st,order) {
+  var maxSize = (1<<order);
+
+  var singleSTtoIJ = function(st) {
+    var ij = Math.floor(st * maxSize);
+    return Math.max(0, Math.min(maxSize-1, ij));
+  };
+
+  return [singleSTtoIJ(st[0]), singleSTtoIJ(st[1])];
+};
+
+
+S2.IJToST = function(ij,order,offsets) {
+  var maxSize = (1<<order);
+
+  return [
+    (ij[0]+offsets[0])/maxSize,
+    (ij[1]+offsets[1])/maxSize
+  ];
+};
+
+
+
+var rotateAndFlipQuadrant = function(n, point, rx, ry)
+{
+       if(ry == 0)
+       {
+               if(rx == 1){
+                       point.x = n - 1 - point.x;
+                       point.y = n - 1 - point.y;
+
+               }
+
+    var x = point.x;
+               point.x = point.y;
+               point.y = x;
+       }
+
+};
+
+
+
+
+
+// hilbert space-filling curve
+// based on http://blog.notdot.net/2009/11/Damn-Cool-Algorithms-Spatial-indexing-with-Quadtrees-and-Hilbert-Curves
+// note: rather then calculating the final integer hilbert position, we just return the list of quads
+// this ensures no precision issues whth large orders (S3 cell IDs use up to 30), and is more
+// convenient for pulling out the individual bits as needed later
+var pointToHilbertQuadList = function(x,y,order,face) {
+  var hilbertMap = {
+    'a': [ [0,'d'], [1,'a'], [3,'b'], [2,'a'] ],
+    'b': [ [2,'b'], [1,'b'], [3,'a'], [0,'c'] ],
+    'c': [ [2,'c'], [3,'d'], [1,'c'], [0,'b'] ],
+    'd': [ [0,'a'], [3,'c'], [1,'d'], [2,'d'] ]
+  };
+
+  if ('number' !== typeof face) {
+    console.warn(new Error("called pointToHilbertQuadList without face value, defaulting to '0'").stack);
+  }
+  var currentSquare = (face % 2) ? 'd' : 'a';
+  var positions = [];
+
+  for (var i=order-1; i>=0; i--) {
+
+    var mask = 1<<i;
+
+    var quad_x = x&mask ? 1 : 0;
+    var quad_y = y&mask ? 1 : 0;
+
+    var t = hilbertMap[currentSquare][quad_x*2+quad_y];
+
+    positions.push(t[0]);
+
+    currentSquare = t[1];
+  }
+
+  return positions;
+};
+
+// S2Cell class
+
+S2.S2Cell = function(){};
+
+S2.S2Cell.FromHilbertQuadKey = function(hilbertQuadkey) {
+  var parts = hilbertQuadkey.split('/');
+  var face = parseInt(parts[0]);
+  var position = parts[1];
+  var maxLevel = position.length;
+  var point = {
+    x : 0,
+    y: 0
+  };
+  var i;
+  var level;
+  var bit;
+  var rx, ry;
+  var val;
+
+       for(i = maxLevel - 1; i >= 0; i--) {
+
+               level = maxLevel - i;
+               bit = position[i];
+               rx = 0;
+    ry = 0;
+               if (bit === '1') {
+                       ry = 1;
+               }
+               else if (bit === '2') {
+                       rx = 1;
+                       ry = 1;
+               }
+               else if (bit === '3') {
+                       rx = 1;
+               }
+
+               val = Math.pow(2, level - 1);
+               rotateAndFlipQuadrant(val, point, rx, ry);
+
+               point.x += val * rx;
+               point.y += val * ry;
+
+       }
+
+  if (face % 2 === 1) {
+    var t = point.x;
+    point.x = point.y;
+    point.y = t;
+  }
+
+
+  return S2.S2Cell.FromFaceIJ(parseInt(face), [point.x, point.y], level);
+};
+
+//static method to construct
+S2.S2Cell.FromLatLng = function(latLng, level) {
+  if ((!latLng.lat && latLng.lat !== 0) || (!latLng.lng && latLng.lng !== 0)) {
+    throw new Error("Pass { lat: lat, lng: lng } to S2.S2Cell.FromLatLng");
+  }
+  var xyz = S2.LatLngToXYZ(latLng);
+
+  var faceuv = S2.XYZToFaceUV(xyz);
+  var st = S2.UVToST(faceuv[1]);
+
+  var ij = S2.STToIJ(st,level);
+
+  return S2.S2Cell.FromFaceIJ (faceuv[0], ij, level);
+};
+
+/*
+S2.faceIjLevelToXyz = function (face, ij, level) {
+  var st = S2.IJToST(ij, level, [0.5, 0.5]);
+  var uv = S2.STToUV(st);
+  var xyz = S2.FaceUVToXYZ(face, uv);
+
+  return S2.XYZToLatLng(xyz);
+  return xyz;
+};
+*/
+
+S2.S2Cell.FromFaceIJ = function(face,ij,level) {
+  var cell = new S2.S2Cell();
+  cell.face = face;
+  cell.ij = ij;
+  cell.level = level;
+
+  return cell;
+};
+
+
+S2.S2Cell.prototype.toString = function() {
+  return 'F'+this.face+'ij['+this.ij[0]+','+this.ij[1]+']@'+this.level;
+};
+
+S2.S2Cell.prototype.getLatLng = function() {
+  var st = S2.IJToST(this.ij,this.level, [0.5,0.5]);
+  var uv = S2.STToUV(st);
+  var xyz = S2.FaceUVToXYZ(this.face, uv);
+
+  return S2.XYZToLatLng(xyz);
+};
+
+S2.S2Cell.prototype.getCornerLatLngs = function() {
+  var result = [];
+  var offsets = [
+    [ 0.0, 0.0 ],
+    [ 0.0, 1.0 ],
+    [ 1.0, 1.0 ],
+    [ 1.0, 0.0 ]
+  ];
+
+  for (var i=0; i<4; i++) {
+    var st = S2.IJToST(this.ij, this.level, offsets[i]);
+    var uv = S2.STToUV(st);
+    var xyz = S2.FaceUVToXYZ(this.face, uv);
+
+    result.push ( S2.XYZToLatLng(xyz) );
+  }
+  return result;
+};
+
+
+S2.S2Cell.prototype.getFaceAndQuads = function () {
+  var quads = pointToHilbertQuadList(this.ij[0], this.ij[1], this.level, this.face);
+
+  return [this.face,quads];
+};
+S2.S2Cell.prototype.toHilbertQuadkey = function () {
+  var quads = pointToHilbertQuadList(this.ij[0], this.ij[1], this.level, this.face);
+
+  return this.face.toString(10) + '/' + quads.join('');
+};
+
+S2.latLngToNeighborKeys = S2.S2Cell.latLngToNeighborKeys = function (lat, lng, level) {
+  return S2.S2Cell.FromLatLng({ lat: lat, lng: lng }, level).getNeighbors().map(function (cell) {
+    return cell.toHilbertQuadkey();
+  });
+};
+S2.S2Cell.prototype.getNeighbors = function() {
+
+  var fromFaceIJWrap = function(face,ij,level) {
+    var maxSize = (1<<level);
+    if (ij[0]>=0 && ij[1]>=0 && ij[0]<maxSize && ij[1]<maxSize) {
+      // no wrapping out of bounds
+      return S2.S2Cell.FromFaceIJ(face,ij,level);
+    } else {
+      // the new i,j are out of range.
+      // with the assumption that they're only a little past the borders we can just take the points as
+      // just beyond the cube face, project to XYZ, then re-create FaceUV from the XYZ vector
+
+      var st = S2.IJToST(ij,level,[0.5,0.5]);
+      var uv = S2.STToUV(st);
+      var xyz = S2.FaceUVToXYZ(face,uv);
+      var faceuv = S2.XYZToFaceUV(xyz);
+      face = faceuv[0];
+      uv = faceuv[1];
+      st = S2.UVToST(uv);
+      ij = S2.STToIJ(st,level);
+      return S2.S2Cell.FromFaceIJ (face, ij, level);
+    }
+  };
+
+  var face = this.face;
+  var i = this.ij[0];
+  var j = this.ij[1];
+  var level = this.level;
+
+
+  return [
+    fromFaceIJWrap(face, [i-1,j], level),
+    fromFaceIJWrap(face, [i,j-1], level),
+    fromFaceIJWrap(face, [i+1,j], level),
+    fromFaceIJWrap(face, [i,j+1], level)
+  ];
+
+};
+
+//
+// Functional Style
+//
+S2.FACE_BITS = 3;
+S2.MAX_LEVEL = 30;
+S2.POS_BITS = (2 * S2.MAX_LEVEL) + 1; // 61 (60 bits of data, 1 bit lsb marker)
+
+S2.facePosLevelToId = S2.S2Cell.facePosLevelToId = S2.fromFacePosLevel = function (faceN, posS, levelN) {
+  var Long = exports.dcodeIO && exports.dcodeIO.Long || long;
+  var faceB;
+  var posB;
+  var bin;
+
+  if (!levelN) {
+    levelN = posS.length;
+  }
+  if (posS.length > levelN) {
+    posS = posS.substr(0, levelN);
+  }
+
+  // 3-bit face value
+  faceB = Long.fromString(faceN.toString(10), true, 10).toString(2);
+  while (faceB.length < S2.FACE_BITS) {
+    faceB = '0' + faceB;
+  }
+
+  // 60-bit position value
+  posB = Long.fromString(posS, true, 4).toString(2);
+  while (posB.length < (2 * levelN)) {
+    posB = '0' + posB;
+  }
+
+  bin = faceB + posB;
+  // 1-bit lsb marker
+  bin += '1';
+  // n-bit padding to 64-bits
+  while (bin.length < (S2.FACE_BITS + S2.POS_BITS)) {
+    bin += '0';
+  }
+
+  return Long.fromString(bin, true, 2).toString(10);
+};
+
+S2.keyToId = S2.S2Cell.keyToId
+= S2.toId = S2.toCellId = S2.fromKey
+= function (key) {
+  var parts = key.split('/');
+
+  return S2.fromFacePosLevel(parts[0], parts[1], parts[1].length);
+};
+
+S2.idToKey = S2.S2Cell.idToKey
+= S2.S2Cell.toKey = S2.toKey
+= S2.fromId = S2.fromCellId
+= S2.S2Cell.toHilbertQuadkey  = S2.toHilbertQuadkey
+= function (idS) {
+  var Long = exports.dcodeIO && exports.dcodeIO.Long || long;
+  var bin = Long.fromString(idS, true, 10).toString(2);
+
+  while (bin.length < (S2.FACE_BITS + S2.POS_BITS)) {
+    bin = '0' + bin;
+  }
+
+  // MUST come AFTER binstr has been left-padded with '0's
+  var lsbIndex = bin.lastIndexOf('1');
+  // substr(start, len)
+  // substring(start, end) // includes start, does not include end
+  var faceB = bin.substring(0, 3);
+  // posB will always be a multiple of 2 (or it's invalid)
+  var posB = bin.substring(3, lsbIndex);
+  var levelN = posB.length / 2;
+
+  var faceS = Long.fromString(faceB, true, 2).toString(10);
+  var posS = Long.fromString(posB, true, 2).toString(4);
+
+  while (posS.length < levelN) {
+    posS = '0' + posS;
+  }
+
+  return faceS + '/' + posS;
+};
+
+S2.keyToLatLng = S2.S2Cell.keyToLatLng = function (key) {
+  var cell2 = S2.S2Cell.FromHilbertQuadKey(key);
+  return cell2.getLatLng();
+};
+
+S2.idToLatLng = S2.S2Cell.idToLatLng = function (id) {
+  var key = S2.idToKey(id);
+  return S2.keyToLatLng(key);
+};
+
+S2.S2Cell.latLngToKey = S2.latLngToKey
+= S2.latLngToQuadkey = function (lat, lng, level) {
+  if (isNaN(level) || level < 1 || level > 30) {
+    throw new Error("'level' is not a number between 1 and 30 (but it should be)");
+  }
+  // TODO
+  //
+  // S2.idToLatLng(id)
+  // S2.keyToLatLng(key)
+  // S2.nextFace(key)     // prevent wrapping on nextKey
+  // S2.prevFace(key)     // prevent wrapping on prevKey
+  //
+  // .toKeyArray(id)  // face,quadtree
+  // .toKey(id)       // hilbert
+  // .toPoint(id)     // ij
+  // .toId(key)       // uint64 (as string)
+  // .toLong(key)     // long.js
+  // .toLatLng(id)    // object? or array?, or string (with comma)?
+  //
+  // maybe S2.HQ.x, S2.GPS.x, S2.CI.x?
+  return S2.S2Cell.FromLatLng({ lat: lat, lng: lng }, level).toHilbertQuadkey();
+};
+
+S2.stepKey = function (key, num) {
+  var Long = exports.dcodeIO && exports.dcodeIO.Long || long;
+  var parts = key.split('/');
+
+  var faceS = parts[0];
+  var posS = parts[1];
+  var level = parts[1].length;
+
+  var posL = Long.fromString(posS, true, 4);
+  // TODO handle wrapping (0 === pos + 1)
+  // (only on the 12 edges of the globe)
+  var otherL;
+  if (num > 0) {
+    otherL = posL.add(Math.abs(num));
+  }
+  else if (num < 0) {
+    otherL = posL.subtract(Math.abs(num));
+  }
+  var otherS = otherL.toString(4);
+
+  if ('0' === otherS) {
+    console.warning(new Error("face/position wrapping is not yet supported"));
+  }
+
+  while (otherS.length < level) {
+    otherS = '0' + otherS;
+  }
+
+  return faceS + '/' + otherS;
+};
+
+S2.S2Cell.prevKey = S2.prevKey = function (key) {
+  return S2.stepKey(key, -1);
+};
+
+S2.S2Cell.nextKey = S2.nextKey = function (key) {
+  return S2.stepKey(key, 1);
+};
+
+})(module.exports );
+});
+
+/**
+ * @class S2GeometryProvider
+ *
+ * @classdesc Geometry provider based on S2 cells.
+ *
+ * @example
+ * ```js
+ * class MyDataProvider extends DataProviderBase {
+ *      ...
+ * }
+ *
+ * const geometryProvider = new S2GeometryProvider();
+ * const dataProvider = new MyDataProvider(geometryProvider);
+ * ```
+ */
+class S2GeometryProvider extends GeometryProviderBase {
+    /**
+     * Create a new S2 geometry provider instance.
+     */
+    constructor(_level = 17) {
+        super();
+        this._level = _level;
+    }
+    /** @inheritdoc */
+    bboxToCellIds(sw, ne) {
+        return this._approxBboxToCellIds(sw, ne);
+    }
+    /** @inheritdoc */
+    getAdjacent(cellId) {
+        const k = s2geometry.S2.idToKey(cellId);
+        const position = k.split('/')[1];
+        const level = position.length;
+        const [a0, a1, a2, a3] = this._getNeighbors(k, level);
+        const existing = [k, a0, a1, a2, a3];
+        const others = Array
+            .from(new Set([
+            ...this._getNeighbors(a0, level),
+            ...this._getNeighbors(a1, level),
+            ...this._getNeighbors(a2, level),
+            ...this._getNeighbors(a3, level),
+        ].filter((o) => {
+            return !existing.includes(o);
+        })));
+        const adjacent = [a0, a1, a2, a3];
+        for (const other of others) {
+            let count = 0;
+            for (const n of this._getNeighbors(other, level)) {
+                if (existing.includes(n)) {
+                    count++;
+                }
+            }
+            if (count === 2) {
+                adjacent.push(other);
+            }
+        }
+        return adjacent.map((a) => s2geometry.S2.keyToId(a));
+    }
+    /** @inheritdoc */
+    getVertices(cellId) {
+        const key = s2geometry.S2.idToKey(cellId);
+        const cell = s2geometry.S2.S2Cell.FromHilbertQuadKey(key);
+        return cell
+            .getCornerLatLngs()
+            .map((c) => {
+            return { lat: c.lat, lng: c.lng };
+        });
+    }
+    /** @inheritdoc */
+    lngLatToCellId(lngLat) {
+        return this._lngLatToId(lngLat, this._level);
+    }
+    _getNeighbors(s2key, level) {
+        const latlng = s2geometry.S2.keyToLatLng(s2key);
+        const neighbors = s2geometry.S2.latLngToNeighborKeys(latlng.lat, latlng.lng, level);
+        return neighbors;
+    }
+    _lngLatToId(lngLat, level) {
+        const s2key = s2geometry.S2.latLngToKey(lngLat.lat, lngLat.lng, level);
+        return s2geometry.S2.keyToId(s2key);
+    }
+}
+
+function convertCameraType(graphCameraType) {
+    switch (graphCameraType) {
+        case "equirectangular":
+        case "spherical":
+            return "spherical";
+        case "fisheye":
+            return "fisheye";
+        default:
+            return "perspective";
+    }
+}
+class GraphConverter {
+    clusterReconstruction(source) {
+        const id = null;
+        const points = source.points;
+        const normalize = 1 / 255;
+        for (const pointId in points) {
+            if (!points.hasOwnProperty(pointId)) {
+                continue;
+            }
+            const color = points[pointId].color;
+            color[0] *= normalize;
+            color[1] *= normalize;
+            color[2] *= normalize;
+        }
+        const lla = source.reference_lla;
+        const reference = {
+            alt: lla.altitude,
+            lat: lla.latitude,
+            lng: lla.longitude,
+        };
+        return {
+            id,
+            points,
+            reference,
+        };
+    }
+    coreImage(source) {
+        const geometry = this._geometry(source.geometry);
+        const computedGeometry = this._geometry(source.computed_geometry);
+        const sequence = { id: source.sequence };
+        const id = source.id;
+        return {
+            computed_geometry: computedGeometry,
+            geometry,
+            id,
+            sequence,
+        };
+    }
+    spatialImage(source) {
+        var _a, _b, _c, _d;
+        source.camera_type = convertCameraType(source.camera_type);
+        source.merge_id = source.merge_cc ? source.merge_cc.toString() : null;
+        source.private = null;
+        const thumbUrl = source.camera_type === 'spherical' ?
+            source.thumb_2048_url : source.thumb_1024_url;
+        source.thumb = (_a = source.thumb) !== null && _a !== void 0 ? _a : { id: null, url: thumbUrl };
+        source.cluster = (_b = source.sfm_cluster) !== null && _b !== void 0 ? _b : { id: null, url: null };
+        source.creator = { id: null, username: null };
+        source.owner = (_c = source.owner) !== null && _c !== void 0 ? _c : { id: null };
+        source.mesh = (_d = source.mesh) !== null && _d !== void 0 ? _d : { id: null, url: null };
+        return source;
+    }
+    _geometry(geometry) {
+        const coords = geometry === null || geometry === void 0 ? void 0 : geometry.coordinates;
+        const lngLat = coords ?
+            {
+                lat: coords[1],
+                lng: coords[0],
+            } : null;
+        return lngLat;
+    }
+}
+
+class GraphQueryCreator {
+    constructor() {
+        this.imagesPath = 'images';
+        this.sequencePath = 'image_ids';
+        this._imageTilesPath = 'tiles';
+        this.coreFields = ['computed_geometry', 'geometry', 'sequence'];
+        this.idFields = ['id'];
+        this.spatialFields = [
+            'altitude',
+            'atomic_scale',
+            'camera_parameters',
+            'camera_type',
+            'captured_at',
+            'compass_angle',
+            'computed_altitude',
+            'computed_compass_angle',
+            'computed_rotation',
+            'exif_orientation',
+            'height',
+            'merge_cc',
+            'mesh',
+            'quality_score',
+            'sfm_cluster',
+            'thumb_1024_url',
+            'thumb_2048_url',
+            'width',
+        ];
+        this.imageTileFields = ['url', 'z', 'x', 'y'];
+    }
+    images(imageIds, fields) {
+        return `image_ids=${imageIds.join(',')}&fields=${fields.join(',')}`;
+    }
+    imagesS2(cellId, fields) {
+        return `s2=${cellId}&fields=${fields.join(',')}`;
+    }
+    imageTiles(z, fields) {
+        return `z=${z}&fields=${fields.join(',')}`;
+    }
+    imageTilesPath(imageId) {
+        return `${imageId}/${this._imageTilesPath}`;
+    }
+    sequence(sequenceId) {
+        return `sequence_id=${sequenceId}`;
+    }
+}
+
+class GraphDataProvider extends DataProviderBase {
+    constructor(options, geometry, converter, queryCreator) {
+        var _a;
+        super(geometry !== null && geometry !== void 0 ? geometry : new S2GeometryProvider());
+        this._convert = converter !== null && converter !== void 0 ? converter : new GraphConverter();
+        this._query = queryCreator !== null && queryCreator !== void 0 ? queryCreator : new GraphQueryCreator();
+        this._method = 'GET';
+        const opts = options !== null && options !== void 0 ? options : {};
+        this._endpoint = (_a = opts.endpoint) !== null && _a !== void 0 ? _a : "https://graph.mapillary.com";
+        this._accessToken = opts.accessToken;
+    }
+    getCluster(url, abort) {
+        return fetchArrayBuffer(url, abort)
+            .then((buffer) => {
+            const reconstructions = decompress(buffer);
+            if (reconstructions.length < 1) {
+                throw new Error('Cluster reconstruction empty');
+            }
+            return this._convert
+                .clusterReconstruction(reconstructions[0]);
+        });
+    }
+    getCoreImages(cellId) {
+        const fields = [
+            ...this._query.idFields,
+            ...this._query.coreFields,
+        ];
+        const query = this._query.imagesS2(cellId, fields);
+        const url = new URL(this._query.imagesPath, this._endpoint).href;
+        return this
+            ._fetchGraphContract(query, url)
+            .then(r => {
+            const result = {
+                cell_id: cellId,
+                images: [],
+            };
+            const items = r.data;
+            for (const item of items) {
+                const coreImage = this._convert.coreImage(item);
+                result.images.push(coreImage);
+            }
+            return result;
+        });
+    }
+    getImageBuffer(url, abort) {
+        return fetchArrayBuffer(url, abort);
+    }
+    getImages(imageIds) {
+        const fields = [
+            ...this._query.idFields,
+            ...this._query.coreFields,
+            ...this._query.spatialFields,
+        ];
+        const query = this._query.images(imageIds, fields);
+        const url = new URL(this._query.imagesPath, this._endpoint).href;
+        return this
+            ._fetchGraphContract(query, url)
+            .then(r => {
+            const result = [];
+            const items = r.data;
+            for (const item of items) {
+                const coreImage = this._convert.coreImage(item);
+                const spatialImage = this._convert.spatialImage(item);
+                const image = Object.assign({}, spatialImage, coreImage);
+                const contract = {
+                    node: image,
+                    node_id: item.id,
+                };
+                result.push(contract);
+            }
+            return result;
+        });
+    }
+    getImageTiles(request) {
+        const fields = [
+            ...this._query.imageTileFields,
+        ];
+        const query = this._query.imageTiles(request.z, fields);
+        const url = new URL(this._query.imageTilesPath(request.imageId), this._endpoint).href;
+        return this
+            ._fetchGraphContract(query, url)
+            .then(r => {
+            const result = {
+                node: r.data,
+                node_id: request.imageId,
+            };
+            return result;
+        });
+    }
+    getMesh(url, abort) {
+        return fetchArrayBuffer(url, abort)
+            .then((buffer) => {
+            return readMeshPbf(buffer);
+        });
+    }
+    getSequence(sequenceId) {
+        const query = this._query.sequence(sequenceId);
+        const url = new URL(this._query.sequencePath, this._endpoint).href;
+        return this
+            ._fetchGraphContract(query, url)
+            .then(r => {
+            const result = {
+                id: sequenceId,
+                image_ids: r.data.map(item => item.id),
+            };
+            return result;
+        });
+    }
+    getSpatialImages(imageIds) {
+        const fields = [
+            ...this._query.idFields,
+            ...this._query.coreFields,
+            ...this._query.spatialFields,
+        ];
+        const query = this._query.images(imageIds, fields);
+        const url = new URL(this._query.imagesPath, this._endpoint).href;
+        return this
+            ._fetchGraphContract(query, url)
+            .then(r => {
+            const result = [];
+            const items = r.data;
+            for (const item of items) {
+                const spatialImage = this._convert.spatialImage(item);
+                const contract = {
+                    node: spatialImage,
+                    node_id: item.id,
+                };
+                result.push(contract);
+            }
+            return result;
+        });
+    }
+    setAccessToken(accessToken) {
+        this._accessToken = accessToken;
+    }
+    _createHeaders() {
+        const headers = [
+            { name: 'Accept', value: 'application/json' },
+            {
+                name: 'Content-Type',
+                value: 'application/x-www-form-urlencoded',
+            },
+        ];
+        if (this._accessToken) {
+            headers.push({
+                name: 'Authorization',
+                value: `OAuth ${this._accessToken}`,
+            });
+        }
+        return headers;
+    }
+    _fetchGraphContract(body, url) {
+        const method = this._method;
+        const headers = this._createHeaders();
+        const query = `${url}?${body}`;
+        return xhrFetch(query, method, "json", headers, null, null)
+            .catch((error) => {
+            const message = this._makeErrorMessage(error);
+            throw new MapillaryError(message);
+        });
+    }
+    _makeErrorMessage(graphError) {
+        const error = graphError.error;
+        const message = error ?
+            `${error.code} (${error.type}, ${error.fbtrace_id}): ${error.message}` :
+            "Failed to fetch data";
+        return message;
+    }
+}
+
+/**
+ * @class Marker
+ *
+ * @classdesc Represents an abstract marker class that should be extended
+ * by marker implementations used in the marker component.
+ */
+class Marker {
+    constructor(id, lngLat) {
+        this._id = id;
+        this._lngLat = lngLat;
+    }
+    /**
+     * Get id.
+     * @returns {string} The id of the marker.
+     */
+    get id() {
+        return this._id;
+    }
+    /**
+     * Get geometry.
+     *
+     * @ignore
+     */
+    get geometry() {
+        return this._geometry;
+    }
+    /**
+     * Get lngLat.
+     * @returns {LngLat} The geographic coordinates of the marker.
+     */
+    get lngLat() {
+        return this._lngLat;
+    }
+    /** @ignore */
+    createGeometry(position) {
+        if (!!this._geometry) {
+            return;
+        }
+        this._createGeometry(position);
+        // update matrix world if raycasting occurs before first render
+        this._geometry.updateMatrixWorld(true);
+    }
+    /** @ignore */
+    disposeGeometry() {
+        if (!this._geometry) {
+            return;
+        }
+        this._disposeGeometry();
+        this._geometry = undefined;
+    }
+    /** @ignore */
+    getInteractiveObjects() {
+        if (!this._geometry) {
+            return [];
+        }
+        return this._getInteractiveObjects();
+    }
+    /** @ignore */
+    lerpAltitude(alt, alpha) {
+        if (!this._geometry) {
+            return;
+        }
+        this._geometry.position.z =
+            (1 - alpha) * this._geometry.position.z + alpha * alt;
+    }
+    /** @ignore */
+    updatePosition(position, lngLat) {
+        if (!!lngLat) {
+            this._lngLat.lat = lngLat.lat;
+            this._lngLat.lng = lngLat.lng;
+        }
+        if (!this._geometry) {
+            return;
+        }
+        this._geometry.position.fromArray(position);
+        this._geometry.updateMatrixWorld(true);
+    }
+}
+
+/**
+ * @class CircleMarker
+ *
+ * @classdesc Non-interactive marker with a flat circle shape. The circle
+ * marker can not be configured to be interactive.
+ *
+ * Circle marker properties can not be updated after creation.
+ *
+ * To create and add one `CircleMarker` with default configuration
+ * and one with configuration use
+ *
+ * @example
+ * ```js
+ * var defaultMarker = new CircleMarker(
+ *     "id-1",
+ *     { lat: 0, lng: 0, });
+ *
+ * var configuredMarker = new CircleMarker(
+ *     "id-2",
+ *     { lat: 0, lng: 0, },
+ *     {
+ *         color: "#0ff",
+ *         opacity: 0.3,
+ *         radius: 0.7,
+ *     });
+ *
+ * markerComponent.add([defaultMarker, configuredMarker]);
+ * ```
+ */
+class CircleMarker extends Marker {
+    constructor(id, lngLat, options) {
+        super(id, lngLat);
+        options = !!options ? options : {};
+        this._color = options.color != null ? options.color : 0xffffff;
+        this._opacity = options.opacity != null ? options.opacity : 0.4;
+        this._radius = options.radius != null ? options.radius : 1;
+    }
+    _createGeometry(position) {
+        const circle = new Mesh(new CircleGeometry(this._radius, 16), new MeshBasicMaterial({
+            color: this._color,
+            opacity: this._opacity,
+            transparent: true,
+        }));
+        circle.up.fromArray([0, 0, 1]);
+        circle.renderOrder = -1;
+        const group = new Object3D();
+        group.add(circle);
+        group.position.fromArray(position);
+        this._geometry = group;
+    }
+    _disposeGeometry() {
+        for (let mesh of this._geometry.children) {
+            mesh.geometry.dispose();
+            mesh.material.dispose();
+        }
+    }
+    _getInteractiveObjects() {
+        return [];
+    }
+}
+
+/**
+ * @class SimpleMarker
+ *
+ * @classdesc Interactive marker with ice cream shape. The sphere
+ * inside the ice cream can be configured to be interactive.
+ *
+ * Simple marker properties can not be updated after creation.
+ *
+ * To create and add one `SimpleMarker` with default configuration
+ * (non-interactive) and one interactive with configuration use
+ *
+ * @example
+ * ```js
+ * var defaultMarker = new SimpleMarker(
+ *     "id-1",
+ *     { lat: 0, lng: 0, });
+ *
+ * var interactiveMarker = new SimpleMarker(
+ *     "id-2",
+ *     { lat: 0, lng: 0, },
+ *     {
+ *         ballColor: "#00f",
+ *         ballOpacity: 0.5,
+ *         color: "#00f",
+ *         interactive: true,
+ *         opacity: 0.3,
+ *         radius: 0.7,
+ *     });
+ *
+ * markerComponent.add([defaultMarker, interactiveMarker]);
+ * ```
+ */
+class SimpleMarker extends Marker {
+    constructor(id, lngLat, options) {
+        super(id, lngLat);
+        options = !!options ? options : {};
+        this._ballColor = options.ballColor != null ? options.ballColor : 0xff0000;
+        this._ballOpacity = options.ballOpacity != null ? options.ballOpacity : 0.8;
+        this._circleToRayAngle = 2;
+        this._color = options.color != null ? options.color : 0xff0000;
+        this._interactive = !!options.interactive;
+        this._opacity = options.opacity != null ? options.opacity : 0.4;
+        this._radius = options.radius != null ? options.radius : 1;
+    }
+    _createGeometry(position) {
+        const radius = this._radius;
+        const height = this._markerHeight(radius);
+        const markerMaterial = new MeshBasicMaterial({
+            color: this._color,
+            opacity: this._opacity,
+            transparent: true,
+            depthWrite: false,
+        });
+        const marker = new Mesh(this._createMarkerGeometry(radius, 8, 8), markerMaterial);
+        const interactive = new Mesh(new SphereGeometry(radius / 2, 8, 8), new MeshBasicMaterial({
+            color: this._ballColor,
+            opacity: this._ballOpacity,
+            transparent: true,
+        }));
+        interactive.position.z = height;
+        interactive.renderOrder = 1;
+        const group = new Object3D();
+        group.add(interactive);
+        group.add(marker);
+        group.position.fromArray(position);
+        this._geometry = group;
+    }
+    _disposeGeometry() {
+        for (const mesh of this._geometry.children) {
+            mesh.geometry.dispose();
+            mesh.material.dispose();
+        }
+    }
+    _getInteractiveObjects() {
+        return this._interactive ? [this._geometry.children[0]] : [];
+    }
+    _markerHeight(radius) {
+        const t = Math.tan(Math.PI - this._circleToRayAngle);
+        return radius * Math.sqrt(1 + t * t);
+    }
+    _createMarkerGeometry(radius, widthSegments, heightSegments) {
+        const height = this._markerHeight(radius);
+        const circleToRayAngle = this._circleToRayAngle;
+        const indexRows = [];
+        const positions = new Float32Array(3 * (widthSegments + 1) * (heightSegments + 1));
+        let positionIndex = 0;
+        for (let y = 0; y <= heightSegments; ++y) {
+            const indexRow = [];
+            for (let x = 0; x <= widthSegments; ++x) {
+                const u = x / widthSegments * Math.PI * 2;
+                const v = y / heightSegments * Math.PI;
+                let r = radius;
+                if (v > circleToRayAngle) {
+                    const t = Math.tan(v - circleToRayAngle);
+                    r = radius * Math.sqrt(1 + t * t);
+                }
+                const arrayIndex = 3 * positionIndex;
+                const sinv = Math.sin(v);
+                positions[arrayIndex + 0] = r * Math.cos(u) * sinv;
+                positions[arrayIndex + 1] = r * Math.sin(u) * sinv;
+                positions[arrayIndex + 2] = r * Math.cos(v) + height;
+                indexRow.push(positionIndex++);
+            }
+            indexRows.push(indexRow);
+        }
+        const indices = new Uint16Array(6 * widthSegments * heightSegments);
+        let index = 0;
+        for (let y = 0; y < heightSegments; ++y) {
+            for (let x = 0; x < widthSegments; ++x) {
+                const pi1 = indexRows[y][x + 1];
+                const pi2 = indexRows[y][x];
+                const pi3 = indexRows[y + 1][x];
+                const pi4 = indexRows[y + 1][x + 1];
+                indices[index++] = pi1;
+                indices[index++] = pi2;
+                indices[index++] = pi4;
+                indices[index++] = pi2;
+                indices[index++] = pi3;
+                indices[index++] = pi4;
+            }
+        }
+        const geometry = new BufferGeometry();
+        const positionAttribute = new BufferAttribute(positions, 3);
+        geometry.setAttribute("position", positionAttribute);
+        geometry.setIndex(new BufferAttribute(indices, 1));
+        return geometry;
+    }
+}
+
+/**
+ * @class Popup
+ *
+ * @classdesc Popup instance for rendering custom HTML content
+ * on top of images. Popups are based on 2D basic image coordinates
+ * (see the {@link Viewer} class documentation for more information about coordinate
+ * systems) and a certain popup is therefore only relevant to a single image.
+ * Popups related to a certain image should be removed when moving
+ * to another image.
+ *
+ * A popup must have both its content and its point or rect set to be
+ * rendered. Popup options can not be updated after creation but the
+ * basic point or rect as well as its content can be changed by calling
+ * the appropriate methods.
+ *
+ * To create and add one `Popup` with default configuration
+ * (tooltip visuals and automatic float) and one with specific options
+ * use
+ *
+ * @example
+ * ```js
+ * var defaultSpan = document.createElement('span');
+ * defaultSpan.innerHTML = 'hello default';
+ *
+ * var defaultPopup = new Popup();
+ * defaultPopup.setDOMContent(defaultSpan);
+ * defaultPopup.setBasicPoint([0.3, 0.3]);
+ *
+ * var cleanSpan = document.createElement('span');
+ * cleanSpan.innerHTML = 'hello clean';
+ *
+ * var cleanPopup = new Popup({
+ *     clean: true,
+ *     float: Alignment.Top,
+ *     offset: 10,
+ *     opacity: 0.7,
+ * });
+ *
+ * cleanPopup.setDOMContent(cleanSpan);
+ * cleanPopup.setBasicPoint([0.6, 0.6]);
+ *
+ * popupComponent.add([defaultPopup, cleanPopup]);
+ * ```
+ *
+ * @description Implementation of API methods and API documentation inspired
+ * by/used from https://github.com/mapbox/mapbox-gl-js/blob/v0.38.0/src/ui/popup.js
+ */
+class Popup {
+    constructor(options, viewportCoords, dom) {
+        this._options = {};
+        options = !!options ? options : {};
+        this._options.capturePointer = options.capturePointer === false ?
+            options.capturePointer : true;
+        this._options.clean = options.clean;
+        this._options.float = options.float;
+        this._options.offset = options.offset;
+        this._options.opacity = options.opacity;
+        this._options.position = options.position;
+        this._dom = !!dom ? dom : new DOM();
+        this._viewportCoords = !!viewportCoords ? viewportCoords : new ViewportCoords();
+        this._notifyChanged$ = new Subject();
+    }
+    /**
+     * @description Internal observable used by the component to
+     * render the popup when its position or content has changed.
+     * @ignore
+     */
+    get changed$() {
+        return this._notifyChanged$;
+    }
+    /**
+     * @description Internal method used by the component to
+     * remove all references to the popup.
+     * @ignore
+     */
+    remove() {
+        if (this._content && this._content.parentNode) {
+            this._content.parentNode.removeChild(this._content);
+        }
+        if (this._container) {
+            this._container.parentNode.removeChild(this._container);
+            delete this._container;
+        }
+        if (this._parentContainer) {
+            delete this._parentContainer;
+        }
+    }
+    /**
+     * Sets a 2D basic image coordinates point to the popup's anchor, and
+     * moves the popup to it.
+     *
+     * @description Overwrites any previously set point or rect.
+     *
+     * @param {Array<number>} basicPoint - Point in 2D basic image coordinates.
+     *
+     * @example
+     * ```js
+     * var popup = new Popup();
+     * popup.setText('hello image');
+     * popup.setBasicPoint([0.3, 0.3]);
+     *
+     * popupComponent.add([popup]);
+     * ```
+     */
+    setBasicPoint(basicPoint) {
+        this._point = basicPoint.slice();
+        this._rect = null;
+        this._notifyChanged$.next(this);
+    }
+    /**
+     * Sets a 2D basic image coordinates rect to the popup's anchor, and
+     * moves the popup to it.
+     *
+     * @description Overwrites any previously set point or rect.
+     *
+     * @param {Array<number>} basicRect - Rect in 2D basic image
+     * coordinates ([topLeftX, topLeftY, bottomRightX, bottomRightY]) .
+     *
+     * @example
+     * ```js
+     * var popup = new Popup();
+     * popup.setText('hello image');
+     * popup.setBasicRect([0.3, 0.3, 0.5, 0.6]);
+     *
+     * popupComponent.add([popup]);
+     * ```
+     */
+    setBasicRect(basicRect) {
+        this._rect = basicRect.slice();
+        this._point = null;
+        this._notifyChanged$.next(this);
+    }
+    /**
+     * Sets the popup's content to the element provided as a DOM node.
+     *
+     * @param {Node} htmlNode - A DOM node to be used as content for the popup.
+     *
+     * @example
+     * ```js
+     * var div = document.createElement('div');
+     * div.innerHTML = 'hello image';
+     *
+     * var popup = new Popup();
+     * popup.setDOMContent(div);
+     * popup.setBasicPoint([0.3, 0.3]);
+     *
+     * popupComponent.add([popup]);
+     * ```
+     */
+    setDOMContent(htmlNode) {
+        if (this._content && this._content.parentNode) {
+            this._content.parentNode.removeChild(this._content);
+        }
+        const className = "mapillary-popup-content" +
+            (this._options.clean === true ? "-clean" : "") +
+            (this._options.capturePointer === true ? " mapillary-popup-capture-pointer" : "");
+        this._content = this._dom.createElement("div", className, this._container);
+        this._content.appendChild(htmlNode);
+        this._notifyChanged$.next(this);
+    }
+    /**
+     * Sets the popup's content to the HTML provided as a string.
+     *
+     * @description This method does not perform HTML filtering or sanitization,
+     * and must be used only with trusted content. Consider
+     * {@link Popup.setText} if the
+     * content is an untrusted text string.
+     *
+     * @param {string} html - A string representing HTML content for the popup.
+     *
+     * @example
+     * ```js
+     * var popup = new Popup();
+     * popup.setHTML('<div>hello image</div>');
+     * popup.setBasicPoint([0.3, 0.3]);
+     *
+     * popupComponent.add([popup]);
+     * ```
+     */
+    setHTML(html) {
+        const frag = this._dom.document.createDocumentFragment();
+        const temp = this._dom.createElement("body");
+        let child;
+        temp.innerHTML = html;
+        while (true) {
+            child = temp.firstChild;
+            if (!child) {
+                break;
+            }
+            frag.appendChild(child);
+        }
+        this.setDOMContent(frag);
+    }
+    /**
+     * Sets the popup's content to a string of text.
+     *
+     * @description This function creates a Text node in the DOM, so it cannot insert raw HTML.
+     * Use this method for security against XSS if the popup content is user-provided.
+     *
+     * @param {string} text - Textual content for the popup.
+     *
+     * @example
+     * ```js
+     * var popup = new Popup();
+     * popup.setText('hello image');
+     * popup.setBasicPoint([0.3, 0.3]);
+     *
+     * popupComponent.add([popup]);
+     * ```
+     */
+    setText(text) {
+        this.setDOMContent(this._dom.document.createTextNode(text));
+    }
+    /**
+     * @description Internal method for attaching the popup to
+     * its parent container so that it is rendered in the DOM tree.
+     * @ignore
+     */
+    setParentContainer(parentContainer) {
+        this._parentContainer = parentContainer;
+    }
+    /**
+     * @description Internal method for updating the rendered
+     * position of the popup called by the popup component.
+     * @ignore
+     */
+    update(renderCamera, size, transform) {
+        if (!this._parentContainer || !this._content) {
+            return;
+        }
+        if (!this._point && !this._rect) {
+            return;
+        }
+        if (!this._container) {
+            this._container = this._dom.createElement("div", "mapillary-popup", this._parentContainer);
+            const showTip = this._options.clean !== true &&
+                this._options.float !== Alignment.Center;
+            if (showTip) {
+                const tipClassName = "mapillary-popup-tip" +
+                    (this._options.capturePointer === true ? " mapillary-popup-capture-pointer" : "");
+                this._tip = this._dom.createElement("div", tipClassName, this._container);
+                this._dom.createElement("div", "mapillary-popup-tip-inner", this._tip);
+            }
+            this._container.appendChild(this._content);
+            this._parentContainer.appendChild(this._container);
+            if (this._options.opacity != null) {
+                this._container.style.opacity = this._options.opacity.toString();
+            }
+        }
+        let pointPixel = null;
+        let position = this._alignmentToPopupAligment(this._options.position);
+        let float = this._alignmentToPopupAligment(this._options.float);
+        const classList = this._container.classList;
+        if (this._point != null) {
+            pointPixel =
+                this._viewportCoords.basicToCanvasSafe(this._point[0], this._point[1], { offsetHeight: size.height, offsetWidth: size.width }, transform, renderCamera.perspective);
+        }
+        else {
+            const alignments = ["center", "top", "bottom", "left", "right", "top-left", "top-right", "bottom-left", "bottom-right"];
+            let appliedPosition = null;
+            for (const alignment of alignments) {
+                if (classList.contains(`mapillary-popup-float-${alignment}`)) {
+                    appliedPosition = alignment;
+                    break;
+                }
+            }
+            [pointPixel, position] = this._rectToPixel(this._rect, position, appliedPosition, renderCamera, size, transform);
+            if (!float) {
+                float = position;
+            }
+        }
+        if (pointPixel == null) {
+            this._container.style.display = "none";
+            return;
+        }
+        this._container.style.display = "";
+        if (!float) {
+            const width = this._container.offsetWidth;
+            const height = this._container.offsetHeight;
+            const floats = this._pixelToFloats(pointPixel, size, width, height);
+            float = floats.length === 0 ? "top" : floats.join("-");
+        }
+        const offset = this._normalizeOffset(this._options.offset);
+        pointPixel = [pointPixel[0] + offset[float][0], pointPixel[1] + offset[float][1]];
+        pointPixel = [Math.round(pointPixel[0]), Math.round(pointPixel[1])];
+        const floatTranslate = {
+            "bottom": "translate(-50%,0)",
+            "bottom-left": "translate(-100%,0)",
+            "bottom-right": "translate(0,0)",
+            "center": "translate(-50%,-50%)",
+            "left": "translate(-100%,-50%)",
+            "right": "translate(0,-50%)",
+            "top": "translate(-50%,-100%)",
+            "top-left": "translate(-100%,-100%)",
+            "top-right": "translate(0,-100%)",
+        };
+        for (const key in floatTranslate) {
+            if (!floatTranslate.hasOwnProperty(key)) {
+                continue;
+            }
+            classList.remove(`mapillary-popup-float-${key}`);
+        }
+        classList.add(`mapillary-popup-float-${float}`);
+        this._container.style.transform = `${floatTranslate[float]} translate(${pointPixel[0]}px,${pointPixel[1]}px)`;
+    }
+    _rectToPixel(rect, position, appliedPosition, renderCamera, size, transform) {
+        if (!position) {
+            const width = this._container.offsetWidth;
+            const height = this._container.offsetHeight;
+            const floatOffsets = {
+                "bottom": [0, height / 2],
+                "bottom-left": [-width / 2, height / 2],
+                "bottom-right": [width / 2, height / 2],
+                "left": [-width / 2, 0],
+                "right": [width / 2, 0],
+                "top": [0, -height / 2],
+                "top-left": [-width / 2, -height / 2],
+                "top-right": [width / 2, -height / 2],
+            };
+            const automaticPositions = ["top", "bottom", "left", "right"];
+            let largestVisibleArea = [0, null, null];
+            for (const automaticPosition of automaticPositions) {
+                const autoPointBasic = this._pointFromRectPosition(rect, automaticPosition);
+                const autoPointPixel = this._viewportCoords.basicToCanvasSafe(autoPointBasic[0], autoPointBasic[1], { offsetHeight: size.height, offsetWidth: size.width }, transform, renderCamera.perspective);
+                if (autoPointPixel == null) {
+                    continue;
+                }
+                const floatOffset = floatOffsets[automaticPosition];
+                const offsetedPosition = [autoPointPixel[0] + floatOffset[0], autoPointPixel[1] + floatOffset[1]];
+                const staticCoeff = appliedPosition != null && appliedPosition === automaticPosition ? 1 : 0.7;
+                const floats = this._pixelToFloats(offsetedPosition, size, width / staticCoeff, height / (2 * staticCoeff));
+                if (floats.length === 0 &&
+                    autoPointPixel[0] > 0 &&
+                    autoPointPixel[0] < size.width &&
+                    autoPointPixel[1] > 0 &&
+                    autoPointPixel[1] < size.height) {
+                    return [autoPointPixel, automaticPosition];
+                }
+                const minX = Math.max(offsetedPosition[0] - width / 2, 0);
+                const maxX = Math.min(offsetedPosition[0] + width / 2, size.width);
+                const minY = Math.max(offsetedPosition[1] - height / 2, 0);
+                const maxY = Math.min(offsetedPosition[1] + height / 2, size.height);
+                const visibleX = Math.max(0, maxX - minX);
+                const visibleY = Math.max(0, maxY - minY);
+                const visibleArea = staticCoeff * visibleX * visibleY;
+                if (visibleArea > largestVisibleArea[0]) {
+                    largestVisibleArea[0] = visibleArea;
+                    largestVisibleArea[1] = autoPointPixel;
+                    largestVisibleArea[2] = automaticPosition;
+                }
+            }
+            if (largestVisibleArea[0] > 0) {
+                return [largestVisibleArea[1], largestVisibleArea[2]];
+            }
+        }
+        const pointBasic = this._pointFromRectPosition(rect, position);
+        const pointPixel = this._viewportCoords.basicToCanvasSafe(pointBasic[0], pointBasic[1], { offsetHeight: size.height, offsetWidth: size.width }, transform, renderCamera.perspective);
+        return [pointPixel, position != null ? position : "top"];
+    }
+    _alignmentToPopupAligment(float) {
+        switch (float) {
+            case Alignment.Bottom:
+                return "bottom";
+            case Alignment.BottomLeft:
+                return "bottom-left";
+            case Alignment.BottomRight:
+                return "bottom-right";
+            case Alignment.Center:
+                return "center";
+            case Alignment.Left:
+                return "left";
+            case Alignment.Right:
+                return "right";
+            case Alignment.Top:
+                return "top";
+            case Alignment.TopLeft:
+                return "top-left";
+            case Alignment.TopRight:
+                return "top-right";
+            default:
+                return null;
+        }
+    }
+    _normalizeOffset(offset) {
+        if (offset == null) {
+            return this._normalizeOffset(0);
+        }
+        if (typeof offset === "number") {
+            // input specifies a radius
+            const sideOffset = offset;
+            const sign = sideOffset >= 0 ? 1 : -1;
+            const cornerOffset = sign * Math.round(Math.sqrt(0.5 * Math.pow(sideOffset, 2)));
+            return {
+                "bottom": [0, sideOffset],
+                "bottom-left": [-cornerOffset, cornerOffset],
+                "bottom-right": [cornerOffset, cornerOffset],
+                "center": [0, 0],
+                "left": [-sideOffset, 0],
+                "right": [sideOffset, 0],
+                "top": [0, -sideOffset],
+                "top-left": [-cornerOffset, -cornerOffset],
+                "top-right": [cornerOffset, -cornerOffset],
+            };
+        }
+        else {
+            // input specifes a value for each position
+            return {
+                "bottom": offset.bottom || [0, 0],
+                "bottom-left": offset.bottomLeft || [0, 0],
+                "bottom-right": offset.bottomRight || [0, 0],
+                "center": offset.center || [0, 0],
+                "left": offset.left || [0, 0],
+                "right": offset.right || [0, 0],
+                "top": offset.top || [0, 0],
+                "top-left": offset.topLeft || [0, 0],
+                "top-right": offset.topRight || [0, 0],
+            };
+        }
+    }
+    _pixelToFloats(pointPixel, size, width, height) {
+        const floats = [];
+        if (pointPixel[1] < height) {
+            floats.push("bottom");
+        }
+        else if (pointPixel[1] > size.height - height) {
+            floats.push("top");
+        }
+        if (pointPixel[0] < width / 2) {
+            floats.push("right");
+        }
+        else if (pointPixel[0] > size.width - width / 2) {
+            floats.push("left");
+        }
+        return floats;
+    }
+    _pointFromRectPosition(rect, position) {
+        const x0 = rect[0];
+        const x1 = rect[0] < rect[2] ? rect[2] : rect[2] + 1;
+        const y0 = rect[1];
+        const y1 = rect[3];
+        switch (position) {
+            case "bottom":
+                return [(x0 + x1) / 2, y1];
+            case "bottom-left":
+                return [x0, y1];
+            case "bottom-right":
+                return [x1, y1];
+            case "center":
+                return [(x0 + x1) / 2, (y0 + y1) / 2];
+            case "left":
+                return [x0, (y0 + y1) / 2];
+            case "right":
+                return [x1, (y0 + y1) / 2];
+            case "top":
+                return [(x0 + x1) / 2, y0];
+            case "top-left":
+                return [x0, y0];
+            case "top-right":
+                return [x1, y0];
+            default:
+                return [(x0 + x1) / 2, y1];
+        }
+    }
+}
+
+function isBrowser() {
+    return (typeof window !== "undefined" &&
+        typeof document !== "undefined");
+}
+function isArraySupported() {
+    return !!(Array.prototype &&
+        Array.prototype.concat &&
+        Array.prototype.filter &&
+        Array.prototype.includes &&
+        Array.prototype.indexOf &&
+        Array.prototype.join &&
+        Array.prototype.map &&
+        Array.prototype.push &&
+        Array.prototype.pop &&
+        Array.prototype.reverse &&
+        Array.prototype.shift &&
+        Array.prototype.slice &&
+        Array.prototype.splice &&
+        Array.prototype.sort &&
+        Array.prototype.unshift);
+}
+function isBlobSupported() {
+    return ("Blob" in window &&
+        "URL" in window);
+}
+function isFunctionSupported() {
+    return !!(Function.prototype &&
+        Function.prototype.apply &&
+        Function.prototype.bind);
+}
+function isJSONSupported() {
+    return ("JSON" in window &&
+        "parse" in JSON &&
+        "stringify" in JSON);
+}
+function isMapSupported() {
+    return "Map" in window;
+}
+function isObjectSupported() {
+    return !!(Object.assign &&
+        Object.keys &&
+        Object.values);
+}
+function isPromiseSupported() {
+    return !!("Promise" in window &&
+        Promise.resolve &&
+        Promise.reject &&
+        Promise.prototype &&
+        Promise.prototype.catch &&
+        Promise.prototype.then);
+}
+function isSetSupported() {
+    return "Set" in window;
+}
+let isWebGLSupportedCache = undefined;
+function isWebGLSupportedCached() {
+    if (isWebGLSupportedCache === undefined) {
+        isWebGLSupportedCache = isWebGLSupported();
+    }
+    return isWebGLSupportedCache;
+}
+function isWebGLSupported() {
+    const attributes = {
+        alpha: false,
+        antialias: false,
+        depth: true,
+        failIfMajorPerformanceCaveat: false,
+        premultipliedAlpha: true,
+        preserveDrawingBuffer: false,
+        stencil: true,
+    };
+    const canvas = document.createElement("canvas");
+    const webGL2Context = canvas.getContext("webgl2", attributes);
+    if (!!webGL2Context) {
+        return true;
+    }
+    const context = canvas.getContext("webgl", attributes) ||
+        canvas
+            .getContext("experimental-webgl", attributes);
+    if (!context) {
+        return false;
+    }
+    const requiredExtensions = ["OES_standard_derivatives"];
+    const supportedExtensions = context.getSupportedExtensions();
+    for (const requiredExtension of requiredExtensions) {
+        if (supportedExtensions.indexOf(requiredExtension) === -1) {
+            return false;
+        }
+    }
+    return true;
+}
+/**
+ * Test whether the current browser supports the full
+ * functionality of MapillaryJS.
+ *
+ * @description The full functionality includes WebGL rendering.
+ *
+ * @return {boolean}
+ *
+ * @example `var supported = isSupported();`
+ */
+function isSupported() {
+    return isFallbackSupported() &&
+        isWebGLSupportedCached();
+}
+/**
+ * Test whether the current browser supports the fallback
+ * functionality of MapillaryJS.
+ *
+ * @description The fallback functionality does not include WebGL
+ * rendering, only 2D canvas rendering.
+ *
+ * @return {boolean}
+ *
+ * @example `var fallbackSupported = isFallbackSupported();`
+ */
+function isFallbackSupported() {
+    return isBrowser() &&
+        isArraySupported() &&
+        isBlobSupported() &&
+        isFunctionSupported() &&
+        isJSONSupported() &&
+        isMapSupported() &&
+        isObjectSupported() &&
+        isPromiseSupported() &&
+        isSetSupported();
+}
+
+/**
+ * Enumeration for camera controls.
+ *
+ * @description Specifies different modes for how the
+ * camera is controlled through pointer, keyboard or
+ * other modes of input.
+ *
+ * @enum {number}
+ * @readonly
+ */
+var CameraControls;
+(function (CameraControls) {
+    /**
+     * Control the camera with custom logic by
+     * attaching a custom camera controls
+     * instance to the {@link Viewer}.
+     */
+    CameraControls[CameraControls["Custom"] = 0] = "Custom";
+    /**
+     * Control the camera from a birds perspective
+     * to get an overview.
+     */
+    CameraControls[CameraControls["Earth"] = 1] = "Earth";
+    /**
+     * Control the camera in a first person view
+     * from the street level perspective.
+     */
+    CameraControls[CameraControls["Street"] = 2] = "Street";
+})(CameraControls || (CameraControls = {}));
+
+/**
+ * Enumeration for render mode
+ * @enum {number}
+ * @readonly
+ * @description Modes for specifying how rendering is done
+ * in the viewer. All modes preserves the original aspect
+ * ratio of the images.
+ */
+var RenderMode;
+(function (RenderMode) {
+    /**
+     * Displays all content within the viewer.
+     *
+     * @description Black bars shown on both
+     * sides of the content. Bars are shown
+     * either below and above or to the left
+     * and right of the content depending on
+     * the aspect ratio relation between the
+     * image and the viewer.
+     */
+    RenderMode[RenderMode["Letterbox"] = 0] = "Letterbox";
+    /**
+     * Fills the viewer by cropping content.
+     *
+     * @description Cropping is done either
+     * in horizontal or vertical direction
+     * depending on the aspect ratio relation
+     * between the image and the viewer.
+     */
+    RenderMode[RenderMode["Fill"] = 1] = "Fill";
+})(RenderMode || (RenderMode = {}));
+
+var RenderPass;
+(function (RenderPass) {
+    /**
+     * Occurs after the background render pass.
+     */
+    RenderPass[RenderPass["Opaque"] = 0] = "Opaque";
+})(RenderPass || (RenderPass = {}));
+
+class ComponentController {
+    constructor(container, navigator, observer, key, options, componentService) {
+        this._container = container;
+        this._observer = observer;
+        this._navigator = navigator;
+        this._options = options != null ? options : {};
+        this._key = key;
+        this._navigable = key == null;
+        this._componentService = !!componentService ?
+            componentService :
+            new ComponentService(this._container, this._navigator);
+        this._coverComponent = this._componentService.getCover();
+        this._initializeComponents();
+        if (key) {
+            this._initilizeCoverComponent();
+            this._subscribeCoverComponent();
+        }
+        else {
+            this._navigator.movedToId$.pipe(first((k) => {
+                return k != null;
+            }))
+                .subscribe((k) => {
+                this._key = k;
+                this._componentService.deactivateCover();
+                this._coverComponent.configure({
+                    id: this._key,
+                    state: CoverState.Hidden,
+                });
+                this._subscribeCoverComponent();
+                this._navigator.stateService.start();
+                this._navigator.cacheService.start();
+                this._navigator.panService.start();
+                this._observer.startEmit();
+            });
+        }
+    }
+    get navigable() {
+        return this._navigable;
+    }
+    get(name) {
+        return this._componentService.get(name);
+    }
+    activate(name) {
+        this._componentService.activate(name);
+    }
+    activateCover() {
+        this._coverComponent.configure({ state: CoverState.Visible });
+    }
+    deactivate(name) {
+        this._componentService.deactivate(name);
+    }
+    deactivateCover() {
+        this._coverComponent.configure({ state: CoverState.Loading });
+    }
+    remove() {
+        this._componentService.remove();
+        if (this._configurationSubscription != null) {
+            this._configurationSubscription.unsubscribe();
+        }
+    }
+    _initializeComponents() {
+        var _a, _b;
+        const options = this._options;
+        this._uFalse((_a = options.fallback) === null || _a === void 0 ? void 0 : _a.image, "imagefallback");
+        this._uFalse((_b = options.fallback) === null || _b === void 0 ? void 0 : _b.navigation, "navigationfallback");
+        this._uFalse(options.marker, "marker");
+        this._uFalse(options.popup, "popup");
+        this._uFalse(options.slider, "slider");
+        this._uFalse(options.spatial, "spatial");
+        this._uFalse(options.tag, "tag");
+        this._uTrue(options.attribution, "attribution");
+        this._uTrue(options.bearing, "bearing");
+        this._uTrue(options.cache, "cache");
+        this._uTrue(options.direction, "direction");
+        this._uTrue(options.image, "image");
+        this._uTrue(options.keyboard, "keyboard");
+        this._uTrue(options.pointer, "pointer");
+        this._uTrue(options.sequence, "sequence");
+        this._uTrue(options.zoom, "zoom");
+    }
+    _initilizeCoverComponent() {
+        let options = this._options;
+        this._coverComponent.configure({ id: this._key });
+        if (options.cover === undefined || options.cover) {
+            this.activateCover();
+        }
+        else {
+            this.deactivateCover();
+        }
+    }
+    _setNavigable(navigable) {
+        if (this._navigable === navigable) {
+            return;
+        }
+        this._navigable = navigable;
+        this._observer.navigable$.next(navigable);
+    }
+    _subscribeCoverComponent() {
+        this._configurationSubscription =
+            this._coverComponent.configuration$.pipe(distinctUntilChanged(undefined, (c) => {
+                return c.state;
+            }))
+                .subscribe((conf) => {
+                if (conf.state === CoverState.Loading) {
+                    this._navigator.stateService.currentId$.pipe(first(), switchMap((key) => {
+                        const keyChanged = key == null || key !== conf.id;
+                        if (keyChanged) {
+                            this._setNavigable(false);
+                        }
+                        return keyChanged ?
+                            this._navigator.moveTo$(conf.id) :
+                            this._navigator.stateService.currentImage$.pipe(first());
+                    }))
+                        .subscribe(() => {
+                        this._navigator.stateService.start();
+                        this._navigator.cacheService.start();
+                        this._navigator.panService.start();
+                        this._observer.startEmit();
+                        this._coverComponent.configure({ state: CoverState.Hidden });
+                        this._componentService.deactivateCover();
+                        this._setNavigable(true);
+                    }, (error) => {
+                        console.error("Failed to deactivate cover.", error);
+                        this._coverComponent.configure({ state: CoverState.Visible });
+                    });
+                }
+                else if (conf.state === CoverState.Visible) {
+                    this._observer.stopEmit();
+                    this._navigator.stateService.stop();
+                    this._navigator.cacheService.stop();
+                    this._navigator.playService.stop();
+                    this._navigator.panService.stop();
+                    this._componentService.activateCover();
+                    this._setNavigable(conf.id == null);
+                }
+            });
+    }
+    _uFalse(option, name) {
+        if (option === undefined) {
+            this._componentService.deactivate(name);
+            return;
+        }
+        if (typeof option === "boolean") {
+            if (option) {
+                this._componentService.activate(name);
+            }
+            else {
+                this._componentService.deactivate(name);
+            }
+            return;
+        }
+        this._componentService.configure(name, option);
+        this._componentService.activate(name);
+    }
+    _uTrue(option, name) {
+        if (option === undefined) {
+            this._componentService.activate(name);
+            return;
+        }
+        if (typeof option === "boolean") {
+            if (option) {
+                this._componentService.activate(name);
+            }
+            else {
+                this._componentService.deactivate(name);
+            }
+            return;
+        }
+        this._componentService.configure(name, option);
+        this._componentService.activate(name);
+    }
+}
+
+class DOMRenderer {
+    constructor(element, renderService, currentFrame$) {
+        this._adaptiveOperation$ = new Subject();
+        this._render$ = new Subject();
+        this._renderAdaptive$ = new Subject();
+        this._subscriptions = new SubscriptionHolder();
+        this._renderService = renderService;
+        this._currentFrame$ = currentFrame$;
+        const subs = this._subscriptions;
+        const rootNode = virtualDom.create(virtualDom.h("div.mapillary-dom-renderer", []));
+        element.appendChild(rootNode);
+        this._offset$ = this._adaptiveOperation$.pipe(scan((adaptive, operation) => {
+            return operation(adaptive);
+        }, {
+            elementHeight: element.offsetHeight,
+            elementWidth: element.offsetWidth,
+            imageAspect: 0,
+            renderMode: RenderMode.Fill,
+        }), filter((adaptive) => {
+            return adaptive.imageAspect > 0 && adaptive.elementWidth > 0 && adaptive.elementHeight > 0;
+        }), map((adaptive) => {
+            const elementAspect = adaptive.elementWidth / adaptive.elementHeight;
+            const ratio = adaptive.imageAspect / elementAspect;
+            let verticalOffset = 0;
+            let horizontalOffset = 0;
+            if (adaptive.renderMode === RenderMode.Letterbox) {
+                if (adaptive.imageAspect > elementAspect) {
+                    verticalOffset = adaptive.elementHeight * (1 - 1 / ratio) / 2;
+                }
+                else {
+                    horizontalOffset = adaptive.elementWidth * (1 - ratio) / 2;
+                }
+            }
+            else {
+                if (adaptive.imageAspect > elementAspect) {
+                    horizontalOffset = -adaptive.elementWidth * (ratio - 1) / 2;
+                }
+                else {
+                    verticalOffset = -adaptive.elementHeight * (1 / ratio - 1) / 2;
+                }
+            }
+            return {
+                bottom: verticalOffset,
+                left: horizontalOffset,
+                right: horizontalOffset,
+                top: verticalOffset,
+            };
+        }));
+        const imageAspectSubscription = this._currentFrame$.pipe(filter((frame) => {
+            return frame.state.currentImage != null;
+        }), distinctUntilChanged((k1, k2) => {
+            return k1 === k2;
+        }, (frame) => {
+            return frame.state.currentImage.id;
+        }), map((frame) => {
+            return frame.state.currentTransform.basicAspect;
+        }), map((aspect) => {
+            return (adaptive) => {
+                adaptive.imageAspect = aspect;
+                return adaptive;
+            };
+        }))
+            .subscribe(this._adaptiveOperation$);
+        const renderAdaptiveSubscription = combineLatest(this._renderAdaptive$.pipe(scan((vNodeHashes, vNodeHash) => {
+            if (vNodeHash.vNode == null) {
+                delete vNodeHashes[vNodeHash.name];
+            }
+            else {
+                vNodeHashes[vNodeHash.name] = vNodeHash.vNode;
+            }
+            return vNodeHashes;
+        }, {})), this._offset$).pipe(map((vo) => {
+            const vNodes = [];
+            const hashes = vo[0];
+            for (const name in hashes) {
+                if (!hashes.hasOwnProperty(name)) {
+                    continue;
+                }
+                vNodes.push(hashes[name]);
+            }
+            const offset = vo[1];
+            const properties = {
+                style: {
+                    bottom: offset.bottom + "px",
+                    left: offset.left + "px",
+                    "pointer-events": "none",
+                    position: "absolute",
+                    right: offset.right + "px",
+                    top: offset.top + "px",
+                },
+            };
+            return {
+                name: "mapillary-dom-adaptive-renderer",
+                vNode: virtualDom.h("div.mapillary-dom-adaptive-renderer", properties, vNodes),
+            };
+        }))
+            .subscribe(this._render$);
+        this._vNode$ = this._render$.pipe(scan((vNodeHashes, vNodeHash) => {
+            if (vNodeHash.vNode == null) {
+                delete vNodeHashes[vNodeHash.name];
+            }
+            else {
+                vNodeHashes[vNodeHash.name] = vNodeHash.vNode;
+            }
+            return vNodeHashes;
+        }, {}), map((hashes) => {
+            const vNodes = [];
+            for (const name in hashes) {
+                if (!hashes.hasOwnProperty(name)) {
+                    continue;
+                }
+                vNodes.push(hashes[name]);
+            }
+            return virtualDom.h("div.mapillary-dom-renderer", vNodes);
+        }));
+        this._vPatch$ = this._vNode$.pipe(scan((nodePatch, vNode) => {
+            nodePatch.vpatch = virtualDom.diff(nodePatch.vNode, vNode);
+            nodePatch.vNode = vNode;
+            return nodePatch;
+        }, { vNode: virtualDom.h("div.mapillary-dom-renderer", []), vpatch: null }), pluck("vpatch"));
+        this._element$ = this._vPatch$.pipe(scan((oldElement, vPatch) => {
+            return virtualDom.patch(oldElement, vPatch);
+        }, rootNode), publishReplay(1), refCount());
+        subs.push(imageAspectSubscription);
+        subs.push(renderAdaptiveSubscription);
+        subs.push(this._element$.subscribe(() => { }));
+        subs.push(this._renderService.size$.pipe(map((size) => {
+            return (adaptive) => {
+                adaptive.elementWidth = size.width;
+                adaptive.elementHeight = size.height;
+                return adaptive;
+            };
+        }))
+            .subscribe(this._adaptiveOperation$));
+        subs.push(this._renderService.renderMode$.pipe(map((renderMode) => {
+            return (adaptive) => {
+                adaptive.renderMode = renderMode;
+                return adaptive;
+            };
+        }))
+            .subscribe(this._adaptiveOperation$));
+    }
+    get element$() {
+        return this._element$;
+    }
+    get render$() {
+        return this._render$;
+    }
+    get renderAdaptive$() {
+        return this._renderAdaptive$;
+    }
+    clear(name) {
+        this._renderAdaptive$.next({ name: name, vNode: null });
+        this._render$.next({ name: name, vNode: null });
+    }
+    remove() {
+        this._subscriptions.unsubscribe();
+    }
+}
+
+class GLRenderer {
+    constructor(canvas, canvasContainer, renderService) {
+        this._renderFrame$ = new Subject();
+        this._renderCameraOperation$ = new Subject();
+        this._render$ = new Subject();
+        this._clear$ = new Subject();
+        this._renderOperation$ = new Subject();
+        this._rendererOperation$ = new Subject();
+        this._eraserOperation$ = new Subject();
+        this._triggerOperation$ = new Subject();
+        this._subscriptions = new SubscriptionHolder();
+        this._opaqueRender$ = new Subject();
+        this._renderService = renderService;
+        const subs = this._subscriptions;
+        this._renderer$ = this._rendererOperation$.pipe(scan((renderer, operation) => {
+            return operation(renderer);
+        }, { needsRender: false, renderer: null }), filter((renderer) => {
+            return !!renderer.renderer;
+        }));
+        this._renderCollection$ = this._renderOperation$.pipe(scan((hashes, operation) => {
+            return operation(hashes);
+        }, {}), share());
+        this._renderCamera$ = this._renderCameraOperation$.pipe(scan((rc, operation) => {
+            return operation(rc);
+        }, { frameId: -1, needsRender: false, perspective: null }));
+        this._eraser$ = this._eraserOperation$.pipe(startWith((eraser) => {
+            return eraser;
+        }), scan((eraser, operation) => {
+            return operation(eraser);
+        }, { needsRender: false }));
+        const trigger$ = this._triggerOperation$.pipe(startWith((trigger) => {
+            return trigger;
+        }), scan((trigger, operation) => {
+            return operation(trigger);
+        }, { needsRender: false }));
+        const clearColor = new Color(0x0F0F0F);
+        const renderSubscription = combineLatest(this._renderer$, this._renderCollection$, this._renderCamera$, this._eraser$, trigger$).pipe(map(([renderer, hashes, rc, eraser, trigger]) => {
+            const renders = Object.keys(hashes)
+                .map((key) => {
+                return hashes[key];
+            });
+            return { camera: rc, eraser: eraser, trigger: trigger, renderer: renderer, renders: renders };
+        }), filter((co) => {
+            let needsRender = co.renderer.needsRender ||
+                co.camera.needsRender ||
+                co.eraser.needsRender ||
+                co.trigger.needsRender;
+            const frameId = co.camera.frameId;
+            for (const render of co.renders) {
+                if (render.frameId !== frameId) {
+                    return false;
+                }
+                needsRender = needsRender || render.needsRender;
+            }
+            return needsRender;
+        }), distinctUntilChanged((n1, n2) => {
+            return n1 === n2;
+        }, (co) => {
+            return co.eraser.needsRender ||
+                co.trigger.needsRender ? -co.camera.frameId : co.camera.frameId;
+        }))
+            .subscribe((co) => {
+            co.renderer.needsRender = false;
+            co.camera.needsRender = false;
+            co.eraser.needsRender = false;
+            co.trigger.needsRender = false;
+            const perspectiveCamera = co.camera.perspective;
+            const backgroundRenders = [];
+            const opaqueRenders = [];
+            for (const render of co.renders) {
+                if (render.pass === RenderPass$1.Background) {
+                    backgroundRenders.push(render.render);
+                }
+                else if (render.pass === RenderPass$1.Opaque) {
+                    opaqueRenders.push(render.render);
+                }
+            }
+            const renderer = co.renderer.renderer;
+            renderer.resetState();
+            renderer.setClearColor(clearColor, 1.0);
+            renderer.clear();
+            for (const renderBackground of backgroundRenders) {
+                renderBackground(perspectiveCamera, renderer);
+            }
+            renderer.clearDepth();
+            for (const renderOpaque of opaqueRenders) {
+                renderOpaque(perspectiveCamera, renderer);
+            }
+            renderer.resetState();
+            this._opaqueRender$.next();
+        });
+        subs.push(renderSubscription);
+        subs.push(this._renderFrame$.pipe(map((rc) => {
+            return (irc) => {
+                irc.frameId = rc.frameId;
+                irc.perspective = rc.perspective;
+                if (rc.changed === true) {
+                    irc.needsRender = true;
+                }
+                return irc;
+            };
+        }))
+            .subscribe(this._renderCameraOperation$));
+        this._renderFrameSubscribe();
+        const renderHash$ = this._render$.pipe(map((hash) => {
+            return (hashes) => {
+                hashes[hash.name] = hash.renderer;
+                return hashes;
+            };
+        }));
+        const clearHash$ = this._clear$.pipe(map((name) => {
+            return (hashes) => {
+                delete hashes[name];
+                return hashes;
+            };
+        }));
+        subs.push(merge(renderHash$, clearHash$)
+            .subscribe(this._renderOperation$));
+        this._webGLRenderer$ = this._render$.pipe(first(), map(() => {
+            canvasContainer.appendChild(canvas);
+            const element = renderService.element;
+            const webGLRenderer = new WebGLRenderer({ canvas: canvas });
+            webGLRenderer.setPixelRatio(window.devicePixelRatio);
+            webGLRenderer.setSize(element.offsetWidth, element.offsetHeight);
+            webGLRenderer.autoClear = false;
+            return webGLRenderer;
+        }), publishReplay(1), refCount());
+        subs.push(this._webGLRenderer$
+            .subscribe(() => { }));
+        const createRenderer$ = this._webGLRenderer$.pipe(first(), map((webGLRenderer) => {
+            return (renderer) => {
+                renderer.needsRender = true;
+                renderer.renderer = webGLRenderer;
+                return renderer;
+            };
+        }));
+        const resizeRenderer$ = this._renderService.size$.pipe(map((size) => {
+            return (renderer) => {
+                if (renderer.renderer == null) {
+                    return renderer;
+                }
+                renderer.renderer.setSize(size.width, size.height);
+                renderer.needsRender = true;
+                return renderer;
+            };
+        }));
+        const clearRenderer$ = this._clear$.pipe(map(() => {
+            return (renderer) => {
+                if (renderer.renderer == null) {
+                    return renderer;
+                }
+                renderer.needsRender = true;
+                return renderer;
+            };
+        }));
+        subs.push(merge(createRenderer$, resizeRenderer$, clearRenderer$)
+            .subscribe(this._rendererOperation$));
+        const renderCollectionEmpty$ = this._renderCollection$.pipe(filter((hashes) => {
+            return Object.keys(hashes).length === 0;
+        }), share());
+        subs.push(renderCollectionEmpty$
+            .subscribe(() => {
+            if (this._renderFrameSubscription == null) {
+                return;
+            }
+            this._renderFrameSubscription.unsubscribe();
+            this._renderFrameSubscription = null;
+            this._renderFrameSubscribe();
+        }));
+        subs.push(renderCollectionEmpty$.pipe(map(() => {
+            return (eraser) => {
+                eraser.needsRender = true;
+                return eraser;
+            };
+        }))
+            .subscribe(this._eraserOperation$));
+    }
+    get render$() {
+        return this._render$;
+    }
+    get opaqueRender$() {
+        return this._opaqueRender$;
+    }
+    get webGLRenderer$() {
+        return this._webGLRenderer$;
+    }
+    clear(name) {
+        this._clear$.next(name);
+    }
+    remove() {
+        this._rendererOperation$.next((renderer) => {
+            if (renderer.renderer != null) {
+                const extension = renderer.renderer
+                    .getContext()
+                    .getExtension('WEBGL_lose_context');
+                if (!!extension) {
+                    extension.loseContext();
+                }
+                renderer.renderer = null;
+            }
+            return renderer;
+        });
+        if (this._renderFrameSubscription != null) {
+            this._renderFrameSubscription.unsubscribe();
+        }
+        this._subscriptions.unsubscribe();
+    }
+    triggerRerender() {
+        this._renderService.renderCameraFrame$
+            .pipe(skip(1), first())
+            .subscribe(() => {
+            this._triggerOperation$.next((trigger) => {
+                trigger.needsRender = true;
+                return trigger;
+            });
+        });
+    }
+    _renderFrameSubscribe() {
+        this._render$.pipe(first(), map(() => {
+            return (irc) => {
+                irc.needsRender = true;
+                return irc;
+            };
+        }))
+            .subscribe((operation) => {
+            this._renderCameraOperation$.next(operation);
+        });
+        this._renderFrameSubscription = this._render$.pipe(first(), mergeMap(() => {
+            return this._renderService.renderCameraFrame$;
+        }))
+            .subscribe(this._renderFrame$);
+    }
+}
+
+class RenderCamera {
+    constructor(elementWidth, elementHeight, renderMode) {
+        this._spatial = new Spatial();
+        this._viewportCoords = new ViewportCoords();
+        this._size = { width: elementWidth, height: elementHeight };
+        this._initialFov = 60;
+        this._alpha = -1;
+        this._renderMode = renderMode;
+        this._zoom = 0;
+        this._frameId = -1;
+        this._changed = false;
+        this._changedForFrame = -1;
+        this._currentImageId = null;
+        this._previousImageId = null;
+        this._currentSpherical = false;
+        this._previousSpherical = false;
+        this._state = null;
+        this._currentProjectedPoints = [];
+        this._previousProjectedPoints = [];
+        this._currentFov = this._initialFov;
+        this._previousFov = this._initialFov;
+        this._camera = new Camera();
+        this._perspective = new PerspectiveCamera(this._initialFov, this._computeAspect(elementWidth, elementHeight), 0.16, 10000);
+        this._perspective.position.copy(this._camera.position);
+        this._perspective.up.copy(this._camera.up);
+        this._perspective.lookAt(this._camera.lookat);
+        this._perspective.updateMatrixWorld(true);
+        this._perspective.matrixAutoUpdate = false;
+        this._rotation = { phi: 0, theta: 0 };
+    }
+    get alpha() {
+        return this._alpha;
+    }
+    get camera() {
+        return this._camera;
+    }
+    get changed() {
+        return this._frameId === this._changedForFrame;
+    }
+    get frameId() {
+        return this._frameId;
+    }
+    get perspective() {
+        return this._perspective;
+    }
+    get renderMode() {
+        return this._renderMode;
+    }
+    get rotation() {
+        return this._rotation;
+    }
+    get zoom() {
+        return this._zoom;
+    }
+    get size() {
+        return this._size;
+    }
+    getTilt() {
+        return 90 - this._spatial.radToDeg(this._rotation.theta);
+    }
+    fovToZoom(fov) {
+        fov = Math.min(90, Math.max(0, fov));
+        const currentFov = this._computeCurrentFov(0);
+        const actualFov = this._alpha === 1 ?
+            currentFov :
+            this._interpolateFov(currentFov, this._computePreviousFov(0), this._alpha);
+        const y0 = Math.tan(actualFov / 2 * Math.PI / 180);
+        const y1 = Math.tan(fov / 2 * Math.PI / 180);
+        const zoom = Math.log(y0 / y1) / Math.log(2);
+        return zoom;
+    }
+    setFrame(frame) {
+        const state = frame.state;
+        if (state.state !== this._state) {
+            this._state = state.state;
+            if (this._state !== State.Custom) {
+                this.setRenderMode(this._renderMode);
+                this.setSize(this._size);
+            }
+            this._changed = true;
+        }
+        const currentImageId = state.currentImage.id;
+        const previousImageId = !!state.previousImage ? state.previousImage.id : null;
+        if (currentImageId !== this._currentImageId) {
+            this._currentImageId = currentImageId;
+            this._currentSpherical = isSpherical(state.currentTransform.cameraType);
+            this._currentProjectedPoints = this._computeProjectedPoints(state.currentTransform);
+            this._changed = true;
+        }
+        if (previousImageId !== this._previousImageId) {
+            this._previousImageId = previousImageId;
+            this._previousSpherical =
+                isSpherical(state.previousTransform.cameraType);
+            this._previousProjectedPoints = this._computeProjectedPoints(state.previousTransform);
+            this._changed = true;
+        }
+        const zoom = state.zoom;
+        if (zoom !== this._zoom) {
+            this._zoom = zoom;
+            this._changed = true;
+        }
+        if (this._changed) {
+            this._currentFov = this._computeCurrentFov(this.zoom);
+            this._previousFov = this._computePreviousFov(this._zoom);
+        }
+        const alpha = state.alpha;
+        if (this._changed || alpha !== this._alpha) {
+            this._alpha = alpha;
+            switch (this._state) {
+                case State.Earth:
+                    this._perspective.fov = 60;
+                    this._changed = true;
+                    break;
+                case State.Custom:
+                    break;
+                default:
+                    this._perspective.fov =
+                        this._interpolateFov(this._currentFov, this._previousFov, this._alpha);
+                    this._changed = true;
+                    break;
+            }
+            if (this._state !== State.Custom) {
+                this._perspective.updateProjectionMatrix();
+            }
+        }
+        const camera = state.camera;
+        if (this._camera.diff(camera) > 1e-9) {
+            this._camera.copy(camera);
+            this._rotation = this._computeRotation(camera);
+            this._perspective.up.copy(camera.up);
+            this._perspective.position.copy(camera.position);
+            // Workaround for shaking camera
+            this._perspective.matrixAutoUpdate = true;
+            this._perspective.lookAt(camera.lookat);
+            this._perspective.matrixAutoUpdate = false;
+            this._perspective.updateMatrix();
+            this._perspective.updateMatrixWorld(false);
+            this._changed = true;
+        }
+        this._setFrameId(frame.id);
+    }
+    setProjectionMatrix(matrix) {
+        this._perspective.projectionMatrix.fromArray(matrix);
+        this._perspective.projectionMatrixInverse
+            .copy(this._perspective.projectionMatrix)
+            .invert();
+        this._changed = true;
+    }
+    setRenderMode(renderMode) {
+        this._renderMode = renderMode;
+        if (this._state === State.Custom) {
+            return;
+        }
+        this._perspective.fov = this._computeFov();
+        this._perspective.updateProjectionMatrix();
+        this._changed = true;
+    }
+    setSize(size) {
+        this._size = size;
+        if (this._state === State.Custom) {
+            return;
+        }
+        this._perspective.aspect = this._computeAspect(size.width, size.height);
+        this._perspective.fov = this._computeFov();
+        this._perspective.updateProjectionMatrix();
+        this._changed = true;
+    }
+    _computeAspect(elementWidth, elementHeight) {
+        return elementWidth === 0 ? 0 : elementWidth / elementHeight;
+    }
+    _computeCurrentFov(zoom) {
+        if (this._perspective.aspect === 0) {
+            return 0;
+        }
+        if (!this._currentImageId) {
+            return this._initialFov;
+        }
+        return this._currentSpherical ?
+            this._yToFov(1, zoom) :
+            this._computeVerticalFov(this._currentProjectedPoints, this._renderMode, zoom, this.perspective.aspect);
+    }
+    _computeFov() {
+        this._currentFov = this._computeCurrentFov(this._zoom);
+        this._previousFov = this._computePreviousFov(this._zoom);
+        return this._interpolateFov(this._currentFov, this._previousFov, this._alpha);
+    }
+    _computePreviousFov(zoom) {
+        if (this._perspective.aspect === 0) {
+            return 0;
+        }
+        if (!this._currentImageId) {
+            return this._initialFov;
+        }
+        return !this._previousImageId ?
+            this._currentFov :
+            this._previousSpherical ?
+                this._yToFov(1, zoom) :
+                this._computeVerticalFov(this._previousProjectedPoints, this._renderMode, zoom, this.perspective.aspect);
+    }
+    _computeProjectedPoints(transform) {
+        const vertices = [[0.5, 0], [1, 0]];
+        const directions = [[0.5, 0], [0, 0.5]];
+        const pointsPerLine = 100;
+        return computeProjectedPoints(transform, vertices, directions, pointsPerLine, this._viewportCoords);
+    }
+    _computeRequiredVerticalFov(projectedPoint, zoom, aspect) {
+        const maxY = Math.max(projectedPoint[0] / aspect, projectedPoint[1]);
+        return this._yToFov(maxY, zoom);
+    }
+    _computeRotation(camera) {
+        let direction = camera.lookat.clone().sub(camera.position);
+        let up = camera.up.clone();
+        let phi = this._spatial.azimuthal(direction.toArray(), up.toArray());
+        let theta = Math.PI / 2 - this._spatial.angleToPlane(direction.toArray(), [0, 0, 1]);
+        return { phi: phi, theta: theta };
+    }
+    _computeVerticalFov(projectedPoints, renderMode, zoom, aspect) {
+        const fovs = projectedPoints
+            .map((projectedPoint) => {
+            return this._computeRequiredVerticalFov(projectedPoint, zoom, aspect);
+        });
+        const fov = renderMode === RenderMode.Fill ?
+            Math.min(...fovs) * 0.995 :
+            Math.max(...fovs);
+        return fov;
+    }
+    _yToFov(y, zoom) {
+        return 2 * Math.atan(y / Math.pow(2, zoom)) * 180 / Math.PI;
+    }
+    _interpolateFov(v1, v2, alpha) {
+        return alpha * v1 + (1 - alpha) * v2;
+    }
+    _setFrameId(frameId) {
+        this._frameId = frameId;
+        if (this._changed) {
+            this._changed = false;
+            this._changedForFrame = frameId;
+        }
+    }
+}
+
+class RenderService {
+    constructor(element, currentFrame$, renderMode, renderCamera) {
+        this._subscriptions = new SubscriptionHolder();
+        this._element = element;
+        this._currentFrame$ = currentFrame$;
+        this._spatial = new Spatial();
+        renderMode = renderMode != null ? renderMode : RenderMode.Fill;
+        this._resize$ = new Subject();
+        this._projectionMatrix$ = new Subject();
+        this._renderCameraOperation$ =
+            new Subject();
+        this._size$ =
+            new BehaviorSubject({
+                height: this._element.offsetHeight,
+                width: this._element.offsetWidth,
+            });
+        const subs = this._subscriptions;
+        subs.push(this._resize$.pipe(map(() => {
+            return {
+                height: this._element.offsetHeight,
+                width: this._element.offsetWidth,
+            };
+        }))
+            .subscribe(this._size$));
+        this._renderMode$ = new BehaviorSubject(renderMode);
+        this._renderCameraHolder$ = this._renderCameraOperation$.pipe(startWith((rc) => {
+            return rc;
+        }), scan((rc, operation) => {
+            return operation(rc);
+        }, renderCamera !== null && renderCamera !== void 0 ? renderCamera : new RenderCamera(this._element.offsetWidth, this._element.offsetHeight, renderMode)), publishReplay(1), refCount());
+        this._renderCameraFrame$ = this._currentFrame$.pipe(withLatestFrom(this._renderCameraHolder$), tap(([frame, rc]) => {
+            rc.setFrame(frame);
+        }), map((args) => {
+            return args[1];
+        }), publishReplay(1), refCount());
+        this._renderCamera$ = this._renderCameraFrame$.pipe(filter((rc) => {
+            return rc.changed;
+        }), publishReplay(1), refCount());
+        this._bearing$ = this._renderCamera$.pipe(map((rc) => {
+            let bearing = this._spatial.radToDeg(this._spatial.azimuthalToBearing(rc.rotation.phi));
+            return this._spatial.wrap(bearing, 0, 360);
+        }), publishReplay(1), refCount());
+        subs.push(this._size$.pipe(skip(1), map((size) => {
+            return (rc) => {
+                rc.setSize(size);
+                return rc;
+            };
+        }))
+            .subscribe(this._renderCameraOperation$));
+        subs.push(this._renderMode$.pipe(skip(1), map((rm) => {
+            return (rc) => {
+                rc.setRenderMode(rm);
+                return rc;
+            };
+        }))
+            .subscribe(this._renderCameraOperation$));
+        subs.push(this._projectionMatrix$.pipe(map((projectionMatrix) => {
+            return (rc) => {
+                rc.setProjectionMatrix(projectionMatrix);
+                return rc;
+            };
+        }))
+            .subscribe(this._renderCameraOperation$));
+        subs.push(this._bearing$.subscribe(() => { }));
+        subs.push(this._renderCameraHolder$.subscribe(() => { }));
+        subs.push(this._size$.subscribe(() => { }));
+        subs.push(this._renderMode$.subscribe(() => { }));
+        subs.push(this._renderCamera$.subscribe(() => { }));
+        subs.push(this._renderCameraFrame$.subscribe(() => { }));
+    }
+    get bearing$() {
+        return this._bearing$;
+    }
+    get element() {
+        return this._element;
+    }
+    get projectionMatrix$() {
+        return this._projectionMatrix$;
+    }
+    get renderCamera$() {
+        return this._renderCamera$;
+    }
+    get renderCameraFrame$() {
+        return this._renderCameraFrame$;
+    }
+    get renderMode$() {
+        return this._renderMode$;
+    }
+    get resize$() {
+        return this._resize$;
+    }
+    get size$() {
+        return this._size$;
+    }
+    dispose() {
+        this._subscriptions.unsubscribe();
+    }
+}
+
+class KeyboardService {
+    constructor(canvasContainer) {
+        this._keyDown$ = fromEvent(canvasContainer, "keydown");
+        this._keyUp$ = fromEvent(canvasContainer, "keyup");
+    }
+    get keyDown$() {
+        return this._keyDown$;
+    }
+    get keyUp$() {
+        return this._keyUp$;
+    }
+}
+
+// MouseEvent.button
+const LEFT_BUTTON = 0;
+const RIGHT_BUTTON = 2;
+// MouseEvent.buttons
+const BUTTONS_MAP = {
+    [LEFT_BUTTON]: 1,
+    [RIGHT_BUTTON]: 2
+};
+class MouseService {
+    constructor(container, canvasContainer, domContainer, doc) {
+        this._subscriptions = new SubscriptionHolder();
+        const subs = this._subscriptions;
+        this._activeSubject$ = new BehaviorSubject(false);
+        this._active$ = this._activeSubject$
+            .pipe(distinctUntilChanged(), publishReplay(1), refCount());
+        this._claimMouse$ = new Subject();
+        this._claimWheel$ = new Subject();
+        this._deferPixelClaims$ = new Subject();
+        this._deferPixels$ = this._deferPixelClaims$
+            .pipe(scan((claims, claim) => {
+            if (claim.deferPixels == null) {
+                delete claims[claim.name];
+            }
+            else {
+                claims[claim.name] = claim.deferPixels;
+            }
+            return claims;
+        }, {}), map((claims) => {
+            let deferPixelMax = -1;
+            for (const key in claims) {
+                if (!claims.hasOwnProperty(key)) {
+                    continue;
+                }
+                const deferPixels = claims[key];
+                if (deferPixels > deferPixelMax) {
+                    deferPixelMax = deferPixels;
+                }
+            }
+            return deferPixelMax;
+        }), startWith(-1), publishReplay(1), refCount());
+        subs.push(this._deferPixels$.subscribe(() => { }));
+        this._documentMouseMove$ =
+            fromEvent(doc, "pointermove")
+                .pipe(filter(this._isMousePen));
+        this._documentMouseUp$ =
+            fromEvent(doc, "pointerup")
+                .pipe(filter(this._isMousePen));
+        this._mouseDown$ =
+            fromEvent(canvasContainer, "pointerdown")
+                .pipe(filter(this._isMousePen));
+        this._mouseEnter$ =
+            fromEvent(canvasContainer, "pointerenter")
+                .pipe(filter(this._isMousePen));
+        this._mouseLeave$ =
+            fromEvent(canvasContainer, "pointerleave")
+                .pipe(filter(this._isMousePen));
+        this._mouseMove$ =
+            fromEvent(canvasContainer, "pointermove")
+                .pipe(filter(this._isMousePen));
+        this._mouseUp$ =
+            fromEvent(canvasContainer, "pointerup")
+                .pipe(filter(this._isMousePen));
+        this._mouseOut$ =
+            fromEvent(canvasContainer, "pointerout")
+                .pipe(filter(this._isMousePen));
+        this._mouseOver$ =
+            fromEvent(canvasContainer, "pointerover")
+                .pipe(filter(this._isMousePen));
+        this._domMouseDown$ =
+            fromEvent(domContainer, "pointerdown")
+                .pipe(filter(this._isMousePen));
+        this._domMouseMove$ =
+            fromEvent(domContainer, "pointermove")
+                .pipe(filter(this._isMousePen));
+        this._click$ =
+            fromEvent(canvasContainer, "click");
+        this._contextMenu$ =
+            fromEvent(canvasContainer, "contextmenu");
+        this._windowBlur$ =
+            fromEvent(window, "blur");
+        this._dblClick$ = merge(fromEvent(container, "click"), fromEvent(canvasContainer, "dblclick"))
+            .pipe(bufferCount(3, 1), filter((events) => {
+            const event1 = events[0];
+            const event2 = events[1];
+            const event3 = events[2];
+            return event1.type === "click" &&
+                event2.type === "click" &&
+                event3.type === "dblclick" &&
+                event1.target.parentNode === canvasContainer &&
+                event2.target.parentNode === canvasContainer;
+        }), map((events) => {
+            return events[2];
+        }), share());
+        subs.push(merge(this._domMouseDown$, this._domMouseMove$, this._dblClick$, this._contextMenu$)
+            .subscribe((event) => {
+            event.preventDefault();
+        }));
+        this._mouseWheel$ = merge(fromEvent(canvasContainer, "wheel"), fromEvent(domContainer, "wheel"))
+            .pipe(share());
+        this._consistentContextMenu$ =
+            merge(this._mouseDown$, this._mouseMove$, this._mouseOut$, this._mouseUp$, this._contextMenu$)
+                .pipe(bufferCount(3, 1), filter((events) => {
+                // fire context menu on mouse up both on mac and windows
+                return events[0].type === "pointerdown" &&
+                    events[1].type === "contextmenu" &&
+                    events[2].type === "pointerup";
+            }), map((events) => {
+                return events[1];
+            }), share());
+        const dragStop$ = merge(this._windowBlur$, this._documentMouseMove$
+            .pipe(filter((e) => {
+            return this._buttonReleased(e, LEFT_BUTTON);
+        })), this._documentMouseUp$
+            .pipe(filter((e) => {
+            return this._mouseButton(e) === LEFT_BUTTON;
+        })))
+            .pipe(share());
+        const mouseDragInitiate$ = this._createMouseDragInitiate$(LEFT_BUTTON, this._mouseDown$, dragStop$, true)
+            .pipe(share());
+        this._mouseDragStart$ =
+            this._createMouseDragStart$(mouseDragInitiate$)
+                .pipe(share());
+        this._mouseDrag$ =
+            this._createMouseDrag$(mouseDragInitiate$, dragStop$)
+                .pipe(share());
+        this._mouseDragEnd$ =
+            this._createMouseDragEnd$(this._mouseDragStart$, dragStop$)
+                .pipe(share());
+        const domMouseDragInitiate$ = this._createMouseDragInitiate$(LEFT_BUTTON, this._domMouseDown$, dragStop$, false)
+            .pipe(share());
+        this._domMouseDragStart$ =
+            this._createMouseDragStart$(domMouseDragInitiate$)
+                .pipe(share());
+        this._domMouseDrag$ =
+            this._createMouseDrag$(domMouseDragInitiate$, dragStop$)
+                .pipe(share());
+        this._domMouseDragEnd$ =
+            this._createMouseDragEnd$(this._domMouseDragStart$, dragStop$)
+                .pipe(share());
+        const rightDragStop$ = merge(this._windowBlur$, this._documentMouseMove$.pipe(filter((e) => {
+            return this._buttonReleased(e, RIGHT_BUTTON);
+        })), this._documentMouseUp$.pipe(filter((e) => {
+            return this._mouseButton(e) === RIGHT_BUTTON;
+        })))
+            .pipe(share());
+        const mouseRightDragInitiate$ = this._createMouseDragInitiate$(RIGHT_BUTTON, this._mouseDown$, rightDragStop$, true)
+            .pipe(share());
+        this._mouseRightDragStart$ =
+            this._createMouseDragStart$(mouseRightDragInitiate$)
+                .pipe(share());
+        this._mouseRightDrag$ =
+            this._createMouseDrag$(mouseRightDragInitiate$, rightDragStop$)
+                .pipe(share());
+        this._mouseRightDragEnd$ =
+            this._createMouseDragEnd$(this._mouseRightDragStart$, rightDragStop$)
+                .pipe(share());
+        this._proximateClick$ = this._mouseDown$
+            .pipe(switchMap((mouseDown) => {
+            return this._click$.pipe(takeUntil(this._createDeferredMouseMove$(mouseDown, this._documentMouseMove$)), take(1));
+        }), share());
+        this._staticClick$ = this._mouseDown$
+            .pipe(switchMap(() => {
+            return this._click$.pipe(takeUntil(this._documentMouseMove$), take(1));
+        }), share());
+        subs.push(this._mouseDragStart$.subscribe());
+        subs.push(this._mouseDrag$.subscribe());
+        subs.push(this._mouseDragEnd$.subscribe());
+        subs.push(this._domMouseDragStart$.subscribe());
+        subs.push(this._domMouseDrag$.subscribe());
+        subs.push(this._domMouseDragEnd$.subscribe());
+        subs.push(this._mouseRightDragStart$.subscribe());
+        subs.push(this._mouseRightDrag$.subscribe());
+        subs.push(this._mouseRightDragEnd$.subscribe());
+        subs.push(this._staticClick$.subscribe());
+        this._mouseOwner$ = this._createOwner$(this._claimMouse$)
+            .pipe(publishReplay(1), refCount());
+        this._wheelOwner$ = this._createOwner$(this._claimWheel$)
+            .pipe(publishReplay(1), refCount());
+        subs.push(this._mouseOwner$.subscribe(() => { }));
+        subs.push(this._wheelOwner$.subscribe(() => { }));
+    }
+    get active$() {
+        return this._active$;
+    }
+    get activate$() {
+        return this._activeSubject$;
+    }
+    get documentMouseMove$() {
+        return this._documentMouseMove$;
+    }
+    get documentMouseUp$() {
+        return this._documentMouseUp$;
+    }
+    get domMouseDragStart$() {
+        return this._domMouseDragStart$;
+    }
+    get domMouseDrag$() {
+        return this._domMouseDrag$;
+    }
+    get domMouseDragEnd$() {
+        return this._domMouseDragEnd$;
+    }
+    get domMouseDown$() {
+        return this._domMouseDown$;
+    }
+    get domMouseMove$() {
+        return this._domMouseMove$;
+    }
+    get mouseOwner$() {
+        return this._mouseOwner$;
+    }
+    get mouseDown$() {
+        return this._mouseDown$;
+    }
+    get mouseEnter$() {
+        return this._mouseEnter$;
+    }
+    get mouseMove$() {
+        return this._mouseMove$;
+    }
+    get mouseLeave$() {
+        return this._mouseLeave$;
+    }
+    get mouseOut$() {
+        return this._mouseOut$;
+    }
+    get mouseOver$() {
+        return this._mouseOver$;
+    }
+    get mouseUp$() {
+        return this._mouseUp$;
+    }
+    get click$() {
+        return this._click$;
+    }
+    get dblClick$() {
+        return this._dblClick$;
+    }
+    get contextMenu$() {
+        return this._consistentContextMenu$;
+    }
+    get mouseWheel$() {
+        return this._mouseWheel$;
+    }
+    get mouseDragStart$() {
+        return this._mouseDragStart$;
+    }
+    get mouseDrag$() {
+        return this._mouseDrag$;
+    }
+    get mouseDragEnd$() {
+        return this._mouseDragEnd$;
+    }
+    get mouseRightDragStart$() {
+        return this._mouseRightDragStart$;
+    }
+    get mouseRightDrag$() {
+        return this._mouseRightDrag$;
+    }
+    get mouseRightDragEnd$() {
+        return this._mouseRightDragEnd$;
+    }
+    get proximateClick$() {
+        return this._proximateClick$;
+    }
+    get staticClick$() {
+        return this._staticClick$;
+    }
+    get windowBlur$() {
+        return this._windowBlur$;
+    }
+    dispose() {
+        this._subscriptions.unsubscribe();
+    }
+    claimMouse(name, zindex) {
+        this._claimMouse$.next({ name: name, zindex: zindex });
+    }
+    unclaimMouse(name) {
+        this._claimMouse$.next({ name: name, zindex: null });
+    }
+    deferPixels(name, deferPixels) {
+        this._deferPixelClaims$.next({ name: name, deferPixels: deferPixels });
+    }
+    undeferPixels(name) {
+        this._deferPixelClaims$.next({ name: name, deferPixels: null });
+    }
+    claimWheel(name, zindex) {
+        this._claimWheel$.next({ name: name, zindex: zindex });
+    }
+    unclaimWheel(name) {
+        this._claimWheel$.next({ name: name, zindex: null });
+    }
+    filtered$(name, observable$) {
+        return this._filtered(name, observable$, this._mouseOwner$);
+    }
+    filteredWheel$(name, observable$) {
+        return this._filtered(name, observable$, this._wheelOwner$);
+    }
+    _createDeferredMouseMove$(origin, mouseMove$) {
+        return mouseMove$.pipe(map((mouseMove) => {
+            const deltaX = mouseMove.clientX - origin.clientX;
+            const deltaY = mouseMove.clientY - origin.clientY;
+            return [mouseMove, Math.sqrt(deltaX * deltaX + deltaY * deltaY)];
+        }), withLatestFrom(this._deferPixels$), filter(([[, delta], deferPixels]) => {
+            return delta > deferPixels;
+        }), map(([[mouseMove]]) => {
+            return mouseMove;
+        }));
+    }
+    _createMouseDrag$(mouseDragStartInitiate$, stop$) {
+        return mouseDragStartInitiate$.pipe(map(([, mouseMove]) => {
+            return mouseMove;
+        }), switchMap((mouseMove) => {
+            return concat(of(mouseMove), this._documentMouseMove$).pipe(takeUntil(stop$));
+        }));
+    }
+    _createMouseDragEnd$(mouseDragStart$, stop$) {
+        return mouseDragStart$.pipe(switchMap(() => {
+            return stop$.pipe(first());
+        }));
+    }
+    _createMouseDragStart$(mouseDragStartInitiate$) {
+        return mouseDragStartInitiate$.pipe(map(([mouseDown]) => {
+            return mouseDown;
+        }));
+    }
+    _createMouseDragInitiate$(button, mouseDown$, stop$, defer) {
+        return mouseDown$.pipe(filter((mouseDown) => {
+            return this._mouseButton(mouseDown) === button;
+        }), switchMap((mouseDown) => {
+            return combineLatest(of(mouseDown), defer ?
+                this._createDeferredMouseMove$(mouseDown, this._documentMouseMove$) :
+                this._documentMouseMove$).pipe(takeUntil(stop$), take(1));
+        }));
+    }
+    _createOwner$(claim$) {
+        return claim$.pipe(scan((claims, claim) => {
+            if (claim.zindex == null) {
+                delete claims[claim.name];
+            }
+            else {
+                claims[claim.name] = claim.zindex;
+            }
+            return claims;
+        }, {}), map((claims) => {
+            let owner = null;
+            let zIndexMax = -1;
+            for (const name in claims) {
+                if (!claims.hasOwnProperty(name)) {
+                    continue;
+                }
+                if (claims[name] > zIndexMax) {
+                    zIndexMax = claims[name];
+                    owner = name;
+                }
+            }
+            return owner;
+        }), startWith(null));
+    }
+    _filtered(name, observable$, owner$) {
+        return observable$.pipe(withLatestFrom(owner$), filter(([, owner]) => {
+            return owner === name;
+        }), map(([item]) => {
+            return item;
+        }));
+    }
+    _mouseButton(event) {
+        const upOrDown = event.type === "pointerdown" || event.type === "pointerup";
+        const InstallTrigger = window.InstallTrigger;
+        if (upOrDown &&
+            typeof InstallTrigger !== 'undefined' &&
+            event.button === RIGHT_BUTTON && event.ctrlKey &&
+            window.navigator.platform.toUpperCase().indexOf('MAC') >= 0) {
+            // Fix for the fact that Firefox (detected by InstallTrigger)
+            // on Mac determines e.button = 2 when using Control + left click.
+            return LEFT_BUTTON;
+        }
+        return event.button;
+    }
+    _buttonReleased(event, button) {
+        // Right button `mouseup` is not fired in
+        // Chrome on Mac outside the window or iframe. If
+        // the button is no longer pressed during move
+        // it may have been released and drag stop
+        // should be emitted.
+        const flag = BUTTONS_MAP[button];
+        return event.buttons === undefined || (event.buttons & flag) !== flag;
+    }
+    _isMousePen(event) {
+        const type = event.pointerType;
+        return type === "mouse" || type === "pen";
+    }
+}
+
+class SpriteAtlas {
+    set json(value) {
+        this._json = value;
+    }
+    set image(value) {
+        this._image = value;
+        this._texture = new Texture(this._image);
+        this._texture.minFilter = NearestFilter;
+    }
+    get loaded() {
+        return !!(this._image && this._json);
+    }
+    getGLSprite(name) {
+        if (!this.loaded) {
+            throw new Error("Sprites cannot be retrieved before the atlas is loaded.");
+        }
+        let definition = this._json[name];
+        if (!definition) {
+            console.warn("Sprite with key" + name + "does not exist in sprite definition.");
+            return new Object3D();
+        }
+        let texture = this._texture.clone();
+        texture.needsUpdate = true;
+        let width = this._image.width;
+        let height = this._image.height;
+        texture.offset.x = definition.x / width;
+        texture.offset.y = (height - definition.y - definition.height) / height;
+        texture.repeat.x = definition.width / width;
+        texture.repeat.y = definition.height / height;
+        let material = new SpriteMaterial({ map: texture });
+        return new Sprite(material);
+    }
+    getDOMSprite(name, float) {
+        if (!this.loaded) {
+            throw new Error("Sprites cannot be retrieved before the atlas is loaded.");
+        }
+        if (float == null) {
+            float = Alignment.Center;
+        }
+        let definition = this._json[name];
+        if (!definition) {
+            console.warn("Sprite with key" + name + "does not exist in sprite definition.");
+            return virtualDom.h("div", {}, []);
+        }
+        let clipTop = definition.y;
+        let clipRigth = definition.x + definition.width;
+        let clipBottom = definition.y + definition.height;
+        let clipLeft = definition.x;
+        let left = -definition.x;
+        let top = -definition.y;
+        let height = this._image.height;
+        let width = this._image.width;
+        switch (float) {
+            case Alignment.Bottom:
+            case Alignment.Center:
+            case Alignment.Top:
+                left -= definition.width / 2;
+                break;
+            case Alignment.BottomLeft:
+            case Alignment.Left:
+            case Alignment.TopLeft:
+                left -= definition.width;
+                break;
+            case Alignment.BottomRight:
+            case Alignment.Right:
+            case Alignment.TopRight:
+        }
+        switch (float) {
+            case Alignment.Center:
+            case Alignment.Left:
+            case Alignment.Right:
+                top -= definition.height / 2;
+                break;
+            case Alignment.Top:
+            case Alignment.TopLeft:
+            case Alignment.TopRight:
+                top -= definition.height;
+                break;
+            case Alignment.Bottom:
+            case Alignment.BottomLeft:
+            case Alignment.BottomRight:
+        }
+        let pixelRatioInverse = 1 / definition.pixelRatio;
+        clipTop *= pixelRatioInverse;
+        clipRigth *= pixelRatioInverse;
+        clipBottom *= pixelRatioInverse;
+        clipLeft *= pixelRatioInverse;
+        left *= pixelRatioInverse;
+        top *= pixelRatioInverse;
+        height *= pixelRatioInverse;
+        width *= pixelRatioInverse;
+        let properties = {
+            src: this._image.src,
+            style: {
+                clip: `rect(${clipTop}px, ${clipRigth}px, ${clipBottom}px, ${clipLeft}px)`,
+                height: `${height}px`,
+                left: `${left}px`,
+                position: "absolute",
+                top: `${top}px`,
+                width: `${width}px`,
+            },
+        };
+        return virtualDom.h("img", properties, []);
+    }
+}
+class SpriteService {
+    constructor(sprite) {
+        this._retina = window.devicePixelRatio > 1;
+        this._spriteAtlasOperation$ = new Subject();
+        this._spriteAtlas$ = this._spriteAtlasOperation$.pipe(startWith((atlas) => {
+            return atlas;
+        }), scan((atlas, operation) => {
+            return operation(atlas);
+        }, new SpriteAtlas()), publishReplay(1), refCount());
+        this._atlasSubscription = this._spriteAtlas$
+            .subscribe(() => { });
+        if (sprite == null) {
+            return;
+        }
+        let format = this._retina ? "@2x" : "";
+        let imageXmlHTTP = new XMLHttpRequest();
+        imageXmlHTTP.open("GET", sprite + format + ".png", true);
+        imageXmlHTTP.responseType = "arraybuffer";
+        imageXmlHTTP.onload = () => {
+            let image = new Image();
+            image.onload = () => {
+                this._spriteAtlasOperation$.next((atlas) => {
+                    atlas.image = image;
+                    return atlas;
+                });
+            };
+            let blob = new Blob([imageXmlHTTP.response]);
+            image.src = window.URL.createObjectURL(blob);
+        };
+        imageXmlHTTP.onerror = (error) => {
+            console.error(new Error(`Failed to fetch sprite sheet (${sprite}${format}.png)`));
+        };
+        imageXmlHTTP.send();
+        let jsonXmlHTTP = new XMLHttpRequest();
+        jsonXmlHTTP.open("GET", sprite + format + ".json", true);
+        jsonXmlHTTP.responseType = "text";
+        jsonXmlHTTP.onload = () => {
+            let json = JSON.parse(jsonXmlHTTP.response);
+            this._spriteAtlasOperation$.next((atlas) => {
+                atlas.json = json;
+                return atlas;
+            });
+        };
+        jsonXmlHTTP.onerror = (error) => {
+            console.error(new Error(`Failed to fetch sheet (${sprite}${format}.json)`));
+        };
+        jsonXmlHTTP.send();
+    }
+    get spriteAtlas$() {
+        return this._spriteAtlas$;
+    }
+    dispose() {
+        this._atlasSubscription.unsubscribe();
+    }
+}
+
+class TouchService {
+    constructor(canvasContainer, domContainer) {
+        this._subscriptions = new SubscriptionHolder();
+        const subs = this._subscriptions;
+        this._activeSubject$ = new BehaviorSubject(false);
+        this._active$ = this._activeSubject$.pipe(distinctUntilChanged(), publishReplay(1), refCount());
+        subs.push(fromEvent(domContainer, "touchmove")
+            .subscribe((event) => {
+            event.preventDefault();
+        }));
+        this._touchStart$ = fromEvent(canvasContainer, "touchstart");
+        this._touchMove$ = fromEvent(canvasContainer, "touchmove");
+        this._touchEnd$ = fromEvent(canvasContainer, "touchend");
+        this._touchCancel$ = fromEvent(canvasContainer, "touchcancel");
+        const tapStart$ = this._touchStart$.pipe(filter((te) => {
+            return te.touches.length === 1 && te.targetTouches.length === 1;
+        }), share());
+        this._doubleTap$ = tapStart$.pipe(bufferWhen(() => {
+            return tapStart$.pipe(first(), switchMap(() => {
+                return merge(timer(300), tapStart$).pipe(take(1));
+            }));
+        }), filter((events) => {
+            return events.length === 2;
+        }), map((events) => {
+            return events[events.length - 1];
+        }), share());
+        subs.push(this._doubleTap$
+            .subscribe((event) => {
+            event.preventDefault();
+        }));
+        this._singleTouchMove$ = this._touchMove$.pipe(filter((te) => {
+            return te.touches.length === 1 && te.targetTouches.length === 1;
+        }), share());
+        let singleTouchStart$ = merge(this._touchStart$, this._touchEnd$, this._touchCancel$).pipe(filter((te) => {
+            return te.touches.length === 1 && te.targetTouches.length === 1;
+        }));
+        let multipleTouchStart$ = merge(this._touchStart$, this._touchEnd$, this._touchCancel$).pipe(filter((te) => {
+            return te.touches.length >= 1;
+        }));
+        let touchStop$ = merge(this._touchEnd$, this._touchCancel$).pipe(filter((te) => {
+            return te.touches.length === 0;
+        }));
+        this._singleTouchDragStart$ = singleTouchStart$.pipe(mergeMap(() => {
+            return this._singleTouchMove$.pipe(takeUntil(merge(touchStop$, multipleTouchStart$)), take(1));
+        }));
+        this._singleTouchDragEnd$ = singleTouchStart$.pipe(mergeMap(() => {
+            return merge(touchStop$, multipleTouchStart$).pipe(first());
+        }));
+        this._singleTouchDrag$ = singleTouchStart$.pipe(switchMap(() => {
+            return this._singleTouchMove$.pipe(skip(1), takeUntil(merge(multipleTouchStart$, touchStop$)));
+        }));
+        let touchesChanged$ = merge(this._touchStart$, this._touchEnd$, this._touchCancel$);
+        this._pinchStart$ = touchesChanged$.pipe(filter((te) => {
+            return te.touches.length === 2 && te.targetTouches.length === 2;
+        }));
+        this._pinchEnd$ = touchesChanged$.pipe(filter((te) => {
+            return te.touches.length !== 2 || te.targetTouches.length !== 2;
+        }));
+        this._pinchOperation$ = new Subject();
+        this._pinch$ = this._pinchOperation$.pipe(scan((pinch, operation) => {
+            return operation(pinch);
+        }, {
+            changeX: 0,
+            changeY: 0,
+            clientX: 0,
+            clientY: 0,
+            distance: 0,
+            distanceChange: 0,
+            distanceX: 0,
+            distanceY: 0,
+            originalEvent: null,
+            pageX: 0,
+            pageY: 0,
+            screenX: 0,
+            screenY: 0,
+            touch1: null,
+            touch2: null,
+        }));
+        const pinchSubscription = this._touchMove$.pipe(filter((te) => {
+            return te.touches.length === 2 && te.targetTouches.length === 2;
+        }), map((te) => {
+            return (previous) => {
+                let touch1 = te.touches[0];
+                let touch2 = te.touches[1];
+                let minX = Math.min(touch1.clientX, touch2.clientX);
+                let maxX = Math.max(touch1.clientX, touch2.clientX);
+                let minY = Math.min(touch1.clientY, touch2.clientY);
+                let maxY = Math.max(touch1.clientY, touch2.clientY);
+                let centerClientX = minX + (maxX - minX) / 2;
+                let centerClientY = minY + (maxY - minY) / 2;
+                let centerPageX = centerClientX + touch1.pageX - touch1.clientX;
+                let centerPageY = centerClientY + touch1.pageY - touch1.clientY;
+                let centerScreenX = centerClientX + touch1.screenX - touch1.clientX;
+                let centerScreenY = centerClientY + touch1.screenY - touch1.clientY;
+                let distanceX = Math.abs(touch1.clientX - touch2.clientX);
+                let distanceY = Math.abs(touch1.clientY - touch2.clientY);
+                let distance = Math.sqrt(distanceX * distanceX + distanceY * distanceY);
+                let distanceChange = distance - previous.distance;
+                let changeX = distanceX - previous.distanceX;
+                let changeY = distanceY - previous.distanceY;
+                let current = {
+                    changeX: changeX,
+                    changeY: changeY,
+                    clientX: centerClientX,
+                    clientY: centerClientY,
+                    distance: distance,
+                    distanceChange: distanceChange,
+                    distanceX: distanceX,
+                    distanceY: distanceY,
+                    originalEvent: te,
+                    pageX: centerPageX,
+                    pageY: centerPageY,
+                    screenX: centerScreenX,
+                    screenY: centerScreenY,
+                    touch1: touch1,
+                    touch2: touch2,
+                };
+                return current;
+            };
+        }))
+            .subscribe(this._pinchOperation$);
+        subs.push(pinchSubscription);
+        this._pinchChange$ = this._pinchStart$.pipe(switchMap(() => {
+            return this._pinch$.pipe(skip(1), takeUntil(this._pinchEnd$));
+        }));
+    }
+    get active$() {
+        return this._active$;
+    }
+    get activate$() {
+        return this._activeSubject$;
+    }
+    get doubleTap$() {
+        return this._doubleTap$;
+    }
+    get touchStart$() {
+        return this._touchStart$;
+    }
+    get touchMove$() {
+        return this._touchMove$;
+    }
+    get touchEnd$() {
+        return this._touchEnd$;
+    }
+    get touchCancel$() {
+        return this._touchCancel$;
+    }
+    get singleTouchDragStart$() {
+        return this._singleTouchDragStart$;
+    }
+    get singleTouchDrag$() {
+        return this._singleTouchDrag$;
+    }
+    get singleTouchDragEnd$() {
+        return this._singleTouchDragEnd$;
+    }
+    get pinch$() {
+        return this._pinchChange$;
+    }
+    get pinchStart$() {
+        return this._pinchStart$;
+    }
+    get pinchEnd$() {
+        return this._pinchEnd$;
+    }
+    dispose() {
+        this._subscriptions.unsubscribe();
+    }
+}
+
+class ConfigurationService {
+    constructor(options) {
+        var _a, _b, _c, _d;
+        const host = (_b = (_a = options === null || options === void 0 ? void 0 : options.url) === null || _a === void 0 ? void 0 : _a.exploreHost) !== null && _b !== void 0 ? _b : "www.mapillary.com";
+        const scheme = (_d = (_c = options === null || options === void 0 ? void 0 : options.url) === null || _c === void 0 ? void 0 : _c.scheme) !== null && _d !== void 0 ? _d : "https";
+        const exploreUrl = `${scheme}://${host}`;
+        this._exploreUrl$ = of(exploreUrl);
+        const imageTiling = (options === null || options === void 0 ? void 0 : options.imageTiling) === false ? false : true;
+        this._imageTiling$ = of(imageTiling);
+    }
+    get exploreUrl$() {
+        return this._exploreUrl$;
+    }
+    get imageTiling$() {
+        return this._imageTiling$;
+    }
+}
+
+class Container {
+    constructor(options, stateService, dom) {
+        var _a;
+        this._onWindowResize = () => {
+            if (this._trackResize) {
+                this.renderService.resize$.next();
+            }
+        };
+        this._dom = dom !== null && dom !== void 0 ? dom : new DOM();
+        if (typeof options.container === "string") {
+            this._container = this._dom.document
+                .getElementById(options.container);
+            if (!this._container) {
+                throw new Error(`Container "${options.container}" not found.`);
+            }
+        }
+        else if (options.container instanceof HTMLElement) {
+            this._container = options.container;
+        }
+        else {
+            throw new Error(`Invalid type: "container" must be ` +
+                `a String or HTMLElement.`);
+        }
+        this._trackResize =
+            options.trackResize === false ?
+                false : true;
+        this.id = (_a = this._container.id) !== null && _a !== void 0 ? _a : "mapillary-fallback-container-id";
+        this._container.classList
+            .add("mapillary-viewer");
+        this._canvasContainer = this._dom
+            .createElement("div", "mapillary-interactive", this._container);
+        this._canvas = this._dom
+            .createElement("canvas", "mapillary-canvas");
+        this._canvas.style.position = "absolute";
+        this._canvas.setAttribute("tabindex", "0");
+        // Add DOM container after canvas container to
+        // render DOM elements on top of the interactive
+        // canvas.
+        this._domContainer = this._dom
+            .createElement("div", "mapillary-dom", this._container);
+        this.configurationService = new ConfigurationService(options);
+        this.renderService =
+            new RenderService(this._container, stateService.currentState$, options.renderMode);
+        this.glRenderer =
+            new GLRenderer(this._canvas, this._canvasContainer, this.renderService);
+        this.domRenderer =
+            new DOMRenderer(this._domContainer, this.renderService, stateService.currentState$);
+        this.keyboardService =
+            new KeyboardService(this._canvasContainer);
+        this.mouseService =
+            new MouseService(this._container, this._canvasContainer, this._domContainer, document);
+        this.touchService =
+            new TouchService(this._canvasContainer, this._domContainer);
+        this.spriteService =
+            new SpriteService(options.sprite);
+        window.addEventListener('resize', this._onWindowResize, false);
+    }
+    get canvas() {
+        return !!this._canvas.parentNode ?
+            this._canvas : null;
+    }
+    get canvasContainer() {
+        return this._canvasContainer;
+    }
+    get container() {
+        return this._container;
+    }
+    get domContainer() {
+        return this._domContainer;
+    }
+    remove() {
+        window.removeEventListener('resize', this._onWindowResize, false);
+        this.spriteService.dispose();
+        this.touchService.dispose();
+        this.mouseService.dispose();
+        this.glRenderer.remove();
+        this.domRenderer.remove();
+        this.renderService.dispose();
+        this._removeNode(this._canvasContainer);
+        this._removeNode(this._domContainer);
+        this._container.classList
+            .remove("mapillary-viewer");
+    }
+    _removeNode(node) {
+        if (node.parentNode) {
+            node.parentNode.removeChild(node);
+        }
+    }
+}
+
+class CacheService {
+    constructor(_graphService, _stateService, _api) {
+        this._graphService = _graphService;
+        this._stateService = _stateService;
+        this._api = _api;
+        this._subscriptions = new SubscriptionHolder();
+        this._started = false;
+        this._cellDepth = 1;
+    }
+    get started() {
+        return this._started;
+    }
+    configure(configuration) {
+        if (!configuration) {
+            this._cellDepth = 1;
+            return;
+        }
+        this._cellDepth = Math.max(1, Math.min(3, configuration.cellDepth));
+    }
+    start() {
+        if (this._started) {
+            return;
+        }
+        const subs = this._subscriptions;
+        subs.push(this._stateService.currentState$
+            .pipe(distinctUntilChanged(undefined, (frame) => {
+            return frame.state.currentImage.id;
+        }), map((frame) => {
+            const state = frame.state;
+            const trajectory = state.trajectory;
+            const trajectoryKeys = trajectory
+                .map((n) => {
+                return n.id;
+            });
+            const sequenceKey = trajectory[trajectory.length - 1].sequenceId;
+            return [
+                trajectoryKeys,
+                state.currentImage.originalLngLat,
+                sequenceKey,
+            ];
+        }), bufferCount(1, 5), withLatestFrom(this._graphService.graphMode$), switchMap(([keepBuffer, graphMode]) => {
+            const keepKeys = keepBuffer[0][0];
+            const lngLat = keepBuffer[0][1];
+            const geometry = this._api.data.geometry;
+            const cellId = geometry.lngLatToCellId(lngLat);
+            const keepCellIds = connectedComponent(cellId, this._cellDepth, geometry);
+            const keepSequenceKey = graphMode === GraphMode.Sequence ?
+                keepBuffer[0][2] :
+                undefined;
+            return this._graphService
+                .uncache$(keepKeys, keepCellIds, keepSequenceKey);
+        }))
+            .subscribe(() => { }));
+        subs.push(this._graphService.graphMode$
+            .pipe(skip(1), withLatestFrom(this._stateService.currentState$), switchMap(([mode, frame]) => {
+            return mode === GraphMode.Sequence ?
+                this._keyToEdges(frame.state.currentImage.id, (image) => {
+                    return image.sequenceEdges$;
+                }) :
+                from(frame.state.trajectory
+                    .map((image) => {
+                    return image.id;
+                })
+                    .slice(frame.state.currentIndex)).pipe(mergeMap((key) => {
+                    return this._keyToEdges(key, (image) => {
+                        return image.spatialEdges$;
+                    });
+                }, 6));
+        }))
+            .subscribe(() => { }));
+        subs.push(this._graphService.dataAdded$
+            .pipe(withLatestFrom(this._stateService.currentId$), switchMap(([_, imageId]) => {
+            return this._graphService.cacheImage$(imageId);
+        }))
+            .subscribe(() => { }));
+        this._started = true;
+    }
+    stop() {
+        if (!this._started) {
+            return;
+        }
+        this._subscriptions.unsubscribe();
+        this._started = false;
+    }
+    _keyToEdges(key, imageToEdgeMap) {
+        return this._graphService.cacheImage$(key).pipe(switchMap(imageToEdgeMap), first((status) => {
+            return status.cached;
+        }), timeout(15000), catchError((error) => {
+            console.error(`Failed to cache edges (${key}).`, error);
+            return empty();
+        }));
+    }
+}
+
+class LoadingService {
+    constructor() {
+        this._loadersSubject$ = new Subject();
+        this._loaders$ = this._loadersSubject$.pipe(scan((loaders, loader) => {
+            if (loader.task !== undefined) {
+                loaders[loader.task] = loader.loading;
+            }
+            return loaders;
+        }, {}), startWith({}), publishReplay(1), refCount());
+    }
+    get loading$() {
+        return this._loaders$.pipe(map((loaders) => {
+            for (const key in loaders) {
+                if (!loaders.hasOwnProperty(key)) {
+                    continue;
+                }
+                if (loaders[key]) {
+                    return true;
+                }
+            }
+            return false;
+        }), debounceTime(100), distinctUntilChanged());
+    }
+    taskLoading$(task) {
+        return this._loaders$.pipe(map((loaders) => {
+            return !!loaders[task];
+        }), debounceTime(100), distinctUntilChanged());
+    }
+    startLoading(task) {
+        this._loadersSubject$.next({ loading: true, task: task });
+    }
+    stopLoading(task) {
+        this._loadersSubject$.next({ loading: false, task: task });
+    }
+}
+
+var PanMode;
+(function (PanMode) {
+    PanMode[PanMode["Disabled"] = 0] = "Disabled";
+    PanMode[PanMode["Enabled"] = 1] = "Enabled";
+    PanMode[PanMode["Started"] = 2] = "Started";
+})(PanMode || (PanMode = {}));
+class PanService {
+    constructor(graphService, stateService, enabled, graphCalculator, spatial, viewportCoords) {
+        this._subscriptions = new SubscriptionHolder();
+        this._graphService = graphService;
+        this._stateService = stateService;
+        this._graphCalculator = graphCalculator !== null && graphCalculator !== void 0 ? graphCalculator : new GraphCalculator();
+        this._spatial = spatial !== null && spatial !== void 0 ? spatial : new Spatial();
+        this._viewportCoords = viewportCoords !== null && viewportCoords !== void 0 ? viewportCoords : new ViewportCoords();
+        this._mode = enabled !== false ?
+            PanMode.Enabled : PanMode.Disabled;
+        this._panImagesSubject$ = new Subject();
+        this._panImages$ = this._panImagesSubject$.pipe(startWith([]), publishReplay(1), refCount());
+        this._subscriptions.push(this._panImages$.subscribe());
+    }
+    get panImages$() {
+        return this._panImages$;
+    }
+    dispose() {
+        this.stop();
+        if (this._panImagesSubscription != null) {
+            this._panImagesSubscription.unsubscribe();
+        }
+        this._subscriptions.unsubscribe();
+    }
+    enable() {
+        if (this._mode !== PanMode.Disabled) {
+            return;
+        }
+        this._mode = PanMode.Enabled;
+        this.start();
+    }
+    disable() {
+        if (this._mode === PanMode.Disabled) {
+            return;
+        }
+        this.stop();
+        this._mode = PanMode.Disabled;
+    }
+    start() {
+        if (this._mode !== PanMode.Enabled) {
+            return;
+        }
+        const panImages$ = this._stateService.currentImage$.pipe(switchMap((current) => {
+            if (!current.merged || isSpherical(current.cameraType)) {
+                return of([]);
+            }
+            const current$ = of(current);
+            const bounds = this._graphCalculator.boundingBoxCorners(current.lngLat, 20);
+            const adjacent$ = this._graphService
+                .cacheBoundingBox$(bounds[0], bounds[1]).pipe(catchError((error) => {
+                console.error(`Failed to cache periphery bounding box (${current.id})`, error);
+                return empty();
+            }), map((images) => {
+                if (isSpherical(current.cameraType)) {
+                    return [];
+                }
+                const potential = [];
+                for (const image of images) {
+                    if (image.id === current.id) {
+                        continue;
+                    }
+                    if (image.mergeId !== current.mergeId) {
+                        continue;
+                    }
+                    if (isSpherical(image.cameraType)) {
+                        continue;
+                    }
+                    if (this._distance(image, current) > 4) {
+                        continue;
+                    }
+                    potential.push(image);
+                }
+                return potential;
+            }));
+            return combineLatest(current$, adjacent$).pipe(withLatestFrom(this._stateService.reference$), map(([[cn, adjacent], reference]) => {
+                const currentDirection = this._spatial.viewingDirection(cn.rotation);
+                const currentTranslation = computeTranslation({ lat: cn.lngLat.lat, lng: cn.lngLat.lng, alt: cn.computedAltitude }, cn.rotation, reference);
+                const currentTransform = this._createTransform(cn, currentTranslation);
+                const currentAzimuthal = this._spatial.wrap(this._spatial.azimuthal(currentDirection.toArray(), currentTransform.upVector().toArray()), 0, 2 * Math.PI);
+                const currentProjectedPoints = this._computeProjectedPoints(currentTransform);
+                const currentHFov = this._computeHorizontalFov(currentProjectedPoints) / 180 * Math.PI;
+                const preferredOverlap = Math.PI / 8;
+                let left = undefined;
+                let right = undefined;
+                for (const a of adjacent) {
+                    const translation = computeTranslation({ lat: a.lngLat.lat, lng: a.lngLat.lng, alt: a.computedAltitude }, a.rotation, reference);
+                    const transform = this._createTransform(a, translation);
+                    const projectedPoints = this._computeProjectedPoints(transform);
+                    const hFov = this._computeHorizontalFov(projectedPoints) / 180 * Math.PI;
+                    const direction = this._spatial.viewingDirection(a.rotation);
+                    const azimuthal = this._spatial.wrap(this._spatial.azimuthal(direction.toArray(), transform.upVector().toArray()), 0, 2 * Math.PI);
+                    const directionChange = this._spatial.angleBetweenVector2(currentDirection.x, currentDirection.y, direction.x, direction.y);
+                    let overlap = Number.NEGATIVE_INFINITY;
+                    if (directionChange > 0) {
+                        if (currentAzimuthal > azimuthal) {
+                            overlap = currentAzimuthal - 2 * Math.PI + currentHFov / 2 - (azimuthal - hFov / 2);
+                        }
+                        else {
+                            overlap = currentAzimuthal + currentHFov / 2 - (azimuthal - hFov / 2);
+                        }
+                    }
+                    else {
+                        if (currentAzimuthal < azimuthal) {
+                            overlap = azimuthal + hFov / 2 - (currentAzimuthal + 2 * Math.PI - currentHFov / 2);
+                        }
+                        else {
+                            overlap = azimuthal + hFov / 2 - (currentAzimuthal - currentHFov / 2);
+                        }
+                    }
+                    const nonOverlap = Math.abs(hFov - overlap);
+                    const distanceCost = this._distance(a, cn);
+                    const timeCost = Math.min(this._timeDifference(a, cn), 4);
+                    const overlapCost = 20 * Math.abs(overlap - preferredOverlap);
+                    const fovCost = Math.min(5, 1 / Math.min(hFov / currentHFov, 1));
+                    const nonOverlapCost = overlap > 0 ? -2 * nonOverlap : 0;
+                    const cost = distanceCost + timeCost + overlapCost + fovCost + nonOverlapCost;
+                    if (overlap > 0 &&
+                        overlap < 0.5 * currentHFov &&
+                        overlap < 0.5 * hFov &&
+                        nonOverlap > 0.5 * currentHFov) {
+                        if (directionChange > 0) {
+                            if (!left) {
+                                left = [cost, a, transform, hFov];
+                            }
+                            else {
+                                if (cost < left[0]) {
+                                    left = [cost, a, transform, hFov];
+                                }
+                            }
+                        }
+                        else {
+                            if (!right) {
+                                right = [cost, a, transform, hFov];
+                            }
+                            else {
+                                if (cost < right[0]) {
+                                    right = [cost, a, transform, hFov];
+                                }
+                            }
+                        }
+                    }
+                }
+                const panImagess = [];
+                if (!!left) {
+                    panImagess.push([left[1], left[2], left[3]]);
+                }
+                if (!!right) {
+                    panImagess.push([right[1], right[2], right[3]]);
+                }
+                return panImagess;
+            }), startWith([]));
+        }));
+        this._panImagesSubscription = this._stateService.currentState$.pipe(map((frame) => {
+            return frame.state.imagesAhead > 0;
+        }), distinctUntilChanged(), switchMap((traversing) => {
+            return traversing ? of([]) : panImages$;
+        }))
+            .subscribe((panImages) => {
+            this._panImagesSubject$.next(panImages);
+        });
+        this._mode = PanMode.Started;
+    }
+    stop() {
+        if (this._mode !== PanMode.Started) {
+            return;
+        }
+        this._panImagesSubscription.unsubscribe();
+        this._panImagesSubject$.next([]);
+        this._mode = PanMode.Enabled;
+    }
+    _distance(image, reference) {
+        const [x, y, z] = geodeticToEnu(image.lngLat.lng, image.lngLat.lat, image.computedAltitude, reference.lngLat.lng, reference.lngLat.lat, reference.computedAltitude);
+        return Math.sqrt(x * x + y * y + z * z);
+    }
+    _timeDifference(image, reference) {
+        const milliSecond = (1000 * 60 * 60 * 24 * 30);
+        return Math.abs(image.capturedAt - reference.capturedAt) / milliSecond;
+    }
+    _createTransform(image, translation) {
+        return new Transform(image.exifOrientation, image.width, image.height, image.scale, image.rotation, translation, image.assetsCached ? image.image : undefined, undefined, image.cameraParameters, image.cameraType);
+    }
+    _computeProjectedPoints(transform) {
+        const vertices = [[1, 0]];
+        const directions = [[0, 0.5]];
+        const pointsPerLine = 20;
+        return computeProjectedPoints(transform, vertices, directions, pointsPerLine, this._viewportCoords);
+    }
+    _computeHorizontalFov(projectedPoints) {
+        const fovs = projectedPoints
+            .map((projectedPoint) => {
+            return this._coordToFov(projectedPoint[0]);
+        });
+        const fov = Math.min(...fovs);
+        return fov;
+    }
+    _coordToFov(x) {
+        return 2 * Math.atan(x) * 180 / Math.PI;
+    }
+}
+
+/**
+ * @class API
+ *
+ * @classdesc Provides methods for access to the API.
+ */
+class APIWrapper {
+    constructor(_data) {
+        this._data = _data;
+    }
+    get data() {
+        return this._data;
+    }
+    getCoreImages$(cellId) {
+        return this._wrap$(this._data.getCoreImages(cellId));
+    }
+    getImages$(imageIds) {
+        return this._wrap$(this._data.getImages(imageIds));
+    }
+    getImageTiles$(tiles) {
+        return this._wrap$(this._data.getImageTiles(tiles));
+    }
+    getSequence$(sequenceId) {
+        return this._wrap$(this._data.getSequence(sequenceId));
+    }
+    getSpatialImages$(imageIds) {
+        return this._wrap$(this._data.getSpatialImages(imageIds));
+    }
+    setAccessToken(accessToken) {
+        this._data.setAccessToken(accessToken);
+    }
+    _wrap$(promise) {
+        return Observable.create((subscriber) => {
+            promise.then((value) => {
+                subscriber.next(value);
+                subscriber.complete();
+            }, (error) => {
+                subscriber.error(error);
+            });
+        });
+    }
+}
+
+/**
+ * @class GraphService
+ *
+ * @classdesc Represents a service for graph operations.
+ */
+class GraphService {
+    /**
+     * Create a new graph service instance.
+     *
+     * @param {Graph} graph - Graph instance to be operated on.
+     */
+    constructor(graph) {
+        this._dataAdded$ = new Subject();
+        this._subscriptions = new SubscriptionHolder();
+        this._onDataAdded = (event) => {
+            this._graph$
+                .pipe(first(), mergeMap(graph => {
+                return graph.updateCells$(event.cellIds).pipe(tap(() => { graph.resetSpatialEdges(); }));
+            }))
+                .subscribe(cellId => { this._dataAdded$.next(cellId); });
+        };
+        const subs = this._subscriptions;
+        this._graph$ = concat(of(graph), graph.changed$).pipe(publishReplay(1), refCount());
+        subs.push(this._graph$.subscribe(() => { }));
+        this._graphMode = GraphMode.Spatial;
+        this._graphModeSubject$ = new Subject();
+        this._graphMode$ = this._graphModeSubject$.pipe(startWith(this._graphMode), publishReplay(1), refCount());
+        subs.push(this._graphMode$.subscribe(() => { }));
+        this._firstGraphSubjects$ = [];
+        this._initializeCacheSubscriptions = [];
+        this._sequenceSubscriptions = [];
+        this._spatialSubscriptions = [];
+        graph.api.data.on("datacreate", this._onDataAdded);
+    }
+    /**
+     * Get dataAdded$.
+     *
+     * @returns {Observable<string>} Observable emitting
+     * a cell id every time data has been added to a cell.
+     */
+    get dataAdded$() {
+        return this._dataAdded$;
+    }
+    /**
+     * Get filter observable.
+     *
+     * @desciption Emits the filter every time it has changed.
+     *
+     * @returns {Observable<FilterFunction>} Observable
+     * emitting the filter function every time it is set.
+     */
+    get filter$() {
+        return this._graph$.pipe(first(), mergeMap((graph) => {
+            return graph.filter$;
+        }));
+    }
+    /**
+     * Get graph mode observable.
+     *
+     * @description Emits the current graph mode.
+     *
+     * @returns {Observable<GraphMode>} Observable
+     * emitting the current graph mode when it changes.
+     */
+    get graphMode$() {
+        return this._graphMode$;
+    }
+    /**
+     * Cache full images in a bounding box.
+     *
+     * @description When called, the full properties of
+     * the image are retrieved. The image cache is not initialized
+     * for any new images retrieved and the image assets are not
+     * retrieved, {@link cacheImage$} needs to be called for caching
+     * assets.
+     *
+     * @param {LngLat} sw - South west corner of bounding box.
+     * @param {LngLat} ne - North east corner of bounding box.
+     * @return {Observable<Array<Image>>} Observable emitting a single item,
+     * the images of the bounding box, when they have all been retrieved.
+     * @throws {Error} Propagates any IO image caching errors to the caller.
+     */
+    cacheBoundingBox$(sw, ne) {
+        return this._graph$.pipe(first(), mergeMap((graph) => {
+            return graph.cacheBoundingBox$(sw, ne);
+        }));
+    }
+    /**
+     * Cache full images in a cell.
+     *
+     * @description When called, the full properties of
+     * the image are retrieved. The image cache is not initialized
+     * for any new images retrieved and the image assets are not
+     * retrieved, {@link cacheImage$} needs to be called for caching
+     * assets.
+     *
+     * @param {string} cellId - Id of the cell.
+     * @return {Observable<Array<Image>>} Observable emitting a single item,
+     * the images of the cell, when they have all been retrieved.
+     * @throws {Error} Propagates any IO image caching errors to the caller.
+     */
+    cacheCell$(cellId) {
+        return this._graph$.pipe(first(), mergeMap((graph) => {
+            return graph.cacheCell$(cellId);
+        }));
+    }
+    /**
+     * Cache a image in the graph and retrieve it.
+     *
+     * @description When called, the full properties of
+     * the image are retrieved and the image cache is initialized.
+     * After that the image assets are cached and the image
+     * is emitted to the observable when.
+     * In parallel to caching the image assets, the sequence and
+     * spatial edges of the image are cached. For this, the sequence
+     * of the image and the required tiles and spatial images are
+     * retrieved. The sequence and spatial edges may be set before
+     * or after the image is returned.
+     *
+     * @param {string} id - Id of the image to cache.
+     * @return {Observable<Image>} Observable emitting a single item,
+     * the image, when it has been retrieved and its assets are cached.
+     * @throws {Error} Propagates any IO image caching errors to the caller.
+     */
+    cacheImage$(id) {
+        const firstGraphSubject$ = new Subject();
+        this._firstGraphSubjects$.push(firstGraphSubject$);
+        const firstGraph$ = firstGraphSubject$.pipe(publishReplay(1), refCount());
+        const image$ = firstGraph$.pipe(map((graph) => {
+            return graph.getNode(id);
+        }), mergeMap((image) => {
+            return image.assetsCached ?
+                of(image) :
+                image.cacheAssets$();
+        }), publishReplay(1), refCount());
+        image$.subscribe(undefined, (error) => {
+            console.error(`Failed to cache image (${id}).`, error);
+        });
+        let initializeCacheSubscription;
+        initializeCacheSubscription = this._graph$.pipe(first(), mergeMap((graph) => {
+            if (graph.isCachingFull(id) || !graph.hasNode(id)) {
+                return graph.cacheFull$(id);
+            }
+            if (graph.isCachingFill(id) || !graph.getNode(id).complete) {
+                return graph.cacheFill$(id);
+            }
+            return of(graph);
+        }), tap((graph) => {
+            if (!graph.hasNode(id)) {
+                throw new GraphMapillaryError(`Failed to cache image (${id})`);
+            }
+            if (!graph.hasInitializedCache(id)) {
+                graph.initializeCache(id);
+            }
+        }), finalize(() => {
+            if (initializeCacheSubscription == null) {
+                return;
+            }
+            this._removeFromArray(initializeCacheSubscription, this._initializeCacheSubscriptions);
+            this._removeFromArray(firstGraphSubject$, this._firstGraphSubjects$);
+        }))
+            .subscribe((graph) => {
+            firstGraphSubject$.next(graph);
+            firstGraphSubject$.complete();
+        }, (error) => {
+            firstGraphSubject$.error(error);
+        });
+        if (!initializeCacheSubscription.closed) {
+            this._initializeCacheSubscriptions.push(initializeCacheSubscription);
+        }
+        const graphSequence$ = firstGraph$.pipe(catchError(() => {
+            return empty();
+        }), mergeMap((graph) => {
+            if (graph.isCachingNodeSequence(id) || !graph.hasNodeSequence(id)) {
+                return graph.cacheNodeSequence$(id);
+            }
+            return of(graph);
+        }), publishReplay(1), refCount());
+        let sequenceSubscription;
+        sequenceSubscription = graphSequence$.pipe(tap((graph) => {
+            if (!graph.getNode(id).sequenceEdges.cached) {
+                graph.cacheSequenceEdges(id);
+            }
+        }), finalize(() => {
+            if (sequenceSubscription == null) {
+                return;
+            }
+            this._removeFromArray(sequenceSubscription, this._sequenceSubscriptions);
+        }))
+            .subscribe(() => { return; }, (error) => {
+            console.error(`Failed to cache sequence edges (${id}).`, error);
+        });
+        if (!sequenceSubscription.closed) {
+            this._sequenceSubscriptions.push(sequenceSubscription);
+        }
+        if (this._graphMode === GraphMode.Spatial) {
+            let spatialSubscription;
+            spatialSubscription = firstGraph$.pipe(catchError(() => {
+                return empty();
+            }), expand((graph) => {
+                if (graph.hasTiles(id)) {
+                    return empty();
+                }
+                return from(graph.cacheTiles$(id)).pipe(mergeMap((graph$) => {
+                    return graph$.pipe(mergeMap((g) => {
+                        if (g.isCachingTiles(id)) {
+                            return empty();
+                        }
+                        return of(g);
+                    }), catchError((error) => {
+                        console.error(`Failed to cache tile data (${id}).`, error);
+                        return empty();
+                    }));
+                }));
+            }), takeLast(1), mergeMap((graph) => {
+                if (graph.hasSpatialArea(id)) {
+                    return of(graph);
+                }
+                return from(graph.cacheSpatialArea$(id)).pipe(mergeMap((graph$) => {
+                    return graph$.pipe(catchError((error) => {
+                        console.error(`Failed to cache spatial images (${id}).`, error);
+                        return empty();
+                    }));
+                }));
+            }), takeLast(1), mergeMap((graph) => {
+                return graph.hasNodeSequence(id) ?
+                    of(graph) :
+                    graph.cacheNodeSequence$(id);
+            }), tap((graph) => {
+                if (!graph.getNode(id).spatialEdges.cached) {
+                    graph.cacheSpatialEdges(id);
+                }
+            }), finalize(() => {
+                if (spatialSubscription == null) {
+                    return;
+                }
+                this._removeFromArray(spatialSubscription, this._spatialSubscriptions);
+            }))
+                .subscribe(() => { return; }, (error) => {
+                const message = `Failed to cache spatial edges (${id}).`;
+                console.error(message, error);
+            });
+            if (!spatialSubscription.closed) {
+                this._spatialSubscriptions.push(spatialSubscription);
+            }
+        }
+        return image$.pipe(first((image) => {
+            return image.assetsCached;
+        }));
+    }
+    /**
+     * Cache a sequence in the graph and retrieve it.
+     *
+     * @param {string} sequenceId - Sequence id.
+     * @returns {Observable<Sequence>} Observable emitting a single item,
+     * the sequence, when it has been retrieved and its assets are cached.
+     * @throws {Error} Propagates any IO image caching errors to the caller.
+     */
+    cacheSequence$(sequenceId) {
+        return this._graph$.pipe(first(), mergeMap((graph) => {
+            if (graph.isCachingSequence(sequenceId) || !graph.hasSequence(sequenceId)) {
+                return graph.cacheSequence$(sequenceId);
+            }
+            return of(graph);
+        }), map((graph) => {
+            return graph.getSequence(sequenceId);
+        }));
+    }
+    /**
+     * Cache a sequence and its images in the graph and retrieve the sequence.
+     *
+     * @description Caches a sequence and its assets are cached and
+     * retrieves all images belonging to the sequence. The image assets
+     * or edges will not be cached.
+     *
+     * @param {string} sequenceId - Sequence id.
+     * @param {string} referenceImageId - Id of image to use as reference
+     * for optimized caching.
+     * @returns {Observable<Sequence>} Observable emitting a single item,
+     * the sequence, when it has been retrieved, its assets are cached and
+     * all images belonging to the sequence has been retrieved.
+     * @throws {Error} Propagates any IO image caching errors to the caller.
+     */
+    cacheSequenceImages$(sequenceId, referenceImageId) {
+        return this._graph$.pipe(first(), mergeMap((graph) => {
+            if (graph.isCachingSequence(sequenceId) || !graph.hasSequence(sequenceId)) {
+                return graph.cacheSequence$(sequenceId);
+            }
+            return of(graph);
+        }), mergeMap((graph) => {
+            if (graph.isCachingSequenceNodes(sequenceId) || !graph.hasSequenceNodes(sequenceId)) {
+                return graph.cacheSequenceNodes$(sequenceId, referenceImageId);
+            }
+            return of(graph);
+        }), map((graph) => {
+            return graph.getSequence(sequenceId);
+        }));
+    }
+    /**
+     * Dispose the graph service and its children.
+     */
+    dispose() {
+        this._graph$
+            .pipe(first())
+            .subscribe((graph) => { graph.unsubscribe(); });
+        this._subscriptions.unsubscribe();
+    }
+    /**
+     * Set a spatial edge filter on the graph.
+     *
+     * @description Resets the spatial edges of all cached images.
+     *
+     * @param {FilterExpression} filter - Filter expression to be applied.
+     * @return {Observable<Graph>} Observable emitting a single item,
+     * the graph, when the spatial edges have been reset.
+     */
+    setFilter$(filter) {
+        this._resetSubscriptions(this._spatialSubscriptions);
+        return this._graph$.pipe(first(), tap((graph) => {
+            graph.resetSpatialEdges();
+            graph.setFilter(filter);
+        }), map(() => {
+            return undefined;
+        }));
+    }
+    /**
+     * Set the graph mode.
+     *
+     * @description If graph mode is set to spatial, caching
+     * is performed with emphasis on spatial edges. If graph
+     * mode is set to sequence no tile data is requested and
+     * no spatial edges are computed.
+     *
+     * When setting graph mode to sequence all spatial
+     * subscriptions are aborted.
+     *
+     * @param {GraphMode} mode - Graph mode to set.
+     */
+    setGraphMode(mode) {
+        if (this._graphMode === mode) {
+            return;
+        }
+        if (mode === GraphMode.Sequence) {
+            this._resetSubscriptions(this._spatialSubscriptions);
+        }
+        this._graphMode = mode;
+        this._graphModeSubject$.next(this._graphMode);
+    }
+    /**
+     * Reset the graph.
+     *
+     * @description Resets the graph but keeps the images of the
+     * supplied ids.
+     *
+     * @param {Array<string>} keepIds - Ids of images to keep in graph.
+     * @return {Observable<Image>} Observable emitting a single item,
+     * the graph, when it has been reset.
+     */
+    reset$(keepIds) {
+        this._abortSubjects(this._firstGraphSubjects$);
+        this._resetSubscriptions(this._initializeCacheSubscriptions);
+        this._resetSubscriptions(this._sequenceSubscriptions);
+        this._resetSubscriptions(this._spatialSubscriptions);
+        return this._graph$.pipe(first(), tap((graph) => {
+            graph.reset(keepIds);
+        }), map(() => {
+            return undefined;
+        }));
+    }
+    /**
+     * Uncache the graph.
+     *
+     * @description Uncaches the graph by removing tiles, images and
+     * sequences. Keeps the images of the supplied ids and the tiles
+     * related to those images.
+     *
+     * @param {Array<string>} keepIds - Ids of images to keep in graph.
+     * @param {Array<string>} keepCellIds - Ids of cells to keep in graph.
+     * @param {string} keepSequenceId - Optional id of sequence
+     * for which the belonging images should not be disposed or
+     * removed from the graph. These images may still be uncached if
+     * not specified in keep ids param.
+     * @return {Observable<Graph>} Observable emitting a single item,
+     * the graph, when the graph has been uncached.
+     */
+    uncache$(keepIds, keepCellIds, keepSequenceId) {
+        return this._graph$.pipe(first(), tap((graph) => {
+            graph.uncache(keepIds, keepCellIds, keepSequenceId);
+        }), map(() => {
+            return undefined;
+        }));
+    }
+    _abortSubjects(subjects) {
+        for (const subject of subjects.slice()) {
+            this._removeFromArray(subject, subjects);
+            subject.error(new Error("Cache image request was aborted."));
+        }
+    }
+    _removeFromArray(object, objects) {
+        const index = objects.indexOf(object);
+        if (index !== -1) {
+            objects.splice(index, 1);
+        }
+    }
+    _resetSubscriptions(subscriptions) {
+        for (const subscription of subscriptions.slice()) {
+            this._removeFromArray(subscription, subscriptions);
+            if (!subscription.closed) {
+                subscription.unsubscribe();
+            }
+        }
+    }
+}
+
+class FrameGenerator {
+    constructor(root) {
+        if (root.requestAnimationFrame) {
+            this._cancelAnimationFrame = root.cancelAnimationFrame.bind(root);
+            this._requestAnimationFrame = root.requestAnimationFrame.bind(root);
+        }
+        else if (root.mozRequestAnimationFrame) {
+            this._cancelAnimationFrame = root.mozCancelAnimationFrame.bind(root);
+            this._requestAnimationFrame = root.mozRequestAnimationFrame.bind(root);
+        }
+        else if (root.webkitRequestAnimationFrame) {
+            this._cancelAnimationFrame = root.webkitCancelAnimationFrame.bind(root);
+            this._requestAnimationFrame = root.webkitRequestAnimationFrame.bind(root);
+        }
+        else if (root.msRequestAnimationFrame) {
+            this._cancelAnimationFrame = root.msCancelAnimationFrame.bind(root);
+            this._requestAnimationFrame = root.msRequestAnimationFrame.bind(root);
+        }
+        else if (root.oRequestAnimationFrame) {
+            this._cancelAnimationFrame = root.oCancelAnimationFrame.bind(root);
+            this._requestAnimationFrame = root.oRequestAnimationFrame.bind(root);
+        }
+        else {
+            this._cancelAnimationFrame = root.clearTimeout.bind(root);
+            this._requestAnimationFrame = (cb) => { return root.setTimeout(cb, 1000 / 60); };
+        }
+    }
+    get cancelAnimationFrame() {
+        return this._cancelAnimationFrame;
+    }
+    get requestAnimationFrame() {
+        return this._requestAnimationFrame;
+    }
+}
+
+class CustomState extends StateBase {
+    constructor(state) {
+        super(state);
+    }
+    setViewMatrix(viewMatrix) {
+        const viewMatrixInverse = new Matrix4()
+            .fromArray(viewMatrix)
+            .invert();
+        const me = viewMatrixInverse.elements;
+        const eye = new Vector3(me[12], me[13], me[14]);
+        const forward = new Vector3(-me[8], -me[9], -me[10]);
+        const up = new Vector3(me[4], me[5], me[6]);
+        const camera = this._camera;
+        camera.position.copy(eye);
+        camera.lookat.copy(eye
+            .clone()
+            .add(forward));
+        camera.up.copy(up);
+        const focal = 0.5 / Math.tan(Math.PI / 3);
+        camera.focal = focal;
+    }
+}
+
+class EarthState extends StateBase {
+    constructor(state) {
+        super(state);
+        const eye = this._camera.position.clone();
+        const forward = this._camera.lookat
+            .clone()
+            .sub(eye)
+            .normalize();
+        const xy = Math.sqrt(forward.x * forward.x + forward.y * forward.y);
+        const angle = Math.atan2(forward.z, xy);
+        const lookat = new Vector3();
+        if (angle > -Math.PI / 45) {
+            lookat.copy(eye);
+            eye.add(new Vector3(forward.x, forward.y, 0)
+                .multiplyScalar(-50));
+            eye.z = 30;
+        }
+        else {
+            // Target a point on invented ground and keep forward direction
+            const l0 = eye.clone();
+            const n = new Vector3(0, 0, 1);
+            const p0 = new Vector3(0, 0, -2);
+            const d = new Vector3().subVectors(p0, l0).dot(n) / forward.dot(n);
+            const maxDistance = 10000;
+            const intersection = l0
+                .clone()
+                .add(forward.
+                clone()
+                .multiplyScalar(Math.min(maxDistance, d)));
+            lookat.copy(intersection);
+            const t = eye
+                .clone()
+                .sub(intersection)
+                .normalize();
+            eye.copy(intersection.add(t.multiplyScalar(Math.max(50, t.length()))));
+        }
+        this._camera.position.copy(eye);
+        this._camera.lookat.copy(lookat);
+        this._camera.up.set(0, 0, 1);
+    }
+    dolly(delta) {
+        const camera = this._camera;
+        const offset = camera.position
+            .clone()
+            .sub(camera.lookat);
+        const length = offset.length();
+        const scaled = length * Math.pow(2, -delta);
+        const clipped = Math.max(1, Math.min(scaled, 4000));
+        offset.normalize();
+        offset.multiplyScalar(clipped);
+        camera.position
+            .copy(camera.lookat)
+            .add(offset);
+    }
+    orbit(rotation) {
+        const camera = this._camera;
+        const q = new Quaternion()
+            .setFromUnitVectors(camera.up, new Vector3(0, 0, 1));
+        const qInverse = q
+            .clone()
+            .invert();
+        const offset = camera.position
+            .clone()
+            .sub(camera.lookat);
+        offset.applyQuaternion(q);
+        const length = offset.length();
+        let phi = Math.atan2(offset.y, offset.x);
+        phi += rotation.phi;
+        let theta = Math.atan2(Math.sqrt(offset.x * offset.x + offset.y * offset.y), offset.z);
+        theta += rotation.theta;
+        const threshold = Math.PI / 36;
+        theta = Math.max(threshold, Math.min(Math.PI / 2 - threshold, theta));
+        offset.x = Math.sin(theta) * Math.cos(phi);
+        offset.y = Math.sin(theta) * Math.sin(phi);
+        offset.z = Math.cos(theta);
+        offset.applyQuaternion(qInverse);
+        camera.position
+            .copy(camera.lookat)
+            .add(offset.multiplyScalar(length));
+    }
+    truck(direction) {
+        const camera = this._camera;
+        camera.position
+            .add(new Vector3().fromArray(direction));
+        camera.lookat
+            .add(new Vector3().fromArray(direction));
+    }
+    update() { }
+}
+
+class InteractiveWaitingState extends InteractiveStateBase {
+    constructor(state) {
+        super(state);
+        this._adjustCameras();
+        this._motionless = this._motionlessTransition();
+    }
+    prepend(images) {
+        super.prepend(images);
+        this._motionless = this._motionlessTransition();
+    }
+    set(images) {
+        super.set(images);
+        this._motionless = this._motionlessTransition();
+    }
+    move(delta) {
+        this._alpha = Math.max(0, Math.min(1, this._alpha + delta));
+    }
+    moveTo(position) {
+        this._alpha = Math.max(0, Math.min(1, position));
+    }
+    update(fps) {
+        this._updateRotation();
+        if (!this._rotationDelta.isZero) {
+            this._applyRotation(this._rotationDelta, this._previousCamera);
+            this._applyRotation(this._rotationDelta, this._currentCamera);
+        }
+        this._updateRotationBasic();
+        if (this._basicRotation[0] !== 0 || this._basicRotation[1] !== 0) {
+            this._applyRotationBasic(this._basicRotation);
+        }
+        let animationSpeed = this._animationSpeed * (60 / fps);
+        this._updateZoom(animationSpeed);
+        this._updateLookat(animationSpeed);
+        this._camera.lerpCameras(this._previousCamera, this._currentCamera, this.alpha);
+    }
+    _getAlpha() {
+        return this._motionless ? Math.round(this._alpha) : this._alpha;
+    }
+    _setCurrentCamera() {
+        super._setCurrentCamera();
+        this._adjustCameras();
+    }
+    _adjustCameras() {
+        if (this._previousImage == null) {
+            return;
+        }
+        if (isSpherical(this._currentImage.cameraType)) {
+            let lookat = this._camera.lookat.clone().sub(this._camera.position);
+            this._currentCamera.lookat.copy(lookat.clone().add(this._currentCamera.position));
+        }
+        if (isSpherical(this._previousImage.cameraType)) {
+            let lookat = this._currentCamera.lookat.clone().sub(this._currentCamera.position);
+            this._previousCamera.lookat.copy(lookat.clone().add(this._previousCamera.position));
+        }
+    }
+}
+
+class WaitingState extends StateBase {
+    constructor(state) {
+        super(state);
+        this._zoom = 0;
+        this._adjustCameras();
+        this._motionless = this._motionlessTransition();
+    }
+    prepend(images) {
+        super.prepend(images);
+        this._motionless = this._motionlessTransition();
+    }
+    set(images) {
+        super.set(images);
+        this._motionless = this._motionlessTransition();
+    }
+    move(delta) {
+        this._alpha = Math.max(0, Math.min(1, this._alpha + delta));
+    }
+    moveTo(position) {
+        this._alpha = Math.max(0, Math.min(1, position));
+    }
+    update(fps) {
+        this._camera.lerpCameras(this._previousCamera, this._currentCamera, this.alpha);
+    }
+    _getAlpha() {
+        return this._motionless ? Math.round(this._alpha) : this._alpha;
+    }
+    _setCurrentCamera() {
+        super._setCurrentCamera();
+        this._adjustCameras();
+    }
+    _adjustCameras() {
+        if (this._previousImage == null) {
+            return;
+        }
+        if (isSpherical(this._currentImage.cameraType)) {
+            let lookat = this._camera.lookat.clone().sub(this._camera.position);
+            this._currentCamera.lookat.copy(lookat.clone().add(this._currentCamera.position));
+        }
+        if (isSpherical(this._previousImage.cameraType)) {
+            let lookat = this._currentCamera.lookat.clone().sub(this._currentCamera.position);
+            this._previousCamera.lookat.copy(lookat.clone().add(this._previousCamera.position));
+        }
+    }
+}
+
+class StateTransitionMatrix {
+    constructor() {
+        const custom = State[State.Custom];
+        const earth = State[State.Earth];
+        const traverse = State[State.Traversing];
+        const wait = State[State.Waiting];
+        const waitInteractively = State[State.WaitingInteractively];
+        this._creators = new Map();
+        const creator = this._creators;
+        creator.set(custom, CustomState);
+        creator.set(earth, EarthState);
+        creator.set(traverse, TraversingState);
+        creator.set(wait, WaitingState);
+        creator.set(waitInteractively, InteractiveWaitingState);
+        this._transitions = new Map();
+        const transitions = this._transitions;
+        transitions.set(custom, [earth, traverse]);
+        transitions.set(earth, [custom, traverse]);
+        transitions.set(traverse, [custom, earth, wait, waitInteractively]);
+        transitions.set(wait, [traverse, waitInteractively]);
+        transitions.set(waitInteractively, [traverse, wait]);
+    }
+    getState(state) {
+        if (state instanceof CustomState) {
+            return State.Custom;
+        }
+        else if (state instanceof EarthState) {
+            return State.Earth;
+        }
+        else if (state instanceof TraversingState) {
+            return State.Traversing;
+        }
+        else if (state instanceof WaitingState) {
+            return State.Waiting;
+        }
+        else if (state instanceof InteractiveWaitingState) {
+            return State.WaitingInteractively;
+        }
+        throw new Error("Invalid state instance");
+    }
+    generate(state, options) {
+        const concreteState = this._creators.get(State[state]);
+        return new concreteState(options);
+    }
+    transition(state, to) {
+        if (!this.validate(state, to)) {
+            throw new Error("Invalid transition");
+        }
+        return this.generate(to, state);
+    }
+    validate(state, to) {
+        const source = State[this.getState(state)];
+        const target = State[to];
+        const transitions = this._transitions;
+        return transitions.has(source) &&
+            transitions.get(source).includes(target);
+    }
+}
+
+class StateContext {
+    constructor(state, transitionMode) {
+        this._transitions = new StateTransitionMatrix();
+        this._state = this._transitions.generate(state, {
+            alpha: 1,
+            camera: new Camera(),
+            currentIndex: -1,
+            reference: { alt: 0, lat: 0, lng: 0 },
+            trajectory: [],
+            transitionMode: transitionMode == null ? TransitionMode.Default : transitionMode,
+            zoom: 0,
+        });
+    }
+    get state() {
+        return this._transitions.getState(this._state);
+    }
+    get reference() {
+        return this._state.reference;
+    }
+    get alpha() {
+        return this._state.alpha;
+    }
+    get camera() {
+        return this._state.camera;
+    }
+    get zoom() {
+        return this._state.zoom;
+    }
+    get currentImage() {
+        return this._state.currentImage;
+    }
+    get previousImage() {
+        return this._state.previousImage;
+    }
+    get currentCamera() {
+        return this._state.currentCamera;
+    }
+    get currentTransform() {
+        return this._state.currentTransform;
+    }
+    get previousTransform() {
+        return this._state.previousTransform;
+    }
+    get trajectory() {
+        return this._state.trajectory;
+    }
+    get currentIndex() {
+        return this._state.currentIndex;
+    }
+    get lastImage() {
+        return this._state.trajectory[this._state.trajectory.length - 1];
+    }
+    get imagesAhead() {
+        return this._state.trajectory.length - 1 - this._state.currentIndex;
+    }
+    get motionless() {
+        return this._state.motionless;
+    }
+    custom() {
+        this._transition(State.Custom);
+    }
+    earth() {
+        this._transition(State.Earth);
+    }
+    traverse() {
+        this._transition(State.Traversing);
+    }
+    wait() {
+        this._transition(State.Waiting);
+    }
+    waitInteractively() {
+        this._transition(State.WaitingInteractively);
+    }
+    getCenter() {
+        return this._state.getCenter();
+    }
+    setCenter(center) {
+        this._state.setCenter(center);
+    }
+    setZoom(zoom) {
+        this._state.setZoom(zoom);
+    }
+    update(fps) {
+        this._state.update(fps);
+    }
+    append(images) {
+        this._state.append(images);
+    }
+    prepend(images) {
+        this._state.prepend(images);
+    }
+    remove(n) {
+        this._state.remove(n);
+    }
+    clear() {
+        this._state.clear();
+    }
+    clearPrior() {
+        this._state.clearPrior();
+    }
+    cut() {
+        this._state.cut();
+    }
+    set(images) {
+        this._state.set(images);
+    }
+    setViewMatrix(matrix) {
+        this._state.setViewMatrix(matrix);
+    }
+    rotate(delta) {
+        this._state.rotate(delta);
+    }
+    rotateUnbounded(delta) {
+        this._state.rotateUnbounded(delta);
+    }
+    rotateWithoutInertia(delta) {
+        this._state.rotateWithoutInertia(delta);
+    }
+    rotateBasic(basicRotation) {
+        this._state.rotateBasic(basicRotation);
+    }
+    rotateBasicUnbounded(basicRotation) {
+        this._state.rotateBasicUnbounded(basicRotation);
+    }
+    rotateBasicWithoutInertia(basicRotation) {
+        this._state.rotateBasicWithoutInertia(basicRotation);
+    }
+    rotateToBasic(basic) {
+        this._state.rotateToBasic(basic);
+    }
+    move(delta) {
+        this._state.move(delta);
+    }
+    moveTo(delta) {
+        this._state.moveTo(delta);
+    }
+    zoomIn(delta, reference) {
+        this._state.zoomIn(delta, reference);
+    }
+    setSpeed(speed) {
+        this._state.setSpeed(speed);
+    }
+    setTransitionMode(mode) {
+        this._state.setTransitionMode(mode);
+    }
+    dolly(delta) {
+        this._state.dolly(delta);
+    }
+    orbit(rotation) {
+        this._state.orbit(rotation);
+    }
+    truck(direction) {
+        this._state.truck(direction);
+    }
+    _transition(to) {
+        if (!this._transitions.validate(this._state, to)) {
+            const from = this._transitions.getState(this._state);
+            console.warn(`Transition not valid (${State[from]} - ${State[to]})`);
+            return;
+        }
+        const state = this._transitions.transition(this._state, to);
+        this._state = state;
+    }
+}
+
+class StateService {
+    constructor(initialState, transitionMode) {
+        this._appendImage$ = new Subject();
+        this._subscriptions = new SubscriptionHolder();
+        const subs = this._subscriptions;
+        this._start$ = new Subject();
+        this._frame$ = new Subject();
+        this._fpsSampleRate = 30;
+        this._contextOperation$ = new BehaviorSubject((context) => {
+            return context;
+        });
+        this._context$ = this._contextOperation$.pipe(scan((context, operation) => {
+            return operation(context);
+        }, new StateContext(initialState, transitionMode)), publishReplay(1), refCount());
+        this._state$ = this._context$.pipe(map((context) => {
+            return context.state;
+        }), distinctUntilChanged(), publishReplay(1), refCount());
+        this._fps$ = this._start$.pipe(switchMap(() => {
+            return this._frame$.pipe(bufferCount(1, this._fpsSampleRate), map(() => {
+                return new Date().getTime();
+            }), pairwise(), map((times) => {
+                return Math.max(20, 1000 * this._fpsSampleRate / (times[1] - times[0]));
+            }), startWith(60));
+        }), share());
+        this._currentState$ = this._frame$.pipe(withLatestFrom(this._fps$, this._context$, (frameId, fps, context) => {
+            return [frameId, fps, context];
+        }), filter((fc) => {
+            return fc[2].currentImage != null;
+        }), tap((fc) => {
+            fc[2].update(fc[1]);
+        }), map((fc) => {
+            return { fps: fc[1], id: fc[0], state: fc[2] };
+        }), share());
+        this._lastState$ = this._currentState$.pipe(publishReplay(1), refCount());
+        let imageChanged$ = this._currentState$.pipe(distinctUntilChanged(undefined, (f) => {
+            return f.state.currentImage.id;
+        }), publishReplay(1), refCount());
+        let imageChangedSubject$ = new Subject();
+        subs.push(imageChanged$
+            .subscribe(imageChangedSubject$));
+        this._currentId$ = new BehaviorSubject(null);
+        subs.push(imageChangedSubject$.pipe(map((f) => {
+            return f.state.currentImage.id;
+        }))
+            .subscribe(this._currentId$));
+        this._currentImage$ = imageChangedSubject$.pipe(map((f) => {
+            return f.state.currentImage;
+        }), publishReplay(1), refCount());
+        this._currentCamera$ = imageChangedSubject$.pipe(map((f) => {
+            return f.state.currentCamera;
+        }), publishReplay(1), refCount());
+        this._currentTransform$ = imageChangedSubject$.pipe(map((f) => {
+            return f.state.currentTransform;
+        }), publishReplay(1), refCount());
+        this._reference$ = imageChangedSubject$.pipe(map((f) => {
+            return f.state.reference;
+        }), distinctUntilChanged((r1, r2) => {
+            return r1.lat === r2.lat && r1.lng === r2.lng;
+        }, (reference) => {
+            return { lat: reference.lat, lng: reference.lng };
+        }), publishReplay(1), refCount());
+        this._currentImageExternal$ = imageChanged$.pipe(map((f) => {
+            return f.state.currentImage;
+        }), publishReplay(1), refCount());
+        subs.push(this._appendImage$.pipe(map((image) => {
+            return (context) => {
+                context.append([image]);
+                return context;
+            };
+        }))
+            .subscribe(this._contextOperation$));
+        this._inMotionOperation$ = new Subject();
+        subs.push(imageChanged$.pipe(map(() => {
+            return true;
+        }))
+            .subscribe(this._inMotionOperation$));
+        subs.push(this._inMotionOperation$.pipe(distinctUntilChanged(), filter((moving) => {
+            return moving;
+        }), switchMap(() => {
+            return this._currentState$.pipe(filter((frame) => {
+                return frame.state.imagesAhead === 0;
+            }), map((frame) => {
+                return [frame.state.camera.clone(), frame.state.zoom];
+            }), pairwise(), map((pair) => {
+                let c1 = pair[0][0];
+                let c2 = pair[1][0];
+                let z1 = pair[0][1];
+                let z2 = pair[1][1];
+                return c1.diff(c2) > 1e-5 || Math.abs(z1 - z2) > 1e-5;
+            }), first((changed) => {
+                return !changed;
+            }));
+        }))
+            .subscribe(this._inMotionOperation$));
+        this._inMotion$ = this._inMotionOperation$.pipe(distinctUntilChanged(), publishReplay(1), refCount());
+        this._inTranslationOperation$ = new Subject();
+        subs.push(imageChanged$.pipe(map(() => {
+            return true;
+        }))
+            .subscribe(this._inTranslationOperation$));
+        subs.push(this._inTranslationOperation$.pipe(distinctUntilChanged(), filter((inTranslation) => {
+            return inTranslation;
+        }), switchMap(() => {
+            return this._currentState$.pipe(filter((frame) => {
+                return frame.state.imagesAhead === 0;
+            }), map((frame) => {
+                return frame.state.camera.position.clone();
+            }), pairwise(), map((pair) => {
+                return pair[0].distanceToSquared(pair[1]) !== 0;
+            }), first((changed) => {
+                return !changed;
+            }));
+        }))
+            .subscribe(this._inTranslationOperation$));
+        this._inTranslation$ = this._inTranslationOperation$.pipe(distinctUntilChanged(), publishReplay(1), refCount());
+        subs.push(this._state$.subscribe(() => { }));
+        subs.push(this._currentImage$.subscribe(() => { }));
+        subs.push(this._currentCamera$.subscribe(() => { }));
+        subs.push(this._currentTransform$.subscribe(() => { }));
+        subs.push(this._reference$.subscribe(() => { }));
+        subs.push(this._currentImageExternal$.subscribe(() => { }));
+        subs.push(this._lastState$.subscribe(() => { }));
+        subs.push(this._inMotion$.subscribe(() => { }));
+        subs.push(this._inTranslation$.subscribe(() => { }));
+        this._frameId = null;
+        this._frameGenerator = new FrameGenerator(window);
+    }
+    get currentState$() {
+        return this._currentState$;
+    }
+    get currentImage$() {
+        return this._currentImage$;
+    }
+    get currentId$() {
+        return this._currentId$;
+    }
+    get currentImageExternal$() {
+        return this._currentImageExternal$;
+    }
+    get currentCamera$() {
+        return this._currentCamera$;
+    }
+    get currentTransform$() {
+        return this._currentTransform$;
+    }
+    get state$() {
+        return this._state$;
+    }
+    get reference$() {
+        return this._reference$;
+    }
+    get inMotion$() {
+        return this._inMotion$;
+    }
+    get inTranslation$() {
+        return this._inTranslation$;
+    }
+    get appendImage$() {
+        return this._appendImage$;
+    }
+    dispose() {
+        this.stop();
+        this._subscriptions.unsubscribe();
+    }
+    custom() {
+        this._inMotionOperation$.next(true);
+        this._invokeContextOperation((context) => {
+            context.custom();
+        });
+    }
+    earth() {
+        this._inMotionOperation$.next(true);
+        this._invokeContextOperation((context) => { context.earth(); });
+    }
+    traverse() {
+        this._inMotionOperation$.next(true);
+        this._invokeContextOperation((context) => { context.traverse(); });
+    }
+    wait() {
+        this._invokeContextOperation((context) => { context.wait(); });
+    }
+    waitInteractively() {
+        this._invokeContextOperation((context) => { context.waitInteractively(); });
+    }
+    appendImagess(images) {
+        this._invokeContextOperation((context) => { context.append(images); });
+    }
+    prependImages(images) {
+        this._invokeContextOperation((context) => { context.prepend(images); });
+    }
+    removeImages(n) {
+        this._invokeContextOperation((context) => { context.remove(n); });
+    }
+    clearImages() {
+        this._invokeContextOperation((context) => { context.clear(); });
+    }
+    clearPriorImages() {
+        this._invokeContextOperation((context) => { context.clearPrior(); });
+    }
+    cutImages() {
+        this._invokeContextOperation((context) => { context.cut(); });
+    }
+    setImages(images) {
+        this._invokeContextOperation((context) => { context.set(images); });
+    }
+    setViewMatrix(matrix) {
+        this._inMotionOperation$.next(true);
+        this._invokeContextOperation((context) => { context.setViewMatrix(matrix); });
+    }
+    rotate(delta) {
+        this._inMotionOperation$.next(true);
+        this._invokeContextOperation((context) => { context.rotate(delta); });
+    }
+    rotateUnbounded(delta) {
+        this._inMotionOperation$.next(true);
+        this._invokeContextOperation((context) => { context.rotateUnbounded(delta); });
+    }
+    rotateWithoutInertia(delta) {
+        this._inMotionOperation$.next(true);
+        this._invokeContextOperation((context) => { context.rotateWithoutInertia(delta); });
+    }
+    rotateBasic(basicRotation) {
+        this._inMotionOperation$.next(true);
+        this._invokeContextOperation((context) => { context.rotateBasic(basicRotation); });
+    }
+    rotateBasicUnbounded(basicRotation) {
+        this._inMotionOperation$.next(true);
+        this._invokeContextOperation((context) => { context.rotateBasicUnbounded(basicRotation); });
+    }
+    rotateBasicWithoutInertia(basicRotation) {
+        this._inMotionOperation$.next(true);
+        this._invokeContextOperation((context) => { context.rotateBasicWithoutInertia(basicRotation); });
+    }
+    rotateToBasic(basic) {
+        this._inMotionOperation$.next(true);
+        this._invokeContextOperation((context) => { context.rotateToBasic(basic); });
+    }
+    move(delta) {
+        this._inMotionOperation$.next(true);
+        this._invokeContextOperation((context) => { context.move(delta); });
+    }
+    moveTo(position) {
+        this._inMotionOperation$.next(true);
+        this._invokeContextOperation((context) => { context.moveTo(position); });
+    }
+    dolly(delta) {
+        this._inMotionOperation$.next(true);
+        this._invokeContextOperation((context) => { context.dolly(delta); });
+    }
+    orbit(rotation) {
+        this._inMotionOperation$.next(true);
+        this._invokeContextOperation((context) => { context.orbit(rotation); });
+    }
+    truck(direction) {
+        this._inMotionOperation$.next(true);
+        this._invokeContextOperation((context) => { context.truck(direction); });
+    }
+    /**
+     * Change zoom level while keeping the reference point position approximately static.
+     *
+     * @parameter {number} delta - Change in zoom level.
+     * @parameter {Array<number>} reference - Reference point in basic coordinates.
+     */
+    zoomIn(delta, reference) {
+        this._inMotionOperation$.next(true);
+        this._invokeContextOperation((context) => { context.zoomIn(delta, reference); });
+    }
+    getCenter() {
+        return this._lastState$.pipe(first(), map((frame) => {
+            return frame.state.getCenter();
+        }));
+    }
+    getZoom() {
+        return this._lastState$.pipe(first(), map((frame) => {
+            return frame.state.zoom;
+        }));
+    }
+    setCenter(center) {
+        this._inMotionOperation$.next(true);
+        this._invokeContextOperation((context) => { context.setCenter(center); });
+    }
+    setSpeed(speed) {
+        this._invokeContextOperation((context) => { context.setSpeed(speed); });
+    }
+    setTransitionMode(mode) {
+        this._invokeContextOperation((context) => { context.setTransitionMode(mode); });
+    }
+    setZoom(zoom) {
+        this._inMotionOperation$.next(true);
+        this._invokeContextOperation((context) => { context.setZoom(zoom); });
+    }
+    start() {
+        if (this._frameId == null) {
+            this._start$.next(null);
+            this._frameId = this._frameGenerator.requestAnimationFrame(this._frame.bind(this));
+            this._frame$.next(this._frameId);
+        }
+    }
+    stop() {
+        if (this._frameId != null) {
+            this._frameGenerator.cancelAnimationFrame(this._frameId);
+            this._frameId = null;
+        }
+    }
+    _invokeContextOperation(action) {
+        this._contextOperation$
+            .next((context) => {
+            action(context);
+            return context;
+        });
+    }
+    _frame() {
+        this._frameId = this._frameGenerator.requestAnimationFrame(this._frame.bind(this));
+        this._frame$.next(this._frameId);
+    }
+}
+
+function cameraControlsToState(cameraControls) {
+    switch (cameraControls) {
+        case CameraControls.Custom:
+            return State.Custom;
+        case CameraControls.Earth:
+            return State.Earth;
+        case CameraControls.Street:
+            return State.Traversing;
+        default:
+            return null;
+    }
+}
+
+class Navigator {
+    constructor(options, api, graphService, loadingService, stateService, cacheService, playService, panService) {
+        var _a;
+        if (api) {
+            this._api = api;
+        }
+        else if (options.dataProvider) {
+            if (options.dataProvider instanceof DataProviderBase) {
+                this._api = new APIWrapper(options.dataProvider);
+            }
+            else {
+                throw new Error("Incorrect type: 'dataProvider' must extend the DataProviderBase class.");
+            }
+        }
+        else {
+            this._api = new APIWrapper(new GraphDataProvider({
+                accessToken: options.accessToken,
+            }));
+        }
+        this._graphService = graphService !== null && graphService !== void 0 ? graphService : new GraphService(new Graph(this.api));
+        this._loadingName = "navigator";
+        this._loadingService = loadingService !== null && loadingService !== void 0 ? loadingService : new LoadingService();
+        const cameraControls = (_a = options.cameraControls) !== null && _a !== void 0 ? _a : CameraControls.Street;
+        this._stateService = stateService !== null && stateService !== void 0 ? stateService : new StateService(cameraControlsToState(cameraControls), options.transitionMode);
+        this._cacheService = cacheService !== null && cacheService !== void 0 ? cacheService : new CacheService(this._graphService, this._stateService, this._api);
+        this._playService = playService !== null && playService !== void 0 ? playService : new PlayService(this._graphService, this._stateService);
+        this._panService = panService !== null && panService !== void 0 ? panService : new PanService(this._graphService, this._stateService, options.combinedPanning);
+        this._idRequested$ = new BehaviorSubject(null);
+        this._movedToId$ = new BehaviorSubject(null);
+        this._request$ = null;
+        this._requestSubscription = null;
+        this._imageRequestSubscription = null;
+    }
+    get api() {
+        return this._api;
+    }
+    get cacheService() {
+        return this._cacheService;
+    }
+    get graphService() {
+        return this._graphService;
+    }
+    get loadingService() {
+        return this._loadingService;
+    }
+    get movedToId$() {
+        return this._movedToId$;
+    }
+    get panService() {
+        return this._panService;
+    }
+    get playService() {
+        return this._playService;
+    }
+    get stateService() {
+        return this._stateService;
+    }
+    dispose() {
+        this._abortRequest("viewer removed");
+        this._cacheService.stop();
+        this._graphService.dispose();
+        this._panService.dispose();
+        this._playService.dispose();
+        this._stateService.dispose();
+    }
+    moveTo$(id) {
+        this._abortRequest(`to id ${id}`);
+        this._loadingService.startLoading(this._loadingName);
+        const image$ = this._moveTo$(id);
+        return this._makeRequest$(image$);
+    }
+    moveDir$(direction) {
+        this._abortRequest(`in dir ${NavigationDirection[direction]}`);
+        this._loadingService.startLoading(this._loadingName);
+        const image$ = this.stateService.currentImage$.pipe(first(), mergeMap((image) => {
+            return ([NavigationDirection.Next, NavigationDirection.Prev].indexOf(direction) > -1 ?
+                image.sequenceEdges$ :
+                image.spatialEdges$).pipe(first(), map((status) => {
+                for (let edge of status.edges) {
+                    if (edge.data.direction === direction) {
+                        return edge.target;
+                    }
+                }
+                return null;
+            }));
+        }), mergeMap((directionId) => {
+            if (directionId == null) {
+                this._loadingService.stopLoading(this._loadingName);
+                return throwError(new Error(`Direction (${direction}) does not exist for current image.`));
+            }
+            return this._moveTo$(directionId);
+        }));
+        return this._makeRequest$(image$);
+    }
+    setFilter$(filter) {
+        this._stateService.clearImages();
+        return this._movedToId$.pipe(first(), mergeMap((id) => {
+            if (id != null) {
+                return this._trajectoryIds$().pipe(mergeMap((ids) => {
+                    return this._graphService.setFilter$(filter).pipe(mergeMap(() => {
+                        return this._cacheIds$(ids);
+                    }));
+                }), last());
+            }
+            return this._idRequested$.pipe(first(), mergeMap((requestedId) => {
+                if (requestedId != null) {
+                    return this._graphService.setFilter$(filter).pipe(mergeMap(() => {
+                        return this._graphService.cacheImage$(requestedId);
+                    }));
+                }
+                return this._graphService.setFilter$(filter).pipe(map(() => {
+                    return undefined;
+                }));
+            }));
+        }), map(() => {
+            return undefined;
+        }));
+    }
+    setAccessToken$(accessToken) {
+        this._abortRequest("to set user token");
+        this._stateService.clearImages();
+        return this._movedToId$.pipe(first(), tap(() => {
+            this._api.setAccessToken(accessToken);
+        }), mergeMap((id) => {
+            return id == null ?
+                this._graphService.reset$([]) :
+                this._trajectoryIds$().pipe(mergeMap((ids) => {
+                    return this._graphService.reset$(ids).pipe(mergeMap(() => {
+                        return this._cacheIds$(ids);
+                    }));
+                }), last(), map(() => {
+                    return undefined;
+                }));
+        }));
+    }
+    _cacheIds$(ids) {
+        const cacheImages$ = ids
+            .map((id) => {
+            return this._graphService.cacheImage$(id);
+        });
+        return from(cacheImages$).pipe(mergeAll());
+    }
+    _abortRequest(reason) {
+        if (this._requestSubscription != null) {
+            this._requestSubscription.unsubscribe();
+            this._requestSubscription = null;
+        }
+        if (this._imageRequestSubscription != null) {
+            this._imageRequestSubscription.unsubscribe();
+            this._imageRequestSubscription = null;
+        }
+        if (this._request$ != null) {
+            if (!(this._request$.isStopped || this._request$.hasError)) {
+                this._request$.error(new CancelMapillaryError(`Request aborted by a subsequent request ${reason}.`));
+            }
+            this._request$ = null;
+        }
+    }
+    _makeRequest$(image$) {
+        const request$ = new ReplaySubject(1);
+        this._requestSubscription = request$
+            .subscribe(undefined, () => { });
+        this._request$ = request$;
+        this._imageRequestSubscription = image$
+            .subscribe((image) => {
+            this._request$ = null;
+            request$.next(image);
+            request$.complete();
+        }, (error) => {
+            this._request$ = null;
+            request$.error(error);
+        });
+        return request$;
+    }
+    _moveTo$(id) {
+        this._idRequested$.next(id);
+        return this._graphService.cacheImage$(id).pipe(tap((image) => {
+            this._stateService.setImages([image]);
+            this._movedToId$.next(image.id);
+        }), finalize(() => {
+            this._loadingService.stopLoading(this._loadingName);
+        }));
+    }
+    _trajectoryIds$() {
+        return this._stateService.currentState$.pipe(first(), map((frame) => {
+            return frame.state.trajectory
+                .map((image) => {
+                return image.id;
+            });
+        }));
+    }
+}
+
+class Projection {
+    constructor(viewportCoords, spatial) {
+        this._spatial = spatial !== null && spatial !== void 0 ? spatial : new Spatial();
+        this._viewportCoords = viewportCoords !== null && viewportCoords !== void 0 ? viewportCoords : new ViewportCoords();
+    }
+    basicToCanvas(basicPoint, container, render, transform) {
+        return this._viewportCoords
+            .basicToCanvasSafe(basicPoint[0], basicPoint[1], container, transform, render.perspective);
+    }
+    canvasToBasic(canvasPoint, container, render, transform) {
+        let basicPoint = this._viewportCoords
+            .canvasToBasic(canvasPoint[0], canvasPoint[1], container, transform, render.perspective);
+        if (basicPoint[0] < 0 ||
+            basicPoint[0] > 1 ||
+            basicPoint[1] < 0 ||
+            basicPoint[1] > 1) {
+            basicPoint = null;
+        }
+        return basicPoint;
+    }
+    eventToUnprojection(event, container, render, reference, transform) {
+        const pixelPoint = this._viewportCoords
+            .canvasPosition(event, container);
+        return this.canvasToUnprojection(pixelPoint, container, render, reference, transform);
+    }
+    canvasToUnprojection(canvasPoint, container, render, reference, transform) {
+        const canvasX = canvasPoint[0];
+        const canvasY = canvasPoint[1];
+        const [viewportX, viewportY] = this._viewportCoords
+            .canvasToViewport(canvasX, canvasY, container);
+        const point3d = new Vector3(viewportX, viewportY, 1)
+            .unproject(render.perspective);
+        let basicPoint = transform
+            .projectBasic(point3d.toArray());
+        if (basicPoint[0] < 0 ||
+            basicPoint[0] > 1 ||
+            basicPoint[1] < 0 ||
+            basicPoint[1] > 1) {
+            basicPoint = null;
+        }
+        const direction3d = point3d
+            .clone()
+            .sub(render.camera.position)
+            .normalize();
+        const dist = -2 / direction3d.z;
+        let lngLat = null;
+        if (dist > 0 && dist < 100 && !!basicPoint) {
+            const point = direction3d
+                .clone()
+                .multiplyScalar(dist)
+                .add(render.camera.position);
+            const [lng, lat] = enuToGeodetic(point.x, point.y, point.z, reference.lng, reference.lat, reference.alt);
+            lngLat = { lat, lng };
+        }
+        const unprojection = {
+            basicPoint: basicPoint,
+            lngLat: lngLat,
+            pixelPoint: [canvasX, canvasY],
+        };
+        return unprojection;
+    }
+    cameraToLngLat(render, reference) {
+        const position = render.camera.position;
+        const [lng, lat] = enuToGeodetic(position.x, position.y, position.z, reference.lng, reference.lat, reference.alt);
+        return { lat, lng };
+    }
+    lngLatToCanvas(lngLat, container, render, reference) {
+        const point3d = geodeticToEnu(lngLat.lng, lngLat.lat, 0, reference.lng, reference.lat, reference.alt);
+        const canvas = this._viewportCoords
+            .projectToCanvasSafe(point3d, container, render.perspective);
+        return canvas;
+    }
+    distanceBetweenLngLats(lngLat1, lngLat2) {
+        return this._spatial
+            .distanceFromLngLat(lngLat1.lng, lngLat1.lat, lngLat2.lng, lngLat2.lat);
+    }
+}
+
+class Observer {
+    constructor(viewer, navigator, container) {
+        this._subscriptions = new SubscriptionHolder();
+        this._emitSubscriptions = new SubscriptionHolder();
+        this._container = container;
+        this._viewer = viewer;
+        this._navigator = navigator;
+        this._projection = new Projection();
+        this._started = false;
+        this._navigable$ = new Subject();
+        const subs = this._subscriptions;
+        // load, navigable, dataloading should always emit,
+        // also when cover is activated.
+        subs.push(this._navigable$
+            .subscribe((navigable) => {
+            const type = "navigable";
+            const event = {
+                navigable,
+                target: this._viewer,
+                type,
+            };
+            this._viewer.fire(type, event);
+        }));
+        subs.push(this._navigator.loadingService.loading$
+            .subscribe((loading) => {
+            const type = "dataloading";
+            const event = {
+                loading,
+                target: this._viewer,
+                type,
+            };
+            this._viewer.fire(type, event);
+        }));
+        subs.push(this._container.glRenderer.opaqueRender$
+            .pipe(first())
+            .subscribe(() => {
+            const type = "load";
+            const event = {
+                target: this._viewer,
+                type,
+            };
+            this._viewer.fire(type, event);
+        }));
+    }
+    get started() {
+        return this._started;
+    }
+    get navigable$() {
+        return this._navigable$;
+    }
+    get projection() {
+        return this._projection;
+    }
+    dispose() {
+        this.stopEmit();
+        this._subscriptions.unsubscribe();
+    }
+    project$(lngLat) {
+        return combineLatest(this._container.renderService.renderCamera$, this._navigator.stateService.currentImage$, this._navigator.stateService.reference$).pipe(first(), map(([render, image, reference]) => {
+            if (this._projection
+                .distanceBetweenLngLats(lngLat, image.lngLat) > 1000) {
+                return null;
+            }
+            const canvasPoint = this._projection.lngLatToCanvas(lngLat, this._container.container, render, reference);
+            return !!canvasPoint ?
+                [Math.round(canvasPoint[0]), Math.round(canvasPoint[1])] :
+                null;
+        }));
+    }
+    projectBasic$(basicPoint) {
+        return combineLatest(this._container.renderService.renderCamera$, this._navigator.stateService.currentTransform$).pipe(first(), map(([render, transform]) => {
+            const canvasPoint = this._projection.basicToCanvas(basicPoint, this._container.container, render, transform);
+            return !!canvasPoint ?
+                [Math.round(canvasPoint[0]), Math.round(canvasPoint[1])] :
+                null;
+        }));
+    }
+    startEmit() {
+        if (this._started) {
+            return;
+        }
+        this._started = true;
+        const subs = this._emitSubscriptions;
+        subs.push(this._navigator.stateService.currentImageExternal$
+            .subscribe((image) => {
+            const type = "image";
+            const event = {
+                image: image,
+                target: this._viewer,
+                type,
+            };
+            this._viewer.fire(type, event);
+        }));
+        subs.push(this._navigator.stateService.currentImageExternal$.pipe(switchMap((image) => {
+            return image.sequenceEdges$;
+        }))
+            .subscribe((status) => {
+            const type = "sequenceedges";
+            const event = {
+                status,
+                target: this._viewer,
+                type,
+            };
+            this._viewer.fire(type, event);
+        }));
+        subs.push(this._navigator.stateService.currentImageExternal$.pipe(switchMap((image) => {
+            return image.spatialEdges$;
+        }))
+            .subscribe((status) => {
+            const type = "spatialedges";
+            const event = {
+                status,
+                target: this._viewer,
+                type,
+            };
+            this._viewer.fire(type, event);
+        }));
+        subs.push(combineLatest(this._navigator.stateService.inMotion$, this._container.mouseService.active$, this._container.touchService.active$).pipe(map((values) => {
+            return values[0] || values[1] || values[2];
+        }), distinctUntilChanged())
+            .subscribe((started) => {
+            const type = started ? "movestart" : "moveend";
+            const event = {
+                target: this._viewer,
+                type,
+            };
+            this._viewer.fire(type, event);
+        }));
+        subs.push(this._container.renderService.bearing$.pipe(auditTime(100), distinctUntilChanged((b1, b2) => {
+            return Math.abs(b2 - b1) < 1;
+        }))
+            .subscribe((bearing) => {
+            const type = "bearing";
+            const event = {
+                bearing,
+                target: this._viewer,
+                type,
+            };
+            this._viewer.fire(type, event);
+        }));
+        const mouseMove$ = this._container.mouseService.active$.pipe(switchMap((active) => {
+            return active ?
+                empty() :
+                this._container.mouseService.mouseMove$;
+        }));
+        subs.push(merge(this._mapMouseEvent$("click", this._container.mouseService.staticClick$), this._mapMouseEvent$("contextmenu", this._container.mouseService.contextMenu$), this._mapMouseEvent$("dblclick", this._container.mouseService.dblClick$), this._mapMouseEvent$("mousedown", this._container.mouseService.mouseDown$), this._mapMouseEvent$("mousemove", mouseMove$), this._mapMouseEvent$("mouseout", this._container.mouseService.mouseOut$), this._mapMouseEvent$("mouseover", this._container.mouseService.mouseOver$), this._mapMouseEvent$("mouseup", this._container.mouseService.mouseUp$))
+            .pipe(withLatestFrom(this._container.renderService.renderCamera$, this._navigator.stateService.reference$, this._navigator.stateService.currentTransform$, this._navigator.stateService.state$), map(([[type, event], render, reference, transform, state]) => {
+            const unprojection = this._projection.eventToUnprojection(event, this._container.container, render, reference, transform);
+            const basicPoint = state === State.Traversing ?
+                unprojection.basicPoint : null;
+            return {
+                basicPoint,
+                lngLat: unprojection.lngLat,
+                originalEvent: event,
+                pixelPoint: unprojection.pixelPoint,
+                target: this._viewer,
+                type: type,
+            };
+        }))
+            .subscribe((event) => {
+            this._viewer.fire(event.type, event);
+        }));
+        subs.push(this._container.renderService.renderCamera$.pipe(distinctUntilChanged(([x1, y1], [x2, y2]) => {
+            return this._closeTo(x1, x2, 1e-2) &&
+                this._closeTo(y1, y2, 1e-2);
+        }, (rc) => {
+            return rc.camera.position.toArray();
+        }))
+            .subscribe(() => {
+            const type = "position";
+            const event = {
+                target: this._viewer,
+                type,
+            };
+            this._viewer.fire(type, event);
+        }));
+        subs.push(this._container.renderService.renderCamera$.pipe(distinctUntilChanged(([phi1, theta1], [phi2, theta2]) => {
+            return this._closeTo(phi1, phi2, 1e-3) &&
+                this._closeTo(theta1, theta2, 1e-3);
+        }, (rc) => {
+            return [rc.rotation.phi, rc.rotation.theta];
+        }))
+            .subscribe(() => {
+            const type = "pov";
+            const event = {
+                target: this._viewer,
+                type,
+            };
+            this._viewer.fire(type, event);
+        }));
+        subs.push(this._container.renderService.renderCamera$.pipe(distinctUntilChanged((fov1, fov2) => {
+            return this._closeTo(fov1, fov2, 1e-2);
+        }, (rc) => {
+            return rc.perspective.fov;
+        }))
+            .subscribe(() => {
+            const type = "fov";
+            const event = {
+                target: this._viewer,
+                type,
+            };
+            this._viewer.fire(type, event);
+        }));
+    }
+    stopEmit() {
+        if (!this.started) {
+            return;
+        }
+        this._emitSubscriptions.unsubscribe();
+        this._started = false;
+    }
+    unproject$(canvasPoint) {
+        return combineLatest(this._container.renderService.renderCamera$, this._navigator.stateService.reference$, this._navigator.stateService.currentTransform$).pipe(first(), map(([render, reference, transform]) => {
+            const unprojection = this._projection.canvasToUnprojection(canvasPoint, this._container.container, render, reference, transform);
+            return unprojection.lngLat;
+        }));
+    }
+    unprojectBasic$(canvasPoint) {
+        return combineLatest(this._container.renderService.renderCamera$, this._navigator.stateService.currentTransform$).pipe(first(), map(([render, transform]) => {
+            return this._projection.canvasToBasic(canvasPoint, this._container.container, render, transform);
+        }));
+    }
+    _closeTo(v1, v2, absoluteTolerance) {
+        return Math.abs(v1 - v2) <= absoluteTolerance;
+    }
+    _mapMouseEvent$(type, mouseEvent$) {
+        return mouseEvent$.pipe(map((event) => {
+            return [type, event];
+        }));
+    }
+}
+
+class CustomRenderer {
+    constructor(_container, _navigator) {
+        this._container = _container;
+        this._navigator = _navigator;
+        this._renderers = {};
+    }
+    add(renderer, viewer) {
+        const subs = new SubscriptionHolder();
+        this._renderers[renderer.id] = { subs, renderer };
+        subs.push(combineLatest([
+            this._container.glRenderer.webGLRenderer$,
+            this._navigator.stateService.reference$,
+        ])
+            .pipe(take(1))
+            .subscribe(([gl, reference]) => {
+            renderer.onAdd(viewer, reference, gl.getContext());
+        }));
+        subs.push(this._container.glRenderer.opaqueRender$
+            .pipe(withLatestFrom(this._container.renderService.renderCamera$, this._container.glRenderer.webGLRenderer$))
+            .subscribe(([, renderCamera, glRenderer]) => {
+            const context = glRenderer.getContext();
+            const viewMatrix = renderCamera.perspective.matrixWorldInverse;
+            const projectionMatrix = renderCamera.perspective.projectionMatrix;
+            renderer.render(context, viewMatrix.toArray(), projectionMatrix.toArray());
+        }));
+        subs.push(this._navigator.stateService.reference$
+            .pipe(skip(1))
+            .subscribe((reference) => {
+            renderer.onReference(viewer, reference);
+        }));
+    }
+    dispose(viewer) {
+        for (const id of Object.keys(this._renderers)) {
+            this.remove(id, viewer);
+        }
+    }
+    has(id) {
+        return id in this._renderers;
+    }
+    remove(id, viewer) {
+        this._renderers[id].subs.unsubscribe();
+        const renderer = this._renderers[id].renderer;
+        delete this._renderers[id];
+        this._container.glRenderer.webGLRenderer$
+            .subscribe((gl) => {
+            renderer.onRemove(viewer, gl.getContext());
+        });
+    }
+}
+
+class CustomCameraControls {
+    constructor(_container, _navigator) {
+        this._container = _container;
+        this._navigator = _navigator;
+        this._controls = null;
+        this._subscriptions = new SubscriptionHolder();
+    }
+    attach(controls, viewer) {
+        if (this._controls) {
+            throw new MapillaryError('Custom camera controls already attached');
+        }
+        const attach$ = new Subject();
+        const active$ = attach$
+            .pipe(switchMap(() => {
+            return this._navigator.stateService.state$;
+        }), map((state) => {
+            return state === State.Custom;
+        }), distinctUntilChanged());
+        const subs = this._subscriptions;
+        subs.push(active$
+            .pipe(startWith(false), pairwise(), withLatestFrom(this._navigator.stateService.reference$, this._container.renderService.renderCamera$))
+            .subscribe(([[deactivate, activate], ref, cam]) => {
+            if (activate) {
+                controls.onActivate(viewer, cam.perspective.matrixWorldInverse.toArray(), cam.perspective.projectionMatrix.toArray(), ref);
+            }
+            else if (deactivate) {
+                controls.onDeactivate(viewer);
+            }
+        }));
+        subs.push(active$
+            .pipe(switchMap(active => {
+            return active ?
+                this._navigator.stateService.currentState$
+                    .pipe(skip(1)) :
+                empty();
+        }))
+            .subscribe(frame => {
+            controls.onAnimationFrame(viewer, frame.id);
+        }));
+        subs.push(active$
+            .pipe(switchMap(active => {
+            return active ?
+                this._navigator.stateService.reference$
+                    .pipe(skip(1)) :
+                empty();
+        }))
+            .subscribe(ref => controls.onReference(viewer, ref)));
+        subs.push(active$
+            .pipe(switchMap(active => {
+            return active ?
+                this._container.renderService.size$
+                    .pipe(skip(1)) :
+                empty();
+        }))
+            .subscribe(() => controls.onResize(viewer)));
+        subs.push(combineLatest([
+            // Include to ensure GL renderer has been initialized
+            this._container.glRenderer.webGLRenderer$,
+            this._container.renderService.renderCamera$,
+            this._navigator.stateService.reference$,
+            this._navigator.stateService.state$,
+        ])
+            .pipe(first())
+            .subscribe(() => {
+            const projectionMatrixCallback = (projectionMatrix) => {
+                if (!this._controls ||
+                    controls !== this._controls) {
+                    return;
+                }
+                this._updateProjectionMatrix(projectionMatrix);
+            };
+            const viewMatrixCallback = (viewMatrix) => {
+                if (!this._controls ||
+                    controls !== this._controls) {
+                    return;
+                }
+                this._updateViewMatrix(viewMatrix);
+            };
+            controls.onAttach(viewer, viewMatrixCallback, projectionMatrixCallback);
+            attach$.next();
+            attach$.complete();
+        }));
+        this._controls = controls;
+    }
+    dispose(viewer) {
+        this.detach(viewer);
+    }
+    detach(viewer) {
+        if (!this._controls) {
+            return;
+        }
+        this._subscriptions.unsubscribe();
+        this._navigator.stateService.state$
+            .subscribe(state => {
+            if (state === State.Custom) {
+                this._controls.onDeactivate(viewer);
+            }
+            this._controls.onDetach(viewer);
+            this._controls = null;
+        });
+    }
+    _updateProjectionMatrix(projectionMatrix) {
+        this._navigator.stateService.state$
+            .pipe(first())
+            .subscribe(state => {
+            if (state !== State.Custom) {
+                const message = "Incorrect camera control mode for " +
+                    "projection matrix update";
+                console.warn(message);
+                return;
+            }
+            this._container.renderService.projectionMatrix$
+                .next(projectionMatrix);
+        });
+    }
+    _updateViewMatrix(viewMatrix) {
+        this._navigator.stateService.state$
+            .pipe(first())
+            .subscribe(state => {
+            if (state !== State.Custom) {
+                const message = "Incorrect camera control mode for " +
+                    "view matrix update";
+                console.warn(message);
+                return;
+            }
+            this._navigator.stateService.setViewMatrix(viewMatrix);
+        });
+    }
+}
+
+/**
+ * @class Viewer
+ *
+ * @classdesc The Viewer object represents the navigable image viewer.
+ * Create a Viewer by specifying a container, client ID, image id and
+ * other options. The viewer exposes methods and events for programmatic
+ * interaction.
+ *
+ * In the case of asynchronous methods, MapillaryJS returns promises to
+ * the results. Notifications are always emitted through JavaScript events.
+ */
+class Viewer extends EventEmitter {
+    /**
+     * Create a new viewer instance.
+     *
+     * @description It is possible to initialize the viewer with or
+     * without a id.
+     *
+     * When you want to show a specific image in the viewer from
+     * the start you should initialize it with a id.
+     *
+     * When you do not know the first image id at implementation
+     * time, e.g. in a map-viewer application you should initialize
+     * the viewer without a id and call `moveTo` instead.
+     *
+     * When initializing with a id the viewer is bound to that id
+     * until the image for that id has been successfully loaded.
+     * Also, a cover with the image of the id will be shown.
+     * If the data for that id can not be loaded because the id is
+     * faulty or other errors occur it is not possible to navigate
+     * to another id because the viewer is not navigable. The viewer
+     * becomes navigable when the data for the id has been loaded and
+     * the image is shown in the viewer. This way of initializing
+     * the viewer is mostly for embedding in blog posts and similar
+     * where one wants to show a specific image initially.
+     *
+     * If the viewer is initialized without a id (with null or
+     * undefined) it is not bound to any particular id and it is
+     * possible to move to any id with `viewer.moveTo("<my-image-id>")`.
+     * If the first move to a id fails it is possible to move to another
+     * id. The viewer will show a black background until a move
+     * succeeds. This way of intitializing is suited for a map-viewer
+     * application when the initial id is not known at implementation
+     * time.
+     *
+     * @param {ViewerOptions} options - Optional configuration object
+     * specifing Viewer's and the components' initial setup.
+     *
+     * @example
+     * ```js
+     * var viewer = new Viewer({
+     *     accessToken: "<my-access-token>",
+     *     container: "<my-container-id>",
+     * });
+     * ```
+     */
+    constructor(options) {
+        super();
+        this._navigator =
+            new Navigator(options);
+        this._container =
+            new Container(options, this._navigator.stateService);
+        this._observer =
+            new Observer(this, this._navigator, this._container);
+        this._componentController =
+            new ComponentController(this._container, this._navigator, this._observer, options.imageId, options.component);
+        this._customRenderer =
+            new CustomRenderer(this._container, this._navigator);
+        this._customCameraControls =
+            new CustomCameraControls(this._container, this._navigator);
+    }
+    /**
+     * Return a boolean indicating if the viewer is in a navigable state.
+     *
+     * @description The navigable state indicates if the viewer supports
+     * moving, i.e. calling the {@link moveTo} and {@link moveDir}
+     * methods or changing the authentication state,
+     * i.e. calling {@link setAccessToken}. The viewer will not be in a navigable
+     * state if the cover is activated and the viewer has been supplied a id.
+     * When the cover is deactivated or the viewer is activated without being
+     * supplied a id it will be navigable.
+     *
+     * @returns {boolean} Boolean indicating whether the viewer is navigable.
+     */
+    get isNavigable() {
+        return this._componentController.navigable;
+    }
+    /**
+     * Activate the combined panning functionality.
+     *
+     * @description The combined panning functionality is active by default.
+     */
+    activateCombinedPanning() {
+        this._navigator.panService.enable();
+    }
+    /**
+     * Activate a component.
+     *
+     * @param {ComponentName | FallbackComponentName} name - Name of
+     * the component which will become active.
+     *
+     * @example
+     * ```js
+     * viewer.activateComponent("marker");
+     * ```
+     */
+    activateComponent(name) {
+        this._componentController.activate(name);
+    }
+    /**
+     * Activate the cover (deactivates all other components).
+     */
+    activateCover() {
+        this._componentController.activateCover();
+    }
+    /**
+     * Add a custom renderer to the viewer's rendering pipeline.
+     *
+     * @description During a render pass, custom renderers
+     * are called in the order they were added.
+     *
+     * @param renderer - The custom renderer implementation.
+     */
+    addCustomRenderer(renderer) {
+        this._customRenderer.add(renderer, this);
+    }
+    /**
+     * Attach custom camera controls to control the viewer's
+     * camera pose and projection.
+     *
+     * @description Custom camera controls allow the API user
+     * to move the viewer's camera freely and define the camera
+     * projection. These camera properties are used
+     * to render the viewer 3D scene directly into the
+     * viewer's GL context.
+     *
+     * Only a single custom camera control instance can be
+     * attached to the viewer. A new custom camera control
+     * instance can be attached after detaching a previous
+     * one.
+     *
+     * Set the viewer's camera controls to
+     * {@link CameraControls.Custom} to activate attached
+     * camera controls. If {@link CameraControls.Custom}
+     * has already been set when a custom camera control
+     * instance is attached, it will be activated immediately.
+     *
+     * Set the viewer's camera controls to any other
+     * {@link CameraControls} mode to deactivate the
+     * custom camera controls.
+     *
+     * @param controls - The custom camera controls implementation.
+     *
+     * @throws {MapillaryError} When camera controls attached
+     * are already attached to the viewer.
+     */
+    attachCustomCameraControls(controls) {
+        this._customCameraControls.attach(controls, this);
+    }
+    /**
+     * Deactivate the combined panning functionality.
+     *
+     * @description Deactivating the combined panning functionality
+     * could be needed in scenarios involving sequence only navigation.
+     */
+    deactivateCombinedPanning() {
+        this._navigator.panService.disable();
+    }
+    /**
+     * Deactivate a component.
+     *
+     * @param {ComponentName | FallbackComponentName} name - Name
+     * of component which become inactive.
+     *
+     * @example
+     * ```js
+     * viewer.deactivateComponent("pointer");
+     * ```
+     */
+    deactivateComponent(name) {
+        this._componentController.deactivate(name);
+    }
+    /**
+     * Deactivate the cover (activates all components marked as active).
+     */
+    deactivateCover() {
+        this._componentController.deactivateCover();
+    }
+    /**
+     * Detach a previously attached custom camera control
+     * instance from the viewer.
+     *
+     * @description If no custom camera control instance
+     * has previously been attached, calling this method
+     * has no effect.
+     *
+     * Already attached custom camera controls need to
+     * be detached before attaching another custom camera
+     * control instance.
+     */
+    detachCustomCameraControls() {
+        this._customCameraControls.detach(this);
+    }
+    fire(type, event) {
+        super.fire(type, event);
+    }
+    /**
+     * Get the bearing of the current viewer camera.
+     *
+     * @description The bearing depends on how the camera
+     * is currently rotated and does not correspond
+     * to the compass angle of the current image if the view
+     * has been panned.
+     *
+     * Bearing is measured in degrees clockwise with respect to
+     * north.
+     *
+     * @returns {Promise<number>} Promise to the bearing
+     * of the current viewer camera.
+     *
+     * @example
+     * ```js
+     * viewer.getBearing().then(b => { console.log(b); });
+     * ```
+     */
+    getBearing() {
+        return new Promise((resolve, reject) => {
+            this._container.renderService.bearing$.pipe(first())
+                .subscribe((bearing) => {
+                resolve(bearing);
+            }, (error) => {
+                reject(error);
+            });
+        });
+    }
+    /**
+     * Get the viewer's camera control mode.
+     *
+     * @description The camera control mode determines
+     * how the camera is controlled when the viewer
+     * recieves pointer and keyboard input.
+     *
+     * @returns {CameraControls} controls - Camera control mode.
+     *
+     * @example
+     * ```js
+     * viewer.getCameraControls().then(c => { console.log(c); });
+     * ```
+     */
+    getCameraControls() {
+        return new Promise((resolve, reject) => {
+            this._navigator.stateService.state$.pipe(first())
+                .subscribe((state) => {
+                switch (state) {
+                    case State.Custom:
+                        resolve(CameraControls.Custom);
+                        break;
+                    case State.Earth:
+                        resolve(CameraControls.Earth);
+                        break;
+                    default:
+                        resolve(CameraControls.Street);
+                        break;
+                }
+            }, (error) => {
+                reject(error);
+            });
+        });
+    }
+    /**
+     * Returns the viewer's canvas element.
+     *
+     * @description This is the element onto which the viewer renders
+     * the WebGL content.
+     *
+     * @returns {HTMLCanvasElement} The viewer's canvas element, or
+     * null or not initialized.
+     */
+    getCanvas() {
+        return this._container.canvas;
+    }
+    /**
+     * Returns the HTML element containing the viewer's canvas element.
+     *
+     * @description This is the element to which event bindings for viewer
+     * interactivity (such as panning and zooming) are attached.
+     *
+     * @returns {HTMLDivElement} The container for the viewer's
+     * canvas element.
+     */
+    getCanvasContainer() {
+        return this._container.canvasContainer;
+    }
+    /**
+     * Get the basic coordinates of the current image that is
+     * at the center of the viewport.
+     *
+     * @description Basic coordinates are 2D coordinates on the [0, 1] interval
+     * and have the origin point, (0, 0), at the top left corner and the
+     * maximum value, (1, 1), at the bottom right corner of the original
+     * image.
+     *
+     * @returns {Promise<number[]>} Promise to the basic coordinates
+     * of the current image at the center for the viewport.
+     *
+     * @example
+     * ```js
+     * viewer.getCenter().then(c => { console.log(c); });
+     * ```
+     */
+    getCenter() {
+        return new Promise((resolve, reject) => {
+            this._navigator.stateService.getCenter()
+                .subscribe((center) => {
+                resolve(center);
+            }, (error) => {
+                reject(error);
+            });
+        });
+    }
+    /**
+     * Get a component.
+     *
+     * @param {string} name - Name of component.
+     * @returns {Component} The requested component.
+     *
+     * @example
+     * ```js
+     * var pointerComponent = viewer.getComponent("pointer");
+     * ```
+     */
+    getComponent(name) {
+        return this._componentController.get(name);
+    }
+    /**
+     * Returns the viewer's containing HTML element.
+     *
+     * @returns {HTMLElement} The viewer's container.
+     */
+    getContainer() {
+        return this._container.container;
+    }
+    /**
+     * Get the viewer's current vertical field of view.
+     *
+     * @description The vertical field of view rendered on the viewer canvas
+     * measured in degrees.
+     *
+     * @returns {Promise<number>} Promise to the current field of view
+     * of the viewer camera.
+     *
+     * @example
+     * ```js
+     * viewer.getFieldOfView().then(fov => { console.log(fov); });
+     * ```
+     */
+    getFieldOfView() {
+        return new Promise((resolve, reject) => {
+            this._container.renderService.renderCamera$.pipe(first())
+                .subscribe((rc) => {
+                resolve(rc.perspective.fov);
+            }, (error) => {
+                reject(error);
+            });
+        });
+    }
+    /**
+     * Get the viewer's current image.
+     *
+     * @returns {Promise<Image>} Promise to the current image.
+     *
+     * @example
+     * ```js
+     * viewer.getImage().then(image => { console.log(image.id); });
+     * ```
+     */
+    getImage() {
+        return new Promise((resolve, reject) => {
+            this._navigator.stateService.currentImage$.pipe(first())
+                .subscribe((image) => { resolve(image); }, (error) => { reject(error); });
+        });
+    }
+    /**
+     * Get the viewer's current point of view.
+     *
+     * @returns {Promise<PointOfView>} Promise to the current point of view
+     * of the viewer camera.
+     *
+     * @example
+     * ```js
+     * viewer.getPointOfView().then(pov => { console.log(pov); });
+     * ```
+     */
+    getPointOfView() {
+        return new Promise((resolve, reject) => {
+            combineLatest(this._container.renderService.renderCamera$, this._container.renderService.bearing$).pipe(first())
+                .subscribe(([rc, bearing]) => {
+                resolve({
+                    bearing: bearing,
+                    tilt: rc.getTilt(),
+                });
+            }, (error) => {
+                reject(error);
+            });
+        });
+    }
+    /**
+     * Get the viewer's current position
+     *
+     * @returns {Promise<LngLat>} Promise to the viewers's current
+     * position.
+     *
+     * @example
+     * ```js
+     * viewer.getPosition().then(pos => { console.log(pos); });
+     * ```
+     */
+    getPosition() {
+        return new Promise((resolve, reject) => {
+            combineLatest(this._container.renderService.renderCamera$, this._navigator.stateService.reference$).pipe(first())
+                .subscribe(([render, reference]) => {
+                resolve(this._observer.projection.cameraToLngLat(render, reference));
+            }, (error) => {
+                reject(error);
+            });
+        });
+    }
+    /**
+     * Get the image's current zoom level.
+     *
+     * @returns {Promise<number>} Promise to the viewers's current
+     * zoom level.
+     *
+     * @example
+     * ```js
+     * viewer.getZoom().then(z => { console.log(z); });
+     * ```
+     */
+    getZoom() {
+        return new Promise((resolve, reject) => {
+            this._navigator.stateService.getZoom()
+                .subscribe((zoom) => {
+                resolve(zoom);
+            }, (error) => {
+                reject(error);
+            });
+        });
+    }
+    /**
+     * Check if a custom renderer has been added to the viewer's
+     * rendering pipeline.
+     *
+     * @param {string} id - Unique id of the custom renderer.
+     * @returns {boolean} Value indicating whether the customer
+     * renderer has been added.
+     */
+    hasCustomRenderer(rendererId) {
+        return this._customRenderer.has(rendererId);
+    }
+    /**
+     * Navigate in a given direction.
+     *
+     * @param {NavigationDirection} direction - Direction in which which to move.
+     * @returns {Promise<Image>} Promise to the image that was navigated to.
+     * @throws If the current image does not have the edge direction
+     * or the edges has not yet been cached.
+     * @throws Propagates any IO errors to the caller.
+     * @throws When viewer is not navigable.
+     * @throws {@link CancelMapillaryError} When a subsequent move request
+     * is made before the move dir call has completed.
+     *
+     * @example
+     * ```js
+     * viewer.moveDir(NavigationDirection.Next).then(
+     *     image => { console.log(image); },
+     *     error => { console.error(error); });
+     * ```
+     */
+    moveDir(direction) {
+        const moveDir$ = this.isNavigable ?
+            this._navigator.moveDir$(direction) :
+            throwError(new Error("Calling moveDir is not supported when viewer is not navigable."));
+        return new Promise((resolve, reject) => {
+            moveDir$.subscribe((image) => {
+                resolve(image);
+            }, (error) => {
+                reject(error);
+            });
+        });
+    }
+    /**
+     * Navigate to a given image id.
+     *
+     * @param {string} imageId - Id of the image to move to.
+     * @returns {Promise<Image>} Promise to the image that was navigated to.
+     * @throws Propagates any IO errors to the caller.
+     * @throws When viewer is not navigable.
+     * @throws {@link CancelMapillaryError} When a subsequent
+     * move request is made before the move to id call has completed.
+     *
+     * @example
+     * ```js
+     * viewer.moveTo("<my-image-id>").then(
+     *     image => { console.log(image); },
+     *     error => { console.error(error); });
+     * ```
+     */
+    moveTo(imageId) {
+        const moveTo$ = this.isNavigable ?
+            this._navigator.moveTo$(imageId) :
+            throwError(new Error("Calling moveTo is not supported when viewer is not navigable."));
+        return new Promise((resolve, reject) => {
+            moveTo$.subscribe((image) => {
+                resolve(image);
+            }, (error) => {
+                reject(error);
+            });
+        });
+    }
+    off(type, handler) {
+        super.off(type, handler);
+    }
+    on(type, handler) {
+        super.on(type, handler);
+    }
+    /**
+     * Project geodetic coordinates to canvas pixel coordinates.
+     *
+     * @description The geodetic coordinates may not always correspond to pixel
+     * coordinates, e.g. if the geodetic coordinates have a position behind the
+     * viewer camera. In the case of no correspondence the returned value will
+     * be `null`.
+     *
+     * If the distance from the viewer camera position to the provided
+     * longitude-latitude is more than 1000 meters `null` will be returned.
+     *
+     * The projection is performed from the ground plane, i.e.
+     * the altitude with respect to the ground plane for the geodetic
+     * point is zero.
+     *
+     * Note that whenever the camera moves, the result of the method will be
+     * different.
+     *
+     * @param {LngLat} lngLat - Geographical coordinates to project.
+     * @returns {Promise<Array<number>>} Promise to the pixel coordinates corresponding
+     * to the lngLat.
+     *
+     * @example
+     * ```js
+     * viewer.project({ lat: 0, lng: 0 })
+     *     .then(pixelPoint => {
+     *          if (!pixelPoint) {
+     *              console.log("no correspondence");
+     *          }
+     *
+     *          console.log(pixelPoint);
+     *     });
+     * ```
+     */
+    project(lngLat) {
+        return new Promise((resolve, reject) => {
+            this._observer.project$(lngLat)
+                .subscribe((pixelPoint) => {
+                resolve(pixelPoint);
+            }, (error) => {
+                reject(error);
+            });
+        });
+    }
+    /**
+     * Project basic image coordinates for the current image to canvas pixel
+     * coordinates.
+     *
+     * @description The basic image coordinates may not always correspond to a
+     * pixel point that lies in the visible area of the viewer container. In the
+     * case of no correspondence the returned value can be `null`.
+     *
+     *
+     * @param {Array<number>} basicPoint - Basic images coordinates to project.
+     * @returns {Promise<Array<number>>} Promise to the pixel coordinates corresponding
+     * to the basic image point.
+     *
+     * @example
+     * ```js
+     * viewer.projectFromBasic([0.3, 0.7])
+     *     .then(pixelPoint => { console.log(pixelPoint); });
+     * ```
+     */
+    projectFromBasic(basicPoint) {
+        return new Promise((resolve, reject) => {
+            this._observer.projectBasic$(basicPoint)
+                .subscribe((pixelPoint) => {
+                resolve(pixelPoint);
+            }, (error) => {
+                reject(error);
+            });
+        });
+    }
+    /**
+     * Clean up and release all internal resources associated with
+     * this viewer.
+     *
+     * @description This includes DOM elements, event bindings, and
+     * WebGL resources.
+     *
+     * Use this method when you are done using the viewer and wish to
+     * ensure that it no longer consumes browser resources. Afterwards,
+     * you must not call any other methods on the viewer.
+     *
+     * @fires remove
+     *
+     * @example
+     * ```js
+     * viewer.remove();
+     * ```
+     */
+    remove() {
+        this._customRenderer.dispose(this);
+        this._customCameraControls.dispose(this);
+        this._observer.dispose();
+        this._componentController.remove();
+        this._navigator.dispose();
+        this._container.remove();
+        const type = "remove";
+        const event = {
+            target: this,
+            type,
+        };
+        this.fire(type, event);
+    }
+    /**
+     * Remove a custom renderer from the viewer's rendering pipeline.
+     *
+     * @param id - Unique id of the custom renderer.
+     */
+    removeCustomRenderer(rendererId) {
+        this._customRenderer.remove(rendererId, this);
+    }
+    /**
+     * Detect the viewer's new width and height and resize it
+     * manually.
+     *
+     * @description The components will also detect the viewer's
+     * new size and resize their rendered elements if needed.
+     *
+     * When the {@link ViewerOptions.trackResize} option is
+     * set to true, the viewer will automatically resize
+     * when the browser window is resized. If any other
+     * custom behavior is preferred, the option should be set
+     * to false and the {@link Viewer.resize} method should
+     * be called on demand.
+     *
+     * @example
+     * ```js
+     * viewer.resize();
+     * ```
+     */
+    resize() {
+        this._container.renderService.resize$.next();
+    }
+    /**
+     * Set the viewer's camera control mode.
+     *
+     * @description The camera control mode determines
+     * how the camera is controlled when the viewer
+     * recieves pointer and keyboard input.
+     *
+     * Changing the camera control mode is not possible
+     * when the slider component is active and attempts
+     * to do so will be ignored.
+     *
+     * @param {CameraControls} controls - Camera control mode.
+     *
+     * @example
+     * ```js
+     * viewer.setCameraControls(CameraControls.Street);
+     * ```
+     */
+    setCameraControls(controls) {
+        const state = cameraControlsToState(controls);
+        if (state === State.Traversing) {
+            this._navigator.stateService.traverse();
+        }
+        else if (state === State.Earth) {
+            this._navigator.stateService.earth();
+        }
+        else if (state === State.Custom) {
+            this._navigator.stateService.custom();
+        }
+        else {
+            console.warn(`Unsupported camera control transition (${controls})`);
+        }
+    }
+    /**
+     * Set the basic coordinates of the current image to be in the
+     * center of the viewport.
+     *
+     * @description Basic coordinates are 2D coordinates on the [0, 1] interval
+     * and has the origin point, (0, 0), at the top left corner and the
+     * maximum value, (1, 1), at the bottom right corner of the original
+     * image.
+     *
+     * @param {number[]} The basic coordinates of the current
+     * image to be at the center for the viewport.
+     *
+     * @example
+     * ```js
+     * viewer.setCenter([0.5, 0.5]);
+     * ```
+     */
+    setCenter(center) {
+        this._navigator.stateService.setCenter(center);
+    }
+    /**
+     * Set the viewer's current vertical field of view.
+     *
+     * @description Sets the vertical field of view rendered
+     * on the viewer canvas measured in degrees. The value
+     * will be clamped to be able to set a valid zoom level
+     * based on the projection model of the current image and
+     * the viewer's current render mode.
+     *
+     * @param {number} fov - Vertical field of view in degrees.
+     *
+     * @example
+     * ```js
+     * viewer.setFieldOfView(45);
+     * ```
+     */
+    setFieldOfView(fov) {
+        this._container.renderService.renderCamera$.pipe(first())
+            .subscribe((rc) => {
+            const zoom = rc.fovToZoom(fov);
+            this._navigator.stateService.setZoom(zoom);
+        });
+    }
+    /**
+     * Set the filter selecting images to use when calculating
+     * the spatial edges.
+     *
+     * @description The following filter types are supported:
+     *
+     * Comparison
+     *
+     * `["==", key, value]` equality: `image[key] = value`
+     *
+     * `["!=", key, value]` inequality: `image[key] ≠ value`
+     *
+     * `["<", key, value]` less than: `image[key] < value`
+     *
+     * `["<=", key, value]` less than or equal: `image[key] ≤ value`
+     *
+     * `[">", key, value]` greater than: `image[key] > value`
+     *
+     * `[">=", key, value]` greater than or equal: `image[key] ≥ value`
+     *
+     * Set membership
+     *
+     * `["in", key, v0, ..., vn]` set inclusion: `image[key] ∈ {v0, ..., vn}`
+     *
+     * `["!in", key, v0, ..., vn]` set exclusion: `image[key] ∉ {v0, ..., vn}`
+     *
+     * Combining
+     *
+     * `["all", f0, ..., fn]` logical `AND`: `f0 ∧ ... ∧ fn`
+     *
+     * A key must be a string that identifies a property name of a
+     * simple {@link Image} property, i.e. a key of the {@link FilterKey}
+     * type. A value must be a string, number, or
+     * boolean. Strictly-typed comparisons are used. The values
+     * `f0, ..., fn` of the combining filter must be filter expressions.
+     *
+     * Clear the filter by setting it to null or empty array.
+     *
+     * Commonly used filter properties (see the {@link Image} class
+     * documentation for a full list of properties that can be used
+     * in a filter) are shown the the example code.
+     *
+     * @param {FilterExpression} filter - The filter expression.
+     * @returns {Promise<void>} Promise that resolves after filter is applied.
+     *
+     * @example
+     * ```js
+     * // Examples
+     * viewer.setFilter(["==", "cameraType", "spherical"]);
+     * viewer.setFilter([">=", "capturedAt", <my-time-stamp>]);
+     * viewer.setFilter(["in", "sequenceId", "<sequence-id-1>", "<sequence-id-2>"]);
+     * ```
+     */
+    setFilter(filter) {
+        return new Promise((resolve, reject) => {
+            this._navigator.setFilter$(filter)
+                .subscribe(() => {
+                resolve(undefined);
+            }, (error) => {
+                reject(error);
+            });
+        });
+    }
+    /**
+     * Set the viewer's render mode.
+     *
+     * @param {RenderMode} renderMode - Render mode.
+     *
+     * @example
+     * ```js
+     * viewer.setRenderMode(RenderMode.Letterbox);
+     * ```
+     */
+    setRenderMode(renderMode) {
+        this._container.renderService.renderMode$.next(renderMode);
+    }
+    /**
+     * Set the viewer's transition mode.
+     *
+     * @param {TransitionMode} transitionMode - Transition mode.
+     *
+     * @example
+     * ```js
+     * viewer.setTransitionMode(TransitionMode.Instantaneous);
+     * ```
+     */
+    setTransitionMode(transitionMode) {
+        this._navigator.stateService.setTransitionMode(transitionMode);
+    }
+    /**
+     * Set an access token for authenticated API requests of protected
+     * resources.
+     *
+     * The token may be a user access token or a client access token.
+     *
+     * @description When the supplied user token is null or undefined,
+     * any previously set user bearer token will be cleared and the
+     * viewer will make unauthenticated requests.
+     *
+     * Calling setAccessToken aborts all outstanding move requests.
+     * The promises of those move requests will be rejected with a
+     * {@link CancelMapillaryError} the rejections need to be caught.
+     *
+     * Calling setAccessToken also resets the complete viewer cache
+     * so it should not be called repeatedly.
+     *
+     * @param {string} [accessToken] accessToken - Optional user
+     * access token or client access token.
+     * @returns {Promise<void>} Promise that resolves after token
+     * is set.
+     *
+     * @throws When viewer is not navigable.
+     *
+     * @example
+     * ```js
+     * viewer.setAccessToken("<my access token>")
+     *     .then(() => { console.log("user token set"); });
+     * ```
+     */
+    setAccessToken(accessToken) {
+        const setAccessToken$ = this.isNavigable ?
+            this._navigator.setAccessToken$(accessToken) :
+            throwError(new Error("Calling setAccessToken is not supported when viewer is not navigable."));
+        return new Promise((resolve, reject) => {
+            setAccessToken$
+                .subscribe(() => {
+                resolve(undefined);
+            }, (error) => {
+                reject(error);
+            });
+        });
+    }
+    /**
+     * Set the image's current zoom level.
+     *
+     * @description Possible zoom level values are on the [0, 3] interval.
+     * Zero means zooming out to fit the image to the view whereas three
+     * shows the highest level of detail.
+     *
+     * @param {number} The image's current zoom level.
+     *
+     * @example
+     * ```js
+     * viewer.setZoom(2);
+     * ```
+     */
+    setZoom(zoom) {
+        this._navigator.stateService.setZoom(zoom);
+    }
+    /**
+     * Trigger the rendering of a single frame.
+     *
+     * @description Use this method with custom renderers to
+     * force the viewer to rerender when the custom content
+     * changes. Calling this multiple times before the next
+     * frame is rendered will still result in only a single
+     * frame being rendered.
+     */
+    triggerRerender() {
+        this._container.glRenderer.triggerRerender();
+    }
+    /**
+     * Unproject canvas pixel coordinates to geodetic
+     * coordinates.
+     *
+     * @description The pixel point may not always correspond to geodetic
+     * coordinates. In the case of no correspondence the returned value will
+     * be `null`.
+     *
+     * The unprojection to a lngLat will be performed towards the ground plane, i.e.
+     * the altitude with respect to the ground plane for the returned lngLat is zero.
+     *
+     * @param {Array<number>} pixelPoint - Pixel coordinates to unproject.
+     * @returns {Promise<LngLat>} Promise to the lngLat corresponding to the pixel point.
+     *
+     * @example
+     * ```js
+     * viewer.unproject([100, 100])
+     *     .then(lngLat => { console.log(lngLat); });
+     * ```
+     */
+    unproject(pixelPoint) {
+        return new Promise((resolve, reject) => {
+            this._observer.unproject$(pixelPoint)
+                .subscribe((lngLat) => {
+                resolve(lngLat);
+            }, (error) => {
+                reject(error);
+            });
+        });
+    }
+    /**
+     * Unproject canvas pixel coordinates to basic image coordinates for the
+     * current image.
+     *
+     * @description The pixel point may not always correspond to basic image
+     * coordinates. In the case of no correspondence the returned value will
+     * be `null`.
+     *
+     * @param {Array<number>} pixelPoint - Pixel coordinates to unproject.
+     * @returns {Promise<LngLat>} Promise to the basic coordinates corresponding
+     * to the pixel point.
+     *
+     * @example
+     * ```js
+     * viewer.unprojectToBasic([100, 100])
+     *     .then(basicPoint => { console.log(basicPoint); });
+     * ```
+     */
+    unprojectToBasic(pixelPoint) {
+        return new Promise((resolve, reject) => {
+            this._observer.unprojectBasic$(pixelPoint)
+                .subscribe((basicPoint) => {
+                resolve(basicPoint);
+            }, (error) => {
+                reject(error);
+            });
+        });
+    }
+}
+
+/**
+ * Internal bootstrap
+ *
+ * This is a workaround to make the CommonJS unit testing
+ * work with Jest. Once Jest/Node supports ES6 modules
+ * fully this should be removed. GeoRBush and UnitBezier
+ * are registered here only to avoid loading them during
+ * unit tests.
+ */
+Graph.register(GeoRBush);
+MarkerSet.register(GeoRBush);
+TraversingState.register(unitbezier);
+ComponentService.registerCover(CoverComponent);
+ComponentService.register(AttributionComponent);
+ComponentService.register(BearingComponent);
+ComponentService.register(CacheComponent);
+ComponentService.register(DirectionComponent);
+ComponentService.register(ImageComponent);
+ComponentService.register(KeyboardComponent);
+ComponentService.register(MarkerComponent);
+ComponentService.register(PointerComponent);
+ComponentService.register(PopupComponent);
+ComponentService.register(SequenceComponent);
+ComponentService.register(SliderComponent);
+ComponentService.register(SpatialComponent);
+ComponentService.register(TagComponent);
+ComponentService.register(ZoomComponent);
+ComponentService.register(ImageFallbackComponent);
+ComponentService.register(NavigationFallbackComponent);
+
+export { Alignment, ArgumentMapillaryError, BearingComponent, CacheComponent, CameraControls, CameraVisualizationMode, CancelMapillaryError, CircleMarker, Component, ComponentSize, DataProviderBase, DirectionComponent, DragPanHandler, ExtremePointTag, Geometry, GeometryProviderBase, GeometryTagError, GraphDataProvider, GraphMapillaryError, Image$1 as Image, KeyPlayHandler, KeySequenceNavigationHandler, KeySpatialNavigationHandler, KeyZoomHandler, KeyboardComponent, MapillaryError, Marker, MarkerComponent, NavigationDirection, OriginalPositionMode, OutlineTag, PointGeometry, PointerComponent, PointsGeometry, PolygonGeometry, Popup, PopupComponent, RectGeometry, RenderMode, RenderPass, S2GeometryProvider, ScrollZoomHandler, SequenceComponent, SimpleMarker, SliderComponent, SliderConfigurationMode, SpatialComponent, SpotTag, Tag, TagComponent, TagDomain, TagMode, TouchZoomHandler, TransitionMode, VertexGeometry, Viewer, ZoomComponent, decompress, enuToGeodetic, fetchArrayBuffer, geodeticToEnu, isFallbackSupported, isSupported, readMeshPbf };
+//# sourceMappingURL=mapillary.module.js.map